[
  {
    "path": ".dockerignore",
    "content": "/target\n/node_modules\n**/node_modules\n\n"
  },
  {
    "path": ".envrc",
    "content": "use flake .\n"
  },
  {
    "path": ".eslintrc.json",
    "content": "{\n    \"env\": {\n        \"browser\": true,\n        \"es2021\": true,\n        \"mocha\": true,\n        \"jest/globals\": true,\n        \"node\": true\n    },\n    \"extends\": [\n        \"plugin:react/recommended\",\n        \"plugin:import/recommended\",\n        \"prettier\"\n    ],\n    \"parser\": \"@typescript-eslint/parser\",\n    \"parserOptions\": {\n        \"ecmaFeatures\": {\n            \"jsx\": true\n        },\n        \"ecmaVersion\": 12,\n        \"sourceType\": \"module\"\n    },\n    \"plugins\": [\n        \"react\",\n        \"jest\",\n        \"prettier\"\n    ],\n    \"settings\": {\n        \"react\": {\n            \"version\": \"18\"\n        },\n        \"import/resolver\": {\n            \"typescript\": {}\n        }\n    },\n    \"rules\": {\n        \"space-before-function-paren\": [\"error\", {\"anonymous\": \"always\", \"named\": \"never\", \"asyncArrow\": \"always\"}],\n        \"curly\": [\"error\", \"multi-line\"],\n        \"no-new\": \"off\",\n        \"no-use-before-define\": \"off\",\n        \"no-useless-constructor\": \"off\",\n        \"react/prop-types\": \"off\",\n        \"camelcase\": \"off\",\n        \"prettier/prettier\": [\"error\", {\n            \"singleQuote\": true,\n            \"semi\": true,\n            \"trailingComma\": \"all\",\n            \"tabWidth\": 2,\n            \"bracketSameLine\": false\n        }],\n        \"import/order\": \"error\",\n        \"no-undef\": \"error\",\n        \"import/no-unresolved\": \"off\",\n        \"react/react-in-jsx-scope\": \"off\"\n    },\n    \"globals\": {\n        \"React\": true,\n        \"JSX\": true\n    }\n}\n"
  },
  {
    "path": ".gitattributes",
    "content": "apps/desktop/src-tauri/model/vocab.txt filter=lfs diff=lfs merge=lfs -text\napps/desktop/src-tauri/model/model.onnx filter=lfs diff=lfs merge=lfs -text\napps/desktop/src-tauri/model/special_tokens_map.json filter=lfs diff=lfs merge=lfs -text\napps/desktop/src-tauri/model/tokenizer.json filter=lfs diff=lfs merge=lfs -text\napps/desktop/src-tauri/model/tokenizer_config.json filter=lfs diff=lfs merge=lfs -text\napps/desktop/src-tauri/bin/qdrant-x86_64-apple-darwin filter=lfs diff=lfs merge=lfs -text\napps/desktop/src-tauri/bin/qdrant-aarch64-apple-darwin filter=lfs diff=lfs merge=lfs -text\napps/desktop/src-tauri/bin/qdrant-x86_64-unknown-linux-gnu filter=lfs diff=lfs merge=lfs -text\napps/desktop/src-tauri/bin/qdrant-x86_64-pc-windows-msvc.exe filter=lfs diff=lfs merge=lfs -text\napps/desktop/src-tauri/model/ggml/ggml-model-q4_0.bin filter=lfs diff=lfs merge=lfs -text\napps/desktop/src-tauri/model/ggml/tokenizer.json filter=lfs diff=lfs merge=lfs -text\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a bug report for bloop.\nlabels: bug\n\n---\n<!--\nThank you for filing a bug report! 🐛 Please provide a short summary of the bug,\nalong with any information you feel relevant to replicating the bug.\n-->\n\n**Describe the bug**\nA clear and concise description of what the bug is. If appropriate add a `frontend` or `backend` label.\n\n**Expected behavior**\nWhat did you expect to happen?\n\n**To Reproduce**\nHow can we reproduce the bug? Helpful information could include:\n- Details about your system\n- The data that you had indexed\n- The query that you ran\n\n**Screenshots or output**\nIf applicable, add screenshots to help illustrate the bug.\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\nlabels: feature\n---\n\n<!--\nThank you for filing a feature request! 🚧 Please provide a short summary of the feature,\nalong with a justification as to why you think it's important.\n-->\n\n**What's the problem?**\nA clear and concise description of the problem. For example, I find... frustrating\n\n**What's the solution?**\nA clear and concise description of what you'd like to happen.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/request-for-comments.md",
    "content": "---\nname: Request for comments\nabout: An engineering/feature proposal with in-depth details\nlabels: rfc\n---\n\n**High level details**\nA short summary of the expected outcome and context.\n\n**Current situation**\nSome details about what we're currently doing.\nFor example:\n * how it's insufficient\n * how it's flawed\n * what were the original motivations\n * why we need to change this\n\n**Proposal**\nDetails about the proposal, and how to implement it.\nFor example:\n * high-level behavior\n * what will/can break\n * what changes are necessary\n\n**Next steps**\nAre there any next steps after we implement these changes?"
  },
  {
    "path": ".github/workflows/build-on-pr-command.yml",
    "content": "name: Build Bloop container with latest PR commit tag on build command\n\non:\n  issue_comment:\n    types: [created]\n\njobs:\n  debug:\n    runs-on: ubuntu-latest\n    steps:\n    - name: $github\n      run:   echo \"$GITHUB_CONTEXT\"\n      env:\n       GITHUB_CONTEXT: ${{ toJson(github) }}\n\n  permissions:\n    runs-on: ubuntu-latest\n    name: Validate user is the member of BloopAI organization\n    if: github.event.issue.pull_request && contains(github.event.comment.body, '/build')\n    outputs:\n      is-member: ${{ steps.membership.outputs.is-member }}\n    steps:\n      - name: Validation\n        id: membership\n        env:\n          ACTOR: ${{ github.triggering_actor }}\n        run: |\n          members=$(curl -L \\\n            -H \"Accept: application/vnd.github+json\" \\\n            -H \"Authorization: Bearer ${{ secrets.BLOOP_DEVOPS_PAT}}\"\\\n            -H \"X-GitHub-Api-Version: 2022-11-28\" \\\n            https://api.github.com/orgs/BloopAI/members | jq -r \".[] .login\")\n          for member in $(echo ${members}); do \n            if [[ $member = $ACTOR ]]; then \n              echo \"is-member=true\" >> $GITHUB_OUTPUT\n            fi\n          done\n\n  build_tag:\n    runs-on: ubuntu-latest\n    name: Run container build on comment\n    needs: [permissions]\n    if: github.event.issue.pull_request && contains(github.event.comment.body, '/build') && needs.permissions.outputs.is-member == 'true'\n    outputs:\n      tag: build-${{ steps.comment-branch.outputs.head_sha }}\n      ref: ${{ steps.comment-branch.outputs.head_ref }}\n    steps:\n      - name: Get PR branch\n        uses: xt0rted/pull-request-comment-branch@v1\n        id: comment-branch\n\n      - name: Checkout PR branch\n        uses: actions/checkout@v4\n        with:\n          ref: ${{ steps.comment-branch.outputs.head_ref }}\n\n  build_and_push:\n    uses: BloopAI/workflows/.github/workflows/build-container.yml@main\n    if: needs.permissions.outputs.is-member == 'true'\n    needs: [permissions, build_tag]\n    with:\n      repository: bloop\n      tag: ${{ needs.build_tag.outputs.tag }}\n      runner: ubuntu-latest\n    secrets:\n      awsRegion: ${{ secrets.AWS_REGION }}\n      awsAccountID: ${{ secrets.AWS_ACCOUNT_ID }}\n      slackBuildWebhook: ${{ secrets.SLACK_BUILD_WEBHOOK }}\n      build-args: |\n        SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}\n        SENTRY_RELEASE_VERSION=${{ needs.build_tag.outputs.tag }}\n\n  report_status:\n    runs-on: ubuntu-latest\n    name: Report status of the build\n    needs: [permissions, build_tag, build_and_push]\n    if: always() && needs.permissions.outputs.is-member == 'true'\n    steps:\n      - name: pr\n        id: pr\n        run: |\n          PR_NUMBER=$(echo $GITHUB_REF | awk 'BEGIN { FS = \"/\" } ; { print $3 }')\n          echo \"number=${PR_NUMBER}\" >> ${GITHUB_OUTPUT}\n\n      - name: Comment failure build\n        if: ${{ contains(needs.*.result, 'failure') }}\n        uses: thollander/actions-comment-pull-request@v2\n        with:\n          message: |\n            :red_circle: Bloop container with `${{ needs.build_tag.outputs.tag }}` tag failed!\n            :link: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\n          pr_number: ${{ steps.pr.outputs.number }}\n\n      - name: Comment success build\n        if: ${{ !contains(needs.*.result, 'failure') }}\n        uses: thollander/actions-comment-pull-request@v2\n        with:\n          message: |\n            :green_circle: Bloop container with `${{ needs.build_tag.outputs.tag }}` tag is ready!\n            :link: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\n          pr_number: ${{ steps.pr.outputs.number }}\n"
  },
  {
    "path": ".github/workflows/build-on-pr.yml",
    "content": "name: Build and push docker container\non: workflow_dispatch\n\njobs:\n  build_and_push:\n    uses: BloopAI/workflows/.github/workflows/build-container.yml@main\n    with:\n      repository: bloop\n      tag: build-${{ github.sha }}\n      runner: ubuntu-latest\n    secrets:\n      awsRegion: ${{ secrets.AWS_REGION }}\n      awsAccountID: ${{ secrets.AWS_ACCOUNT_ID }}\n      slackBuildWebhook: ${{ secrets.SLACK_BUILD_WEBHOOK }}\n      build-args: |\n        SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}\n        SENTRY_RELEASE_VERSION=${{ github.sha }}\n\n  validate_helm:\n    uses: BloopAI/reusable-workflows/.github/workflows/validate-helm-chart.yml@main\n    with:\n      path: helm/bloop\n    secrets:\n      slackBuildWebhook: ${{ secrets.SLACK_BUILD_WEBHOOK }}\n"
  },
  {
    "path": ".github/workflows/build-on-release.yml",
    "content": "name: Build&Push bloop docker container image with release tag\non:\n  release:\n    types: [published, prereleased]\n\njobs:\n  build_and_push:\n    uses: BloopAI/workflows/.github/workflows/build-container.yml@main\n    with:\n      repository: bloop\n      tag: ${{ github.event.release.tag_name }}\n      runner: ubuntu-latest\n    secrets:\n      awsRegion: ${{ secrets.AWS_REGION }}\n      awsAccountID: ${{ secrets.AWS_ACCOUNT_ID }}\n      slackBuildWebhook: ${{ secrets.SLACK_BUILD_WEBHOOK }}\n      build-args: |\n        SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}\n        SENTRY_RELEASE_VERSION=${{ github.event.release.tag_name }}\n"
  },
  {
    "path": ".github/workflows/client-test.yml",
    "content": "name: Client Tests\n\non:\n  pull_request:\n    types: [opened, synchronize]\n    branches: [main]\n    paths:\n      - \"client/**\"\n      - \".github/workflows/client**\"\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - run: 'echo \"No checks required\" '\n  fmt-clippy-build:\n    runs-on: ubuntu-latest\n    steps:\n      - run: 'echo \"No checks required\" '\n\n  build-client:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Use Node.js\n        uses: actions/setup-node@v3\n        with:\n          node-version: 16\n          cache: \"npm\"\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Run lint\n        run: npm run lint\n\n      - name: Run type-check\n        run: npm run client-type-check\n\n      - name: Run tests\n        run: npm --prefix client run test\n"
  },
  {
    "path": ".github/workflows/dependencies.yml",
    "content": "name: Dependency matrix\t\n\non:\t\n  workflow_dispatch:\n  pull_request:\t\n    branches: [main]\t\n    paths:\t\n      - \"flake.nix\"\t\n      - \"flake.lock\"\t\n      - \".github/workflows/dependencies.yml\"\t\n\njobs:\t\n  qdrant-rustup:\t\n    strategy:\t\n      fail-fast: false\t\n      matrix:\t\n        package: [ qdrant ]\t\n        target: [\t\n          x86_64-unknown-linux-gnu,\t\n          x86_64-apple-darwin,\t\n          aarch64-apple-darwin,\t\n          x86_64-pc-windows-msvc\n        ]\t\n\n        include:\t\n          - target: x86_64-unknown-linux-gnu\t\n            os: ubuntu-20.04\n            cross: false\n\n          - target: x86_64-apple-darwin\t\n            os: macos-11\n            cross: false\n\n          - target: aarch64-apple-darwin\t\n            os: macos-11\n            cross: true\n            \n          - target: x86_64-pc-windows-msvc\n            os: windows-latest\n            cross: false\n\n    runs-on: ${{ matrix.os }}\t\n    steps:\t\n      - name: Install Rust stable\t\n        uses: dtolnay/rust-toolchain@stable\n        with:\t\n          toolchain: 1.74.0\n          target: ${{ matrix.target }}\t\n\n      - name: Install Protoc\n        uses: arduino/setup-protoc@v2\n        with:\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Build qdrant\t\n        env:\t\n          VERSION: 1.7.1\n        run: |\t\n          cargo install --target ${{ matrix.target }} --git https://github.com/qdrant/qdrant --tag v${{ env.VERSION }} --root . qdrant\t\n      - name: Upload binaries\t\n        uses: actions/upload-artifact@v3\t\n        with:\t\n          name: \"${{ matrix.package }}_${{ matrix.target }}\"\t\n          path: bin\t\n"
  },
  {
    "path": ".github/workflows/dummy.yml",
    "content": "name: dummy\n\non:\n  pull_request:\n    paths-ignore:\n      - \".github/workflows/server**\"\n      - \".github/workflows/client**\"\n      - \"apps/desktop/**\"\n      - \"server/**\"\n      - \"client/**\"\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - run: 'echo \"No checks required\" '\n  fmt-clippy-build:\n    runs-on: ubuntu-latest\n    steps:\n      - run: 'echo \"No checks required\" '\n  build-client:\n    runs-on: ubuntu-latest\n    steps:\n      - run: 'echo \"No checks required\" '\n"
  },
  {
    "path": ".github/workflows/server-test.yml",
    "content": "name: Server Unit Tests\n\non:\n  pull_request:\n    branches: [main]\n    paths:\n      - \"flake.*\"\n      - \"server/**\"\n      - \".github/workflows/server**\"\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  clippy-test:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          lfs: true\n\n      - name: Install Rust stable\n        uses: dtolnay/rust-toolchain@stable\n        with:\n          toolchain: 1.73.0\n          components: rustfmt, clippy\n\n      # https://github.com/actions/cache/blob/main/examples.md#rust---cargo\n      - name: Set up cargo cache\n        uses: actions/cache@v3\n        continue-on-error: false\n        with:\n          path: |\n            ~/.cargo/bin/\n            ~/.cargo/registry/index/\n            ~/.cargo/registry/cache/\n            ~/.cargo/git/db/\n            target/            \n          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}\n          restore-keys: ${{ runner.os }}-cargo-\n\n      - name: Rustfmt\n        run: cargo --locked fmt -p bleep -- --check\n\n      - name: Clippy\n        run: cargo --locked clippy -p bleep\n\n      - name: Tests\n        run: cargo --locked test -p bleep --release"
  },
  {
    "path": ".github/workflows/tauri-release.yml",
    "content": "name: Tauri Release\n\non:\n  workflow_dispatch:\n  pull_request:\n    paths:\n      - \".github/workflows/tauri-release.yml\"\n  release:\n    types: [published]\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build-and-sign-tauri:\n    strategy:\n      fail-fast: false\n      matrix:\n        target:\n          [x86_64-unknown-linux-gnu, x86_64-apple-darwin, aarch64-apple-darwin]\n\n        include:\n          - target: x86_64-unknown-linux-gnu\n            name: ubuntu-20.04\n\n          - target: x86_64-apple-darwin\n            name: macos-11\n\n          - target: aarch64-apple-darwin\n            name: macos-11\n\n\n    runs-on: ${{ matrix.name }}\n    env:\n      ORT_LIB_LOCATION: ${{ github.workspace }}/lib/${{ matrix.target }}/onnxruntime\n\n    steps:\n      - if: matrix.name == 'ubuntu-20.04'\n        uses: pierotofy/set-swap-space@v1.0\n        with:\n          swap-size-gb: 10\n\n      - name: Checkout code\n        uses: actions/checkout@v4\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Create LFS file list\n        run: git lfs ls-files --long | cut -d ' ' -f1 | sort > .lfs-assets-id\n\n      - name: LFS Cache\n        uses: actions/cache@v3\n        with:\n          path: .git/lfs/objects\n          key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}\n          restore-keys: |\n            ${{ runner.os }}-lfs-\n\n      - name: Git LFS Pull\n        run: git lfs install && git lfs pull\n\n      - name: Install Protoc\n        uses: arduino/setup-protoc@v2\n        with:\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n\n      - if: matrix.target == 'x86_64-unknown-linux-gnu'\n        run: sudo apt-get update && sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf\n\n      - name: Use Node.js\n        uses: actions/setup-node@v3\n        with:\n          node-version: 16\n          cache: \"npm\"\n\n      - name: Install app dependencies\n        run: npm ci\n\n      - name: Install Rust stable\n        uses: dtolnay/rust-toolchain@stable\n        with:\n          toolchain: 1.73.0\n          target: ${{ matrix.target }}\n\n      - name: Check if keys exist\n        env:\n          analytics_key: ${{ secrets.ANALYTICS_BE_WRITE_KEY_PROD }}\n          analytics_fe_key: ${{ secrets.ANALYTICS_FE_WRITE_KEY_PROD }}\n          sentry_dsn: ${{ secrets.SENTRY_DSN_BE }}\n          sentry_dsn_fe: ${{ secrets.SENTRY_DSN_FE }}\n        if: ${{ env.analytics_fe_key == '' || env.sentry_dsn_fe == '' || env.analytics_key == '' || env.sentry_dsn == ''}}\n        run: exit 1\n\n      - name: Set environment\n        run: echo \"{\\\"analytics_key\\\":\\\"${{ secrets.ANALYTICS_BE_WRITE_KEY_PROD }}\\\",\\\"analytics_data_plane\\\":\\\"${{ secrets.ANALYTICS_DATA_PLANE_URL }}\\\",\\\"sentry_dsn_fe\\\":\\\"${{ secrets.SENTRY_DSN_FE }}\\\",\\\"sentry_dsn\\\":\\\"${{ secrets.SENTRY_DSN_BE }}\\\",\\\"analytics_key_fe\\\":\\\"${{ secrets.ANALYTICS_FE_WRITE_KEY_PROD }}\\\",\\\"answer_api_url\\\":\\\"${{ secrets.ANSWER_API_URL }}\\\",\\\"cognito_userpool_id\\\":\\\"${{ secrets.COGNITO_USERPOOL_ID }}\\\",\\\"cognito_client_id\\\":\\\"${{ secrets.COGNITO_CLIENT_ID }}\\\",\\\"cognito_auth_url\\\":\\\"${{ secrets.COGNITO_AUTH_URL }}\\\",\\\"cognito_mgmt_url\\\":\\\"${{ secrets.COGNITO_MGMT_URL }}\\\",\\\"cognito_config_url\\\":\\\"${{ secrets.COGNITO_CONFIG_URL }}\\\"}\" > apps/desktop/src-tauri/config/config.json\n\n      - name: Check environment is set\n        run: du -h apps/desktop/src-tauri/config/config.json\n\n      - name: Set providerShortName in tauri.conf.json\n        uses: jossef/action-set-json-field@v2.1\n        with:\n          file: apps/desktop/src-tauri/tauri.conf.json\n          field: tauri.bundle.macOS.providerShortName\n          value: ${{ secrets.MAC_PROVIDER_SHORT_NAME }}\n\n      - name: Set signingIdentity in tauri.conf.json\n        uses: jossef/action-set-json-field@v2.1\n        with:\n          file: apps/desktop/src-tauri/tauri.conf.json\n          field: tauri.bundle.macOS.signingIdentity\n          value: ${{ secrets.APPLE_SIGNING_IDENTITY }}\n\n      - name: Create Signing API Key\n        if: matrix.name == 'macos-11'\n        run: echo \"${{ secrets.APPLE_API_KEY_CONTENT }}\" > apiKey.p8\n\n      - name: Remove onnxruntime from aarch64-apple-darwin builds\n        if: matrix.target == 'aarch64-apple-darwin'\n        uses: jossef/action-set-json-field@v2.1\n        with:\n          file: apps/desktop/src-tauri/tauri.conf.json\n          field: tauri.bundle.macOS.frameworks\n          value: '[]'\n          parse_json: true\n\n      - name: get release version\n        id: release-version\n        run: echo \"RELEASE_VERSION=$(cat apps/desktop/src-tauri/tauri.conf.json | jq '.package.version' | tr -d '\"')\" >> \"$GITHUB_OUTPUT\"\n\n      - uses: tauri-apps/tauri-action@cb58ba3f65bd456ee564376585a8400bf0b71f47\n        env:\n          NODE_OPTIONS: \"--max-old-space-size=4096\"\n          ORT_LIB_LOCATION: ${{ github.workspace }}/lib/${{ matrix.target }}/onnxruntime\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }}\n          APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}\n          APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}\n          APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}\n          APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}\n          APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}\n          APPLE_API_KEY_PATH: /Users/runner/work/bloop/bloop/apiKey.p8\n          TAURI_BIN_PATH: apps/desktop/src-tauri/bin\n          TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}\n          TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}\n          SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}\n          SENTRY_RELEASE_VERSION: ${{ steps.release-version.outputs.RELEASE_VERSION }}\n        with:\n          args: -- --target \"${{ matrix.target }}\" -v\n\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v3\n        with:\n          name: ${{ matrix.target }}-app\n          path: target/${{ matrix.target }}/release/bundle\n          retention-days: 5\n\n      - name: Setup Sentry CLI\n        uses: mathieu-bour/setup-sentry-cli@v1.3.0\n        with:\n          token: ${{ secrets.SENTRY_AUTH_TOKEN }}\n          organization: ${{ secrets.SENTRY_ORG }}\n          project: ${{ secrets.SENTRY_PROJECT }}\n          version: 2.21.2\n\n      - name: Create Sentry release\n        run: |\n          sentry-cli releases new \"${{ steps.release-version.outputs.RELEASE_VERSION }}\"\n          sentry-cli releases set-commits \"${{ steps.release-version.outputs.RELEASE_VERSION }}\" --auto\n\n      - name: (MacOS) Upload source maps to Sentry\n        if: matrix.name == 'macos-11'\n        run: |\n          sentry-cli debug-files upload \\\n          --log-level debug \\\n          --include-sources \\\n          target/${{ matrix.target }}/release/bloop.dSYM\n\n      - name: Rename tar.gz in macos\n        if: matrix.name == 'macos-11'\n        run: |\n          new_filename=\"bloop_${{ steps.release-version.outputs.RELEASE_VERSION }}_$(echo ${{ matrix.target }} | cut -d '-' -f 1).app.tar.gz\"\n          mv \"target/${{ matrix.target }}/release/bundle/macos/bloop.app.tar.gz\" \"target/${{ matrix.target }}/release/bundle/macos/${new_filename}\"\n\n      - name: List files\n        run: ls -R target/${{ matrix.target }}/release/bundle\n\n      - name: Generate Changelog\n        run: |\n          release_version=\"${{ steps.release-version.outputs.RELEASE_VERSION }}\"\n          sed \"s/VERSION/${release_version}/g\" release_description_template.txt > new_description.txt\n          cat new_description.txt\n\n      - name: Upload release assets\n        uses: softprops/action-gh-release@v1\n        if: startsWith(github.ref, 'refs/tags/v')\n        with:\n          files: |\n            target/${{ matrix.target }}/release/bundle/deb/*.deb\n            target/${{ matrix.target }}/release/bundle/appimage/*.AppImage\n            target/${{ matrix.target }}/release/bundle/appimage/*.tar.gz\n            target/${{ matrix.target }}/release/bundle/dmg/*.dmg\n            target/${{ matrix.target }}/release/bundle/macos/*.tar.gz\n"
  },
  {
    "path": ".github/workflows/tauri-test.yml",
    "content": "name: Tauri Tests\n\non:\n  pull_request:\n    types: [opened, synchronize]\n    branches: [main]\n    paths:\n      - \"apps/desktop/**\"\n      - \".github/workflows/tauri**\"\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  clippy-test:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          lfs: true\n\n      - name: Install Rust stable\n        uses: dtolnay/rust-toolchain@stable\n        with:\n          toolchain: 1.73.0\n          components: rustfmt, clippy\n\n      - name: Install Tauri deps\n        run: | \n              sudo apt update && \\\n              sudo apt install libwebkit2gtk-4.0-dev \\\n                build-essential \\\n                curl \\\n                wget \\\n                file \\\n                libssl-dev \\\n                libgtk-3-dev \\\n                libayatana-appindicator3-dev \\\n                librsvg2-dev\n\n      # https://github.com/actions/cache/blob/main/examples.md#rust---cargo\n      - name: Set up cargo cache\n        uses: actions/cache@v3\n        continue-on-error: false\n        with:\n          path: |\n            ~/.cargo/bin/\n            ~/.cargo/registry/index/\n            ~/.cargo/registry/cache/\n            ~/.cargo/git/db/\n            target/            \n          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}\n          restore-keys: ${{ runner.os }}-cargo-\n\n      - name: Rustfmt\n        run: cargo --locked fmt -p bloop -- --check\n\n      - name: Clippy\n        run: cargo --locked clippy -p bloop\n\n      - name: Tests\n        run: cargo --locked test -p bloop --release"
  },
  {
    "path": ".gitignore",
    "content": "# Env\n.direnv\n.idea\n.DS_Store\n.vscode\n\n# Generated by Cargo\n# will have compiled files and executables\ndebug/\ntarget/\n\n# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries\n# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html\n# Cargo.lock\n\n# These are backup files generated by rustfmt\n**/*.rs.bk\n\n# MSVC Windows builds of rustc generate these, which store debugging information\n*.pdb\n\n# node\n/node_modules/\n\n# Index artifacts\nserver/index\nserver/schema\nserver/target\nserver/tmp_index\n\nserver/bleep/bleep.db*\n\nplaywright-report\n.tests.env\n\n.env\n\n# local config\n/local_config.json\n\napps/desktop/src-tauri/dylibs/*.so\napps/desktop/src-tauri/dylibs/*.dll\napps/desktop/src-tauri/frameworks/*.dylib\n\n# qdrant\n/qdrant/storage\n/qdrant/snapshots\n.qdrant-initialized\n\n"
  },
  {
    "path": ".gitpod.Dockerfile",
    "content": "FROM axonasif/workspace-base\n\nARG NIX_VERSION=\"2.11.0\"\nARG NIX_CONFIG=\"experimental-features = nix-command flakes\"\n\nENV NIX_VERSION=${NIX_VERSION}\n\nUSER root\n\n# Dazzle does not rebuild a layer until one of its lines are changed. Increase this counter to rebuild this layer.\nENV TRIGGER_REBUILD=1\n\nRUN addgroup --system nixbld \\\n    && adduser gitpod nixbld \\\n    && for i in $(seq 1 30); do useradd -ms /bin/bash nixbld$i && adduser nixbld$i nixbld; done \\\n    && mkdir -m 0755 /nix && chown gitpod /nix \\\n    && mkdir -p /etc/nix && echo 'sandbox = false' > /etc/nix/nix.conf\n\n# Install Nix\nUSER gitpod\nENV USER gitpod\nWORKDIR /home/gitpod\n\nRUN curl https://nixos.org/releases/nix/nix-$NIX_VERSION/install | sh\n\nRUN echo '. /home/gitpod/.nix-profile/etc/profile.d/nix.sh' >> /home/gitpod/.bashrc.d/200-nix\nRUN mkdir -p /home/gitpod/.config/nixpkgs && echo '{ allowUnfree = true; }' >> /home/gitpod/.config/nixpkgs/config.nix\nRUN mkdir -p /home/gitpod/.config/nix && echo $NIX_CONFIG >> /home/gitpod/.config/nix/nix.conf\n\n# Install cachix\nRUN . /home/gitpod/.nix-profile/etc/profile.d/nix.sh \\\n    && nix-env -iA cachix -f https://cachix.org/api/v1/install \\\n    && cachix use cachix\n\n# Install direnv & other files\nRUN mkdir -p $HOME/.config/direnv && printf '%s\\n' \"[whitelist]\" 'prefix = [ \"/workspace\" ]' >  $HOME/.config/direnv/config.toml \\\n    && printf '%s\\n' 'source <(direnv hook bash)' > $HOME/.bashrc.d/999-direnv \\\n    && printf '%s\\n' \\\n        'dirs=($HOME/.cargo $HOME/.cache/nix) && mkdir -p \"${dirs[@]}\"' \\\n        'create-overlay /nix \"${dirs[@]}\"' > $HOME/.runonce/100-nix \\\n    && . /home/gitpod/.nix-profile/etc/profile.d/nix.sh \\\n    && nix-env -iA nixpkgs.direnv"
  },
  {
    "path": ".gitpod.yml",
    "content": "# This configuration file was automatically generated by Gitpod.\n# Please adjust to your needs (see https://www.gitpod.io/docs/introduction/learn-gitpod/gitpod-yaml)\n# and commit this file to your remote git repository to share the goodness with others.\n\n# Learn more from ready-to-use templates: https://www.gitpod.io/docs/introduction/getting-started/quickstart\n\nimage:\n  file: .gitpod.Dockerfile\n\nports:\n  - name: Bloop Web Interface\n    port: 7878\n    protocol: http\n\nadditionalRepositories:\n  - url: https://github.com/bloopai/answer-api\n    checkoutLocation: answer-api\n\ntasks:\n  - name: Backend\n    init: |\n      nix run nixpkgs#cachix use bloopai\n\n      # Setup Git LFS\n      git lfs pull model\n      git lfs install --skip-smudge\n\n      # Cache build artifacts\n      nix develop -c cargo build --locked --features=ee-cloud --bin bleep\n      nix develop -c npm i\n\n      # this is working around a gitpod bug\n      # https://github.com/gitpod-io/gitpod/issues/524\n      find target -exec stat --format='%.Y %n' {} + > /workspace/.ts\n    command: |\n      while read -r ts file; do touch -d \"@${ts}\" \"${file}\"; done < /workspace/.ts\n\n      bloop_url=$(gp url 7878 | sed 's;https://;;')\n\n      git reset --hard\n\n      nix develop -c cargo watch -w server -- \\\n        cargo run --locked --features=ee-cloud --bin bleep -- \\\n        \\\n        --qdrant-url http://localhost:6334 \\\n        --answer-api-url http://localhost:7879 \\\n        --model-dir /workspace/bloop/model \\\n        --frontend-dist /workspace/bloop/client/dist \\\n        --instance-domain $bloop_url \\\n        --bloop-instance-secret \"$BLOOP_INSTANCE_SECRET\" \\\n        --bloop-instance-org \"$BLOOP_INSTANCE_ORG\" \\\n        --cognito-config-url https://cloud-auth-staging.bloop.ai/bloop_config\n\n    openMode: split-left\n\n  - name: Frontend\n    command: |\n      nix develop -c npm i\n      nix develop -c npm run build-web -- -- --watch --minify false\n    openMode: split-right\n\n  - name: Qdrant\n    command: |\n      nix run nixpkgs#qdrant -- --config-path qdrant/config.yaml\n    openMode: tab-after\n\n  - name: answer-api\n    command: |\n      cd /workspace/answer-api\n      nix run /workspace/answer-api\n    openMode: tab-after\n\ngithub:\n    prebuilds:\n        # enable for the default branch (defaults to true)\n        master: true\n        # enable for all branches in this repo (defaults to false)\n        branches: true\n        # enable for pull requests coming from this repo (defaults to true)\n        pullRequests: true\n        # enable for pull requests coming from forks (defaults to false)\n        pullRequestsFromForks: false\n        # add a check to pull requests (defaults to true)\n        addCheck: true\n        # add a \"Review in Gitpod\" button as a comment to pull requests (defaults to false)\n        addComment: true\n        # add a \"Review in Gitpod\" button to the pull request's description (defaults to false)\n        addBadge: true\n"
  },
  {
    "path": ".taurignore",
    "content": "server/bleep/bleep.db*\napps/desktop/src-tauri/dylibs/*.so\napps/desktop/src-tauri/dylibs/*.dylib\napps/desktop/src-tauri/dylibs/*.dll\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, caste, color, religion, or sexual\nidentity and orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the overall\n  community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or advances of\n  any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email address,\n  without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\nmaintainers@bloop.ai through e-mail, with an appropriate subject line.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n### Attribution\n\nThis Code of Conduct is adapted from the [Next.js project][nextjs-coc]\n\nThe original text is from the [Contributor Covenant][homepage],\nversion 2.1, available at\n[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].\n\nFor answers to common questions about this code of conduct, see the FAQ at\n[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at\n[https://www.contributor-covenant.org/translations][translations].\n\n[nextjs-coc]: https://raw.githubusercontent.com/vercel/next.js/canary/CODE_OF_CONDUCT.md\n[homepage]: https://www.contributor-covenant.org\n[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html\n[FAQ]: https://www.contributor-covenant.org/faq\n[translations]: https://www.contributor-covenant.org/translations\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "Guide to new contributors\n=========================\n\nThanks for your interest in contributing to `bloop`!\n\nBefore jumping in, please\ntake a look at our [code of conduct](./CODE_OF_CONDUCT.md).\n\n## Have a question?\n\nIf you have a question about using\n[bloop](https://github.com/bloopai/bloop), please raise a ticket in\nthe repository, or say hi on our [Discord\nserver](https://discord.gg/kZEgj5pyjm).\n\n## Would like to contribute?\n\nIf you have a great idea, some novel optimization, or a bug fix ready to be\nmerged, the maintainer team will help you get it into `bloop`.\n\nWe aim to maintain a high quality of contributions, and therefore all PRs need\nto go through a review process. During the review period you may be asked to\nmake some changes to the code. When everybody's happy, we'll land the code, and\nship it in the next release!\n\nAfter a longer period of inactivity, we will close PRs. Some of the code may\neventually make it into `bloop` if there's someone else to champion it, in\naccordance with the license.\n\nIn case you are not certain that your code quality or the feature\nyou're working on is suitable for `bloop`, please open an issue\nwith the question, or send in the PR anyway.\n\nThis allows the maintainers to give you hands on feedback, and\nwork with you on specifics rather than theoretical proposals.\n\nA quick list of things maintainers will check during review.\nThe following 2 steps are requirements for all PRs:\n\n * Don't break public APIs.\n * Make sure you use `rustfmt`, follow `clippy`, and check tests, or your PR will fail the CI!\n \nAdditionally, please pay attention to the following points as it helps\nwith our review. However, these are _not_ required:\n\n * Please follow one of the issue/PR templates to make our work easier.\n * Document your changes as best as you can where appropriate.\n * Take a look at surrounding code, and try to match the style.\n * If you implement new high-level features, make sure you have a minimal\n   example either in the documentation or in form of tests/benchmarks.\n * If you have a short & sweet bug fix, please create a PR and\n   describe instructions to reproduce the bug, or a negative unit test.\n * Provide tests for logic changes, and benchmarks for performance\n   work if possible.\n \nFollowing these points will help the maintainers to run through your\ncode and merge it in a timely manner.\n\nMake sure the description of the PR clearly explains the motivation\nand link to any other resources we might need to consider when\nreviewing.\n\n## Get in touch!\n\nIf you're still in doubt, email us at <maintainers@bloop.ai>, and we'll get you\nstarted!\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nresolver = \"2\"\nmembers = [\n    \"server/bleep\",\n    \"apps/desktop/src-tauri\"\n]\n\n[profile.dev]\nopt-level = 3\n\n[profile.release]\ndebug = 1\nlto = true\nsplit-debuginfo = \"packed\"\n\n[profile.profiler]\ninherits = \"release\"\ndebug = true\nsplit-debuginfo = \"unpacked\"\nstrip = \"none\"\n\n[patch.crates-io]\nesaxx-rs = { git = \"https://github.com/bloopai/esaxx-rs\" }\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM node AS frontend\n\nWORKDIR /build\nCOPY package.json package-lock.json ./\nRUN npm ci\nCOPY apps/ apps\nCOPY client/ client\nCOPY playwright.config.js .\nRUN npm run build-web\n\nFROM rust:1.73-slim-bookworm as builder\nWORKDIR /build\nRUN apt-get update && \\\n    apt-get -y install make clang libc-dev curl cmake python3 protobuf-compiler pkg-config libssl3 libssl-dev git && \\\n    rm -rf /var/lib/apt/lists/* && \\\n    curl -sLo sccache.tar.gz https://github.com/mozilla/sccache/releases/download/v0.3.3/sccache-v0.3.3-x86_64-unknown-linux-musl.tar.gz && \\\n    tar xzf sccache.tar.gz && \\\n    mv sccache-*/sccache /usr/bin/sccache\nENV RUSTC_WRAPPER=\"/usr/bin/sccache\"\nENV PYTHON /usr/bin/python3\nENV CC /usr/bin/clang\nENV CXX /usr/bin/clang++\nCOPY server server\nCOPY apps/desktop/src-tauri apps/desktop/src-tauri\nCOPY Cargo.lock Cargo.toml .\nRUN --mount=target=/root/.cache/sccache,type=cache --mount=target=/build/target,type=cache \\\n    cargo --locked build --bin bleep --release && \\\n    cp /build/target/release/bleep / && \\\n    sccache --show-stats && \\\n    mkdir /dylib && \\\n    cp /build/target/release/libonnxruntime.so /dylib/\n\nFROM debian:bookworm-slim\nVOLUME [\"/repos\", \"/data\"]\nRUN apt-get update && apt-get -y install openssl ca-certificates libprotobuf-lite32 && rm -rf /var/lib/apt/lists/*\nCOPY model /model\nCOPY --from=builder /bleep /\nCOPY --from=builder /dylib /dylib\nCOPY --from=frontend /build/client/dist /frontend\n\nARG OPENAI_API_KEY\nARG GITHUB_ACCESS_TOKEN\n\nENTRYPOINT [\"/bleep\", \"--host=0.0.0.0\", \"--source-dir=/repos\", \"--index-dir=/data\", \"--model-dir=/model\", \"--dylib-dir=/dylib\", \"--disable-log-write\", \"--frontend-dist=/frontend\", \"--openai-api-key=$OPENAI_API_KEY\", \"--github-access-token=$GITHUB_ACCESS_TOKEN\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://assets.bloop.ai/bloop_github_logo_dark.png\">\n  <img alt=\"bloop logo\" src=\"https://assets.bloop.ai/bloop_github_logo_light.png\">\n</picture>\n\nbloop is ChatGPT for your code. Ask questions in natural language, search for code and generate patches using your existing codebase as context. \n\nEngineers are increasing their productivity by using bloop to:\n- Explain how files or features work in simple language\n- Write new features, using their code as context\n- Understand how to use poorly documented open source libraries\n- Pinpoint errors\n- Ask questions about English language codebases in other languages\n- Reduce code duplication by checking for existing functionality\n\nhttps://github.com/BloopAI/bloop/assets/7957964/01db3ccb-4af0-49a0-92d6-5a9c42357a51\n\n## Features\n\n- AI-based conversational search\n- Code Studio, an LLM playground that uses your code as context\n- Blazing fast regex search\n- Sync your local and GitHub repositories\n- Sophisticated query filters so you can narrow down your results\n- Find functions, variables or traits with symbol search\n- Precise code navigation (go-to-reference and go-to-definition) for 10+ of the most popular languages built with [Tree-sitter](https://tree-sitter.github.io/tree-sitter/)\n- Privacy focussed on-device embedding for semantic search\n\nbloop stands on the shoulders of the Rust ecosystem. Our search indexes are powered by [Tantivy](https://github.com/quickwit-oss/tantivy) and [Qdrant](https://github.com/qdrant/qdrant), and our multi-platform app is built with [Tauri](https://github.com/tauri-apps/tauri).\n\nhttps://github.com/BloopAI/bloop/assets/7957964/93715188-d8d5-477b-8cd1-95d9cbd368cb\n\n## Get Started\n\nThe simplest way to get started with bloop is to [download the app](https://github.com/BloopAI/bloop/releases) and follow the onboarding steps. Checkout our [getting started guide](https://bloop.ai/understand/docs/getting-started) and our references for [conversational](https://bloop.ai/understand/docs/natural-language-queries) and [regex](https://bloop.ai/understand/docs/regex-queries) search and [Code Studio](https://bloop.ai/understand/docs/code-studio).\n\nFor instructions on how to build from source or run bloop from the command line, check out these pages:\n\n- [Build bloop app from source](./apps/desktop/README.md)\n- [Run bloop from the command line](./server/README.md)\n\nIf you encounter any index issues you can wipe the bloop cache and reindex. Instructions on how to do this on different platforms [are here](./apps/desktop/README.md).\n\n## Building From Source\n\nYou can build bloop from source and run it with your own OpenAI API key. Clone the repo, make sure the `oss` branch is checked out, and create a file called `local_config.json` at the top-level of the repo. `local_config.json` should contain the following fields:\n\n```json\n{\n    \"github_access_token\": \"<YOUR_GITHUB_ACCESS_TOKEN>\",\n    \"openai_api_key\": \"<YOUR_OPENAI_API_KEY>\"\n}\n```\n\nThen follow [these installation instructions](./apps/desktop/README.md). If built from source, bloop will not collect any telemetry. \n\n## Contributing\n\n[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/BloopAI/bloop)\n\n[![Open in Codeanywhere](https://codeanywhere.com/img/open-in-codeanywhere-btn.svg)](https://app.codeanywhere.com/#https://github.com/BloopAI/bloop)\n\nWe welcome contributions big and small! Before jumping in please read [our contributors guide](./CONTRIBUTING.md) and [our code of conduct](./CODE_OF_CONDUCT.md).\n\nHere's how to find your way around the repo:\n\n- `apps/desktop`: The Tauri app\n- `server/bleep`: The Rust backend which contains the core search and navigation logic\n- `client`: The React frontend\n\nWe use Git LFS for dependencies that are expensive to build.\n\nTo make sure you have everything you need to start building, you'll need to\ninstall the `git-lfs` package for your favourite operating system, then run the\nfollowing commands in this repo:\n\n    git lfs install\n    git lfs pull\n\nIf you find a bug or have a feature request, [open an issue](https://github.com/BloopAI/bloop/issues)! You can find the application logs here:\n\n| OS      | Logs Path |\n| ----------- | ----------- |\n| MacOS      | `~/Library/Application\\ Support/ai.bloop.bloop/bleep/logs`       |\n| Windows   | `%APPDATA%/bloop/bleep/logs`        |\n| Linux   | `~/.local/share/bloop/bleep/logs`        |\n\n## Privacy\n\nWe store as little data as possible. We use telemetry to helps us identify bugs and make data-driven product decisions. You can read our full privacy policy [here](https://bloop.ai/privacy).\n\n## License\n\nbloop is licensed under the `Apache 2.0` license as defined in [LICENSE](./LICENSE).\n\n"
  },
  {
    "path": "apps/desktop/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist/*\n!dist/.keep \ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n/.env\n\n# Qdrant build relic\nsrc-tauri/bin/schema_generator\n# Auto-generated from Cargo.toml/lock files\n.crates*"
  },
  {
    "path": "apps/desktop/README.md",
    "content": "# bloop App\n\nThe bloop app is built using [Tauri](https://github.com/tauri-apps/tauri), a Rust framework for building cross-platform apps.\n\n## Dependencies\n\nTo build the Tauri app you need the following dependencies:\n- `rustup`\n- `clang` `cmake` `wget`\n- `protobuf`\n- `onnxruntime`\n\nLinux users need to ensure that the following are present:\n- `AppImageKit`\n- `atk`\n- `dbus`\n- `glib` `gtk3` (including `webkit-gtk`)\n- `pango`\n \n## Setup\n\nAll commands should be run from the root directory unless specified otherwise.\n\nFirst make sure dependencies have been downloaded and installed:\n```\ngit lfs install\ngit lfs pull\n\nnpm install\n``` \n\nThen to build the app locally:\n\n```\nnpm run build-app\n```\n\nAlternatively, to run the app in dev mode:\n```\nnpm run start-app\n```\n\n## Wiping an index\n\nDeleting and re-indexing the bloop index can fix corruption issues. bloop's index is stored:\n\n| OS      | Index Path |\n| ----------- | ----------- |\n| MacOS      | `~/Library/Application\\ Support/ai.bloop.bloop`       |\n| Windows   | `%APPDATA%/bloop`        |\n| Linux   | `~/.local/share/bloop`        |"
  },
  {
    "path": "apps/desktop/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\" />\n  <link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.ico\" />\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  <title>bloop</title>\n  <!--    <script>-->\n  <!--      !function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error(\"Segment snippet included twice.\");else{analytics.invoked=!0;analytics.methods=[\"trackSubmit\",\"trackClick\",\"trackLink\",\"trackForm\",\"pageview\",\"identify\",\"reset\",\"group\",\"track\",\"ready\",\"alias\",\"debug\",\"page\",\"once\",\"off\",\"on\",\"addSourceMiddleware\",\"addIntegrationMiddleware\",\"setAnonymousId\",\"addDestinationMiddleware\"];analytics.factory=function(e){return function(){var t=Array.prototype.slice.call(arguments);t.unshift(e);analytics.push(t);return analytics}};for(var e=0;e<analytics.methods.length;e++){var key=analytics.methods[e];analytics[key]=analytics.factory(key)}analytics.load=function(key,e){var t=document.createElement(\"script\");t.type=\"text/javascript\";t.async=!0;t.src=\"https://cdn.segment.com/analytics.js/v1/\" + key + \"/analytics.min.js\";var n=document.getElementsByTagName(\"script\")[0];n.parentNode.insertBefore(t,n);analytics._loadOptions=e};analytics._writeKey=\"vzA96L5JgFpDEtOKJKFIKcMxHMfkuVAC\";;analytics.SNIPPET_VERSION=\"4.15.3\";-->\n  <!--        analytics.load(\"vzA96L5JgFpDEtOKJKFIKcMxHMfkuVAC\");-->\n  <!--      }}();-->\n  <!--    </script>-->\n</head>\n\n<body data-theme=\"system\">\n  <div id=\"root\"></div>\n  <script type=\"module\" src=\"/src/main.tsx\"></script>\n</body>\n\n</html>\n"
  },
  {
    "path": "apps/desktop/package.json",
    "content": "{\n  \"name\": \"@bloop/desktop\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"tsc\": \"tsc\",\n    \"vite-build\": \"vite build\",\n    \"build\": \"npm-run-all tsc vite-build\",\n    \"preview\": \"vite preview\",\n    \"tauri\": \"tauri\"\n  }\n}\n"
  },
  {
    "path": "apps/desktop/postcss.config.cjs",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n}\n"
  },
  {
    "path": "apps/desktop/src/App.tsx",
    "content": "import React, {\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport { invoke } from '@tauri-apps/api';\nimport { open } from '@tauri-apps/api/shell';\nimport { homeDir } from '@tauri-apps/api/path';\nimport { relaunch } from '@tauri-apps/api/process';\nimport { message, open as openDialog } from '@tauri-apps/api/dialog';\nimport { listen } from '@tauri-apps/api/event';\nimport * as tauriOs from '@tauri-apps/api/os';\nimport { getVersion } from '@tauri-apps/api/app';\nimport { BrowserRouter } from 'react-router-dom';\nimport ClientApp from '../../../client/src/App';\nimport '../../../client/src/index.css';\nimport useKeyboardNavigation from '../../../client/src/hooks/useKeyboardNavigation';\nimport { LocaleContext } from '../../../client/src/context/localeContext';\nimport i18n from '../../../client/src/i18n';\nimport {\n  getPlainFromStorage,\n  LANGUAGE_KEY,\n  savePlainToStorage,\n  USER_FONT_SIZE_KEY,\n} from '../../../client/src/services/storage';\nimport { LocaleType } from '../../../client/src/types/general';\nimport { DeviceContextProvider } from '../../../client/src/context/providers/DeviceContextProvider';\nimport TextSearch from './TextSearch';\n\nfunction App() {\n  const [homeDirectory, setHomeDir] = useState('');\n  const [indexFolder, setIndexFolder] = useState('');\n  const [os, setOs] = useState({\n    arch: '',\n    type: '',\n    platform: '',\n    version: '',\n  });\n  const [release, setRelease] = useState('');\n  const contentContainer = useRef<HTMLDivElement>(null);\n  const [locale, setLocale] = useState<LocaleType>(\n    (getPlainFromStorage(LANGUAGE_KEY) as LocaleType | null) || 'en',\n  );\n\n  useEffect(() => {\n    i18n.changeLanguage(locale);\n    savePlainToStorage(LANGUAGE_KEY, locale);\n  }, [locale]);\n\n  const localeContextValue = useMemo(\n    () => ({\n      locale,\n      setLocale,\n    }),\n    [locale],\n  );\n\n  useEffect(() => {\n    homeDir().then(setHomeDir);\n    Promise.all([\n      tauriOs.arch(),\n      tauriOs.type(),\n      tauriOs.platform(),\n      tauriOs.version(),\n      getVersion(),\n    ]).then(([arch, type, platform, version, appVersion]) => {\n      setOs({ arch, type, platform, version });\n      setRelease(appVersion);\n      // checkUpdateAndInstall(appVersion);\n      // intervalId = window.setInterval(\n      //   () => checkUpdateAndInstall(appVersion),\n      //   1000 * 60 * 60,\n      // );\n    });\n  }, []);\n\n  const handleKeyEvent = useCallback((e: KeyboardEvent) => {\n    if (\n      (e.key === '=' || e.key === '-' || e.key === '0') &&\n      (e.metaKey || e.ctrlKey) &&\n      !e.shiftKey\n    ) {\n      const root = document.querySelector(':root');\n      if (!root) {\n        return;\n      }\n      const style = window\n        .getComputedStyle(root, null)\n        .getPropertyValue('font-size');\n      const fontSize = parseFloat(style);\n\n      const newFontSize =\n        e.key === '0' ? 16 : fontSize + (e.key === '=' ? 1 : -1);\n      (root as HTMLElement).style.fontSize = newFontSize + 'px';\n      savePlainToStorage(USER_FONT_SIZE_KEY, newFontSize);\n    }\n  }, []);\n  useKeyboardNavigation(handleKeyEvent);\n\n  useEffect(() => {\n    const root = document.querySelector(':root');\n    if (!root) {\n      return;\n    }\n    const newFontSize = getPlainFromStorage(USER_FONT_SIZE_KEY);\n    if (newFontSize) {\n      (root as HTMLElement).style.fontSize = newFontSize + 'px';\n    }\n  }, []);\n\n  useEffect(() => {\n    const onContextMenu = (e: MouseEvent) => {\n      if (!import.meta.env.DEV) {\n        e.preventDefault();\n      }\n    };\n    document.addEventListener('contextmenu', onContextMenu);\n\n    return () => {\n      document.removeEventListener('contextmenu', onContextMenu);\n    };\n  }, []);\n\n  const deviceContextValue = useMemo(\n    () => ({\n      openFolderInExplorer: (path: string) => {\n        invoke('show_folder_in_finder', { path });\n      },\n      openLink: (path: string) => {\n        open(path);\n      },\n      homeDir: homeDirectory,\n      chooseFolder: openDialog,\n      indexFolder,\n      setIndexFolder,\n      listen,\n      os,\n      invokeTauriCommand: invoke,\n      release,\n      apiUrl: 'http://127.0.0.1:7878/api',\n      isRepoManagementAllowed: true,\n      forceAnalytics: false,\n      isSelfServe: false,\n      showNativeMessage: message,\n      relaunch,\n    }),\n    [homeDirectory, indexFolder, os, release],\n  );\n\n  return (\n    <DeviceContextProvider deviceContextValue={deviceContextValue}>\n      <LocaleContext.Provider value={localeContextValue}>\n        <TextSearch contentRoot={contentContainer.current} />\n        <div\n          ref={contentContainer}\n          className=\"w-screen h-screen overflow-hidden\"\n        >\n          <BrowserRouter>\n            <ClientApp />\n          </BrowserRouter>\n        </div>\n      </LocaleContext.Provider>\n    </DeviceContextProvider>\n  );\n}\n\nexport default App;\n"
  },
  {
    "path": "apps/desktop/src/TextSearch.tsx",
    "content": "import React, { useCallback, useEffect, useState } from 'react';\nimport {\n  ACTIVE_HIGHLIGHT_CLASSNAME,\n  HIGHLIGHT_CLASSNAME,\n  markNode,\n  unmark,\n} from '../../../client/src/utils/textSearch';\nimport SearchOnPage from '../../../client/src/components/SearchOnPage';\n\nconst TextSearch = ({\n  contentRoot,\n}: {\n  contentRoot: HTMLDivElement | null;\n}) => {\n  const [searchValue, setSearchValue] = useState('');\n  const [isSearchActive, setSearchActive] = useState(false);\n  const [resultNum, setResultNum] = useState(0);\n  const [currentResult, setCurrentResult] = useState(0);\n  const [currentHighlightParent, setCurrentHighlightParent] =\n    useState<HTMLElement | null>(null);\n\n  useEffect(() => {\n    const toggleSearch = (e: KeyboardEvent) => {\n      const fullCodeInView =\n        !!document.getElementsByClassName('code-full-view').length;\n      if (e.code === 'KeyF' && e.metaKey && !fullCodeInView) {\n        setSearchActive((prev) => !prev);\n      } else if (e.code === 'Enter') {\n        const isNext = !e.shiftKey;\n        setCurrentResult((prev) =>\n          isNext\n            ? prev < resultNum\n              ? prev + 1\n              : 1\n            : prev > 1\n            ? prev - 1\n            : resultNum,\n        );\n      } else if (e.code === 'Escape') {\n        setSearchActive((prev) => {\n          if (prev) {\n            e.preventDefault();\n          }\n          return false;\n        });\n      }\n    };\n    window.addEventListener('keypress', toggleSearch);\n\n    return () => {\n      window.removeEventListener('keypress', toggleSearch);\n    };\n  }, [resultNum]);\n\n  useEffect(() => {\n    if (!isSearchActive) {\n      unmark();\n    }\n  }, [isSearchActive]);\n\n  const doSearch = useCallback(\n    (searchTerm: string) => {\n      unmark();\n\n      if (searchTerm === '') {\n        setResultNum(0);\n        setCurrentResult(0);\n        setCurrentHighlightParent(null);\n        return;\n      }\n      const regex = new RegExp(\n        searchTerm.replace(/[-[\\]{}()*+?.,\\\\^$|#\\s]/g, '\\\\$&'),\n        'gi',\n      );\n      if (contentRoot) {\n        markNode(contentRoot, regex);\n        const allHighlights =\n          document.getElementsByClassName(HIGHLIGHT_CLASSNAME);\n        const resNum = allHighlights.length;\n        setResultNum(resNum);\n        let prevIndexInNewHighlights = currentHighlightParent\n          ? [...allHighlights].findIndex(\n              (el) =>\n                el.parentNode?.parentNode?.isSameNode(currentHighlightParent),\n            )\n          : -1;\n        setCurrentResult((prev) => {\n          const newR = resNum\n            ? prevIndexInNewHighlights >= 0\n              ? prevIndexInNewHighlights + 1\n              : 1\n            : 0;\n          if (prev === newR && allHighlights?.[newR - 1]) {\n            allHighlights[newR - 1].classList?.add(ACTIVE_HIGHLIGHT_CLASSNAME);\n            setCurrentHighlightParent(\n              allHighlights[newR - 1].parentNode as HTMLElement,\n            );\n          }\n          return newR;\n        });\n      }\n    },\n    [contentRoot, currentHighlightParent],\n  );\n\n  const handleChange = useCallback(\n    (searchTerm: string) => {\n      setSearchValue(searchTerm);\n      if (searchTerm.length > 1) {\n        doSearch(searchTerm);\n      }\n    },\n    [doSearch],\n  );\n\n  useEffect(() => {\n    const highlights = document.getElementsByClassName(HIGHLIGHT_CLASSNAME);\n    [...highlights].forEach((el) =>\n      el.classList.remove(ACTIVE_HIGHLIGHT_CLASSNAME),\n    );\n    const elementToShow = highlights[currentResult - 1];\n    if (elementToShow) {\n      setCurrentHighlightParent(elementToShow.parentNode as HTMLElement);\n      elementToShow.scrollIntoView({\n        behavior: 'smooth',\n        block: 'center',\n        inline: 'nearest',\n      });\n      elementToShow.classList.add(ACTIVE_HIGHLIGHT_CLASSNAME);\n    }\n  }, [currentResult]);\n\n  return (\n    <SearchOnPage\n      onCancel={() => {\n        handleChange('');\n        setSearchActive(false);\n      }}\n      handleSearch={handleChange}\n      isSearchActive={isSearchActive}\n      resultNum={resultNum}\n      currentResult={currentResult}\n      setCurrentResult={setCurrentResult}\n      searchValue={searchValue}\n      containerClassName=\"fixed top-[100px] right-[5px] w-80\"\n    />\n  );\n};\n\nexport default TextSearch;\n"
  },
  {
    "path": "apps/desktop/src/global.d.ts",
    "content": "export {};\n"
  },
  {
    "path": "apps/desktop/src/main.tsx",
    "content": "import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport App from './App';\n\nReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(\n  <React.StrictMode>\n    <App />\n  </React.StrictMode>,\n);\n"
  },
  {
    "path": "apps/desktop/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "apps/desktop/src-tauri/.gitignore",
    "content": "# Generated by Cargo\n# will have compiled files and executables\n/target/\n"
  },
  {
    "path": "apps/desktop/src-tauri/Cargo.toml",
    "content": "[package]\nname = \"bloop\"\nversion = \"0.6.4\"\ndescription = \"Search code. Fast.\"\nauthors = [\"Bloop AI Developers\"]\nlicense = \"Apache-2.0\"\nrepository = \"https://github.com/bloopai/bloop\"\ndefault-run = \"bloop\"\nedition = \"2021\"\nrust-version = \"1.57\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[build-dependencies]\ntauri-build = { version = \"1.4.1\", features = [] }\n\n[dependencies]\nserde_json = \"1.0\"\nserde = { version = \"1.0\", features = [\"derive\"] }\ntauri = { version = \"1.5.0\", features = [\"dialog-open\", \"fs-all\", \"http-all\", \"native-tls-vendored\", \"os-all\", \"path-all\", \"process-all\", \"shell-all\", \"window-all\"] }\nbleep = { path = \"../../../server/bleep\", package = \"bleep\" }\nanyhow = \"1.0.75\"\ntokio = { version = \"1.32.0\", features = [\"rt-multi-thread\"] }\ntracing = \"0.1.37\"\ntracing-subscriber = { version = \"0.3.17\", features = [\"env-filter\"] }\ncolor-eyre = \"0.6.2\"\nonce_cell = \"1.18.0\"\nsentry = \"0.31.7\"\nqdrant-client = \"1.5.0\"\ngit-version = \"0.3.5\"\nsentry-anyhow = \"0.31.7\"\nsysinfo = \"0.29.10\"\n\n[target.'cfg(unix)'.dependencies]\nnix = { version = \"0.26.4\", default-features = false, features = [ \"resource\" ] }\n\n[features]\n# by default Tauri runs in production mode\n# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL\ndefault = [\"custom-protocol\"]\n# this feature is used for production builds where `devPath` points to the filesystem\n# DO NOT remove this\ncustom-protocol = [\"tauri/custom-protocol\"]\n"
  },
  {
    "path": "apps/desktop/src-tauri/bin/qdrant-aarch64-apple-darwin",
    "content": "version https://git-lfs.github.com/spec/v1\noid sha256:790591ece18fdc99761f59c31282237b07389ce3dd2eecd50cceb95fddbe96f5\nsize 52682248\n"
  },
  {
    "path": "apps/desktop/src-tauri/bin/qdrant-x86_64-apple-darwin",
    "content": "version https://git-lfs.github.com/spec/v1\noid sha256:de8dad5075c1bcbe403e0df610a2b2933ca49457300cda0af1a104a080e5ce76\nsize 65580008\n"
  },
  {
    "path": "apps/desktop/src-tauri/bin/qdrant-x86_64-unknown-linux-gnu",
    "content": "version https://git-lfs.github.com/spec/v1\noid sha256:4abf2e680bf51edde94dee2ba51b7941c9a72f3748303716a94c9b01aaf21d83\nsize 49078720\n"
  },
  {
    "path": "apps/desktop/src-tauri/build.rs",
    "content": "use std::{\n    env, fs,\n    path::{Path, PathBuf},\n    thread,\n    time::Duration,\n};\n\nfn main() {\n    // we do not require libonnx for apple silicon\n    if !is_apple_silicon() {\n        if env::var(\"ORT_DYLIB_PATH\").is_err() {\n            let out_dir = PathBuf::from(env::var(\"OUT_DIR\").unwrap());\n            let profile_dir = out_dir\n                // \"target/.../build/bloop-hash\"\n                .parent()\n                .unwrap()\n                // \"target/.../build\"\n                .parent()\n                .unwrap()\n                // \"target/.../\"\n                .parent()\n                .unwrap();\n\n            copy(profile_dir);\n        } else {\n            println!(\"cargo:rerun-if-env-changed=ORT_DYLIB_PATH\");\n        }\n    }\n\n    tauri_build::build()\n}\n\nfn copy(profile_dir: &Path) {\n    let target_os = env::var(\"CARGO_CFG_TARGET_OS\").unwrap();\n\n    let (dylib_names, target_parent) = match target_os.as_str() {\n        \"macos\" => {\n            let name = \"libonnxruntime.dylib\";\n            (vec![name], Path::new(\".\").join(\"frameworks\"))\n        }\n        \"linux\" => {\n            let name = \"libonnxruntime.so\";\n            (vec![name], Path::new(\".\").join(\"dylibs\"))\n        }\n        \"windows\" => {\n            let main = \"onnxruntime.dll\";\n            let providers = \"onnxruntime_providers_shared.dll\";\n            (vec![main, providers], Path::new(\".\").join(\"dylibs\"))\n        }\n        other => panic!(\"unknown OS {other}\"),\n    };\n\n    for dylib_name in dylib_names {\n        let dylib_path = profile_dir.join(dylib_name);\n        let target_path = target_parent.join(dylib_name);\n        wait_for(&dylib_path);\n        println!(\"target: {target_path:?}, {:?}\", env::current_dir());\n        fs::copy(dylib_path, target_path).unwrap();\n    }\n}\n\nfn wait_for(dylib_path: &Path) {\n    println!(\"waiting for: {dylib_path:?}\");\n    for _ in 0..1000 {\n        if dylib_path.exists() {\n            return;\n        }\n\n        thread::sleep(Duration::from_millis(500));\n    }\n\n    panic!(\"timeout waiting for ort download\");\n}\n\nfn is_apple_silicon() -> bool {\n    let target = env::var(\"TARGET\").unwrap();\n    let components: Vec<_> = target.split('-').map(|s| s.to_string()).collect();\n    components[0] == \"aarch64\" && components[2] == \"darwin\"\n}\n"
  },
  {
    "path": "apps/desktop/src-tauri/config/config.json",
    "content": "{}\n"
  },
  {
    "path": "apps/desktop/src-tauri/dylibs/.keep",
    "content": ""
  },
  {
    "path": "apps/desktop/src-tauri/frameworks/.keep",
    "content": ""
  },
  {
    "path": "apps/desktop/src-tauri/installer.nsi",
    "content": "Unicode true\n; Set the compression algorithm. Default is LZMA.\n!if \"{{compression}}\" == \"\"\n  SetCompressor /SOLID lzma\n!else\n  SetCompressor /SOLID \"{{compression}}\"\n!endif\n\n!include MUI2.nsh\n!include FileFunc.nsh\n!include x64.nsh\n!include WordFunc.nsh\n!include \"StrFunc.nsh\"\n${StrCase}\n${StrLoc}\n\n!define MANUFACTURER \"{{manufacturer}}\"\n!define PRODUCTNAME \"{{product_name}}\"\n!define VERSION \"{{version}}\"\n!define VERSIONWITHBUILD \"{{version_with_build}}\"\n!define SHORTDESCRIPTION \"{{short_description}}\"\n!define INSTALLMODE \"{{install_mode}}\"\n!define LICENSE \"{{license}}\"\n!define INSTALLERICON \"{{installer_icon}}\"\n!define SIDEBARIMAGE \"{{sidebar_image}}\"\n!define HEADERIMAGE \"{{header_image}}\"\n!define MAINBINARYNAME \"{{main_binary_name}}\"\n!define MAINBINARYSRCPATH \"{{main_binary_path}}\"\n!define BUNDLEID \"{{bundle_id}}\"\n!define COPYRIGHT \"{{copyright}}\"\n!define OUTFILE \"{{out_file}}\"\n!define ARCH \"{{arch}}\"\n!define PLUGINSPATH \"{{additional_plugins_path}}\"\n!define ALLOWDOWNGRADES \"{{allow_downgrades}}\"\n!define DISPLAYLANGUAGESELECTOR \"{{display_language_selector}}\"\n!define INSTALLWEBVIEW2MODE \"{{install_webview2_mode}}\"\n!define WEBVIEW2INSTALLERARGS \"{{webview2_installer_args}}\"\n!define WEBVIEW2BOOTSTRAPPERPATH \"{{webview2_bootstrapper_path}}\"\n!define WEBVIEW2INSTALLERPATH \"{{webview2_installer_path}}\"\n!define UNINSTKEY \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${PRODUCTNAME}\"\n!define MANUPRODUCTKEY \"Software\\${MANUFACTURER}\\${PRODUCTNAME}\"\n!define UNINSTALLERSIGNCOMMAND \"{{uninstaller_sign_cmd}}\"\n\nName \"${PRODUCTNAME}\"\nBrandingText \"${COPYRIGHT}\"\nOutFile \"${OUTFILE}\"\n\nVIProductVersion \"${VERSIONWITHBUILD}\"\nVIAddVersionKey \"ProductName\" \"${PRODUCTNAME}\"\nVIAddVersionKey \"FileDescription\" \"${SHORTDESCRIPTION}\"\nVIAddVersionKey \"LegalCopyright\" \"${COPYRIGHT}\"\nVIAddVersionKey \"FileVersion\" \"${VERSION}\"\nVIAddVersionKey \"ProductVersion\" \"${VERSION}\"\n\n; Plugins path, currently exists for linux only\n!if \"${PLUGINSPATH}\" != \"\"\n    !addplugindir \"${PLUGINSPATH}\"\n!endif\n\n!if \"${UNINSTALLERSIGNCOMMAND}\" != \"\"\n  !uninstfinalize '${UNINSTALLERSIGNCOMMAND}'\n!endif\n\n; Handle install mode, `perUser`, `perMachine` or `both`\n!if \"${INSTALLMODE}\" == \"perMachine\"\n  RequestExecutionLevel highest\n!endif\n\n!if \"${INSTALLMODE}\" == \"currentUser\"\n  RequestExecutionLevel user\n!endif\n\n!if \"${INSTALLMODE}\" == \"both\"\n  !define MULTIUSER_MUI\n  !define MULTIUSER_INSTALLMODE_INSTDIR \"${PRODUCTNAME}\"\n  !define MULTIUSER_INSTALLMODE_COMMANDLINE\n  !if \"${ARCH}\" == \"x64\"\n    !define MULTIUSER_USE_PROGRAMFILES64\n  !else if \"${ARCH}\" == \"arm64\"\n    !define MULTIUSER_USE_PROGRAMFILES64\n  !endif\n  !define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY \"${UNINSTKEY}\"\n  !define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME \"CurrentUser\"\n  !define MULTIUSER_INSTALLMODEPAGE_SHOWUSERNAME\n  !define MULTIUSER_INSTALLMODE_FUNCTION RestorePreviousInstallLocation\n  !define MULTIUSER_EXECUTIONLEVEL Highest\n  !include MultiUser.nsh\n!endif\n\n; installer icon\n!if \"${INSTALLERICON}\" != \"\"\n  !define MUI_ICON \"${INSTALLERICON}\"\n!endif\n\n; installer sidebar image\n!if \"${SIDEBARIMAGE}\" != \"\"\n  !define MUI_WELCOMEFINISHPAGE_BITMAP \"${SIDEBARIMAGE}\"\n!endif\n\n; installer header image\n!if \"${HEADERIMAGE}\" != \"\"\n  !define MUI_HEADERIMAGE\n  !define MUI_HEADERIMAGE_BITMAP  \"${HEADERIMAGE}\"\n!endif\n\n; Define registry key to store installer language\n!define MUI_LANGDLL_REGISTRY_ROOT \"HKCU\"\n!define MUI_LANGDLL_REGISTRY_KEY \"${MANUPRODUCTKEY}\"\n!define MUI_LANGDLL_REGISTRY_VALUENAME \"Installer Language\"\n\n; Installer pages, must be ordered as they appear\n; 1. Welcome Page\n!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive\n!insertmacro MUI_PAGE_WELCOME\n\n; 2. License Page (if defined)\n!if \"${LICENSE}\" != \"\"\n  !define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive\n  !insertmacro MUI_PAGE_LICENSE \"${LICENSE}\"\n!endif\n\n; 3. Install mode (if it is set to `both`)\n!if \"${INSTALLMODE}\" == \"both\"\n  !define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive\n  !insertmacro MULTIUSER_PAGE_INSTALLMODE\n!endif\n\n\n; 4. Custom page to ask user if he wants to reinstall/uninstall\n;    only if a previous installtion was detected\nVar ReinstallPageCheck\nPage custom PageReinstall PageLeaveReinstall\nFunction PageReinstall\n  Call CheckOldMsiBloopInstall\n  StrCmp $R6 \"\" 0 compare_version\n\n  ; Check if there is an existing installation, if not, abort the reinstall page\n  ReadRegStr $R0 SHCTX \"${UNINSTKEY}\" \"\"\n  ReadRegStr $R1 SHCTX \"${UNINSTKEY}\" \"UninstallString\"\n  ${IfThen} \"$R0$R1\" == \"\" ${|} Abort ${|}\n\n  ; Compare this installar version with the existing installation\n  ; and modify the messages presented to the user accordingly\n  compare_version:\n  StrCpy $R4 \"$(older)\"\n  ${If} $R7 == \"msi\"\n    ReadRegStr $R0 HKLM \"$R6\" \"DisplayVersion\"\n  ${Else}\n    ReadRegStr $R0 SHCTX \"${UNINSTKEY}\" \"DisplayVersion\"\n  ${EndIf}\n  ${IfThen} $R0 == \"\" ${|} StrCpy $R4 \"$(unknown)\" ${|}\n\n  nsis_tauri_utils::SemverCompare \"${VERSION}\" $R0\n  Pop $R0\n  ; Reinstalling the same version\n  ${If} $R0 == 0\n    StrCpy $R1 \"$(alreadyInstalledLong)\"\n    StrCpy $R2 \"$(addOrReinstall)\"\n    StrCpy $R3 \"$(uninstallApp)\"\n    !insertmacro MUI_HEADER_TEXT \"$(alreadyInstalled)\" \"$(chooseMaintenanceOption)\"\n    StrCpy $R5 \"2\"\n  ; Upgrading\n  ${ElseIf} $R0 == 1\n    StrCpy $R1 \"$(olderOrUnknownVersionInstalled)\"\n    StrCpy $R2 \"$(uninstallBeforeInstalling)\"\n    StrCpy $R3 \"$(dontUninstall)\"\n    !insertmacro MUI_HEADER_TEXT \"$(alreadyInstalled)\" \"$(choowHowToInstall)\"\n    StrCpy $R5 \"1\"\n  ; Downgrading\n  ${ElseIf} $R0 == -1\n    StrCpy $R1 \"$(newerVersionInstalled)\"\n    StrCpy $R2 \"$(uninstallBeforeInstalling)\"\n    !if \"${ALLOWDOWNGRADES}\" == \"true\"\n      StrCpy $R3 \"$(dontUninstall)\"\n    !else\n      StrCpy $R3 \"$(dontUninstallDowngrade)\"\n    !endif\n    !insertmacro MUI_HEADER_TEXT \"$(alreadyInstalled)\" \"$(choowHowToInstall)\"\n    StrCpy $R5 \"1\"\n  ${Else}\n    Abort\n  ${EndIf}\n\n  Call SkipIfPassive\n\n  nsDialogs::Create 1018\n  Pop $R4\n  ${IfThen} $(^RTL) == 1 ${|} nsDialogs::SetRTL $(^RTL) ${|}\n\n  ${NSD_CreateLabel} 0 0 100% 24u $R1\n  Pop $R1\n\n  ${NSD_CreateRadioButton} 30u 50u -30u 8u $R2\n  Pop $R2\n  ${NSD_OnClick} $R2 PageReinstallUpdateSelection\n\n  ${NSD_CreateRadioButton} 30u 70u -30u 8u $R3\n  Pop $R3\n  ; disable this radio button if downgrading and downgrades are disabled\n  !if \"${ALLOWDOWNGRADES}\" == \"false\"\n    ${IfThen} $R0 == -1 ${|} EnableWindow $R3 0 ${|}\n  !endif\n  ${NSD_OnClick} $R3 PageReinstallUpdateSelection\n\n  ; Check the first radio button if this the first time\n  ; we enter this page or if the second button wasn't\n  ; selected the last time we were on this page\n  ${If} $ReinstallPageCheck != 2\n    SendMessage $R2 ${BM_SETCHECK} ${BST_CHECKED} 0\n  ${Else}\n    SendMessage $R3 ${BM_SETCHECK} ${BST_CHECKED} 0\n  ${EndIf}\n\n  ${NSD_SetFocus} $R2\n  nsDialogs::Show\nFunctionEnd\nFunction PageReinstallUpdateSelection\n  ${NSD_GetState} $R2 $R1\n  ${If} $R1 == ${BST_CHECKED}\n    StrCpy $ReinstallPageCheck 1\n  ${Else}\n    StrCpy $ReinstallPageCheck 2\n  ${EndIf}\nFunctionEnd\nFunction PageLeaveReinstall\n  ${NSD_GetState} $R2 $R1\n\n  ; $R5 holds whether we are reinstalling the same version or not\n  ; $R5 == \"1\" -> different versions\n  ; $R5 == \"2\" -> same version\n  ;\n  ; $R1 holds the radio buttons state. its meaning is dependant on the context\n  StrCmp $R5 \"1\" 0 +2 ; Existing install is not the same version?\n    StrCmp $R1 \"1\" reinst_uninstall reinst_done ; $R1 == \"1\", then user chose to uninstall existing version, otherwise skip uninstalling\n  StrCmp $R1 \"1\" reinst_done ; Same version? skip uninstalling\n\n  reinst_uninstall:\n    HideWindow\n    ClearErrors\n\n    ${If} $R7 == \"msi\"\n      ReadRegStr $R1 HKLM \"$R6\" \"UninstallString\"\n      ExecWait '$R1' $0\n    ${Else}\n      ReadRegStr $4 SHCTX \"${MANUPRODUCTKEY}\" \"\"\n      ReadRegStr $R1 SHCTX \"${UNINSTKEY}\" \"UninstallString\"\n      ExecWait '$R1 /P _?=$4' $0\n    ${EndIf}\n\n    BringToFront\n\n    ${IfThen} ${Errors} ${|} StrCpy $0 2 ${|} ; ExecWait failed, set fake exit code\n\n    ${If} $0 <> 0\n    ${OrIf} ${FileExists} \"$INSTDIR\\${MAINBINARYNAME}.exe\"\n      ${If} $0 = 1 ; User aborted uninstaller?\n        StrCmp $R5 \"2\" 0 +2 ; Is the existing install the same version?\n          Quit ; ...yes, already installed, we are done\n        Abort\n      ${EndIf}\n      MessageBox MB_ICONEXCLAMATION \"$(unableToUninstall)\"\n      Abort\n    ${Else}\n      StrCpy $0 $R1 1\n      ${IfThen} $0 == '\"' ${|} StrCpy $R1 $R1 -1 1 ${|} ; Strip quotes from UninstallString\n      Delete $R1\n      RMDir $INSTDIR\n    ${EndIf}\n  reinst_done:\nFunctionEnd\n\n; 5. Choose install directoy page\n!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive\n!insertmacro MUI_PAGE_DIRECTORY\n\n; 6. Start menu shortcut page\n!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive\nVar AppStartMenuFolder\n!insertmacro MUI_PAGE_STARTMENU Application $AppStartMenuFolder\n\n; 7. Installation page\n!insertmacro MUI_PAGE_INSTFILES\n\n; 8. Finish page\n;\n; Don't auto jump to finish page after installation page,\n; because the installation page has useful info that can be used debug any issues with the installer.\n!define MUI_FINISHPAGE_NOAUTOCLOSE\n; Use show readme button in the finish page as a button create a desktop shortcut\n!define MUI_FINISHPAGE_SHOWREADME\n!define MUI_FINISHPAGE_SHOWREADME_TEXT \"$(createDesktop)\"\n!define MUI_FINISHPAGE_SHOWREADME_FUNCTION CreateDesktopShortcut\n; Show run app after installation.\n!define MUI_FINISHPAGE_RUN \"$INSTDIR\\${MAINBINARYNAME}.exe\"\n!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive\n!insertmacro MUI_PAGE_FINISH\n\n; Uninstaller Pages\n; 1. Confirm uninstall page\nVar DeleteAppDataCheckbox\nVar DeleteAppDataCheckboxState\n!define /ifndef WS_EX_LAYOUTRTL         0x00400000\n!define MUI_PAGE_CUSTOMFUNCTION_SHOW un.ConfirmShow\nFunction un.ConfirmShow\n    FindWindow $1 \"#32770\" \"\" $HWNDPARENT ; Find inner dialog\n    ${If} $(^RTL) == 1\n      System::Call 'USER32::CreateWindowEx(i${__NSD_CheckBox_EXSTYLE}|${WS_EX_LAYOUTRTL},t\"${__NSD_CheckBox_CLASS}\",t \"$(deleteAppData)\",i${__NSD_CheckBox_STYLE},i 50,i 100,i 400, i 25,i$1,i0,i0,i0)i.s'\n    ${Else}\n      System::Call 'USER32::CreateWindowEx(i${__NSD_CheckBox_EXSTYLE},t\"${__NSD_CheckBox_CLASS}\",t \"$(deleteAppData)\",i${__NSD_CheckBox_STYLE},i 0,i 100,i 400, i 25,i$1,i0,i0,i0)i.s'\n    ${EndIf}\n    Pop $DeleteAppDataCheckbox\n    SendMessage $HWNDPARENT ${WM_GETFONT} 0 0 $1\n    SendMessage $DeleteAppDataCheckbox ${WM_SETFONT} $1 1\nFunctionEnd\n!define MUI_PAGE_CUSTOMFUNCTION_LEAVE un.ConfirmLeave\nFunction un.ConfirmLeave\n    SendMessage $DeleteAppDataCheckbox ${BM_GETCHECK} 0 0 $DeleteAppDataCheckboxState\nFunctionEnd\n!insertmacro MUI_UNPAGE_CONFIRM\n\n; 2. Uninstalling Page\n!insertmacro MUI_UNPAGE_INSTFILES\n\n;Languages\n{{#each languages}}\n!insertmacro MUI_LANGUAGE \"{{this}}\"\n{{/each}}\n!insertmacro MUI_RESERVEFILE_LANGDLL\n{{#each language_files}}\n  !include \"{{this}}\"\n{{/each}}\n\n!macro SetContext\n  !if \"${INSTALLMODE}\" == \"currentUser\"\n    SetShellVarContext current\n  !else if \"${INSTALLMODE}\" == \"perMachine\"\n    SetShellVarContext all\n  !endif\n\n  ${If} ${RunningX64}\n    !if \"${ARCH}\" == \"x64\"\n      SetRegView 64\n    !else if \"${ARCH}\" == \"arm64\"\n      SetRegView 64\n    !else\n      SetRegView 32\n    !endif\n  ${EndIf}\n!macroend\n\nVar PassiveMode\nFunction .onInit\n  ${GetOptions} $CMDLINE \"/P\" $PassiveMode\n  IfErrors +2 0\n    StrCpy $PassiveMode 1\n\n  !if \"${DISPLAYLANGUAGESELECTOR}\" == \"true\"\n    !insertmacro MUI_LANGDLL_DISPLAY\n  !endif\n\n  !insertmacro SetContext\n\n  ${If} $INSTDIR == \"\"\n    ; Set default install location\n    !if \"${INSTALLMODE}\" == \"perMachine\"\n      ${If} ${RunningX64}\n        !if \"${ARCH}\" == \"x64\"\n          StrCpy $INSTDIR \"$PROGRAMFILES64\\${PRODUCTNAME}\"\n        !else if \"${ARCH}\" == \"arm64\"\n          StrCpy $INSTDIR \"$PROGRAMFILES64\\${PRODUCTNAME}\"\n        !else\n          StrCpy $INSTDIR \"$PROGRAMFILES\\${PRODUCTNAME}\"\n        !endif\n      ${Else}\n        StrCpy $INSTDIR \"$PROGRAMFILES\\${PRODUCTNAME}\"\n      ${EndIf}\n    !else if \"${INSTALLMODE}\" == \"currentUser\"\n      StrCpy $INSTDIR \"$LOCALAPPDATA\\${PRODUCTNAME}\"\n    !endif\n\n    Call RestorePreviousInstallLocation\n  ${EndIf}\n\n\n  !if \"${INSTALLMODE}\" == \"both\"\n    !insertmacro MULTIUSER_INIT\n  !endif\nFunctionEnd\n\n\nSection EarlyChecks\n  ; Abort silent installer if downgrades is disabled\n  !if \"${ALLOWDOWNGRADES}\" == \"false\"\n  IfSilent 0 silent_downgrades_done\n    ; If downgrading\n    ${If} $R0 == -1\n      System::Call 'kernel32::AttachConsole(i -1)i.r0'\n      ${If} $0 != 0\n        System::Call 'kernel32::GetStdHandle(i -11)i.r0'\n        System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)' ; set red color\n        FileWrite $0 \"$(silentDowngrades)\"\n      ${EndIf}\n      Abort\n    ${EndIf}\n  silent_downgrades_done:\n  !endif\n\nSectionEnd\n\nSection WebView2\n  ; Check if Webview2 is already installed and skip this section\n  ${If} ${RunningX64}\n    ReadRegStr $4 HKLM \"SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}\" \"pv\"\n  ${Else}\n    ReadRegStr $4 HKLM \"SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}\" \"pv\"\n  ${EndIf}\n  ReadRegStr $5 HKCU \"SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}\" \"pv\"\n\n  StrCmp $4 \"\" 0 webview2_done\n  StrCmp $5 \"\" 0 webview2_done\n\n  ; Webview2 install modes\n  !if \"${INSTALLWEBVIEW2MODE}\" == \"downloadBootstrapper\"\n    Delete \"$TEMP\\MicrosoftEdgeWebview2Setup.exe\"\n    DetailPrint \"$(webview2Downloading)\"\n    nsis_tauri_utils::download \"https://go.microsoft.com/fwlink/p/?LinkId=2124703\" \"$TEMP\\MicrosoftEdgeWebview2Setup.exe\"\n    Pop $0\n    ${If} $0 == 0\n      DetailPrint \"$(webview2DownloadSuccess)\"\n    ${Else}\n      DetailPrint \"$(webview2DownloadError)\"\n      Abort \"$(webview2AbortError)\"\n    ${EndIf}\n    StrCpy $6 \"$TEMP\\MicrosoftEdgeWebview2Setup.exe\"\n    Goto install_webview2\n  !endif\n\n  !if \"${INSTALLWEBVIEW2MODE}\" == \"embedBootstrapper\"\n    Delete \"$TEMP\\MicrosoftEdgeWebview2Setup.exe\"\n    File \"/oname=$TEMP\\MicrosoftEdgeWebview2Setup.exe\" \"${WEBVIEW2BOOTSTRAPPERPATH}\"\n    DetailPrint \"$(installingWebview2)\"\n    StrCpy $6 \"$TEMP\\MicrosoftEdgeWebview2Setup.exe\"\n    Goto install_webview2\n  !endif\n\n  !if \"${INSTALLWEBVIEW2MODE}\" == \"offlineInstaller\"\n    Delete \"$TEMP\\MicrosoftEdgeWebView2RuntimeInstaller.exe\"\n    File \"/oname=$TEMP\\MicrosoftEdgeWebView2RuntimeInstaller.exe\" \"${WEBVIEW2INSTALLERPATH}\"\n    DetailPrint \"$(installingWebview2)\"\n    StrCpy $6 \"$TEMP\\MicrosoftEdgeWebView2RuntimeInstaller.exe\"\n    Goto install_webview2\n  !endif\n\n  Goto webview2_done\n\n  install_webview2:\n    DetailPrint \"$(installingWebview2)\"\n    ; $6 holds the path to the webview2 installer\n    ExecWait \"$6 ${WEBVIEW2INSTALLERARGS} /install\" $1\n    ${If} $1 == 0\n      DetailPrint \"$(webview2InstallSuccess)\"\n    ${Else}\n      DetailPrint \"$(webview2InstallError)\"\n      Abort \"$(webview2AbortError)\"\n    ${EndIf}\n  webview2_done:\nSectionEnd\n\n!macro CheckIfAppIsRunning\n  nsis_tauri_utils::FindProcess \"${MAINBINARYNAME}.exe\"\n  Pop $R0\n  ${If} $R0 = 0\n      IfSilent kill 0\n      ${IfThen} $PassiveMode != 1 ${|} MessageBox MB_OKCANCEL \"$(appRunningOkKill)\" IDOK kill IDCANCEL cancel ${|}\n      kill:\n        nsis_tauri_utils::KillProcess \"${MAINBINARYNAME}.exe\"\n        Pop $R0\n        Sleep 500\n        ${If} $R0 = 0\n          Goto app_check_done\n        ${Else}\n          IfSilent silent ui\n          silent:\n            System::Call 'kernel32::AttachConsole(i -1)i.r0'\n            ${If} $0 != 0\n              System::Call 'kernel32::GetStdHandle(i -11)i.r0'\n              System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)' ; set red color\n              FileWrite $0 \"$(appRunning)$\\n\"\n            ${EndIf}\n            Abort\n          ui:\n            Abort \"$(failedToKillApp)\"\n        ${EndIf}\n      cancel:\n        Abort \"$(appRunning)\"\n  ${EndIf}\n  app_check_done:\n!macroend\n\nVar AppSize\nSection Install\n  SetOutPath $INSTDIR\n  StrCpy $AppSize 0\n\n  !insertmacro CheckIfAppIsRunning\n\n  ; Copy main executable\n  File \"${MAINBINARYSRCPATH}\"\n  ${GetSize} \"$INSTDIR\" \"/M=${MAINBINARYNAME}.exe /S=0B\" $0 $1 $2\n  IntOp $AppSize $AppSize + $0\n\n  ; Copy resources\n  {{#each resources}}\n    CreateDirectory \"$INSTDIR\\\\{{this.[0]}}\"\n    File /a \"/oname={{this.[1]}}\" \"{{@key}}\"\n    ${GetSize} \"$INSTDIR\" \"/M={{this.[1]}} /S=0B\" $0 $1 $2\n    IntOp $AppSize $AppSize + $0\n  {{/each}}\n\n  ; Copy external binaries\n  {{#each binaries}}\n    File /a \"/oname={{this}}\" \"{{@key}}\"\n    ${GetSize} \"$INSTDIR\" \"/M={{this}} /S=0B\" $0 $1 $2\n    IntOp $AppSize $AppSize + $0\n  {{/each}}\n\n  ; Create uninstaller\n  WriteUninstaller \"$INSTDIR\\uninstall.exe\"\n\n  ; Save $INSTDIR in registry for future installations\n  WriteRegStr SHCTX \"${MANUPRODUCTKEY}\" \"\" $INSTDIR\n\n  !if \"${INSTALLMODE}\" == \"both\"\n    ; Save install mode to be selected by default for the next installation such as updating\n    ; or when uninstalling\n    WriteRegStr SHCTX \"${UNINSTKEY}\" $MultiUser.InstallMode 1\n  !endif\n\n  ; Registry information for add/remove programs\n  WriteRegStr SHCTX \"${UNINSTKEY}\" \"DisplayName\" \"${PRODUCTNAME}\"\n  WriteRegStr SHCTX \"${UNINSTKEY}\" \"DisplayIcon\" \"$\\\"$INSTDIR\\${MAINBINARYNAME}.exe$\\\"\"\n  WriteRegStr SHCTX \"${UNINSTKEY}\" \"DisplayVersion\" \"${VERSION}\"\n  WriteRegStr SHCTX \"${UNINSTKEY}\" \"Publisher\" \"${MANUFACTURER}\"\n  WriteRegStr SHCTX \"${UNINSTKEY}\" \"InstallLocation\" \"$\\\"$INSTDIR$\\\"\"\n  WriteRegStr SHCTX \"${UNINSTKEY}\" \"UninstallString\" \"$\\\"$INSTDIR\\uninstall.exe$\\\"\"\n  WriteRegDWORD SHCTX \"${UNINSTKEY}\" \"NoModify\" \"1\"\n  WriteRegDWORD SHCTX \"${UNINSTKEY}\" \"NoRepair\" \"1\"\n  IntOp $AppSize $AppSize / 1000\n  IntFmt $AppSize \"0x%08X\" $AppSize\n  WriteRegDWORD SHCTX \"${UNINSTKEY}\" \"EstimatedSize\" \"$AppSize\"\n\n  ; Create start menu shortcut (GUI)\n  !insertmacro MUI_STARTMENU_WRITE_BEGIN Application\n    Call CreateStartMenuShortcut\n  !insertmacro MUI_STARTMENU_WRITE_END\n\n  ; Create shortcuts for silent and passive installers, which\n  ; can be disabled by passing `/NS` flag\n  ; GUI installer has buttons for users to control creating them\n  IfSilent check_ns_flag 0\n  ${IfThen} $PassiveMode == 1 ${|} Goto check_ns_flag ${|}\n  Goto shortcuts_done\n  check_ns_flag:\n    ${GetOptions} $CMDLINE \"/NS\" $R0\n    IfErrors 0 shortcuts_done\n      Call CreateDesktopShortcut\n      Call CreateStartMenuShortcut\n  shortcuts_done:\n\n  ; Auto close this page for passive mode\n  ${IfThen} $PassiveMode == 1 ${|} SetAutoClose true ${|}\nSectionEnd\n\nFunction .onInstSuccess\n  ; Check for `/R` flag only in silent and passive installers because\n  ; GUI installer has a toggle for the user to (re)start the app\n  IfSilent check_r_flag 0\n  ${IfThen} $PassiveMode == 1 ${|} Goto check_r_flag ${|}\n  Goto run_done\n  check_r_flag:\n    ${GetOptions} $CMDLINE \"/R\" $R0\n    IfErrors run_done 0\n      Exec '\"$INSTDIR\\${MAINBINARYNAME}.exe\"'\n  run_done:\nFunctionEnd\n\nFunction un.onInit\n  !insertmacro SetContext\n\n  !if \"${INSTALLMODE}\" == \"both\"\n    !insertmacro MULTIUSER_UNINIT\n  !endif\n\n  !insertmacro MUI_UNGETLANGUAGE\nFunctionEnd\n\nSection Uninstall\n  !insertmacro CheckIfAppIsRunning\n\n  ; Delete the app directory and its content from disk\n  ; Copy main executable\n  Delete \"$INSTDIR\\${MAINBINARYNAME}.exe\"\n\n  ; Delete resources\n  {{#each resources}}\n    Delete \"$INSTDIR\\\\{{this.[1]}}\"\n    RMDir \"$INSTDIR\\\\{{this.[0]}}\"\n  {{/each}}\n\n  ; Delete external binaries\n  {{#each binaries}}\n    Delete \"$INSTDIR\\\\{{this}}\"\n  {{/each}}\n\n  ; Delete uninstaller\n  Delete \"$INSTDIR\\uninstall.exe\"\n\n  RMDir \"$INSTDIR\"\n\n  ; Remove start menu shortcut\n  !insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder\n  Delete \"$SMPROGRAMS\\$AppStartMenuFolder\\${MAINBINARYNAME}.lnk\"\n  RMDir \"$SMPROGRAMS\\$AppStartMenuFolder\"\n\n  ; Remove desktop shortcuts\n  Delete \"$DESKTOP\\${MAINBINARYNAME}.lnk\"\n\n  ; Remove registry information for add/remove programs\n  !if \"${INSTALLMODE}\" == \"both\"\n    DeleteRegKey SHCTX \"${UNINSTKEY}\"\n  !else if \"${INSTALLMODE}\" == \"perMachine\"\n    DeleteRegKey HKLM \"${UNINSTKEY}\"\n  !else\n    DeleteRegKey HKCU \"${UNINSTKEY}\"\n  !endif\n\n  DeleteRegValue HKCU \"${MANUPRODUCTKEY}\" \"Installer Language\"\n\n  ; Delete app data\n  ${If} $DeleteAppDataCheckboxState == 1\n    SetShellVarContext current\n    RmDir /r \"$APPDATA\\${BUNDLEID}\"\n    RmDir /r \"$LOCALAPPDATA\\${BUNDLEID}\"\n  ${EndIf}\n\n  ${GetOptions} $CMDLINE \"/P\" $R0\n  IfErrors +2 0\n    SetAutoClose true\nSectionEnd\n\nFunction RestorePreviousInstallLocation\n  ReadRegStr $4 SHCTX \"${MANUPRODUCTKEY}\" \"\"\n  StrCmp $4 \"\" +2 0\n    StrCpy $INSTDIR $4\nFunctionEnd\n\nFunction SkipIfPassive\n  ${IfThen} $PassiveMode == 1  ${|} Abort ${|}\nFunctionEnd\n\nFunction CreateDesktopShortcut\n  CreateShortcut \"$DESKTOP\\${MAINBINARYNAME}.lnk\" \"$INSTDIR\\${MAINBINARYNAME}.exe\"\n  ApplicationID::Set \"$DESKTOP\\${MAINBINARYNAME}.lnk\" \"${BUNDLEID}\"\nFunctionEnd\n\nFunction CreateStartMenuShortcut\n  CreateDirectory \"$SMPROGRAMS\\$AppStartMenuFolder\"\n  CreateShortcut \"$SMPROGRAMS\\$AppStartMenuFolder\\${MAINBINARYNAME}.lnk\" \"$INSTDIR\\${MAINBINARYNAME}.exe\"\n  ApplicationID::Set \"$SMPROGRAMS\\$AppStartMenuFolder\\${MAINBINARYNAME}.lnk\" \"${BUNDLEID}\"\nFunctionEnd\n\nVar CurrentKey\nVar KeyIdx\nFunction CheckOldMsiBloopInstall\n  StrCpy $KeyIdx 0\n  loop:\n    EnumRegKey $CurrentKey HKLM \"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\" $KeyIdx\n    ; Check against each GUID in the provided list\n    ; 0.5.7\n    StrCmp $CurrentKey \"{2C8429F8-54EB-467C-BE37-C8DCD3FCE6BC}\" found\n    ; 0.5.6\n    StrCmp $CurrentKey \"{4A778A50-FD64-4E96-BA76-35F5D66B8899}\" found\n    ; 0.5.5\n    StrCmp $CurrentKey \"{ABCE7896-96F3-4DE1-885F-2634F0CAB59A}\" found\n    ; 0.5.4\n    StrCmp $CurrentKey \"{6ED61500-55C2-4E6F-83EE-951F91904EA5}\" found\n    ; 0.5.3\n    StrCmp $CurrentKey \"{E86D72B5-7DB3-4601-9926-77B2767730DB}\" found\n    ; 0.5.2\n    StrCmp $CurrentKey \"{97C55FFD-6A65-4BA1-ABAE-40EDE303A5F6}\" found\n    ; 0.5.1\n    StrCmp $CurrentKey \"{C84E9599-EAFF-4F07-B216-9EF97F07A25A}\" found\n    ; 0.5.0\n    StrCmp $CurrentKey \"{0F439DE0-93F0-48E9-BEFE-6D43F203A231}\" found\n    ; 0.4.17\n    StrCmp $CurrentKey \"{1302A8FE-5796-4B36-AD6C-2F525CC18158} \" found\n    ; 0.4.16\n    StrCmp $CurrentKey \"{EC1C0015-E851-4D47-B758-78FAFDCDA9B5}\" found\n    ; 0.4.15\n    StrCmp $CurrentKey \"{7BADAB06-87E8-4DF5-9262-0917D61D70F8}\" found\n    ; 0.4.14\n    StrCmp $CurrentKey \"{B3030CB3-5DDC-41A8-84FA-A3A02BF1054B}\" found\n    ; 0.4.13\n    StrCmp $CurrentKey \"{14515A1F-8824-49A1-80A6-E43F529627D2}\" found\n    ; 0.4.12\n    StrCmp $CurrentKey \"{97594AB3-17EB-4B4D-BF6E-BC14AA55439C}\" found\n    ; 0.4.11\n    StrCmp $CurrentKey \"{4B855395-4A08-4DF3-9EC9-21BD49658925}\" found\n    ; 0.4.10 (had no Windows build)\n    ; ...\n    ; 0.4.9\n    StrCmp $CurrentKey \"{7954AC40-85A5-4705-93C8-7ECE1EA779A8}\" found\n    ; 0.4.8\n    StrCmp $CurrentKey \"{756A33B8-AABE-4010-88E1-CD967371B26A}\" found\n\n    StrCmp $CurrentKey \"\" notfound\n\n    ; If none of the GUIDs match, continue with the next key\n    IntOp $KeyIdx $KeyIdx + 1\n    Goto loop\n  notfound:\n    StrCpy $R6 \"\"\n    Goto done\n  found:\n    StrCpy $R7 \"msi\"\n    StrCpy $R6 \"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$CurrentKey\"\n  done:\nFunctionEnd\n"
  },
  {
    "path": "apps/desktop/src-tauri/model/ggml/tokenizer.json",
    "content": "version https://git-lfs.github.com/spec/v1\noid sha256:65c14be98b474267fa3b8129ac643ca9356234b54bb19d8d1b95ca67b55f469f\nsize 466248\n"
  },
  {
    "path": "apps/desktop/src-tauri/model/model.onnx",
    "content": "version https://git-lfs.github.com/spec/v1\noid sha256:712cda19c5e4803e4d2f1d5ae972083537ffc2531759b67641622251c8ec3caf\nsize 22998413\n"
  },
  {
    "path": "apps/desktop/src-tauri/model/special_tokens_map.json",
    "content": "version https://git-lfs.github.com/spec/v1\noid sha256:b6d346be366a7d1d48332dbc9fdf3bf8960b5d879522b7799ddba59e76237ee3\nsize 125\n"
  },
  {
    "path": "apps/desktop/src-tauri/model/tokenizer.json",
    "content": "version https://git-lfs.github.com/spec/v1\noid sha256:d241a60d5e8f04cc1b2b3e9ef7a4921b27bf526d9f6050ab90f9267a1f9e5c66\nsize 711396\n"
  },
  {
    "path": "apps/desktop/src-tauri/model/tokenizer_config.json",
    "content": "version https://git-lfs.github.com/spec/v1\noid sha256:5a0279327ed471a6e4cfb88b6f62f73d8e8ddf3fdc1e9cbb98f692b1b9ae0d91\nsize 579\n"
  },
  {
    "path": "apps/desktop/src-tauri/model/vocab.txt",
    "content": "version https://git-lfs.github.com/spec/v1\noid sha256:07eced375cec144d27c900241f3e339478dec958f92fddbc551f295c992038a3\nsize 231508\n"
  },
  {
    "path": "apps/desktop/src-tauri/src/QDRANT_CONFIG_TEMPLATE.yml",
    "content": "telemetry_disabled: true\nstorage:\n  # Where to store all the data\n  storage_path: {storage}\n\n  # Where to store snapshots\n  snapshots_path: {snapshots}\n\n  # If true - point's payload will not be stored in memory.\n  # It will be read from the disk every time it is requested.\n  # This setting saves RAM by (slightly) increasing the response time.\n  # Note: those payload values that are involved in filtering and are indexed - remain in RAM.\n  on_disk_payload: false\n\n  # Write-ahead-log related configuration\n  wal:\n    # Size of a single WAL segment\n    wal_capacity_mb: 32\n\n    # Number of WAL segments to create ahead of actual data requirement\n    wal_segments_ahead: 0\n\n\n  performance:\n    # Number of parallel threads used for search operations. If 0 - auto selection.\n    max_search_threads: 4\n\n  optimizers:\n    # The minimal fraction of deleted vectors in a segment, required to perform segment optimization\n    deleted_threshold: 0.2\n\n    # The minimal number of vectors in a segment, required to perform segment optimization\n    vacuum_min_vector_number: 1000\n\n    # Target amount of segments optimizer will try to keep.\n    # Real amount of segments may vary depending on multiple parameters:\n    #  - Amount of stored points\n    #  - Current write RPS\n    #\n    # It is recommended to select default number of segments as a factor of the number of search threads,\n    # so that each segment would be handled evenly by one of the threads.\n    # If `default_segment_number = 0`, will be automatically selected by the number of available CPUs\n    default_segment_number: 2\n\n    # Do not create segments larger this size (in KiloBytes).\n    # Large segments might require disproportionately long indexation times,\n    # therefore it makes sense to limit the size of segments.\n    #\n    # If indexation speed have more priority for your - make this parameter lower.\n    # If search speed is more important - make this parameter higher.\n    # Note: 1Kb = 1 vector of size 256\n    # If not set, will be automatically selected considering the number of available CPUs.\n    max_segment_size_kb: null\n\n    # Maximum size (in KiloBytes) of vectors to store in-memory per segment.\n    # Segments larger than this threshold will be stored as read-only memmaped file.\n    # To enable memmap storage, lower the threshold\n    # Note: 1Kb = 1 vector of size 256\n    # If not set, mmap will not be used.\n    memmap_threshold_kb: null\n\n    # Maximum size (in KiloBytes) of vectors allowed for plain index.\n    # Default value based on https://github.com/google-research/google-research/blob/master/scann/docs/algorithms.md\n    # Note: 1Kb = 1 vector of size 256\n    indexing_threshold_kb: 20000\n\n    # Interval between forced flushes.\n    flush_interval_sec: 5\n    \n    # Max number of threads, which can be used for optimization.\n    max_optimization_threads: 1\n\n  # Default parameters of HNSW Index. Could be overridden for each collection individually\n  hnsw_index:\n    # Number of edges per node in the index graph. Larger the value - more accurate the search, more space required.\n    m: 16\n    # Number of neighbours to consider during the index building. Larger the value - more accurate the search, more time required to build index.\n    ef_construct: 100\n    # Minimal size (in KiloBytes) of vectors for additional payload-based indexing.\n    # If payload chunk is smaller than `full_scan_threshold_kb` additional indexing won't be used -\n    # in this case full-scan search should be preferred by query planner and additional indexing is not required.\n    # Note: 1Kb = 1 vector of size 256\n    full_scan_threshold_kb: 10000\n    # Number of parallel threads used for background index building. If 0 - auto selection.\n    max_indexing_threads: 0\n\nservice:\n\n  # Maximum size of POST data in a single request in megabytes\n  max_request_size_mb: 32\n\n  # Number of parallel workers used for serving the api. If 0 - equal to the number of available cores.\n  # If missing - Same as storage.max_search_threads\n  max_workers: 0\n\n  # Host to bind the service on\n  host: 127.0.0.1\n\n  # HTTP port to bind the service on\n  http_port: 6333\n\n  # gRPC port to bind the service on.\n  # If `null` - gRPC is disabled. Default: null\n  # Uncomment to enable gRPC:\n  grpc_port: 6334\n\n  # Enable CORS headers in REST API.\n  # If enabled, browsers would be allowed to query REST endpoints regardless of query origin.\n  # More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS\n  # Default: true\n  enable_cors: true\n\ncluster:\n  # Use `enabled: true` to run Qdrant in distributed deployment mode\n  enabled: false\n\n  # Configuration of the inter-cluster communication\n  p2p:\n    # Port for internal communication between peers\n    port: 6335\n\n  # Configuration related to distributed consensus algorithm\n  consensus:\n    # How frequently peers should ping each other.\n    # Setting this parameter to lower value will allow consensus\n    # to detect disconnected nodes earlier, but too frequent\n    # tick period may create significant network and CPU overhead.\n    # We encourage you NOT to change this parameter unless you know what you are doing.\n    tick_period_ms: 100\n\n"
  },
  {
    "path": "apps/desktop/src-tauri/src/backend.rs",
    "content": "use bleep::{Application, Configuration, Environment};\nuse tracing::error;\n\nuse super::{Manager, Payload, Runtime};\nuse std::thread;\nuse std::time::Duration;\n\n#[tauri::command]\npub fn get_last_log_file(config: tauri::State<Configuration>) -> Option<String> {\n    let log_dir = config.log_dir();\n\n    let mut entries = std::fs::read_dir(log_dir)\n        .ok()?\n        .collect::<Result<Vec<_>, _>>()\n        .ok()?;\n\n    // Sort the entries by modified time (most recent first)\n    entries.sort_by_key(|entry| {\n        entry\n            .metadata()\n            .and_then(|m| m.modified())\n            .unwrap_or(std::time::SystemTime::UNIX_EPOCH)\n    });\n    entries.reverse();\n\n    // The first entry is the most recent log file\n    let filename = match entries.first() {\n        Some(path) => path.path().to_string_lossy().to_string(),\n        None => {\n            tracing::warn!(\"No log files found\");\n            return None;\n        }\n    };\n\n    std::fs::read_to_string(filename).ok()\n}\n\npub fn initialize<R: Runtime>(app: &mut tauri::App<R>) -> tauri::plugin::Result<()> {\n    let handle = app.handle();\n    let configuration = crate::config::init(&app.handle()).clone();\n    app.manage(configuration.clone());\n\n    let runtime = tokio::runtime::Builder::new_multi_thread()\n        .enable_all()\n        .thread_name(\"bleep-backend\")\n        .build()\n        .unwrap();\n\n    runtime.spawn(start_backend(configuration, handle));\n    app.manage(runtime);\n\n    Ok(())\n}\n\nasync fn wait_for_qdrant() -> anyhow::Result<()> {\n    use qdrant_client::prelude::*;\n    let qdrant =\n        QdrantClient::new(Some(QdrantClientConfig::from_url(\"http://127.0.0.1:6334\"))).unwrap();\n\n    for _ in 0..60 {\n        if qdrant.health_check().await.is_ok() {\n            return Ok(());\n        }\n\n        tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n    }\n\n    anyhow::bail!(\"qdrant cannot be started\");\n}\n\nasync fn start_backend<R: Runtime>(configuration: Configuration, app: tauri::AppHandle<R>) {\n    tracing::info!(\"booting bleep back-end\");\n\n    if let Err(err) = wait_for_qdrant().await {\n        error!(?err, \"qdrant failed to come up\");\n        thread::sleep(Duration::from_secs(4));\n        app.emit_all(\n            \"server-crashed\",\n            Payload {\n                message: \"Failed to start qdrant\".into(),\n            },\n        )\n        .unwrap();\n    };\n\n    app.manage(configuration.clone());\n\n    let initialized = Application::initialize(Environment::insecure_local(), configuration).await;\n\n    match initialized {\n        Ok(backend) => {\n            if let Err(err) = backend.run().await {\n                error!(?err, \"server crashed error\");\n                app.emit_all(\n                    \"server-crashed\",\n                    Payload {\n                        message: err.to_string(),\n                    },\n                )\n                .unwrap()\n            }\n        }\n        Err(err) => {\n            error!(?err, \"server failed to start\");\n            app.emit_all(\n                \"server-crashed\",\n                Payload {\n                    message: \"Something bad happened\".into(),\n                },\n            )\n            .unwrap();\n        }\n    }\n}\n\n// ensure that the leading header and trailer are stripped\n#[cfg(windows)]\n#[test]\nfn device_id_on_single_line() {\n    assert_eq!(get_device_id().lines().count(), 1)\n}\n"
  },
  {
    "path": "apps/desktop/src-tauri/src/config.rs",
    "content": "use bleep::Configuration;\nuse once_cell::sync::OnceCell;\nuse tauri::Runtime;\n\nstatic CONFIG: OnceCell<Configuration> = OnceCell::new();\n\npub fn init<R: Runtime>(app: &tauri::AppHandle<R>) -> &Configuration {\n    CONFIG.get_or_init(|| {\n        let config = create_configuration(app);\n        // only after that set up bleep logging hooks\n        bleep::Application::install_logging(&config);\n\n        config\n    })\n}\n\nfn create_configuration<R: Runtime>(app: &tauri::AppHandle<R>) -> Configuration {\n    let path = app\n        .path_resolver()\n        .resolve_resource(\"config/config.json\")\n        .expect(\"failed to resolve resource\");\n\n    let mut bundled = Configuration::read(path).unwrap();\n    bundled.qdrant_url = \"http://127.0.0.1:6334\".into();\n    bundled.max_threads = bleep::default_parallelism() / 2;\n    bundled.model_dir = app\n        .path_resolver()\n        .resolve_resource(\"model\")\n        .expect(\"bad bundle\");\n\n    bundled.dylib_dir = Some(if cfg!(all(target_os = \"macos\", debug_assertions)) {\n        app.path_resolver()\n            .resolve_resource(\"dylibs\")\n            .expect(\"missing `apps/desktop/src-tauri/dylibs`\")\n            .parent()\n            .expect(\"invalid path\")\n            .to_owned()\n    } else if cfg!(target_os = \"macos\") {\n        app.path_resolver()\n            .resolve_resource(\"dylibs\")\n            .expect(\"missing `apps/desktop/src-tauri/dylibs`\")\n            .parent()\n            .expect(\"invalid path\")\n            .parent()\n            .expect(\"invalid path\")\n            .join(\"Frameworks\")\n    } else {\n        app.path_resolver()\n            .resolve_resource(\"dylibs\")\n            .expect(\"missing `apps/desktop/src-tauri/dylibs`\")\n    });\n\n    let data_dir = app.path_resolver().app_data_dir().unwrap();\n    bundled.index_dir = data_dir.join(\"bleep\");\n\n    Configuration::merge(\n        bundled,\n        Configuration::cli_overriding_config_file().unwrap(),\n    )\n}\n"
  },
  {
    "path": "apps/desktop/src-tauri/src/main.rs",
    "content": "#![cfg_attr(\n    all(not(debug_assertions), target_os = \"windows\"),\n    windows_subsystem = \"windows\"\n)]\n\nmod backend;\nmod config;\nmod qdrant;\n\nuse sysinfo::{ProcessExt, ProcessRefreshKind, RefreshKind, Signal, System, SystemExt};\npub use tauri::{plugin, App, Manager, Runtime};\n\nuse std::{path::PathBuf, thread, time::Duration};\n\n// the payload type must implement `Serialize` and `Clone`.\n#[derive(Clone, serde::Serialize)]\nstruct Payload {\n    message: String,\n}\n\nfn relative_command_path(command: impl AsRef<str>) -> Option<PathBuf> {\n    let cmd = if cfg!(windows) {\n        format!(\"{}.exe\", command.as_ref())\n    } else {\n        command.as_ref().into()\n    };\n\n    std::env::current_exe()\n        .ok()?\n        .parent()\n        .map(|dir| dir.join(cmd))\n        .filter(|path| path.is_file())\n}\n\nfn main() {\n    cleanup_old_processes();\n\n    _ = color_eyre::install();\n\n    tauri::Builder::default()\n        .plugin(qdrant::QdrantSupervisor::default())\n        .setup(backend::initialize)\n        .invoke_handler(tauri::generate_handler![\n            show_folder_in_finder,\n            show_main_window,\n            backend::get_last_log_file,\n        ])\n        .run(tauri::generate_context!())\n        .expect(\"error running tauri application\");\n}\n\n#[tauri::command]\nfn show_main_window(app_handle: tauri::AppHandle) {\n    if let Some(window) = app_handle.get_window(\"main\") {\n        if !cfg!(target_os = \"macos\") {\n            window.unminimize().unwrap();\n        }\n        window.unminimize().unwrap();\n        window.set_focus().unwrap();\n        window.show().unwrap();\n    }\n}\n\n#[tauri::command]\nfn show_folder_in_finder(path: String) {\n    let path = PathBuf::from(path).canonicalize().unwrap();\n\n    #[cfg(target_os = \"macos\")]\n    {\n        std::process::Command::new(\"open\")\n            .arg(path)\n            .arg(\"-R\") // will reveal the file in finder instead of opening it\n            .spawn()\n            .unwrap();\n    }\n    #[cfg(target_os = \"linux\")]\n    {\n        std::process::Command::new(\"xdg-open\")\n            .arg(path)\n            .spawn()\n            .unwrap();\n    }\n    #[cfg(target_os = \"windows\")]\n    {\n        std::process::Command::new(\"explorer\")\n            .arg(path)\n            .spawn()\n            .unwrap();\n    }\n}\n\nfn cleanup_old_processes() {\n    const PROCESS_BLACKLIST: &[&str] = &[\"qdrant\", \"bleep\"];\n\n    // Limit total open files from `sysinfo` crate on Linux.\n    sysinfo::set_open_files_limit(10);\n\n    let mut sys =\n        System::new_with_specifics(RefreshKind::new().with_processes(ProcessRefreshKind::new()));\n\n    for name in PROCESS_BLACKLIST {\n        for process in sys.processes_by_exact_name(name) {\n            if process.kill_with(Signal::Term).is_none() && !process.kill() {\n                tracing::error!(?name, \"was not able to close existing process\");\n            }\n        }\n    }\n\n    // We now wait for these processes to close.\n\n    let mut remaining_procs = vec![];\n    for _ in 0..10 {\n        thread::sleep(Duration::from_millis(500));\n        sys.refresh_processes();\n        remaining_procs = PROCESS_BLACKLIST\n            .iter()\n            .flat_map(|name| sys.processes_by_exact_name(name))\n            .collect();\n\n        if remaining_procs.is_empty() {\n            break;\n        }\n    }\n\n    // As a last-ditch resort, kill any remaining processes.\n    for proc in remaining_procs {\n        proc.kill();\n    }\n}\n"
  },
  {
    "path": "apps/desktop/src-tauri/src/qdrant.rs",
    "content": "use std::{\n    fs::{create_dir_all, write, File},\n    path::{Path, PathBuf},\n    process::{Child, Command},\n};\n\nuse tauri::{plugin::Plugin, Runtime};\nuse tracing::{error, info, warn};\n\nuse super::relative_command_path;\n\n#[derive(Default)]\npub(super) struct QdrantSupervisor {\n    child: Option<Child>,\n    stdout_file: Option<PathBuf>,\n    stderr_file: Option<PathBuf>,\n}\n\nimpl<R> Plugin<R> for QdrantSupervisor\nwhere\n    R: Runtime,\n{\n    fn name(&self) -> &'static str {\n        \"qdrant\"\n    }\n\n    fn initialize(\n        &mut self,\n        app: &tauri::AppHandle<R>,\n        _config: serde_json::Value,\n    ) -> tauri::plugin::Result<()> {\n        // initialize the system configuration\n        let _initialize_config = crate::config::init(app);\n\n        let data_dir = app.path_resolver().app_data_dir().unwrap();\n        let qdrant_dir = data_dir.join(\"qdrant\");\n        let qd_config_dir = qdrant_dir.join(\"config\");\n\n        let qd_logs_dir = qdrant_dir.join(\"logs\");\n        let stdout_file = qd_logs_dir.join(\"latest.stdout\");\n        let stderr_file = qd_logs_dir.join(\"latest.stderr\");\n\n        create_dir_all(&qd_config_dir).unwrap();\n        create_dir_all(&qd_logs_dir).unwrap();\n\n        write(\n            qd_config_dir.join(\"config.yaml\"),\n            format!(\n                include_str!(\"./QDRANT_CONFIG_TEMPLATE.yml\"),\n                storage = &qdrant_dir.join(\"storage\").to_string_lossy(),\n                snapshots = &qdrant_dir.join(\"snapshots\").to_string_lossy()\n            ),\n        )\n        .unwrap();\n\n        let command = relative_command_path(\"qdrant\").expect(\"bad bundle\");\n\n        self.stdout_file = Some(stdout_file.clone());\n        self.stderr_file = Some(stderr_file.clone());\n        self.child = Some(run_command(\n            &command,\n            &qdrant_dir,\n            &stdout_file,\n            &stderr_file,\n        ));\n\n        Ok(())\n    }\n\n    fn on_event(&mut self, _app: &tauri::AppHandle<R>, event: &tauri::RunEvent) {\n        use tauri::RunEvent::{Exit, ExitRequested};\n        if matches!(event, Exit | ExitRequested { .. }) {\n            let Some(mut child) = self.child.take() else {\n                warn!(\"qdrant has been killed\");\n                return;\n            };\n\n            if let Err(err) = child.kill() {\n                warn!(?err, \"failed to kill qdrant\");\n            }\n        } else if let Some(ref mut child) = self.child {\n            match child.try_wait() {\n                Ok(Some(status)) if status.success() => {\n                    // don't fire again\n                    _ = self.child.take();\n                }\n                Ok(Some(_)) => {\n                    // don't fire again\n                    _ = self.child.take();\n                }\n                Ok(None) => {\n                    // all is normal, this is what we want\n                }\n                Err(err) => {\n                    error!(?err, \"failed to monitor qdrant subprocess\");\n                }\n            }\n        }\n    }\n}\n\nimpl Drop for QdrantSupervisor {\n    fn drop(&mut self) {\n        if let Some(mut child) = self.child.take() {\n            if let Err(err) = child.kill() {\n                warn!(?err, \"failed to kill qdrant\");\n            }\n        }\n    }\n}\n\n#[cfg(unix)]\nfn run_command(command: &Path, qdrant_dir: &Path, stdout: &Path, stderr: &Path) -> Child {\n    use nix::sys::resource::{getrlimit, setrlimit, Resource};\n\n    let logs_file = File::create(stdout).unwrap();\n    let stderr_logs_file = File::create(stderr).unwrap();\n\n    match getrlimit(Resource::RLIMIT_NOFILE) {\n        Ok((current_soft, current_hard)) => {\n            info!(current_soft, current_hard, \"got rlimit/nofile\");\n            let new_soft = current_soft.max(current_hard.min(10000));\n            if let Err(err) = setrlimit(Resource::RLIMIT_NOFILE, new_soft, current_hard) {\n                error!(\n                    ?err,\n                    new_soft, current_soft, current_hard, \"failed to set rlimit/nofile\"\n                );\n            } else {\n                info!(new_soft, current_hard, \"set rlimit/nofile\");\n            }\n        }\n        Err(err) => {\n            error!(?err, \"failed to get rlimit/nofile\");\n        }\n    }\n\n    Command::new(command)\n        .current_dir(qdrant_dir)\n        .stdout(logs_file)\n        .stderr(stderr_logs_file)\n        .spawn()\n        .expect(\"failed to start qdrant\")\n}\n\n#[cfg(windows)]\nfn run_command(command: &Path, qdrant_dir: &Path, stdout: &Path, stderr: &Path) -> Child {\n    use std::os::windows::process::CommandExt;\n\n    let qd_logs_dir = qdrant_dir.join(\"logs\");\n    create_dir_all(&qd_logs_dir).unwrap();\n\n    let logs_file = File::create(stdout).unwrap();\n    let stderr_logs_file = File::create(stderr).unwrap();\n\n    Command::new(command)\n        .current_dir(qdrant_dir)\n        // Add a CREATE_NO_WINDOW flag to prevent qdrant console popup\n        .creation_flags(0x08000000)\n        .stdout(logs_file)\n        .stderr(stderr_logs_file)\n        .spawn()\n        .expect(\"failed to start qdrant\")\n}\n"
  },
  {
    "path": "apps/desktop/src-tauri/tauri.conf.json",
    "content": "{\n  \"$schema\": \"../../../node_modules/@tauri-apps/cli/schema.json\",\n  \"build\": {\n    \"beforeBuildCommand\": \"npm run build\",\n    \"beforeDevCommand\": \"npm run dev\",\n    \"devPath\": \"http://localhost:5173\",\n    \"distDir\": \"../dist\"\n  },\n  \"package\": {\n    \"productName\": \"bloop\",\n    \"version\": \"0.6.4\"\n  },\n  \"tauri\": {\n    \"allowlist\": {\n      \"fs\": {\n        \"all\": true\n      },\n      \"window\": {\n        \"all\": true\n      },\n      \"dialog\": {\n        \"open\": true\n      },\n      \"http\": {\n        \"all\": true\n      },\n      \"os\": {\n        \"all\": true\n      },\n      \"shell\": {\n        \"all\": true\n      },\n      \"path\": {\n        \"all\": true\n      },\n      \"process\": {\n        \"all\": true\n      }\n    },\n    \"bundle\": {\n      \"active\": true,\n      \"category\": \"DeveloperTool\",\n      \"copyright\": \"Bloop AI Limited\",\n      \"deb\": {\n        \"depends\": []\n      },\n      \"externalBin\": [\n        \"bin/qdrant\"\n      ],\n      \"icon\": [\n        \"icons/32x32.png\",\n        \"icons/128x128.png\",\n        \"icons/128x128@2x.png\",\n        \"icons/icon.icns\",\n        \"icons/icon.ico\"\n      ],\n      \"identifier\": \"ai.bloop.bloop\",\n      \"longDescription\": \"Helping developers find code faster\",\n      \"macOS\": {\n        \"entitlements\": null,\n        \"exceptionDomain\": \"\",\n        \"frameworks\": [\n          \"frameworks/libonnxruntime.dylib\"\n        ],\n        \"providerShortName\": null,\n        \"signingIdentity\": null\n      },\n      \"resources\": [\n        \"model/*\",\n        \"dylibs/*\",\n        \"config/config.json\"\n      ],\n      \"shortDescription\": \"\",\n      \"targets\": \"all\",\n      \"windows\": {\n        \"certificateThumbprint\": \"b955de6f8483ad3b14497e798a6eef48a137931b\",\n        \"digestAlgorithm\": \"sha256\",\n        \"timestampUrl\": \"http://timestamp.sectigo.com\",\n        \"nsis\": {\n          \"installMode\": \"currentUser\",\n          \"template\": \"installer.nsi\"\n        }\n      }\n    },\n    \"security\": {\n      \"csp\": null\n    },\n    \"windows\": [\n      {\n        \"fullscreen\": false,\n        \"height\": 900,\n        \"resizable\": true,\n        \"title\": \"bloop\",\n        \"width\": 1400,\n        \"hiddenTitle\": true,\n        \"titleBarStyle\": \"Overlay\",\n        \"minHeight\": 700,\n        \"minWidth\": 1000,\n        \"fileDropEnabled\": false\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "apps/desktop/tailwind.config.cjs",
    "content": "/** @type {import('tailwindcss').Config} */\nconst basicConfig = require(\"../../client/tailwind.config.cjs\");\n\nmodule.exports = {\n  ...basicConfig,\n  content: ['../../client/src/**/*.{ts,tsx,js,jsx}', './src/**/*.tsx']\n}\n"
  },
  {
    "path": "apps/desktop/tsconfig.json",
    "content": "{\n  \"extends\": \"../../client/tsconfig.json\",\n  \"include\": [\"src\"],\n}\n"
  },
  {
    "path": "apps/desktop/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Node\",\n    \"allowSyntheticDefaultImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "apps/desktop/vite.config.ts",
    "content": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\nimport EnvironmentPlugin from 'vite-plugin-environment';\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  build: {\n    sourcemap: true, // Source map generation must be turned on\n  },\n  envDir: '../../.',\n  plugins: [\n    react(),\n    EnvironmentPlugin(\n      {\n        ONBOARDING: '',\n        API_URL: '',\n      },\n      {\n        defineOn: 'import.meta.env',\n      },\n    ),\n  ],\n  publicDir: '../../client/public',\n  define: {\n    __APP_SESSION__: (Math.random() * 100000).toString(),\n  },\n  server: {\n    fs: {\n      strict: false,\n    },\n  },\n});\n"
  },
  {
    "path": "client/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n\n\nstorybook-static\ncoverage\n\n.env\n"
  },
  {
    "path": "client/.storybook/main.cjs",
    "content": "module.exports = {\n  \"stories\": [\n    \"../src/**/*.stories.mdx\",\n    \"../src/**/*.stories.@(js|jsx|ts|tsx)\"\n  ],\n  \"addons\": [\n    \"@storybook/addon-links\",\n    \"@storybook/addon-essentials\",\n    \"@storybook/addon-interactions\"\n  ],\n  framework: {\n    name: '@storybook/react-vite',\n    options: {},\n  },\n  \"features\": {\n    \"storyStoreV7\": true\n  }\n}\n"
  },
  {
    "path": "client/.storybook/preview-head.html",
    "content": "<script>\n  window.global = window;\n</script>"
  },
  {
    "path": "client/.storybook/preview.cjs",
    "content": "import '../src/index.css';\n\nexport const parameters = {\n  actions: { argTypesRegex: \"^on[A-Z].*\" },\n  controls: {\n    matchers: {\n      color: /(background|color)$/i,\n      date: /Date$/,\n    },\n  },\n}\n"
  },
  {
    "path": "client/README.md",
    "content": "# Client\n\n## Searching in the browser\n\nYou can use bloop in the browser, without running the Tauri app. First follow [the steps](./../server/README.md) to install and run the search server. Make sure that `API_URL` is set in `.env` (e.g. `API_URL=http://localhost:7878/api`). Then, in the root directory run:\n\n```\nnpm install\nnpm run start-web\n```\n\nOpen `localhost:5173` in a browser and, hey presto, you've got bloop in the browser.\n"
  },
  {
    "path": "client/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\" />\n  <link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.ico\" />\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  <title>bloop</title>\n</head>\n\n<body data-theme=\"system\">\n  <div id=\"root\"></div>\n  <script type=\"module\" src=\"/src/main.tsx\"></script>\n</body>\n\n</html>\n"
  },
  {
    "path": "client/jest.config.js",
    "content": "/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */\nmodule.exports = {\n  preset: 'ts-jest',\n  testEnvironment: 'jsdom',\n  setupFilesAfterEnv: ['<rootDir>/tests/setupTests.ts'],\n  collectCoverageFrom: [\n    'src/utils/{!(services),}.{js,jsx,ts,tsx}',\n    '!**/node_modules/**',\n    '!**/vendor/**',\n  ],\n  coverageThreshold: {\n    global: {\n      branches: 10,\n      functions: 21,\n      lines: 18,\n    },\n  },\n};\n"
  },
  {
    "path": "client/package.json",
    "content": "{\n  \"name\": \"@bloop/client\",\n  \"private\": true,\n  \"version\": \"0.6.4\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\",\n    \"lint\": \"eslint src --ext ts --ext tsx --fix\",\n    \"type-check\": \"tsc\",\n    \"storybook\": \"sb dev -p 6006\",\n    \"build-storybook\": \"sb build\",\n    \"test\": \"jest --collect-coverage --passWithNoTests\",\n    \"chromatic\": \"npx chromatic --project-token=6115d726666b\"\n  }\n}"
  },
  {
    "path": "client/postcss.config.cjs",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n}\n"
  },
  {
    "path": "client/src/App.tsx",
    "content": "import React, { memo } from 'react';\nimport { DndProvider } from 'react-dnd';\nimport { HTML5Backend } from 'react-dnd-html5-backend';\nimport { Toaster } from 'sonner';\nimport Project from './Project';\nimport CommandBar from './CommandBar';\nimport ProjectContextProvider from './context/providers/ProjectContextProvider';\nimport CommandBarContextProvider from './context/providers/CommandBarContextProvider';\nimport { UIContextProvider } from './context/providers/UIContextProvider';\nimport Settings from './Settings';\nimport ProjectSettings from './ProjectSettings';\nimport TabsContextProvider from './context/providers/TabsContextProvider';\nimport { FileHighlightsContextProvider } from './context/providers/FileHighlightsContextProvider';\nimport RepositoriesContextProvider from './context/providers/RepositoriesContextProvider';\n\nconst toastOptions = {\n  unStyled: true,\n  classNames: {\n    toast:\n      'w-[20.75rem] p-4 pl-5 grid grid-cols-[1rem_1fr] items-start gap-3 rounded-md border border-bg-border bg-bg-base shadow-high',\n    error: 'text-red',\n    info: 'text-label-title',\n    title: 'body-s-b',\n    description: '!text-label-muted body-s mt-1.5',\n    actionButton: 'col-span-full',\n    cancelButton: 'bg-orange-400',\n    closeButton:\n      '!bg-bg-base !text-label-muted !border-none !left-[unset] !right-2 !top-6 !w-6 !h-6',\n  },\n};\n\nconst App = () => {\n  return (\n    <DndProvider backend={HTML5Backend}>\n      <UIContextProvider>\n        <ProjectContextProvider>\n          <Toaster closeButton toastOptions={toastOptions} />\n          <RepositoriesContextProvider>\n            <CommandBarContextProvider>\n              <Settings />\n              <ProjectSettings />\n              <FileHighlightsContextProvider>\n                <TabsContextProvider>\n                  <CommandBar />\n                  <Project />\n                </TabsContextProvider>\n              </FileHighlightsContextProvider>\n            </CommandBarContextProvider>\n          </RepositoriesContextProvider>\n        </ProjectContextProvider>\n      </UIContextProvider>\n    </DndProvider>\n  );\n};\n\nexport default memo(App);\n"
  },
  {
    "path": "client/src/CloudApp.tsx",
    "content": "import React, { useEffect, useMemo, useState } from 'react';\nimport { BrowserRouter } from 'react-router-dom';\nimport packageJson from '../package.json';\nimport App from './App';\nimport { LocaleContext } from './context/localeContext';\nimport i18n from './i18n';\nimport './index.css';\nimport {\n  getPlainFromStorage,\n  LANGUAGE_KEY,\n  savePlainToStorage,\n} from './services/storage';\nimport { LocaleType } from './types/general';\nimport { DeviceContextProvider } from './context/providers/DeviceContextProvider';\n\nconst CloudApp = () => {\n  const [locale, setLocale] = useState<LocaleType>(\n    (getPlainFromStorage(LANGUAGE_KEY) as LocaleType | null) || 'en',\n  );\n\n  const deviceContextValue = useMemo(\n    () => ({\n      openFolderInExplorer: () => {},\n      openLink: (p: string) => window.open(p),\n      chooseFolder: () => Promise.resolve(null),\n      homeDir: '$HOME',\n      listen: () => {},\n      os: {\n        arch: '',\n        type: '',\n        platform: '',\n        version: '',\n      },\n      invokeTauriCommand: () => Promise.resolve(''),\n      release: packageJson.version,\n      apiUrl: import.meta.env.API_URL || '/api',\n      isRepoManagementAllowed: true,\n      isSelfServe: true,\n      forceAnalytics: true,\n      showNativeMessage: alert,\n      relaunch: () => {},\n    }),\n    [],\n  );\n\n  useEffect(() => {\n    i18n.changeLanguage(locale);\n    savePlainToStorage(LANGUAGE_KEY, locale);\n  }, [locale]);\n\n  const localeContextValue = useMemo(\n    () => ({\n      locale,\n      setLocale,\n    }),\n    [locale],\n  );\n\n  return (\n    <DeviceContextProvider deviceContextValue={deviceContextValue}>\n      <LocaleContext.Provider value={localeContextValue}>\n        <BrowserRouter>\n          <App />\n        </BrowserRouter>\n      </LocaleContext.Provider>\n    </DeviceContextProvider>\n  );\n};\n\nexport default CloudApp;\n"
  },
  {
    "path": "client/src/CommandBar/Body/Item.tsx",
    "content": "import React, {\n  memo,\n  ReactElement,\n  useCallback,\n  useContext,\n  useEffect,\n  useRef,\n} from 'react';\nimport {\n  CommandBarItemGeneralType,\n  CommandBarStepEnum,\n} from '../../types/general';\nimport useShortcuts from '../../hooks/useShortcuts';\nimport useKeyboardNavigation from '../../hooks/useKeyboardNavigation';\nimport { checkEventKeys } from '../../utils/keyboardUtils';\nimport { CommandBarContext } from '../../context/commandBarContext';\nimport { CheckmarkInSquareIcon } from '../../icons';\nimport {\n  RECENT_COMMANDS_KEY,\n  updateArrayInStorage,\n} from '../../services/storage';\nimport { ArrowNavigationContext } from '../../context/arrowNavigationContext';\n\ntype Props = CommandBarItemGeneralType & {\n  index: string;\n  isFirst?: boolean;\n  isWithCheckmark?: boolean;\n  customRightElement?: ReactElement;\n  focusedItemProps?: Record<string, any>;\n  disableKeyNav?: boolean;\n  itemKey: string;\n};\n\nconst CommandBarItem = ({\n  Icon,\n  label,\n  shortcut,\n  index,\n  id,\n  footerBtns,\n  isFirst,\n  iconContainerClassName,\n  footerHint,\n  customRightElement,\n  onClick,\n  focusedItemProps,\n  disableKeyNav,\n  isWithCheckmark,\n  closeOnClick,\n  itemKey,\n}: Props) => {\n  const ref = useRef<HTMLButtonElement>(null);\n  const shortcutKeys = useShortcuts(shortcut);\n  const { setFocusedItem, setChosenStep, setIsVisible } = useContext(\n    CommandBarContext.Handlers,\n  );\n  const { setFocusedIndex, focusedIndex } = useContext(ArrowNavigationContext);\n\n  useEffect(() => {\n    if (focusedIndex === index) {\n      setFocusedItem({\n        footerHint,\n        footerBtns,\n        focusedItemProps,\n      });\n      ref.current?.scrollIntoView({ block: 'nearest' });\n    }\n  }, [focusedIndex, index, footerBtns, footerHint, focusedItemProps]);\n\n  const handleMouseMove = useCallback(\n    (e: React.MouseEvent) => {\n      if (e.movementX || e.movementY) {\n        setFocusedIndex(index);\n      }\n    },\n    [index, setFocusedIndex],\n  );\n\n  const handleClick = useCallback(\n    (e: React.MouseEvent | KeyboardEvent) => {\n      if (onClick) {\n        onClick(e);\n        if (closeOnClick) {\n          setIsVisible(false);\n          setChosenStep({ id: CommandBarStepEnum.INITIAL });\n        }\n      } else {\n        setChosenStep({\n          id: id as Exclude<\n            CommandBarStepEnum,\n            CommandBarStepEnum.ADD_TO_STUDIO | CommandBarStepEnum.SEARCH_DOCS\n          >,\n        });\n      }\n      updateArrayInStorage(RECENT_COMMANDS_KEY, itemKey);\n    },\n    [id, onClick, closeOnClick, itemKey],\n  );\n\n  const handleKeyEvent = useCallback(\n    (e: KeyboardEvent) => {\n      const shortAction = footerBtns.find((b) => checkEventKeys(e, b.shortcut));\n      if (\n        (focusedIndex === index && shortAction && !shortAction.action) ||\n        checkEventKeys(e, shortcut)\n      ) {\n        e.preventDefault();\n        e.stopPropagation();\n        handleClick(e);\n        return;\n      }\n      if (focusedIndex === index && shortAction?.action) {\n        e.preventDefault();\n        e.stopPropagation();\n        shortAction.action();\n      }\n    },\n    [focusedIndex === index, shortcut, footerBtns, handleClick],\n  );\n  useKeyboardNavigation(handleKeyEvent, disableKeyNav);\n\n  return (\n    <button\n      className={`flex items-center gap-3 rounded-md px-2 h-10 ${\n        focusedIndex === index\n          ? 'bg-bg-base-hover text-label-title'\n          : 'text-label-base'\n      } text-left ${isFirst ? 'scroll-mt-8' : ''}`}\n      onMouseMove={handleMouseMove}\n      onClick={handleClick}\n      data-node-index={index}\n      ref={ref}\n    >\n      <div\n        className={`rounded-6 w-6 h-6 flex items-center justify-center relative ${\n          iconContainerClassName || 'bg-bg-border'\n        }`}\n      >\n        <Icon sizeClassName=\"w-3.5 h-3.5\" />\n        {isWithCheckmark && (\n          <CheckmarkInSquareIcon\n            sizeClassName=\"w-4 h-4\"\n            className=\"text-blue bg-bg-base absolute -bottom-1.5 -right-1.5 z-10\"\n          />\n        )}\n      </div>\n      <p className=\"flex-1 body-s-b ellipsis\">{label}</p>\n      {!!shortcutKeys && (\n        <div className=\"flex items-center gap-1\">\n          {shortcutKeys.map((k) => (\n            <div\n              key={k}\n              className={`min-w-5 h-5 px-1 flex-shrink-0 flex items-center justify-center rounded \n              border border-bg-border bg-bg-base shadow-low body-mini-b text-label-base`}\n            >\n              {k}\n            </div>\n          ))}\n        </div>\n      )}\n      {customRightElement}\n    </button>\n  );\n};\n\nexport default memo(CommandBarItem);\n"
  },
  {
    "path": "client/src/CommandBar/Body/Section.tsx",
    "content": "import { Dispatch, memo, SetStateAction } from 'react';\nimport {\n  CommandBarItemCustomType,\n  CommandBarItemGeneralType,\n} from '../../types/general';\nimport SectionDivider from './SectionDivider';\nimport Item from './Item';\n\ntype Props = {\n  title?: string;\n  items: (CommandBarItemCustomType | CommandBarItemGeneralType)[];\n  disableKeyNav?: boolean;\n  index: string;\n};\n\nconst CommandBarBodySection = ({\n  title,\n  items,\n  disableKeyNav,\n  index,\n}: Props) => {\n  return (\n    <div className=\"flex flex-col select-none\">\n      {!!title && <SectionDivider text={title} />}\n      {items.map(({ key, ...Rest }, i) =>\n        'Component' in Rest ? (\n          <Rest.Component\n            {...Rest.componentProps}\n            key={key}\n            isFirst={i === 0}\n            index={`${index}-${key}`}\n            disableKeyNav={disableKeyNav}\n          />\n        ) : (\n          <Item\n            key={key}\n            {...Rest}\n            index={`${index}-${key}`}\n            isFirst={i === 0}\n            disableKeyNav={disableKeyNav}\n            itemKey={key}\n          />\n        ),\n      )}\n    </div>\n  );\n};\n\nexport default memo(CommandBarBodySection);\n"
  },
  {
    "path": "client/src/CommandBar/Body/SectionDivider.tsx",
    "content": "import { memo } from 'react';\n\ntype Props = {\n  text: string;\n};\n\nconst SectionDivider = ({ text }: Props) => {\n  return (\n    <div className=\"flex items-center gap-1 px-2 py-1 body-mini-b text-label-muted\">\n      {text}\n    </div>\n  );\n};\n\nexport default memo(SectionDivider);\n"
  },
  {
    "path": "client/src/CommandBar/Body/index.tsx",
    "content": "import { memo, useEffect, useMemo } from 'react';\nimport { CommandBarSectionType } from '../../types/general';\nimport useKeyboardNavigation from '../../hooks/useKeyboardNavigation';\nimport { useArrowNavigation } from '../../hooks/useArrowNavigation';\nimport { ArrowNavigationContext } from '../../context/arrowNavigationContext';\nimport { noOp } from '../../utils';\nimport Section from './Section';\n\ntype Props = {\n  sections: CommandBarSectionType[];\n  disableKeyNav?: boolean;\n  onFocusedIndexChange?: (i: string) => void;\n};\n\nconst CommandBarBody = ({\n  sections,\n  disableKeyNav,\n  onFocusedIndexChange,\n}: Props) => {\n  const { focusedIndex, setFocusedIndex, handleArrowKey, navContainerRef } =\n    useArrowNavigation();\n\n  useEffect(() => {\n    if (sections?.[0]?.items?.[0]) {\n      setFocusedIndex(`${sections[0].key}-${sections[0].items[0].key}`);\n    }\n  }, [sections]);\n\n  useEffect(() => {\n    if (onFocusedIndexChange) {\n      onFocusedIndexChange(focusedIndex);\n    }\n  }, [focusedIndex]);\n\n  useKeyboardNavigation(handleArrowKey, disableKeyNav);\n\n  const contextValue = useMemo(\n    () => ({\n      focusedIndex,\n      setFocusedIndex,\n      handleClose: noOp,\n    }),\n    [focusedIndex],\n  );\n\n  return (\n    <div\n      className=\"flex flex-col gap-1 flex-1 w-full p-2 overflow-auto show-scrollbar\"\n      ref={navContainerRef}\n    >\n      <ArrowNavigationContext.Provider value={contextValue}>\n        {sections.map((s) => (\n          <Section\n            key={s.key}\n            title={s.label}\n            items={s.items}\n            disableKeyNav={disableKeyNav}\n            index={s.key}\n          />\n        ))}\n      </ArrowNavigationContext.Provider>\n    </div>\n  );\n};\n\nexport default memo(CommandBarBody);\n"
  },
  {
    "path": "client/src/CommandBar/Footer/HintButton.tsx",
    "content": "import { ForwardedRef, forwardRef, memo } from 'react';\nimport useShortcuts from '../../hooks/useShortcuts';\n\ntype Props = {\n  label: string;\n  shortcut?: string[];\n};\n\nconst HintButton = forwardRef(\n  ({ label, shortcut }: Props, ref: ForwardedRef<HTMLDivElement>) => {\n    const shortcutKeys = useShortcuts(shortcut);\n    return (\n      <div\n        className=\"inline-flex pl-2 py-1 pr-1 items-center gap-1 rounded-6 bg-bg-base body-mini-b text-label-muted text-center\"\n        ref={ref}\n      >\n        {label}\n        {shortcutKeys?.map((k) => (\n          <div\n            key={k}\n            className=\"min-w-[1.25rem] h-5 px-1 flex items-center justify-center rounded bg-bg-base-hover\"\n          >\n            {k}\n          </div>\n        ))}\n      </div>\n    );\n  },\n);\n\nHintButton.displayName = 'HintButtonWithRef';\n\nexport default memo(HintButton);\n"
  },
  {
    "path": "client/src/CommandBar/Footer/index.tsx",
    "content": "import { memo, useCallback, useContext, useRef } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { CommandBarContext } from '../../context/commandBarContext';\nimport Dropdown from '../../components/Dropdown';\nimport useKeyboardNavigation from '../../hooks/useKeyboardNavigation';\nimport HintButton from './HintButton';\n\ntype Props = {\n  ActionsDropdown?: (props: any) => JSX.Element | null;\n  actionsDropdownProps?: Record<string, any>;\n  onDropdownVisibilityChange?: (isVisible: boolean) => void;\n};\n\nconst CommandBarFooter = ({\n  ActionsDropdown,\n  actionsDropdownProps,\n  onDropdownVisibilityChange,\n}: Props) => {\n  const { t } = useTranslation();\n  const { focusedItem } = useContext(CommandBarContext.FooterValues);\n  const actionsBtn = useRef<HTMLDivElement>(null);\n\n  const handleKeyEvent = useCallback((e: KeyboardEvent) => {\n    if ((e.metaKey || e.ctrlKey) && e.key === 'k') {\n      e.preventDefault();\n      e.stopPropagation();\n      actionsBtn.current?.click();\n    }\n  }, []);\n  useKeyboardNavigation(handleKeyEvent, !ActionsDropdown);\n\n  return (\n    <div className=\"flex items-center gap-1 w-full py-2.5 pl-4 pr-3 border-t border-bg-border\">\n      <p className=\"text-label-base code-mini flex-1\">\n        {focusedItem?.footerHint}\n      </p>\n      {focusedItem?.footerBtns?.map((b) => <HintButton key={b.label} {...b} />)}\n      {!!ActionsDropdown && (\n        <Dropdown\n          dropdownPlacement=\"top-end\"\n          color=\"base\"\n          DropdownComponent={ActionsDropdown}\n          dropdownComponentProps={actionsDropdownProps}\n          appendTo={document.body}\n          onVisibilityChange={onDropdownVisibilityChange}\n        >\n          <HintButton\n            shortcut={['cmd', 'K']}\n            label={t('Actions')}\n            ref={actionsBtn}\n          />\n        </Dropdown>\n      )}\n    </div>\n  );\n};\n\nexport default memo(CommandBarFooter);\n"
  },
  {
    "path": "client/src/CommandBar/Header/ChipItem.tsx",
    "content": "import { memo } from 'react';\n\ntype Props = { text: string };\n\nconst CommandBarChipItem = ({ text }: Props) => {\n  return (\n    <div className=\"flex px-1 gap-1 items-center rounded bg-bg-border code-mini text-label-base ellipsis\">\n      {text}\n    </div>\n  );\n};\n\nexport default memo(CommandBarChipItem);\n"
  },
  {
    "path": "client/src/CommandBar/Header/index.tsx",
    "content": "import {\n  ChangeEvent,\n  memo,\n  ReactElement,\n  useCallback,\n  useContext,\n  useState,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport Tooltip from '../../components/Tooltip';\nimport useKeyboardNavigation from '../../hooks/useKeyboardNavigation';\nimport { CommandBarContext } from '../../context/commandBarContext';\nimport { CommandBarStepEnum } from '../../types/general';\nimport ChipItem from './ChipItem';\n\ntype PropsWithoutInput = {\n  noInput: true;\n  customSubmitHandler?: never;\n  onChange?: never;\n  value?: never;\n  placeholder?: never;\n};\n\ntype PropsWithInput = {\n  noInput?: false;\n  value: string;\n  onChange: (e: ChangeEvent<HTMLInputElement>) => void;\n  customSubmitHandler?: (value: string) => void;\n  placeholder?: string;\n};\n\ntype GeneralProps = {\n  handleBack?: () => void;\n  breadcrumbs?: string[];\n  customRightComponent?: ReactElement;\n  disableKeyNav?: boolean;\n};\n\ntype Props = GeneralProps & (PropsWithInput | PropsWithoutInput);\n\nconst CommandBarHeader = ({\n  handleBack,\n  breadcrumbs,\n  customRightComponent,\n  customSubmitHandler,\n  onChange,\n  value,\n  placeholder,\n  noInput,\n  disableKeyNav,\n}: Props) => {\n  const { t } = useTranslation();\n  const { isVisible } = useContext(CommandBarContext.General);\n  const { setIsVisible, setChosenStep } = useContext(\n    CommandBarContext.Handlers,\n  );\n  const [isComposing, setIsComposing] = useState(false);\n\n  const onCompositionStart = useCallback(() => {\n    setIsComposing(true);\n  }, []);\n\n  const onCompositionEnd = useCallback(() => {\n    // this event comes before keydown and sets state faster causing unintentional submit\n    setTimeout(() => setIsComposing(false), 10);\n  }, []);\n\n  const handleKeyEvent = useCallback(\n    (e: KeyboardEvent) => {\n      if (\n        e.key === 'Escape' ||\n        (e.key === 'Backspace' && !value && !isComposing)\n      ) {\n        e.stopPropagation();\n        e.preventDefault();\n        if (handleBack) {\n          handleBack();\n        } else {\n          setChosenStep({ id: CommandBarStepEnum.INITIAL });\n          setIsVisible(false);\n        }\n      } else if (e.key === 'Enter' && customSubmitHandler && !isComposing) {\n        e.stopPropagation();\n        e.preventDefault();\n        customSubmitHandler(value);\n      }\n    },\n    [setIsVisible, handleBack, customSubmitHandler, value, isComposing],\n  );\n  useKeyboardNavigation(handleKeyEvent, !isVisible || disableKeyNav);\n\n  return (\n    <div className=\"w-full flex flex-col p-4 items-start gap-4 border-b border-bg-border\">\n      <div className=\"flex gap-1 items-center justify-between w-full select-none\">\n        <div className=\"flex gap-1 items-center\">\n          {!!handleBack && (\n            <Tooltip text={t('Back')} placement={'top'}>\n              <button\n                className=\"w-5 flex gap-1 items-center justify-center rounded border border-bg-border code-mini text-label-base ellipsis\"\n                onClick={handleBack}\n              >\n                ←\n              </button>\n            </Tooltip>\n          )}\n          {breadcrumbs?.map((b) => <ChipItem key={b} text={b} />)}\n        </div>\n        {customRightComponent}\n      </div>\n      {!noInput && (\n        <input\n          value={value}\n          onChange={onChange}\n          placeholder={placeholder || t('Search projects or commands...')}\n          type=\"search\"\n          id=\"command-input\"\n          autoComplete=\"off\"\n          autoCorrect=\"off\"\n          className=\"w-full bg-transparent outline-none focus:outline-0 body-base placeholder:text-label-muted\"\n          autoFocus\n          onCompositionStart={onCompositionStart}\n          onCompositionEnd={onCompositionEnd}\n          disabled={disableKeyNav}\n        />\n      )}\n    </div>\n  );\n};\n\nexport default memo(CommandBarHeader);\n"
  },
  {
    "path": "client/src/CommandBar/Tutorial/TutorialBody.tsx",
    "content": "import React, { memo, useCallback, useContext } from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { UIContext } from '../../context/uiContext';\n\ntype Props = {\n  stepNumber: number;\n  title: string;\n  description: string;\n  hint?: string;\n};\n\nconst TutorialBody = ({ stepNumber, title, hint, description }: Props) => {\n  useTranslation();\n  const { setOnBoardingState } = useContext(UIContext.Onboarding);\n\n  const onSkip = useCallback(() => {\n    setOnBoardingState({\n      isCommandBarTutorialFinished: true,\n      isCodeNavigated: true,\n      isCodeExplained: true,\n      isChatOpened: true,\n      isFileExplained: true,\n    });\n  }, []);\n\n  return (\n    <span\n      className={`inline-flex items-center flex-shrink-0 z-60 relative`}\n      onClick={(e) => e.stopPropagation()}\n    >\n      <span className=\"flex flex-col p-3 gap-4 w-max max-w-[18.625rem] rounded-md bg-bg-contrast shadow-high text-label-contrast\">\n        <span className=\"flex flex-col gap-1.5 items-end select-none\">\n          <span className=\"flex gap-3 items-center w-full\">\n            <span className=\"w-5 h-5 rounded flex items-center justify-center bg-bg-base code-tiny-b text-label-title\">\n              {stepNumber}\n            </span>\n            <p className=\"text-label-contrast body-s-b flex-1\">{title}</p>\n            <button\n              className=\"body-mini text-label-contrast/60\"\n              onClick={onSkip}\n            >\n              <Trans>Skip</Trans>\n            </button>\n          </span>\n          <span className=\"pl-8 flex flex-col\">\n            <span className=\"body-s\">{description}</span>\n            {!!hint && (\n              <>\n                <br />\n                <span className=\"body-s-b\">{hint}</span>\n              </>\n            )}\n          </span>\n        </span>\n      </span>\n      <span className=\"relative -left-px z-10\">\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"8\"\n          height=\"16\"\n          viewBox=\"0 0 8 16\"\n          fill=\"none\"\n        >\n          <path\n            d=\"M7.31833 6.50518C8.21336 7.30076 8.21336 8.69924 7.31833 9.49482L2.54292e-07 16L9.53674e-07 -3.93402e-07L7.31833 6.50518Z\"\n            className=\"fill-bg-contrast\"\n          />\n        </svg>\n      </span>\n    </span>\n  );\n};\n\nexport default memo(TutorialBody);\n"
  },
  {
    "path": "client/src/CommandBar/Tutorial/TutorialTooltip.tsx",
    "content": "import React, { memo, PropsWithChildren, useEffect, useState } from 'react';\nimport Tippy from '@tippyjs/react/headless';\n\ntype Props = {\n  content: React.ReactElement;\n  wrapperClassName?: string;\n};\n\nconst TutorialTooltip = ({\n  children,\n  content,\n  wrapperClassName,\n}: PropsWithChildren<Props>) => {\n  const [isVisible, setIsVisible] = useState(false);\n\n  useEffect(() => {\n    setTimeout(() => setIsVisible(true), 150);\n  }, []);\n\n  return (\n    <Tippy\n      animation={false}\n      visible={isVisible}\n      placement=\"left\"\n      render={() => content}\n      interactive\n    >\n      <div className={wrapperClassName || 'flex flex-col w-full'}>\n        {children}\n      </div>\n    </Tippy>\n  );\n};\n\nexport default memo(TutorialTooltip);\n"
  },
  {
    "path": "client/src/CommandBar/index.tsx",
    "content": "import { memo, useCallback, useContext, useMemo } from 'react';\nimport Modal from '../components/Modal';\nimport useKeyboardNavigation from '../hooks/useKeyboardNavigation';\nimport { CommandBarStepEnum } from '../types/general';\nimport { CommandBarContext } from '../context/commandBarContext';\nimport { useGlobalShortcuts } from '../hooks/useGlobalShortcuts';\nimport { checkEventKeys } from '../utils/keyboardUtils';\nimport { UIContext } from '../context/uiContext';\nimport Initial from './steps/Initial';\nimport PrivateRepos from './steps/PrivateRepos';\nimport PublicRepos from './steps/PublicRepos';\nimport LocalRepos from './steps/LocalRepos';\nimport Documentation from './steps/Documentation';\nimport CreateProject from './steps/CreateProject';\nimport ManageRepos from './steps/ManageRepos';\nimport AddNewRepo from './steps/AddNewRepo';\nimport ToggleTheme from './steps/ToggleTheme';\nimport SearchFiles from './steps/SeachFiles';\nimport AddFileToStudio from './steps/AddToStudio';\nimport SearchDocs from './steps/SeachDocs';\n\ntype Props = {};\n\nconst CommandBar = ({}: Props) => {\n  const { chosenStep } = useContext(CommandBarContext.CurrentStep);\n  const { isVisible } = useContext(CommandBarContext.General);\n  const { setChosenStep, setIsVisible } = useContext(\n    CommandBarContext.Handlers,\n  );\n  const { onBoardingState } = useContext(UIContext.Onboarding);\n  const globalShortcuts = useGlobalShortcuts();\n\n  const handleClose = useCallback(() => {\n    setIsVisible(false);\n    setChosenStep({\n      id: CommandBarStepEnum.INITIAL,\n    });\n  }, []);\n\n  const shouldShowTutorial = useMemo(() => {\n    return !onBoardingState.isCommandBarTutorialFinished;\n  }, [onBoardingState]);\n\n  const handleKeyEvent = useCallback(\n    (e: KeyboardEvent) => {\n      if (checkEventKeys(e, ['cmd', 'K'])) {\n        e.stopPropagation();\n        e.preventDefault();\n        setIsVisible(true);\n      }\n      Object.values(globalShortcuts).forEach((s) => {\n        if (checkEventKeys(e, s.shortcut)) {\n          e.stopPropagation();\n          e.preventDefault();\n          s.action();\n        }\n      });\n    },\n    [isVisible, globalShortcuts],\n  );\n  useKeyboardNavigation(handleKeyEvent);\n\n  return (\n    <Modal\n      isVisible={isVisible}\n      onClose={handleClose}\n      noBg\n      containerClassName={\n        'h-[28.875rem] !z-90 bg-bg-base border border-bg-border backdrop-blur-8 shadow-float'\n      }\n    >\n      {chosenStep.id === CommandBarStepEnum.INITIAL ? (\n        <Initial shouldShowTutorial={shouldShowTutorial} />\n      ) : chosenStep.id === CommandBarStepEnum.PRIVATE_REPOS ? (\n        <PrivateRepos shouldShowTutorial={shouldShowTutorial} />\n      ) : chosenStep.id === CommandBarStepEnum.PUBLIC_REPOS ? (\n        <PublicRepos />\n      ) : chosenStep.id === CommandBarStepEnum.LOCAL_REPOS ? (\n        <LocalRepos />\n      ) : chosenStep.id === CommandBarStepEnum.DOCS ? (\n        <Documentation />\n      ) : chosenStep.id === CommandBarStepEnum.CREATE_PROJECT ? (\n        <CreateProject />\n      ) : chosenStep.id === CommandBarStepEnum.MANAGE_REPOS ? (\n        <ManageRepos shouldShowTutorial={shouldShowTutorial} />\n      ) : chosenStep.id === CommandBarStepEnum.ADD_NEW_REPO ? (\n        <AddNewRepo shouldShowTutorial={shouldShowTutorial} />\n      ) : chosenStep.id === CommandBarStepEnum.TOGGLE_THEME ? (\n        <ToggleTheme />\n      ) : chosenStep.id === CommandBarStepEnum.SEARCH_FILES ? (\n        <SearchFiles {...(chosenStep.data || {})} />\n      ) : chosenStep.id === CommandBarStepEnum.SEARCH_DOCS ? (\n        <SearchDocs {...chosenStep.data} />\n      ) : chosenStep.id === CommandBarStepEnum.ADD_TO_STUDIO ? (\n        <AddFileToStudio {...chosenStep.data} />\n      ) : null}\n    </Modal>\n  );\n};\n\nexport default memo(CommandBar);\n"
  },
  {
    "path": "client/src/CommandBar/steps/AddNewRepo.tsx",
    "content": "import React, { memo, useCallback, useContext, useMemo } from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { toast } from 'sonner';\nimport { useGlobalShortcuts } from '../../hooks/useGlobalShortcuts';\nimport {\n  CommandBarItemGeneralType,\n  CommandBarStepEnum,\n} from '../../types/general';\nimport { GlobeIcon, HardDriveIcon, RepositoryIcon } from '../../icons';\nimport Header from '../Header';\nimport Body from '../Body';\nimport Footer from '../Footer';\nimport { CommandBarContext } from '../../context/commandBarContext';\nimport { DeviceContext } from '../../context/deviceContext';\nimport { scanLocalRepos, syncRepo } from '../../services/api';\nimport SpinLoaderContainer from '../../components/Loaders/SpinnerLoader';\nimport TutorialBody from '../Tutorial/TutorialBody';\nimport TutorialTooltip from '../Tutorial/TutorialTooltip';\nimport { tutorialSteps } from '../../consts/tutorialSteps';\n\ntype Props = {\n  shouldShowTutorial?: boolean;\n};\n\nconst AddNewRepo = ({ shouldShowTutorial }: Props) => {\n  const { t } = useTranslation();\n  const globalShortcuts = useGlobalShortcuts();\n  const { setChosenStep } = useContext(CommandBarContext.Handlers);\n  const { homeDir, chooseFolder } = useContext(DeviceContext);\n\n  const handleBack = useCallback(() => {\n    setChosenStep({ id: CommandBarStepEnum.MANAGE_REPOS });\n  }, []);\n\n  const handleChooseFolder = useCallback(async () => {\n    let folder: string | string[] | null;\n    if (chooseFolder) {\n      try {\n        folder = await chooseFolder({\n          directory: true,\n          defaultPath: homeDir,\n        });\n      } catch (err) {\n        console.log(err);\n      }\n    }\n    // @ts-ignore\n    if (typeof folder === 'string') {\n      scanLocalRepos(folder).then((data) => {\n        if (data.list.length === 1) {\n          syncRepo(data.list[0].ref);\n          toast(t('Indexing repository'), {\n            description: (\n              <Trans values={{ repoName: data.list[0].name }}>\n                <span className=\"text-label-base body-s-b\">repoName</span> has\n                started indexing. You’ll receive a notification as soon as this\n                process completes.\n              </Trans>\n            ),\n            icon: <SpinLoaderContainer sizeClassName=\"w-4 h-4\" />,\n            unstyled: true,\n          });\n          handleBack();\n          return;\n        } else if (!data.list.length) {\n          toast.error(t('Not a git repository'), {\n            description: t('The folder you selected is not a git repository.'),\n            icon: <HardDriveIcon sizeClassName=\"w-4 h-4\" />,\n            unstyled: true,\n          });\n        } else if (data.list.length > 1) {\n          toast.error(t('Folder too large'), {\n            description: t(\n              'The folder you selected has multiple git repositories nested inside.',\n            ),\n            icon: <HardDriveIcon sizeClassName=\"w-4 h-4\" />,\n            unstyled: true,\n          });\n        }\n      });\n    }\n  }, [chooseFolder, homeDir, handleBack]);\n\n  const initialSections = useMemo(() => {\n    const contextItems: CommandBarItemGeneralType[] = [\n      {\n        label: t('Private repository'),\n        Icon: RepositoryIcon,\n        id: CommandBarStepEnum.PRIVATE_REPOS,\n        key: 'private',\n        shortcut: globalShortcuts.openPrivateRepos.shortcut,\n        footerHint: '',\n        footerBtns: [{ label: t('Next'), shortcut: ['entr'] }],\n      },\n      {\n        label: t('Public repository'),\n        Icon: GlobeIcon,\n        id: CommandBarStepEnum.PUBLIC_REPOS,\n        key: 'public',\n        shortcut: globalShortcuts.openPublicRepos.shortcut,\n        footerHint: '',\n        footerBtns: [{ label: t('Next'), shortcut: ['entr'] }],\n      },\n      {\n        label: t('Local repository'),\n        Icon: HardDriveIcon,\n        id: CommandBarStepEnum.LOCAL_REPOS,\n        onClick: handleChooseFolder,\n        key: 'local',\n        shortcut: globalShortcuts.openLocalRepos.shortcut,\n        footerHint: '',\n        footerBtns: [{ label: t('Next'), shortcut: ['entr'] }],\n      },\n    ];\n    return [\n      {\n        items: contextItems,\n        itemsOffset: 0,\n        key: 'context-items',\n      },\n    ];\n  }, [t, globalShortcuts]);\n\n  return (\n    <div className=\"flex flex-col h-[28.875rem] w-[40rem] overflow-auto\">\n      <Header\n        breadcrumbs={[t('Add repository')]}\n        noInput\n        handleBack={handleBack}\n      />\n      {shouldShowTutorial ? (\n        <TutorialTooltip\n          content={\n            <TutorialBody\n              stepNumber={2}\n              title={t(tutorialSteps[1].title)}\n              description={t(tutorialSteps[1].description)}\n              hint={t(tutorialSteps[1].hint[0])}\n            />\n          }\n          wrapperClassName=\"absolute top-[5rem] left-0 right-0\"\n        >\n          <div className=\"\" />\n        </TutorialTooltip>\n      ) : null}\n      <Body sections={initialSections} />\n      <Footer />\n    </div>\n  );\n};\n\nexport default memo(AddNewRepo);\n"
  },
  {
    "path": "client/src/CommandBar/steps/AddToStudio.tsx",
    "content": "import {\n  ChangeEvent,\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport {\n  AddDocToStudioDataType,\n  AddFileToStudioDataType,\n  CommandBarItemGeneralType,\n  CommandBarSectionType,\n  CommandBarStepEnum,\n  TabTypesEnum,\n} from '../../types/general';\nimport { PlusSignIcon } from '../../icons';\nimport Header from '../Header';\nimport Body from '../Body';\nimport Footer from '../Footer';\nimport { CommandBarContext } from '../../context/commandBarContext';\nimport { ProjectContext } from '../../context/projectContext';\nimport { TabsContext } from '../../context/tabsContext';\nimport { postCodeStudio } from '../../services/api';\nimport TokenUsage from '../../components/TokenUsage';\nimport { TOKEN_LIMIT } from '../../consts/codeStudio';\n\ntype Props = (AddFileToStudioDataType | AddDocToStudioDataType) & {};\n\nconst AddToStudio = (props: Props) => {\n  const { t } = useTranslation();\n  const { setChosenStep } = useContext(CommandBarContext.Handlers);\n  const { project, refreshCurrentProjectStudios } = useContext(\n    ProjectContext.Current,\n  );\n  const { openNewTab } = useContext(TabsContext.Handlers);\n  const [inputValue, setInputValue] = useState('');\n  const [sectionsToShow, setSectionsToShow] = useState<CommandBarSectionType[]>(\n    [],\n  );\n\n  const handleChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n    setInputValue(e.target.value);\n  }, []);\n\n  const handleBack = useCallback(() => {\n    setChosenStep({ id: CommandBarStepEnum.INITIAL });\n  }, []);\n\n  const handleNewCodeStudio = useCallback(async () => {\n    if (project?.id) {\n      const newId = await postCodeStudio(project.id);\n      refreshCurrentProjectStudios();\n      if ('path' in props) {\n        openNewTab(\n          {\n            type: TabTypesEnum.FILE,\n            studioId: newId,\n            ...props,\n          },\n          'left',\n        );\n      } else {\n        openNewTab(\n          {\n            type: TabTypesEnum.DOC,\n            studioId: newId,\n            ...props,\n          },\n          'left',\n        );\n      }\n      openNewTab({ type: TabTypesEnum.STUDIO, studioId: newId }, 'right');\n    }\n  }, [project?.id, props, openNewTab, refreshCurrentProjectStudios]);\n\n  const handleAddToCodeStudio = useCallback(\n    async (studioId: string) => {\n      if (project?.id) {\n        if ('path' in props) {\n          openNewTab(\n            {\n              type: TabTypesEnum.FILE,\n              studioId,\n              ...props,\n            },\n            'left',\n          );\n        } else {\n          openNewTab(\n            {\n              type: TabTypesEnum.DOC,\n              studioId,\n              ...props,\n            },\n            'left',\n          );\n        }\n        openNewTab({ type: TabTypesEnum.STUDIO, studioId }, 'right');\n      }\n    },\n    [project?.id, props, openNewTab],\n  );\n\n  const initialSections = useMemo(() => {\n    return [\n      {\n        items: [\n          {\n            label: t('New studio conversation'),\n            Icon: PlusSignIcon,\n            id: 'new_code_studio',\n            key: 'new_code_studio',\n            onClick: handleNewCodeStudio,\n            closeOnClick: true,\n            footerHint: '',\n            footerBtns: [{ label: t('Create new'), shortcut: ['entr'] }],\n          },\n        ],\n        itemsOffset: 0,\n        key: 'new-items',\n      },\n      {\n        items: (project?.studios || []).map((s) => ({\n          label: s.name,\n          Icon: () => (\n            <TokenUsage\n              percent={(s.token_counts.total / TOKEN_LIMIT) * 100}\n              sizeClassName={'w-6 h-6'}\n            />\n          ),\n          iconContainerClassName: 'bg-transparent',\n          id: s.id,\n          key: s.id,\n          onClick: () => handleAddToCodeStudio(s.id),\n          closeOnClick: true,\n          footerHint: t('{{count}} context files used', {\n            count: s.context.length,\n          }),\n          footerBtns: [{ label: t('Add to existing'), shortcut: ['entr'] }],\n        })),\n        label: t('Existing studio conversations'),\n        itemsOffset: 1,\n        key: 'studio-items',\n      },\n    ];\n  }, [t, project?.studios, handleNewCodeStudio, openNewTab, props]);\n\n  useEffect(() => {\n    if (!inputValue) {\n      setSectionsToShow(initialSections);\n      return;\n    }\n    const newSectionsToShow: CommandBarSectionType[] = [];\n    initialSections.forEach((s) => {\n      const items = (s.items as CommandBarItemGeneralType[]).filter((item) => {\n        return item.label?.toLowerCase().includes(inputValue?.toLowerCase());\n      });\n\n      if (items.length) {\n        newSectionsToShow.push({\n          ...s,\n          items,\n        });\n      }\n    });\n    setSectionsToShow(newSectionsToShow);\n  }, [initialSections, inputValue]);\n\n  const breadcrumbs = useMemo(() => {\n    return [t(`Add ${'path' in props ? 'file' : 'doc'} to studio`)];\n  }, [props, t]);\n\n  return (\n    <div className=\"flex flex-col h-[28.875rem] w-[40rem] overflow-auto\">\n      <Header\n        value={inputValue}\n        onChange={handleChange}\n        breadcrumbs={breadcrumbs}\n        handleBack={handleBack}\n        placeholder={t('Search studio conversations...')}\n      />\n      <Body sections={sectionsToShow} />\n      <Footer />\n    </div>\n  );\n};\n\nexport default memo(AddToStudio);\n"
  },
  {
    "path": "client/src/CommandBar/steps/CreateProject.tsx",
    "content": "import {\n  ChangeEvent,\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport Header from '../Header';\nimport Footer from '../Footer';\nimport { CommandBarStepEnum } from '../../types/general';\nimport { CommandBarContext } from '../../context/commandBarContext';\nimport { createProject } from '../../services/api';\nimport { ProjectContext } from '../../context/projectContext';\n\ntype Props = {};\n\nconst CreateProject = ({}: Props) => {\n  const { t } = useTranslation();\n  const [inputValue, setInputValue] = useState('');\n  const { setChosenStep, setFocusedItem, setIsVisible } = useContext(\n    CommandBarContext.Handlers,\n  );\n  const { setCurrentProjectId } = useContext(ProjectContext.Current);\n  const { refreshAllProjects } = useContext(ProjectContext.All);\n\n  const handleInputChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n    setInputValue(e.target.value);\n  }, []);\n\n  useEffect(() => {\n    setFocusedItem({\n      footerHint: t('Provide a short, concise title for your project'),\n      footerBtns: [{ label: t('Create project'), shortcut: ['entr'] }],\n    });\n  }, [t]);\n\n  const switchProject = useCallback((id: string) => {\n    setCurrentProjectId(id);\n    setIsVisible(false);\n    refreshAllProjects();\n    setChosenStep({\n      id: CommandBarStepEnum.INITIAL,\n    });\n  }, []);\n\n  const breadcrumbs = useMemo(() => {\n    return [t('Create project')];\n  }, [t]);\n\n  const handleBack = useCallback(() => {\n    setChosenStep({ id: CommandBarStepEnum.INITIAL });\n  }, []);\n\n  const submitHandler = useCallback(\n    async (value: string) => {\n      setInputValue('');\n      const newId = await createProject(value);\n      switchProject(newId);\n    },\n    [switchProject],\n  );\n\n  return (\n    <div className=\"flex flex-col h-[28.875rem] w-[40rem] overflow-auto\">\n      <Header\n        breadcrumbs={breadcrumbs}\n        handleBack={handleBack}\n        customSubmitHandler={submitHandler}\n        placeholder={t('Untitled project')}\n        value={inputValue}\n        onChange={handleInputChange}\n      />\n      <div className=\"flex-1\" />\n      <Footer />\n    </div>\n  );\n};\n\nexport default memo(CreateProject);\n"
  },
  {
    "path": "client/src/CommandBar/steps/Documentation/ActionsDropdown.tsx",
    "content": "import { memo, useContext, useEffect, useMemo } from 'react';\nimport SectionItem from '../../../components/Dropdown/Section/SectionItem';\nimport DropdownSection from '../../../components/Dropdown/Section';\nimport { CommandBarContext } from '../../../context/commandBarContext';\n\ntype Props = {\n  handleClose: () => void;\n};\n\nconst ActionsDropDown = ({ handleClose }: Props) => {\n  const { focusedItem } = useContext(CommandBarContext.FooterValues);\n\n  const focusedDropdownItems = useMemo(() => {\n    return (\n      (focusedItem &&\n        'focusedItemProps' in focusedItem &&\n        focusedItem.focusedItemProps?.dropdownItems) ||\n      []\n    );\n  }, [focusedItem]);\n\n  const focusedDropdownItemsLength = useMemo(() => {\n    return focusedDropdownItems.reduce(\n      (prev: number, curr: { items: Record<string, any>[]; key: string }) =>\n        prev + curr.items.length,\n      0,\n    );\n  }, [focusedDropdownItems]);\n\n  useEffect(() => {\n    if (!focusedDropdownItemsLength) {\n      handleClose();\n    }\n  }, [focusedDropdownItemsLength]);\n\n  return (\n    <div>\n      {!!focusedDropdownItems.length &&\n        focusedDropdownItems.map(\n          (section: { items: Record<string, any>[]; key: string }) => (\n            <DropdownSection key={section.key}>\n              {section.items.map((item: Record<string, any>) => (\n                <SectionItem\n                  color=\"base\"\n                  shortcut={item.shortcut}\n                  key={item.key}\n                  onClick={item.onClick}\n                  label={item.label}\n                  icon={item.icon}\n                  index={item.key}\n                />\n              ))}\n            </DropdownSection>\n          ),\n        )}\n    </div>\n  );\n};\n\nexport default memo(ActionsDropDown);\n"
  },
  {
    "path": "client/src/CommandBar/steps/Documentation/index.tsx",
    "content": "import {\n  ChangeEvent,\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport {\n  CommandBarSectionType,\n  CommandBarStepEnum,\n} from '../../../types/general';\nimport { CommandBarContext } from '../../../context/commandBarContext';\nimport {\n  getIndexedDocs,\n  indexDocsUrl,\n  verifyDocsUrl,\n} from '../../../services/api';\nimport { PlusSignIcon } from '../../../icons';\nimport { DocShortType } from '../../../types/api';\nimport Header from '../../Header';\nimport Body from '../../Body';\nimport Footer from '../../Footer';\nimport DocItem from '../items/DocItem';\nimport ActionsDropdown from './ActionsDropdown';\n\ntype Props = {};\n\nconst Documentation = ({}: Props) => {\n  const { t } = useTranslation();\n  const [isAddMode, setIsAddMode] = useState(false);\n  const [hasFetched, setHasFetched] = useState(false);\n  const [indexedDocs, setIndexedDocs] = useState<DocShortType[]>([]);\n  const [addedDoc, setAddedDoc] = useState<null | { id: string; url: string }>(\n    null,\n  );\n  const [isDropdownVisible, setIsDropdownVisible] = useState(false);\n  const { setChosenStep, setFocusedItem } = useContext(\n    CommandBarContext.Handlers,\n  );\n  const [inputValue, setInputValue] = useState('');\n\n  const handleInputChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n    setInputValue(e.target.value);\n  }, []);\n\n  const enterAddMode = useCallback(() => {\n    setFocusedItem({\n      footerHint: t('Paste a link to any documentation web page'),\n      footerBtns: [{ label: t('Sync'), shortcut: ['entr'] }],\n    });\n    setIsAddMode(true);\n  }, []);\n\n  const addItem = useMemo(() => {\n    return {\n      itemsOffset: 0,\n      key: 'add-docs',\n      items: [\n        {\n          label: t('Add documentation'),\n          Icon: PlusSignIcon,\n          footerHint: t('Add any library documentation'),\n          footerBtns: [\n            {\n              label: t('Add'),\n              shortcut: ['entr'],\n            },\n          ],\n          key: 'add',\n          id: 'Add',\n          onClick: enterAddMode,\n        },\n      ],\n    };\n  }, [t]);\n  const [sections, setSections] = useState<CommandBarSectionType[]>([addItem]);\n\n  const breadcrumbs = useMemo(() => {\n    const arr = [t('Docs')];\n    if (isAddMode) {\n      arr.push(t('Add docs'));\n    }\n    return arr;\n  }, [t, isAddMode]);\n\n  const handleBack = useCallback(() => {\n    if (isAddMode) {\n      setIsAddMode(false);\n    } else {\n      setChosenStep({ id: CommandBarStepEnum.INITIAL });\n    }\n  }, [isAddMode]);\n\n  const refetchDocs = useCallback(() => {\n    getIndexedDocs().then((data) => {\n      setIndexedDocs(data);\n      setHasFetched(true);\n      if (addedDoc && data.find((d) => d.id === addedDoc.id)) {\n        setAddedDoc(null);\n      }\n    });\n  }, [addedDoc]);\n\n  useEffect(() => {\n    const mapped = indexedDocs.map((d) => ({\n      Component: DocItem,\n      componentProps: {\n        doc: d,\n        isIndexed: d.index_status === 'done',\n        refetchDocs,\n      },\n      key: d.id,\n    }));\n    if (addedDoc) {\n      mapped.unshift({\n        Component: DocItem,\n        componentProps: {\n          doc: {\n            url: addedDoc.url,\n            id: addedDoc.id,\n            name: '',\n            favicon: '',\n            index_status: 'indexing',\n          },\n          isIndexed: false,\n          refetchDocs: () => {\n            refetchDocs();\n            setAddedDoc(null);\n          },\n        },\n        key: `doc-${addedDoc.id}`,\n      });\n    }\n    setSections([\n      addItem,\n      {\n        key: 'indexed-docs',\n        label: t('Indexed documentation web pages'),\n        items: mapped,\n      },\n    ]);\n  }, [indexedDocs, addedDoc, hasFetched, refetchDocs]);\n\n  useEffect(() => {\n    if (!isAddMode || !hasFetched) {\n      refetchDocs();\n    }\n  }, [isAddMode]);\n\n  const handleAddSubmit = useCallback(async (inputValue: string) => {\n    setFocusedItem({\n      footerHint: t('Verifying access...'),\n      footerBtns: [],\n    });\n    setInputValue('');\n    try {\n      await verifyDocsUrl(inputValue.trim());\n      setIsAddMode(false);\n      const newId = await indexDocsUrl(inputValue);\n      setAddedDoc({ id: newId, url: inputValue });\n    } catch (err) {\n      setFocusedItem({\n        footerHint: t(\n          \"We couldn't find any docs at that link. Try again or make sure the link is correct!\",\n        ),\n        footerBtns: [],\n      });\n    }\n  }, []);\n\n  const sectionsToShow = useMemo(() => {\n    if (!inputValue) {\n      return sections;\n    }\n    const newSections: CommandBarSectionType[] = [];\n    sections.forEach((s) => {\n      const newItems = s.items.filter(\n        (i) =>\n          ('label' in i ? i.label : i.componentProps.doc.name)\n            ?.toLowerCase()\n            .includes(inputValue?.toLowerCase()),\n      );\n      if (newItems.length) {\n        newSections.push({ ...s, items: newItems });\n      }\n    });\n    return newSections;\n  }, [inputValue, sections]);\n\n  return (\n    <div className=\"flex flex-col h-[28.875rem] w-[40rem] overflow-auto\">\n      <Header\n        breadcrumbs={breadcrumbs}\n        handleBack={handleBack}\n        customSubmitHandler={isAddMode ? handleAddSubmit : undefined}\n        placeholder={\n          isAddMode ? t('Documentation URL...') : t('Search docs...')\n        }\n        onChange={handleInputChange}\n        value={inputValue}\n        disableKeyNav={!isAddMode && isDropdownVisible}\n      />\n      {isAddMode ? (\n        <div className=\"flex-1\" />\n      ) : (\n        <Body\n          sections={sectionsToShow}\n          disableKeyNav={!isAddMode && isDropdownVisible}\n        />\n      )}\n      <Footer\n        onDropdownVisibilityChange={setIsDropdownVisible}\n        ActionsDropdown={isAddMode ? undefined : ActionsDropdown}\n      />\n    </div>\n  );\n};\n\nexport default memo(Documentation);\n"
  },
  {
    "path": "client/src/CommandBar/steps/Initial.tsx",
    "content": "import React, {\n  ChangeEvent,\n  memo,\n  useCallback,\n  useContext,\n  useMemo,\n  useState,\n} from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { ProjectContext } from '../../context/projectContext';\nimport {\n  BugIcon,\n  ChatBubblesIcon,\n  CloseSignInCircleIcon,\n  CodeStudioIcon,\n  CogIcon,\n  ColorSwitchIcon,\n  DocumentsIcon,\n  MagazineIcon,\n  MagnifyToolIcon,\n  PlusSignIcon,\n  RegexIcon,\n  RepositoryIcon,\n} from '../../icons';\nimport { CommandBarContext } from '../../context/commandBarContext';\nimport Header from '../Header';\nimport Body from '../Body';\nimport Footer from '../Footer';\nimport {\n  CommandBarItemGeneralType,\n  CommandBarSectionType,\n  CommandBarStepEnum,\n  TabTypesEnum,\n} from '../../types/general';\nimport { UIContext } from '../../context/uiContext';\nimport { useGlobalShortcuts } from '../../hooks/useGlobalShortcuts';\nimport {\n  getJsonFromStorage,\n  RECENT_COMMANDS_KEY,\n} from '../../services/storage';\nimport { bubbleUpRecentItems } from '../../utils/commandBarUtils';\nimport { TabsContext } from '../../context/tabsContext';\nimport TutorialBody from '../Tutorial/TutorialBody';\nimport TutorialTooltip from '../Tutorial/TutorialTooltip';\nimport { tutorialSteps } from '../../consts/tutorialSteps';\nimport {\n  closeTabShortcut,\n  newChatTabShortcut,\n  newStudioTabShortcut,\n} from '../../consts/shortcuts';\nimport { postCodeStudio } from '../../services/api';\n\ntype Props = {\n  shouldShowTutorial?: boolean;\n};\n\nconst InitialCommandBar = ({ shouldShowTutorial }: Props) => {\n  const { t } = useTranslation();\n  const { setIsVisible } = useContext(CommandBarContext.Handlers);\n  const { tabItems } = useContext(CommandBarContext.FocusedTab);\n  const { openNewTab, closeCurrentTab } = useContext(TabsContext.Handlers);\n  const { tab: leftTab } = useContext(TabsContext.CurrentLeft);\n  const { tab: rightTab } = useContext(TabsContext.CurrentRight);\n  const { projects } = useContext(ProjectContext.All);\n  const { setCurrentProjectId, project } = useContext(ProjectContext.Current);\n  const { theme } = useContext(UIContext.Theme);\n  const [inputValue, setInputValue] = useState('');\n  const globalShortcuts = useGlobalShortcuts();\n\n  const handleInputChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n    setInputValue(e.target.value);\n  }, []);\n\n  const switchProject = useCallback((id: string) => {\n    setCurrentProjectId(id);\n    setIsVisible(false);\n  }, []);\n\n  const initialSections = useMemo(() => {\n    const recentKeys = getJsonFromStorage<string[]>(RECENT_COMMANDS_KEY);\n    const contextItems: CommandBarItemGeneralType[] = [\n      {\n        label: t('Add new repository'),\n        Icon: PlusSignIcon,\n        id: CommandBarStepEnum.ADD_NEW_REPO,\n        key: CommandBarStepEnum.ADD_NEW_REPO,\n        shortcut: ['cmd', 'A'],\n        footerHint: '',\n        footerBtns: [\n          {\n            label: t('Add'),\n            shortcut: ['entr'],\n          },\n        ],\n      },\n      {\n        label: t('Manage repositories'),\n        Icon: RepositoryIcon,\n        id: CommandBarStepEnum.MANAGE_REPOS,\n        key: CommandBarStepEnum.MANAGE_REPOS,\n        shortcut: globalShortcuts.openManageRepos.shortcut,\n        footerHint: '',\n        footerBtns: [{ label: t('Manage'), shortcut: ['entr'] }],\n      },\n      {\n        label: t('Manage docs'),\n        Icon: MagazineIcon,\n        id: CommandBarStepEnum.DOCS,\n        key: CommandBarStepEnum.DOCS,\n        shortcut: globalShortcuts.openAddDocs.shortcut,\n        footerHint: '',\n        footerBtns: [{ label: t('Manage'), shortcut: ['entr'] }],\n      },\n    ];\n    const projectItems: CommandBarItemGeneralType[] = projects\n      .map(\n        (p): CommandBarItemGeneralType => ({\n          label: p.name,\n          Icon: MagazineIcon,\n          id: `project-${p.id}`,\n          key: `project-${p.id}`,\n          onClick: () => switchProject(p.id),\n          footerHint:\n            project?.id === p.id\n              ? t('Manage project')\n              : t(`Switch to`) + ' ' + p.name,\n          footerBtns:\n            project?.id === p.id\n              ? [{ label: t('Manage'), shortcut: ['entr'] }]\n              : [\n                  {\n                    label: t('Open'),\n                    shortcut: ['entr'],\n                  },\n                ],\n        }),\n      )\n      .concat({\n        label: t('New project'),\n        Icon: MagazineIcon,\n        id: CommandBarStepEnum.CREATE_PROJECT,\n        key: CommandBarStepEnum.CREATE_PROJECT,\n        shortcut: globalShortcuts.createNewProject.shortcut,\n        footerHint: t('Create new project'),\n        footerBtns: [\n          {\n            label: t('Manage'),\n            shortcut: ['entr'],\n          },\n        ],\n      });\n    const themeItems: CommandBarItemGeneralType[] = [\n      {\n        label: t(`Theme`),\n        Icon: ColorSwitchIcon,\n        id: CommandBarStepEnum.TOGGLE_THEME,\n        key: CommandBarStepEnum.TOGGLE_THEME,\n        shortcut: globalShortcuts.toggleTheme.shortcut,\n        footerHint: t(`Change application colour theme`),\n        footerBtns: [\n          {\n            label: t('Select'),\n            shortcut: ['entr'],\n          },\n        ],\n      },\n    ];\n    const otherCommands: CommandBarItemGeneralType[] = [\n      ...(!!leftTab || !!rightTab\n        ? [\n            {\n              label: t(`Close current tab`),\n              Icon: CloseSignInCircleIcon,\n              id: `close-tab`,\n              key: `close-tab`,\n              onClick: closeCurrentTab,\n              shortcut: closeTabShortcut,\n              closeOnClick: true,\n              footerHint: t(`Close currently focused tab`),\n              footerBtns: [\n                {\n                  label: t('Close'),\n                  shortcut: ['entr'],\n                },\n              ],\n            },\n            {\n              label: t(`Close all tabs`),\n              Icon: CloseSignInCircleIcon,\n              id: `close-tabs`,\n              key: `close-tabs`,\n              onClick: globalShortcuts.closeAllTabs.action,\n              shortcut: globalShortcuts.closeAllTabs.shortcut,\n              closeOnClick: true,\n              footerHint: t(`Close all open tabs`),\n              footerBtns: [\n                {\n                  label: t('Close'),\n                  shortcut: ['entr'],\n                },\n              ],\n            },\n          ]\n        : []),\n      {\n        label: t(`Account settings`),\n        Icon: CogIcon,\n        id: `account-settings`,\n        key: `account-settings`,\n        onClick: globalShortcuts.openSettings.action,\n        shortcut: globalShortcuts.openSettings.shortcut,\n        footerHint: t(`Open account settings`),\n        footerBtns: [\n          {\n            label: t('Open'),\n            shortcut: ['entr'],\n          },\n        ],\n      },\n      {\n        label: t(`Documentation`),\n        Icon: DocumentsIcon,\n        id: `app-docs`,\n        key: `app-docs`,\n        onClick: globalShortcuts.openAppDocs.action,\n        shortcut: globalShortcuts.openAppDocs.shortcut,\n        footerHint: t(`View bloop app documentation on our website`),\n        footerBtns: [\n          {\n            label: t('Open'),\n            shortcut: ['entr'],\n          },\n        ],\n      },\n      {\n        label: t(`Report a bug`),\n        Icon: BugIcon,\n        id: `bug`,\n        key: `bug`,\n        onClick: globalShortcuts.reportABug.action,\n        shortcut: globalShortcuts.reportABug.shortcut,\n        footerHint: t(`Report a bug`),\n        footerBtns: [\n          {\n            label: t('Open'),\n            shortcut: ['entr'],\n          },\n        ],\n      },\n      {\n        label: t(`Code search`),\n        Icon: RegexIcon,\n        id: `toggle-regex`,\n        key: `toggle-regex`,\n        onClick: globalShortcuts.toggleRegex.action,\n        shortcut: globalShortcuts.toggleRegex.shortcut,\n        footerHint: t(`Search your repositories using RegExp`),\n        footerBtns: [\n          {\n            label: t('Toggle'),\n            shortcut: ['entr'],\n          },\n        ],\n      },\n      {\n        label: t(`File search`),\n        Icon: MagnifyToolIcon,\n        id: CommandBarStepEnum.SEARCH_FILES,\n        key: CommandBarStepEnum.SEARCH_FILES,\n        shortcut: globalShortcuts.openSearchFiles.shortcut,\n        footerHint: t(`Search your files in this project`),\n        footerBtns: [\n          {\n            label: t('Search'),\n            shortcut: ['entr'],\n          },\n        ],\n      },\n    ];\n    const commandsItems = [...themeItems, ...otherCommands];\n    const newTabItems = project?.repos.length\n      ? [\n          {\n            label: t('New conversation'),\n            Icon: ChatBubblesIcon,\n            id: 'new chat',\n            key: 'new_chat',\n            onClick: () => openNewTab({ type: TabTypesEnum.CHAT }),\n            shortcut: newChatTabShortcut,\n            closeOnClick: true,\n            footerHint: '',\n            footerBtns: [\n              {\n                label: t('Open'),\n                shortcut: ['entr'],\n              },\n            ],\n          },\n          {\n            label: t('New studio conversation'),\n            Icon: CodeStudioIcon,\n            id: 'new studio',\n            key: 'new_studio',\n            onClick: async () => {\n              const newId = await postCodeStudio(project.id);\n              openNewTab({ type: TabTypesEnum.STUDIO, studioId: newId });\n            },\n            shortcut: newStudioTabShortcut,\n            closeOnClick: true,\n            footerHint: '',\n            footerBtns: [\n              {\n                label: t('Open'),\n                shortcut: ['entr'],\n              },\n            ],\n          },\n        ]\n      : [];\n    const chatItems: CommandBarItemGeneralType[] = (\n      project?.conversations || []\n    )\n      .slice(-5)\n      .map((c) => ({\n        label: c.title,\n        id: `chat-${c.id}`,\n        key: `chat-${c.id}`,\n        Icon: ChatBubblesIcon,\n        closeOnClick: true,\n        onClick: () => {\n          openNewTab({\n            type: TabTypesEnum.CHAT,\n            title: c.title,\n            conversationId: c.id,\n          });\n        },\n        footerHint: '',\n        footerBtns: [{ label: t('Open'), shortcut: ['entr'] }],\n      }));\n    return bubbleUpRecentItems(\n      [\n        ...(newTabItems.length\n          ? [\n              {\n                items: newTabItems,\n                key: 'new-tab-items',\n              },\n            ]\n          : []),\n        ...(tabItems.length\n          ? [\n              {\n                items: tabItems,\n                key: 'tab-items',\n              },\n            ]\n          : []),\n        ...(chatItems.length\n          ? [\n              {\n                label: t('Recent conversations'),\n                items: chatItems,\n                key: 'chat-items',\n              },\n            ]\n          : []),\n        {\n          items: contextItems,\n          label: t('Manage context'),\n          key: 'context-items',\n        },\n        {\n          items: projectItems,\n          label: t('Recent projects'),\n          key: 'recent-projects',\n        },\n        {\n          items: commandsItems,\n          label: t('Commands'),\n          key: 'general-commands',\n        },\n      ],\n      recentKeys || [],\n      t('Recently used'),\n    );\n  }, [\n    t,\n    projects,\n    project,\n    theme,\n    globalShortcuts,\n    tabItems,\n    openNewTab,\n    shouldShowTutorial,\n    closeCurrentTab,\n    !!leftTab || !!rightTab,\n  ]);\n\n  const sectionsToShow = useMemo(() => {\n    if (!inputValue) {\n      return initialSections;\n    }\n    const newSections: CommandBarSectionType[] = [];\n    initialSections.forEach((s) => {\n      const newItems = (s.items as CommandBarItemGeneralType[]).filter(\n        (i) => i.label?.toLowerCase().includes(inputValue?.toLowerCase()),\n      );\n      if (newItems.length) {\n        newSections.push({\n          ...s,\n          items: newItems,\n        });\n      }\n    });\n    return newSections;\n  }, [inputValue, initialSections]);\n\n  return (\n    <div className=\"flex flex-col h-[28.875rem] w-[40rem] overflow-auto\">\n      <Header\n        breadcrumbs={[project?.name || 'Default project']}\n        value={inputValue}\n        onChange={handleInputChange}\n      />\n      {shouldShowTutorial ? (\n        <TutorialTooltip\n          content={\n            <TutorialBody\n              stepNumber={1}\n              title={t(tutorialSteps[0].title)}\n              description={t(tutorialSteps[0].description)}\n              hint={\n                t(tutorialSteps[0].hint[0]) + t(tutorialSteps[0].hint[1]) + '.'\n              }\n            />\n          }\n          wrapperClassName=\"absolute top-[8.5rem] left-0 right-0\"\n        >\n          <div className=\"\" />\n        </TutorialTooltip>\n      ) : null}\n      {!!sectionsToShow.length ? (\n        <Body sections={sectionsToShow} />\n      ) : (\n        <div className=\"flex-1 items-center justify-center text-label-muted text-center py-2\">\n          <Trans>No commands found...</Trans>\n        </div>\n      )}\n      <Footer />\n    </div>\n  );\n};\n\nexport default memo(InitialCommandBar);\n"
  },
  {
    "path": "client/src/CommandBar/steps/LocalRepos.tsx",
    "content": "import {\n  ChangeEvent,\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { CommandBarContext } from '../../context/commandBarContext';\nimport { PlusSignIcon } from '../../icons';\nimport {\n  CommandBarSectionType,\n  CommandBarStepEnum,\n  RepoProvider,\n} from '../../types/general';\nimport { getIndexedRepos, scanLocalRepos, syncRepo } from '../../services/api';\nimport { DeviceContext } from '../../context/deviceContext';\nimport Footer from '../Footer';\nimport Body from '../Body';\nimport Header from '../Header';\nimport RepoItem from './items/RepoItem';\n\ntype Props = {};\n\nconst LocalRepos = ({}: Props) => {\n  const { t } = useTranslation();\n  const [chosenFolder, setChosenFolder] = useState<string | null>(null);\n  const [inputValue, setInputValue] = useState('');\n  const { homeDir, chooseFolder } = useContext(DeviceContext);\n  const { setChosenStep, setFocusedItem } = useContext(\n    CommandBarContext.Handlers,\n  );\n\n  const handleInputChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n    setInputValue(e.target.value);\n  }, []);\n\n  const handleChooseFolder = useCallback(async () => {\n    let folder: string | string[] | null;\n    if (chooseFolder) {\n      try {\n        folder = await chooseFolder({\n          directory: true,\n          defaultPath: homeDir,\n        });\n      } catch (err) {\n        console.log(err);\n      }\n    }\n    // @ts-ignore\n    if (typeof folder === 'string') {\n      setChosenFolder(folder);\n    }\n  }, [chooseFolder, homeDir]);\n\n  const enterAddMode = useCallback(async () => {\n    setFocusedItem({\n      footerHint: t('Select a folder containing a git repository'),\n      footerBtns: [{ label: t('Start indexing'), shortcut: ['entr'] }],\n    });\n    await handleChooseFolder();\n  }, []);\n\n  useEffect(() => {\n    if (chosenFolder) {\n      scanLocalRepos(chosenFolder).then((data) => {\n        if (data.list.length === 1) {\n          syncRepo(data.list[0].ref);\n          refetchRepos();\n          return;\n        }\n      });\n    }\n  }, [chosenFolder]);\n\n  const addItem = useMemo(() => {\n    return {\n      itemsOffset: 0,\n      key: 'add',\n      items: [\n        {\n          label: t('Add local repository'),\n          Icon: PlusSignIcon,\n          footerHint: t('Add a repository from your local machine'),\n          footerBtns: [\n            {\n              label: t('Select folder'),\n              shortcut: ['entr'],\n            },\n          ],\n          key: 'add',\n          id: 'Add',\n          onClick: enterAddMode,\n        },\n      ],\n    };\n  }, []);\n  const [sections, setSections] = useState<CommandBarSectionType[]>([addItem]);\n\n  const breadcrumbs = useMemo(() => {\n    return [t('Local repositories')];\n  }, [t]);\n\n  const handleBack = useCallback(() => {\n    setChosenStep({ id: CommandBarStepEnum.INITIAL });\n  }, []);\n\n  const refetchRepos = useCallback(() => {\n    getIndexedRepos().then((data) => {\n      const mapped = data.list\n        .filter((r) => r.provider === RepoProvider.Local)\n        .map((r) => ({\n          Component: RepoItem,\n          componentProps: { repo: { ...r, shortName: r.name }, refetchRepos },\n          key: r.ref,\n        }));\n      if (!mapped.length) {\n        enterAddMode();\n      }\n      setSections([\n        addItem,\n        {\n          key: 'indexed-repos',\n          label: t('Indexed local repositories'),\n          items: mapped,\n        },\n      ]);\n    });\n  }, []);\n\n  useEffect(() => {\n    refetchRepos();\n  }, []);\n\n  const sectionsToShow = useMemo(() => {\n    if (!inputValue) {\n      return sections;\n    }\n    const newSections: CommandBarSectionType[] = [];\n    sections.forEach((s) => {\n      const newItems = s.items.filter(\n        (i) =>\n          ('label' in i ? i.label : i.componentProps.repo.shortName)\n            ?.toLowerCase()\n            .includes(inputValue?.toLowerCase()),\n      );\n      if (newItems.length) {\n        newSections.push({ ...s, items: newItems });\n      }\n    });\n    return newSections;\n  }, [inputValue, sections]);\n\n  return (\n    <div className=\"flex flex-col h-[28.875rem] w-[40rem] overflow-auto\">\n      <Header\n        breadcrumbs={breadcrumbs}\n        handleBack={handleBack}\n        value={inputValue}\n        onChange={handleInputChange}\n        placeholder={t('Search local repos...')}\n      />\n      <Body sections={sectionsToShow} />\n      <Footer />\n    </div>\n  );\n};\n\nexport default memo(LocalRepos);\n"
  },
  {
    "path": "client/src/CommandBar/steps/ManageRepos/ActionsDropdown.tsx",
    "content": "import { Dispatch, memo, SetStateAction, useContext, useMemo } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport SectionLabel from '../../../components/Dropdown/Section/SectionLabel';\nimport SectionItem from '../../../components/Dropdown/Section/SectionItem';\nimport DropdownSection from '../../../components/Dropdown/Section';\nimport { CommandBarContext } from '../../../context/commandBarContext';\nimport { HardDriveIcon, ShapesIcon } from '../../../icons';\nimport GitHubIcon from '../../../icons/GitHubIcon';\nimport { Filter, Provider } from './index';\n\ntype Props = {\n  setRepoType: Dispatch<SetStateAction<Provider>>;\n  repoType: Provider;\n  setFilter: Dispatch<SetStateAction<Filter>>;\n  filter: Filter;\n  handleClose: () => void;\n};\n\nconst ActionsDropDown = ({\n  setRepoType,\n  repoType,\n  setFilter,\n  filter,\n  handleClose,\n}: Props) => {\n  const { t } = useTranslation();\n  const { focusedItem } = useContext(CommandBarContext.FooterValues);\n\n  const providerIconMap = useMemo(\n    () => ({\n      [Provider.All]: ShapesIcon,\n      [Provider.GitHub]: GitHubIcon,\n      [Provider.Local]: HardDriveIcon,\n    }),\n    [],\n  );\n\n  const providerOptions = useMemo(\n    () => [Provider.All, Provider.GitHub, Provider.Local],\n    [],\n  );\n  const filterOptions = useMemo(\n    () => [Filter.All, Filter.Indexed, Filter.Indexing, Filter.InThisProject],\n    [],\n  );\n\n  const focusedDropdownItems = useMemo(() => {\n    return (\n      (focusedItem &&\n        'focusedItemProps' in focusedItem &&\n        focusedItem.focusedItemProps?.dropdownItems) ||\n      []\n    );\n  }, [focusedItem]);\n\n  return (\n    <div>\n      {!!focusedDropdownItems.length &&\n        focusedDropdownItems.map(\n          (section: { items: Record<string, any>[]; key: string }) => (\n            <DropdownSection borderBottom key={section.key}>\n              {section.items.map((item: Record<string, any>) => (\n                <SectionItem\n                  color=\"base\"\n                  shortcut={item.shortcut}\n                  key={item.key}\n                  onClick={() => {\n                    item.onClick();\n                    handleClose();\n                  }}\n                  label={item.label}\n                  icon={item.icon}\n                  index={item.key}\n                />\n              ))}\n            </DropdownSection>\n          ),\n        )}\n      <DropdownSection>\n        <SectionLabel text={t('Filter repositories by')} />\n        {providerOptions.map((type, i) => {\n          const Icon = providerIconMap[type];\n          return (\n            <SectionItem\n              color=\"base\"\n              shortcut={['shift', (i + 1).toString()]}\n              key={type}\n              index={`provider-${type}`}\n              isSelected={repoType === type}\n              onClick={() => {\n                setRepoType(type);\n                handleClose();\n              }}\n              label={t(type)}\n              icon={<Icon sizeClassName=\"w-4 h-4\" />}\n            />\n          );\n        })}\n      </DropdownSection>\n      <DropdownSection>\n        <SectionLabel text={t('Display')} />\n        {filterOptions.map((type) => (\n          <SectionItem\n            color=\"base\"\n            key={type}\n            isSelected={filter === type}\n            index={`display-${type}`}\n            onClick={() => {\n              setFilter(type);\n              handleClose();\n            }}\n            label={t(type)}\n          />\n        ))}\n      </DropdownSection>\n    </div>\n  );\n};\n\nexport default memo(ActionsDropDown);\n"
  },
  {
    "path": "client/src/CommandBar/steps/ManageRepos/index.tsx",
    "content": "import React, {\n  ChangeEvent,\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport {\n  CommandBarItemCustomType,\n  CommandBarItemGeneralType,\n  CommandBarItemType,\n  CommandBarSectionType,\n  CommandBarStepEnum,\n  RepoProvider,\n  SyncStatus,\n} from '../../../types/general';\nimport { PlusSignIcon } from '../../../icons';\nimport Header from '../../Header';\nimport Body from '../../Body';\nimport Footer from '../../Footer';\nimport { getIndexedRepos } from '../../../services/api';\nimport { mapReposBySections } from '../../../utils/mappers';\nimport { ProjectContext } from '../../../context/projectContext';\nimport { CommandBarContext } from '../../../context/commandBarContext';\nimport RepoItem from '../items/RepoItem';\nimport TutorialTooltip from '../../Tutorial/TutorialTooltip';\nimport TutorialBody from '../../Tutorial/TutorialBody';\nimport { tutorialSteps } from '../../../consts/tutorialSteps';\nimport { UIContext } from '../../../context/uiContext';\nimport ActionsDropdown from './ActionsDropdown';\n\ntype Props = {\n  shouldShowTutorial?: boolean;\n};\n\nexport enum Filter {\n  All = 'All',\n  Indexed = 'Indexed',\n  Indexing = 'Indexing',\n  InThisProject = 'In this project',\n}\n\nexport enum Provider {\n  All = 'All',\n  GitHub = 'GitHub',\n  Local = 'Local',\n}\n\nconst ManageRepos = ({ shouldShowTutorial }: Props) => {\n  const { t } = useTranslation();\n  const { project } = useContext(ProjectContext.Current);\n  const { setChosenStep } = useContext(CommandBarContext.Handlers);\n  const { setOnBoardingState } = useContext(UIContext.Onboarding);\n  const [sections, setSections] = useState<CommandBarSectionType[]>([]);\n  const [sectionsToShow, setSectionsToShow] = useState<CommandBarSectionType[]>(\n    [],\n  );\n  const [isDropdownVisible, setIsDropdownVisible] = useState(false);\n  const [filter, setFilter] = useState<Filter>(Filter.All);\n  const [repoType, setRepoType] = useState<Provider>(Provider.All);\n  const [inputValue, setInputValue] = useState('');\n  const [tutorialStep, setTutorialStep] = useState(0);\n  const [selectedRepo, setSelectedRepo] = useState('');\n\n  const handleInputChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n    setInputValue(e.target.value);\n  }, []);\n\n  const addItem = useMemo(() => {\n    return {\n      itemsOffset: 0,\n      key: 'add',\n      items: [\n        {\n          label: t('Add new repository'),\n          Icon: PlusSignIcon,\n          id: CommandBarStepEnum.ADD_NEW_REPO,\n          shortcut: ['cmd', 'A'],\n          footerHint: '',\n          footerBtns: [\n            {\n              label: t('Add'),\n              shortcut: ['entr'],\n            },\n          ],\n          key: 'add',\n        },\n      ],\n    };\n  }, []);\n\n  const refetchRepos = useCallback(() => {\n    getIndexedRepos().then((data) => {\n      const mapped = mapReposBySections(data.list).map((o) => ({\n        items: o.items.map((r) => ({\n          Component: RepoItem,\n          componentProps: {\n            repo: r,\n            refetchRepos,\n            onSync: () => {\n              setSelectedRepo(r.shortName);\n              setTutorialStep(3);\n            },\n            onDone: () => {\n              setTutorialStep(4);\n            },\n            onAddToProject: () => {\n              setOnBoardingState((prev) => ({\n                ...prev,\n                isCommandBarTutorialFinished: true,\n              }));\n              setTutorialStep(5);\n            },\n          },\n          key: r.ref,\n        })),\n        itemsOffset: o.offset + 1,\n        label: o.org === 'Local' ? t('Local') : o.org,\n        key: o.org,\n      }));\n      setSections([addItem, ...mapped]);\n    });\n  }, []);\n\n  useEffect(() => {\n    if (filter === Filter.All && !inputValue && repoType === Provider.All) {\n      setSectionsToShow(sections);\n      return;\n    }\n    const newSectionsToShow: CommandBarSectionType[] = [];\n    const filterByStatus = (item: CommandBarItemType) => {\n      if ('componentProps' in item) {\n        switch (filter) {\n          case Filter.Indexing:\n            return [\n              SyncStatus.Syncing,\n              SyncStatus.Indexing,\n              SyncStatus.Queued,\n            ].includes(item.componentProps.repo.sync_status);\n          case Filter.Indexed:\n            return item.componentProps.repo.sync_status === SyncStatus.Done;\n          case Filter.InThisProject:\n            return !!project?.repos.find(\n              (r) => r.repo.ref === item.componentProps.repo.ref,\n            );\n          default:\n            return true;\n        }\n      }\n      return false;\n    };\n\n    const filterByProvider = (item: CommandBarItemType) => {\n      if ('componentProps' in item) {\n        switch (repoType) {\n          case Provider.GitHub:\n            return item.componentProps.repo.provider === RepoProvider.GitHub;\n          case Provider.Local:\n            return item.componentProps.repo.provider === RepoProvider.Local;\n          default:\n            return true;\n        }\n      }\n      return false;\n    };\n\n    const filterByName = (\n      item: CommandBarItemGeneralType | CommandBarItemCustomType,\n    ) => {\n      return 'componentProps' in item\n        ? item.componentProps.repo.shortName\n            ?.toLowerCase()\n            .includes(inputValue?.toLowerCase())\n        : item.label?.toLowerCase().includes(inputValue?.toLowerCase());\n    };\n\n    sections.forEach((s) => {\n      const items = s.items.filter(\n        (item) =>\n          filterByProvider(item) && filterByStatus(item) && filterByName(item),\n      );\n\n      if (items.length) {\n        newSectionsToShow.push({\n          ...s,\n          items,\n        });\n      }\n    });\n    setSectionsToShow(newSectionsToShow);\n  }, [sections, filter, inputValue, project?.repos, repoType]);\n\n  useEffect(() => {\n    refetchRepos();\n  }, []);\n\n  useEffect(() => {\n    // if user started with non-private repo\n    if (shouldShowTutorial && tutorialStep === 0 && sections.length > 1) {\n      const firstRepo = (sections[1].items[0] as CommandBarItemCustomType)\n        .componentProps.repo;\n      setTutorialStep(firstRepo.isSyncing ? 3 : 4);\n      setSelectedRepo(firstRepo.shortName);\n    }\n  }, [sections, tutorialStep, shouldShowTutorial]);\n\n  const handleBack = useCallback(() => {\n    setChosenStep({ id: CommandBarStepEnum.INITIAL });\n  }, []);\n\n  const actionsDropdownProps = useMemo(() => {\n    return {\n      repoType,\n      setRepoType,\n      filter,\n      setFilter,\n    };\n  }, [repoType, filter]);\n\n  return (\n    <div className=\"flex flex-col h-[28.875rem] w-[40rem] overflow-auto\">\n      <Header\n        breadcrumbs={[t('Manage repositories')]}\n        value={inputValue}\n        onChange={handleInputChange}\n        handleBack={handleBack}\n        placeholder={t('')}\n        disableKeyNav={isDropdownVisible}\n      />\n      {shouldShowTutorial && tutorialStep < 5 ? (\n        <TutorialTooltip\n          content={\n            <TutorialBody\n              stepNumber={tutorialStep + 1}\n              title={t(tutorialSteps[tutorialStep].title)}\n              description={t(tutorialSteps[tutorialStep].description, {\n                repoName: selectedRepo,\n              })}\n              hint={\n                tutorialStep > 0\n                  ? t(tutorialSteps[tutorialStep].hint[0])\n                  : t(tutorialSteps[tutorialStep].hint[0]) +\n                    t(tutorialSteps[0].hint[1]) +\n                    '.'\n              }\n            />\n          }\n          wrapperClassName=\"absolute top-[7.5rem] left-0 right-0\"\n        >\n          <div className=\"\" />\n        </TutorialTooltip>\n      ) : null}\n      {sectionsToShow.length ? (\n        <Body sections={sectionsToShow} disableKeyNav={isDropdownVisible} />\n      ) : (\n        <div className=\"flex-1 items-center justify-center text-label-muted text-center py-2\">\n          <Trans>No repositories found...</Trans>\n        </div>\n      )}\n      <Footer\n        onDropdownVisibilityChange={setIsDropdownVisible}\n        ActionsDropdown={ActionsDropdown}\n        actionsDropdownProps={actionsDropdownProps}\n      />\n    </div>\n  );\n};\n\nexport default memo(ManageRepos);\n"
  },
  {
    "path": "client/src/CommandBar/steps/PrivateRepos/ActionsDropdown.tsx",
    "content": "import { memo, useContext, useMemo } from 'react';\nimport SectionItem from '../../../components/Dropdown/Section/SectionItem';\nimport DropdownSection from '../../../components/Dropdown/Section';\nimport { CommandBarContext } from '../../../context/commandBarContext';\n\ntype Props = {};\n\nconst ActionsDropDown = ({}: Props) => {\n  const { focusedItem } = useContext(CommandBarContext.FooterValues);\n\n  const focusedDropdownItems = useMemo(() => {\n    return (\n      (focusedItem &&\n        'focusedItemProps' in focusedItem &&\n        focusedItem.focusedItemProps?.dropdownItems) ||\n      []\n    );\n  }, [focusedItem]);\n\n  return (\n    <div>\n      {!!focusedDropdownItems.length &&\n        focusedDropdownItems.map(\n          (section: { items: Record<string, any>[]; key: string }) => (\n            <DropdownSection key={section.key}>\n              {section.items.map((item: Record<string, any>) => (\n                <SectionItem\n                  color=\"base\"\n                  shortcut={item.shortcut}\n                  key={item.key}\n                  index={item.key}\n                  onClick={item.onClick}\n                  label={item.label}\n                  icon={item.icon}\n                />\n              ))}\n            </DropdownSection>\n          ),\n        )}\n    </div>\n  );\n};\n\nexport default memo(ActionsDropDown);\n"
  },
  {
    "path": "client/src/CommandBar/steps/PrivateRepos/index.tsx",
    "content": "import React, {\n  ChangeEvent,\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { CommandBarContext } from '../../../context/commandBarContext';\nimport {\n  CommandBarItemCustomType,\n  CommandBarSectionType,\n  CommandBarStepEnum,\n  RepoProvider,\n} from '../../../types/general';\nimport { getRepos } from '../../../services/api';\nimport { mapReposBySections } from '../../../utils/mappers';\nimport Header from '../../Header';\nimport Body from '../../Body';\nimport Footer from '../../Footer';\nimport RepoItem from '../items/RepoItem';\nimport TutorialBody from '../../Tutorial/TutorialBody';\nimport TutorialTooltip from '../../Tutorial/TutorialTooltip';\nimport { tutorialSteps } from '../../../consts/tutorialSteps';\nimport { UIContext } from '../../../context/uiContext';\nimport ActionsDropdown from './ActionsDropdown';\n\ntype Props = {\n  shouldShowTutorial?: boolean;\n};\n\nconst PrivateReposStep = ({ shouldShowTutorial }: Props) => {\n  const { t } = useTranslation();\n  const [sections, setSections] = useState<CommandBarSectionType[]>([]);\n  const [sectionsToShow, setSectionsToShow] = useState<CommandBarSectionType[]>(\n    [],\n  );\n  const { setChosenStep } = useContext(CommandBarContext.Handlers);\n  const { setOnBoardingState } = useContext(UIContext.Onboarding);\n  const [inputValue, setInputValue] = useState('');\n  const [tutorialStep, setTutorialStep] = useState(2);\n  const [isDropdownVisible, setIsDropdownVisible] = useState(false);\n  const [selectedRepo, setSelectedRepo] = useState('');\n\n  const handleInputChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n    setInputValue(e.target.value);\n  }, []);\n\n  const refetchRepos = useCallback(async () => {\n    const data = await getRepos();\n    const mapped = mapReposBySections(\n      data.list.filter((r) => r.provider !== RepoProvider.Local),\n    ).map((o) => ({\n      items: o.items.map((r) => ({\n        Component: RepoItem,\n        componentProps: {\n          repo: r,\n          refetchRepos,\n          onSync: () => {\n            setSelectedRepo(r.shortName);\n            setTutorialStep(3);\n          },\n          onDone: () => {\n            setTutorialStep(4);\n          },\n          onAddToProject: () => {\n            setOnBoardingState((prev) => ({\n              ...prev,\n              isCommandBarTutorialFinished: true,\n            }));\n            setTutorialStep(5);\n          },\n        },\n        key: r.ref,\n      })),\n      itemsOffset: o.offset,\n      label: o.org,\n      key: o.org,\n    }));\n    setSections(mapped);\n  }, []);\n\n  useEffect(() => {\n    if (!inputValue) {\n      setSectionsToShow(sections);\n      return;\n    }\n    const newSectionsToShow: CommandBarSectionType[] = [];\n    sections.forEach((s) => {\n      const items = (s.items as CommandBarItemCustomType[]).filter((item) => {\n        return item.componentProps.repo.shortName\n          ?.toLowerCase()\n          .includes(inputValue?.toLowerCase());\n      });\n\n      if (items.length) {\n        newSectionsToShow.push({\n          ...s,\n          items,\n        });\n      }\n    });\n    setSectionsToShow(newSectionsToShow);\n  }, [sections, inputValue]);\n\n  useEffect(() => {\n    refetchRepos();\n  }, []);\n\n  const breadcrumbs = useMemo(() => {\n    return [t('Add private repository')];\n  }, [t]);\n\n  const handleBack = useCallback(() => {\n    setChosenStep({ id: CommandBarStepEnum.MANAGE_REPOS });\n  }, []);\n\n  return (\n    <div className=\"flex flex-col h-[28.875rem] w-[40rem] overflow-auto\">\n      <Header\n        breadcrumbs={breadcrumbs}\n        handleBack={handleBack}\n        value={inputValue}\n        onChange={handleInputChange}\n        placeholder={t('Search private repos...')}\n        disableKeyNav={isDropdownVisible}\n      />\n      {shouldShowTutorial && tutorialStep < 5 ? (\n        <TutorialTooltip\n          content={\n            <TutorialBody\n              stepNumber={tutorialStep + 1}\n              title={t(tutorialSteps[tutorialStep].title)}\n              description={t(tutorialSteps[tutorialStep].description, {\n                repoName: selectedRepo,\n              })}\n              hint={t(tutorialSteps[tutorialStep].hint[0])}\n            />\n          }\n          wrapperClassName=\"absolute top-[8.5rem] left-0 right-0\"\n        >\n          <div className=\"\" />\n        </TutorialTooltip>\n      ) : null}\n      {sectionsToShow.length ? (\n        <Body sections={sectionsToShow} disableKeyNav={isDropdownVisible} />\n      ) : (\n        <div className=\"flex-1 items-center justify-center text-label-muted text-center py-2\">\n          <Trans>No repositories found...</Trans>\n        </div>\n      )}\n      <Footer\n        onDropdownVisibilityChange={setIsDropdownVisible}\n        ActionsDropdown={ActionsDropdown}\n      />\n    </div>\n  );\n};\n\nexport default memo(PrivateReposStep);\n"
  },
  {
    "path": "client/src/CommandBar/steps/PublicRepos.tsx",
    "content": "import {\n  ChangeEvent,\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport axios from 'axios';\nimport { CommandBarStepEnum } from '../../types/general';\nimport { CommandBarContext } from '../../context/commandBarContext';\nimport { syncRepo } from '../../services/api';\nimport Header from '../Header';\nimport Footer from '../Footer';\n\ntype Props = {};\n\nconst PublicRepos = ({}: Props) => {\n  const { t } = useTranslation();\n  const { setChosenStep, setFocusedItem } = useContext(\n    CommandBarContext.Handlers,\n  );\n  const [inputValue, setInputValue] = useState('');\n\n  const handleInputChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n    setInputValue(e.target.value);\n  }, []);\n\n  useEffect(() => {\n    setFocusedItem({\n      footerHint: t('Paste a link to any public repository hosted on GitHub'),\n      footerBtns: [{ label: t('Start indexing'), shortcut: ['entr'] }],\n    });\n  }, []);\n\n  const breadcrumbs = useMemo(() => {\n    return [t('Add public repository')];\n  }, [t]);\n\n  const handleBack = useCallback(() => {\n    setChosenStep({ id: CommandBarStepEnum.MANAGE_REPOS });\n  }, []);\n\n  const handleAddSubmit = useCallback((inputValue: string) => {\n    setFocusedItem({\n      footerHint: t('Verifying access...'),\n      footerBtns: [],\n    });\n    let cleanRef = inputValue\n      .replace('https://', '')\n      .replace('github.com/', '')\n      .replace(/\\.git$/, '')\n      .replace(/\"$/, '')\n      .replace(/^\"/, '')\n      .replace(/\\/$/, '');\n    if (inputValue.startsWith('git@github.com:')) {\n      cleanRef = inputValue.slice(15).replace(/\\.git$/, '');\n    }\n    axios(`https://api.github.com/repos/${cleanRef}`)\n      .then((resp) => {\n        if (resp?.data?.visibility === 'public') {\n          syncRepo(`github.com/${cleanRef}`);\n          handleBack();\n        } else {\n          setFocusedItem({\n            footerHint: t(\n              \"This is not a public repository / We couldn't find this repository\",\n            ),\n            footerBtns: [],\n          });\n        }\n      })\n      .catch((err) => {\n        console.log(err);\n        setFocusedItem({\n          footerHint: t(\n            \"This is not a public repository / We couldn't find this repository\",\n          ),\n          footerBtns: [],\n        });\n      });\n  }, []);\n\n  return (\n    <div className=\"flex flex-col h-[28.875rem] w-[40rem] overflow-auto\">\n      <Header\n        breadcrumbs={breadcrumbs}\n        placeholder={t('Repository URL...')}\n        handleBack={handleBack}\n        value={inputValue}\n        onChange={handleInputChange}\n        customSubmitHandler={handleAddSubmit}\n      />\n      <div className=\"flex-1\" />\n      <Footer />\n    </div>\n  );\n};\n\nexport default memo(PublicRepos);\n"
  },
  {
    "path": "client/src/CommandBar/steps/SeachDocs.tsx",
    "content": "import {\n  ChangeEvent,\n  memo,\n  useCallback,\n  useContext,\n  useDeferredValue,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport Header from '../Header';\nimport { CommandBarStepEnum, TabTypesEnum } from '../../types/general';\nimport { CommandBarContext } from '../../context/commandBarContext';\nimport { getDocById, searchDocSections } from '../../services/api';\nimport { DocSectionType, DocShortType } from '../../types/api';\nimport Body from '../Body';\nimport { TabsContext } from '../../context/tabsContext';\nimport Footer from '../Footer';\nimport { UIContext } from '../../context/uiContext';\nimport { MagazineIcon } from '../../icons';\nimport RenderedSection from '../../Project/CurrentTabContent/DocTab/RenderedSection';\n\ntype Props = {\n  studioId?: string;\n  docId: string;\n};\n\nconst SearchDocs = ({ studioId, docId }: Props) => {\n  const { t } = useTranslation();\n  const [inputValue, setInputValue] = useState('');\n  const { setChosenStep, setIsVisible } = useContext(\n    CommandBarContext.Handlers,\n  );\n  const { openNewTab } = useContext(TabsContext.Handlers);\n  const { setIsLeftSidebarFocused } = useContext(UIContext.Focus);\n  const [docSections, setDocSections] = useState<DocSectionType[]>([]);\n  const [fullDoc, setFullDoc] = useState<DocShortType | null>(null);\n  const [focusedIndex, setFocusedIndex] = useState('');\n  const searchValue = useDeferredValue(inputValue);\n\n  const handleInputChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n    setInputValue(e.target.value);\n  }, []);\n\n  useEffect(() => {\n    searchDocSections(docId, searchValue || ',').then(setDocSections);\n  }, [searchValue, docId]);\n\n  useEffect(() => {\n    getDocById(docId).then(setFullDoc);\n  }, [docId]);\n\n  const breadcrumbs = useMemo(() => {\n    return studioId ? [t('Add doc to studio')] : [t('Search docs')];\n  }, [t, studioId]);\n\n  const handleBack = useCallback(() => {\n    setChosenStep({ id: CommandBarStepEnum.INITIAL });\n  }, []);\n\n  const constructSectionTitle = useCallback(\n    (docTitle: string, ancestry: string[], sectionHeader: string) => {\n      let title = docTitle + ' ';\n      if (ancestry.length) {\n        title += '/';\n      }\n      ancestry.forEach((h, i) => {\n        title += h.replace(/#/g, '');\n        if (i !== ancestry.length - 1) {\n          title += '/';\n        }\n      });\n      if (sectionHeader) {\n        title += '/';\n        title += sectionHeader.replace(/#/g, '');\n      }\n      return title;\n    },\n    [],\n  );\n\n  const favIconComponent = useMemo(() => {\n    if (fullDoc?.favicon) {\n      // eslint-disable-next-line react/display-name\n      return (props: { className?: string; sizeClassName?: string }) => (\n        <img\n          src={fullDoc?.favicon}\n          alt={fullDoc?.name}\n          className={`${props.sizeClassName || ''} ${props.className || ''}`}\n        />\n      );\n    }\n    return MagazineIcon;\n  }, [fullDoc?.favicon]);\n\n  const sections = useMemo(() => {\n    return [\n      {\n        key: 'docs',\n        items: docSections.map(\n          ({\n            doc_title,\n            header,\n            ancestry,\n            point_id,\n            doc_id,\n            relative_url,\n          }) => ({\n            key: `${point_id}`,\n            id: `doc-section-${point_id}`,\n            onClick: () => {\n              openNewTab({\n                type: TabTypesEnum.DOC,\n                docId: doc_id,\n                relativeUrl: relative_url,\n                title: doc_title,\n                studioId,\n                favicon: fullDoc?.favicon,\n                initialSections: [point_id],\n              });\n              setIsLeftSidebarFocused(false);\n              setIsVisible(false);\n              setChosenStep({ id: CommandBarStepEnum.INITIAL });\n            },\n            label: constructSectionTitle(doc_title, ancestry, header),\n            footerHint: ``,\n            footerBtns: [\n              {\n                label: studioId ? t('Add doc') : t('Open'),\n                shortcut: ['entr'],\n              },\n            ],\n            Icon: favIconComponent,\n          }),\n        ),\n        itemsOffset: 0,\n      },\n    ];\n  }, [docSections, studioId]);\n\n  const focusedDoc = useMemo(() => {\n    return docSections.find((ds) => `docs-${ds.point_id}` === focusedIndex);\n  }, [docSections, focusedIndex]);\n\n  return (\n    <div className=\"flex flex-col h-[28.875rem] w-[50rem] overflow-auto\">\n      <Header\n        breadcrumbs={breadcrumbs}\n        handleBack={studioId ? undefined : handleBack}\n        placeholder={t('Search docs...')}\n        value={inputValue}\n        onChange={handleInputChange}\n      />\n      {docSections.length ? (\n        <div className=\"flex items-start overflow-auto flex-1\">\n          <div className=\"flex-1 flex flex-col overflow-auto h-full\">\n            <Body sections={sections} onFocusedIndexChange={setFocusedIndex} />\n          </div>\n          <div className=\"flex-1 border-l border-bg-border h-full overflow-auto p-2\">\n            {!!focusedDoc && (\n              <RenderedSection\n                text={focusedDoc.text}\n                isEditingSelection={false}\n                baseUrl={focusedDoc.doc_source}\n              />\n            )}\n          </div>\n        </div>\n      ) : (\n        <div className=\"flex-1 items-center justify-center text-label-muted text-center py-2\" />\n      )}\n      {!!docSections.length && <Footer />}\n    </div>\n  );\n};\n\nexport default memo(SearchDocs);\n"
  },
  {
    "path": "client/src/CommandBar/steps/SeachFiles.tsx",
    "content": "import React, {\n  ChangeEvent,\n  memo,\n  useCallback,\n  useContext,\n  useDeferredValue,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport Header from '../Header';\nimport { CommandBarStepEnum, TabTypesEnum } from '../../types/general';\nimport { CommandBarContext } from '../../context/commandBarContext';\nimport {\n  getAutocomplete,\n  getCodeStudio,\n  patchCodeStudio,\n} from '../../services/api';\nimport { FileResItem } from '../../types/api';\nimport Body from '../Body';\nimport FileIcon from '../../components/FileIcon';\nimport { TabsContext } from '../../context/tabsContext';\nimport { ProjectContext } from '../../context/projectContext';\nimport Footer from '../Footer';\nimport { splitPath } from '../../utils';\nimport { getJsonFromStorage, RECENT_FILES_KEY } from '../../services/storage';\nimport { UIContext } from '../../context/uiContext';\nimport { filterOutDuplicates } from '../../utils/mappers';\n\ntype Props = {\n  studioId?: string;\n};\n\nconst SearchFiles = ({ studioId }: Props) => {\n  const { t } = useTranslation();\n  const [inputValue, setInputValue] = useState('');\n  const { setChosenStep, setIsVisible } = useContext(\n    CommandBarContext.Handlers,\n  );\n  const { project, refreshCurrentProjectRepos } = useContext(\n    ProjectContext.Current,\n  );\n  const { openNewTab } = useContext(TabsContext.Handlers);\n  const { setIsLeftSidebarFocused } = useContext(UIContext.Focus);\n  const [files, setFiles] = useState<\n    { path: string; repo: string; branch: string | null }[]\n  >([]);\n  const searchValue = useDeferredValue(inputValue);\n\n  const handleInputChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n    setInputValue(e.target.value);\n  }, []);\n\n  useEffect(() => {\n    if (!searchValue) {\n      const recentFiles = getJsonFromStorage<string[]>(RECENT_FILES_KEY);\n      const newFiles: { path: string; repo: string; branch: string | null }[] =\n        [];\n      recentFiles?.forEach((f) => {\n        const [repo, path, branch] = f.split(':');\n        if (project?.repos.find((r) => r.repo.ref === repo)) {\n          newFiles.push({ repo, path, branch: branch || null });\n        }\n      });\n      if (newFiles.length > 1) {\n        setFiles(newFiles.reverse());\n        return;\n      }\n    }\n    if (project?.id) {\n      getAutocomplete(\n        project.id,\n        `path:${searchValue}&content=false&file=true&page_size=20`,\n      ).then((respPath) => {\n        const fileResults = respPath.data\n          .filter(\n            (d): d is FileResItem => d.kind === 'file_result' && !d.data.is_dir,\n          )\n          .map((d) => ({\n            path: d.data.relative_path.text,\n            repo: d.data.repo_ref,\n            branch: d.data.branches || null,\n          }));\n        setFiles(fileResults);\n      });\n    }\n  }, [searchValue, project?.id]);\n\n  const breadcrumbs = useMemo(() => {\n    return studioId ? [t('Add file to studio')] : [t('Search files')];\n  }, [t, studioId]);\n\n  const handleBack = useCallback(() => {\n    setChosenStep({ id: CommandBarStepEnum.INITIAL });\n  }, []);\n\n  const sections = useMemo(() => {\n    return [\n      {\n        key: 'files',\n        items: filterOutDuplicates(\n          files.map((f) => ({ ...f, key: `${f.path}-${f.repo}-${f.branch}` })),\n          'key',\n        ).map(({ path, repo, branch, key }) => {\n          const addMultipleFilesToStudio = async () => {\n            if (project?.id && studioId) {\n              const studio = await getCodeStudio(project.id, studioId);\n              const patchedFile = studio?.context.find(\n                (f) =>\n                  f.path === path && f.repo === repo && f.branch === branch,\n              );\n              if (!patchedFile) {\n                await patchCodeStudio(project.id, studioId, {\n                  context: [\n                    ...(studio?.context || []),\n                    {\n                      path,\n                      branch: branch,\n                      repo,\n                      hidden: false,\n                      ranges: [],\n                    },\n                  ],\n                });\n                refreshCurrentProjectRepos();\n                openNewTab({\n                  type: TabTypesEnum.FILE,\n                  path,\n                  repoRef: repo,\n                  branch,\n                  studioId,\n                  isFileInContext: true,\n                  initialRanges: [],\n                });\n              }\n            }\n          };\n          return {\n            key,\n            id: key,\n            onClick: async (e: React.MouseEvent | KeyboardEvent) => {\n              if (studioId && e.shiftKey && project?.id) {\n                await addMultipleFilesToStudio();\n              } else {\n                openNewTab({\n                  type: TabTypesEnum.FILE,\n                  path,\n                  repoRef: repo,\n                  branch,\n                  studioId,\n                });\n                setIsLeftSidebarFocused(false);\n                setIsVisible(false);\n                setChosenStep({ id: CommandBarStepEnum.INITIAL });\n              }\n            },\n            label: path,\n            footerHint: `${splitPath(repo)\n              .slice(repo.startsWith('local//') ? -1 : -2)\n              .join('/')} ${\n              branch ? `/ ${splitPath(branch).pop()} ` : ''\n            }/ ${path}`,\n            footerBtns: [\n              ...(studioId\n                ? [\n                    {\n                      label: t('Add multiple files'),\n                      shortcut: ['shift', 'entr'],\n                      action: addMultipleFilesToStudio,\n                    },\n                  ]\n                : []),\n              {\n                label: studioId ? t('Add file') : t('Open'),\n                shortcut: ['entr'],\n              },\n            ],\n            Icon: (props: { sizeClassName?: string }) => (\n              <FileIcon filename={path} noMargin />\n            ),\n          };\n        }),\n        itemsOffset: 0,\n      },\n    ];\n  }, [files, studioId, project?.id]);\n\n  return (\n    <div className=\"flex flex-col h-[28.875rem] w-[40rem] overflow-auto\">\n      <Header\n        breadcrumbs={breadcrumbs}\n        handleBack={studioId ? undefined : handleBack}\n        placeholder={t('Search files...')}\n        value={inputValue}\n        onChange={handleInputChange}\n      />\n      {files.length ? (\n        <Body sections={sections} />\n      ) : (\n        <div className=\"flex-1 items-center justify-center text-label-muted text-center py-2\">\n          <Trans>No files found...</Trans>\n        </div>\n      )}\n      <Footer />\n    </div>\n  );\n};\n\nexport default memo(SearchFiles);\n"
  },
  {
    "path": "client/src/CommandBar/steps/ToggleTheme.tsx",
    "content": "import { memo, useCallback, useContext, useMemo } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport {\n  CommandBarItemGeneralType,\n  CommandBarStepEnum,\n} from '../../types/general';\nimport {\n  MacintoshIcon,\n  ThemeBlackIcon,\n  ThemeDarkIcon,\n  ThemeLightIcon,\n} from '../../icons';\nimport Header from '../Header';\nimport Body from '../Body';\nimport Footer from '../Footer';\nimport { CommandBarContext } from '../../context/commandBarContext';\nimport { Theme } from '../../types';\nimport { UIContext } from '../../context/uiContext';\n\ntype Props = {};\n\nconst ToggleTheme = ({}: Props) => {\n  const { t } = useTranslation();\n  const { setChosenStep } = useContext(CommandBarContext.Handlers);\n  const { setTheme } = useContext(UIContext.Theme);\n\n  const handleBack = useCallback(() => {\n    setChosenStep({ id: CommandBarStepEnum.INITIAL });\n  }, []);\n\n  const initialSections = useMemo(() => {\n    const themeOptions = ['light', 'dark', 'black', 'system'] as Theme[];\n    const themeMap = {\n      light: ThemeLightIcon,\n      dark: ThemeDarkIcon,\n      black: ThemeBlackIcon,\n      system: MacintoshIcon,\n    };\n    const themeItems: CommandBarItemGeneralType[] = themeOptions.map((th) => ({\n      label: t(`Use ${th} theme`),\n      Icon: themeMap[th],\n      id: `${th}-theme`,\n      key: `${th}-theme`,\n      onClick: () => setTheme(th),\n      footerHint: t(`Use ${th} theme`),\n      footerBtns: [\n        {\n          label: t('Toggle'),\n          shortcut: ['entr'],\n        },\n      ],\n    }));\n    return [\n      {\n        items: themeItems,\n        itemsOffset: 0,\n        key: 'theme-commands',\n      },\n    ];\n  }, [t]);\n\n  return (\n    <div className=\"flex flex-col h-[28.875rem] w-[40rem] overflow-auto\">\n      <Header\n        breadcrumbs={[t('Application theme')]}\n        noInput\n        handleBack={handleBack}\n      />\n      <Body sections={initialSections} />\n      <Footer />\n    </div>\n  );\n};\n\nexport default memo(ToggleTheme);\n"
  },
  {
    "path": "client/src/CommandBar/steps/items/DocItem.tsx",
    "content": "import {\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport {\n  CloseSignInCircleIcon,\n  LinkChainIcon,\n  MagazineIcon,\n  PlusSignIcon,\n  TrashCanIcon,\n} from '../../../icons';\nimport { DeviceContext } from '../../../context/deviceContext';\nimport { DocShortType } from '../../../types/api';\nimport {\n  addDocToProject,\n  API_BASE_URL,\n  cancelDocIndexing,\n  deleteDocProvider,\n  getDocById,\n  removeDocFromProject,\n  resyncDoc,\n} from '../../../services/api';\nimport Item from '../../Body/Item';\nimport SpinLoaderContainer from '../../../components/Loaders/SpinnerLoader';\nimport Button from '../../../components/Button';\nimport { ProjectContext } from '../../../context/projectContext';\n\ntype Props = {\n  doc: DocShortType;\n  index: string;\n  isFirst: boolean;\n  isIndexed: boolean;\n  disableKeyNav?: boolean;\n  refetchDocs: () => {};\n};\n\nconst DocItem = ({\n  doc,\n  isFirst,\n  index,\n  isIndexed,\n  disableKeyNav,\n  refetchDocs,\n}: Props) => {\n  const { t } = useTranslation();\n  const [docToShow, setDocToShow] = useState(doc);\n  const [isIndexingFinished, setIsIndexingFinished] = useState(\n    !!doc.id && doc.index_status === 'done',\n  );\n  const { openLink } = useContext(DeviceContext);\n  const { project, refreshCurrentProjectDocs } = useContext(\n    ProjectContext.Current,\n  );\n  const eventSourceRef = useRef<EventSource | null>(null);\n\n  const refetchDoc = useCallback(() => {\n    getDocById(doc.id).then((data) => {\n      setDocToShow(data);\n    });\n  }, [doc.id]);\n\n  const startEventSource = useCallback(() => {\n    setIsIndexingFinished(false);\n    eventSourceRef.current?.close();\n    eventSourceRef.current = new EventSource(\n      `${API_BASE_URL}/docs/${doc.id}/status`,\n    );\n    eventSourceRef.current.onmessage = (ev) => {\n      try {\n        const data = JSON.parse(ev.data);\n        console.log(data);\n        if (data.Ok.Done) {\n          eventSourceRef.current?.close();\n          eventSourceRef.current = null;\n          setIsIndexingFinished(true);\n          refetchDoc();\n          return;\n        }\n      } catch (err) {\n        console.log(err);\n        eventSourceRef.current?.close();\n        eventSourceRef.current = null;\n      }\n    };\n    eventSourceRef.current.onerror = (err) => {\n      console.log(err);\n      eventSourceRef.current?.close();\n      eventSourceRef.current = null;\n    };\n  }, [doc.id]);\n\n  useEffect(() => {\n    return () => {\n      eventSourceRef.current?.close();\n      eventSourceRef.current = null;\n    };\n  }, []);\n\n  useEffect(() => {\n    if (!isIndexed && !eventSourceRef.current && !isIndexingFinished) {\n      startEventSource();\n    }\n  }, [isIndexed]);\n\n  const handleCancelSync = useCallback(async () => {\n    await cancelDocIndexing(doc.id);\n    eventSourceRef.current?.close();\n    eventSourceRef.current = null;\n    setIsIndexingFinished(true);\n  }, [doc.id]);\n\n  const isIndexing = useMemo(() => {\n    return !isIndexed && !isIndexingFinished;\n  }, [isIndexed, isIndexingFinished]);\n\n  const handleRemove = useCallback(() => {\n    if (docToShow.id) {\n      deleteDocProvider(docToShow.id).then(() => {\n        refetchDocs();\n      });\n    } else {\n      refetchDocs();\n    }\n  }, [docToShow.id]);\n\n  const handleResync = useCallback(async () => {\n    await resyncDoc(doc.id);\n    startEventSource();\n  }, [doc.id]);\n\n  const favIconComponent = useMemo(() => {\n    // eslint-disable-next-line react/display-name\n    return (props: { className?: string; sizeClassName?: string }) => (\n      <img\n        src={docToShow.favicon}\n        alt={docToShow.name}\n        className={`${props.sizeClassName || ''} ${props.className || ''}`}\n      />\n    );\n  }, [docToShow.favicon]);\n\n  const handleAddToProject = useCallback(() => {\n    if (project?.id) {\n      return addDocToProject(project.id, doc.id).finally(() => {\n        refreshCurrentProjectDocs();\n      });\n    }\n  }, [doc.id, project?.id, refreshCurrentProjectDocs]);\n\n  const handleRemoveFromProject = useCallback(() => {\n    if (project?.id) {\n      return removeDocFromProject(project.id, doc.id).finally(() => {\n        refreshCurrentProjectDocs();\n      });\n    }\n  }, [doc.id, project?.id, refreshCurrentProjectDocs]);\n\n  const isInProject = useMemo(() => {\n    return project?.docs.find((d) => d.id === doc.id);\n  }, [project?.docs, doc.id]);\n\n  const focusedItemProps = useMemo(() => {\n    const dropdownItems1 = [];\n    if (isIndexing) {\n      dropdownItems1.push({\n        onClick: handleCancelSync,\n        label: t('Stop indexing'),\n        icon: (\n          <span className=\"w-6 h-6 flex items-center justify-center rounded-6 bg-bg-border\">\n            <CloseSignInCircleIcon sizeClassName=\"w-3.5 h-3.5\" />\n          </span>\n        ),\n        key: 'stop_indexing',\n      });\n    }\n    if (isIndexingFinished) {\n      dropdownItems1.push(\n        isInProject\n          ? {\n              onClick: handleRemoveFromProject,\n              label: t('Remove from project'),\n              icon: (\n                <span className=\"w-6 h-6 flex items-center justify-center rounded-6 bg-bg-border\">\n                  <TrashCanIcon sizeClassName=\"w-3.5 h-3.5\" />\n                </span>\n              ),\n              key: 'remove_from_project',\n            }\n          : {\n              onClick: handleAddToProject,\n              label: t('Add to project'),\n              icon: (\n                <span className=\"w-6 h-6 flex items-center justify-center rounded-6 bg-bg-border\">\n                  <PlusSignIcon sizeClassName=\"w-3.5 h-3.5\" />\n                </span>\n              ),\n              key: 'add_to_project',\n            },\n      );\n      dropdownItems1.push({\n        onClick: handleResync,\n        label: t('Re-sync'),\n        shortcut: ['cmd', 'R'],\n        key: 'resync',\n      });\n      dropdownItems1.push({\n        onClick: handleRemove,\n        label: t('Remove'),\n        shortcut: ['cmd', 'D'],\n        key: 'remove',\n      });\n    }\n    const dropdownItems = [];\n    if (dropdownItems1.length) {\n      dropdownItems.push({ items: dropdownItems1, key: '1', itemsOffset: 0 });\n    }\n    return {\n      dropdownItems,\n    };\n  }, [\n    t,\n    isInProject,\n    handleAddToProject,\n    handleRemoveFromProject,\n    handleCancelSync,\n    isIndexing,\n    handleAddToProject,\n    handleResync,\n    handleRemove,\n    doc,\n  ]);\n\n  return (\n    <Item\n      key={docToShow.id || doc.url}\n      itemKey={`doc-${docToShow.id || doc.url}`}\n      index={index}\n      isFirst={isFirst}\n      isWithCheckmark={!!isInProject}\n      Icon={\n        isIndexing\n          ? SpinLoaderContainer\n          : docToShow.favicon\n          ? favIconComponent\n          : MagazineIcon\n      }\n      label={docToShow.name}\n      id={'doc_settings'}\n      footerHint={\n        isIndexing ? (\n          t('Indexing...')\n        ) : (\n          <span className=\"flex gap-1 items-center\">\n            {t(`Indexed`)}\n            <Button\n              variant=\"ghost\"\n              size=\"small\"\n              onClick={() => openLink(docToShow.url)}\n            >\n              <LinkChainIcon sizeClassName=\"w-3.5 h-3.5\" />\n              <Trans>Open</Trans>\n            </Button>\n          </span>\n        )\n      }\n      onClick={\n        isIndexing\n          ? handleCancelSync\n          : isInProject\n          ? handleRemoveFromProject\n          : handleAddToProject\n      }\n      iconContainerClassName={\n        isIndexingFinished\n          ? 'bg-bg-contrast text-label-contrast'\n          : 'bg-bg-border'\n      }\n      footerBtns={\n        isIndexingFinished\n          ? [\n              {\n                label: isInProject\n                  ? t('Remove from project')\n                  : t('Add to project'),\n                shortcut: ['entr'],\n              },\n            ]\n          : [\n              {\n                label: t('Stop indexing'),\n                shortcut: ['entr'],\n              },\n            ]\n      }\n      customRightElement={\n        isIndexing ? (\n          <p className=\"body-mini-b text-label-link\">{t('Indexing...')}</p>\n        ) : undefined\n      }\n      focusedItemProps={focusedItemProps}\n      disableKeyNav={disableKeyNav}\n    />\n  );\n};\n\nexport default memo(DocItem);\n"
  },
  {
    "path": "client/src/CommandBar/steps/items/RepoItem.tsx",
    "content": "import React, {\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport {\n  CommandBarStepEnum,\n  RepoIndexingStatusType,\n  RepoProvider,\n  RepoUi,\n  SyncStatus,\n} from '../../../types/general';\nimport {\n  addRepoToProject,\n  cancelSync,\n  deleteRepo,\n  removeRepoFromProject,\n  syncRepo,\n} from '../../../services/api';\nimport {\n  CloseSignInCircleIcon,\n  LinkChainIcon,\n  PlusSignIcon,\n  RepositoryIcon,\n  TrashCanIcon,\n} from '../../../icons';\nimport { DeviceContext } from '../../../context/deviceContext';\nimport { getFileManagerName } from '../../../utils';\nimport Item from '../../Body/Item';\nimport SpinLoaderContainer from '../../../components/Loaders/SpinnerLoader';\nimport { ProjectContext } from '../../../context/projectContext';\nimport { repoStatusMap } from '../../../consts/general';\nimport { RepositoriesContext } from '../../../context/repositoriesContext';\n\ntype Props = {\n  repo: RepoUi;\n  index: string;\n  isFirst: boolean;\n  refetchRepos: () => void;\n  disableKeyNav?: boolean;\n  indexingStatus?: RepoIndexingStatusType;\n  tutorialPopup?: React.ReactElement;\n  onSync?: () => void;\n  onDone?: () => void;\n  onAddToProject?: () => void;\n};\n\nconst RepoItem = ({\n  repo,\n  isFirst,\n  index,\n  refetchRepos,\n  disableKeyNav,\n  indexingStatus,\n  onSync,\n  onDone,\n  onAddToProject,\n}: Props) => {\n  const { t } = useTranslation();\n  const { project, refreshCurrentProjectRepos } = useContext(\n    ProjectContext.Current,\n  );\n  const { openFolderInExplorer, os, openLink } = useContext(DeviceContext);\n\n  const onRepoSync = useCallback(\n    async (e?: MouseEvent | KeyboardEvent | React.MouseEvent) => {\n      e?.preventDefault();\n      e?.stopPropagation();\n      await syncRepo(repo.ref);\n      onSync?.();\n    },\n    [repo.ref, onSync],\n  );\n\n  const status = useMemo(() => {\n    return indexingStatus?.status || repo.sync_status;\n  }, [indexingStatus]);\n\n  useEffect(() => {\n    if (status === SyncStatus.Done) {\n      onDone?.();\n    }\n  }, [status]);\n\n  const handleAddToProject = useCallback(() => {\n    if (project?.id) {\n      onAddToProject?.();\n      return addRepoToProject(\n        project.id,\n        repo.ref,\n        repo.branch_filter?.select?.[0],\n      ).finally(() => {\n        refreshCurrentProjectRepos();\n      });\n    }\n  }, [repo]);\n\n  const handleOpenInFinder = useCallback(() => {\n    openFolderInExplorer(repo.ref.slice(6));\n  }, [openFolderInExplorer, repo.ref]);\n\n  const handleOpenInGitHub = useCallback(() => {\n    openLink('https://' + repo.ref);\n  }, [openLink, repo.ref]);\n\n  const handleRemoveFromProject = useCallback(() => {\n    if (project?.id) {\n      return removeRepoFromProject(project.id, repo.ref).finally(() => {\n        refreshCurrentProjectRepos();\n      });\n    }\n  }, [repo]);\n\n  const handleCancelSync = useCallback(() => {\n    cancelSync(repo.ref);\n  }, [repo.ref]);\n\n  const isIndexing = useMemo(() => {\n    return [\n      SyncStatus.Indexing,\n      SyncStatus.Syncing,\n      SyncStatus.Queued,\n    ].includes(status);\n  }, [status]);\n\n  const onRepoRemove = useCallback(async () => {\n    await deleteRepo(repo.ref);\n    refetchRepos();\n  }, [repo.ref]);\n\n  const isInProject = useMemo(() => {\n    return project?.repos.find((r) => r.repo.ref === repo.ref);\n  }, [project, repo.ref]);\n\n  const focusedItemProps = useMemo(() => {\n    const dropdownItems1 = [];\n    if (isIndexing) {\n      dropdownItems1.push({\n        onClick: handleCancelSync,\n        label: t('Stop indexing'),\n        icon: (\n          <span className=\"w-6 h-6 flex items-center justify-center rounded-6 bg-bg-border\">\n            <CloseSignInCircleIcon sizeClassName=\"w-3.5 h-3.5\" />\n          </span>\n        ),\n        key: 'stop_indexing',\n      });\n    }\n    if (status === SyncStatus.Done || status === SyncStatus.Cancelled) {\n      dropdownItems1.push(\n        isInProject\n          ? {\n              onClick: handleRemoveFromProject,\n              label: t('Remove from project'),\n              icon: (\n                <span className=\"w-6 h-6 flex items-center justify-center rounded-6 bg-bg-border\">\n                  <TrashCanIcon sizeClassName=\"w-3.5 h-3.5\" />\n                </span>\n              ),\n              key: 'remove_from_project',\n            }\n          : {\n              onClick: handleAddToProject,\n              label: t('Add to project'),\n              icon: (\n                <span className=\"w-6 h-6 flex items-center justify-center rounded-6 bg-bg-border\">\n                  <PlusSignIcon sizeClassName=\"w-3.5 h-3.5\" />\n                </span>\n              ),\n              key: 'add_to_project',\n            },\n      );\n      dropdownItems1.push({\n        onClick: onRepoSync,\n        label: t('Re-sync'),\n        shortcut: ['cmd', 'R'],\n        key: 'resync',\n      });\n      dropdownItems1.push({\n        onClick: onRepoRemove,\n        label: t('Remove'),\n        shortcut: ['cmd', 'D'],\n        key: 'remove',\n      });\n    }\n    const dropdownItems2 = [\n      repo.provider === RepoProvider.Local\n        ? {\n            onClick: handleOpenInFinder,\n            label: t(`Open in {{viewer}}`, {\n              viewer: getFileManagerName(os.type),\n            }),\n            key: 'openInFinder',\n          }\n        : {\n            onClick: handleOpenInGitHub,\n            label: t(`Open in GitHub`),\n            icon: (\n              <span className=\"w-6 h-6 flex items-center justify-center rounded-6 bg-bg-border\">\n                <LinkChainIcon sizeClassName=\"w-3.5 h-3.5\" />\n              </span>\n            ),\n            key: 'openInGitHub',\n          },\n    ];\n    const dropdownItems = [];\n    if (dropdownItems1.length) {\n      dropdownItems.push({ items: dropdownItems1, key: '1', itemsOffset: 0 });\n    }\n    if (dropdownItems2.length) {\n      dropdownItems.push({\n        items: dropdownItems2,\n        key: '2',\n        itemsOffset: dropdownItems1.length,\n      });\n    }\n    return {\n      dropdownItems,\n    };\n  }, [\n    t,\n    isInProject,\n    handleAddToProject,\n    handleRemoveFromProject,\n    handleCancelSync,\n    status,\n    repo.provider,\n    isIndexing,\n    handleOpenInFinder,\n    handleOpenInGitHub,\n    onRepoRemove,\n  ]);\n\n  return (\n    <Item\n      key={repo.ref}\n      index={index}\n      isFirst={isFirst}\n      itemKey={`repo-${repo.ref}`}\n      Icon={isIndexing ? SpinLoaderContainer : RepositoryIcon}\n      label={repo.shortName}\n      id={CommandBarStepEnum.REPO_SETTINGS}\n      footerHint={\n        isIndexing\n          ? t('Indexing...')\n          : status === SyncStatus.Done\n          ? ``\n          : t('Index repository')\n      }\n      iconContainerClassName={\n        status === SyncStatus.Done || status === SyncStatus.Cancelled\n          ? 'bg-bg-contrast text-label-contrast'\n          : 'bg-bg-border'\n      }\n      isWithCheckmark={!!isInProject}\n      onClick={\n        status === SyncStatus.Done\n          ? isInProject\n            ? handleRemoveFromProject\n            : handleAddToProject\n          : isIndexing\n          ? handleCancelSync\n          : onRepoSync\n      }\n      footerBtns={\n        status === SyncStatus.Done || status === SyncStatus.Cancelled\n          ? [\n              {\n                label: isInProject\n                  ? t('Remove from project')\n                  : t('Add to project'),\n                shortcut: ['entr'],\n                action: isInProject\n                  ? handleRemoveFromProject\n                  : handleAddToProject,\n              },\n            ]\n          : isIndexing\n          ? [\n              {\n                label: t('Stop indexing'),\n                shortcut: ['entr'],\n                action: handleCancelSync,\n              },\n            ]\n          : [\n              {\n                label: t('Start indexing'),\n                shortcut: ['entr'],\n                action: onRepoSync,\n              },\n            ]\n      }\n      customRightElement={\n        isIndexing ? (\n          <p className=\"body-mini-b text-label-link\">\n            {t(repoStatusMap[status].text)}\n            {indexingStatus?.percentage !== null &&\n              indexingStatus?.percentage !== undefined &&\n              ` · ${indexingStatus?.percentage}%`}\n          </p>\n        ) : undefined\n      }\n      focusedItemProps={focusedItemProps}\n      disableKeyNav={disableKeyNav}\n    />\n  );\n};\n\nconst WithIndexingStatus = (props: Omit<Props, 'indexingStatus'>) => {\n  const { indexingStatus } = useContext(RepositoriesContext);\n  const repoIndexingStatus = useMemo(() => {\n    return indexingStatus[props.repo.ref];\n  }, [indexingStatus[props.repo.ref]]);\n\n  return <RepoItem {...props} indexingStatus={repoIndexingStatus} />;\n};\n\nexport default memo(WithIndexingStatus);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/ChatTab/ActionsDropdown.tsx",
    "content": "import { memo, useCallback } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport DropdownSection from '../../../components/Dropdown/Section';\nimport SectionItem from '../../../components/Dropdown/Section/SectionItem';\nimport { SplitViewIcon, TrashCanIcon } from '../../../icons';\nimport { deleteConversation } from '../../../services/api';\nimport { openInSplitViewShortcut } from '../../../consts/shortcuts';\n\ntype Props = {\n  handleMoveToAnotherSide: () => void;\n  refreshCurrentProjectConversations: () => void;\n  closeTab: (tabKey: string, side: 'left' | 'right') => void;\n  conversationId?: string;\n  projectId?: string;\n  tabKey: string;\n  side: 'left' | 'right';\n};\n\nconst ActionsDropdown = ({\n  handleMoveToAnotherSide,\n  refreshCurrentProjectConversations,\n  conversationId,\n  projectId,\n  closeTab,\n  tabKey,\n  side,\n}: Props) => {\n  const { t } = useTranslation();\n\n  const removeConversation = useCallback(async () => {\n    if (projectId && conversationId) {\n      await deleteConversation(projectId, conversationId);\n      refreshCurrentProjectConversations();\n      closeTab(tabKey, side);\n    }\n  }, [\n    projectId,\n    conversationId,\n    closeTab,\n    refreshCurrentProjectConversations,\n    tabKey,\n    side,\n  ]);\n\n  return (\n    <div>\n      <DropdownSection>\n        <SectionItem\n          index={'split-view'}\n          label={t('Open in split view')}\n          shortcut={openInSplitViewShortcut}\n          onClick={handleMoveToAnotherSide}\n          icon={<SplitViewIcon sizeClassName=\"w-4 h-4\" />}\n        />\n        {conversationId && (\n          <SectionItem\n            index={'del-chat'}\n            label={t('Delete conversation')}\n            // shortcut={shortcuts.splitView}\n            onClick={removeConversation}\n            // isFocused\n            icon={<TrashCanIcon sizeClassName=\"w-4 h-4\" />}\n          />\n        )}\n      </DropdownSection>\n    </div>\n  );\n};\n\nexport default memo(ActionsDropdown);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/ChatTab/ChatPersistentState.tsx",
    "content": "import {\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { ProjectContext } from '../../../context/projectContext';\nimport {\n  ChatMessage,\n  ChatMessageAuthor,\n  ChatMessageServer,\n  ChatMessageUser,\n  ChatTabType,\n  InputValueType,\n  ParsedQueryType,\n  ParsedQueryTypeEnum,\n  TabTypesEnum,\n} from '../../../types/general';\nimport { conversationsCache } from '../../../services/cache';\nimport { mapLoadingSteps, mapUserQuery } from '../../../mappers/conversation';\nimport { focusInput } from '../../../utils/domUtils';\nimport { ChatsContext } from '../../../context/chatsContext';\nimport { TabsContext } from '../../../context/tabsContext';\nimport { API_BASE_URL, getConversation } from '../../../services/api';\nimport {\n  concatenateParsedQuery,\n  splitUserInputAfterAutocomplete,\n} from '../../../utils';\n\ntype Options = {\n  path: string;\n  lines: [number, number];\n  repoRef: string;\n  branch?: string | null;\n};\n\ntype Props = {\n  tabKey: string;\n  tabTitle?: string;\n  conversationId?: string;\n  initialQuery?: Options;\n  side: 'left' | 'right';\n};\n\nconst ChatPersistentState = ({\n  tabKey,\n  tabTitle,\n  side,\n  initialQuery,\n  conversationId: convId,\n}: Props) => {\n  const { t } = useTranslation();\n  const { project, refreshCurrentProjectConversations } = useContext(\n    ProjectContext.Current,\n  );\n  const { setChats } = useContext(ChatsContext);\n  const { openNewTab, updateTabProperty } = useContext(TabsContext.Handlers);\n\n  const eventSource = useRef<EventSource | null>(null);\n\n  const [conversation, setConversation] = useState<ChatMessage[]>([]);\n  useEffect(() => {\n    setChats((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], conversation } };\n    });\n  }, [conversation]);\n\n  const [selectedLines, setSelectedLines] = useState<[number, number] | null>(\n    null,\n  );\n  useEffect(() => {\n    setChats((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], selectedLines } };\n    });\n  }, [selectedLines]);\n\n  const [inputValue, setInputValue] = useState<InputValueType>({\n    plain: '',\n    parsed: [],\n  });\n  useEffect(() => {\n    setChats((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], inputValue } };\n    });\n  }, [inputValue]);\n\n  const [submittedQuery, setSubmittedQuery] = useState<\n    InputValueType & { options?: Options }\n  >(\n    initialQuery\n      ? {\n          parsed: [\n            {\n              type: ParsedQueryTypeEnum.TEXT,\n              text: `#explain_${initialQuery.path}:${initialQuery.lines.join(\n                '-',\n              )}-${Date.now()}`,\n            },\n          ],\n          plain: `#explain_${initialQuery.path}:${initialQuery.lines.join(\n            '-',\n          )}-${Date.now()}`,\n          options: initialQuery,\n        }\n      : {\n          parsed: [],\n          plain: '',\n        },\n  );\n  useEffect(() => {\n    setChats((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], submittedQuery } };\n    });\n  }, [submittedQuery]);\n\n  const [isLoading, setLoading] = useState(false);\n  useEffect(() => {\n    setChats((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], isLoading } };\n    });\n  }, [isLoading]);\n\n  const [isDeprecatedModalOpen, setDeprecatedModalOpen] = useState(false);\n  useEffect(() => {\n    setChats((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], isDeprecatedModalOpen } };\n    });\n  }, [isDeprecatedModalOpen]);\n\n  const [hideMessagesFrom, setHideMessagesFrom] = useState<null | number>(null);\n  useEffect(() => {\n    setChats((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], hideMessagesFrom } };\n    });\n  }, [hideMessagesFrom]);\n\n  const [queryIdToEdit, setQueryIdToEdit] = useState('');\n  useEffect(() => {\n    setChats((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], queryIdToEdit } };\n    });\n  }, [queryIdToEdit]);\n\n  const [inputImperativeValue, setInputImperativeValue] =\n    useState<InputValueType>({ plain: '', parsed: [] });\n  useEffect(() => {\n    setChats((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], inputImperativeValue } };\n    });\n  }, [inputImperativeValue]);\n\n  const [threadId, setThreadId] = useState('');\n  useEffect(() => {\n    setChats((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], threadId } };\n    });\n  }, [threadId]);\n\n  const [conversationId, setConversationId] = useState('');\n  useEffect(() => {\n    setChats((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], conversationId } };\n    });\n  }, [conversationId]);\n\n  const closeDeprecatedModal = useCallback(() => {\n    setDeprecatedModalOpen(false);\n  }, []);\n\n  useEffect(() => {\n    setChats((prev) => {\n      return {\n        ...prev,\n        [tabKey]: {\n          ...prev[tabKey],\n          setConversation,\n          setInputValue,\n          setSelectedLines,\n          setSubmittedQuery,\n          setThreadId,\n          closeDeprecatedModal,\n        },\n      };\n    });\n  }, []);\n\n  const setInputValueImperatively = useCallback(\n    (value: ParsedQueryType[] | string) => {\n      setInputValue(\n        typeof value === 'string'\n          ? { plain: value, parsed: splitUserInputAfterAutocomplete(value) }\n          : { parsed: value, plain: concatenateParsedQuery(value) },\n      );\n      setInputImperativeValue(\n        typeof value === 'string'\n          ? { plain: value, parsed: splitUserInputAfterAutocomplete(value) }\n          : { parsed: value, plain: concatenateParsedQuery(value) },\n      );\n      focusInput();\n    },\n    [],\n  );\n  useEffect(() => {\n    setChats((prev) => {\n      return {\n        ...prev,\n        [tabKey]: { ...prev[tabKey], setInputValueImperatively },\n      };\n    });\n  }, [setInputValueImperatively]);\n\n  const makeSearch = useCallback(\n    async (query: string, options?: Options) => {\n      if (!query) {\n        return;\n      }\n      eventSource.current?.close();\n      setInputValue({ plain: '', parsed: [] });\n      setInputImperativeValue({ plain: '', parsed: [] });\n      setLoading(true);\n      setQueryIdToEdit('');\n      setHideMessagesFrom(null);\n      const url = `${API_BASE_URL}/projects/${project?.id}/answer${\n        options ? `/explain` : ``\n      }`;\n      const queryParams: Record<string, string> = {\n        answer_model: 'gpt-4-turbo-24k',\n      };\n      if (conversationId) {\n        queryParams.conversation_id = conversationId;\n        if (queryIdToEdit) {\n          queryParams.parent_query_id = queryIdToEdit;\n        }\n      }\n      if (options) {\n        queryParams.relative_path = options.path;\n        queryParams.repo_ref = options.repoRef;\n        if (options.branch) {\n          queryParams.branch = options.branch;\n        }\n        queryParams.line_start = options.lines[0].toString();\n        queryParams.line_end = options.lines[1].toString();\n        queryParams.q = query;\n      } else {\n        queryParams.q = query;\n      }\n      const fullUrl = url + '?' + new URLSearchParams(queryParams).toString();\n      console.log(fullUrl);\n      eventSource.current = new EventSource(fullUrl);\n      setSelectedLines(null);\n      let firstResultCame: boolean;\n      eventSource.current.onerror = (err) => {\n        console.log('SSE error', err);\n        firstResultCame = false;\n        stopGenerating();\n        setConversation((prev) => {\n          const newConversation = prev.slice(0, -1);\n          const lastMessage: ChatMessage = {\n            author: ChatMessageAuthor.Server,\n            isLoading: false,\n            error: t(\n              \"We couldn't answer your question. You can try asking again in a few moments, or rephrasing your question.\",\n            ),\n            loadingSteps: [],\n            queryId: '',\n            responseTimestamp: new Date().toISOString(),\n          };\n          if (!options) {\n            // setInputValue(prev[prev.length - 2]?.text || submittedQuery);\n            setInputValueImperatively(\n              (prev[prev.length - 2] as ChatMessageUser)?.parsedQuery ||\n                prev[prev.length - 2]?.text ||\n                submittedQuery.parsed,\n            );\n          }\n          setSubmittedQuery({ plain: '', parsed: [] });\n          return [...newConversation, lastMessage];\n        });\n      };\n      let conversation_id = '';\n      setConversation((prev) =>\n        prev[prev.length - 1].author === ChatMessageAuthor.Server &&\n        (prev[prev.length - 1] as ChatMessageServer).isLoading\n          ? prev\n          : [\n              ...prev,\n              {\n                author: ChatMessageAuthor.Server,\n                isLoading: true,\n                loadingSteps: [],\n                text: '',\n                conclusion: '',\n                queryId: '',\n                responseTimestamp: '',\n              },\n            ],\n      );\n      eventSource.current.onmessage = (ev) => {\n        console.log(ev.data);\n        if (\n          ev.data === '{\"Err\":\"incompatible client\"}' ||\n          ev.data === '{\"Err\":\"failed to check compatibility\"}'\n        ) {\n          eventSource.current?.close();\n          if (ev.data === '{\"Err\":\"incompatible client\"}') {\n            setDeprecatedModalOpen(true);\n          } else {\n            setConversation((prev) => {\n              const newConversation = prev.slice(0, -1);\n              const lastMessage: ChatMessage = {\n                author: ChatMessageAuthor.Server,\n                isLoading: false,\n                error: t(\n                  \"We couldn't answer your question. You can try asking again in a few moments, or rephrasing your question.\",\n                ),\n                loadingSteps: [],\n                queryId: '',\n                responseTimestamp: new Date().toISOString(),\n              };\n              if (!options) {\n                // setInputValue(prev[prev.length - 1]?.text || submittedQuery);\n                setInputValueImperatively(\n                  (prev[prev.length - 1] as ChatMessageUser)?.parsedQuery ||\n                    prev[prev.length - 2]?.text ||\n                    submittedQuery.parsed,\n                );\n              }\n              setSubmittedQuery({ plain: '', parsed: [] });\n              return [...newConversation, lastMessage];\n            });\n          }\n          setLoading(false);\n          return;\n        }\n        try {\n          const data = JSON.parse(ev.data);\n          if (data?.Ok?.ChatEvent) {\n            const newMessage = data.Ok.ChatEvent;\n            conversationsCache[conversation_id] = undefined; // clear cache on new answer\n            setConversation((prev) => {\n              const newConversation = prev?.slice(0, -1) || [];\n              const lastMessage = prev?.slice(-1)[0];\n              const messageToAdd = {\n                author: ChatMessageAuthor.Server,\n                isLoading: true,\n                loadingSteps: mapLoadingSteps(newMessage.search_steps, t),\n                text: newMessage.answer,\n                conclusion: newMessage.conclusion,\n                queryId: newMessage.id,\n                responseTimestamp: newMessage.response_timestamp,\n                explainedFile: newMessage.focused_chunk?.repo_path,\n              };\n              const lastMessages: ChatMessage[] =\n                lastMessage?.author === ChatMessageAuthor.Server\n                  ? [messageToAdd]\n                  : [...prev.slice(-1), messageToAdd];\n              return [...newConversation, ...lastMessages];\n            });\n            // workaround: sometimes we get [^summary]: before it is removed from response\n            if (newMessage.answer?.length > 11 && !firstResultCame) {\n              if (newMessage.focused_chunk?.repo_path) {\n                openNewTab(\n                  {\n                    type: TabTypesEnum.FILE,\n                    path: newMessage.focused_chunk.repo_path.path,\n                    repoRef: newMessage.focused_chunk.repo_path.repo,\n                    scrollToLine:\n                      newMessage.focused_chunk.start_line > -1\n                        ? `${newMessage.focused_chunk.start_line}_${newMessage.focused_chunk.end_line}`\n                        : undefined,\n                  },\n                  side === 'left' ? 'right' : 'left',\n                );\n              }\n              firstResultCame = true;\n            }\n          } else if (data?.Ok?.StreamEnd) {\n            const message = data.Ok.StreamEnd;\n            conversation_id = message.conversation_id;\n            setThreadId(message.thread_id);\n            setConversationId(message.conversation_id);\n            if (conversation.length < 2) {\n              updateTabProperty<ChatTabType, 'conversationId'>(\n                tabKey,\n                'conversationId',\n                message.conversation_id,\n                side,\n              );\n            }\n            eventSource.current?.close();\n            eventSource.current = null;\n            setLoading(false);\n            setConversation((prev) => {\n              const newConversation = prev.slice(0, -1);\n              const lastMessage = {\n                ...prev.slice(-1)[0],\n                isLoading: false,\n              };\n              return [...newConversation, lastMessage];\n            });\n            refreshCurrentProjectConversations();\n            setTimeout(() => focusInput(), 100);\n            return;\n          } else if (data.Err) {\n            setConversation((prev) => {\n              const lastMessageIsServer =\n                prev[prev.length - 1].author === ChatMessageAuthor.Server;\n              const newConversation = prev.slice(\n                0,\n                lastMessageIsServer ? -2 : -1,\n              );\n              const lastMessage: ChatMessageServer = {\n                ...(lastMessageIsServer\n                  ? (prev.slice(-1)[0] as ChatMessageServer)\n                  : {\n                      author: ChatMessageAuthor.Server,\n                      loadingSteps: [],\n                      queryId: '',\n                      responseTimestamp: new Date().toISOString(),\n                    }),\n                isLoading: false,\n                error:\n                  data.Err === 'request failed 5 times'\n                    ? t(\n                        'Failed to get a response from OpenAI. Try again in a few moments.',\n                      )\n                    : t(\n                        \"We couldn't answer your question. You can try asking again in a few moments, or rephrasing your question.\",\n                      ),\n              };\n              if (!options) {\n                setInputValueImperatively(\n                  (\n                    prev[\n                      prev.length - (lastMessageIsServer ? 2 : 1)\n                    ] as ChatMessageUser\n                  )?.parsedQuery ||\n                    prev[prev.length - 2]?.text ||\n                    submittedQuery.parsed,\n                );\n              }\n              setSubmittedQuery({ plain: '', parsed: [] });\n              return [...newConversation, lastMessage];\n            });\n            eventSource.current?.close();\n            eventSource.current = null;\n            setLoading(false);\n          }\n        } catch (err) {\n          console.log('failed to parse response', err);\n        }\n      };\n    },\n    [conversationId, t, queryIdToEdit, openNewTab, side],\n  );\n\n  useEffect(() => {\n    return () => {\n      eventSource.current?.close();\n    };\n  }, []);\n\n  useEffect(() => {\n    if (!submittedQuery.plain) {\n      return;\n    }\n    let userQuery = submittedQuery.plain;\n    let userQueryParsed = submittedQuery.parsed;\n    const options = submittedQuery.options;\n    if (submittedQuery.plain.startsWith('#explain_')) {\n      const [prefix, ending] = submittedQuery.plain.split(':');\n      const [lineStart, lineEnd] = ending.split('-');\n      const filePath = prefix.slice(9);\n      userQuery = t(\n        `Explain lines {{lineStart}} - {{lineEnd}} in {{filePath}}`,\n        {\n          lineStart: Number(lineStart) + 1,\n          lineEnd: Number(lineEnd) + 1,\n          filePath,\n        },\n      );\n      userQueryParsed = [{ type: ParsedQueryTypeEnum.TEXT, text: userQuery }];\n    }\n    setConversation((prev) => {\n      return (prev.length === 1 && submittedQuery.options) ||\n        (prev.length === 2 &&\n          submittedQuery.options?.lines &&\n          submittedQuery.options === initialQuery)\n        ? prev\n        : [\n            ...prev,\n            {\n              author: ChatMessageAuthor.User,\n              text: userQuery,\n              parsedQuery: userQueryParsed,\n              isLoading: false,\n            },\n          ];\n    });\n    makeSearch(userQuery, options);\n  }, [submittedQuery]);\n\n  useEffect(() => {\n    if (conversation.length && conversation.length < 3 && !tabTitle) {\n      updateTabProperty<ChatTabType, 'title'>(\n        tabKey,\n        'title',\n        conversation[0].text,\n        side,\n      );\n    }\n  }, [conversation, tabKey, side, tabTitle]);\n\n  const stopGenerating = useCallback(() => {\n    eventSource.current?.close();\n    setLoading(false);\n    setConversation((prev) => {\n      const newConversation = prev.slice(0, -1);\n      const lastMessage = {\n        ...prev.slice(-1)[0],\n        isLoading: false,\n      };\n      return [...newConversation, lastMessage];\n    });\n    setTimeout(focusInput, 100);\n  }, []);\n  useEffect(() => {\n    setChats((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], stopGenerating } };\n    });\n  }, [stopGenerating]);\n\n  const onMessageEdit = useCallback(\n    (parentQueryId: string, i: number) => {\n      setQueryIdToEdit(parentQueryId);\n      if (isLoading) {\n        stopGenerating();\n      }\n      setHideMessagesFrom(i);\n      const mes = conversation[i] as ChatMessageUser;\n      setInputValueImperatively(mes.parsedQuery || mes.text!);\n    },\n    [isLoading, conversation],\n  );\n  useEffect(() => {\n    setChats((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], onMessageEdit } };\n    });\n  }, [onMessageEdit]);\n\n  const onMessageEditCancel = useCallback(() => {\n    setQueryIdToEdit('');\n    setInputValue({ plain: '', parsed: [] });\n    setInputImperativeValue({ plain: '', parsed: [] });\n    setHideMessagesFrom(null);\n  }, []);\n  useEffect(() => {\n    setChats((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], onMessageEditCancel } };\n    });\n  }, [onMessageEditCancel]);\n\n  useEffect(() => {\n    // if it was open from history and not updated from sse message\n    if (convId && project?.id && !conversation.length) {\n      getConversation(project.id, convId).then((resp) => {\n        const conv: ChatMessage[] = [];\n        let hasOpenedTab = false;\n        resp.exchanges.forEach((m) => {\n          // @ts-ignore\n          const userQuery = m.search_steps.find((s) => s.type === 'QUERY');\n          const parsedQuery = mapUserQuery(m);\n          conv.push({\n            author: ChatMessageAuthor.User,\n            text: m.query.raw_query || userQuery?.content?.query || '',\n            parsedQuery,\n            isFromHistory: true,\n          });\n          conv.push({\n            author: ChatMessageAuthor.Server,\n            isLoading: false,\n            loadingSteps: mapLoadingSteps(m.search_steps, t),\n            text: m.answer,\n            conclusion: m.conclusion,\n            queryId: m.id,\n            responseTimestamp: m.response_timestamp,\n            explainedFile: m.focused_chunk?.repo_path.path,\n          });\n          if (!hasOpenedTab && m.focused_chunk?.repo_path) {\n            openNewTab(\n              {\n                type: TabTypesEnum.FILE,\n                path: m.focused_chunk.repo_path.path,\n                repoRef: m.focused_chunk.repo_path.repo,\n                scrollToLine:\n                  m.focused_chunk.start_line > -1\n                    ? `${m.focused_chunk.start_line}_${m.focused_chunk.end_line}`\n                    : undefined,\n              },\n              side === 'left' ? 'right' : 'left',\n            );\n            hasOpenedTab = true;\n          }\n        });\n        setConversation(conv);\n        setThreadId(resp.thread_id);\n        setConversationId(convId);\n      });\n    }\n  }, [convId, project?.id]);\n\n  return null;\n};\n\nexport default memo(ChatPersistentState);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/ChatTab/Conversation.tsx",
    "content": "import React, {\n  memo,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport { ChatMessageServer } from '../../../types/general';\nimport { ProjectContext } from '../../../context/projectContext';\nimport { ChatContext, ChatsContext } from '../../../context/chatsContext';\nimport ScrollToBottom from '../../../components/ScrollToBottom';\nimport Input from './Input';\nimport ScrollableContent from './ScrollableContent';\nimport DeprecatedClientModal from './DeprecatedClientModal';\n\ntype Props = {\n  side: 'left' | 'right';\n  tabKey: string;\n};\n\nconst Conversation = ({ side, tabKey }: Props) => {\n  const { project } = useContext(ProjectContext.Current);\n  const { chats } = useContext(ChatsContext);\n  const scrollableRef = useRef<HTMLDivElement>(null);\n  const [isScrollable, setIsScrollable] = useState(false);\n\n  const chatData: ChatContext | undefined = useMemo(\n    () => chats[tabKey],\n    [chats, tabKey],\n  );\n\n  useEffect(() => {\n    setTimeout(() => {\n      if (scrollableRef.current) {\n        setIsScrollable(\n          scrollableRef.current.scrollHeight >\n            scrollableRef.current.clientHeight,\n        );\n      }\n    }, 100);\n  }, [chatData?.conversation, chatData?.hideMessagesFrom]);\n\n  return !chatData ? null : (\n    <div className=\"w-full max-w-2xl mx-auto flex flex-col flex-1 overflow-auto\">\n      <ScrollToBottom\n        className=\"max-w-full flex flex-col overflow-auto\"\n        wrapperRef={scrollableRef}\n      >\n        <ScrollableContent\n          chatData={chatData}\n          side={side}\n          projectId={project?.id!}\n        />\n      </ScrollToBottom>\n      <Input\n        selectedLines={chatData.selectedLines}\n        setSelectedLines={chatData.setSelectedLines}\n        onStop={chatData.stopGenerating}\n        submittedQuery={chatData.submittedQuery}\n        isStoppable={chatData.isLoading}\n        onMessageEditCancel={chatData.onMessageEditCancel}\n        generationInProgress={\n          (\n            chatData.conversation[\n              chatData.conversation.length - 1\n            ] as ChatMessageServer\n          )?.isLoading\n        }\n        hideMessagesFrom={chatData.hideMessagesFrom}\n        queryIdToEdit={chatData.queryIdToEdit}\n        valueToEdit={chatData.inputImperativeValue}\n        setInputValue={chatData.setInputValue}\n        value={chatData.inputValue}\n        setConversation={chatData.setConversation}\n        conversation={chatData.conversation}\n        setSubmittedQuery={chatData.setSubmittedQuery}\n        isInputAtBottom={isScrollable}\n        projectId={project?.id || '0'}\n      />\n      <DeprecatedClientModal\n        isOpen={chatData.isDeprecatedModalOpen}\n        onClose={chatData.closeDeprecatedModal}\n      />\n    </div>\n  );\n};\n\nexport default memo(Conversation);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/ChatTab/DeprecatedClientModal.tsx",
    "content": "import { useContext } from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { CloseSignIcon } from '../../../icons';\nimport { DeviceContext } from '../../../context/deviceContext';\nimport Button from '../../../components/Button';\nimport Modal from '../../../components/Modal';\n\ntype Props = {\n  isOpen: boolean;\n  onClose: () => void;\n};\n\nconst DeprecatedClientModal = ({ isOpen, onClose }: Props) => {\n  const { t } = useTranslation();\n  const { openLink, relaunch } = useContext(DeviceContext);\n  return (\n    <Modal isVisible={isOpen} onClose={onClose} noBg>\n      <div className=\"w-[32.75rem] flex flex-col items-start rounded-md border border-bg-border bg-bg-shade shadow-float overflow-hidden\">\n        <div className=\"p-10 relative flex flex-col gap-8\">\n          <div className=\"flex flex-col gap-4\">\n            <h4 className=\"title-m-b text-label-title\">\n              <Trans>Update Required</Trans>\n            </h4>\n            <p className=\"body-base text-label-base\">\n              <Trans>\n                We&apos;ve made some exciting enhancements to bloop! To continue\n                enjoying the full functionality, including the natural language\n                search feature, please update your app to the latest version.\n              </Trans>\n            </p>\n            <p className=\"body-base text-label-base\">\n              <Trans>\n                To update your app, please visit our releases page on GitHub and\n                download the latest version manually. Thank you for using bloop.\n              </Trans>\n            </p>\n          </div>\n          <div className=\"flex justify-between items-center\">\n            <button\n              className=\"body-mini-b text-label-link\"\n              onClick={() =>\n                openLink('https://github.com/BloopAI/bloop/releases')\n              }\n            >\n              <Trans>Visit the downloads page</Trans>\n            </button>\n            <Button onClick={relaunch}>\n              <Trans>Restart the app</Trans>\n            </Button>\n          </div>\n        </div>\n        <div className=\"absolute top-3 right-3\">\n          <Button\n            variant=\"tertiary\"\n            size=\"small\"\n            onClick={onClose}\n            onlyIcon\n            title={t('Close')}\n          >\n            <CloseSignIcon sizeClassName=\"w-3.5 h-3.5\" />\n          </Button>\n        </div>\n      </div>\n    </Modal>\n  );\n};\n\nexport default DeprecatedClientModal;\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/ChatTab/Input/ProseMirror/index.tsx",
    "content": "import { memo, useCallback, useEffect, useMemo, useState } from 'react';\nimport { EditorState, TextSelection, Transaction } from 'prosemirror-state';\nimport { Schema } from 'prosemirror-model';\nimport { keymap } from 'prosemirror-keymap';\nimport { baseKeymap } from 'prosemirror-commands';\nimport {\n  NodeViewComponentProps,\n  ProseMirror,\n  react,\n  ReactNodeViewConstructor,\n  useNodeViews,\n} from '@nytimes/react-prosemirror';\nimport { schema as basicSchema } from 'prosemirror-schema-basic';\n// @ts-ignore\nimport * as icons from 'file-icons-js';\nimport { useTranslation } from 'react-i18next';\nimport {\n  InputEditorContent,\n  ParsedQueryType,\n} from '../../../../../types/general';\nimport { getFileExtensionForLang } from '../../../../../utils';\nimport { blurInput } from '../../../../../utils/domUtils';\nimport { MentionOptionType } from '../../../../../types/results';\nimport { getMentionsPlugin } from './mentionPlugin';\nimport { addMentionNodes, mapEditorContentToInputValue } from './utils';\nimport { placeholderPlugin } from './placeholderPlugin';\n\nconst schema = new Schema({\n  nodes: addMentionNodes(basicSchema.spec.nodes),\n  marks: basicSchema.spec.marks,\n});\n\nfunction Paragraph({ children }: NodeViewComponentProps) {\n  return <p>{children}</p>;\n}\n\nconst reactNodeViews: Record<string, ReactNodeViewConstructor> = {\n  paragraph: () => ({\n    component: Paragraph,\n    dom: document.createElement('div'),\n    contentDOM: document.createElement('span'),\n  }),\n};\n\ntype Props = {\n  getDataLang: (search: string) => Promise<MentionOptionType[]>;\n  getDataPath: (search: string) => Promise<MentionOptionType[]>;\n  getDataRepo: (search: string) => Promise<MentionOptionType[]>;\n  initialValue?: Record<string, any> | null;\n  onChange: (contents: InputEditorContent[]) => void;\n  onSubmit?: (s: { parsed: ParsedQueryType[]; plain: string }) => void;\n  placeholder: string;\n};\n\nconst InputCore = ({\n  getDataLang,\n  getDataPath,\n  getDataRepo,\n  initialValue,\n  onChange,\n  onSubmit,\n  placeholder,\n}: Props) => {\n  const { t } = useTranslation();\n  const mentionPlugin = useMemo(\n    () =>\n      getMentionsPlugin({\n        delay: 10,\n        getSuggestions: async (\n          type: string,\n          text: string,\n          done: (s: MentionOptionType[]) => void,\n        ) => {\n          const data = await Promise.all([\n            getDataRepo(text),\n            getDataPath(text),\n            getDataLang(text),\n          ]);\n          done([...data[0], ...data[1], ...data[2]]);\n        },\n        getSuggestionsHTML: (items) => {\n          return (\n            '<div class=\"suggestion-item-list rounded-md border border-bg-border p-1 shadow-high max-h-[40vh] overflow-auto bg-bg-shade\">' +\n            items\n              .map(\n                (i) =>\n                  `<div>${\n                    i.isFirst\n                      ? `<div class=\"flex items-center gap-2 px-2 py-1 text-label-muted body-mini-b cursor-default\">\n                        ${t(\n                          i.type === 'repo'\n                            ? 'Repositories'\n                            : i.type === 'dir'\n                            ? 'Directories'\n                            : i.type === 'lang'\n                            ? 'Languages'\n                            : 'Files',\n                        )}\n                      </div>`\n                      : ''\n                  }<div class=\"suggestion-item cursor-pointer flex items-center justify-between rounded-6 gap-2 px-2 h-8 body-s text-label-title max-w-[600px] ellipsis\"><span class=\"flex items-center gap-2\">${\n                    i.type === 'repo'\n                      ? `<svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"> <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M4.6665 1.33301C3.56193 1.33301 2.6665 2.22844 2.6665 3.33301V12.6663C2.6665 13.7709 3.56193 14.6663 4.6665 14.6663H12.6665C13.0347 14.6663 13.3332 14.3679 13.3332 13.9997V1.99967C13.3332 1.63148 13.0347 1.33301 12.6665 1.33301H4.6665ZM4.6665 11.9997H11.9998V13.333H4.6665C4.29831 13.333 3.99984 13.0345 3.99984 12.6663C3.99984 12.2982 4.29831 11.9997 4.6665 11.9997Z\" fill=\"currentColor\" /></svg>`\n                      : i.type === 'dir'\n                      ? `<svg viewBox=\"0 0 20 20\" width=\"16\" height=\"16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"> <path d=\"M2 5.5C2 4.39543 2.89543 3.5 4 3.5H7.17157C7.70201 3.5 8.21071 3.71071 8.58579 4.08579L9.41421 4.91421C9.78929 5.28929 10.298 5.5 10.8284 5.5H16C17.1046 5.5 18 6.39543 18 7.5V14.5C18 15.6046 17.1046 16.5 16 16.5H4C2.89543 16.5 2 15.6046 2 14.5V5.5Z\" fill=\"currentColor\"></path></svg>`\n                      : `<span class=\"${\n                          icons.getClassWithColor(\n                            getFileExtensionForLang(i.display, true),\n                          ) || icons.getClassWithColor('.txt')\n                        } text-left w-4 h-4 file-icon flex items-center flex-shrink-0\"></span>`\n                  }<span class=\"ellipsis text-left\">${i.display}</span></span>${\n                    i.hint\n                      ? `<span class=\"ellipsis text-label-muted text-left body-mini\">${i.hint}</span>`\n                      : ''\n                  }</div></div>`,\n              )\n              .join('') +\n            '</div>'\n          );\n        },\n      }),\n    [],\n  );\n\n  const plugins = useMemo(() => {\n    return [\n      placeholderPlugin(placeholder),\n      react(),\n      mentionPlugin,\n      keymap({\n        ...baseKeymap,\n        Escape: (state) => {\n          const key = Object.keys(state).find((k) =>\n            k.startsWith('autosuggestions'),\n          );\n\n          // @ts-ignore\n          if (key && state[key]?.active) {\n            return true;\n          }\n          blurInput();\n          return true;\n        },\n        Enter: (state) => {\n          const key = Object.keys(state).find((k) =>\n            k.startsWith('autosuggestions'),\n          );\n          // @ts-ignore\n          if (key && state[key]?.active) {\n            return false;\n          }\n          const parts = state.toJSON().doc?.content?.[0]?.content;\n          // trying to submit with no text\n          if (!parts) {\n            return false;\n          }\n          onSubmit?.(mapEditorContentToInputValue(parts));\n          return true;\n        },\n        'Ctrl-Enter': baseKeymap.Enter,\n        'Cmd-Enter': baseKeymap.Enter,\n        'Shift-Enter': baseKeymap.Enter,\n      }),\n    ];\n  }, [onSubmit]);\n\n  const { nodeViews, renderNodeViews } = useNodeViews(reactNodeViews);\n  const [mount, setMount] = useState<HTMLDivElement | null>(null);\n  const [state, setState] = useState(\n    EditorState.create({\n      doc: initialValue\n        ? schema.topNodeType.create(null, [schema.nodeFromJSON(initialValue)])\n        : undefined,\n      schema,\n      plugins,\n    }),\n  );\n\n  useEffect(() => {\n    if (mount) {\n      const newState = EditorState.create({\n        schema,\n        plugins,\n        doc: initialValue\n          ? schema.topNodeType.create(null, [schema.nodeFromJSON(initialValue)])\n          : undefined,\n      });\n      const endPos = newState.selection.$to.after() - 1;\n      newState.selection = new TextSelection(newState.doc.resolve(endPos));\n      setState(newState);\n    }\n  }, [mount, initialValue, plugins]);\n\n  const dispatchTransaction = useCallback(\n    (tr: Transaction) => setState((oldState) => oldState.apply(tr)),\n    [],\n  );\n\n  useEffect(() => {\n    const newValue = state.toJSON().doc?.content?.[0]?.content || '';\n    onChange(newValue || []);\n  }, [state]);\n\n  return (\n    <div className=\"w-full body-base pb-4 !leading-[24px] overflow-auto bg-transparent outline-none focus:outline-0 resize-none flex-grow-0 flex flex-col justify-center\">\n      <ProseMirror\n        mount={mount}\n        state={state}\n        nodeViews={nodeViews}\n        dispatchTransaction={dispatchTransaction}\n      >\n        <div ref={setMount} autoCorrect=\"off\" />\n        {renderNodeViews()}\n      </ProseMirror>\n    </div>\n  );\n};\n\nexport default memo(InputCore);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/ChatTab/Input/ProseMirror/mentionPlugin.ts",
    "content": "import { Plugin, PluginKey } from 'prosemirror-state';\nimport { Decoration, DecorationSet, EditorView } from 'prosemirror-view';\nimport { ResolvedPos } from 'prosemirror-model';\nimport { MentionOptionType } from '../../../../../types/results';\n\nexport function getRegexp(mentionTrigger: string, allowSpace?: boolean) {\n  return allowSpace\n    ? new RegExp('(^|\\\\s)' + mentionTrigger + '([\\\\w-\\\\+]*\\\\s?[\\\\w-\\\\+.]*)$')\n    : new RegExp('(^|\\\\s)' + mentionTrigger + '([\\\\w-\\\\+.]*)$');\n}\n\nconst insertAfterSelect = String.fromCharCode(160);\n\nexport function getMatch(\n  $position: ResolvedPos,\n  opts: {\n    mentionTrigger: string;\n    allowSpace?: boolean;\n  },\n) {\n  try {\n    // take current para text content upto cursor start.\n    // this makes the regex simpler and parsing the matches easier.\n    const parastart = $position.before();\n    const text = $position.doc.textBetween(\n      parastart,\n      $position.pos,\n      '\\n',\n      '\\0',\n    );\n\n    const regex = getRegexp(opts.mentionTrigger, opts.allowSpace);\n\n    const match = text.match(regex);\n\n    // if match found, return match with useful information.\n    if (match) {\n      // adjust match.index to remove the matched extra space\n      match.index =\n        match[0].startsWith(' ') || match[0].startsWith(insertAfterSelect)\n          ? (match.index || 0) + 1\n          : match.index;\n      match[0] =\n        match[0].startsWith(' ') || match[0].startsWith(insertAfterSelect)\n          ? match[0].substring(1, match[0].length)\n          : match[0];\n\n      // The absolute position of the match in the document\n      const from = $position.start() + match.index!;\n      const to = from + match[0].length;\n\n      const queryText = match[2];\n\n      return {\n        range: { from: from, to: to },\n        queryText: queryText,\n        type: 'mention',\n      };\n    }\n    // else if no match don't return anything.\n  } catch (e) {\n    console.log(e);\n  }\n}\n\n/**\n * Util to debounce call to a function.\n * >>> debounce(function(){}, 1000, this)\n */\nexport const debounce = (function () {\n  let timeoutId: number;\n  return function (func: () => void, timeout: number, context: any): number {\n    // @ts-ignore\n    context = context || this;\n    clearTimeout(timeoutId);\n    timeoutId = window.setTimeout(function () {\n      // @ts-ignore\n      func.apply(context, arguments);\n    }, timeout);\n\n    return timeoutId;\n  };\n})();\n\ntype State = {\n  active: boolean;\n  range: {\n    from: number;\n    to: number;\n  };\n  type: string;\n  text: string;\n  suggestions: Record<string, any>[];\n  index: number;\n};\n\nconst getNewState = function () {\n  return {\n    active: false,\n    range: {\n      from: 0,\n      to: 0,\n    },\n    type: '',\n    text: '',\n    suggestions: [],\n    index: 0, // current active suggestion index\n  };\n};\n\ntype Options = {\n  mentionTrigger: string;\n  allowSpace?: boolean;\n  activeClass: string;\n  suggestionTextClass?: string;\n  getSuggestions: (\n    type: string,\n    text: string,\n    done: (s: MentionOptionType[]) => void,\n  ) => void;\n  delay: number;\n  getSuggestionsHTML: (items: MentionOptionType[], type: string) => string;\n};\n\nexport function getMentionsPlugin(opts: Partial<Options>) {\n  // default options\n  const defaultOpts = {\n    mentionTrigger: '@',\n    allowSpace: false,\n    getSuggestions: (\n      type: string,\n      text: string,\n      cb: (s: { name: string }[]) => void,\n    ) => {\n      cb([]);\n    },\n    getSuggestionsHTML: (items: { name: string }[]) =>\n      '<div class=\"suggestion-item-list\">' +\n      items\n        .map((i) => '<div class=\"suggestion-item\">' + i.name + '</div>')\n        .join('') +\n      '</div>',\n    activeClass: 'suggestion-item-active',\n    suggestionTextClass: 'prosemirror-suggestion',\n    maxNoOfSuggestions: 10,\n    delay: 500,\n  };\n\n  const options = Object.assign({}, defaultOpts, opts) as Options;\n\n  // timeoutId for clearing debounced calls\n  let showListTimeoutId: number;\n\n  // dropdown element\n  const el = document.createElement('div');\n\n  const showList = function (\n    view: EditorView,\n    state: State,\n    suggestions: MentionOptionType[],\n    opts: Options,\n  ) {\n    try {\n      el.innerHTML = opts.getSuggestionsHTML(suggestions, state.type);\n\n      // attach new item event handlers\n      el.querySelectorAll('.suggestion-item').forEach(\n        function (itemNode, index) {\n          itemNode.addEventListener('click', function () {\n            select(view, state, opts);\n            view.focus();\n          });\n          // TODO: setIndex() needlessly queries.\n          // We already have the itemNode. SHOULD OPTIMIZE.\n          itemNode.addEventListener('mouseover', function () {\n            setIndex(index, state, opts);\n          });\n          itemNode.addEventListener('mouseout', function () {\n            setIndex(index, state, opts);\n          });\n        },\n      );\n\n      // highlight first element by default - like Facebook.\n      addClassAtIndex(state.index, opts.activeClass);\n\n      // TODO: knock off domAtPos usage. It's not documented and is not officially a public API.\n      // It's used currently, only to optimize the the query for textDOM\n      const node = view.domAtPos(view.state.selection.$from.pos);\n      const paraDOM = node.node;\n      const textDOM = (paraDOM as HTMLElement).querySelector(\n        '.' + opts.suggestionTextClass,\n      );\n\n      const offset = textDOM?.getBoundingClientRect();\n\n      document.body.appendChild(el);\n      el.classList.add('suggestion-item-container');\n      el.style.position = 'fixed';\n      el.style.left = -9999 + 'px';\n      const offsetLeft = offset?.left || 0;\n      const offsetTop = offset?.top || 0;\n      setTimeout(() => {\n        el.style.left =\n          offsetLeft + el.clientWidth < window.innerWidth\n            ? offsetLeft + 'px'\n            : offsetLeft +\n              (window.innerWidth - (offsetLeft + el.clientWidth) - 10) +\n              'px';\n        el.style.bottom =\n          window.innerHeight - offsetTop + el.clientHeight > window.innerHeight\n            ? window.innerHeight - offsetTop - el.clientHeight - 20 + 'px'\n            : window.innerHeight - offsetTop + 'px';\n      }, 10);\n\n      el.style.display = 'block';\n      el.style.zIndex = '80';\n    } catch (e) {\n      console.log(e);\n    }\n  };\n\n  const hideList = function () {\n    el.style.display = 'none';\n  };\n\n  const removeClassAtIndex = function (index: number, className: string) {\n    const itemList = el.querySelector('.suggestion-item-list')?.childNodes;\n    const prevItem = itemList?.[index];\n    (prevItem as HTMLElement)?.classList.remove(className);\n  };\n\n  const addClassAtIndex = function (index: number, className: string) {\n    const itemList = el.querySelector('.suggestion-item-list')?.childNodes;\n    const prevItem = itemList?.[index];\n    (prevItem as HTMLElement)?.classList.add(className);\n    return prevItem as HTMLElement | undefined;\n  };\n\n  const setIndex = function (index: number, state: State, opts: Options) {\n    removeClassAtIndex(state.index, opts.activeClass);\n    state.index = index;\n    addClassAtIndex(state.index, opts.activeClass);\n  };\n\n  const goNext = function (view: EditorView, state: State, opts: Options) {\n    removeClassAtIndex(state.index, opts.activeClass);\n    state.index++;\n    state.index = state.index === state.suggestions.length ? 0 : state.index;\n    const el = addClassAtIndex(state.index, opts.activeClass);\n    el?.scrollIntoView({ block: 'nearest' });\n  };\n\n  const goPrev = function (view: EditorView, state: State, opts: Options) {\n    removeClassAtIndex(state.index, opts.activeClass);\n    state.index--;\n    state.index =\n      state.index === -1 ? state.suggestions.length - 1 : state.index;\n    const el = addClassAtIndex(state.index, opts.activeClass);\n    el?.scrollIntoView({ block: 'nearest' });\n  };\n\n  const select = function (view: EditorView, state: State, opts: Options) {\n    const item = state.suggestions[state.index];\n    const attrs = {\n      ...item,\n    };\n    const node = view.state.schema.nodes[state.type].create(attrs);\n    const spaceNode = view.state.schema.text(insertAfterSelect);\n\n    const tr = view.state.tr.replaceWith(state.range.from, state.range.to, [\n      node,\n      spaceNode,\n    ]);\n\n    //var newState = view.state.apply(tr);\n    //view.updateState(newState);\n    view.dispatch(tr);\n  };\n\n  return new Plugin({\n    key: new PluginKey('autosuggestions'),\n\n    // we will need state to track if suggestion dropdown is currently active or not\n    state: {\n      init() {\n        return getNewState();\n      },\n\n      apply(tr, state) {\n        try {\n          // compute state.active for current transaction and return\n          const newState = getNewState();\n          const selection = tr.selection;\n          if (selection.from !== selection.to) {\n            return newState;\n          }\n\n          const $position = selection.$from;\n          const match = getMatch($position, options);\n\n          // if match found update state\n          if (match) {\n            newState.active = true;\n            newState.range = match.range;\n            newState.type = match.type!;\n            newState.text = match.queryText;\n          }\n\n          return newState;\n        } catch (e) {\n          console.log(e);\n          return state;\n        }\n      },\n    },\n\n    // We'll need props to hi-jack keydown/keyup & enter events when suggestion dropdown\n    // is active.\n    props: {\n      handleKeyDown(view, e) {\n        const state = this.getState(view.state);\n\n        if (!state?.active && !state?.suggestions.length) {\n          return false;\n        }\n\n        if (e.key === 'ArrowDown') {\n          e.stopPropagation();\n          goNext(view, state, options);\n          return true;\n        } else if (e.key === 'ArrowUp') {\n          e.stopPropagation();\n          goPrev(view, state, options);\n          return true;\n        } else if (e.key === 'Enter') {\n          e.stopPropagation();\n          select(view, state, options);\n          return true;\n        } else if (e.key === 'Escape') {\n          e.stopPropagation();\n          clearTimeout(showListTimeoutId);\n          hideList();\n          // @ts-ignore\n          this.state = getNewState();\n          return true;\n        } else {\n          // didn't handle. handover to prosemirror for handling.\n          return false;\n        }\n      },\n\n      // to decorate the currently active @mention text in ui\n      decorations(editorState) {\n        const { active, range } = this.getState(editorState) || {};\n\n        if (!active || !range) return null;\n\n        return DecorationSet.create(editorState.doc, [\n          Decoration.inline(range.from, range.to, {\n            nodeName: 'span',\n            class: options.suggestionTextClass,\n          }),\n        ]);\n      },\n    },\n\n    // To track down state mutations and add dropdown reactions\n    view() {\n      return {\n        update: (view) => {\n          const state = this.key?.getState(view.state);\n          if (!state.active) {\n            hideList();\n            clearTimeout(showListTimeoutId);\n            return;\n          }\n          // debounce the call to avoid multiple requests\n          showListTimeoutId = debounce(\n            function () {\n              // get suggestions and set new state\n              options.getSuggestions(\n                state.type,\n                state.text,\n                function (suggestions) {\n                  // update `state` argument with suggestions\n                  state.suggestions = suggestions;\n                  showList(view, state, suggestions, options);\n                },\n              );\n            },\n            options.delay,\n            this,\n          );\n        },\n        destroy: () => {\n          hideList();\n        },\n      };\n    },\n  });\n}\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/ChatTab/Input/ProseMirror/nodes.ts",
    "content": "// @ts-ignore\nimport * as icons from 'file-icons-js';\nimport { type AttributeSpec, type NodeSpec } from 'prosemirror-model';\nimport { getFileExtensionForLang, splitPath } from '../../../../../utils';\n\nexport const mentionNode: NodeSpec = {\n  group: 'inline',\n  inline: true,\n  atom: true,\n\n  attrs: {\n    id: '' as AttributeSpec,\n    display: '' as AttributeSpec,\n    type: 'lang' as AttributeSpec,\n    isFirst: '' as AttributeSpec,\n  },\n\n  selectable: false,\n  draggable: false,\n\n  toDOM: (node) => {\n    const isDir =\n      node.attrs.type === 'dir' ||\n      node.attrs.display.endsWith('/') ||\n      node.attrs.display.endsWith('\\\\');\n    const folderIcon = document.createElement('span');\n    folderIcon.innerHTML = `<svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M2 5.5C2 4.39543 2.89543 3.5 4 3.5H7.17157C7.70201 3.5 8.21071 3.71071 8.58579 4.08579L9.41421 4.91421C9.78929 5.28929 10.298 5.5 10.8284 5.5H16C17.1046 5.5 18 6.39543 18 7.5V14.5C18 15.6046 17.1046 16.5 16 16.5H4C2.89543 16.5 2 15.6046 2 14.5V5.5Z\"\n      fill=\"currentColor\"\n    />\n  </svg>`;\n    folderIcon.className = 'w-4 h-4 flex-shrink-0';\n\n    const repoIcon = document.createElement('span');\n    repoIcon.innerHTML = `<svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fill-rule=\"evenodd\"\n      clip-rule=\"evenodd\"\n      d=\"M4.6665 1.33301C3.56193 1.33301 2.6665 2.22844 2.6665 3.33301V12.6663C2.6665 13.7709 3.56193 14.6663 4.6665 14.6663H12.6665C13.0347 14.6663 13.3332 14.3679 13.3332 13.9997V1.99967C13.3332 1.63148 13.0347 1.33301 12.6665 1.33301H4.6665ZM4.6665 11.9997H11.9998V13.333H4.6665C4.29831 13.333 3.99984 13.0345 3.99984 12.6663C3.99984 12.2982 4.29831 11.9997 4.6665 11.9997Z\"\n      fill=\"currentColor\"\n    />\n  </svg>`;\n    repoIcon.className = 'w-4 h-4 flex-shrink-0';\n\n    return [\n      'span',\n      {\n        'data-type': node.attrs.type,\n        'data-id': node.attrs.id,\n        'data-first': node.attrs.isFirst,\n        'data-display': node.attrs.display,\n        class:\n          'prosemirror-tag-node inline-flex gap-1 h-[22px] items-center align-bottom bg-bg-base border border-bg-border rounded px-1',\n      },\n      isDir\n        ? folderIcon\n        : node.attrs.type === 'repo'\n        ? repoIcon\n        : [\n            'span',\n            {\n              class: `text-left w-4 h-4 file-icon flex-shrink-0 inline-flex items-center ${\n                icons.getClassWithColor(\n                  (node.attrs.type === 'lang'\n                    ? node.attrs.display.includes(' ')\n                      ? '.txt'\n                      : getFileExtensionForLang(node.attrs.display, true)\n                    : node.attrs.display) || '.txt',\n                ) || icons.getClassWithColor('index.txt')\n              }`,\n            },\n            '',\n          ],\n      node.attrs.type === 'lang'\n        ? node.attrs.display\n        : isDir\n        ? splitPath(node.attrs.display).slice(-2)[0]\n        : splitPath(node.attrs.display).pop(),\n    ];\n  },\n\n  parseDOM: [\n    {\n      // match tag with following CSS Selector\n      tag: 'span[data-type][data-id][data-first][data-display]',\n\n      getAttrs: (dom) => {\n        const id = (dom as HTMLElement).getAttribute('data-id');\n        const type = (dom as HTMLElement).getAttribute('data-type');\n        const isFirst = (dom as HTMLElement).getAttribute('data-first');\n        const display = (dom as HTMLElement).getAttribute('data-display');\n        return {\n          id,\n          type,\n          isFirst,\n          display,\n        };\n      },\n    },\n  ],\n};\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/ChatTab/Input/ProseMirror/placeholderPlugin.ts",
    "content": "import { Plugin } from 'prosemirror-state';\nimport { EditorView } from 'prosemirror-view';\n\nexport const placeholderPlugin = (text: string) => {\n  const update = (view: EditorView) => {\n    if (view.state.doc.content.size > 2) {\n      view.dom.removeAttribute('data-placeholder');\n    } else {\n      view.dom.setAttribute('data-placeholder', text);\n    }\n  };\n\n  return new Plugin({\n    view(view) {\n      update(view);\n\n      return { update };\n    },\n  });\n};\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/ChatTab/Input/ProseMirror/utils.ts",
    "content": "import OrderedMap from 'orderedmap';\nimport { type NodeSpec } from 'prosemirror-model';\nimport {\n  InputEditorContent,\n  ParsedQueryTypeEnum,\n} from '../../../../../types/general';\nimport { mentionNode } from './nodes';\n\nexport function addMentionNodes(nodes: OrderedMap<NodeSpec>) {\n  return nodes.append({\n    mention: mentionNode,\n  });\n}\n\nexport const mapEditorContentToInputValue = (\n  inputState: InputEditorContent[],\n) => {\n  const getType = (type: string) =>\n    type === 'lang' || type === 'repo' ? type : 'path';\n  const newValue = inputState\n    .map((s) =>\n      s.type === 'mention'\n        ? `${getType(s.attrs.type)}:${s.attrs.id}`\n        : s.type === 'text'\n        ? s.text?.replace(new RegExp(String.fromCharCode(160), 'g'), ' ')\n        : '',\n    )\n    .join('');\n  const newValueParsed = inputState.map((s) =>\n    s.type === 'mention'\n      ? {\n          type:\n            s.attrs.type === 'lang'\n              ? ParsedQueryTypeEnum.LANG\n              : s.attrs.type === 'repo'\n              ? ParsedQueryTypeEnum.REPO\n              : ParsedQueryTypeEnum.PATH,\n          text: s.attrs.id,\n        }\n      : { type: ParsedQueryTypeEnum.TEXT, text: s.text },\n  );\n  return {\n    plain: newValue,\n    parsed: newValueParsed,\n  };\n};\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/ChatTab/Input/ReactMentions/index.tsx",
    "content": "import React, {\n  memo,\n  ReactNode,\n  useCallback,\n  useEffect,\n  useRef,\n  useState,\n} from 'react';\nimport {\n  Mention,\n  MentionsInput,\n  OnChangeHandlerFunc,\n  SuggestionDataItem,\n} from 'react-mentions';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { getFileExtensionForLang, splitPath } from '../../../../../utils';\nimport FileIcon from '../../../../../components/FileIcon';\nimport { FolderIcon, RepositoryIcon } from '../../../../../icons';\nimport { InputValueType } from '../../../../../types/general';\nimport { blurInput } from '../../../../../utils/domUtils';\nimport { MentionOptionType } from '../../../../../types/results';\n\ntype Props = {\n  placeholder: string;\n  getDataLang: (s: string) => Promise<MentionOptionType[]>;\n  getDataPath: (s: string) => Promise<MentionOptionType[]>;\n  getDataRepo: (s: string) => Promise<MentionOptionType[]>;\n  value?: InputValueType;\n  onChange: (v: string) => void;\n  onSubmit: (v: InputValueType) => void;\n  isDisabled?: boolean;\n  initialValue?: InputValueType;\n};\n\nconst inputStyle = {\n  '&multiLine': {\n    highlighter: {\n      maxHeight: 300,\n      overflow: 'auto',\n    },\n    input: {\n      maxHeight: 300,\n      overflow: 'auto',\n      outline: 'none',\n    },\n  },\n  suggestions: {\n    list: {\n      maxHeight: '40vh',\n      overflowY: 'auto',\n      backgroundColor: 'rgb(var(--bg-shade))',\n      border: '1px solid rgb(var(--bg-border))',\n      boxShadow: 'var(--shadow-high)',\n      padding: 4,\n      zIndex: 100,\n      borderRadius: 6,\n      marginTop: 6,\n    },\n  },\n};\n\nconst ReactMentionsInput = ({\n  placeholder,\n  onSubmit,\n  onChange,\n  getDataPath,\n  getDataRepo,\n  getDataLang,\n  value,\n  isDisabled,\n  initialValue,\n}: Props) => {\n  const { t } = useTranslation();\n  const inputRef = useRef<HTMLTextAreaElement>(null);\n  const [isComposing, setComposition] = useState(false);\n  const [inputValue, setInputValue] = useState('');\n\n  useEffect(() => {\n    if (initialValue) {\n      setInputValue(initialValue.plain);\n    }\n  }, [initialValue]);\n\n  useEffect(() => {\n    if (inputRef.current) {\n      // We need to reset the height momentarily to get the correct scrollHeight for the textarea\n      inputRef.current.style.height = '56px';\n      const scrollHeight = inputRef.current.scrollHeight;\n\n      // We then set the height directly, outside of the render loop\n      // Trying to set this with state or a ref will product an incorrect value.\n      inputRef.current.style.height =\n        Math.max(Math.min(scrollHeight, 300), 56) + 'px';\n    }\n  }, [inputRef.current, value]);\n\n  const handleKeyDown = useCallback(\n    (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n      if (isComposing) {\n        return true;\n      }\n      if (e.key === 'Enter' && !e.shiftKey && onSubmit && value) {\n        e.preventDefault();\n        blurInput();\n        onSubmit({\n          plain: value.plain\n            .replace(/\\|(repo:.*?)\\|/, '$1')\n            .replace(/\\|(path:.*?)\\|/, '$1')\n            .replace(/\\|(lang:.*?)\\|/, '$1'),\n          parsed: value.parsed,\n        });\n      }\n    },\n    [isComposing, onSubmit, value],\n  );\n\n  const repoTransform = useCallback((id: string, trans: string) => {\n    const split = splitPath(trans);\n    return trans.startsWith('local//')\n      ? split.slice(-1)[0]\n      : split.slice(-2).join('/');\n  }, []);\n\n  const pathTransform = useCallback((id: string, trans: string) => {\n    const split = splitPath(trans);\n    return `${split[split.length - 1] || split[split.length - 2]}`;\n  }, []);\n\n  const onCompositionStart = useCallback(() => {\n    setComposition(true);\n  }, []);\n\n  const onCompositionEnd = useCallback(() => {\n    // this event comes before keydown and sets state faster causing unintentional submit\n    setTimeout(() => setComposition(false), 10);\n  }, []);\n\n  const handleChange = useCallback<OnChangeHandlerFunc>((e) => {\n    setInputValue(e.target.value);\n  }, []);\n  useEffect(() => {\n    onChange(inputValue);\n  }, [inputValue]);\n\n  const renderRepoSuggestion = useCallback(\n    (\n      entry: SuggestionDataItem,\n      search: string,\n      highlightedDisplay: ReactNode,\n      index: number,\n      focused: boolean,\n    ) => {\n      const d = entry as MentionOptionType;\n      return (\n        <div>\n          {d.isFirst ? (\n            <div className=\"flex items-center rounded-6 gap-2 px-2 py-1 text-label-muted caption cursor-default\">\n              <Trans>Repositories</Trans>\n            </div>\n          ) : null}\n          <div\n            className={`flex items-center justify-between rounded-6 gap-2 px-2 h-8 ${\n              focused ? 'bg-bg-base-hover' : ''\n            } body-s text-label-title cursor-pointer max-w-[600px] ellipsis`}\n          >\n            <span className=\"flex items-center gap-2\">\n              <RepositoryIcon sizeClassName=\"w-4 h-4\" />\n              <span className=\"ellipsis text-left \">{d.display}</span>\n            </span>\n            <span className=\"ellipsis text-label-muted text-left body-mini\">\n              {d.hint}\n            </span>\n          </div>\n        </div>\n      );\n    },\n    [],\n  );\n\n  const renderPathSuggestion = useCallback(\n    (\n      entry: SuggestionDataItem,\n      search: string,\n      highlightedDisplay: ReactNode,\n      index: number,\n      focused: boolean,\n    ) => {\n      const d = entry as MentionOptionType;\n      return (\n        <div>\n          {d.isFirst ? (\n            <div className=\"flex items-center rounded-6 gap-2 px-2 py-1 text-label-muted caption cursor-default\">\n              <Trans>{d.type === 'dir' ? 'Directories' : 'Files'}</Trans>\n            </div>\n          ) : null}\n          <div\n            className={`flex items-center justify-between rounded-6 gap-2 px-2 h-8 ${\n              focused ? 'bg-bg-base-hover' : ''\n            } body-s text-label-title cursor-pointer max-w-[600px] ellipsis`}\n          >\n            <span className=\"flex items-center gap-2\">\n              {d.type === 'dir' ? (\n                <FolderIcon sizeClassName=\"w-4 h-4\" />\n              ) : (\n                <FileIcon filename={d.display} />\n              )}\n              <span className=\"ellipsis text-left \">{d.display}</span>\n            </span>\n            <span className=\"ellipsis text-label-muted text-left body-mini\">\n              {d.hint}\n            </span>\n          </div>\n        </div>\n      );\n    },\n    [],\n  );\n\n  const renderLangSuggestion = useCallback(\n    (\n      entry: SuggestionDataItem,\n      search: string,\n      highlightedDisplay: ReactNode,\n      index: number,\n      focused: boolean,\n    ) => {\n      const d = entry as MentionOptionType;\n      return (\n        <div>\n          {d.isFirst ? (\n            <div className=\"flex items-center rounded-6 gap-2 px-2 py-1 text-label-muted caption cursor-default\">\n              <Trans>Languages</Trans>\n            </div>\n          ) : null}\n          <div\n            className={`flex items-center justify-between rounded-6 gap-2 px-2 h-8 ${\n              focused ? 'bg-bg-base-hover' : ''\n            } body-s text-label-title cursor-pointer max-w-[600px] ellipsis`}\n          >\n            <span className=\"flex items-center gap-2\">\n              <FileIcon filename={getFileExtensionForLang(d.display, true)} />\n              <span className=\"ellipsis text-left \">{d.display}</span>\n            </span>\n            <span className=\"ellipsis text-label-muted text-left body-mini\">\n              {d.hint}\n            </span>\n          </div>\n        </div>\n      );\n    },\n    [],\n  );\n\n  return (\n    <div className=\"w-full body-base pb-4 !leading-[24px] bg-transparent outline-none focus:outline-0 resize-none flex-grow-0 flex flex-col justify-center\">\n      <MentionsInput\n        value={inputValue}\n        // id={id}\n        onChange={handleChange}\n        className={`ReactMention w-full bg-transparent rounded-lg outline-none focus:outline-0 resize-none\n        placeholder:text-current flex-grow-0`}\n        placeholder={placeholder}\n        inputRef={inputRef}\n        disabled={isDisabled}\n        onCompositionStart={onCompositionStart}\n        onCompositionEnd={onCompositionEnd}\n        // @ts-ignore\n        onKeyDown={handleKeyDown}\n        // onFocus={handleInputFocus}\n        style={inputStyle}\n      >\n        <Mention\n          trigger=\"@\"\n          markup=\"|repo:__id__|\"\n          data={getDataRepo}\n          renderSuggestion={renderRepoSuggestion}\n          className=\"relative before:bg-bg-shade-hover before:rounded before:absolute before:-top-0.5 before:-bottom-0.5 before:-left-1 before:-right-0.5\"\n          appendSpaceOnAdd\n          displayTransform={repoTransform}\n        />\n        <Mention\n          trigger=\"@\"\n          markup=\"|path:__id__|\"\n          data={getDataPath}\n          renderSuggestion={renderPathSuggestion}\n          className=\"relative before:bg-bg-shade-hover before:rounded before:absolute before:-top-0.5 before:-bottom-0.5 before:-left-1 before:-right-0.5\"\n          appendSpaceOnAdd\n          displayTransform={pathTransform}\n        />\n        <Mention\n          trigger=\"@\"\n          markup=\"|lang:__id__|\"\n          data={getDataLang}\n          appendSpaceOnAdd\n          renderSuggestion={renderLangSuggestion}\n          className=\"relative before:bg-bg-shade-hover before:rounded before:absolute before:-top-0.5 before:-bottom-0.5 before:-left-1 before:-right-0.5\"\n        />\n      </MentionsInput>\n    </div>\n  );\n};\n\nexport default memo(ReactMentionsInput);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/ChatTab/Input/index.tsx",
    "content": "import {\n  Dispatch,\n  memo,\n  SetStateAction,\n  useCallback,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport {\n  ChatMessage,\n  ChatMessageServer,\n  FileTabType,\n  InputEditorContent,\n  InputValueType,\n  ParsedQueryType,\n  TabTypesEnum,\n} from '../../../../types/general';\nimport { getAutocomplete } from '../../../../services/api';\nimport { FileResItem, LangItem, RepoItem } from '../../../../types/api';\nimport useKeyboardNavigation from '../../../../hooks/useKeyboardNavigation';\nimport KeyboardHint from '../../../../components/KeyboardHint';\nimport { focusInput } from '../../../../utils/domUtils';\nimport { MentionOptionType } from '../../../../types/results';\nimport { splitPath, splitUserInputAfterAutocomplete } from '../../../../utils';\nimport { openTabsCache } from '../../../../services/cache';\nimport { CommandBarContext } from '../../../../context/commandBarContext';\nimport { UIContext } from '../../../../context/uiContext';\nimport { PersonIcon } from '../../../../icons';\nimport InputCore from './ProseMirror';\nimport { mapEditorContentToInputValue } from './ProseMirror/utils';\nimport ReactMentionsInput from './ReactMentions';\n\ntype Props = {\n  value?: InputValueType;\n  valueToEdit?: InputValueType;\n  generationInProgress?: boolean;\n  isStoppable?: boolean;\n  onStop?: () => void;\n  setInputValue: Dispatch<SetStateAction<InputValueType>>;\n  selectedLines?: [number, number] | null;\n  setSelectedLines?: (l: [number, number] | null) => void;\n  queryIdToEdit?: string;\n  onMessageEditCancel?: () => void;\n  conversation: ChatMessage[];\n  hideMessagesFrom: number | null;\n  setConversation: Dispatch<SetStateAction<ChatMessage[]>>;\n  setSubmittedQuery: Dispatch<SetStateAction<InputValueType>>;\n  submittedQuery: InputValueType;\n  isInputAtBottom?: boolean;\n  projectId: string;\n};\n\nconst ConversationInput = ({\n  value,\n  valueToEdit,\n  setInputValue,\n  generationInProgress,\n  isStoppable,\n  onStop,\n  queryIdToEdit,\n  onMessageEditCancel,\n  conversation,\n  hideMessagesFrom,\n  setConversation,\n  setSubmittedQuery,\n  submittedQuery,\n  isInputAtBottom,\n  projectId,\n}: Props) => {\n  const { t } = useTranslation();\n  const { isVisible } = useContext(CommandBarContext.General);\n  const { chatInputType } = useContext(UIContext.ChatInputType);\n  const { setIsLeftSidebarFocused } = useContext(UIContext.Focus);\n  const [initialProseValue, setInitialProseValue] = useState<\n    Record<string, any> | null | undefined\n  >({\n    type: 'paragraph',\n    content: value?.parsed\n      .filter((pq) => ['path', 'lang', 'text'].includes(pq.type))\n      .map((pq) =>\n        pq.type === 'text'\n          ? { type: 'text', text: pq.text }\n          : {\n              type: 'mention',\n              attrs: {\n                id: pq.text,\n                display: pq.text,\n                type: pq.type,\n                isFirst: false,\n              },\n            },\n      ),\n  });\n  const [initialMentionsValue, setInitialMentionsValue] = useState(value);\n  const [hasRendered, setHasRendered] = useState(false);\n  const containerRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    setHasRendered(true);\n    setTimeout(focusInput, 500);\n  }, []);\n\n  useEffect(() => {\n    if (hasRendered && valueToEdit) {\n      setInitialProseValue({\n        type: 'paragraph',\n        content: valueToEdit?.parsed\n          .filter((pq) => ['path', 'lang', 'text', 'repo'].includes(pq.type))\n          .map((pq) =>\n            pq.type === 'text'\n              ? { type: 'text', text: pq.text }\n              : {\n                  type: 'mention',\n                  attrs: {\n                    id: pq.text,\n                    display: pq.text,\n                    type: pq.type,\n                    isFirst: false,\n                  },\n                },\n          ),\n      });\n      setInitialMentionsValue(valueToEdit);\n    }\n  }, [valueToEdit]);\n\n  // useEffect(() => {\n  //   if (containerRef.current) {\n  //     setIsInputAtBottom(containerRef.current)\n  //   }\n  // }, [conversation]);\n\n  const onSubmit = useCallback(\n    (value: { parsed: ParsedQueryType[]; plain: string }) => {\n      if (\n        (conversation[conversation.length - 1] as ChatMessageServer)\n          ?.isLoading ||\n        !value.plain.trim()\n      ) {\n        return;\n      }\n      if (hideMessagesFrom !== null) {\n        setConversation((prev) => prev.slice(0, hideMessagesFrom));\n      }\n      setSubmittedQuery(value);\n    },\n    [conversation, submittedQuery, hideMessagesFrom],\n  );\n\n  const onChangeProseMirrorInput = useCallback(\n    (inputState: InputEditorContent[]) => {\n      setInputValue(mapEditorContentToInputValue(inputState));\n      setIsLeftSidebarFocused(false);\n    },\n    [],\n  );\n\n  const onChangeReactMentionsInput = useCallback((newVal: string) => {\n    setInputValue({\n      plain: newVal,\n      parsed: splitUserInputAfterAutocomplete(newVal),\n    });\n    setIsLeftSidebarFocused(false);\n  }, []);\n\n  const onSubmitButtonClicked = useCallback(() => {\n    if (value && onSubmit) {\n      onSubmit(value);\n    }\n  }, [value, onSubmit]);\n\n  const getDataPath = useCallback(\n    async (search: string, callback?: (v: MentionOptionType[]) => void) => {\n      const respPath = await getAutocomplete(\n        projectId,\n        `path:${search}&content=false&page_size=8`,\n      );\n      const fileResults = respPath.data.filter(\n        (d): d is FileResItem => d.kind === 'file_result',\n      );\n      const dirResults = fileResults\n        .filter((d) => d.data.is_dir)\n        .map((d) => ({\n          path: d.data.relative_path.text,\n          repo: d.data.repo_ref,\n        }));\n      const filesResults = openTabsCache.tabs\n        .filter(\n          (t): t is FileTabType =>\n            t.type === TabTypesEnum.FILE &&\n            (!search || t.path?.toLowerCase().includes(search?.toLowerCase())),\n        )\n        .map((t) => ({ path: t.path, repo: t.repoRef }));\n      filesResults.push(\n        ...fileResults\n          .filter(\n            (d) =>\n              !d.data.is_dir &&\n              !filesResults.find(\n                (f) =>\n                  f.path === d.data.relative_path.text &&\n                  f.repo === d.data.repo_ref,\n              ),\n          )\n          .map((d) => ({\n            path: d.data.relative_path.text,\n            repo: d.data.repo_ref,\n          })),\n      );\n      const results: MentionOptionType[] = [];\n      filesResults.forEach((fr, i) => {\n        results.push({\n          id: `${fr.repo}-${fr.path}`,\n          display: fr.path,\n          type: 'file',\n          isFirst: i === 0,\n          hint: splitPath(fr.repo).pop(),\n        });\n      });\n      dirResults.forEach((fr, i) => {\n        results.push({\n          id: `${fr.repo}-${fr.path}`,\n          display: fr.path,\n          type: 'dir',\n          isFirst: i === 0,\n          hint: splitPath(fr.repo).pop(),\n        });\n      });\n      callback?.(results);\n      return results;\n    },\n    [projectId],\n  );\n\n  const getDataLang = useCallback(\n    async (search: string, callback?: (v: MentionOptionType[]) => void) => {\n      const respLang = await getAutocomplete(\n        projectId,\n        `lang:${search}&content=false&page_size=8`,\n      );\n      const langResults = respLang.data\n        .filter((d): d is LangItem => d.kind === 'lang')\n        .map((d) => d.data);\n      const results: MentionOptionType[] = [];\n      langResults.forEach((fr, i) => {\n        results.push({ id: fr, display: fr, type: 'lang', isFirst: i === 0 });\n      });\n      callback?.(results);\n      return results;\n    },\n    [projectId],\n  );\n\n  const getDataRepo = useCallback(\n    async (search: string, callback?: (v: MentionOptionType[]) => void) => {\n      const respRepo = await getAutocomplete(\n        projectId,\n        `repo:${search}&content=false&path=false&file=false&page_size=8`,\n      );\n      const repoResults = respRepo.data\n        .filter((d): d is RepoItem => d.kind === 'repository_result')\n        .map((d) => d.data);\n      const results: MentionOptionType[] = [];\n      repoResults.forEach((rr, i) => {\n        results.push({\n          id: rr.name.text,\n          display: rr.name.text.replace('github.com/', ''),\n          type: 'repo',\n          isFirst: i === 0,\n        });\n      });\n      callback?.(results);\n      return results;\n    },\n    [projectId],\n  );\n\n  const handleKeyEvent = useCallback(\n    (e: KeyboardEvent) => {\n      if (\n        e.key === 'Escape' &&\n        ((onMessageEditCancel && queryIdToEdit) || (isStoppable && onStop))\n      ) {\n        e.preventDefault();\n        e.stopPropagation();\n        onMessageEditCancel?.();\n        onStop?.();\n      }\n    },\n    [onMessageEditCancel, isStoppable, onStop],\n  );\n  useKeyboardNavigation(\n    handleKeyEvent,\n    (!queryIdToEdit && !isStoppable) || isVisible,\n  );\n\n  return (\n    <div\n      className={`flex items-start w-full p-4 gap-4 rounded-tl-md rounded-tr-md ${\n        isInputAtBottom\n          ? 'bg-bg-base border-t border-x border-bg-border shadow-medium'\n          : ''\n      }`}\n      ref={containerRef}\n    >\n      <div className=\"w-7 h-7 rounded-full overflow-hidden select-none bg-bg-shade flex items-center justify-center text-label-base\">\n        <PersonIcon sizeClassName=\"w-6 h-6\" />\n      </div>\n      <div className=\"flex flex-col gap-1 flex-1 items-start\">\n        <p className=\"body-base-b text-label-title select-none\">\n          <Trans>You</Trans>\n        </p>\n        {chatInputType === 'simplified' ? (\n          <ReactMentionsInput\n            value={value}\n            onChange={onChangeReactMentionsInput}\n            onSubmit={onSubmit}\n            placeholder={\n              isStoppable && generationInProgress\n                ? t('Generating answer...')\n                : t('Write a message, @ to mention files, folders or docs...')\n            }\n            isDisabled={isStoppable && generationInProgress}\n            getDataLang={getDataLang}\n            getDataPath={getDataPath}\n            getDataRepo={getDataRepo}\n            initialValue={initialMentionsValue}\n          />\n        ) : generationInProgress ? (\n          <div className=\"select-none text-label-muted body-base cursor-default\">\n            <Trans>Generating answer...</Trans>\n          </div>\n        ) : (\n          <InputCore\n            getDataLang={getDataLang}\n            getDataPath={getDataPath}\n            getDataRepo={getDataRepo}\n            initialValue={initialProseValue}\n            onChange={onChangeProseMirrorInput}\n            onSubmit={onSubmit}\n            placeholder={t(\n              'Write a message, @ to mention files, folders or docs...',\n            )}\n          />\n        )}\n        <div className=\"self-end flex gap-2 items-center select-none\">\n          {isStoppable && (\n            <button\n              className=\"flex gap-1 items-center py-1 pr-1 pl-2 rounded-6 body-mini-b text-label-base bg-bg-base disabled:text-label-muted disabled:bg-bg-base\"\n              onClick={onStop}\n            >\n              <Trans>Stop generating</Trans>\n              <KeyboardHint shortcut=\"Esc\" />\n            </button>\n          )}\n          {!!queryIdToEdit && (\n            <button\n              className=\"flex gap-1 items-center py-1 pr-1 pl-2 rounded-6 body-mini-b text-label-base bg-bg-base disabled:text-label-muted disabled:bg-bg-base\"\n              onClick={onMessageEditCancel}\n            >\n              <Trans>Cancel</Trans>\n              <KeyboardHint shortcut=\"Esc\" />\n            </button>\n          )}\n          <button\n            className=\"flex gap-1 items-center py-1 pr-1 pl-2 rounded-6 body-mini-b text-label-base bg-bg-base disabled:text-label-muted disabled:bg-bg-base\"\n            disabled={!value?.plain || generationInProgress}\n            onClick={onSubmitButtonClicked}\n          >\n            <Trans>Submit</Trans>\n            <KeyboardHint shortcut=\"entr\" />\n          </button>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default memo(ConversationInput);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/ChatTab/Message/LoadingStep.tsx",
    "content": "import { memo, useCallback, useContext } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport FileChip from '../../../../components/Chips/FileChip';\nimport { ChatLoadingStep, TabTypesEnum } from '../../../../types/general';\nimport { TabsContext } from '../../../../context/tabsContext';\n\ntype Props = ChatLoadingStep & {\n  side: 'left' | 'right';\n  repo?: string;\n};\n\nconst LoadingStep = ({ type, path, displayText, side, repo }: Props) => {\n  const { t } = useTranslation();\n  const { openNewTab } = useContext(TabsContext.Handlers);\n\n  const handleClickFile = useCallback(() => {\n    if (type === 'proc' && repo && path) {\n      openNewTab(\n        {\n          type: TabTypesEnum.FILE,\n          repoRef: repo,\n          path,\n        },\n        side === 'left' ? 'right' : 'left',\n      );\n    }\n  }, [path, repo, side]);\n\n  return (\n    <div className=\"flex gap-2 body-s text-label-base items-center\">\n      <span>{type === 'proc' ? t('Reading ') : displayText}</span>\n      {type === 'proc' ? (\n        <FileChip\n          onClick={handleClickFile}\n          fileName={path.split('/').pop() || ''}\n          filePath={path || ''}\n        />\n      ) : null}\n    </div>\n  );\n};\n\nexport default memo(LoadingStep);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/ChatTab/Message/UserParsedQuery/LangChip.tsx",
    "content": "import { getFileExtensionForLang } from '../../../../../utils';\nimport FileIcon from '../../../../../components/FileIcon';\n\ntype Props = {\n  lang: string;\n};\n\nconst LangChip = ({ lang }: Props) => {\n  return (\n    <span\n      className={`inline-flex items-center bg-bg-base rounded-4 overflow-hidden \n                text-label-title align-middle h-6`}\n    >\n      <span className=\"flex gap-1 px-1 py-0.5 items-center code-s\">\n        <FileIcon filename={getFileExtensionForLang(lang, true)} />\n        <span className=\"\">{lang}</span>\n      </span>\n    </span>\n  );\n};\n\nexport default LangChip;\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/ChatTab/Message/UserParsedQuery/PathChip.tsx",
    "content": "import { useMemo } from 'react';\nimport { FolderIcon } from '../../../../../icons';\nimport FileIcon from '../../../../../components/FileIcon';\nimport { splitPath } from '../../../../../utils';\n\ntype Props = {\n  path: string;\n};\n\nconst PathChip = ({ path }: Props) => {\n  const isFolder = useMemo(() => path.endsWith('/'), [path]);\n  return (\n    <span\n      className={`inline-flex items-center bg-bg-base rounded-4 overflow-hidden \n                text-label-title align-middle h-6`}\n    >\n      <span className=\"flex gap-1 px-1 py-0.5 items-center code-s\">\n        {isFolder ? (\n          <FolderIcon raw sizeClassName=\"w-3.5 h-3.5\" />\n        ) : (\n          <FileIcon filename={path} />\n        )}\n        <span className=\"\">\n          {isFolder ? path.replace(/\\/$/, '') : splitPath(path).pop()}\n        </span>\n      </span>\n    </span>\n  );\n};\n\nexport default PathChip;\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/ChatTab/Message/UserParsedQuery/RepoChip.tsx",
    "content": "import { RepositoryIcon } from '../../../../../icons';\nimport { splitPath } from '../../../../../utils';\n\ntype Props = {\n  name: string;\n};\n\nconst RepoChip = ({ name }: Props) => {\n  return (\n    <span\n      className={`inline-flex items-center bg-bg-base rounded-4 overflow-hidden \n                text-label-title align-middle h-6`}\n    >\n      <span className=\"flex gap-1 px-1 py-0.5 items-center code-s\">\n        <RepositoryIcon sizeClassName=\"w-4 h-4\" />\n        <span className=\"\">{splitPath(name).pop()}</span>\n      </span>\n    </span>\n  );\n};\n\nexport default RepoChip;\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/ChatTab/Message/UserParsedQuery/index.tsx",
    "content": "import { memo } from 'react';\nimport {\n  ParsedQueryType,\n  ParsedQueryTypeEnum,\n} from '../../../../../types/general';\nimport PathChip from './PathChip';\nimport LangChip from './LangChip';\nimport RepoChip from './RepoChip';\n\ntype Props = {\n  textQuery: string;\n  parsedQuery?: ParsedQueryType[];\n};\n\nconst UserParsedQuery = ({ textQuery, parsedQuery }: Props) => {\n  return (\n    <span className=\"\">\n      {parsedQuery\n        ? parsedQuery.map((p, i) =>\n            p.type === ParsedQueryTypeEnum.TEXT ? (\n              p.text\n            ) : p.type === ParsedQueryTypeEnum.PATH ? (\n              <PathChip path={p.text} key={i} />\n            ) : p.type === ParsedQueryTypeEnum.LANG ? (\n              <LangChip lang={p.text} key={i} />\n            ) : p.type === ParsedQueryTypeEnum.REPO ? (\n              <RepoChip name={p.text} key={i} />\n            ) : null,\n          )\n        : textQuery}\n    </span>\n  );\n};\n\nexport default memo(UserParsedQuery);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/ChatTab/Message/index.tsx",
    "content": "import { memo, useCallback, useContext, useEffect, useState } from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { format } from 'date-fns';\nimport {\n  ChatLoadingStep,\n  ChatMessageAuthor,\n  ParsedQueryType,\n} from '../../../../types/general';\nimport MarkdownWithCode from '../../../../components/MarkdownWithCode';\nimport Button from '../../../../components/Button';\nimport {\n  CheckListIcon,\n  LikeIcon,\n  PencilIcon,\n  PersonIcon,\n  UnlikeIcon,\n  WarningSignIcon,\n} from '../../../../icons';\nimport { getDateFnsLocale } from '../../../../utils';\nimport SpinLoaderContainer from '../../../../components/Loaders/SpinnerLoader';\nimport {\n  getPlainFromStorage,\n  LOADING_STEPS_SHOWN_KEY,\n  savePlainToStorage,\n} from '../../../../services/storage';\nimport { LocaleContext } from '../../../../context/localeContext';\nimport { upvoteAnswer } from '../../../../services/api';\nimport CopyButton from '../../../../components/MarkdownWithCode/CopyButton';\nimport UserParsedQuery from './UserParsedQuery';\nimport LoadingStep from './LoadingStep';\n\ntype Props = {\n  author: ChatMessageAuthor;\n  text: string;\n  parsedQuery?: ParsedQueryType[];\n  error?: string;\n  threadId: string;\n  queryId: string;\n  responseTimestamp: string | null;\n  showInlineFeedback: boolean;\n  isLoading?: boolean;\n  loadingSteps?: ChatLoadingStep[];\n  i: number;\n  onMessageEdit: (queryId: string, i: number) => void;\n  singleFileExplanation?: boolean;\n  side: 'left' | 'right';\n  projectId: string;\n};\n\nconst ConversationMessage = ({\n  author,\n  text,\n  parsedQuery,\n  i,\n  queryId,\n  onMessageEdit,\n  singleFileExplanation,\n  threadId,\n  isLoading,\n  loadingSteps,\n  showInlineFeedback,\n  responseTimestamp,\n  error,\n  side,\n  projectId,\n}: Props) => {\n  const { t } = useTranslation();\n  const { locale } = useContext(LocaleContext);\n  const [isUpvote, setIsUpvote] = useState(false);\n  const [isDownvote, setIsDownvote] = useState(false);\n  const [isLoadingStepsShown, setLoadingStepsShown] = useState(\n    getPlainFromStorage(LOADING_STEPS_SHOWN_KEY)\n      ? !!Number(getPlainFromStorage(LOADING_STEPS_SHOWN_KEY))\n      : true,\n  );\n\n  useEffect(() => {\n    savePlainToStorage(\n      LOADING_STEPS_SHOWN_KEY,\n      isLoadingStepsShown ? '1' : '0',\n    );\n  }, [isLoadingStepsShown]);\n\n  const toggleStepsShown = useCallback(() => {\n    setLoadingStepsShown((prev) => !prev);\n  }, []);\n\n  const handleEdit = useCallback(() => {\n    onMessageEdit(queryId, i);\n  }, [onMessageEdit, queryId, i]);\n\n  const handleUpvote = useCallback(() => {\n    setIsUpvote(true);\n    setIsDownvote(false);\n    return upvoteAnswer(projectId, threadId, queryId, { type: 'positive' });\n  }, [showInlineFeedback, threadId, queryId, projectId]);\n\n  const handleDownvote = useCallback(() => {\n    setIsUpvote(false);\n    setIsDownvote(true);\n    return upvoteAnswer(projectId, threadId, queryId, {\n      type: 'negative',\n      feedback: '',\n    });\n  }, [showInlineFeedback, threadId, queryId, projectId]);\n\n  return (\n    <div\n      className={`flex items-start gap-5 rounded-md p-4 relative group ${\n        error ? '' : 'hover:bg-bg-sub-hover'\n      }`}\n    >\n      {error ? (\n        <div className=\"flex items-center w-full gap-4 select-none\">\n          <div className=\"w-7 h-7 flex items-center justify-center rounded-full bg-red-subtle text-red flex-shrink-0\">\n            <WarningSignIcon sizeClassName=\"w-3.5 h-3.5\" />\n          </div>\n          <p className=\"text-red body-s\">{error}</p>\n        </div>\n      ) : (\n        <>\n          <div className=\"relative\">\n            <div className=\"flex w-7 h-7 items-center justify-center rounded-full flex-shrink-0 bg-brand-default-subtitle overflow-hidden text-label-base\">\n              {author === ChatMessageAuthor.User ? (\n                <PersonIcon sizeClassName=\"w-6 h-6\" />\n              ) : isLoading ? (\n                <SpinLoaderContainer\n                  sizeClassName=\"w-4.5 h-4.5\"\n                  colorClassName=\"text-brand-default\"\n                />\n              ) : (\n                <img className=\"bloop-head-img w-7 h-7\" alt=\"bloop\" />\n              )}\n            </div>\n            {(isUpvote || isDownvote) && (\n              <div\n                className={`absolute -right-1.5 -bottom-2 rotate-[-15deg] ${\n                  isUpvote ? 'text-brand-default' : 'text-red'\n                }`}\n              >\n                {isUpvote ? (\n                  <LikeIcon sizeClassName=\"w-4 h-4\" className=\"icon-stroked\" />\n                ) : (\n                  <UnlikeIcon\n                    sizeClassName=\"w-4 h-4\"\n                    className=\"icon-stroked\"\n                  />\n                )}\n              </div>\n            )}\n          </div>\n          <div className=\"flex flex-col gap-1 flex-1 overflow-auto\">\n            <div className=\"body-base-b text-label-title select-none flex items-center gap-1.5\">\n              {author === ChatMessageAuthor.User ? <Trans>You</Trans> : 'bloop'}\n              {author === ChatMessageAuthor.Server && (\n                <p className=\"body-mini text-label-muted\">\n                  ·{' '}\n                  {isLoading ? (\n                    <Trans>Streaming response...</Trans>\n                  ) : responseTimestamp ? (\n                    format(\n                      new Date(responseTimestamp),\n                      'hh:mm aa',\n                      getDateFnsLocale(locale),\n                    )\n                  ) : null}\n                </p>\n              )}\n              {author === ChatMessageAuthor.Server && (\n                <Button\n                  size=\"mini\"\n                  variant={isLoadingStepsShown ? 'tertiary-active' : 'tertiary'}\n                  onlyIcon\n                  title={t(\n                    `${isLoadingStepsShown ? 'Hide' : 'Show'} search steps`,\n                  )}\n                  onClick={toggleStepsShown}\n                >\n                  <CheckListIcon\n                    className=\"text-green\"\n                    sizeClassName=\"w-3.5 h-3.5\"\n                  />\n                </Button>\n              )}\n            </div>\n            {!!loadingSteps?.length && (\n              <div\n                className={`${\n                  isLoadingStepsShown ? 'my-2' : ''\n                } flex flex-col gap-3 overflow-hidden transition-all duration-200 ease-linear`}\n                style={{\n                  maxHeight: isLoadingStepsShown ? loadingSteps.length * 36 : 0,\n                }}\n              >\n                {loadingSteps.map((s, i) => (\n                  <LoadingStep {...s} key={i} side={side} />\n                ))}\n              </div>\n            )}\n            <div className=\"text-label-title body-base code-studio-md break-word overflow-auto\">\n              {author === ChatMessageAuthor.Server ? (\n                <MarkdownWithCode\n                  markdown={text!}\n                  side={side}\n                  singleFileExplanation={singleFileExplanation}\n                />\n              ) : (\n                <UserParsedQuery textQuery={text!} parsedQuery={parsedQuery} />\n              )}\n            </div>\n          </div>\n          <div className=\"opacity-0 group-hover:opacity-100 flex items-center gap-1 p-1 absolute -top-4 right-4 rounded-6 border border-bg-border bg-bg-base shadow-medium\">\n            {author === ChatMessageAuthor.User ? (\n              <Button\n                variant=\"tertiary\"\n                size=\"mini\"\n                onlyIcon\n                title={t('Edit')}\n                onClick={handleEdit}\n              >\n                <PencilIcon sizeClassName=\"w-3.5 h-3.5\" />\n              </Button>\n            ) : (\n              !isLoading && (\n                <>\n                  <Button\n                    variant={isUpvote ? 'tertiary-active' : 'tertiary'}\n                    size=\"mini\"\n                    onlyIcon\n                    title={t('Upvote')}\n                    onClick={handleUpvote}\n                  >\n                    <LikeIcon sizeClassName=\"w-3.5 h-3.5\" />\n                  </Button>\n                  <Button\n                    variant={isDownvote ? 'tertiary-active' : 'tertiary'}\n                    size=\"mini\"\n                    onlyIcon\n                    title={t('Downvote')}\n                    onClick={handleDownvote}\n                  >\n                    <UnlikeIcon sizeClassName=\"w-3.5 h-3.5\" />\n                  </Button>\n                </>\n              )\n            )}\n            <CopyButton code={text} isInHeader btnVariant=\"tertiary\" />\n          </div>\n        </>\n      )}\n    </div>\n  );\n};\n\nexport default memo(ConversationMessage);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/ChatTab/ScrollableContent.tsx",
    "content": "import { Fragment, memo, useContext, useEffect } from 'react';\nimport { Trans } from 'react-i18next';\nimport { ChatMessageAuthor, ChatMessageServer } from '../../../types/general';\nimport { WarningSignIcon } from '../../../icons';\nimport { ChatContext } from '../../../context/chatsContext';\nimport FunctionContext from '../../../components/ScrollToBottom/FunctionContext';\nimport StarterMessage from './StarterMessage';\nimport Message from './Message';\n\ntype Props = {\n  chatData: ChatContext;\n  side: 'left' | 'right';\n  projectId: string;\n};\n\nconst ScrollableContent = ({ chatData, side, projectId }: Props) => {\n  const { scrollToBottom } = useContext(FunctionContext);\n\n  useEffect(() => {\n    if (chatData.submittedQuery.plain) {\n      scrollToBottom({ behavior: 'smooth' });\n    }\n  }, [chatData.submittedQuery]);\n\n  return (\n    <Fragment>\n      <StarterMessage\n        isEmptyConversation\n        setInputValueImperatively={chatData.setInputValueImperatively}\n      />\n      {(chatData.hideMessagesFrom === null\n        ? chatData.conversation\n        : chatData.conversation.slice(0, chatData.hideMessagesFrom + 1)\n      ).map((m, i) => (\n        <Message\n          key={i}\n          i={i}\n          side={side}\n          projectId={projectId}\n          isLoading={m.author === ChatMessageAuthor.Server && m.isLoading}\n          loadingSteps={\n            m.author === ChatMessageAuthor.Server ? m.loadingSteps : []\n          }\n          author={m.author}\n          text={m.text || ''}\n          parsedQuery={\n            m.author === ChatMessageAuthor.Server ? undefined : m.parsedQuery\n          }\n          error={m.author === ChatMessageAuthor.Server ? m.error : ''}\n          showInlineFeedback={\n            m.author === ChatMessageAuthor.Server &&\n            !m.isLoading &&\n            !chatData.isLoading &&\n            i === chatData.conversation.length - 1 &&\n            !m.isFromHistory\n          }\n          threadId={chatData.threadId}\n          queryId={\n            m.author === ChatMessageAuthor.Server\n              ? m.queryId\n              : (chatData.conversation[i - 1] as ChatMessageServer)?.queryId ||\n                '00000000-0000-0000-0000-000000000000'\n          }\n          onMessageEdit={chatData.onMessageEdit}\n          responseTimestamp={\n            m.author === ChatMessageAuthor.Server ? m.responseTimestamp : null\n          }\n          singleFileExplanation={\n            m.author === ChatMessageAuthor.Server && !!m.explainedFile\n          }\n        />\n      ))}\n      {chatData.hideMessagesFrom !== null && (\n        <div className=\"flex items-center w-full p-4 gap-4 select-none\">\n          <div className=\"w-7 h-7 flex items-center justify-center rounded-full bg-yellow-subtle text-yellow\">\n            <WarningSignIcon sizeClassName=\"w-3.5 h-3.5\" />\n          </div>\n          <p className=\"text-yellow body-s\">\n            <Trans>\n              Editing previously submitted questions will discard all answers\n              and questions following it\n            </Trans>\n          </p>\n        </div>\n      )}\n    </Fragment>\n  );\n};\n\nexport default memo(ScrollableContent);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/ChatTab/StarterMessage.tsx",
    "content": "import { memo, useCallback, useContext, useEffect, useState } from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { ChatBubblesIcon } from '../../../icons';\nimport { TutorialQuestionType } from '../../../types/api';\nimport { getTutorialQuestions } from '../../../services/api';\nimport { ProjectContext } from '../../../context/projectContext';\n\ntype Props = {\n  isEmptyConversation: boolean;\n  setInputValueImperatively: (v: string) => void;\n};\n\nconst StarterMessage = ({\n  isEmptyConversation,\n  setInputValueImperatively,\n}: Props) => {\n  useTranslation();\n  const [tutorials, setTutorials] = useState<TutorialQuestionType[]>([]);\n  const { project } = useContext(ProjectContext.Current);\n\n  const getDiverseTutorials = useCallback(async () => {\n    if (project?.repos.length) {\n      const tutorials = [];\n      let tutorialsPerRepo = Math.floor(10 / project.repos.length);\n      let remainingTutorials = 10;\n\n      for (const repo of project.repos) {\n        const repoTutorials = await getTutorialQuestions(repo.repo.ref);\n\n        const tutorialsToAdd = Math.min(\n          tutorialsPerRepo,\n          repoTutorials.questions.length,\n          remainingTutorials,\n        );\n\n        tutorials.push(...repoTutorials.questions.slice(0, tutorialsToAdd));\n\n        remainingTutorials -= tutorialsToAdd;\n\n        if (remainingTutorials <= 0) {\n          break;\n        }\n      }\n\n      setTutorials(tutorials);\n    }\n  }, [project?.repos]);\n\n  useEffect(() => {\n    getDiverseTutorials();\n  }, [getDiverseTutorials]);\n\n  return (\n    <div className=\"flex items-start gap-5 rounded-md p-4\">\n      <div className=\"flex w-7 h-7 items-center justify-center rounded-full bg-brand-default-subtitle\">\n        <img className=\"bloop-head-img w-7 h-7\" alt=\"bloop\" />\n      </div>\n      <div className=\"flex flex-col gap-1 flex-1\">\n        <p className=\"body-base-b text-label-title select-none\">bloop</p>\n        <p className=\"text-label-base body-base\">\n          <Trans>\n            Hi, I am bloop! In{' '}\n            <span className=\"body-base-b inline-flex items-center gap-1 relative top-0.5\">\n              <ChatBubblesIcon\n                sizeClassName=\"w-4 h-4\"\n                className=\"text-brand-default\"\n              />\n              Chat mode\n            </span>{' '}\n            I can answer any questions related to any of your repositories.\n          </Trans>\n        </p>\n        {isEmptyConversation && !!tutorials.length && (\n          <p className=\"text-label-base body-base mt-4\">\n            <Trans>\n              Below are a few suggestions you can ask me to get started:\n            </Trans>\n          </p>\n        )}\n        {isEmptyConversation && !!tutorials.length && (\n          <div className=\"pt-2 flex items-start gap-2 flex-wrap\">\n            {tutorials.map((t, i) => (\n              <button\n                key={i}\n                className=\"h-7 rounded-full border border-bg-border px-2.5 body-s text-label-title\"\n                onClick={() => {\n                  setInputValueImperatively(t.question);\n                }}\n              >\n                {t.tag}\n              </button>\n            ))}\n          </div>\n        )}\n      </div>\n    </div>\n  );\n};\n\nexport default memo(StarterMessage);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/ChatTab/index.tsx",
    "content": "import React, {\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport Button from '../../../components/Button';\nimport {\n  ChatBubblesIcon,\n  MoreHorizontalIcon,\n  SplitViewIcon,\n} from '../../../icons';\nimport Dropdown from '../../../components/Dropdown';\nimport { checkEventKeys } from '../../../utils/keyboardUtils';\nimport useKeyboardNavigation from '../../../hooks/useKeyboardNavigation';\nimport { TabsContext } from '../../../context/tabsContext';\nimport { ChatTabType } from '../../../types/general';\nimport { ProjectContext } from '../../../context/projectContext';\nimport { CommandBarContext } from '../../../context/commandBarContext';\nimport { UIContext } from '../../../context/uiContext';\nimport { openInSplitViewShortcut } from '../../../consts/shortcuts';\nimport Conversation from './Conversation';\nimport ActionsDropdown from './ActionsDropdown';\n\ntype Props = ChatTabType & {\n  noBorder?: boolean;\n  side: 'left' | 'right';\n  tabKey: string;\n  handleMoveToAnotherSide: () => void;\n};\n\nconst ChatTab = ({\n  noBorder,\n  side,\n  title,\n  conversationId,\n  tabKey,\n  handleMoveToAnotherSide,\n}: Props) => {\n  const { t } = useTranslation();\n  const { focusedPanel } = useContext(TabsContext.FocusedPanel);\n  const { closeTab } = useContext(TabsContext.Handlers);\n  const { isLeftSidebarFocused } = useContext(UIContext.Focus);\n  const { setFocusedTabItems } = useContext(CommandBarContext.Handlers);\n  const { project, refreshCurrentProjectConversations } = useContext(\n    ProjectContext.Current,\n  );\n\n  const dropdownComponentProps = useMemo(() => {\n    return {\n      handleMoveToAnotherSide,\n      conversationId,\n      projectId: project?.id,\n      tabKey,\n      closeTab,\n      refreshCurrentProjectConversations,\n      side,\n    };\n  }, [\n    handleMoveToAnotherSide,\n    conversationId,\n    closeTab,\n    project?.id,\n    tabKey,\n    refreshCurrentProjectConversations,\n    side,\n  ]);\n\n  const handleKeyEvent = useCallback(\n    (e: KeyboardEvent) => {\n      if (checkEventKeys(e, openInSplitViewShortcut)) {\n        handleMoveToAnotherSide();\n      }\n    },\n    [handleMoveToAnotherSide],\n  );\n  useKeyboardNavigation(\n    handleKeyEvent,\n    focusedPanel !== side || isLeftSidebarFocused,\n  );\n\n  useEffect(() => {\n    if (focusedPanel === side) {\n      setFocusedTabItems([\n        {\n          label: t('Open in split view'),\n          Icon: SplitViewIcon,\n          id: 'split_view',\n          key: 'split_view',\n          onClick: handleMoveToAnotherSide,\n          closeOnClick: true,\n          shortcut: openInSplitViewShortcut,\n          footerHint: '',\n          footerBtns: [{ label: t('Move'), shortcut: ['entr'] }],\n        },\n      ]);\n    }\n  }, [focusedPanel, side, handleMoveToAnotherSide]);\n\n  return (\n    <div\n      className={`flex flex-col flex-1 h-full overflow-auto ${\n        noBorder ? '' : 'border-l border-bg-border'\n      }`}\n    >\n      <div className=\"w-full h-10 px-4 flex justify-between items-center flex-shrink-0 border-b border-bg-border bg-bg-sub\">\n        <div className=\"flex items-center gap-3 body-s text-label-title ellipsis\">\n          <ChatBubblesIcon\n            sizeClassName=\"w-4 h-4\"\n            className=\"text-brand-default\"\n          />\n          {title || t('New conversation')}\n        </div>\n        {focusedPanel === side && (\n          <Dropdown\n            DropdownComponent={ActionsDropdown}\n            dropdownComponentProps={dropdownComponentProps}\n            appendTo={document.body}\n            dropdownPlacement=\"bottom-end\"\n          >\n            <Button\n              variant=\"tertiary\"\n              size=\"mini\"\n              onlyIcon\n              title={t('More actions')}\n            >\n              <MoreHorizontalIcon sizeClassName=\"w-3.5 h-3.5\" />\n            </Button>\n          </Dropdown>\n        )}\n      </div>\n      <div className=\"flex-1 flex flex-col max-w-full px-4 overflow-auto\">\n        <Conversation side={side} tabKey={tabKey} />\n      </div>\n    </div>\n  );\n};\n\nexport default memo(ChatTab);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/DocTab/ActionsDropdown.tsx",
    "content": "import { memo } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport DropdownSection from '../../../components/Dropdown/Section';\nimport SectionItem from '../../../components/Dropdown/Section/SectionItem';\nimport {\n  SplitViewIcon,\n  StudioCloseSignIcon,\n  StudioPlusSignIcon,\n} from '../../../icons';\nimport {\n  addToStudioShortcut,\n  openInSplitViewShortcut,\n  removeFromStudioShortcut,\n} from '../../../consts/shortcuts';\n\ntype Props = {\n  handleMoveToAnotherSide: () => void;\n  handleAddToStudio: () => void;\n  handleRemoveFromStudio: () => void;\n  isDocInContext: boolean;\n};\n\nconst ActionsDropdown = ({\n  handleMoveToAnotherSide,\n  handleAddToStudio,\n  handleRemoveFromStudio,\n  isDocInContext,\n}: Props) => {\n  const { t } = useTranslation();\n\n  return (\n    <div>\n      <DropdownSection borderBottom>\n        {isDocInContext ? (\n          <SectionItem\n            index={'del-from-studio'}\n            label={t('Remove from studio')}\n            onClick={handleRemoveFromStudio}\n            shortcut={removeFromStudioShortcut}\n            icon={<StudioCloseSignIcon sizeClassName=\"w-4 h-4\" />}\n          />\n        ) : (\n          <SectionItem\n            index={'add-to-studio'}\n            label={t('Add to studio')}\n            onClick={handleAddToStudio}\n            shortcut={addToStudioShortcut}\n            icon={<StudioPlusSignIcon sizeClassName=\"w-4 h-4\" />}\n          />\n        )}\n      </DropdownSection>\n      <DropdownSection>\n        <SectionItem\n          index={'split-view'}\n          label={t('Open in split view')}\n          shortcut={openInSplitViewShortcut}\n          onClick={handleMoveToAnotherSide}\n          icon={<SplitViewIcon sizeClassName=\"w-4 h-4\" />}\n        />\n      </DropdownSection>\n    </div>\n  );\n};\n\nexport default memo(ActionsDropdown);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/DocTab/DocSection.tsx",
    "content": "import React, { Dispatch, memo, SetStateAction, useCallback } from 'react';\nimport { Trans } from 'react-i18next';\nimport Button from '../../../components/Button';\nimport { DocSectionType } from '../../../types/api';\nimport RenderedSection from './RenderedSection';\n\ntype Props = DocSectionType & {\n  isSelected: boolean;\n  isNothingSelected: boolean;\n  isEditingSelection: boolean;\n  setSelectedSections: Dispatch<SetStateAction<string[]>>;\n};\n\nconst DocSection = ({\n  text,\n  isSelected,\n  setSelectedSections,\n  point_id,\n  isNothingSelected,\n  doc_source,\n  isEditingSelection,\n}: Props) => {\n  const setSelected = useCallback(\n    (b: boolean) => {\n      setSelectedSections((prev) => {\n        if (b) {\n          return [...prev, point_id];\n        }\n        return prev.filter((r) => r !== point_id);\n      });\n    },\n    [point_id, setSelectedSections],\n  );\n\n  const handleClick = useCallback(() => {\n    if (isEditingSelection) {\n      setSelected(!isSelected);\n    }\n  }, [isSelected, isEditingSelection]);\n  return (\n    <div\n      data-section-id={point_id}\n      className={`body-s relative group ${\n        isSelected\n          ? 'bg-bg-selected opacity-100'\n          : isEditingSelection\n          ? `hover:bg-bg-sub-hover ${\n              isNothingSelected ? '' : 'opacity-50 hover:opacity-100'\n            }`\n          : ''\n      } ${\n        isEditingSelection ? 'cursor-pointer' : ''\n      } pl-8 pr-4 py-3 transition-opacity duration-150 ease-in-out`}\n      onClick={handleClick}\n    >\n      {isEditingSelection && (\n        <div\n          className={`absolute top-2 right-2 z-10 ${\n            isSelected ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'\n          } transition-opacity duration-150 ease-in-out`}\n        >\n          <Button size=\"mini\" variant=\"secondary\" onClick={handleClick}>\n            {!isSelected ? (\n              <Trans>Select section</Trans>\n            ) : (\n              <Trans>Clear section</Trans>\n            )}\n          </Button>\n        </div>\n      )}\n      <RenderedSection\n        text={text}\n        baseUrl={doc_source}\n        isEditingSelection={isEditingSelection}\n      />\n    </div>\n  );\n};\n\nexport default memo(DocSection);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/DocTab/RenderedSection.tsx",
    "content": "import React, { memo, useCallback, useContext, useMemo } from 'react';\nimport { Remarkable } from 'remarkable';\nimport { highlightCode } from '../../../utils/prism';\nimport { DeviceContext } from '../../../context/deviceContext';\n\nconst md = new Remarkable({\n  html: false,\n  highlight(str: string, lang: string): string {\n    try {\n      return highlightCode(str, lang);\n    } catch (err) {\n      console.log(err);\n      return '';\n    }\n  },\n  linkTarget: '__blank',\n});\n\ntype Props = {\n  text: string;\n  baseUrl: string;\n  isEditingSelection: boolean;\n};\n\nconst RenderedSection = ({ text, baseUrl, isEditingSelection }: Props) => {\n  const { openLink } = useContext(DeviceContext);\n\n  const markdown = useMemo(() => md.render(text), [text]);\n\n  const handleClick = useCallback(\n    (e: React.MouseEvent) => {\n      // @ts-ignore\n      const href = e.target.getAttribute('href');\n      if (href) {\n        e.preventDefault();\n        e.stopPropagation();\n        openLink(\n          href.startsWith('http://') || href.startsWith('https://')\n            ? href\n            : new URL(href, baseUrl).href,\n        );\n      }\n    },\n    [openLink, baseUrl],\n  );\n\n  return (\n    <div\n      className={`body-s doc-section ${\n        isEditingSelection ? 'cursor-pointer' : ''\n      }`}\n    >\n      <div\n        dangerouslySetInnerHTML={{ __html: markdown }}\n        onClick={handleClick}\n      />\n    </div>\n  );\n};\n\nexport default memo(RenderedSection);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/DocTab/index.tsx",
    "content": "import React, {\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { CommandBarStepEnum, DocTabType } from '../../../types/general';\nimport {\n  MagazineIcon,\n  MoreHorizontalIcon,\n  SplitViewIcon,\n  StudioCloseSignIcon,\n  StudioPlusSignIcon,\n} from '../../../icons';\nimport Dropdown from '../../../components/Dropdown';\nimport Button from '../../../components/Button';\nimport { TabsContext } from '../../../context/tabsContext';\nimport { checkEventKeys } from '../../../utils/keyboardUtils';\nimport useKeyboardNavigation from '../../../hooks/useKeyboardNavigation';\nimport { UIContext } from '../../../context/uiContext';\nimport { CommandBarContext } from '../../../context/commandBarContext';\nimport {\n  addToStudioShortcut,\n  escapeShortcut,\n  openInSplitViewShortcut,\n  removeFromStudioShortcut,\n  saveShortcut,\n  selectLinesShortcut,\n} from '../../../consts/shortcuts';\nimport {\n  getCodeStudio,\n  getDocSections,\n  getDocTokenCount,\n  getIndexedPages,\n  patchCodeStudio,\n} from '../../../services/api';\nimport {\n  CodeStudioType,\n  DocPageType,\n  DocSectionType,\n} from '../../../types/api';\nimport { findElementInCurrentTab } from '../../../utils/domUtils';\nimport { ProjectContext } from '../../../context/projectContext';\nimport Badge from '../../../components/Badge';\nimport { humanNumber } from '../../../utils';\nimport ActionsDropdown from './ActionsDropdown';\nimport DocSection from './DocSection';\n\ntype Props = DocTabType & {\n  noBorder?: boolean;\n  side: 'left' | 'right';\n  tabKey: string;\n  handleMoveToAnotherSide: () => void;\n};\n\nconst DocTab = ({\n  side,\n  tabKey,\n  handleMoveToAnotherSide,\n  docId,\n  title,\n  favicon,\n  noBorder,\n  relativeUrl,\n  studioId,\n  initialSections,\n  isDocInContext,\n}: Props) => {\n  const { t } = useTranslation();\n  const { focusedPanel } = useContext(TabsContext.FocusedPanel);\n  const { updateTabProperty } = useContext(TabsContext.Handlers);\n  const { isLeftSidebarFocused } = useContext(UIContext.Focus);\n  const { setFocusedTabItems, setChosenStep, setIsVisible } = useContext(\n    CommandBarContext.Handlers,\n  );\n  const { isVisible: isCommandBarVisible } = useContext(\n    CommandBarContext.General,\n  );\n  const { project, refreshCurrentProjectStudios } = useContext(\n    ProjectContext.Current,\n  );\n  const [fullDoc, setFullDoc] = useState<DocPageType | null>(null);\n  const [studio, setStudio] = useState<CodeStudioType | null>(null);\n  const [sections, setSections] = useState<DocSectionType[]>([]);\n  const [selectedSections, setSelectedSections] = useState(\n    initialSections || [],\n  );\n  const [tokenCount, setTokenCount] = useState(0);\n  const [isEditingSelection, setIsEditingSelection] = useState(false);\n\n  const refreshStudio = useCallback(() => {\n    if (studioId && project?.id) {\n      getCodeStudio(project.id, studioId).then(setStudio);\n    } else {\n      setStudio(null);\n    }\n  }, [studioId, project?.id]);\n\n  useEffect(() => {\n    refreshStudio();\n  }, [refreshStudio]);\n\n  useEffect(() => {\n    setSelectedSections(initialSections || []);\n  }, [initialSections]);\n\n  useEffect(() => {\n    getIndexedPages(docId).then((resp) => {\n      const doc = resp.find((p) => p.relative_url === relativeUrl);\n      if (doc) {\n        setFullDoc(doc);\n      }\n    });\n    getDocSections(docId, relativeUrl).then((resp) => {\n      setSections(resp);\n    });\n  }, [docId, relativeUrl]);\n\n  useEffect(() => {\n    if (project?.id) {\n      getDocTokenCount(project.id, docId, relativeUrl, selectedSections).then(\n        setTokenCount,\n      );\n    }\n  }, [selectedSections, relativeUrl, docId, project?.id]);\n\n  useEffect(() => {\n    if (initialSections?.length && sections.length) {\n      const firstSelectedSection =\n        initialSections?.length === 1\n          ? initialSections[0]\n          : initialSections?.length\n          ? sections.find((s) => initialSections?.includes(s.point_id))\n              ?.point_id\n          : '';\n      findElementInCurrentTab(\n        `[data-active=\"true\"] [data-section-id=\"${firstSelectedSection}\"]`,\n      )?.scrollIntoView();\n    }\n  }, [sections.length, initialSections]);\n\n  const handleAddToStudio = useCallback(() => {\n    setChosenStep({\n      id: CommandBarStepEnum.ADD_TO_STUDIO,\n      data: { docId, relativeUrl, favicon, title },\n    });\n    setIsVisible(true);\n  }, [docId, relativeUrl, favicon, title]);\n\n  const handleRemoveFromStudio = useCallback(async () => {\n    if (project?.id && studioId && studio) {\n      const patchedDoc = studio?.doc_context.find(\n        (d) => d.doc_id === docId && d.relative_url === relativeUrl,\n      );\n      if (patchedDoc) {\n        await patchCodeStudio(project.id, studioId, {\n          doc_context: studio?.doc_context.filter(\n            (d) => d.doc_id !== docId || d.relative_url !== relativeUrl,\n          ),\n        });\n        refreshCurrentProjectStudios();\n        refreshStudio();\n        setIsEditingSelection(false);\n        updateTabProperty<DocTabType, 'isDocInContext'>(\n          tabKey,\n          'isDocInContext',\n          false,\n          side,\n        );\n        updateTabProperty<DocTabType, 'initialSections'>(\n          tabKey,\n          'initialSections',\n          undefined,\n          side,\n        );\n        updateTabProperty<DocTabType, 'studioId'>(\n          tabKey,\n          'studioId',\n          undefined,\n          side,\n        );\n        setStudio(null);\n        setSelectedSections([]);\n      }\n    }\n  }, [docId, relativeUrl, project?.id, studioId, studio]);\n\n  const dropdownComponentProps = useMemo(() => {\n    return {\n      handleMoveToAnotherSide,\n      handleAddToStudio,\n      handleRemoveFromStudio,\n      isDocInContext,\n    };\n  }, [\n    handleMoveToAnotherSide,\n    handleAddToStudio,\n    handleRemoveFromStudio,\n    isDocInContext,\n  ]);\n\n  useEffect(() => {\n    if (focusedPanel === side) {\n      setFocusedTabItems([\n        {\n          label: t('Open in split view'),\n          Icon: SplitViewIcon,\n          id: 'split_view',\n          key: 'split_view',\n          onClick: handleMoveToAnotherSide,\n          closeOnClick: true,\n          shortcut: openInSplitViewShortcut,\n          footerHint: '',\n          footerBtns: [{ label: t('Move'), shortcut: ['entr'] }],\n        },\n        ...(studioId\n          ? [\n              {\n                label: t('Remove from studio'),\n                Icon: StudioCloseSignIcon,\n                id: 'doc_from_studio',\n                key: 'doc_from_studio',\n                onClick: handleRemoveFromStudio,\n                shortcut: removeFromStudioShortcut,\n                footerHint: t('Remove page from code studio context'),\n                footerBtns: [{ label: t('Remove'), shortcut: ['entr'] }],\n              },\n            ]\n          : [\n              {\n                label: t('Add to studio'),\n                Icon: StudioPlusSignIcon,\n                id: 'doc_to_studio',\n                key: 'doc_to_studio',\n                onClick: handleAddToStudio,\n                shortcut: addToStudioShortcut,\n                footerHint: t('Add file to code studio context'),\n                footerBtns: [{ label: t('Add'), shortcut: ['entr'] }],\n              },\n            ]),\n      ]);\n    }\n  }, [\n    focusedPanel,\n    side,\n    handleMoveToAnotherSide,\n    handleRemoveFromStudio,\n    handleAddToStudio,\n  ]);\n\n  const hasChanges = useMemo(() => {\n    return (\n      (studioId && !isDocInContext) ||\n      JSON.stringify(initialSections) !== JSON.stringify(selectedSections)\n    );\n  }, [studioId, isDocInContext, initialSections, selectedSections]);\n\n  const handleEditRanges = useCallback(() => {\n    setIsEditingSelection(true);\n  }, []);\n\n  useEffect(() => {\n    if (studioId && !isDocInContext) {\n      handleEditRanges();\n    }\n  }, [studioId, isDocInContext, handleEditRanges]);\n\n  const handleCancelStudio = useCallback(() => {\n    setIsEditingSelection(false);\n    if (isDocInContext) {\n      setSelectedSections(initialSections || []);\n    } else {\n      setSelectedSections([]);\n      updateTabProperty<DocTabType, 'studioId'>(\n        tabKey,\n        'studioId',\n        undefined,\n        side,\n      );\n    }\n  }, [tabKey, side, isDocInContext, initialSections]);\n\n  const handleSubmitToStudio = useCallback(async () => {\n    if (project?.id && studioId && studio) {\n      const patchedDoc = studio?.doc_context.find(\n        (f) =>\n          f.doc_id === docId &&\n          f.doc_source === fullDoc?.doc_source &&\n          f.relative_url === relativeUrl,\n      );\n      if (!patchedDoc) {\n        await patchCodeStudio(project.id, studioId, {\n          doc_context: [\n            ...(studio?.doc_context || []),\n            {\n              doc_id: docId,\n              doc_source: fullDoc?.doc_source || '',\n              doc_icon: favicon || '',\n              doc_title: title || '',\n              relative_url: relativeUrl,\n              absolute_url: fullDoc?.absolute_url || '',\n              ranges: selectedSections,\n              hidden: false,\n            },\n          ],\n        });\n      } else {\n        patchedDoc.ranges = selectedSections;\n        const newContext = studio?.doc_context\n          .filter(\n            (f) =>\n              f.doc_id !== docId ||\n              f.doc_source !== fullDoc?.doc_source ||\n              f.relative_url !== relativeUrl,\n          )\n          .concat(patchedDoc);\n        await patchCodeStudio(project.id, studioId, {\n          doc_context: newContext,\n        });\n      }\n      refreshCurrentProjectStudios();\n      refreshStudio();\n      setIsEditingSelection(false);\n      updateTabProperty<DocTabType, 'isDocInContext'>(\n        tabKey,\n        'isDocInContext',\n        true,\n        side,\n      );\n      updateTabProperty<DocTabType, 'initialSections'>(\n        tabKey,\n        'initialSections',\n        selectedSections,\n        side,\n      );\n    }\n  }, [\n    project?.id,\n    studio,\n    docId,\n    relativeUrl,\n    fullDoc,\n    studioId,\n    selectedSections,\n  ]);\n\n  const handleKeyEvent = useCallback(\n    (e: KeyboardEvent) => {\n      if (checkEventKeys(e, openInSplitViewShortcut)) {\n        handleMoveToAnotherSide();\n      } else if (checkEventKeys(e, addToStudioShortcut)) {\n        e.preventDefault();\n        e.stopPropagation();\n        handleAddToStudio();\n      } else if (checkEventKeys(e, removeFromStudioShortcut)) {\n        e.preventDefault();\n        e.stopPropagation();\n        handleRemoveFromStudio();\n      } else if (checkEventKeys(e, escapeShortcut) && studioId) {\n        e.preventDefault();\n        e.stopPropagation();\n        handleCancelStudio();\n      } else if (checkEventKeys(e, saveShortcut) && studioId) {\n        e.preventDefault();\n        e.stopPropagation();\n        handleSubmitToStudio();\n      } else if (\n        checkEventKeys(e, selectLinesShortcut) &&\n        studioId &&\n        !isEditingSelection\n      ) {\n        e.preventDefault();\n        e.stopPropagation();\n        handleEditRanges();\n      }\n    },\n    [\n      handleMoveToAnotherSide,\n      handleAddToStudio,\n      handleRemoveFromStudio,\n      handleCancelStudio,\n      studioId,\n      handleSubmitToStudio,\n      handleEditRanges,\n    ],\n  );\n  useKeyboardNavigation(\n    handleKeyEvent,\n    focusedPanel !== side || isLeftSidebarFocused || isCommandBarVisible,\n  );\n\n  return (\n    <div\n      className={`flex flex-col flex-1 h-full overflow-auto ${\n        noBorder ? '' : 'border-l border-bg-border'\n      } inline-container`}\n    >\n      <div\n        className={`w-full px-4 flex gap-1 items-center flex-shrink-0 border-b border-bg-border bg-bg-sub ${\n          !!studio && studioId ? 'wrap-in-md-container' : 'h-10'\n        }`}\n      >\n        <div className=\"flex items-center gap-3 body-s text-label-title ellipsis\">\n          {favicon ? (\n            <img src={favicon} alt={relativeUrl} className=\"w-4 h-4\" />\n          ) : (\n            <MagazineIcon sizeClassName=\"w-4 h-4\" />\n          )}\n          <span className=\"ellipsis\">{title || relativeUrl}</span>\n        </div>\n        {!!studio && studioId && (\n          <div className=\"flex items-center gap-3 body-s\">\n            <div className=\"w-px h-4 bg-bg-border flex-shrink-1 ml-3\" />\n            <Badge\n              text={\n                selectedSections.length\n                  ? t('# selected section', {\n                      count: selectedSections.length,\n                    })\n                  : t('Whole page')\n              }\n              type=\"blue-subtle\"\n              size=\"small\"\n            />\n            <p\n              className={`select-none ${\n                tokenCount < 18000 && tokenCount > 1500\n                  ? 'text-yellow'\n                  : tokenCount <= 1500\n                  ? 'text-green'\n                  : 'text-red'\n              } code-mini`}\n            >\n              {humanNumber(tokenCount)}{' '}\n              <Trans count={tokenCount}># tokens</Trans>\n            </p>\n          </div>\n        )}\n        <div className=\"flex-1\" />\n        {focusedPanel === side &&\n          (studioId && (hasChanges || isEditingSelection) ? (\n            <div className=\"flex items-center gap-3\">\n              {!isEditingSelection && (\n                <>\n                  <Button\n                    variant=\"secondary\"\n                    size=\"mini\"\n                    onClick={handleEditRanges}\n                    shortcut={selectLinesShortcut}\n                    title={t('Select sections')}\n                    tooltipClassName=\"flex-shrink-0\"\n                  >\n                    <Trans>Select sections</Trans>\n                  </Button>\n                  <div className=\"w-px h-4 bg-bg-border flex-shrink-0\" />\n                </>\n              )}\n              <Button\n                variant=\"tertiary\"\n                size=\"mini\"\n                onClick={handleCancelStudio}\n                title={t('Cancel')}\n                shortcut={escapeShortcut}\n                tooltipClassName=\"flex-shrink-0\"\n              >\n                <Trans>Cancel</Trans>\n              </Button>\n              <Button\n                variant={isDocInContext ? 'secondary' : 'studio'}\n                size=\"mini\"\n                onClick={handleSubmitToStudio}\n                title={t(isDocInContext ? 'Save changes' : 'Submit')}\n                shortcut={saveShortcut}\n                tooltipClassName=\"flex-shrink-0\"\n              >\n                <Trans>{isDocInContext ? 'Save changes' : 'Submit'}</Trans>\n              </Button>\n            </div>\n          ) : (\n            studioId && (\n              <div className=\"flex items-center gap-3\">\n                <Button\n                  variant=\"secondary\"\n                  size=\"mini\"\n                  onClick={handleEditRanges}\n                  shortcut={selectLinesShortcut}\n                  title={t('Edit selected sections')}\n                  tooltipClassName=\"flex-shrink-0\"\n                >\n                  <Trans>Edit sections</Trans>\n                </Button>\n              </div>\n            )\n          ))}\n        {!isEditingSelection && (\n          <Dropdown\n            DropdownComponent={ActionsDropdown}\n            dropdownComponentProps={dropdownComponentProps}\n            appendTo={document.body}\n            dropdownPlacement=\"bottom-end\"\n          >\n            <Button\n              variant=\"tertiary\"\n              size=\"mini\"\n              onlyIcon\n              title={t('More actions')}\n            >\n              <MoreHorizontalIcon sizeClassName=\"w-3.5 h-3.5\" />\n            </Button>\n          </Dropdown>\n        )}\n      </div>\n      <div\n        className=\"flex-1 flex flex-col max-w-full overflow-auto\"\n        data-active={(focusedPanel === side).toString()}\n      >\n        {sections.map((s) => {\n          return (\n            <DocSection\n              key={s.point_id}\n              {...s}\n              isSelected={selectedSections.includes(s.point_id)}\n              setSelectedSections={setSelectedSections}\n              isNothingSelected={!selectedSections.length}\n              isEditingSelection={isEditingSelection}\n            />\n          );\n        })}\n      </div>\n    </div>\n  );\n};\n\nexport default memo(DocTab);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/DropTarget.tsx",
    "content": "import { memo } from 'react';\nimport { useDrop } from 'react-dnd';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { TabType } from '../../types/general';\nimport { SplitViewIcon } from '../../icons';\n\ntype Props = {\n  onDrop: (t: TabType) => void;\n};\n\nconst DropTarget = ({ onDrop }: Props) => {\n  useTranslation();\n  const [{ isOver, canDrop }, drop] = useDrop(\n    () => ({\n      accept: 'tab-left',\n      drop: (item: { t: TabType }, monitor) => {\n        onDrop(item.t);\n      },\n      collect: (monitor) => {\n        return {\n          isOver: !!monitor.isOver(),\n          canDrop: !!monitor.canDrop(),\n        };\n      },\n    }),\n    [onDrop],\n  );\n\n  return (\n    <div\n      className={`absolute top-10 right-0 w-1/2 bottom-0 ${\n        isOver && canDrop ? 'bg-bg-sub' : ''\n      } z-30`}\n      ref={drop}\n    >\n      {isOver && canDrop && (\n        <div className=\"absolute w-full h-full bg-bg-selected flex flex-col\">\n          <div className=\"h-10 border-b border-bg-border w-full\" />\n          <div className=\"flex-1 flex items-center justify-center\">\n            <div className=\"flex items-center gap-3 text-label-base body-s-b\">\n              <SplitViewIcon sizeClassName=\"w-4.5 h-4.5\" />\n              <p>\n                <Trans>Release to open in split view</Trans>\n              </p>\n            </div>\n          </div>\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport default memo(DropTarget);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/EmptyTab.tsx",
    "content": "import { memo } from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport useShortcuts from '../../hooks/useShortcuts';\n\ntype Props = {};\n\nconst EmptyTab = ({}: Props) => {\n  useTranslation();\n  const shortcut = useShortcuts(['cmd']);\n  return (\n    <div className=\"flex-1 h-full flex flex-col items-center justify-center gap-6 select-none cursor-default\">\n      <div className=\"w-15 h-15 flex items-center justify-center rounded-xl border border-bg-divider\">\n        <img alt=\"bloop\" className=\"w-6 h-6 bloop-head-img\" />\n      </div>\n      <div className=\"flex flex-col gap-2 items-center text-center max-w-[18.75rem]\">\n        <p className=\"body-base-b text-label-title\">\n          <Trans>No file selected</Trans>\n        </p>\n        <p className=\"body-s text-label-base !leading-5\">\n          <Trans>Select a file or open a new tab to display it here.</Trans>{' '}\n          <Trans values={{ cmdKey: shortcut?.[0] }}>\n            Press{' '}\n            <span className=\"min-w-[20px] h-5 px-0.5 inline-flex items-center justify-center rounded border border-bg-border bg-bg-base shadow-low\">\n              cmdKey\n            </span>{' '}\n            <span className=\"w-5 h-5 inline-flex items-center justify-center rounded border border-bg-border bg-bg-base shadow-low\">\n              K\n            </span>{' '}\n            on your keyboard to open the Command bar.\n          </Trans>\n        </p>\n      </div>\n    </div>\n  );\n};\n\nexport default memo(EmptyTab);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/FileTab/ActionsDropdown.tsx",
    "content": "import { memo } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport DropdownSection from '../../../components/Dropdown/Section';\nimport SectionItem from '../../../components/Dropdown/Section/SectionItem';\nimport {\n  SplitViewIcon,\n  FileWithSparksIcon,\n  StudioPlusSignIcon,\n  StudioCloseSignIcon,\n} from '../../../icons';\nimport {\n  addToStudioShortcut,\n  openInSplitViewShortcut,\n  explainFileShortcut,\n  removeFromStudioShortcut,\n} from '../../../consts/shortcuts';\n\ntype Props = {\n  handleExplain: () => void;\n  handleMoveToAnotherSide: () => void;\n  handleAddToStudio: () => void;\n  handleRemoveFromStudio: () => void;\n  isFileInContext: boolean;\n};\n\nconst ActionsDropdown = ({\n  handleExplain,\n  handleMoveToAnotherSide,\n  handleAddToStudio,\n  handleRemoveFromStudio,\n  isFileInContext,\n}: Props) => {\n  const { t } = useTranslation();\n\n  return (\n    <div>\n      <DropdownSection borderBottom>\n        <SectionItem\n          index=\"explain-file\"\n          label={t('Explain file')}\n          onClick={handleExplain}\n          shortcut={explainFileShortcut}\n          icon={<FileWithSparksIcon sizeClassName=\"w-4 h-4\" />}\n        />\n        {isFileInContext ? (\n          <SectionItem\n            index={'del-from-studio'}\n            label={t('Remove from studio')}\n            onClick={handleRemoveFromStudio}\n            shortcut={removeFromStudioShortcut}\n            icon={<StudioCloseSignIcon sizeClassName=\"w-4 h-4\" />}\n          />\n        ) : (\n          <SectionItem\n            index={'add-to-studio'}\n            label={t('Add to studio')}\n            onClick={handleAddToStudio}\n            shortcut={addToStudioShortcut}\n            icon={<StudioPlusSignIcon sizeClassName=\"w-4 h-4\" />}\n          />\n        )}\n      </DropdownSection>\n      <DropdownSection>\n        <SectionItem\n          index={'split-view'}\n          label={t('Open in split view')}\n          shortcut={openInSplitViewShortcut}\n          onClick={handleMoveToAnotherSide}\n          icon={<SplitViewIcon sizeClassName=\"w-4 h-4\" />}\n        />\n      </DropdownSection>\n    </div>\n  );\n};\n\nexport default memo(ActionsDropdown);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/FileTab/index.tsx",
    "content": "import React, {\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n  useTransition,\n} from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport {\n  forceFileToBeIndexed,\n  getCodeStudio,\n  getFileContent,\n  getFileTokenCount,\n  getHoverables,\n  patchCodeStudio,\n} from '../../../services/api';\nimport FileIcon from '../../../components/FileIcon';\nimport Button from '../../../components/Button';\nimport {\n  EyeCutIcon,\n  FileWithSparksIcon,\n  MoreHorizontalIcon,\n  SplitViewIcon,\n  StudioCloseSignIcon,\n  StudioPlusSignIcon,\n} from '../../../icons';\nimport { CodeStudioType, FileResponse } from '../../../types/api';\nimport { mapRanges } from '../../../mappers/results';\nimport { Range } from '../../../types/results';\nimport IpynbRenderer from '../../../components/IpynbRenderer';\nimport SpinLoaderContainer from '../../../components/Loaders/SpinnerLoader';\nimport {\n  CommandBarStepEnum,\n  FileTabType,\n  SyncStatus,\n  TabTypesEnum,\n} from '../../../types/general';\nimport { FileHighlightsContext } from '../../../context/fileHighlightsContext';\nimport Dropdown from '../../../components/Dropdown';\nimport { TabsContext } from '../../../context/tabsContext';\nimport { checkEventKeys } from '../../../utils/keyboardUtils';\nimport useKeyboardNavigation from '../../../hooks/useKeyboardNavigation';\nimport { CommandBarContext } from '../../../context/commandBarContext';\nimport BreadcrumbsPathContainer from '../../../components/Breadcrumbs/PathContainer';\nimport { RepositoriesContext } from '../../../context/repositoriesContext';\nimport { UIContext } from '../../../context/uiContext';\nimport {\n  addToStudioShortcut,\n  escapeShortcut,\n  explainFileShortcut,\n  openInSplitViewShortcut,\n  removeFromStudioShortcut,\n  saveShortcut,\n  selectLinesShortcut,\n} from '../../../consts/shortcuts';\nimport { ProjectContext } from '../../../context/projectContext';\nimport Badge from '../../../components/Badge';\nimport { humanNumber } from '../../../utils';\nimport { findElementInCurrentTab } from '../../../utils/domUtils';\nimport CodeFullSelectable from '../../../components/Code/CodeFullSelectable';\nimport ActionsDropdown from './ActionsDropdown';\n\ntype Props = {\n  tabKey: string;\n  repoRef: string;\n  path: string;\n  scrollToLine?: string;\n  tokenRange?: string;\n  noBorder?: boolean;\n  branch?: string | null;\n  side: 'left' | 'right';\n  studioId?: string;\n  initialRanges?: [number, number][];\n  isFileInContext?: boolean;\n  isTemp?: boolean;\n  handleMoveToAnotherSide: () => void;\n};\n\nconst FileTab = ({\n  path,\n  noBorder,\n  repoRef,\n  scrollToLine,\n  branch = null,\n  side,\n  tokenRange,\n  handleMoveToAnotherSide,\n  tabKey,\n  studioId,\n  initialRanges,\n  isFileInContext,\n  isTemp,\n}: Props) => {\n  const { t } = useTranslation();\n  const [file, setFile] = useState<FileResponse | null>(null);\n  const [hoverableRanges, setHoverableRanges] = useState<\n    Record<number, Range[]> | undefined\n  >(undefined);\n  const [indexRequested, setIndexRequested] = useState(false);\n  const [isFetched, setIsFetched] = useState(false);\n  const [studio, setStudio] = useState<CodeStudioType | null>(null);\n  const [selectedLines, setSelectedLines] = useState<[number, number][]>(\n    initialRanges || [],\n  );\n  const [isEditingRanges, setIsEditingRanges] = useState(false);\n  const [tokenCount, setTokenCount] = useState(0);\n  const { setFocusedTabItems, setIsVisible, setChosenStep } = useContext(\n    CommandBarContext.Handlers,\n  );\n  const { isVisible: isCommandBarVisible } = useContext(\n    CommandBarContext.General,\n  );\n  const [isPending, startTransition] = useTransition();\n  const { openNewTab, updateTabProperty } = useContext(TabsContext.Handlers);\n  const { focusedPanel } = useContext(TabsContext.FocusedPanel);\n  const { isLeftSidebarFocused } = useContext(UIContext.Focus);\n  const { setOnBoardingState } = useContext(UIContext.Onboarding);\n  const { project, refreshCurrentProjectStudios } = useContext(\n    ProjectContext.Current,\n  );\n  const { fileHighlights, hoveredLines } = useContext(\n    FileHighlightsContext.Values,\n  );\n  const { indexingStatus } = useContext(RepositoriesContext);\n\n  const refreshStudio = useCallback(() => {\n    if (studioId && project?.id) {\n      getCodeStudio(project.id, studioId).then(setStudio);\n    } else {\n      setStudio(null);\n    }\n  }, [studioId, project?.id]);\n\n  useEffect(() => {\n    refreshStudio();\n  }, [refreshStudio]);\n\n  const highlights = useMemo(() => {\n    return fileHighlights[path]?.sort((a, b) =>\n      a && b && a?.lines?.[1] - a?.lines?.[0] < b?.lines?.[1] - b?.lines?.[0]\n        ? -1\n        : 1,\n    );\n  }, [path, fileHighlights]);\n\n  useEffect(() => {\n    setIndexRequested(false);\n    setIsFetched(false);\n  }, [path, repoRef]);\n\n  const refetchFile = useCallback(async () => {\n    try {\n      setSelectedLines(initialRanges || []);\n      const resp = await getFileContent(repoRef, path, branch);\n      if (!resp) {\n        setIsFetched(true);\n        return;\n      }\n      startTransition(() => {\n        setFile(resp);\n        setIsFetched(true);\n      });\n      if (initialRanges) {\n        setTimeout(\n          () => {\n            const line = findElementInCurrentTab(\n              `[data-active=\"true\"] [data-line-number=\"${\n                initialRanges?.[0] ? initialRanges[0][0] : 0\n              }\"]`,\n            );\n            line?.scrollIntoView({\n              behavior: 'auto',\n              block:\n                !!initialRanges?.[0] &&\n                initialRanges[0][0] > 1 &&\n                initialRanges[0][1] - initialRanges[0][0] > 5\n                  ? 'start'\n                  : 'center',\n            });\n          },\n          !initialRanges?.[0]\n            ? 100\n            : initialRanges[0][0] > 1000\n            ? 1000\n            : initialRanges[0][0] > 500\n            ? 800\n            : 500,\n        );\n      }\n      // if (item.indexed) {\n      const data = await getHoverables(path, repoRef, branch);\n      setHoverableRanges(mapRanges(data.ranges));\n      // }\n    } catch (err) {\n      setIsFetched(true);\n    }\n  }, [repoRef, path, branch]);\n\n  useEffect(() => {\n    refetchFile();\n  }, [refetchFile]);\n\n  useEffect(() => {\n    if (project?.id) {\n      const mappedLines: [number, number][] = selectedLines.map((r) => [\n        r[0],\n        r[1] + 1,\n      ]);\n      getFileTokenCount(\n        project.id,\n        path,\n        repoRef,\n        branch || undefined,\n        mappedLines,\n      ).then(setTokenCount);\n    }\n  }, [path, repoRef, branch, selectedLines]);\n\n  const handleEditRanges = useCallback(() => {\n    setIsEditingRanges(true);\n  }, []);\n\n  useEffect(() => {\n    if (studioId && !isFileInContext) {\n      handleEditRanges();\n    }\n  }, [studioId, isFileInContext, handleEditRanges]);\n\n  const handleCancelStudio = useCallback(() => {\n    setIsEditingRanges(false);\n    if (isFileInContext) {\n      setSelectedLines(initialRanges || []);\n    } else {\n      setSelectedLines([]);\n      updateTabProperty<FileTabType, 'studioId'>(\n        tabKey,\n        'studioId',\n        undefined,\n        side,\n      );\n    }\n  }, [tabKey, side, isFileInContext, initialRanges]);\n\n  useEffect(() => {\n    if (indexingStatus[repoRef]?.status === SyncStatus.Done) {\n      setTimeout(refetchFile, 2000);\n    }\n  }, [indexingStatus[repoRef]?.status]);\n\n  const onIndexRequested = useCallback(async () => {\n    if (path) {\n      setIndexRequested(true);\n      await forceFileToBeIndexed(repoRef, path);\n      setTimeout(() => refetchFile(), 1000);\n    }\n  }, [repoRef, path]);\n\n  const handleClick = useCallback(() => {\n    if (isTemp) {\n      updateTabProperty<FileTabType, 'isTemp'>(tabKey, 'isTemp', false, side);\n    }\n  }, [updateTabProperty, tabKey, side, isTemp]);\n\n  const linesNumber = useMemo(() => {\n    return file?.contents?.split(/\\n(?!$)/g).length || 0;\n  }, [file?.contents]);\n\n  const handleExplain = useCallback(() => {\n    openNewTab(\n      {\n        type: TabTypesEnum.CHAT,\n        initialQuery: {\n          path,\n          repoRef,\n          branch,\n          lines: [0, linesNumber - 1],\n        },\n      },\n      side === 'left' ? 'right' : 'left',\n    );\n    setOnBoardingState((prev) =>\n      prev.isFileExplained ? prev : { ...prev, isFileExplained: true },\n    );\n  }, [path, repoRef, branch, linesNumber, side, openNewTab]);\n\n  const handleAddToStudio = useCallback(() => {\n    setChosenStep({\n      id: CommandBarStepEnum.ADD_TO_STUDIO,\n      data: { path, repoRef, branch },\n    });\n    setIsVisible(true);\n  }, [path, repoRef, branch]);\n\n  const handleRemoveFromStudio = useCallback(async () => {\n    if (project?.id && studioId && studio) {\n      const patchedFile = studio?.context.find(\n        (f) => f.path === path && f.repo === repoRef && f.branch === branch,\n      );\n      if (patchedFile) {\n        await patchCodeStudio(project.id, studioId, {\n          context: studio?.context.filter(\n            (f) => f.path !== path || f.repo !== repoRef || f.branch !== branch,\n          ),\n        });\n        refreshCurrentProjectStudios();\n        refreshStudio();\n        setIsEditingRanges(false);\n        updateTabProperty<FileTabType, 'isFileInContext'>(\n          tabKey,\n          'isFileInContext',\n          false,\n          side,\n        );\n        updateTabProperty<FileTabType, 'initialRanges'>(\n          tabKey,\n          'initialRanges',\n          undefined,\n          side,\n        );\n        updateTabProperty<FileTabType, 'studioId'>(\n          tabKey,\n          'studioId',\n          undefined,\n          side,\n        );\n        setStudio(null);\n        setSelectedLines([]);\n      }\n    }\n  }, [path, repoRef, branch, project?.id, studioId, studio]);\n\n  const handleSubmitToStudio = useCallback(async () => {\n    if (project?.id && studioId && studio) {\n      const patchedFile = studio?.context.find(\n        (f) => f.path === path && f.repo === repoRef && f.branch === branch,\n      );\n      const mappedRanges = selectedLines.map((r) => ({\n        start: r[0],\n        end: r[1] + 1,\n      }));\n      if (!patchedFile) {\n        await patchCodeStudio(project.id, studioId, {\n          context: [\n            ...(studio?.context || []),\n            {\n              path,\n              branch: branch || null,\n              repo: repoRef,\n              hidden: false,\n              ranges: mappedRanges || [],\n            },\n          ],\n        });\n      } else {\n        patchedFile.ranges = mappedRanges;\n        const newContext = studio?.context\n          .filter(\n            (f) => f.path !== path || f.repo !== repoRef || f.branch !== branch,\n          )\n          .concat(patchedFile);\n        await patchCodeStudio(project.id, studioId, {\n          context: newContext,\n        });\n      }\n      refreshCurrentProjectStudios();\n      refreshStudio();\n      setIsEditingRanges(false);\n      updateTabProperty<FileTabType, 'isFileInContext'>(\n        tabKey,\n        'isFileInContext',\n        true,\n        side,\n      );\n      updateTabProperty<FileTabType, 'initialRanges'>(\n        tabKey,\n        'initialRanges',\n        selectedLines,\n        side,\n      );\n    }\n  }, [project?.id, studio, path, repoRef, branch, selectedLines, studioId]);\n\n  const hasChanges = useMemo(() => {\n    return (\n      (studioId && !isFileInContext) ||\n      JSON.stringify(initialRanges) !== JSON.stringify(selectedLines)\n    );\n  }, [studioId, isFileInContext, initialRanges, selectedLines]);\n\n  const handleKeyEvent = useCallback(\n    (e: KeyboardEvent) => {\n      if (checkEventKeys(e, explainFileShortcut)) {\n        handleExplain();\n      } else if (checkEventKeys(e, openInSplitViewShortcut)) {\n        handleMoveToAnotherSide();\n      } else if (checkEventKeys(e, addToStudioShortcut)) {\n        e.preventDefault();\n        e.stopPropagation();\n        handleAddToStudio();\n      } else if (checkEventKeys(e, removeFromStudioShortcut)) {\n        e.preventDefault();\n        e.stopPropagation();\n        handleRemoveFromStudio();\n      } else if (checkEventKeys(e, escapeShortcut) && studioId) {\n        e.preventDefault();\n        e.stopPropagation();\n        handleCancelStudio();\n      } else if (checkEventKeys(e, saveShortcut) && studioId) {\n        e.preventDefault();\n        e.stopPropagation();\n        handleSubmitToStudio();\n      } else if (\n        checkEventKeys(e, selectLinesShortcut) &&\n        studioId &&\n        !isEditingRanges\n      ) {\n        e.preventDefault();\n        e.stopPropagation();\n        handleEditRanges();\n      }\n    },\n    [\n      handleExplain,\n      handleMoveToAnotherSide,\n      handleAddToStudio,\n      handleCancelStudio,\n      studioId,\n      handleSubmitToStudio,\n      handleEditRanges,\n    ],\n  );\n  useKeyboardNavigation(\n    handleKeyEvent,\n    !file?.contents ||\n      focusedPanel !== side ||\n      isLeftSidebarFocused ||\n      isCommandBarVisible,\n  );\n\n  useEffect(() => {\n    if (focusedPanel === side && file?.contents) {\n      setFocusedTabItems([\n        {\n          label: t('Explain file'),\n          Icon: FileWithSparksIcon,\n          id: 'explain_file',\n          key: 'explain_file',\n          onClick: handleExplain,\n          closeOnClick: true,\n          shortcut: explainFileShortcut,\n          footerHint: '',\n          footerBtns: [{ label: t('Explain'), shortcut: ['entr'] }],\n        },\n        ...(studioId && isFileInContext\n          ? [\n              {\n                label: t('Remove from studio'),\n                Icon: StudioCloseSignIcon,\n                id: 'file_from_studio',\n                key: 'file_from_studio',\n                onClick: handleRemoveFromStudio,\n                shortcut: removeFromStudioShortcut,\n                footerHint: t('Remove file from code studio context'),\n                footerBtns: [{ label: t('Remove'), shortcut: ['entr'] }],\n              },\n            ]\n          : [\n              {\n                label: t('Add to studio'),\n                Icon: StudioPlusSignIcon,\n                id: 'file_to_studio',\n                key: 'file_to_studio',\n                onClick: studioId ? handleSubmitToStudio : handleAddToStudio,\n                shortcut: studioId ? saveShortcut : addToStudioShortcut,\n                closeOnClick: !!studio,\n                footerHint: t('Add file to code studio context'),\n                footerBtns: [{ label: t('Add'), shortcut: ['entr'] }],\n              },\n            ]),\n        {\n          label: t('Open in split view'),\n          Icon: SplitViewIcon,\n          id: 'split_view',\n          key: 'split_view',\n          onClick: handleMoveToAnotherSide,\n          closeOnClick: true,\n          shortcut: openInSplitViewShortcut,\n          footerHint: '',\n          footerBtns: [{ label: t('Move'), shortcut: ['entr'] }],\n        },\n      ]);\n    }\n  }, [\n    focusedPanel,\n    side,\n    file?.contents,\n    handleExplain,\n    handleMoveToAnotherSide,\n    handleAddToStudio,\n  ]);\n\n  const dropdownComponentProps = useMemo(() => {\n    return {\n      handleExplain,\n      handleMoveToAnotherSide,\n      handleAddToStudio,\n      handleRemoveFromStudio,\n      isFileInContext,\n    };\n  }, [\n    handleExplain,\n    handleMoveToAnotherSide,\n    handleAddToStudio,\n    handleRemoveFromStudio,\n    isFileInContext,\n  ]);\n\n  return (\n    <div\n      className={`flex flex-col flex-1 h-full overflow-auto ${\n        noBorder ? '' : 'border-l border-bg-border'\n      } inline-container`}\n      onClick={handleClick}\n    >\n      <div\n        className={`w-full px-4 flex gap-1 items-center flex-shrink-0 border-b border-bg-border bg-bg-sub ${\n          !!studio && studioId ? 'wrap-in-sm-container' : 'h-10'\n        }`}\n      >\n        <div className=\"flex items-center gap-3 body-s text-label-title ellipsis flex-shrink\">\n          <FileIcon filename={path} noMargin />\n          <BreadcrumbsPathContainer\n            path={path}\n            repoRef={repoRef}\n            nonInteractive\n          />\n        </div>\n        {!!studio && studioId && (\n          <div className=\"flex items-center gap-3 body-s flex-shrink-0\">\n            <div className=\"w-px h-4 bg-bg-border flex-shrink-0 ml-3\" />\n            <Badge\n              text={\n                selectedLines.length\n                  ? selectedLines.length === 1\n                    ? t('Lines # - #', {\n                        start: selectedLines[0][0] + 1,\n                        end: selectedLines[0][1] ? selectedLines[0][1] + 1 : '',\n                      })\n                    : t('# ranges', { count: selectedLines.length })\n                  : t('Whole file')\n              }\n              type=\"blue-subtle\"\n              size=\"small\"\n            />\n            <p\n              className={`select-none ${\n                tokenCount < 18000 && tokenCount > 1500\n                  ? 'text-yellow'\n                  : tokenCount <= 1500\n                  ? 'text-green'\n                  : 'text-red'\n              } code-mini flex-shrink-0`}\n            >\n              {humanNumber(tokenCount)}{' '}\n              <Trans count={tokenCount}># tokens</Trans>\n            </p>\n          </div>\n        )}\n        <div className=\"flex-1\" />\n        <div className=\"flex items-center gap-3\">\n          {focusedPanel === side &&\n            (studioId && (hasChanges || isEditingRanges) ? (\n              <div className=\"flex items-center gap-3\">\n                {!isEditingRanges && (\n                  <>\n                    <Button\n                      variant=\"secondary\"\n                      size=\"mini\"\n                      onClick={handleEditRanges}\n                      shortcut={selectLinesShortcut}\n                      title={t('Create line ranges')}\n                    >\n                      <Trans>Create ranges</Trans>\n                    </Button>\n                    <div className=\"w-px h-4 bg-bg-border flex-shrink-0\" />\n                  </>\n                )}\n                <Button\n                  variant=\"tertiary\"\n                  size=\"mini\"\n                  onClick={handleCancelStudio}\n                  title={t('Cancel')}\n                  shortcut={escapeShortcut}\n                >\n                  <Trans>Cancel</Trans>\n                </Button>\n                <Button\n                  variant={isFileInContext ? 'secondary' : 'studio'}\n                  size=\"mini\"\n                  onClick={handleSubmitToStudio}\n                  title={t(isFileInContext ? 'Save changes' : 'Add to studio')}\n                  shortcut={saveShortcut}\n                >\n                  <Trans>{isFileInContext ? 'Save changes' : 'Add'}</Trans>\n                </Button>\n              </div>\n            ) : (\n              studioId && (\n                <Button\n                  variant=\"secondary\"\n                  size=\"mini\"\n                  onClick={handleEditRanges}\n                  shortcut={selectLinesShortcut}\n                  title={t('Edit selected lines')}\n                  tooltipClassName=\"flex-shrink-0\"\n                >\n                  <Trans>Edit ranges</Trans>\n                </Button>\n              )\n            ))}\n          {!isEditingRanges && (\n            <Dropdown\n              DropdownComponent={ActionsDropdown}\n              dropdownComponentProps={dropdownComponentProps}\n              dropdownPlacement=\"bottom-end\"\n              appendTo={document.body}\n            >\n              <Button\n                variant=\"tertiary\"\n                size=\"mini\"\n                onlyIcon\n                title={t('More actions')}\n              >\n                <MoreHorizontalIcon sizeClassName=\"w-3.5 h-3.5\" />\n              </Button>\n            </Dropdown>\n          )}\n        </div>\n      </div>\n      <div className=\"relative flex-1 h-full max-w-full\">\n        <div\n          className=\"flex-1 h-full max-w-full pl-4 py-4 overflow-auto\"\n          data-active={(focusedPanel === side).toString()}\n          data-path={path}\n        >\n          {file?.lang === 'jupyter notebook' ? (\n            <IpynbRenderer data={file.contents} />\n          ) : file ? (\n            <CodeFullSelectable\n              code={file.contents}\n              language={file.lang}\n              isSearchDisabled={focusedPanel !== side}\n              currentSelection={selectedLines}\n              setCurrentSelection={setSelectedLines}\n              relativePath={path}\n              repoRef={repoRef}\n              hoverableRanges={hoverableRanges}\n              scrollToLine={scrollToLine}\n              branch={branch}\n              tokenRange={tokenRange}\n              highlights={highlights}\n              hoveredLines={hoveredLines}\n              side={side}\n              isEditingRanges={isEditingRanges}\n            />\n          ) : isFetched && !file ? (\n            <div className=\"flex-1 h-full flex flex-col items-center justify-center gap-6\">\n              <div className=\"w-15 h-15 flex items-center justify-center rounded-xl border border-bg-divider\">\n                <EyeCutIcon sizeClassName=\"w-5 h-5\" />\n              </div>\n              <div className=\"flex flex-col gap-2 items-center text-center max-w-[18.75rem]\">\n                <p className=\"body-base-b text-label-title\">\n                  <Trans>File not indexed</Trans>\n                </p>\n                <p className=\"body-s text-label-base !leading-5\">\n                  <Trans>\n                    This might be because the file is too big or it has one of\n                    bloop&apos;s excluded file types.\n                  </Trans>\n                </p>\n              </div>\n              {!indexRequested ? (\n                <Button\n                  size=\"large\"\n                  variant=\"primary\"\n                  onClick={onIndexRequested}\n                >\n                  <Trans>Index</Trans>\n                </Button>\n              ) : (\n                <div className=\"text-bg-main mt-6\">\n                  <SpinLoaderContainer sizeClassName=\"w-8 h-8\" />\n                </div>\n              )}\n            </div>\n          ) : null}\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default memo(FileTab);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/Header/AddTabButton.tsx",
    "content": "import React, { memo, useCallback, useContext, useMemo } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { PlusSignIcon } from '../../../icons';\nimport Button from '../../../components/Button';\nimport { checkEventKeys } from '../../../utils/keyboardUtils';\nimport { TabTypesEnum } from '../../../types/general';\nimport useKeyboardNavigation from '../../../hooks/useKeyboardNavigation';\nimport { TabsContext } from '../../../context/tabsContext';\nimport Dropdown from '../../../components/Dropdown';\nimport {\n  newChatTabShortcut,\n  newStudioTabShortcut,\n} from '../../../consts/shortcuts';\nimport { postCodeStudio } from '../../../services/api';\nimport { ProjectContext } from '../../../context/projectContext';\nimport AddTabDropdown from './AddTabDropdown';\n\ntype Props = {\n  tabsLength: number;\n  side: 'left' | 'right';\n  focusedPanel: 'left' | 'right';\n};\n\nconst AddTabButton = ({ side, focusedPanel, tabsLength }: Props) => {\n  const { t } = useTranslation();\n  const { openNewTab } = useContext(TabsContext.Handlers);\n  const { refreshCurrentProjectStudios, project } = useContext(\n    ProjectContext.Current,\n  );\n\n  const dropdownComponentProps = useMemo(() => {\n    return { side };\n  }, [side]);\n\n  const openChatTab = useCallback(() => {\n    openNewTab({ type: TabTypesEnum.CHAT }, side);\n  }, [openNewTab, side]);\n\n  const openStudioTab = useCallback(async () => {\n    if (project?.id) {\n      const newId = await postCodeStudio(project?.id);\n      refreshCurrentProjectStudios();\n      openNewTab({ type: TabTypesEnum.STUDIO, studioId: newId }, side);\n    }\n  }, [openNewTab, side, project?.id]);\n\n  const handleKeyEvent = useCallback(\n    (e: KeyboardEvent) => {\n      if (checkEventKeys(e, newChatTabShortcut)) {\n        e.stopPropagation();\n        e.preventDefault();\n        openChatTab();\n      } else if (checkEventKeys(e, newStudioTabShortcut)) {\n        e.stopPropagation();\n        e.preventDefault();\n        openStudioTab();\n      }\n    },\n    [openNewTab],\n  );\n  useKeyboardNavigation(handleKeyEvent, side !== focusedPanel);\n\n  return (\n    <Dropdown\n      appendTo={document.body}\n      DropdownComponent={AddTabDropdown}\n      dropdownComponentProps={dropdownComponentProps}\n      size=\"auto\"\n      dropdownPlacement={tabsLength > 1 ? 'bottom-end' : 'bottom-start'}\n    >\n      <Button variant=\"tertiary\" size=\"small\" onlyIcon title={t('New tab')}>\n        <PlusSignIcon sizeClassName=\"w-4 h-4\" />\n      </Button>\n    </Dropdown>\n  );\n};\n\nexport default memo(AddTabButton);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/Header/AddTabDropdown.tsx",
    "content": "import React, { memo, useCallback, useContext } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport SectionItem from '../../../components/Dropdown/Section/SectionItem';\nimport { ChatBubblesIcon, CodeStudioIcon } from '../../../icons';\nimport { TabsContext } from '../../../context/tabsContext';\nimport { TabTypesEnum } from '../../../types/general';\nimport { postCodeStudio } from '../../../services/api';\nimport { ProjectContext } from '../../../context/projectContext';\nimport {\n  newChatTabShortcut,\n  newStudioTabShortcut,\n} from '../../../consts/shortcuts';\n\ntype Props = {\n  side: 'left' | 'right';\n};\n\nconst AddTabDropdown = ({ side }: Props) => {\n  const { t } = useTranslation();\n  const { openNewTab } = useContext(TabsContext.Handlers);\n  const { refreshCurrentProjectStudios, project } = useContext(\n    ProjectContext.Current,\n  );\n\n  const openChatTab = useCallback(() => {\n    openNewTab({ type: TabTypesEnum.CHAT }, side);\n  }, [openNewTab, side]);\n\n  const openStudioTab = useCallback(async () => {\n    if (project?.id) {\n      const newId = await postCodeStudio(project?.id);\n      refreshCurrentProjectStudios();\n      openNewTab({ type: TabTypesEnum.STUDIO, studioId: newId }, side);\n    }\n  }, [openNewTab, side, project?.id]);\n\n  return (\n    <div>\n      <div className=\"flex flex-col p-1 items-start border-y border-bg-border\">\n        <SectionItem\n          icon={\n            <ChatBubblesIcon\n              sizeClassName=\"w-4 h-4\"\n              className=\"text-brand-default\"\n            />\n          }\n          label={t('New conversation')}\n          shortcut={newChatTabShortcut}\n          onClick={openChatTab}\n          index={'chat'}\n        />\n        <SectionItem\n          icon={\n            <CodeStudioIcon\n              sizeClassName=\"w-4 h-4\"\n              className=\"text-brand-studio\"\n            />\n          }\n          label={t('New studio conversation')}\n          shortcut={newStudioTabShortcut}\n          onClick={openStudioTab}\n          index={'studio'}\n        />\n      </div>\n    </div>\n  );\n};\n\nexport default memo(AddTabDropdown);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/Header/TabButton.tsx",
    "content": "import React, {\n  memo,\n  MouseEvent,\n  useCallback,\n  useContext,\n  useRef,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { useDrag, useDrop } from 'react-dnd';\nimport {\n  DraggableTabItem,\n  TabType,\n  TabTypesEnum,\n} from '../../../types/general';\nimport FileIcon from '../../../components/FileIcon';\nimport { splitPath } from '../../../utils';\nimport Button from '../../../components/Button';\nimport {\n  ChatBubblesIcon,\n  CloseSignIcon,\n  CodeStudioIcon,\n  MagazineIcon,\n} from '../../../icons';\nimport { TabsContext } from '../../../context/tabsContext';\nimport { closeTabShortcut } from '../../../consts/shortcuts';\n\ntype Props = TabType & {\n  tabKey: string;\n  isActive: boolean;\n  side: 'left' | 'right';\n  isOnlyTab: boolean;\n  moveTab: (\n    i: number,\n    j: number,\n    sourceSide: 'left' | 'right',\n    targetSide: 'left' | 'right',\n  ) => void;\n  i: number;\n  repoRef?: string;\n  path?: string;\n  title?: string;\n  branch?: string | null;\n  scrollToLine?: string;\n  tokenRange?: string;\n  focusedPanel: 'left' | 'right';\n  isTemp?: boolean;\n  studioId?: string;\n  initialRanges?: [number, number][];\n  isFileInContext?: boolean;\n  isDocInContext?: boolean;\n  initialSections?: string[];\n  conversationId?: string;\n  initialQuery?: {\n    path: string;\n    lines: [number, number];\n    repoRef: string;\n    branch?: string | null | undefined;\n  };\n  docId?: string;\n  favicon?: string;\n  relativeUrl?: string;\n};\n\nconst TabButton = ({\n  isActive,\n  tabKey,\n  repoRef,\n  path,\n  type,\n  title,\n  side,\n  moveTab,\n  isOnlyTab,\n  i,\n  branch,\n  scrollToLine,\n  tokenRange,\n  focusedPanel,\n  isTemp,\n  studioId,\n  initialRanges,\n  initialSections,\n  isFileInContext,\n  isDocInContext,\n  conversationId,\n  initialQuery,\n  relativeUrl,\n  favicon,\n  docId,\n}: Props) => {\n  const { t } = useTranslation();\n  const { closeTab, setActiveLeftTab, setActiveRightTab, setFocusedPanel } =\n    useContext(TabsContext.Handlers);\n  const ref = useRef<HTMLAnchorElement>(null);\n  const [{ handlerId }, drop] = useDrop({\n    accept: [`tab-left`, `tab-right`],\n    canDrop: (item: DraggableTabItem) => true,\n    collect(monitor) {\n      return {\n        handlerId: monitor.getHandlerId(),\n      };\n    },\n    hover(item: DraggableTabItem, monitor) {\n      if (!ref.current) {\n        return;\n      }\n      const dragIndex = item.index;\n      const hoverIndex = i;\n      const sourceSide = item.side as 'left' | 'right';\n      const targetSide = side as 'left' | 'right';\n      // Don't replace items with themselves\n      if (dragIndex === hoverIndex && sourceSide === targetSide) {\n        return;\n      }\n      // Determine rectangle on screen\n      const hoverBoundingRect = ref.current?.getBoundingClientRect();\n      // Get vertical middle\n      const hoverMiddleX =\n        (hoverBoundingRect.right - hoverBoundingRect.left) / 2;\n      // Determine mouse position\n      const clientOffset = monitor.getClientOffset();\n      // Get pixels to the left\n      const hoverClientX = (clientOffset?.x || 0) - hoverBoundingRect.left;\n      // Only perform the move when the mouse has crossed half of the items width\n      // When dragging left, only move when the cursor is below 50%\n      // When dragging right, only move when the cursor is above 50%\n      // Dragging left\n      if (\n        dragIndex < hoverIndex &&\n        hoverClientX < hoverMiddleX &&\n        sourceSide === targetSide\n      ) {\n        return;\n      }\n      // Dragging right\n      if (\n        dragIndex > hoverIndex &&\n        hoverClientX > hoverMiddleX &&\n        sourceSide === targetSide\n      ) {\n        return;\n      }\n      // Time to actually perform the action\n      moveTab(dragIndex, hoverIndex, sourceSide, targetSide);\n      // Note: we're mutating the monitor item here!\n      // Generally it's better to avoid mutations,\n      // but it's good here for the sake of performance\n      // to avoid expensive index searches.\n      item.index = hoverIndex;\n      if (sourceSide !== targetSide) {\n        item.side = targetSide;\n      }\n    },\n  });\n  const [{ isDragging }, drag] = useDrag({\n    type: `tab-${side}`,\n    canDrag: side !== 'left' || !isOnlyTab,\n    item: (): DraggableTabItem => {\n      return {\n        id: tabKey,\n        index: i,\n        // @ts-ignore\n        t: {\n          key: tabKey,\n          repoRef: repoRef!,\n          path: path!,\n          type,\n          title,\n          branch,\n          scrollToLine,\n          tokenRange,\n          studioId,\n          initialRanges,\n          initialSections,\n          isFileInContext,\n          isDocInContext,\n          conversationId,\n          initialQuery,\n          favicon,\n          relativeUrl,\n          docId,\n        },\n        side,\n      };\n    },\n    collect: (monitor) => ({\n      isDragging: monitor.isDragging(),\n    }),\n  });\n  drag(drop(ref));\n\n  const handleClose = useCallback(\n    (e: MouseEvent) => {\n      e.stopPropagation();\n      closeTab(tabKey, side);\n    },\n    [tabKey, side],\n  );\n\n  const handleClick = useCallback(() => {\n    const setAction = side === 'left' ? setActiveLeftTab : setActiveRightTab;\n    // @ts-ignore\n    setAction({\n      path,\n      repoRef,\n      key: tabKey,\n      type,\n      title,\n      branch,\n      scrollToLine,\n      tokenRange,\n      studioId,\n      initialRanges,\n      initialSections,\n      isFileInContext,\n      isDocInContext,\n      conversationId,\n      initialQuery,\n      docId,\n      relativeUrl,\n      favicon,\n    });\n    setFocusedPanel(side);\n  }, [\n    path,\n    repoRef,\n    tabKey,\n    side,\n    branch,\n    scrollToLine,\n    tokenRange,\n    title,\n    studioId,\n    initialRanges,\n    initialSections,\n    isFileInContext,\n    isDocInContext,\n    conversationId,\n    initialQuery,\n  ]);\n\n  return (\n    <a\n      href=\"#\"\n      ref={ref}\n      onClick={handleClick}\n      className={`flex h-7 w-[9rem] gap-1.5 pl-2 pr-1.5 ${\n        isDragging\n          ? 'opacity-0 border border-bg-border-selected bg-bg-selected'\n          : 'opacity-100'\n      } flex-shrink-0 items-center rounded ellipsis group ${\n        isActive\n          ? focusedPanel === side\n            ? 'bg-bg-base-hover'\n            : 'bg-bg-base'\n          : ''\n      } hover:bg-bg-base-hover transition duration-75 ease-in-out select-none`}\n      data-handler-id={handlerId}\n    >\n      {type === TabTypesEnum.FILE ? (\n        <FileIcon filename={path} noMargin />\n      ) : type === TabTypesEnum.CHAT ? (\n        <ChatBubblesIcon\n          sizeClassName=\"w-4 h-4\"\n          className=\"text-brand-default\"\n        />\n      ) : type === TabTypesEnum.STUDIO ? (\n        <CodeStudioIcon sizeClassName=\"w-4 h-4\" className=\"text-brand-studio\" />\n      ) : favicon ? (\n        <img src={favicon} className=\"w-4 h-4\" alt={title} />\n      ) : (\n        <MagazineIcon sizeClassName=\"w-4 h-4\" />\n      )}\n      <p\n        className={`body-mini-b ellipsis group-hover:text-label-title flex-1 ${\n          isActive ? 'text-label-title' : 'text-label-muted'\n        } ${isTemp ? '!italic' : ''} transition duration-75 ease-in-out`}\n      >\n        {type === TabTypesEnum.FILE\n          ? splitPath(path).pop()\n          : type === TabTypesEnum.CHAT\n          ? title || t('New conversation')\n          : type === TabTypesEnum.STUDIO\n          ? title || t('New studio conversation')\n          : title || relativeUrl}\n      </p>\n      <Button\n        variant=\"ghost\"\n        size=\"mini\"\n        onlyIcon\n        title={t('Close tab')}\n        shortcut={isActive ? closeTabShortcut : undefined}\n        className={`opacity-0 group-hover:opacity-100 ${\n          isActive ? 'opacity-100' : ''\n        }`}\n        onClick={handleClose}\n      >\n        <CloseSignIcon sizeClassName={'w-3 h-3'} />\n      </Button>\n    </a>\n  );\n};\n\nexport default memo(TabButton);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/Header/index.tsx",
    "content": "import React, { memo, useCallback, useContext, useMemo } from 'react';\nimport HeaderRightPart from '../../../components/Header/HeaderRightPart';\nimport { TabsContext } from '../../../context/tabsContext';\nimport { TabType, TabTypesEnum } from '../../../types/general';\nimport AddTabButton from './AddTabButton';\nimport TabButton from './TabButton';\n\ntype Props = {\n  side: 'left' | 'right';\n};\n\nconst ProjectHeader = ({ side }: Props) => {\n  const { leftTabs, rightTabs } = useContext(TabsContext.All);\n  const { focusedPanel } = useContext(TabsContext.FocusedPanel);\n  const { tab } = useContext(\n    TabsContext[side === 'left' ? 'CurrentLeft' : 'CurrentRight'],\n  );\n  const { setLeftTabs, setRightTabs, setActiveRightTab, setActiveLeftTab } =\n    useContext(TabsContext.Handlers);\n  const tabs = useMemo(() => {\n    return side === 'left' ? leftTabs : rightTabs;\n  }, [side, rightTabs, leftTabs]);\n\n  const moveTab = useCallback(\n    (\n      dragIndex: number,\n      hoverIndex: number,\n      sourceSide: 'left' | 'right',\n      targetSide: 'left' | 'right',\n    ) => {\n      if (sourceSide === targetSide) {\n        const action = side === 'left' ? setLeftTabs : setRightTabs;\n        action((prevTabs) => {\n          const newTabs = JSON.parse(JSON.stringify(prevTabs));\n          newTabs.splice(dragIndex, 1);\n          const newTab = prevTabs[dragIndex];\n          newTabs.splice(\n            hoverIndex,\n            0,\n            newTab.type === TabTypesEnum.FILE && newTab.isTemp\n              ? { ...newTab, isTemp: false }\n              : newTab,\n          );\n          return newTabs;\n        });\n      } else {\n        const sourceAction = sourceSide === 'left' ? setLeftTabs : setRightTabs;\n        const sourceTabAction =\n          sourceSide === 'left' ? setActiveLeftTab : setActiveRightTab;\n        const targetAction = targetSide === 'left' ? setLeftTabs : setRightTabs;\n        const targetTabAction =\n          targetSide === 'left' ? setActiveLeftTab : setActiveRightTab;\n\n        sourceAction((prevSourceTabs) => {\n          const newSourceTabs = JSON.parse(JSON.stringify(prevSourceTabs));\n          const [movedTab] = newSourceTabs.splice(dragIndex, 1);\n          sourceTabAction(\n            newSourceTabs.length\n              ? newSourceTabs[dragIndex - 1] || newSourceTabs[0]\n              : null,\n          );\n\n          targetAction((prevTargetTabs) => {\n            const newTargetTabs = JSON.parse(JSON.stringify(prevTargetTabs));\n            if (!newTargetTabs.find((t: TabType) => t.key === movedTab.key)) {\n              newTargetTabs.splice(hoverIndex, 0, movedTab);\n            }\n            targetTabAction(movedTab);\n            return newTargetTabs;\n          });\n\n          return newSourceTabs;\n        });\n      }\n    },\n    [side],\n  );\n\n  return (\n    <div\n      className={`flex justify-between items-center h-10 border-b border-bg-border overflow-hidden select-none ${\n        side === 'right' ? 'border-l border-bg-border' : ''\n      }`}\n    >\n      <div\n        className=\"flex pl-4 pr-2 items-center gap-1 flex-1 h-full overflow-auto fade-right\"\n        data-tauri-drag-region\n      >\n        {tabs.map(({ key, ...t }, i) => (\n          <TabButton\n            key={`${t.type}-${key}`}\n            {...t}\n            tabKey={key}\n            isActive={tab?.key === key}\n            focusedPanel={focusedPanel}\n            side={side}\n            isOnlyTab={tabs.length === 1}\n            moveTab={moveTab}\n            i={i}\n          />\n        ))}\n        {!!tabs.length && <div className=\"h-3 w-px bg-bg-border mx-1\" />}\n        <AddTabButton\n          tabsLength={tabs.length}\n          side={side}\n          focusedPanel={focusedPanel}\n        />\n      </div>\n      {(side === 'right' || !rightTabs.length) && (\n        <div className=\"border-l border-bg-border h-full\">\n          <HeaderRightPart />\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport default memo(ProjectHeader);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/StudioTab/ActionsDropdown.tsx",
    "content": "import { memo, useCallback } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport DropdownSection from '../../../components/Dropdown/Section';\nimport SectionItem from '../../../components/Dropdown/Section/SectionItem';\nimport { BroomIcon, SplitViewIcon, TrashCanIcon } from '../../../icons';\nimport { deleteCodeStudio } from '../../../services/api';\nimport { openInSplitViewShortcut } from '../../../consts/shortcuts';\n\ntype Props = {\n  clearConversation: () => void;\n  handleMoveToAnotherSide: () => void;\n  refreshCurrentProjectStudios: () => void;\n  closeTab: (tabKey: string, side: 'left' | 'right') => void;\n  studioId?: string;\n  projectId?: string;\n  tabKey: string;\n  side: 'left' | 'right';\n};\n\nconst ActionsDropdown = ({\n  handleMoveToAnotherSide,\n  refreshCurrentProjectStudios,\n  clearConversation,\n  studioId,\n  projectId,\n  closeTab,\n  tabKey,\n  side,\n}: Props) => {\n  const { t } = useTranslation();\n\n  const removeConversation = useCallback(async () => {\n    if (projectId && studioId) {\n      await deleteCodeStudio(projectId, studioId);\n      refreshCurrentProjectStudios();\n      closeTab(tabKey, side);\n    }\n  }, [\n    projectId,\n    studioId,\n    closeTab,\n    refreshCurrentProjectStudios,\n    tabKey,\n    side,\n  ]);\n\n  return (\n    <div>\n      <DropdownSection borderBottom>\n        <SectionItem\n          label={t('Open in split view')}\n          shortcut={openInSplitViewShortcut}\n          onClick={handleMoveToAnotherSide}\n          index={'split-view'}\n          icon={<SplitViewIcon sizeClassName=\"w-4 h-4\" />}\n        />\n        {studioId && (\n          <SectionItem\n            label={t('Clear conversation')}\n            // shortcut={shortcuts.splitView}\n            onClick={clearConversation}\n            index=\"clear-chat\"\n            icon={<BroomIcon sizeClassName=\"w-4 h-4\" />}\n          />\n        )}\n      </DropdownSection>\n      <DropdownSection>\n        {studioId && (\n          <SectionItem\n            label={t('Delete conversation')}\n            // shortcut={shortcuts.splitView}\n            onClick={removeConversation}\n            index={'del-chat'}\n            icon={<TrashCanIcon sizeClassName=\"w-4 h-4\" />}\n          />\n        )}\n      </DropdownSection>\n    </div>\n  );\n};\n\nexport default memo(ActionsDropdown);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/StudioTab/Conversation/ContextError.tsx",
    "content": "import { memo } from 'react';\nimport { Trans } from 'react-i18next';\nimport { WarningSignIcon } from '../../../../icons';\n\ntype Props = {};\n\nconst ContextError = ({}: Props) => {\n  return (\n    <div className=\"w-full flex items-center gap-4 p-4 select-none\">\n      <div className=\"w-7 h-7 flex-shrink-0 flex items-center justify-center rounded-full bg-red-subtle text-red\">\n        <WarningSignIcon sizeClassName=\"w-3.5 h-3.5\" />\n      </div>\n      <p className=\"body-s text-red flex-1\">\n        <Trans>\n          We can’t generate a response because some files have a missing source\n          in your Context files.\n        </Trans>\n      </p>\n    </div>\n  );\n};\n\nexport default memo(ContextError);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/StudioTab/Conversation/GeneratedDiff.tsx",
    "content": "import { Dispatch, memo, SetStateAction, useCallback, useContext } from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { GeneratedCodeDiff } from '../../../../types/api';\nimport { BranchIcon, WarningSignIcon } from '../../../../icons';\nimport CodeDiff from '../../../../components/Code/CodeDiff';\n\ntype Props = {\n  diff: GeneratedCodeDiff;\n  onDiffRemoved: (i: number) => void;\n  onDiffChanged: (i: number, p: string) => void;\n  applyError?: boolean;\n};\n\nconst GeneratedDiff = ({\n  diff,\n  onDiffRemoved,\n  onDiffChanged,\n  applyError,\n}: Props) => {\n  useTranslation();\n  // const { repositories } = useContext(RepositoriesContext);\n\n  // const onDiffClick = useCallback(\n  //   (chunk: DiffChunkType) => {\n  // const repoFull = repositories?.find((r) => r.ref === chunk.repo);\n  // if (repoFull) {\n  // setLeftPanel({\n  //   type: StudioLeftPanelType.DIFF,\n  //   data: {\n  //     filePath: chunk.file,\n  //     repo: repoFull,\n  //     branch: chunk.branch,\n  //     hunks: chunk.hunks,\n  //   },\n  // });\n  // }\n  //   },\n  //   [repositories],\n  // );\n  const onDiffClick = useCallback(() => {}, []);\n\n  return (\n    <div className=\"my-4 flex flex-col rounded-md overflow-hidden bg-bg-sub hover:bg-bg-sub-hover transition-all duration-150 ease-in-out\">\n      <div className=\"w-full\">\n        <div\n          className={`w-full flex items-center justify-center gap-1 py-2 ${\n            applyError ? 'bg-red-subtle text-red' : ' bg-blue-subtle text-blue'\n          } body-mini`}\n        >\n          {applyError ? (\n            <WarningSignIcon raw sizeClassName=\"w-3.5 h-3.5\" />\n          ) : (\n            <BranchIcon raw sizeClassName=\"w-3.5 h-3.5\" />\n          )}\n          <Trans>\n            {applyError\n              ? 'Failed to apply the diff'\n              : 'Generated diffs to be applied'}\n          </Trans>\n        </div>\n      </div>\n      <div className=\" p-4\">\n        <p className=\"body-s text-label-title\">\n          {diff.chunks.find((c) => c.repo.startsWith('local//')) ? (\n            <Trans>\n              The following changes can be applied to your repository. Make sure\n              the generated diffs are valid before you apply the changes.\n            </Trans>\n          ) : (\n            <Trans>\n              The following changes represent the git diff for the remote\n              repository. Please note that these changes cannot be applied\n              directly to a remote repository. Use the &quot;Copy&quot; button\n              to copy the changes and apply them locally.\n            </Trans>\n          )}\n        </p>\n        {diff.chunks.map((d, i) => (\n          <CodeDiff\n            key={d.file}\n            filePath={d.file}\n            language={d.lang || 'diff'}\n            {...d}\n            i={i}\n            onClick={onDiffClick}\n            onDiffChanged={onDiffChanged}\n            onDiffRemoved={onDiffRemoved}\n          />\n        ))}\n      </div>\n    </div>\n  );\n};\n\nexport default memo(GeneratedDiff);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/StudioTab/Conversation/Input/TemplatesDropdown.tsx",
    "content": "import {\n  ChangeEvent,\n  MouseEvent,\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useState,\n  useRef,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport DropdownSection from '../../../../../components/Dropdown/Section';\nimport { StudioTemplateType } from '../../../../../types/api';\nimport SectionItem from '../../../../../components/Dropdown/Section/SectionItem';\nimport { CogIcon, TemplatesIcon } from '../../../../../icons';\nimport { UIContext } from '../../../../../context/uiContext';\nimport { ProjectSettingSections } from '../../../../../types/general';\nimport { ArrowNavigationContext } from '../../../../../context/arrowNavigationContext';\n\ntype Props = {\n  templates: StudioTemplateType[];\n  onTemplateSelected: (t: string) => void;\n  handleClose: () => void;\n};\n\nconst TemplatesDropdown = ({ templates, onTemplateSelected }: Props) => {\n  const { t } = useTranslation();\n  const [inputValue, setInputValue] = useState('');\n  const [templatesToShow, setTemplatesToShow] = useState(templates);\n  const { setProjectSettingsOpen, setProjectSettingsSection } = useContext(\n    UIContext.ProjectSettings,\n  );\n  const { focusedIndex } = useContext(ArrowNavigationContext);\n\n  const inputRef = useRef<HTMLInputElement | null>(null);\n\n  useEffect(() => {\n    if (!inputValue) {\n      setTemplatesToShow(templates);\n    } else {\n      setTemplatesToShow(\n        templates.filter(\n          (t) => t.name?.toLowerCase().includes(inputValue?.toLowerCase()),\n        ),\n      );\n    }\n  }, [templates, inputValue]);\n\n  const handleChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n    setInputValue(e.target.value);\n  }, []);\n\n  const handleManage = useCallback(() => {\n    setProjectSettingsSection(ProjectSettingSections.TEMPLATES);\n    setProjectSettingsOpen(true);\n  }, []);\n\n  const noPropagate = useCallback((e: MouseEvent<HTMLInputElement>) => {\n    e.stopPropagation();\n  }, []);\n\n  useEffect(() => {\n    if (focusedIndex === 'search-templates') {\n      inputRef.current?.focus();\n    }\n  }, [focusedIndex]);\n\n  return (\n    <div>\n      <DropdownSection borderBottom>\n        <input\n          className=\"w-full h-8 bg-transparent body-s px-2 outline-0 focus:outline-0 focus:outline-none placeholder:text-label-muted\"\n          value={inputValue}\n          name={'search'}\n          onChange={handleChange}\n          type=\"search\"\n          autoComplete=\"off\"\n          autoCorrect=\"off\"\n          placeholder={t('Search templates...')}\n          autoFocus\n          onClick={noPropagate}\n          ref={inputRef}\n          data-node-index={'search-templates'}\n        />\n      </DropdownSection>\n      <DropdownSection borderBottom>\n        {templatesToShow.map((t) => (\n          <SectionItem\n            label={t.name}\n            key={t.id}\n            index={`templ-${t.id}`}\n            onClick={() => onTemplateSelected(t.content)}\n            icon={<TemplatesIcon sizeClassName=\"w-3.5 h-3.5\" />}\n          />\n        ))}\n      </DropdownSection>\n      <DropdownSection>\n        <SectionItem\n          label={t('Manage templates')}\n          icon={<CogIcon sizeClassName=\"w-3.5 h-3.5\" />}\n          onClick={handleManage}\n          index={'manage-templ'}\n        />\n      </DropdownSection>\n    </div>\n  );\n};\n\nexport default memo(TemplatesDropdown);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/StudioTab/Conversation/Input/index.tsx",
    "content": "import React, {\n  ChangeEvent,\n  memo,\n  useCallback,\n  useEffect,\n  useImperativeHandle,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { StudioConversationMessageAuthor } from '../../../../../types/general';\nimport MarkdownWithCode from '../../../../../components/MarkdownWithCode';\nimport {\n  PencilIcon,\n  PersonIcon,\n  RefreshIcon,\n  TemplatesIcon,\n  TrashCanIcon,\n  WarningSignIcon,\n} from '../../../../../icons';\nimport Button from '../../../../../components/Button';\nimport CopyButton from '../../../../../components/MarkdownWithCode/CopyButton';\nimport { StudioTemplateType } from '../../../../../types/api';\nimport Dropdown from '../../../../../components/Dropdown';\nimport { useTemplateShortcut } from '../../../../../consts/shortcuts';\nimport SpinLoaderContainer from '../../../../../components/Loaders/SpinnerLoader';\nimport TemplatesDropdown from './TemplatesDropdown';\n\ntype Props = {\n  author: StudioConversationMessageAuthor;\n  message: string;\n  isLoading?: boolean;\n  onMessageChange: (m: string, i?: number) => void;\n  onMessageRemoved?: (i: number, andSubsequent?: boolean) => void;\n  i?: number;\n  inputRef?: React.MutableRefObject<HTMLTextAreaElement | null>;\n  templatesRef?: React.MutableRefObject<HTMLButtonElement | null>;\n  isTokenLimitExceeded: boolean;\n  isLast: boolean;\n  isActiveTab: boolean;\n  side: 'left' | 'right';\n  templates?: StudioTemplateType[];\n  setIsDropdownShown: (b: boolean) => void;\n};\n\nconst ConversationInput = ({\n  author,\n  message,\n  onMessageChange,\n  i,\n  onMessageRemoved,\n  inputRef,\n  isLast,\n  isTokenLimitExceeded,\n  side,\n  templates,\n  setIsDropdownShown,\n  templatesRef,\n  isActiveTab,\n  isLoading,\n}: Props) => {\n  const { t } = useTranslation();\n  const [isFocused, setFocused] = useState(i === undefined);\n  const [value, setValue] = useState(message);\n  const ref = useRef<HTMLTextAreaElement | null>(null);\n  const cloneRef = useRef<HTMLTextAreaElement | null>(null);\n  useImperativeHandle(inputRef, () => ref.current!);\n\n  const handleChange = useCallback(\n    (e: ChangeEvent<HTMLTextAreaElement>) => {\n      if (isActiveTab) {\n        if (i === undefined) {\n          onMessageChange(e.target.value, i);\n        }\n        setValue(e.target.value);\n      }\n    },\n    [i, onMessageChange, isActiveTab],\n  );\n\n  useEffect(() => {\n    if (isFocused) {\n      setValue(message);\n    }\n  }, [isFocused, message]);\n\n  useEffect(() => {\n    if (i === undefined) {\n      setFocused(true);\n    }\n  }, [i, message]);\n\n  const handleBlur = useCallback(() => {\n    setTimeout(() => setFocused(false), 100); // to allow press on top buttons\n    if (i !== undefined) {\n      onMessageChange(value, i);\n    }\n  }, [onMessageChange, value, i]);\n\n  useEffect(() => {\n    if (!isActiveTab) {\n      inputRef?.current?.blur();\n    } else {\n      inputRef?.current?.focus();\n    }\n  }, [isActiveTab]);\n\n  useEffect(() => {\n    if (ref.current && cloneRef.current) {\n      cloneRef.current.style.height = '19px';\n      const scrollHeight = cloneRef.current.scrollHeight;\n\n      // We then set the height directly, outside of the render loop\n      // Trying to set this with state or a ref will product an incorrect value.\n      ref.current.style.height =\n        Math.min(Math.max(scrollHeight, 19), 300) + 'px';\n    }\n  }, [message, isFocused, value]);\n\n  const dropdownProps = useMemo(() => {\n    return {\n      templates,\n      onTemplateSelected: onMessageChange,\n    };\n  }, [templates, onMessageChange, i]);\n\n  return (\n    <div className=\"flex items-start w-full p-4 gap-4 rounded-md hover:bg-bg-sub-hover relative group\">\n      {author === StudioConversationMessageAuthor.USER ? (\n        <div className=\"w-7 h-7 rounded-full overflow-hidden select-none flex-shrink-0 flex items-center justify-center text-label-base\">\n          <PersonIcon sizeClassName=\"w-6 h-6\" />\n        </div>\n      ) : (\n        <div className=\"flex w-7 h-7 items-center justify-center rounded-full bg-brand-studio-subtle flex-shrink-0\">\n          {isLoading ? (\n            <SpinLoaderContainer\n              sizeClassName=\"w-5 h-5\"\n              colorClassName=\"text-brand-studio\"\n            />\n          ) : (\n            <img className=\"bloop-head-img w-7 h-7\" alt=\"bloop\" />\n          )}\n        </div>\n      )}\n      <div className=\"flex flex-col gap-1 flex-1 items-start overflow-auto\">\n        <div className=\"w-full flex items-center justify-between gap-1\">\n          <p className=\"body-base-b text-label-title select-none\">\n            {author === StudioConversationMessageAuthor.USER ? (\n              <Trans>You</Trans>\n            ) : (\n              'bloop'\n            )}\n          </p>\n          {i === undefined && (\n            <Dropdown\n              DropdownComponent={TemplatesDropdown}\n              dropdownComponentProps={dropdownProps}\n              dropdownPlacement=\"bottom-end\"\n              appendTo={document.body}\n              size=\"auto\"\n              onVisibilityChange={setIsDropdownShown}\n            >\n              <Button\n                variant=\"tertiary\"\n                size=\"small\"\n                title={t('Use template')}\n                shortcut={useTemplateShortcut}\n                ref={templatesRef}\n              >\n                <TemplatesIcon sizeClassName=\"w-4 h-4\" />\n                <Trans>Templates</Trans>\n              </Button>\n            </Dropdown>\n          )}\n        </div>\n        <div\n          className={`w-full code-studio-md break-words body-base relative ${\n            isFocused || i === undefined ? 'flex flex-col' : ''\n          }`}\n        >\n          {isFocused || i === undefined ? (\n            <>\n              <textarea\n                className={`w-full bg-transparent outline-none focus:outline-0 resize-none body-base placeholder:text-label-muted`}\n                placeholder={t('Start typing...')}\n                value={value}\n                onChange={handleChange}\n                autoComplete=\"off\"\n                spellCheck=\"false\"\n                ref={ref}\n                rows={1}\n                onBlur={handleBlur}\n                autoFocus\n              />\n              <textarea\n                className={`resize-none body-base absolute top-0 left-0 right-0 -z-10 opacity-0`}\n                value={value}\n                disabled\n                rows={1}\n                ref={cloneRef}\n              />\n            </>\n          ) : (\n            <>\n              <MarkdownWithCode markdown={message} isCodeStudio side={side} />\n              {author === StudioConversationMessageAuthor.ASSISTANT &&\n                isLast &&\n                isTokenLimitExceeded && (\n                  <div\n                    className={\n                      'flex p-2 gap-2 items-start rounded bg-red-subtle text-red body-mini'\n                    }\n                  >\n                    <WarningSignIcon sizeClassName=\"w-5 h-5\" />\n                    <Trans>\n                      Token limit reached, this answer may be incomplete. To\n                      generate a full answer, please reduce the number of tokens\n                      used and regenerate.\n                    </Trans>\n                  </div>\n                )}\n            </>\n          )}\n        </div>\n      </div>\n      {i !== undefined && (\n        <div className=\"opacity-0 group-hover:opacity-100 absolute -top-4 right-4 flex items-center p-1 gap-1 rounded-6 border border-bg-border bg-bg-base shadow-medium\">\n          {author === StudioConversationMessageAuthor.ASSISTANT && (\n            <CopyButton code={message} isInHeader btnVariant=\"tertiary\" />\n          )}\n          <Button\n            size=\"mini\"\n            variant=\"tertiary\"\n            onlyIcon\n            title={t('Edit')}\n            onClick={() => setFocused(true)}\n          >\n            <PencilIcon sizeClassName=\"w-3.5 h-3.5\" />\n          </Button>\n          {author === StudioConversationMessageAuthor.USER && (\n            <Button\n              size=\"mini\"\n              variant=\"tertiary\"\n              onlyIcon\n              title={t('Retry')}\n              onClick={() => onMessageRemoved?.(i, true)}\n            >\n              <RefreshIcon sizeClassName=\"w-3.5 h-3.5\" />\n            </Button>\n          )}\n          <div className=\"w-px h-3 bg-bg-border flex-shrink-0\" />\n          <Button\n            size=\"mini\"\n            variant=\"tertiary\"\n            onlyIcon\n            title={t('Remove')}\n            onClick={() => onMessageRemoved?.(i)}\n          >\n            <TrashCanIcon sizeClassName=\"w-3.5 h-3.5\" />\n          </Button>\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport default memo(ConversationInput);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/StudioTab/Conversation/NoFilesMessage.tsx",
    "content": "import { memo, useCallback, useContext, useMemo } from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { ProjectContext } from '../../../../context/projectContext';\nimport { PlusSignIcon, WarningSignIcon } from '../../../../icons';\nimport Button from '../../../../components/Button';\nimport { CommandBarContext } from '../../../../context/commandBarContext';\nimport { CommandBarStepEnum } from '../../../../types/general';\n\ntype Props = {\n  studioId: string;\n};\n\nconst NoFilesMessage = ({ studioId }: Props) => {\n  useTranslation();\n  const { project } = useContext(ProjectContext.Current);\n  const { setChosenStep, setIsVisible } = useContext(\n    CommandBarContext.Handlers,\n  );\n\n  const onAddFile = useCallback(() => {\n    setChosenStep({ id: CommandBarStepEnum.SEARCH_FILES, data: { studioId } });\n    setIsVisible(true);\n  }, [studioId]);\n\n  const isEmptyContext = useMemo(() => {\n    const fullStudio = project?.studios.find(\n      (s) => s.id.toString() === studioId.toString(),\n    );\n    return !!fullStudio && !fullStudio.context.length;\n  }, [project?.studios, studioId]);\n\n  return isEmptyContext ? (\n    <div className=\"w-full flex items-center gap-4 p-4 select-none\">\n      <div className=\"w-7 h-7 flex-shrink-0 flex items-center justify-center rounded-full bg-yellow-subtle text-yellow\">\n        <WarningSignIcon sizeClassName=\"w-3.5 h-3.5\" />\n      </div>\n      <p className=\"body-s text-yellow flex-1\">\n        <Trans>\n          Add at least one context file before submitting your first request.\n        </Trans>\n      </p>\n      <Button variant=\"studio\" size=\"small\" onClick={onAddFile}>\n        <PlusSignIcon sizeClassName=\"w-4 h-4\" />\n        <Trans>Add files</Trans>\n      </Button>\n    </div>\n  ) : null;\n};\n\nexport default memo(NoFilesMessage);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/StudioTab/Conversation/StarterMessage.tsx",
    "content": "import { memo } from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { CodeStudioIcon } from '../../../../icons';\n\ntype Props = {};\n\nconst StarterMessage = ({}: Props) => {\n  useTranslation();\n  return (\n    <div className=\"flex items-start gap-5 rounded-md p-4\">\n      <div className=\"flex w-7 h-7 items-center justify-center rounded-full bg-brand-studio-subtle\">\n        <img className=\"bloop-head-img w-7 h-7\" alt=\"bloop\" />\n      </div>\n      <div className=\"flex flex-col gap-1 flex-1\">\n        <p className=\"body-base-b text-label-title select-none\">bloop</p>\n        <p className=\"text-label-base body-base\">\n          <Trans>\n            Hi, I am bloop! In{' '}\n            <span className=\"body-base-b inline-flex items-center gap-1 relative top-0.5\">\n              <CodeStudioIcon\n                sizeClassName=\"w-4 h-4\"\n                className=\"text-brand-studio\"\n              />\n              Studio mode\n            </span>{' '}\n            you can choose files from your codebase, write a prompt and generate\n            patches, scripts and tests.\n          </Trans>\n        </p>\n      </div>\n    </div>\n  );\n};\n\nexport default memo(StarterMessage);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/StudioTab/Conversation/index.tsx",
    "content": "import React, {\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport ScrollToBottom from '../../../../components/ScrollToBottom';\nimport { StudioContext } from '../../../../context/studiosContext';\nimport { TOKEN_LIMIT } from '../../../../consts/codeStudio';\nimport { BranchIcon, WarningSignIcon } from '../../../../icons';\nimport SpinLoaderContainer from '../../../../components/Loaders/SpinnerLoader';\nimport { StudioConversationMessageAuthor } from '../../../../types/general';\nimport { getTemplates } from '../../../../services/api';\nimport {\n  HistoryConversationTurn,\n  StudioTemplateType,\n} from '../../../../types/api';\nimport { checkEventKeys } from '../../../../utils/keyboardUtils';\nimport { useTemplateShortcut } from '../../../../consts/shortcuts';\nimport useKeyboardNavigation from '../../../../hooks/useKeyboardNavigation';\nimport KeyHintButton from '../../../../components/Button/KeyHintButton';\nimport Button from '../../../../components/Button';\nimport { CommandBarContext } from '../../../../context/commandBarContext';\nimport { mapConversation } from '../../../../utils/mappers';\nimport GeneratedDiff from './GeneratedDiff';\nimport ConversationInput from './Input';\nimport StarterMessage from './StarterMessage';\nimport NoFilesMessage from './NoFilesMessage';\nimport ContextError from './ContextError';\n\ntype Props = {\n  side: 'left' | 'right';\n  tabKey: string;\n  studioData: StudioContext;\n  isActiveTab: boolean;\n  studioId: string;\n  snapshot?: HistoryConversationTurn | null;\n};\n\nconst generateShortcut = ['cmd', 'entr'];\nconst stopShortcut = ['Esc'];\nconst noShortcut: string[] = [];\n\nconst Conversation = ({\n  side,\n  studioData,\n  isActiveTab,\n  studioId,\n  snapshot,\n}: Props) => {\n  const { t } = useTranslation();\n  const scrollableRef = useRef<HTMLDivElement>(null);\n  const inputRef = useRef<HTMLTextAreaElement>(null);\n  const [templates, setTemplates] = useState<StudioTemplateType[]>([]);\n  const [isDropdownShown, setIsDropdownShown] = useState(false);\n  const { isVisible } = useContext(CommandBarContext.General);\n  const templatesRef = useRef<HTMLButtonElement | null>(null);\n\n  const refetchTemplates = useCallback(() => {\n    getTemplates().then(setTemplates);\n  }, []);\n\n  useEffect(() => {\n    refetchTemplates();\n  }, []);\n  const [isScrollable, setIsScrollable] = useState(false);\n\n  useEffect(() => {\n    setTimeout(() => {\n      if (scrollableRef.current) {\n        setIsScrollable(\n          scrollableRef.current.scrollHeight >\n            scrollableRef.current.clientHeight,\n        );\n      }\n    }, 100);\n  }, [studioData?.conversation]);\n\n  const hasContextError = useMemo(() => {\n    return (\n      studioData.tokenCount?.per_file?.includes(null) ||\n      studioData.tokenCount?.per_doc_file?.includes(null)\n    );\n  }, [studioData.tokenCount]);\n\n  const isTokenLimitExceeded = useMemo(() => {\n    return (studioData.tokenCount?.total || 0) > TOKEN_LIMIT;\n  }, [studioData.tokenCount?.total]);\n\n  const handleKeyEvent = useCallback(\n    (e: KeyboardEvent) => {\n      if (checkEventKeys(e, generateShortcut)) {\n        e.preventDefault();\n        e.stopPropagation();\n        if (\n          studioData.inputValue &&\n          !isTokenLimitExceeded &&\n          !hasContextError &&\n          !snapshot\n          // && !isChangeUnsaved\n        ) {\n          studioData.onSubmit();\n        }\n      }\n      if (checkEventKeys(e, stopShortcut) && studioData.isLoading) {\n        e.preventDefault();\n        e.stopPropagation();\n        studioData.handleCancel();\n      }\n      if (checkEventKeys(e, useTemplateShortcut)) {\n        templatesRef.current?.parentElement?.click();\n      }\n    },\n    [\n      studioData.inputValue,\n      isTokenLimitExceeded,\n      studioData.onSubmit,\n      studioData.isLoading,\n      studioData.handleCancel,\n      snapshot,\n    ],\n  );\n  useKeyboardNavigation(\n    handleKeyEvent,\n    !isActiveTab || isDropdownShown || isVisible || !!snapshot,\n  );\n\n  const hasCodeBlock = useMemo(() => {\n    return studioData.conversation.some(\n      (m) =>\n        m.author === StudioConversationMessageAuthor.ASSISTANT &&\n        m.message.includes('```'),\n    );\n  }, [studioData.conversation]);\n\n  const isDiffForLocalRepo = useMemo(() => {\n    return studioData.diff?.chunks.find((c) => c.repo.startsWith('local//'));\n  }, [studioData.diff]);\n\n  const mappedSnapshotConversation = useMemo(() => {\n    if (!snapshot) {\n      return null;\n    }\n    return mapConversation(snapshot.messages);\n  }, [snapshot]);\n\n  return !studioData ? null : (\n    <div className=\"w-full max-w-2xl mx-auto flex flex-col flex-1 overflow-auto\">\n      <ScrollToBottom\n        className=\"max-w-full flex flex-col overflow-auto\"\n        wrapperRef={scrollableRef}\n        key={snapshot?.id}\n      >\n        <StarterMessage />\n        <NoFilesMessage studioId={studioId} />\n        {(mappedSnapshotConversation || studioData.conversation).map((m, i) => (\n          <ConversationInput\n            key={i}\n            author={m.author}\n            message={m.error || m.message}\n            isLoading={m.isLoading}\n            onMessageChange={studioData.onMessageChange}\n            onMessageRemoved={studioData.onMessageRemoved}\n            i={i}\n            isTokenLimitExceeded={isTokenLimitExceeded}\n            isLast={i === studioData.conversation.length - 1}\n            side={side}\n            templates={templates}\n            isActiveTab={isActiveTab}\n            setIsDropdownShown={setIsDropdownShown}\n          />\n        ))}\n        {!!studioData.diff && (\n          <GeneratedDiff\n            diff={studioData.diff}\n            onDiffRemoved={studioData.onDiffRemoved}\n            onDiffChanged={studioData.onDiffChanged}\n            applyError={studioData.isDiffApplyError}\n          />\n        )}\n        {(studioData.isDiffApplied ||\n          studioData.waitingForDiff ||\n          studioData.isDiffGenFailed) && (\n          <div\n            className={`w-full flex items-center py-2 px-3 my-3 ${\n              studioData.waitingForDiff ? 'justify-between' : 'justify-center'\n            } rounded gap-3 border ${\n              studioData.isDiffGenFailed\n                ? 'bg-red-subtle border-red-subtle-hover text-red'\n                : studioData.waitingForDiff\n                ? 'bg-bg-base border-bg-border text-green'\n                : 'bg-blue-subtle border-blue-subtle-hover text-blue'\n            } body-s-b`}\n          >\n            <div className=\"flex gap-3 items-center\">\n              {studioData.isDiffGenFailed ? (\n                <WarningSignIcon sizeClassName=\"w-3.5 h-3.5\" />\n              ) : studioData.waitingForDiff ? (\n                <SpinLoaderContainer\n                  sizeClassName=\"w-3.5 h-3.5\"\n                  colorClassName=\"text-green\"\n                />\n              ) : (\n                <BranchIcon sizeClassName=\"w-3.5 h-3.5\" />\n              )}\n              <Trans>\n                {studioData.isDiffGenFailed\n                  ? 'Diff generation failed'\n                  : studioData.waitingForDiff\n                  ? 'Generating diff...'\n                  : 'The diff has been applied locally.'}\n              </Trans>\n            </div>\n            {studioData.waitingForDiff && (\n              <Button\n                onClick={studioData.handleCancelDiff}\n                variant=\"danger\"\n                size=\"mini\"\n              >\n                <Trans>Cancel</Trans>\n              </Button>\n            )}\n          </div>\n        )}\n        {hasContextError && <ContextError />}\n        {!studioData.isLoading &&\n          !snapshot &&\n          !studioData.waitingForDiff &&\n          !studioData.diff &&\n          !(\n            studioData.conversation[studioData.conversation.length - 1]\n              ?.author === StudioConversationMessageAuthor.USER\n          ) && (\n            <ConversationInput\n              key={'new'}\n              author={studioData.inputAuthor}\n              message={studioData.inputValue}\n              isLoading={false}\n              onMessageChange={studioData.onMessageChange}\n              inputRef={inputRef}\n              isTokenLimitExceeded={isTokenLimitExceeded}\n              isLast\n              side={side}\n              templates={templates}\n              setIsDropdownShown={setIsDropdownShown}\n              templatesRef={templatesRef}\n              isActiveTab={isActiveTab}\n            />\n          )}\n      </ScrollToBottom>\n      {!snapshot && (\n        <div\n          className={`flex items-start justify-between flex-shrink-0 w-full p-4 gap-4 border-t border-bg-border ${\n            isScrollable\n              ? 'bg-bg-base border-x rounded-tl-md rounded-tr-md'\n              : ''\n          }`}\n        >\n          <div className=\"flex gap-2 items-center select-none\">\n            <KeyHintButton\n              text={t('Clear conversation')}\n              shortcut={noShortcut}\n              onClick={studioData.clearConversation}\n              disabled={!!snapshot}\n            />\n          </div>\n          <div className=\"flex gap-2 items-center select-none\">\n            {studioData.isLoading ? (\n              <KeyHintButton\n                text={t('Stop generating')}\n                shortcut={stopShortcut}\n                onClick={studioData.handleCancel}\n              />\n            ) : (\n              <>\n                {(hasCodeBlock || studioData.diff) &&\n                  (studioData.isDiffApplied ||\n                  studioData.waitingForDiff ? null : !studioData.diff ? (\n                    <KeyHintButton\n                      text={t('Apply changes')}\n                      shortcut={noShortcut}\n                      onClick={studioData.handleApplyChanges}\n                      disabled={!!snapshot}\n                    />\n                  ) : (\n                    <>\n                      <KeyHintButton\n                        text={t(isDiffForLocalRepo ? 'Cancel' : 'Close')}\n                        shortcut={noShortcut}\n                        onClick={() => studioData.setDiff(null)}\n                      />\n                      {isDiffForLocalRepo && (\n                        <KeyHintButton\n                          text={'Apply locally'}\n                          shortcut={noShortcut}\n                          onClick={studioData.handleConfirmDiff}\n                        />\n                      )}\n                    </>\n                  ))}\n                {!studioData.diff && (\n                  <KeyHintButton\n                    text={t('Generate')}\n                    shortcut={generateShortcut}\n                    onClick={studioData.onSubmit}\n                    disabled={\n                      hasContextError ||\n                      isTokenLimitExceeded ||\n                      !studioData.inputValue ||\n                      !!snapshot\n                    }\n                  />\n                )}\n              </>\n            )}\n          </div>\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport default memo(Conversation);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/StudioTab/DeprecatedClientModal.tsx",
    "content": "import { useContext } from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { CloseSignIcon } from '../../../icons';\nimport { DeviceContext } from '../../../context/deviceContext';\nimport Button from '../../../components/Button';\nimport Modal from '../../../components/Modal';\n\ntype Props = {\n  isOpen: boolean;\n  onClose: () => void;\n};\n\nconst DeprecatedClientModal = ({ isOpen, onClose }: Props) => {\n  const { t } = useTranslation();\n  const { openLink, relaunch } = useContext(DeviceContext);\n  return (\n    <Modal isVisible={isOpen} onClose={onClose} noBg>\n      <div className=\"w-[32.75rem] flex flex-col items-start rounded-md border border-bg-border bg-bg-shade shadow-float overflow-hidden\">\n        <div className=\"p-10 relative flex flex-col gap-8\">\n          <div className=\"flex flex-col gap-4\">\n            <h4 className=\"title-m-b text-label-title\">\n              <Trans>Update Required</Trans>\n            </h4>\n            <p className=\"body-base text-label-base\">\n              <Trans>\n                We&apos;ve made some exciting enhancements to bloop! To continue\n                enjoying the full functionality, including the natural language\n                search feature, please update your app to the latest version.\n              </Trans>\n            </p>\n            <p className=\"body-base text-label-base\">\n              <Trans>\n                To update your app, please visit our releases page on GitHub and\n                download the latest version manually. Thank you for using bloop.\n              </Trans>\n            </p>\n          </div>\n          <div className=\"flex justify-between items-center\">\n            <button\n              className=\"body-mini-b text-label-link\"\n              onClick={() =>\n                openLink('https://github.com/BloopAI/bloop/releases')\n              }\n            >\n              <Trans>Visit the downloads page</Trans>\n            </button>\n            <Button onClick={relaunch}>\n              <Trans>Restart the app</Trans>\n            </Button>\n          </div>\n        </div>\n        <div className=\"absolute top-3 right-3\">\n          <Button\n            variant=\"tertiary\"\n            size=\"small\"\n            onClick={onClose}\n            onlyIcon\n            title={t('Close')}\n          >\n            <CloseSignIcon sizeClassName=\"w-3.5 h-3.5\" />\n          </Button>\n        </div>\n      </div>\n    </Modal>\n  );\n};\n\nexport default DeprecatedClientModal;\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/StudioTab/StudioPersistentState.tsx",
    "content": "import {\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport throttle from 'lodash.throttle';\nimport { DeviceContext } from '../../../context/deviceContext';\nimport { ProjectContext } from '../../../context/projectContext';\nimport {\n  StudioConversationMessage,\n  StudioConversationMessageAuthor,\n  StudioTabType,\n} from '../../../types/general';\nimport { TabsContext } from '../../../context/tabsContext';\nimport {\n  API_BASE_URL,\n  confirmStudioDiff,\n  generateStudioDiff,\n  getCodeStudio,\n  patchCodeStudio,\n} from '../../../services/api';\nimport { StudiosContext } from '../../../context/studiosContext';\nimport {\n  CodeStudioTokenCountType,\n  CodeStudioType,\n  GeneratedCodeDiff,\n} from '../../../types/api';\nimport { mapConversation } from '../../../utils/mappers';\n\ntype Props = {\n  tabKey: string; //studioId\n  side: 'left' | 'right';\n};\n\nconst throttledPatch = throttle(\n  (projectId, studioId, data) => {\n    return patchCodeStudio(projectId, studioId, data);\n  },\n  2000,\n  { leading: false, trailing: true },\n);\n\nconst StudioPersistentState = ({ tabKey, side }: Props) => {\n  const { t } = useTranslation();\n  const { project, refreshCurrentProjectStudios } = useContext(\n    ProjectContext.Current,\n  );\n  const { setStudios } = useContext(StudiosContext);\n  const { updateTabProperty } = useContext(TabsContext.Handlers);\n  const inputRef = useRef<HTMLTextAreaElement | null>(null);\n  const abortController = useRef<AbortController | null>(null);\n  const eventSource = useRef<EventSource | null>(null);\n\n  const [name, setName] = useState('');\n\n  const [inputValue, setInputValue] = useState('');\n  useEffect(() => {\n    setStudios((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], inputValue } };\n    });\n  }, [inputValue]);\n\n  const [isLoading, setIsLoading] = useState(false);\n  useEffect(() => {\n    setStudios((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], isLoading } };\n    });\n  }, [isLoading]);\n\n  const [conversation, setConversation] = useState<StudioConversationMessage[]>(\n    [],\n  );\n  useEffect(() => {\n    setStudios((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], conversation } };\n    });\n  }, [conversation]);\n\n  const [inputAuthor, setInputAuthor] = useState(\n    StudioConversationMessageAuthor.USER,\n  );\n  useEffect(() => {\n    setStudios((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], inputAuthor } };\n    });\n  }, [inputAuthor]);\n\n  const [tokenCount, setTokenCount] = useState<CodeStudioTokenCountType | null>(\n    null,\n  );\n  useEffect(() => {\n    setStudios((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], tokenCount } };\n    });\n  }, [tokenCount]);\n\n  const [waitingForDiff, setWaitingForDiff] = useState(false);\n  useEffect(() => {\n    setStudios((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], waitingForDiff } };\n    });\n  }, [waitingForDiff]);\n\n  const [isDiffApplied, setDiffApplied] = useState(false);\n  useEffect(() => {\n    setStudios((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], isDiffApplied } };\n    });\n  }, [isDiffApplied]);\n\n  const [isDiffApplyError, setDiffApplyError] = useState(false);\n  useEffect(() => {\n    setStudios((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], isDiffApplyError } };\n    });\n  }, [isDiffApplyError]);\n\n  const [isDiffGenFailed, setDiffGenFailed] = useState(false);\n  useEffect(() => {\n    setStudios((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], isDiffGenFailed } };\n    });\n  }, [isDiffGenFailed]);\n\n  const [diff, setDiff] = useState<GeneratedCodeDiff | null>(null);\n  useEffect(() => {\n    setStudios((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], diff } };\n    });\n  }, [diff]);\n\n  useEffect(() => {\n    setStudios((prev) => {\n      return {\n        ...prev,\n        [tabKey]: {\n          ...prev[tabKey],\n          setConversation,\n          setDiff,\n        },\n      };\n    });\n  }, []);\n\n  const refetchCodeStudio = useCallback(\n    (key?: keyof CodeStudioType) => {\n      if (tabKey && project?.id) {\n        return getCodeStudio(project.id, tabKey).then((s) => {\n          if (key) {\n            if (key === 'token_counts') {\n              setTokenCount(s.token_counts);\n            }\n            if (key === 'messages') {\n              const mappedConv = mapConversation(s.messages);\n              setConversation(mappedConv);\n            }\n          } else {\n            const mappedConv = mapConversation(s.messages);\n            setTokenCount(s.token_counts);\n            setName(s.name);\n            if (\n              mappedConv[mappedConv.length - 1]?.author ===\n              StudioConversationMessageAuthor.USER\n              // && !isPreviewing\n            ) {\n              const editedMessage = mappedConv[s.messages.length - 1];\n              setInputValue((prev) =>\n                prev < editedMessage.message ? editedMessage.message : prev,\n              );\n              setConversation(mappedConv.slice(0, -1));\n            } else {\n              setConversation(mappedConv);\n            }\n          }\n        });\n      }\n    },\n    [tabKey, project?.id, side],\n  );\n  useEffect(() => {\n    setStudios((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], refetchCodeStudio } };\n    });\n  }, [refetchCodeStudio]);\n\n  useEffect(() => {\n    refetchCodeStudio();\n  }, [refetchCodeStudio, project?.repos, project?.studios]);\n\n  useEffect(() => {\n    if (name) {\n      updateTabProperty<StudioTabType, 'title'>(tabKey, 'title', name, side);\n      refreshCurrentProjectStudios();\n    }\n  }, [name]);\n\n  const setInput = useCallback((value: StudioConversationMessage) => {\n    setInputValue(value.message);\n    setInputAuthor(value.author);\n    // Focus on the input\n    if (inputRef.current) {\n      inputRef.current.focus();\n    }\n  }, []);\n\n  const saveConversation = useCallback(\n    async (\n      force?: boolean,\n      newConversation?: StudioConversationMessage[],\n      newInput?: string,\n    ) => {\n      if (!project?.id) {\n        return;\n      }\n      const messages: ({ User: string } | { Assistant: string })[] = (\n        newConversation || conversation\n      )\n        .map((c) => ({ [c.author as 'User']: c.message }))\n        .concat(\n          !newConversation && (newInput || inputValue)\n            ? [{ [inputAuthor as 'User']: newInput || inputValue }]\n            : [],\n        );\n      if (force) {\n        await patchCodeStudio(project.id, tabKey, {\n          messages,\n        });\n      } else {\n        throttledPatch(project.id, tabKey, {\n          messages,\n        });\n      }\n    },\n    [conversation, inputValue, inputAuthor],\n  );\n\n  const onMessageChange = useCallback(\n    (message: string, i?: number) => {\n      if (i === undefined) {\n        setInputValue(message);\n        saveConversation(false, undefined, message);\n      } else {\n        setConversation((prev) => {\n          const newConv = JSON.parse(JSON.stringify(prev));\n          newConv[i].message = message;\n          saveConversation(false, newConv);\n          return newConv;\n        });\n      }\n    },\n    [saveConversation],\n  );\n  useEffect(() => {\n    setStudios((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], onMessageChange } };\n    });\n  }, [onMessageChange]);\n\n  const handleCancel = useCallback(() => {\n    eventSource.current?.close();\n    setIsLoading(false);\n    if (\n      conversation[conversation.length - 1]?.author ===\n      StudioConversationMessageAuthor.USER\n    ) {\n      const editedMessage = conversation[conversation.length - 1];\n      setInputValue((prev) =>\n        prev < editedMessage.message ? editedMessage.message : prev,\n      );\n      setConversation(conversation.slice(0, -1));\n      saveConversation(false, conversation.slice(0, -1));\n    } else {\n      const newConv = [\n        ...conversation.slice(0, -1),\n        { ...conversation[conversation.length - 1], isLoading: false },\n      ];\n      setConversation(newConv);\n      saveConversation();\n    }\n  }, [conversation]);\n  useEffect(() => {\n    setStudios((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], handleCancel } };\n    });\n  }, [handleCancel]);\n\n  const onSubmit = useCallback(async () => {\n    if (!inputValue || !project?.id) {\n      return;\n    }\n    await saveConversation(true);\n    setDiffApplied(false);\n    setDiffApplyError(false);\n    setDiff(null);\n    setDiffGenFailed(false);\n    setConversation((prev) => [\n      ...prev,\n      { message: inputValue, author: inputAuthor },\n    ]);\n    setInput({\n      author: StudioConversationMessageAuthor.USER,\n      message: '',\n    });\n    setIsLoading(true);\n\n    eventSource.current?.close();\n    eventSource.current = new EventSource(\n      `${API_BASE_URL}/projects/${project.id}/studios/${tabKey}/generate`,\n    );\n    eventSource.current.onerror = (err) => {\n      console.log('SSE error', err);\n      setConversation((prev) => {\n        const newConv = [\n          ...prev,\n          {\n            author: StudioConversationMessageAuthor.ASSISTANT,\n            message: '',\n            isLoading: false,\n            error: t(\n              \"We couldn't answer your question. You can try asking again in a few moments, or rephrasing your question.\",\n            ),\n          },\n        ];\n        saveConversation(false, newConv);\n        return newConv;\n      });\n      setIsLoading(false);\n      eventSource.current?.close();\n    };\n    setConversation((prev) => {\n      return [\n        ...prev,\n        {\n          author: StudioConversationMessageAuthor.ASSISTANT,\n          isLoading: true,\n          message: '',\n        },\n      ];\n    });\n    let i = 0;\n    eventSource.current.onmessage = (ev) => {\n      if (ev.data === '[DONE]') {\n        eventSource.current?.close();\n        setIsLoading(false);\n        setConversation((prev) => {\n          return [\n            ...prev.slice(0, -1),\n            {\n              ...prev[prev.length - 1],\n              isLoading: false,\n            },\n          ];\n        });\n        refetchCodeStudio();\n        return;\n      }\n      try {\n        const data = JSON.parse(ev.data);\n        if (data.Ok) {\n          const newMessage = data.Ok;\n          setConversation((prev) => {\n            const newConv = [\n              ...prev.slice(0, -1),\n              {\n                author: StudioConversationMessageAuthor.ASSISTANT,\n                isLoading: true,\n                message: newMessage,\n              },\n            ];\n            saveConversation(false, newConv);\n            return newConv;\n          });\n          i++;\n        } else if (data.Err) {\n          setConversation((prev) => {\n            const newConv = [\n              ...prev,\n              {\n                author: StudioConversationMessageAuthor.ASSISTANT,\n                isLoading: false,\n                message: data.Err,\n              },\n            ];\n            saveConversation(false, newConv);\n            return newConv;\n          });\n        }\n      } catch (err) {\n        setIsLoading(false);\n        console.log('failed to parse response', err);\n      }\n    };\n  }, [\n    tabKey,\n    conversation,\n    inputValue,\n    inputAuthor,\n    saveConversation,\n    project?.id,\n  ]);\n  useEffect(() => {\n    setStudios((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], onSubmit } };\n    });\n  }, [onSubmit]);\n\n  useEffect(() => {\n    return () => {\n      eventSource.current?.close();\n    };\n  }, []);\n\n  const onMessageRemoved = useCallback(\n    async (i: number, andSubsequent?: boolean) => {\n      if (andSubsequent) {\n        // Set input to the message being removed\n        setInput(conversation[i]);\n      }\n      setWaitingForDiff(false);\n      setDiffGenFailed(false);\n      setDiff(null);\n      setDiffApplied(false);\n      setDiffApplyError(false);\n      if (\n        i === conversation.length - 1 &&\n        conversation[i].author === StudioConversationMessageAuthor.ASSISTANT &&\n        isLoading\n      ) {\n        handleCancel();\n      }\n      setConversation((prev) => {\n        const newConv = prev.filter((_, j) =>\n          andSubsequent ? i > j : i !== j,\n        );\n        if (\n          newConv[newConv.length - 1]?.author ===\n          StudioConversationMessageAuthor.USER\n        ) {\n          const editedMessage = newConv[messages.length - 1];\n          setInputValue(editedMessage.message);\n          return newConv.slice(0, -1);\n        }\n        return newConv;\n      });\n\n      const messages = conversation.filter((m, j) =>\n        andSubsequent ? i > j : i !== j,\n      );\n      await saveConversation(true, messages);\n      refetchCodeStudio('token_counts');\n    },\n    [conversation, isLoading],\n  );\n  useEffect(() => {\n    setStudios((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], onMessageRemoved } };\n    });\n  }, [onMessageRemoved]);\n\n  const clearConversation = useCallback(async () => {\n    if (!project?.id) {\n      return;\n    }\n    await patchCodeStudio(project.id, tabKey, {\n      messages: [],\n    });\n    setDiff(null);\n    setWaitingForDiff(false);\n    setDiffApplied(false);\n    setDiffApplyError(false);\n    setDiffGenFailed(false);\n    setInput({\n      author: StudioConversationMessageAuthor.USER,\n      message: '',\n    });\n    setConversation([]);\n    refetchCodeStudio('token_counts');\n  }, [tabKey, project?.id]);\n  useEffect(() => {\n    setStudios((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], clearConversation } };\n    });\n  }, [clearConversation]);\n\n  const handleApplyChanges = useCallback(async () => {\n    if (!project?.id) {\n      return;\n    }\n    setWaitingForDiff(true);\n    setDiffGenFailed(false);\n    try {\n      abortController.current = new AbortController();\n      const resp = await generateStudioDiff(\n        project.id,\n        tabKey,\n        abortController.current?.signal,\n      );\n      setDiff(resp);\n    } catch (err) {\n      console.log(err);\n      setDiffGenFailed(true);\n    } finally {\n      setWaitingForDiff(false);\n    }\n  }, [tabKey, project?.id]);\n  useEffect(() => {\n    setStudios((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], handleApplyChanges } };\n    });\n  }, [handleApplyChanges]);\n\n  const handleCancelDiff = useCallback(() => {\n    abortController.current?.abort();\n    setWaitingForDiff(false);\n  }, []);\n  useEffect(() => {\n    setStudios((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], handleCancelDiff } };\n    });\n  }, [handleCancelDiff]);\n\n  const handleConfirmDiff = useCallback(async () => {\n    if (!diff || !project?.id) {\n      return;\n    }\n    const result = diff.chunks.map((c) => c.raw_patch).join('');\n    try {\n      await confirmStudioDiff(project.id, tabKey, result);\n      setDiff(null);\n      setDiffApplied(true);\n    } catch (err: unknown) {\n      console.log(err);\n      // @ts-ignore\n      if (err.code !== 'ERR_CANCELED') {\n        setDiffApplyError(true);\n      }\n    }\n  }, [tabKey, diff, project?.id]);\n  useEffect(() => {\n    setStudios((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], handleConfirmDiff } };\n    });\n  }, [handleConfirmDiff]);\n\n  const onDiffChanged = useCallback((i: number, v: string) => {\n    setDiff((prev) => {\n      const newValue: GeneratedCodeDiff = JSON.parse(JSON.stringify(prev));\n      newValue.chunks[i].raw_patch = v;\n      newValue.chunks[i].hunks = v\n        .split(/\\n(?=@@ -)/)\n        .slice(1)\n        .map((h) => {\n          const startLine = h.match(/@@ -(\\d+)/)?.[1];\n          return {\n            line_start: Number(startLine),\n            patch: h.split('\\n').slice(1).join('\\n'),\n          };\n        });\n      return newValue;\n    });\n  }, []);\n  useEffect(() => {\n    setStudios((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], onDiffChanged } };\n    });\n  }, [onDiffChanged]);\n\n  const onDiffRemoved = useCallback((i: number) => {\n    setDiff((prev) => {\n      const newValue: GeneratedCodeDiff = JSON.parse(JSON.stringify(prev));\n      newValue.chunks.splice(i, 1);\n      if (!newValue.chunks.length) {\n        return null;\n      }\n      return newValue;\n    });\n  }, []);\n  useEffect(() => {\n    setStudios((prev) => {\n      return { ...prev, [tabKey]: { ...prev[tabKey], onDiffRemoved } };\n    });\n  }, [onDiffRemoved]);\n\n  return null;\n};\n\nexport default memo(StudioPersistentState);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/StudioTab/index.tsx",
    "content": "import React, {\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n} from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { format } from 'date-fns';\nimport Button from '../../../components/Button';\nimport {\n  DateTimeCalendarIcon,\n  FileWithSparksIcon,\n  MoreHorizontalIcon,\n  PromptIcon,\n  SplitViewIcon,\n} from '../../../icons';\nimport Dropdown from '../../../components/Dropdown';\nimport { checkEventKeys } from '../../../utils/keyboardUtils';\nimport useKeyboardNavigation from '../../../hooks/useKeyboardNavigation';\nimport { TabsContext } from '../../../context/tabsContext';\nimport { CommandBarStepEnum, StudioTabType } from '../../../types/general';\nimport { ProjectContext } from '../../../context/projectContext';\nimport { CommandBarContext } from '../../../context/commandBarContext';\nimport { UIContext } from '../../../context/uiContext';\nimport TokenUsage from '../../../components/TokenUsage';\nimport { StudioContext, StudiosContext } from '../../../context/studiosContext';\nimport { TOKEN_LIMIT } from '../../../consts/codeStudio';\nimport {\n  addToStudioShortcut,\n  escapeShortcut,\n  openInSplitViewShortcut,\n  saveShortcut,\n} from '../../../consts/shortcuts';\nimport { getDateFnsLocale } from '../../../utils';\nimport { LocaleContext } from '../../../context/localeContext';\nimport { patchCodeStudio } from '../../../services/api';\nimport ActionsDropdown from './ActionsDropdown';\nimport Conversation from './Conversation';\n\ntype Props = StudioTabType & {\n  noBorder?: boolean;\n  side: 'left' | 'right';\n  tabKey: string;\n  handleMoveToAnotherSide: () => void;\n};\n\nconst StudioTab = ({\n  noBorder,\n  side,\n  studioId,\n  tabKey,\n  handleMoveToAnotherSide,\n  snapshot,\n}: Props) => {\n  const { t } = useTranslation();\n  const { locale } = useContext(LocaleContext);\n  const { focusedPanel } = useContext(TabsContext.FocusedPanel);\n  const { studios } = useContext(StudiosContext);\n  const { closeTab, updateTabProperty } = useContext(TabsContext.Handlers);\n  const { isLeftSidebarFocused } = useContext(UIContext.Focus);\n  const { setFocusedTabItems, setChosenStep, setIsVisible } = useContext(\n    CommandBarContext.Handlers,\n  );\n  const { project, refreshCurrentProjectStudios } = useContext(\n    ProjectContext.Current,\n  );\n\n  const studioData: StudioContext | undefined = useMemo(\n    () => studios[tabKey],\n    [studios, tabKey],\n  );\n\n  const dropdownComponentProps = useMemo(() => {\n    return {\n      handleMoveToAnotherSide,\n      studioId,\n      projectId: project?.id,\n      tabKey,\n      closeTab,\n      refreshCurrentProjectStudios,\n      side,\n      clearConversation: studioData?.clearConversation,\n    };\n  }, [\n    handleMoveToAnotherSide,\n    studioId,\n    closeTab,\n    project?.id,\n    tabKey,\n    refreshCurrentProjectStudios,\n    studioData?.clearConversation,\n    side,\n  ]);\n\n  const handleAddFiles = useCallback(() => {\n    setChosenStep({\n      id: CommandBarStepEnum.SEARCH_FILES,\n      data: { studioId },\n    });\n    setIsVisible(true);\n  }, [studioId]);\n\n  const cancelSnapshot = useCallback(() => {\n    updateTabProperty<StudioTabType, 'snapshot'>(\n      tabKey,\n      'snapshot',\n      undefined,\n      side,\n    );\n  }, [updateTabProperty, side, tabKey]);\n\n  const saveSnapshot = useCallback(async () => {\n    if (snapshot && project?.id) {\n      await patchCodeStudio(project.id, tabKey, {\n        context: snapshot.context,\n        messages: snapshot.messages,\n      });\n      studioData?.refetchCodeStudio?.();\n      refreshCurrentProjectStudios();\n      cancelSnapshot();\n    }\n  }, [tabKey, snapshot, project?.id, studioData, cancelSnapshot]);\n\n  const handleKeyEvent = useCallback(\n    (e: KeyboardEvent) => {\n      if (checkEventKeys(e, openInSplitViewShortcut)) {\n        handleMoveToAnotherSide();\n      } else if (checkEventKeys(e, addToStudioShortcut)) {\n        handleAddFiles();\n      } else if (checkEventKeys(e, escapeShortcut) && snapshot) {\n        e.preventDefault();\n        e.stopPropagation();\n        cancelSnapshot();\n      } else if (checkEventKeys(e, saveShortcut) && snapshot) {\n        e.preventDefault();\n        e.stopPropagation();\n        saveSnapshot();\n      }\n    },\n    [handleMoveToAnotherSide, handleAddFiles, snapshot, cancelSnapshot],\n  );\n  useKeyboardNavigation(\n    handleKeyEvent,\n    focusedPanel !== side || isLeftSidebarFocused,\n  );\n\n  useEffect(() => {\n    if (focusedPanel === side) {\n      setFocusedTabItems([\n        {\n          label: t('Open in split view'),\n          Icon: SplitViewIcon,\n          id: 'split_view',\n          key: 'split_view',\n          onClick: handleMoveToAnotherSide,\n          closeOnClick: true,\n          shortcut: openInSplitViewShortcut,\n          footerHint: '',\n          footerBtns: [{ label: t('Move'), shortcut: ['entr'] }],\n        },\n        {\n          label: t('Add files to studio'),\n          Icon: FileWithSparksIcon,\n          id: 'add_file_to_studio',\n          key: 'add_file_to_studio',\n          onClick: handleAddFiles,\n          shortcut: addToStudioShortcut,\n          footerHint: '',\n          footerBtns: [{ label: t('Search files'), shortcut: ['entr'] }],\n        },\n      ]);\n    }\n  }, [focusedPanel, side, handleMoveToAnotherSide]);\n\n  return (\n    <div\n      className={`flex flex-col flex-1 h-full overflow-auto ${\n        noBorder ? '' : 'border-l border-bg-border'\n      }`}\n    >\n      <div className=\"w-full h-10 px-4 flex justify-between gap-2 items-center flex-shrink-0 border-b border-bg-border bg-bg-sub\">\n        <div className=\"flex items-center gap-3 body-s text-label-title ellipsis\">\n          {snapshot ? (\n            <DateTimeCalendarIcon sizeClassName=\"w-4 h-4\" />\n          ) : (\n            <PromptIcon sizeClassName=\"w-4 h-4\" />\n          )}\n          <span className=\"ellipsis\">\n            {snapshot ? (\n              format(\n                new Date(snapshot.modified_at + '.000Z'),\n                'd MMM yyyy · hh:mm a',\n                getDateFnsLocale(locale),\n              )\n            ) : (\n              <Trans>Prompts</Trans>\n            )}\n          </span>\n        </div>\n        <div className=\"flex items-center gap-1.5 flex-shrink-0\">\n          <TokenUsage\n            percent={\n              (((snapshot?.token_counts || studioData?.tokenCount)?.total ||\n                0) /\n                TOKEN_LIMIT) *\n              100\n            }\n          />\n          <span className=\"body-mini-b text-label-base\">\n            <Trans\n              values={{\n                count:\n                  (snapshot?.token_counts || studioData?.tokenCount)?.total ||\n                  0,\n                total: TOKEN_LIMIT,\n              }}\n            >\n              <span\n                className={\n                  ((snapshot?.token_counts || studioData?.tokenCount)?.total ||\n                    0) > TOKEN_LIMIT\n                    ? 'text-bg-danger'\n                    : ''\n                }\n              >\n                #\n              </span>{' '}\n              of # tokens\n            </Trans>\n          </span>\n        </div>\n        <div className=\"flex gap-2 items-center flex-shrink-0 select-none\">\n          {focusedPanel === side && snapshot ? (\n            <div className=\"flex items-center gap-2 flex-shrink-0\">\n              <Button\n                variant=\"tertiary\"\n                size=\"mini\"\n                title={t('Go to current state')}\n                shortcut={escapeShortcut}\n                tooltipPlacement=\"bottom\"\n                onClick={cancelSnapshot}\n              >\n                <Trans>Back to current</Trans>\n              </Button>\n              <Button\n                variant=\"studio\"\n                size=\"mini\"\n                title={t('Continue from this state')}\n                tooltipPlacement=\"bottom\"\n                shortcut={saveShortcut}\n                onClick={saveSnapshot}\n              >\n                <Trans>Restore session</Trans>\n              </Button>\n            </div>\n          ) : (\n            <Dropdown\n              DropdownComponent={ActionsDropdown}\n              dropdownComponentProps={dropdownComponentProps}\n              appendTo={document.body}\n              dropdownPlacement=\"bottom-end\"\n            >\n              <Button\n                variant=\"tertiary\"\n                size=\"mini\"\n                onlyIcon\n                title={t('More actions')}\n              >\n                <MoreHorizontalIcon sizeClassName=\"w-3.5 h-3.5\" />\n              </Button>\n            </Dropdown>\n          )}\n        </div>\n      </div>\n      <div className=\"flex-1 flex flex-col max-w-full px-4 overflow-auto\">\n        {!!studioData && (\n          <Conversation\n            side={side}\n            tabKey={tabKey}\n            studioData={studioData}\n            studioId={studioId}\n            isActiveTab={focusedPanel === side && !isLeftSidebarFocused}\n            snapshot={snapshot}\n          />\n        )}\n      </div>\n    </div>\n  );\n};\n\nexport default memo(StudioTab);\n"
  },
  {
    "path": "client/src/Project/CurrentTabContent/index.tsx",
    "content": "import React, { memo, useCallback, useContext } from 'react';\nimport { useDrop } from 'react-dnd';\nimport { Trans } from 'react-i18next';\nimport { TabsContext } from '../../context/tabsContext';\nimport { DraggableTabItem, TabType, TabTypesEnum } from '../../types/general';\nimport { SplitViewIcon } from '../../icons';\nimport { UIContext } from '../../context/uiContext';\nimport EmptyTab from './EmptyTab';\nimport FileTab from './FileTab';\nimport Header from './Header';\nimport ChatTab from './ChatTab';\nimport StudioTab from './StudioTab';\nimport DocTab from './DocTab';\n\ntype Props = {\n  side: 'left' | 'right';\n  onDrop: (t: TabType) => void;\n  moveToAnotherSide: (t: TabType) => void;\n  shouldStretch?: boolean;\n};\n\nconst CurrentTabContent = ({\n  side,\n  onDrop,\n  shouldStretch,\n  moveToAnotherSide,\n}: Props) => {\n  const { tab } = useContext(\n    TabsContext[side === 'left' ? 'CurrentLeft' : 'CurrentRight'],\n  );\n  const { setFocusedPanel } = useContext(TabsContext.Handlers);\n  const { setIsLeftSidebarFocused } = useContext(UIContext.Focus);\n\n  const [{ isOver, canDrop }, drop] = useDrop(\n    () => ({\n      accept: side === 'right' ? 'tab-left' : 'tab-right',\n      canDrop: (i: DraggableTabItem) => i.side !== side,\n      drop: (item: DraggableTabItem) => {\n        onDrop(item.t);\n      },\n      collect: (monitor) => ({\n        isOver: monitor.isOver(),\n        canDrop: monitor.canDrop(),\n      }),\n    }),\n    [onDrop],\n  );\n\n  const focusPanel = useCallback(() => {\n    setFocusedPanel(side);\n    setIsLeftSidebarFocused(false);\n  }, [side]);\n\n  const handleMoveToAnotherSide = useCallback(() => {\n    if (tab) {\n      moveToAnotherSide(tab);\n    }\n  }, [moveToAnotherSide, tab]);\n\n  return (\n    <div\n      className={`${\n        shouldStretch ? 'flex-1' : ''\n      } h-full flex flex-col overflow-hidden`}\n      onClick={focusPanel}\n    >\n      <Header side={side} />\n      <div className=\"overflow-hidden h-full flex-1 relative\" ref={drop}>\n        {tab?.type === TabTypesEnum.FILE ? (\n          <FileTab\n            {...tab}\n            key={tab.key}\n            tabKey={tab.key}\n            noBorder={side === 'left'}\n            side={side}\n            handleMoveToAnotherSide={handleMoveToAnotherSide}\n          />\n        ) : tab?.type === TabTypesEnum.CHAT ? (\n          <ChatTab\n            {...tab}\n            noBorder={side === 'left'}\n            side={side}\n            tabKey={tab.key}\n            key={tab.key}\n            handleMoveToAnotherSide={handleMoveToAnotherSide}\n          />\n        ) : tab?.type === TabTypesEnum.STUDIO ? (\n          <StudioTab\n            {...tab}\n            noBorder={side === 'left'}\n            side={side}\n            tabKey={tab.key}\n            key={tab.key}\n            handleMoveToAnotherSide={handleMoveToAnotherSide}\n          />\n        ) : tab?.type === TabTypesEnum.DOC ? (\n          <DocTab\n            {...tab}\n            noBorder={side === 'left'}\n            side={side}\n            tabKey={tab.key}\n            key={tab.key}\n            handleMoveToAnotherSide={handleMoveToAnotherSide}\n          />\n        ) : (\n          <EmptyTab />\n        )}\n        {isOver && canDrop && (\n          <div className=\"absolute top-0 bottom-0 left-0 right-0 bg-bg-sub\">\n            <div className=\"absolute w-full h-full bg-bg-selected flex flex-col\">\n              <div className=\"h-10 border-b border-bg-border w-full\" />\n              <div className=\"flex-1 flex items-center justify-center\">\n                <div className=\"flex items-center gap-3 text-label-base body-s-b\">\n                  <SplitViewIcon sizeClassName=\"w-4.5 h-4.5\" />\n                  <p>\n                    <Trans>Release to open in split view</Trans>\n                  </p>\n                </div>\n              </div>\n            </div>\n          </div>\n        )}\n      </div>\n    </div>\n  );\n};\n\nexport default memo(CurrentTabContent);\n"
  },
  {
    "path": "client/src/Project/EmptyProject.tsx",
    "content": "import React, { memo, useCallback, useContext } from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport Header from '../components/Header';\nimport { ShapesIcon } from '../icons';\nimport Button from '../components/Button';\nimport { CommandBarContext } from '../context/commandBarContext';\nimport { CommandBarStepEnum } from '../types/general';\nimport useShortcuts from '../hooks/useShortcuts';\n\ntype Props = {};\n\nconst EmptyProject = ({}: Props) => {\n  useTranslation();\n  const shortcut = useShortcuts(['cmd']);\n  const { setIsVisible, setChosenStep } = useContext(\n    CommandBarContext.Handlers,\n  );\n\n  const openCommandBar = useCallback(() => {\n    setIsVisible(true);\n    setChosenStep({ id: CommandBarStepEnum.MANAGE_REPOS });\n  }, []);\n\n  return (\n    <div className=\"w-screen h-screen flex flex-col\">\n      <Header />\n      <div className=\"w-full h-[calc(100vh-2.5rem)] flex flex-col\">\n        <div className=\"flex-1 flex items-center justify-center\">\n          <div className=\"flex flex-col gap-6 items-center select-none\">\n            <div className=\"p-3.5 flex items-center justify-center border border-bg-divider rounded-xl\">\n              <ShapesIcon sizeClassName=\"w-5 h-5\" />\n            </div>\n            <div className=\"flex flex-col gap-2 items-center max-w-[15.875rem] text-center\">\n              <p className=\"body-base-b text-label-title\">\n                <Trans>This project is empty</Trans>\n              </p>\n              <p className=\"body-s text-label-base !leading-5\">\n                <Trans values={{ cmdKey: shortcut?.[0] }}>\n                  Press{' '}\n                  <span className=\"min-w-[20px] h-5 px-0.5 inline-flex items-center justify-center rounded border border-bg-border bg-bg-base shadow-low\">\n                    cmdKey\n                  </span>{' '}\n                  <span className=\"w-5 h-5 inline-flex items-center justify-center rounded border border-bg-border bg-bg-base shadow-low\">\n                    K\n                  </span>{' '}\n                  on your keyboard to open the Command bar and add a repository.\n                </Trans>\n              </p>\n            </div>\n            <Button size=\"large\" onClick={openCommandBar}>\n              <Trans>Open Command Bar</Trans>\n            </Button>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default memo(EmptyProject);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/NavPanel/Conversations/ConversationsDropdown.tsx",
    "content": "import { memo, useCallback, useContext } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport DropdownSection from '../../../../components/Dropdown/Section';\nimport SectionItem from '../../../../components/Dropdown/Section/SectionItem';\nimport { TrashCanIcon } from '../../../../icons';\nimport { deleteConversation } from '../../../../services/api';\nimport { ProjectContext } from '../../../../context/projectContext';\n\ntype Props = {};\n\nconst ConversationsDropdown = ({}: Props) => {\n  const { t } = useTranslation();\n  const { project, refreshCurrentProjectConversations } = useContext(\n    ProjectContext.Current,\n  );\n\n  const handleRemoveAllConversations = useCallback(async () => {\n    if (project?.id && project.conversations.length) {\n      await Promise.allSettled(\n        project.conversations.map((c) => deleteConversation(project.id, c.id)),\n      );\n      refreshCurrentProjectConversations();\n    }\n  }, [project?.id, project?.conversations]);\n\n  return (\n    <div>\n      <DropdownSection>\n        <SectionItem\n          onClick={handleRemoveAllConversations}\n          label={t('Delete all conversations')}\n          index={'del-chats'}\n          icon={<TrashCanIcon sizeClassName=\"w-4 h-4\" />}\n        />\n      </DropdownSection>\n    </div>\n  );\n};\n\nexport default memo(ConversationsDropdown);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/NavPanel/Conversations/CoversationEntry.tsx",
    "content": "import React, { memo, useCallback, useContext } from 'react';\nimport { ConversationShortType } from '../../../../types/api';\nimport { TabsContext } from '../../../../context/tabsContext';\nimport { TabTypesEnum } from '../../../../types/general';\nimport { useArrowNavigationItemProps } from '../../../../hooks/useArrowNavigationItemProps';\n\ntype Props = ConversationShortType & {\n  index: string;\n  isCurrentPath?: boolean;\n};\n\nconst ConversationEntry = ({ title, id, index, isCurrentPath }: Props) => {\n  const { openNewTab } = useContext(TabsContext.Handlers);\n\n  const onClick = useCallback(() => {\n    openNewTab({ type: TabTypesEnum.CHAT, conversationId: id, title });\n  }, [openNewTab, id, title]);\n\n  const { isFocused, isLeftSidebarFocused, props } =\n    useArrowNavigationItemProps<HTMLAnchorElement>(index, onClick);\n\n  return (\n    <a\n      href=\"#\"\n      className={`w-full text-left h-7 flex-shrink-0 flex items-center gap-3 pr-2 cursor-pointer\n        ellipsis body-mini group pl-10.5 ${\n          isCurrentPath\n            ? isLeftSidebarFocused\n              ? 'bg-bg-shade-hover text-label-title'\n              : 'bg-bg-shade text-label-title'\n            : isFocused\n            ? 'bg-bg-sub-hover text-label-title'\n            : 'text-label-base'\n        }`}\n      {...props}\n    >\n      <span className=\"ellipsis\">{title}</span>\n    </a>\n  );\n};\n\nexport default memo(ConversationEntry);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/NavPanel/Conversations/index.tsx",
    "content": "import React, { Dispatch, memo, SetStateAction, useContext } from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport Dropdown from '../../../../components/Dropdown';\nimport {\n  ArrowTriangleBottomIcon,\n  ChatBubblesIcon,\n  MoreHorizontalIcon,\n} from '../../../../icons';\nimport Button from '../../../../components/Button';\nimport { ProjectContext } from '../../../../context/projectContext';\nimport { useNavPanel } from '../../../../hooks/useNavPanel';\nimport ConversationsDropdown from './ConversationsDropdown';\nimport ConversationEntry from './CoversationEntry';\n\ntype Props = {\n  setExpanded: Dispatch<SetStateAction<string>>;\n  isExpanded: boolean;\n  index: string;\n  currentPath?: string;\n};\n\nconst reactRoot = document.getElementById('root')!;\n\nconst ConversationsNav = ({\n  isExpanded,\n  setExpanded,\n  index,\n  currentPath,\n}: Props) => {\n  const { t } = useTranslation();\n  const { project } = useContext(ProjectContext.Current);\n  const { noPropagate, itemProps } = useNavPanel(\n    index,\n    setExpanded,\n    isExpanded,\n  );\n\n  return (\n    <div className=\"select-none overflow-hidden w-full flex-shrink-0\">\n      <span {...itemProps}>\n        <ChatBubblesIcon\n          sizeClassName=\"w-3.5 h-3.5\"\n          className=\"text-brand-default\"\n        />\n        <p className=\"flex items-center gap-1 body-s-b flex-1 ellipsis\">\n          <span className=\"text-label-title ellipsis\">\n            <Trans>Chat conversations</Trans>\n          </span>\n          {isExpanded && (\n            <ArrowTriangleBottomIcon\n              sizeClassName=\"w-2 h-2\"\n              className=\"text-label-muted\"\n            />\n          )}\n        </p>\n        {isExpanded && (\n          <div onClick={noPropagate}>\n            <Dropdown\n              DropdownComponent={ConversationsDropdown}\n              appendTo={reactRoot}\n              dropdownPlacement=\"bottom-start\"\n              size=\"auto\"\n            >\n              <Button\n                variant=\"tertiary\"\n                size=\"mini\"\n                onlyIcon\n                title={t('More actions')}\n              >\n                <MoreHorizontalIcon sizeClassName=\"w-3.5 h-3.5\" />\n              </Button>\n            </Dropdown>\n          </div>\n        )}\n      </span>\n      {isExpanded && (\n        <div className={'overflow-hidden'}>\n          {project?.conversations.map((c) => (\n            <ConversationEntry\n              key={c.id}\n              {...c}\n              index={`${index}-${c.id}`}\n              isCurrentPath={currentPath === c.id}\n            />\n          ))}\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport default memo(ConversationsNav);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/NavPanel/Doc/DocDropdown.tsx",
    "content": "import { memo, useCallback, useContext } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport DropdownSection from '../../../../components/Dropdown/Section';\nimport SectionItem from '../../../../components/Dropdown/Section/SectionItem';\nimport { RefreshIcon, TrashCanIcon } from '../../../../icons';\nimport { removeDocFromProject, resyncDoc } from '../../../../services/api';\nimport { ProjectContext } from '../../../../context/projectContext';\n\ntype Props = {\n  docId: string;\n};\n\nconst ConversationsDropdown = ({ docId }: Props) => {\n  const { t } = useTranslation();\n  const { project, refreshCurrentProjectDocs } = useContext(\n    ProjectContext.Current,\n  );\n\n  const handleRemoveFromProject = useCallback(async () => {\n    if (project?.id) {\n      await removeDocFromProject(project?.id, docId);\n      refreshCurrentProjectDocs();\n    }\n  }, [docId, project?.id]);\n\n  const handleResync = useCallback(() => {\n    return resyncDoc(docId);\n  }, []);\n\n  return (\n    <div>\n      <DropdownSection borderBottom>\n        <SectionItem\n          onClick={handleResync}\n          index={'re-sync'}\n          label={t('Re-sync')}\n          icon={<RefreshIcon sizeClassName=\"w-4 h-4\" />}\n        />\n      </DropdownSection>\n      <DropdownSection>\n        <SectionItem\n          onClick={handleRemoveFromProject}\n          index={'del-from-project'}\n          label={t('Remove from project')}\n          icon={<TrashCanIcon sizeClassName=\"w-4 h-4\" />}\n        />\n      </DropdownSection>\n    </div>\n  );\n};\n\nexport default memo(ConversationsDropdown);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/NavPanel/Doc/DocEntry.tsx",
    "content": "import React, { memo, useCallback, useContext } from 'react';\nimport { DocPageType } from '../../../../types/api';\nimport { TabsContext } from '../../../../context/tabsContext';\nimport { TabTypesEnum } from '../../../../types/general';\nimport { FileIcon } from '../../../../icons';\nimport { useArrowNavigationItemProps } from '../../../../hooks/useArrowNavigationItemProps';\n\ntype Props = DocPageType & {\n  index: string;\n  favicon?: string;\n  isCurrentPath?: boolean;\n};\n\nconst DocEntry = ({\n  index,\n  doc_id,\n  doc_title,\n  relative_url,\n  favicon,\n  isCurrentPath,\n}: Props) => {\n  const { openNewTab } = useContext(TabsContext.Handlers);\n\n  const onClick = useCallback(() => {\n    openNewTab({\n      type: TabTypesEnum.DOC,\n      title: doc_title,\n      docId: doc_id,\n      favicon,\n      relativeUrl: relative_url,\n    });\n  }, [openNewTab, doc_id, doc_title, favicon, relative_url]);\n\n  const { isFocused, isLeftSidebarFocused, props } =\n    useArrowNavigationItemProps<HTMLAnchorElement>(index, onClick);\n\n  return (\n    <a\n      href=\"#\"\n      className={`w-full text-left h-7 flex-shrink-0 flex items-center gap-3 pr-2 cursor-pointer\n        ellipsis body-mini group pl-10.5  ${\n          isCurrentPath\n            ? isLeftSidebarFocused\n              ? 'bg-bg-shade-hover text-label-title'\n              : 'bg-bg-shade text-label-title'\n            : isFocused\n            ? 'bg-bg-sub-hover text-label-title'\n            : 'text-label-base'\n        }`}\n      {...props}\n    >\n      <FileIcon sizeClassName=\"w-3.5 h-3.5\" />\n      <span className=\"ellipsis\">{doc_title}</span>\n    </a>\n  );\n};\n\nexport default memo(DocEntry);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/NavPanel/Doc/index.tsx",
    "content": "import React, {\n  Dispatch,\n  memo,\n  SetStateAction,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { DocPageType } from '../../../../types/api';\nimport { useNavPanel } from '../../../../hooks/useNavPanel';\nimport { getIndexedPages } from '../../../../services/api';\nimport {\n  ArrowTriangleBottomIcon,\n  MagazineIcon,\n  MagnifyToolIcon,\n  MoreHorizontalIcon,\n} from '../../../../icons';\nimport Dropdown from '../../../../components/Dropdown';\nimport Button from '../../../../components/Button';\nimport { CommandBarContext } from '../../../../context/commandBarContext';\nimport { CommandBarStepEnum } from '../../../../types/general';\nimport DocDropdown from './DocDropdown';\nimport DocEntry from './DocEntry';\n\ntype Props = {\n  setExpanded: Dispatch<SetStateAction<string>>;\n  isExpanded: boolean;\n  projectId: string;\n  index: string;\n  docId: string;\n  favicon?: string;\n  title?: string;\n  url: string;\n  currentPath?: string;\n};\n\nconst reactRoot = document.getElementById('root')!;\n\nconst DocNav = ({\n  isExpanded,\n  setExpanded,\n  projectId,\n  index,\n  favicon,\n  docId,\n  title,\n  url,\n  currentPath,\n}: Props) => {\n  const { t } = useTranslation();\n  const [pages, setPages] = useState<DocPageType[]>([]);\n  const { noPropagate, itemProps } = useNavPanel(\n    index,\n    setExpanded,\n    isExpanded,\n  );\n  const { setChosenStep, setIsVisible } = useContext(\n    CommandBarContext.Handlers,\n  );\n\n  const fetchPages = useCallback(async () => {\n    const resp = await getIndexedPages(docId);\n    setPages(resp);\n  }, [docId]);\n\n  useEffect(() => {\n    fetchPages();\n  }, [fetchPages]);\n\n  const dropdownComponentProps = useMemo(() => {\n    return {\n      key: docId,\n      docId,\n    };\n  }, [docId]);\n\n  const onSearch = useCallback(() => {\n    setChosenStep({ id: CommandBarStepEnum.SEARCH_DOCS, data: { docId } });\n    setIsVisible(true);\n  }, [docId]);\n\n  return (\n    <div className=\"select-none flex-shrink-0\">\n      <span {...itemProps}>\n        {favicon ? (\n          <img src={favicon} alt={title || url} className={'w-3.5 h-3.5'} />\n        ) : (\n          <MagazineIcon sizeClassName=\"w-3.5 h-3.5\" />\n        )}\n        <p className=\"flex items-center gap-1 body-s-b flex-1 ellipsis\">\n          <span className=\"text-label-title ellipsis\">{title}</span>\n          {isExpanded && <ArrowTriangleBottomIcon sizeClassName=\"w-2 h-2\" />}\n        </p>\n        {isExpanded && (\n          <div onClick={noPropagate} className=\"flex gap-1 items-center\">\n            <Button\n              size=\"mini\"\n              variant=\"tertiary\"\n              onlyIcon\n              title={t('Search')}\n              onClick={onSearch}\n            >\n              <MagnifyToolIcon sizeClassName=\"w-3.5 h-3.5\" />\n            </Button>\n            <Dropdown\n              DropdownComponent={DocDropdown}\n              dropdownComponentProps={dropdownComponentProps}\n              appendTo={reactRoot}\n              dropdownPlacement=\"bottom-start\"\n            >\n              <Button\n                variant=\"tertiary\"\n                size=\"mini\"\n                onlyIcon\n                title={t('More actions')}\n              >\n                <MoreHorizontalIcon sizeClassName=\"w-3.5 h-3.5\" />\n              </Button>\n            </Dropdown>\n          </div>\n        )}\n      </span>\n      {isExpanded && (\n        <div\n          className={`relative ${\n            isExpanded ? 'overflow-auto' : 'overflow-hidden'\n          }`}\n        >\n          <div className=\"absolute top-0 bottom-0 left-[1.375rem] w-px bg-bg-border\" />\n          {pages.map((p) => (\n            <DocEntry\n              key={p.absolute_url}\n              {...p}\n              index={`${index}-${p.relative_url}`}\n              favicon={favicon}\n              isCurrentPath={currentPath === p.relative_url}\n            />\n          ))}\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport default memo(DocNav);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/NavPanel/Repo/RepoDropdown.tsx",
    "content": "import {\n  memo,\n  useCallback,\n  useContext,\n  useState,\n  MouseEvent,\n  ChangeEvent,\n  useMemo,\n  useEffect,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport DropdownSection from '../../../../components/Dropdown/Section';\nimport SectionItem from '../../../../components/Dropdown/Section/SectionItem';\nimport {\n  ArrowTriangleBottomIcon,\n  BranchIcon,\n  RefreshIcon,\n  TrashCanIcon,\n} from '../../../../icons';\nimport {\n  changeRepoBranch,\n  indexRepoBranch,\n  removeRepoFromProject,\n  syncRepo,\n} from '../../../../services/api';\nimport SpinLoaderContainer from '../../../../components/Loaders/SpinnerLoader';\nimport SectionLabel from '../../../../components/Dropdown/Section/SectionLabel';\nimport Button from '../../../../components/Button';\nimport { ProjectContext } from '../../../../context/projectContext';\nimport { RepoIndexingStatusType } from '../../../../types/general';\nimport { RepositoriesContext } from '../../../../context/repositoriesContext';\n\ntype Props = {\n  repoRef: string;\n  projectId: string;\n  selectedBranch?: string | null;\n  allBranches: { name: string; last_commit_unix_secs: number }[];\n  indexedBranches: string[];\n  indexingStatus?: RepoIndexingStatusType;\n  handleClose: () => void;\n};\n\nconst RepoDropdown = ({\n  repoRef,\n  selectedBranch,\n  indexedBranches,\n  allBranches,\n  projectId,\n  indexingStatus,\n}: Props) => {\n  const { t } = useTranslation();\n  const [isBranchesOpen, setIsBranchesOpen] = useState(false);\n  const [isSyncing, setIsSyncing] = useState(false);\n  const [search, setSearch] = useState('');\n  const [branchesToSync, setBranchesToSync] = useState<string[]>([]);\n  const { refreshCurrentProjectRepos } = useContext(ProjectContext.Current);\n\n  const onRepoSync = useCallback(\n    async (e?: MouseEvent) => {\n      e?.stopPropagation();\n      await syncRepo(repoRef);\n      setIsSyncing(true);\n    },\n    [repoRef],\n  );\n\n  const toggleBranches = useCallback((e?: MouseEvent) => {\n    e?.stopPropagation();\n    setIsBranchesOpen((prev) => !prev);\n  }, []);\n\n  const handleInputChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n    setSearch(e.target.value);\n  }, []);\n\n  const noPropagate = useCallback((e?: MouseEvent) => {\n    e?.stopPropagation();\n  }, []);\n\n  useEffect(() => {\n    setBranchesToSync((prevState) =>\n      prevState.filter((p) => !indexedBranches.includes(p)),\n    );\n  }, [indexedBranches]);\n\n  const notSyncedBranches = useMemo(() => {\n    return [...allBranches]\n      .reverse()\n      .filter(\n        (b) =>\n          !indexedBranches.includes(b.name) && !branchesToSync.includes(b.name),\n      )\n      .map((b) => b.name);\n  }, [indexedBranches, allBranches, branchesToSync]);\n\n  const handleRemoveFromProject = useCallback(async () => {\n    if (projectId) {\n      await removeRepoFromProject(projectId, repoRef);\n      refreshCurrentProjectRepos();\n    }\n  }, [projectId, repoRef]);\n\n  const indexedBranchesToShow = useMemo(() => {\n    if (!search) {\n      return indexedBranches;\n    }\n    return indexedBranches.filter(\n      (b) =>\n        b\n          .replace(/^origin\\//, '')\n          ?.toLowerCase()\n          .includes(search?.toLowerCase()),\n    );\n  }, [indexedBranches, search]);\n\n  const indexingBranchesToShow = useMemo(() => {\n    if (!search) {\n      return branchesToSync;\n    }\n    return branchesToSync.filter(\n      (b) =>\n        b\n          .replace(/^origin\\//, '')\n          ?.toLowerCase()\n          .includes(search?.toLowerCase()),\n    );\n  }, [branchesToSync, search]);\n\n  const notIndexedBranchesToShow = useMemo(() => {\n    if (!search) {\n      return notSyncedBranches;\n    }\n    return notSyncedBranches.filter(\n      (b) =>\n        b\n          .replace(/^origin\\//, '')\n          ?.toLowerCase()\n          .includes(search?.toLowerCase()),\n    );\n  }, [notSyncedBranches, search]);\n\n  const switchToBranch = useCallback(\n    async (branch: string, e?: MouseEvent) => {\n      e?.stopPropagation();\n      await changeRepoBranch(projectId, repoRef, branch);\n      refreshCurrentProjectRepos();\n    },\n    [projectId, repoRef],\n  );\n\n  return (\n    <div onClick={noPropagate}>\n      <DropdownSection borderBottom>\n        <SectionItem\n          onClick={onRepoSync}\n          label={t('Re-sync')}\n          index={'re-sync'}\n          icon={\n            isSyncing ? (\n              <SpinLoaderContainer sizeClassName=\"w-4 h-4\" />\n            ) : (\n              <RefreshIcon sizeClassName=\"w-4 h-4\" />\n            )\n          }\n        />\n        <SectionItem\n          onClick={toggleBranches}\n          label={t('Branches')}\n          index={'branches'}\n          icon={<BranchIcon sizeClassName=\"w-4 h-4\" />}\n          customRightElement={\n            <span className=\"body-s text-label-muted overflow-hidden flex items-center gap-2\">\n              <span className=\"ellipsis\">{selectedBranch}</span>\n              <ArrowTriangleBottomIcon\n                sizeClassName=\"w-2 h-2\"\n                className={`${\n                  isBranchesOpen ? 'rotate-180' : 'rotate-0'\n                } transition-transform duration-150 ease-in-out`}\n              />\n            </span>\n          }\n        />\n      </DropdownSection>\n      <div\n        style={{\n          maxHeight: isBranchesOpen ? undefined : 0,\n        }}\n        className=\"overflow-hidden\"\n      >\n        <DropdownSection borderBottom>\n          <input\n            value={search}\n            name={'search'}\n            placeholder={t('Search branches...')}\n            onChange={handleInputChange}\n            className=\"px-2 bg-transparent h-8 rounded focus:outline-0 focus:outline-none\"\n            autoFocus\n            type=\"search\"\n            autoComplete=\"off\"\n          />\n        </DropdownSection>\n        {!!indexedBranchesToShow.length && (\n          <DropdownSection borderBottom>\n            <SectionLabel text={t('Synced')} />\n            {indexedBranchesToShow.map((b) => (\n              <SectionItem\n                onClick={(e) => switchToBranch(b, e)}\n                label={b.replace(/^origin\\//, '')}\n                icon={<BranchIcon sizeClassName=\"w-4 h-4\" />}\n                key={b}\n                isSelected={selectedBranch === b}\n                index={`branch-${b}`}\n              />\n            ))}\n          </DropdownSection>\n        )}\n        {!!indexingBranchesToShow.length && (\n          <DropdownSection borderBottom>\n            <SectionLabel text={t('Syncing')} />\n            {indexingBranchesToShow.map((b) => (\n              <SectionItem\n                onClick={noPropagate}\n                icon={<SpinLoaderContainer sizeClassName=\"w-4 h-4\" />}\n                label={b.replace(/^origin\\//, '')}\n                key={b}\n                customRightElement={\n                  <span className=\"text-label-muted body-s\">\n                    {indexingStatus?.branch === b\n                      ? indexingStatus?.percentage + '%'\n                      : t('Queued...')}\n                  </span>\n                }\n                index={`branch-${b}`}\n              />\n            ))}\n          </DropdownSection>\n        )}\n        {!!notIndexedBranchesToShow.length && (\n          <DropdownSection borderBottom>\n            <SectionLabel text={t('Not synced')} />\n            {notIndexedBranchesToShow.map((b) => (\n              <SectionItem\n                onClick={noPropagate}\n                label={b.replace(/^origin\\//, '')}\n                key={b}\n                icon={<BranchIcon sizeClassName=\"w-4 h-4\" />}\n                customRightElement={\n                  <Button\n                    variant=\"secondary\"\n                    size=\"mini\"\n                    onClick={async () => {\n                      setBranchesToSync((prev) => [...prev, b]);\n                      await indexRepoBranch(repoRef, b);\n                    }}\n                  >\n                    {t('Sync')}\n                  </Button>\n                }\n                index={`branch-${b}`}\n              />\n            ))}\n          </DropdownSection>\n        )}\n      </div>\n      <DropdownSection>\n        <SectionItem\n          onClick={handleRemoveFromProject}\n          index={'del-from-project'}\n          label={t('Remove from project')}\n          icon={<TrashCanIcon sizeClassName=\"w-4 h-4\" />}\n        />\n      </DropdownSection>\n    </div>\n  );\n};\nconst WithIndexingStatus = (props: Omit<Props, 'indexingStatus'>) => {\n  const { indexingStatus } = useContext(RepositoriesContext);\n  const repoIndexingStatus = useMemo(() => {\n    return indexingStatus[props.repoRef];\n  }, [indexingStatus[props.repoRef]]);\n\n  return <RepoDropdown {...props} indexingStatus={repoIndexingStatus} />;\n};\n\nexport default memo(WithIndexingStatus);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/NavPanel/Repo/RepoEntry.tsx",
    "content": "import React, {\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useState,\n} from 'react';\nimport { ChevronRightIcon, EyeCutIcon, FolderIcon } from '../../../../icons';\nimport FileIcon from '../../../../components/FileIcon';\nimport { DirectoryEntry } from '../../../../types/api';\nimport { TabsContext } from '../../../../context/tabsContext';\nimport {\n  RepoIndexingStatusType,\n  SyncStatus,\n  TabTypesEnum,\n} from '../../../../types/general';\nimport SpinLoaderContainer from '../../../../components/Loaders/SpinnerLoader';\nimport { useArrowNavigationItemProps } from '../../../../hooks/useArrowNavigationItemProps';\n\ntype Props = {\n  name: string;\n  isDirectory: boolean;\n  level: number;\n  fullPath: string;\n  fetchFiles: (path: string) => Promise<DirectoryEntry[]>;\n  defaultOpen?: boolean;\n  indexed: boolean;\n  repoRef: string;\n  lastIndex: string;\n  currentPath?: string;\n  branch?: string | null;\n  indexingData?: RepoIndexingStatusType;\n  index: string;\n};\n\nconst RepoEntry = ({\n  name,\n  level,\n  isDirectory,\n  currentPath,\n  fullPath,\n  fetchFiles,\n  defaultOpen,\n  indexed,\n  repoRef,\n  lastIndex,\n  branch,\n  indexingData,\n  index,\n}: Props) => {\n  const { openNewTab } = useContext(TabsContext.Handlers);\n\n  const [isOpen, setOpen] = useState(\n    defaultOpen || (currentPath && currentPath.startsWith(fullPath)),\n  );\n  const [subItems, setSubItems] = useState<DirectoryEntry[] | null>(null);\n  const [isMounted, setIsMounted] = useState(false);\n\n  const refetchFolderFiles = useCallback(() => {\n    fetchFiles(fullPath).then(setSubItems);\n  }, [fullPath, fetchFiles]);\n\n  useEffect(() => {\n    if (indexingData?.status === SyncStatus.Done && isDirectory) {\n      refetchFolderFiles();\n    }\n  }, [indexingData?.status]);\n\n  useEffect(() => {\n    if (currentPath && currentPath.startsWith(fullPath)) {\n      setOpen(true);\n    }\n  }, [currentPath, fullPath]);\n\n  useEffect(() => {\n    if (\n      subItems?.length &&\n      subItems.find(\n        (si) => si.entry_data !== 'Directory' && !si.entry_data.File.indexed,\n      ) &&\n      isMounted\n    ) {\n      refetchFolderFiles();\n    } else {\n      setIsMounted(true);\n    }\n  }, [lastIndex]);\n\n  useEffect(() => {\n    if (isDirectory && isOpen) {\n      refetchFolderFiles();\n    }\n  }, [isOpen, isDirectory, refetchFolderFiles]);\n\n  const onClick = useCallback(() => {\n    if (isDirectory) {\n      setOpen((prev) => !prev);\n    } else {\n      openNewTab({\n        type: TabTypesEnum.FILE,\n        path: fullPath,\n        repoRef,\n        branch,\n      });\n    }\n  }, [isDirectory, fullPath, openNewTab, repoRef, branch]);\n\n  const { isFocused, isLeftSidebarFocused, props } =\n    useArrowNavigationItemProps<HTMLAnchorElement>(index, onClick);\n\n  return (\n    <div\n      style={{\n        maxHeight: isOpen && subItems ? undefined : 28,\n      }}\n      className=\"flex flex-col transition-all ease-linear overflow-hidden flex-shrink-0 w-full min-w-fit\"\n    >\n      <a\n        className={`min-w-full w-max text-left h-7 flex-shrink-0 flex items-center gap-3 px-4 cursor-pointer body-mini group\n      ${\n        currentPath === fullPath\n          ? isLeftSidebarFocused\n            ? 'bg-bg-shade-hover text-label-title'\n            : 'bg-bg-shade text-label-title'\n          : isFocused\n          ? 'bg-bg-sub-hover text-label-title'\n          : ''\n      } ${\n        isOpen && isDirectory\n          ? 'text-label-title'\n          : indexed\n          ? 'text-label-base'\n          : 'text-label-muted'\n      }`}\n        style={{ paddingLeft: level * 27 }}\n        {...props}\n      >\n        {isDirectory ? (\n          <div className=\"w-4 h-4 flex items-center justify-center\">\n            <ChevronRightIcon\n              sizeClassName=\"w-3.5 h-3.5\"\n              className={`${\n                isOpen\n                  ? 'transform rotate-90 text-label-base'\n                  : 'text-label-muted'\n              } transition-all duration-200`}\n            />\n          </div>\n        ) : null}\n        {isDirectory ? (\n          <FolderIcon sizeClassName=\"w-4 h-4\" />\n        ) : !indexed ? (\n          indexingData?.status === SyncStatus.Indexing ? (\n            <SpinLoaderContainer sizeClassName=\"w-4 h-4\" />\n          ) : (\n            <EyeCutIcon sizeClassName=\"w-4 h-4\" />\n          )\n        ) : (\n          <FileIcon filename={name} noMargin />\n        )}\n        {isDirectory ? name.slice(0, -1) : name}\n        {/*{!indexed && !indexRequested && (*/}\n        {/*  <Button*/}\n        {/*    variant=\"secondary\"*/}\n        {/*    size=\"tiny\"*/}\n        {/*    onClick={onIndexRequested}*/}\n        {/*    className=\"opacity-0 group-hover:opacity-100 transform scale-75 transition-opacity ease-in-out duration-150\"*/}\n        {/*  >*/}\n        {/*    <Trans>Index</Trans>*/}\n        {/*  </Button>*/}\n        {/*)}*/}\n        {/*{!indexed && indexRequested && isIndexing && (*/}\n        {/*  <div className=\"text-bg-main\">*/}\n        {/*    <LiteLoaderContainer sizeClassName=\"w-4 h-4\" />*/}\n        {/*  </div>*/}\n        {/*)}*/}\n      </a>\n      {isOpen && subItems?.length ? (\n        <div className=\"relative\">\n          <div\n            className=\"absolute top-0 bottom-0 left-2 w-px bg-bg-border\"\n            style={{ left: level * 27 + 8 }}\n          />\n          {subItems.map((si, sii) => (\n            <RepoEntry\n              key={name + si.name}\n              name={si.name}\n              isDirectory={si.entry_data === 'Directory'}\n              level={level + 1}\n              fetchFiles={fetchFiles}\n              fullPath={fullPath + si.name}\n              indexed={\n                si.entry_data !== 'Directory'\n                  ? si.entry_data.File.indexed\n                  : true\n              }\n              repoRef={repoRef}\n              lastIndex={lastIndex}\n              currentPath={currentPath}\n              branch={branch}\n              index={`${index}-${sii}`}\n            />\n          ))}\n        </div>\n      ) : null}\n    </div>\n  );\n};\n\nexport default memo(RepoEntry);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/NavPanel/Repo/index.tsx",
    "content": "import React, {\n  Dispatch,\n  memo,\n  SetStateAction,\n  useCallback,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { DirectoryEntry } from '../../../../types/api';\nimport { getFolderContent } from '../../../../services/api';\nimport { splitPath } from '../../../../utils';\nimport GitHubIcon from '../../../../icons/GitHubIcon';\nimport Dropdown from '../../../../components/Dropdown';\nimport {\n  ArrowTriangleBottomIcon,\n  HardDriveIcon,\n  MoreHorizontalIcon,\n} from '../../../../icons';\nimport Button from '../../../../components/Button';\nimport { RepoIndexingStatusType, SyncStatus } from '../../../../types/general';\nimport SpinLoaderContainer from '../../../../components/Loaders/SpinnerLoader';\nimport Tooltip from '../../../../components/Tooltip';\nimport { repoStatusMap } from '../../../../consts/general';\nimport { useNavPanel } from '../../../../hooks/useNavPanel';\nimport RepoEntry from './RepoEntry';\nimport RepoDropdown from './RepoDropdown';\n\ntype Props = {\n  repoRef: string;\n  setExpanded: Dispatch<SetStateAction<string>>;\n  isExpanded: boolean;\n  projectId: string;\n  lastIndex: string;\n  currentPath?: string;\n  branch: string;\n  allBranches: { name: string; last_commit_unix_secs: number }[];\n  indexedBranches: string[];\n  indexingData?: RepoIndexingStatusType;\n  index: string;\n};\n\nconst reactRoot = document.getElementById('root')!;\n\nconst RepoNav = ({\n  repoRef,\n  isExpanded,\n  setExpanded,\n  branch,\n  indexedBranches,\n  allBranches,\n  projectId,\n  lastIndex,\n  currentPath,\n  indexingData,\n  index,\n}: Props) => {\n  const { t } = useTranslation();\n  const [files, setFiles] = useState<DirectoryEntry[]>([]);\n  const { noPropagate, itemProps } = useNavPanel(\n    index,\n    setExpanded,\n    isExpanded,\n  );\n\n  const fetchFiles = useCallback(\n    async (path?: string) => {\n      const resp = await getFolderContent(repoRef, path, branch);\n      if (!resp.entries) {\n        return [];\n      }\n      return resp?.entries.sort((a, b) => {\n        if ((a.entry_data === 'Directory') === (b.entry_data === 'Directory')) {\n          return a.name?.toLowerCase() < b.name?.toLowerCase() ? -1 : 1;\n        } else {\n          return a.entry_data === 'Directory' ? -1 : 1;\n        }\n      });\n    },\n    [repoRef, branch, indexingData?.status],\n  );\n\n  const refetchParentFolder = useCallback(() => {\n    fetchFiles().then(setFiles);\n  }, [fetchFiles]);\n\n  useEffect(() => {\n    refetchParentFolder();\n  }, [refetchParentFolder]);\n\n  const dropdownComponentProps = useMemo(() => {\n    return {\n      key: repoRef,\n      projectId,\n      repoRef,\n      selectedBranch: branch,\n      indexedBranches,\n      allBranches,\n    };\n  }, [projectId, repoRef, branch, indexedBranches, allBranches]);\n\n  const isIndexing = useMemo(() => {\n    if (!indexingData) {\n      return false;\n    }\n    return [\n      SyncStatus.Indexing,\n      SyncStatus.Syncing,\n      SyncStatus.Queued,\n    ].includes(indexingData.status);\n  }, [indexingData]);\n\n  return (\n    <div className=\"select-none flex-shrink-0\">\n      <span {...itemProps}>\n        {isIndexing && indexingData ? (\n          <Tooltip\n            text={`${t(repoStatusMap[indexingData.status].text)}${\n              indexingData?.percentage ? ` · ${indexingData?.percentage}%` : ''\n            }`}\n            placement=\"bottom-start\"\n          >\n            <SpinLoaderContainer\n              sizeClassName=\"w-3.5 h-3.5\"\n              colorClassName=\"text-blue\"\n            />\n          </Tooltip>\n        ) : repoRef.startsWith('github.com') ? (\n          <GitHubIcon sizeClassName=\"w-3.5 h-3.5\" />\n        ) : (\n          <HardDriveIcon sizeClassName=\"w-3.5 h-3.5\" />\n        )}\n        <p className=\"flex items-center gap-1 body-s-b flex-1 ellipsis\">\n          <span className=\"text-label-title\">{splitPath(repoRef).pop()}</span>\n          {isExpanded && (\n            <>\n              <span className=\"text-label-muted\">/</span>\n              <span className=\"flex items-center text-label-muted gap-1 body-s-b\">\n                {branch?.replace(/^origin\\//, '')}{' '}\n                <ArrowTriangleBottomIcon sizeClassName=\"w-2 h-2\" />\n              </span>\n            </>\n          )}\n        </p>\n        {isExpanded && (\n          <div onClick={noPropagate}>\n            <Dropdown\n              DropdownComponent={RepoDropdown}\n              dropdownComponentProps={dropdownComponentProps}\n              appendTo={reactRoot}\n              dropdownPlacement=\"bottom-start\"\n            >\n              <Button\n                variant=\"tertiary\"\n                size=\"mini\"\n                onlyIcon\n                title={t('More actions')}\n              >\n                <MoreHorizontalIcon sizeClassName=\"w-3.5 h-3.5\" />\n              </Button>\n            </Dropdown>\n          </div>\n        )}\n      </span>\n      {isExpanded && (\n        <div className={isExpanded ? 'overflow-auto' : 'overflow-hidden'}>\n          {files.map((f, fi) => (\n            <RepoEntry\n              key={f.name}\n              name={f.name}\n              indexed={\n                f.entry_data !== 'Directory' ? f.entry_data.File.indexed : true\n              }\n              isDirectory={f.entry_data === 'Directory'}\n              level={1}\n              fetchFiles={fetchFiles}\n              fullPath={f.name}\n              repoRef={repoRef}\n              currentPath={currentPath}\n              lastIndex={lastIndex}\n              branch={branch}\n              indexingData={indexingData}\n              index={`${index}-${fi}`}\n            />\n          ))}\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport default memo(RepoNav);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/NavPanel/Studios/AddContextFile.tsx",
    "content": "import React, { memo, useCallback, useContext } from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport Button from '../../../../components/Button';\nimport { CommandBarContext } from '../../../../context/commandBarContext';\nimport { CommandBarStepEnum } from '../../../../types/general';\nimport { useArrowNavigationItemProps } from '../../../../hooks/useArrowNavigationItemProps';\nimport PlusSign from '../../../../icons/PlusSign';\n\ntype Props = {\n  studioId: string;\n  index: string;\n  isFull?: boolean;\n};\n\nconst AddContextFile = ({ studioId, index, isFull }: Props) => {\n  useTranslation();\n  const { setChosenStep, setIsVisible } = useContext(\n    CommandBarContext.Handlers,\n  );\n\n  const handleClick = useCallback(() => {\n    setChosenStep({ id: CommandBarStepEnum.SEARCH_FILES, data: { studioId } });\n    setIsVisible(true);\n  }, [studioId]);\n\n  const {\n    isFocused,\n    props: { onClick, ...props },\n  } = useArrowNavigationItemProps<HTMLDivElement>(index, handleClick);\n\n  return (\n    <div\n      className={`${\n        isFull ? 'pb-2' : isFocused ? 'text-label-title' : 'text-label-base '\n      } pr-4 pl-10.5 ${isFocused ? 'bg-bg-sub-hover' : ''}`}\n      {...props}\n    >\n      {isFull ? (\n        <div className=\"flex flex-col items-center p-4 gap-4 rounded-md border border-dashed border-bg-border\">\n          <p className=\"select-none body-mini text-label-base\">\n            <Trans>\n              Studio conversation require at least one context file.\n            </Trans>\n          </p>\n          <Button variant=\"secondary\" size=\"mini\" onClick={onClick}>\n            <Trans>Add files</Trans>\n          </Button>\n        </div>\n      ) : (\n        <div\n          className=\"flex items-center gap-3 cursor-pointer h-7 w-full\"\n          onClick={onClick}\n        >\n          <PlusSign sizeClassName=\"w-3.5 h-3.5\" />\n          <Trans>Add files</Trans>\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport default memo(AddContextFile);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/NavPanel/Studios/StudioEntry.tsx",
    "content": "import React, {\n  Dispatch,\n  memo,\n  SetStateAction,\n  useCallback,\n  useEffect,\n} from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { CodeStudioType, HistoryConversationTurn } from '../../../../types/api';\nimport {\n  CodeStudioIcon,\n  MagazineIcon,\n  PromptIcon,\n  RangeIcon,\n} from '../../../../icons';\nimport ChevronRight from '../../../../icons/ChevronRight';\nimport TokenUsage from '../../../../components/TokenUsage';\nimport { TOKEN_LIMIT } from '../../../../consts/codeStudio';\nimport { humanNumber } from '../../../../utils';\nimport Tooltip from '../../../../components/Tooltip';\nimport Badge from '../../../../components/Badge';\nimport { IndexingStatusType } from '../../../../types/general';\nimport { useArrowNavigationItemProps } from '../../../../hooks/useArrowNavigationItemProps';\nimport PlusSign from '../../../../icons/PlusSign';\nimport Button from '../../../../components/Button';\nimport StudioSubItem from './StudioSubItem';\nimport AddContextFile from './AddContextFile';\nimport StudioFile from './StudioFile';\nimport StudioHistory from './StudioHistory';\n\ntype Props = CodeStudioType & {\n  index: string;\n  expandedIndex: string;\n  setExpandedIndex: Dispatch<SetStateAction<string>>;\n  indexingStatus: IndexingStatusType;\n  projectId: string;\n  previewingSnapshot: HistoryConversationTurn | null;\n  currentPath?: { path: string; repoRef: string };\n  currentDoc?: { docId: string; relativeUrl: string };\n  isViewingPrompts?: boolean;\n};\n\nconst StudioEntry = ({\n  id,\n  index,\n  name,\n  expandedIndex,\n  setExpandedIndex,\n  context,\n  token_counts,\n  doc_context,\n  indexingStatus,\n  projectId,\n  previewingSnapshot,\n  currentPath,\n  currentDoc,\n  isViewingPrompts,\n}: Props) => {\n  const { t } = useTranslation();\n\n  const onClick = useCallback(() => {\n    setExpandedIndex((prev) => (prev === index ? '' : index));\n  }, [index]);\n\n  const { isFocused, focusedIndex, props } =\n    useArrowNavigationItemProps<HTMLAnchorElement>(index, onClick);\n\n  useEffect(() => {\n    if (focusedIndex.startsWith(index) && focusedIndex !== index) {\n      setExpandedIndex(index);\n    }\n  }, [index, focusedIndex]);\n\n  return (\n    <div className=\"body-mini\">\n      <a\n        href=\"#\"\n        className={`w-full text-left h-7 flex-shrink-0 flex items-center gap-3 justify-between pr-2 cursor-pointer\n          ellipsis body-mini group ${isFocused ? 'bg-bg-sub-hover' : ''} ${\n            expandedIndex === index || focusedIndex.startsWith(index)\n              ? 'text-label-title'\n              : 'text-label-base'\n          } pl-4`}\n        {...props}\n      >\n        <span className=\"flex items-center gap-3\">\n          <ChevronRight\n            sizeClassName=\"w-3.5 h-3.5\"\n            className={`${\n              expandedIndex === index ? 'rotate-90' : ''\n            } transition-transform duration-150`}\n          />\n          <CodeStudioIcon sizeClassName=\"w-3.5 h-3.5\" />\n          <span className=\"ellipsis\">{name}</span>\n        </span>\n        {!token_counts.total && (\n          <Badge text={t('New')} type=\"studio\" size=\"mini\" />\n        )}\n      </a>\n      {expandedIndex === index && (\n        <div className=\"relative\">\n          <div className=\"absolute top-0 bottom-0 left-[1.375rem] w-px bg-bg-border\" />\n          <StudioHistory\n            studioName={name}\n            studioId={id}\n            projectId={projectId}\n            shouldRefresh={token_counts}\n            index={`${index}-history`}\n            previewingSnapshot={previewingSnapshot?.id}\n          />\n          <StudioSubItem\n            studioId={id}\n            index={`${index}-prompts`}\n            studioName={name}\n            isCurrentPath={isViewingPrompts}\n          >\n            <PromptIcon sizeClassName=\"w-3.5 h-3.5\" />\n            <span className=\"flex-1 ellipsis\">\n              <Trans>Prompts</Trans>\n            </span>\n            <TokenUsage percent={(token_counts.messages / TOKEN_LIMIT) * 100} />\n          </StudioSubItem>\n          <div className=\"body-tiny text-label-base pl-10.5 pr-4 h-7 flex items-center\">\n            <Trans>Context files</Trans>\n          </div>\n          {!previewingSnapshot && (\n            <AddContextFile\n              studioId={id}\n              index={`${index}-add-file`}\n              isFull={!context.length}\n            />\n          )}\n          {(previewingSnapshot?.context || context).map((f, i) => (\n            <StudioFile\n              key={`${f.path}-${f.repo}-${f.branch}`}\n              studioId={id}\n              index={`${index}-${f.path}-${f.repo}-${f.branch}`}\n              studioName={name}\n              tokens={token_counts.per_file[i]}\n              indexingData={indexingStatus[f.repo]}\n              currentPath={currentPath}\n              {...f}\n            />\n          ))}\n          {!!(doc_context.length || previewingSnapshot?.doc_context.length) && (\n            <div className=\"body-tiny text-label-base pl-10.5 pr-4 h-7 flex items-center\">\n              <Trans>Documentation in studio</Trans>\n            </div>\n          )}\n          {(previewingSnapshot?.doc_context || doc_context).map((d, i) => (\n            <StudioSubItem\n              key={`${d.doc_id}-${d.doc_id}-${d.relative_url}`}\n              studioId={id}\n              index={`${index}-${d.doc_id}-${d.relative_url}`}\n              studioName={name}\n              docId={d.doc_id}\n              relativeUrl={d.relative_url}\n              docTitle={d.doc_title || ''}\n              docFavicon={d.doc_icon || ''}\n              sections={d.ranges}\n              isCurrentPath={\n                currentDoc?.docId === d.doc_id &&\n                currentDoc?.relativeUrl === d.relative_url\n              }\n            >\n              {d.doc_icon ? (\n                <img\n                  src={d.doc_icon}\n                  alt={d.doc_title || d.absolute_url}\n                  className={'w-4 h-4'}\n                />\n              ) : (\n                <MagazineIcon sizeClassName=\"w-4 h-4\" />\n              )}\n              <span className=\"flex-1 ellipsis\">{d.doc_title}</span>\n              {!!d.ranges.length && (\n                <Tooltip\n                  text={t('# selected section', { count: d.ranges.length })}\n                  placement={'top'}\n                >\n                  <RangeIcon sizeClassName=\"w-3.5 h-3.5\" />\n                </Tooltip>\n              )}\n              <span\n                className={`code-mini w-10 text-right ${\n                  (token_counts.per_doc_file[i] || 0) < 18000 &&\n                  (token_counts.per_doc_file[i] || 0) > 1500\n                    ? 'text-yellow'\n                    : (token_counts.per_doc_file[i] || 0) <= 1500\n                    ? 'text-green'\n                    : 'text-red'\n                }`}\n              >\n                {humanNumber(token_counts.per_doc_file[i] || 0)}\n              </span>\n            </StudioSubItem>\n          ))}\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport default memo(StudioEntry);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/NavPanel/Studios/StudioFile.tsx",
    "content": "import React, { memo, useMemo } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport Tooltip from '../../../../components/Tooltip';\nimport { RangeIcon, WarningSignIcon } from '../../../../icons';\nimport FileIcon from '../../../../components/FileIcon';\nimport { humanNumber, splitPath } from '../../../../utils';\nimport {\n  RepoIndexingStatusType,\n  StudioContextFile,\n  SyncStatus,\n} from '../../../../types/general';\nimport { repoStatusMap } from '../../../../consts/general';\nimport SpinLoaderContainer from '../../../../components/Loaders/SpinnerLoader';\nimport StudioSubItem from './StudioSubItem';\n\ntype Props = StudioContextFile & {\n  studioId: string;\n  index: string;\n  studioName: string;\n  tokens?: number | null;\n  indexingData?: RepoIndexingStatusType;\n  currentPath?: { path: string; repoRef: string };\n};\n\nconst StudioFile = ({\n  path,\n  ranges,\n  repo,\n  branch,\n  studioId,\n  index,\n  studioName,\n  tokens,\n  indexingData,\n  currentPath,\n}: Props) => {\n  const { t } = useTranslation();\n\n  const isIndexing = useMemo(() => {\n    if (!indexingData) {\n      return false;\n    }\n    return [\n      SyncStatus.Indexing,\n      SyncStatus.Syncing,\n      SyncStatus.Queued,\n    ].includes(indexingData.status);\n  }, [indexingData]);\n\n  return (\n    <StudioSubItem\n      key={`${path}-${repo}-${branch}`}\n      studioId={studioId}\n      index={index}\n      studioName={studioName}\n      path={path}\n      repoRef={repo}\n      branch={branch}\n      ranges={ranges}\n      isCurrentPath={\n        currentPath?.path === path && currentPath?.repoRef === repo\n      }\n    >\n      {isIndexing && indexingData ? (\n        <Tooltip\n          text={`${t(repoStatusMap[indexingData.status].text)}${\n            indexingData?.percentage ? ` · ${indexingData?.percentage}%` : ''\n          }`}\n          placement=\"bottom-start\"\n        >\n          <SpinLoaderContainer\n            sizeClassName=\"w-4 h-4\"\n            colorClassName=\"text-blue\"\n          />\n        </Tooltip>\n      ) : tokens === null ? (\n        <Tooltip\n          text={t('Missing source')}\n          placement={'bottom-start'}\n          wrapperClassName=\"w-4 h-4\"\n        >\n          <WarningSignIcon sizeClassName=\"w-4 h-4\" className=\"text-red\" />\n        </Tooltip>\n      ) : (\n        <FileIcon filename={path} noMargin />\n      )}\n      <span\n        className={`flex-1 ellipsis ${\n          tokens === null && !isIndexing ? 'text-red' : ''\n        }`}\n      >\n        {splitPath(path).pop()}\n      </span>\n      {!!ranges.length && (\n        <Tooltip\n          text={\n            ranges.length === 1\n              ? t('Lines # - #', {\n                  start: ranges[0].start + 1,\n                  end: ranges[0].end ? ranges[0].end + 1 : '',\n                })\n              : t('# ranges', { count: ranges.length })\n          }\n          placement={'top'}\n        >\n          <RangeIcon sizeClassName=\"w-3.5 h-3.5\" />\n        </Tooltip>\n      )}\n      {tokens !== null && (\n        <span\n          className={`code-mini w-10 text-right ${\n            (tokens || 0) < 18000 && (tokens || 0) > 1500\n              ? 'text-yellow'\n              : (tokens || 0) <= 1500\n              ? 'text-green'\n              : 'text-red'\n          }`}\n        >\n          {humanNumber(tokens || 0)}\n        </span>\n      )}\n    </StudioSubItem>\n  );\n};\n\nexport default memo(StudioFile);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/NavPanel/Studios/StudioHistory.tsx",
    "content": "import React, {\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useState,\n} from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { format } from 'date-fns';\nimport { HistoryConversationTurn } from '../../../../types/api';\nimport { getCodeStudioHistory } from '../../../../services/api';\nimport ChevronRight from '../../../../icons/ChevronRight';\nimport { ArrowHistoryIcon, DateTimeCalendarIcon } from '../../../../icons';\nimport { getDateFnsLocale } from '../../../../utils';\nimport { LocaleContext } from '../../../../context/localeContext';\nimport Badge from '../../../../components/Badge';\nimport { useArrowNavigationItemProps } from '../../../../hooks/useArrowNavigationItemProps';\nimport StudioSubItem from './StudioSubItem';\n\ntype Props = {\n  projectId: string;\n  studioId: string;\n  shouldRefresh: any;\n  index: string;\n  studioName: string;\n  previewingSnapshot?: string;\n};\n\nconst StudioHistory = ({\n  projectId,\n  studioId,\n  shouldRefresh,\n  index,\n  studioName,\n  previewingSnapshot,\n}: Props) => {\n  const { t } = useTranslation();\n  const { locale } = useContext(LocaleContext);\n  const [snapshots, setSnapshots] = useState<HistoryConversationTurn[]>([]);\n\n  const onClick = useCallback(() => {\n    setIsExpanded((prev) => !prev);\n  }, []);\n\n  const { isFocused, focusedIndex, props } =\n    useArrowNavigationItemProps<HTMLAnchorElement>(index, onClick);\n\n  const [isExpanded, setIsExpanded] = useState(focusedIndex.startsWith(index));\n\n  useEffect(() => {\n    getCodeStudioHistory(projectId, studioId).then((r) => setSnapshots(r));\n  }, [shouldRefresh, projectId, studioId]);\n\n  useEffect(() => {\n    if (focusedIndex.startsWith(index) && focusedIndex !== index) {\n      setIsExpanded(true);\n    }\n  }, [focusedIndex, index]);\n\n  return !snapshots.length ? null : (\n    <div>\n      <a\n        className={`w-full h-7 flex items-center gap-3 pl-10.5 pr-4 cursor-pointer ${\n          isFocused ? 'bg-bg-sub-hover text-label-title' : 'text-label-base'\n        }`}\n        {...props}\n      >\n        <span className=\"flex items-center gap-3\">\n          <ChevronRight\n            sizeClassName=\"w-3.5 h-3.5\"\n            className={`${\n              isExpanded ? 'rotate-90' : ''\n            } transition-transform duration-150`}\n          />\n          <ArrowHistoryIcon sizeClassName=\"w-3.5 h-3.5\" />\n          <span className=\"ellipsis\">\n            <Trans>History</Trans>\n          </span>\n        </span>\n      </a>\n      {isExpanded && (\n        <div className=\"relative\">\n          <div className=\"absolute top-0 bottom-3 left-12 w-px bg-bg-border\" />\n          {snapshots.map((s, i) => (\n            <StudioSubItem\n              key={s.id}\n              morePadding\n              studioId={studioId}\n              index={`${index}-${s.id}`}\n              studioName={studioName}\n              snapshot={i === 0 ? null : s}\n              isCurrentPath={previewingSnapshot === s.id}\n            >\n              <DateTimeCalendarIcon sizeClassName=\"w-3.5 h-3.5\" />\n              <span className=\"flex-1 ellipsis\">\n                {format(\n                  new Date(s.modified_at + '.000Z'),\n                  'd MMM · hh:mm a',\n                  getDateFnsLocale(locale),\n                )}\n              </span>\n              {i === 0 && (\n                <Badge text={t('Current')} type=\"green\" size=\"mini\" />\n              )}\n            </StudioSubItem>\n          ))}\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport default memo(StudioHistory);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/NavPanel/Studios/StudioSubItem.tsx",
    "content": "import React, { memo, PropsWithChildren, useCallback, useContext } from 'react';\nimport { TabsContext } from '../../../../context/tabsContext';\nimport { TabTypesEnum } from '../../../../types/general';\nimport { Range } from '../../../../types/results';\nimport { HistoryConversationTurn } from '../../../../types/api';\nimport { useArrowNavigationItemProps } from '../../../../hooks/useArrowNavigationItemProps';\n\ntype Props = {\n  index: string;\n  studioId: string;\n  studioName: string;\n  path?: string;\n  repoRef?: string;\n  branch?: string | null;\n  ranges?: Range[];\n  docId?: string;\n  relativeUrl?: string;\n  docTitle?: string;\n  docFavicon?: string;\n  sections?: string[];\n  morePadding?: boolean;\n  snapshot?: HistoryConversationTurn | null;\n  isCurrentPath?: boolean;\n};\n\nconst StudioSubItem = ({\n  index,\n  children,\n  studioId,\n  studioName,\n  path,\n  repoRef,\n  branch,\n  ranges,\n  relativeUrl,\n  docId,\n  docTitle,\n  docFavicon,\n  sections,\n  morePadding,\n  snapshot,\n  isCurrentPath,\n}: PropsWithChildren<Props>) => {\n  const { openNewTab } = useContext(TabsContext.Handlers);\n\n  const onClick = useCallback(() => {\n    if (path && repoRef) {\n      openNewTab({\n        type: TabTypesEnum.FILE,\n        path,\n        repoRef,\n        branch,\n        studioId,\n        isFileInContext: true,\n        initialRanges: ranges?.map((r) => [r.start, r.end]),\n      });\n    } else if (docId) {\n      openNewTab({\n        type: TabTypesEnum.DOC,\n        studioId,\n        title: docTitle,\n        relativeUrl: relativeUrl || '',\n        docId,\n        favicon: docFavicon,\n        isDocInContext: true,\n        initialSections: sections,\n      });\n    } else if (snapshot !== undefined) {\n      openNewTab({\n        type: TabTypesEnum.STUDIO,\n        studioId,\n        title: studioName,\n        snapshot: snapshot || undefined,\n      });\n    } else if (!path && !docId) {\n      openNewTab({ type: TabTypesEnum.STUDIO, studioId, title: studioName });\n    }\n  }, [\n    path,\n    openNewTab,\n    studioId,\n    studioName,\n    repoRef,\n    branch,\n    ranges,\n    sections,\n    docId,\n    docTitle,\n    docFavicon,\n    relativeUrl,\n    snapshot,\n  ]);\n\n  const { isFocused, isLeftSidebarFocused, props } =\n    useArrowNavigationItemProps<HTMLAnchorElement>(index, onClick);\n\n  return (\n    <a\n      href=\"#\"\n      className={`w-full h-7 flex items-center gap-3 ${\n        morePadding ? 'pl-[4.25rem]' : 'pl-10.5'\n      } pr-4 ${\n        isCurrentPath\n          ? isLeftSidebarFocused\n            ? 'bg-bg-shade-hover text-label-title'\n            : 'bg-bg-shade text-label-title'\n          : isFocused\n          ? 'bg-bg-sub-hover text-label-title'\n          : 'text-label-base'\n      }`}\n      {...props}\n    >\n      {children}\n    </a>\n  );\n};\n\nexport default memo(StudioSubItem);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/NavPanel/Studios/StudiosDropdown.tsx",
    "content": "import { memo, useCallback, useContext } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport DropdownSection from '../../../../components/Dropdown/Section';\nimport SectionItem from '../../../../components/Dropdown/Section/SectionItem';\nimport { TrashCanIcon } from '../../../../icons';\nimport { deleteCodeStudio } from '../../../../services/api';\nimport { ProjectContext } from '../../../../context/projectContext';\n\ntype Props = {};\n\nconst StudiosDropdown = ({}: Props) => {\n  const { t } = useTranslation();\n  const { project, refreshCurrentProjectStudios } = useContext(\n    ProjectContext.Current,\n  );\n\n  const handleRemoveAllStudios = useCallback(async () => {\n    if (project?.id && project.studios.length) {\n      await Promise.allSettled(\n        project.studios.map((c) => deleteCodeStudio(project.id, c.id)),\n      );\n      refreshCurrentProjectStudios();\n    }\n  }, [project?.id, project?.studios]);\n\n  return (\n    <div>\n      <DropdownSection>\n        <SectionItem\n          index={'del-chats'}\n          onClick={handleRemoveAllStudios}\n          label={t('Delete all conversations')}\n          icon={<TrashCanIcon sizeClassName=\"w-4 h-4\" />}\n        />\n      </DropdownSection>\n    </div>\n  );\n};\n\nexport default memo(StudiosDropdown);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/NavPanel/Studios/index.tsx",
    "content": "import React, {\n  Dispatch,\n  memo,\n  SetStateAction,\n  useContext,\n  useMemo,\n  useState,\n} from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport Dropdown from '../../../../components/Dropdown';\nimport {\n  ArrowTriangleBottomIcon,\n  CodeStudioIcon,\n  MoreHorizontalIcon,\n} from '../../../../icons';\nimport Button from '../../../../components/Button';\nimport { ProjectContext } from '../../../../context/projectContext';\nimport { useNavPanel } from '../../../../hooks/useNavPanel';\nimport {\n  DocTabType,\n  FileTabType,\n  IndexingStatusType,\n  StudioTabType,\n  TabTypesEnum,\n} from '../../../../types/general';\nimport StudioEntry from './StudioEntry';\nimport StudiosDropdown from './StudiosDropdown';\n\ntype Props = {\n  setExpanded: Dispatch<SetStateAction<string>>;\n  isExpanded: boolean;\n  index: string;\n  indexingStatus: IndexingStatusType;\n  currentlyFocusedTab?: StudioTabType | FileTabType | DocTabType;\n};\n\nconst reactRoot = document.getElementById('root')!;\n\nconst StudiosNav = ({\n  isExpanded,\n  setExpanded,\n  index,\n  indexingStatus,\n  currentlyFocusedTab,\n}: Props) => {\n  const { t } = useTranslation();\n  const [expandedIndex, setExpandedIndex] = useState('');\n  const { project } = useContext(ProjectContext.Current);\n  const { noPropagate, itemProps } = useNavPanel(\n    index,\n    setExpanded,\n    isExpanded,\n  );\n\n  const previewingSnapshot = useMemo(() => {\n    return currentlyFocusedTab?.type === TabTypesEnum.STUDIO &&\n      currentlyFocusedTab?.snapshot\n      ? {\n          studioId: currentlyFocusedTab.studioId,\n          snapshot: currentlyFocusedTab.snapshot,\n        }\n      : null;\n  }, [currentlyFocusedTab]);\n\n  const currentPath = useMemo(() => {\n    return currentlyFocusedTab?.type === TabTypesEnum.FILE &&\n      currentlyFocusedTab.studioId\n      ? {\n          studioId: currentlyFocusedTab.studioId,\n          path: currentlyFocusedTab.path,\n          repoRef: currentlyFocusedTab.repoRef,\n        }\n      : undefined;\n  }, [currentlyFocusedTab]);\n\n  const currentDoc = useMemo(() => {\n    return currentlyFocusedTab?.type === TabTypesEnum.DOC &&\n      currentlyFocusedTab.studioId\n      ? {\n          studioId: currentlyFocusedTab.studioId,\n          docId: currentlyFocusedTab.docId,\n          relativeUrl: currentlyFocusedTab.relativeUrl,\n        }\n      : undefined;\n  }, [currentlyFocusedTab]);\n\n  return (\n    <div className=\"select-none overflow-hidden w-full flex-shrink-0\">\n      <span {...itemProps}>\n        <CodeStudioIcon\n          sizeClassName=\"w-3.5 h-3.5\"\n          className=\"text-brand-studio\"\n        />\n        <p className=\"flex items-center gap-1 body-s-b flex-1 ellipsis\">\n          <span className=\"text-label-title ellipsis\">\n            <Trans>Studio conversations</Trans>\n          </span>\n          {isExpanded && (\n            <ArrowTriangleBottomIcon\n              sizeClassName=\"w-2 h-2\"\n              className=\"text-label-muted\"\n            />\n          )}\n        </p>\n        {isExpanded && (\n          <div onClick={noPropagate}>\n            <Dropdown\n              DropdownComponent={StudiosDropdown}\n              appendTo={reactRoot}\n              dropdownPlacement=\"bottom-start\"\n              size=\"auto\"\n            >\n              <Button\n                variant=\"tertiary\"\n                size=\"mini\"\n                onlyIcon\n                title={t('More actions')}\n              >\n                <MoreHorizontalIcon sizeClassName=\"w-3.5 h-3.5\" />\n              </Button>\n            </Dropdown>\n          </div>\n        )}\n      </span>\n      {isExpanded && (\n        <div className={'overflow-hidden'}>\n          {project?.studios.map((c) => (\n            <StudioEntry\n              key={c.id}\n              {...c}\n              index={`${index}-${c.id}`}\n              expandedIndex={expandedIndex}\n              setExpandedIndex={setExpandedIndex}\n              indexingStatus={indexingStatus}\n              projectId={project.id}\n              previewingSnapshot={\n                previewingSnapshot?.studioId.toString() === c.id.toString()\n                  ? previewingSnapshot.snapshot\n                  : null\n              }\n              currentPath={\n                currentPath?.studioId.toString() === c.id.toString()\n                  ? currentPath\n                  : undefined\n              }\n              currentDoc={\n                currentDoc?.studioId.toString() === c.id.toString()\n                  ? currentDoc\n                  : undefined\n              }\n              isViewingPrompts={\n                currentlyFocusedTab?.type === TabTypesEnum.STUDIO &&\n                currentlyFocusedTab.studioId.toString() === c.id.toString() &&\n                !currentlyFocusedTab.snapshot\n              }\n            />\n          ))}\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport default memo(StudiosNav);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/NavPanel/index.tsx",
    "content": "import { memo, useContext, useEffect, useMemo, useState } from 'react';\nimport { ProjectContext } from '../../../context/projectContext';\nimport { TabTypesEnum } from '../../../types/general';\nimport { TabsContext } from '../../../context/tabsContext';\nimport { RepositoriesContext } from '../../../context/repositoriesContext';\nimport { ArrowNavigationContext } from '../../../context/arrowNavigationContext';\nimport RepoNav from './Repo';\nimport ConversationsNav from './Conversations';\nimport StudiosNav from './Studios';\nimport DocNav from './Doc';\n\ntype Props = {};\n\nconst NavPanel = ({}: Props) => {\n  const [expanded, setExpanded] = useState('');\n  const { project } = useContext(ProjectContext.Current);\n  const { focusedPanel } = useContext(TabsContext.FocusedPanel);\n  const { tab: leftTab } = useContext(TabsContext.CurrentLeft);\n  const { tab: rightTab } = useContext(TabsContext.CurrentRight);\n  const { indexingStatus } = useContext(RepositoriesContext);\n  const { setFocusedIndex, focusedIndex } = useContext(ArrowNavigationContext);\n\n  const currentlyFocusedTab = useMemo(() => {\n    return focusedPanel === 'left' ? leftTab : rightTab;\n  }, [focusedPanel, leftTab, rightTab]);\n\n  useEffect(() => {\n    if (project?.repos.length === 1) {\n      setExpanded(`repo-${project.repos[0].repo.ref}`);\n    }\n  }, [project?.repos]);\n\n  useEffect(() => {\n    if (\n      currentlyFocusedTab?.type === TabTypesEnum.FILE &&\n      currentlyFocusedTab?.repoRef\n    ) {\n      if (!currentlyFocusedTab.studioId) {\n        setExpanded(`repo-${currentlyFocusedTab.repoRef}`);\n      } else {\n        setExpanded('studios');\n        const { studioId, repoRef, branch, path } = currentlyFocusedTab;\n        setFocusedIndex(`studios-${studioId}-${path}-${repoRef}-${branch}`);\n      }\n    } else if (currentlyFocusedTab?.type === TabTypesEnum.STUDIO) {\n      setExpanded('studios');\n      const { snapshot, studioId } = currentlyFocusedTab;\n      setFocusedIndex(\n        `studios-${studioId}-${\n          snapshot ? `history-${snapshot.id}` : 'prompts'\n        }`,\n      );\n    } else if (\n      currentlyFocusedTab?.type === TabTypesEnum.CHAT &&\n      currentlyFocusedTab.conversationId\n    ) {\n      setExpanded('conversations');\n      setFocusedIndex(`conversations-${currentlyFocusedTab.conversationId}`);\n    } else if (currentlyFocusedTab?.type === TabTypesEnum.DOC) {\n      const { studioId, docId, relativeUrl } = currentlyFocusedTab;\n      if (studioId) {\n        setExpanded('studios');\n        setFocusedIndex(`studios-${studioId}-${docId}-${relativeUrl}`);\n      } else {\n        setExpanded(`doc-${docId}`);\n        setFocusedIndex(`doc-${docId}-${relativeUrl}`);\n      }\n    }\n  }, [currentlyFocusedTab]);\n\n  return (\n    <div className=\"flex flex-col h-full flex-1 overflow-auto\">\n      {!!project?.studios.length && (\n        <StudiosNav\n          setExpanded={setExpanded}\n          isExpanded={expanded === 'studios'}\n          index={`studios`}\n          indexingStatus={indexingStatus}\n          currentlyFocusedTab={\n            currentlyFocusedTab?.type === TabTypesEnum.STUDIO ||\n            (currentlyFocusedTab?.type === TabTypesEnum.FILE &&\n              currentlyFocusedTab?.studioId) ||\n            (currentlyFocusedTab?.type === TabTypesEnum.DOC &&\n              currentlyFocusedTab?.studioId)\n              ? currentlyFocusedTab\n              : undefined\n          }\n        />\n      )}\n      {!!project?.conversations.length && (\n        <ConversationsNav\n          setExpanded={setExpanded}\n          isExpanded={expanded === 'conversations'}\n          index={`conversations`}\n          currentPath={\n            currentlyFocusedTab?.type === TabTypesEnum.CHAT\n              ? currentlyFocusedTab?.conversationId\n              : undefined\n          }\n        />\n      )}\n      {project?.repos.map((r) => (\n        <RepoNav\n          projectId={project?.id}\n          key={r.repo.ref}\n          setExpanded={setExpanded}\n          isExpanded={expanded === `repo-${r.repo.ref}`}\n          repoRef={r.repo.ref}\n          branch={r.branch}\n          lastIndex={r.repo.last_index}\n          allBranches={r.repo.branches}\n          indexingData={indexingStatus[r.repo.ref]}\n          indexedBranches={r.repo.branch_filter?.select || []}\n          currentPath={\n            currentlyFocusedTab?.type === TabTypesEnum.FILE &&\n            currentlyFocusedTab?.repoRef === r.repo.ref\n              ? currentlyFocusedTab?.path\n              : undefined\n          }\n          index={`repo-${r.repo.ref}`}\n        />\n      ))}\n      {project?.docs.map((d) => (\n        <DocNav\n          projectId={project?.id}\n          key={d.id}\n          setExpanded={setExpanded}\n          isExpanded={expanded === `doc-${d.id}`}\n          index={`doc-${d.id}`}\n          docId={d.id}\n          title={d.name}\n          favicon={d.favicon}\n          url={d.url}\n          currentPath={\n            currentlyFocusedTab?.type === TabTypesEnum.DOC &&\n            currentlyFocusedTab?.docId === d.id\n              ? currentlyFocusedTab?.relativeUrl\n              : undefined\n          }\n        />\n      ))}\n    </div>\n  );\n};\n\nexport default memo(NavPanel);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/RegexSearchPanel/AutocompleteMenu.tsx",
    "content": "import React, { memo, useMemo } from 'react';\nimport { Trans } from 'react-i18next';\nimport { ResultItemType, SuggestionType } from '../../../types/results';\nimport AutocompleteMenuItem from './AutocompleteMenuItem';\n\ntype Props = {\n  getMenuProps: () => any;\n  getItemProps: ({\n    item,\n    index,\n  }: {\n    item: SuggestionType;\n    index: number;\n  }) => any;\n  isOpen: boolean;\n  options: SuggestionType[];\n  highlightedIndex: number;\n};\n\nconst AutocompleteMenu = ({\n  getMenuProps,\n  isOpen,\n  options,\n  getItemProps,\n  highlightedIndex,\n}: Props) => {\n  const queryOptions = useMemo(\n    () =>\n      options.filter(\n        (o) =>\n          o.type === ResultItemType.FLAG ||\n          o.type === ResultItemType.LANG ||\n          o.type === ResultItemType.FILE ||\n          o.type === ResultItemType.REPO,\n      ),\n    [options],\n  );\n  const resultOptions = useMemo(\n    () => options.filter((o) => o.type === ResultItemType.CODE),\n    [options],\n  );\n\n  return (\n    <div className={`list-none`}>\n      <ul {...getMenuProps()}>\n        {isOpen && (\n          <>\n            {queryOptions.length ? (\n              <span className=\"text-label-base body-mini-b p-2\">\n                <Trans>Query suggestions</Trans>\n              </span>\n            ) : null}\n            {queryOptions.map((item, index) => (\n              <AutocompleteMenuItem\n                key={`${item}${index}`}\n                item={item}\n                index={index}\n                isFocused={highlightedIndex === index}\n                getItemProps={getItemProps}\n                isFirst={index === 0}\n              />\n            ))}\n            {resultOptions.length ? (\n              <span className=\"text-label-base body-mini-b p-2\">\n                <Trans>Result suggestions</Trans>\n              </span>\n            ) : null}\n            {resultOptions.map((item, index) => (\n              <AutocompleteMenuItem\n                key={`${item}${index}`}\n                item={item}\n                index={queryOptions.length + index}\n                isFocused={highlightedIndex === index + queryOptions.length}\n                getItemProps={getItemProps}\n                isFirst={index === 0}\n              />\n            ))}\n          </>\n        )}\n      </ul>\n    </div>\n  );\n};\n\nexport default memo(AutocompleteMenu);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/RegexSearchPanel/AutocompleteMenuItem.tsx",
    "content": "import React, { memo, useEffect, useMemo, useRef } from 'react';\nimport { Trans } from 'react-i18next';\nimport { ResultItemType, SuggestionType } from '../../../types/results';\nimport CodeResult from './Results/CodeResult';\n\ntype Props = {\n  item: SuggestionType;\n  index: number;\n  getItemProps: ({\n    item,\n    index,\n  }: {\n    item: SuggestionType;\n    index: number;\n  }) => any;\n  isFocused: boolean;\n  isFirst: boolean;\n};\n\nconst AutocompleteMenuItem = ({\n  getItemProps,\n  item,\n  index,\n  isFocused,\n  isFirst,\n}: Props) => {\n  const ref = useRef<HTMLAnchorElement>(null);\n\n  useEffect(() => {\n    if (isFocused) {\n      ref.current?.scrollIntoView({ block: 'nearest' });\n    }\n  }, [isFocused]);\n\n  const snippets = useMemo(() => {\n    if (item.type === ResultItemType.CODE) {\n      return item.snippets?.slice(0, 1).map((s) => ({\n        ...s,\n        line_range: {\n          start: s.lineStart || 0,\n          end: (s.lineStart || 0) + s.code.split('\\n').length,\n        },\n        highlights: s.highlights || [],\n        symbols: [],\n        data: s.code.split('\\n').slice(0, 5).join('\\n'), // don't render big snippets that have over 5 lines\n      }));\n    }\n    return [];\n  }, [item]);\n\n  return (\n    <li\n      {...getItemProps({ item, index })}\n      ref={ref}\n      tabIndex={0}\n      className={`text-label-base cursor-pointer w-full flex justify-between items-center ${\n        isFirst ? 'scroll-mt-8' : ''\n      } outline-0 outline-none ${\n        item.type === ResultItemType.FLAG ? 'h-9' : ''\n      } hover:bg-bg-base-hover gap-1group \n      ${\n        isFocused ? 'bg-bg-shade-hover' : 'focus:bg-bg-shade-hover'\n      } transition duration-150 ease-in-out`}\n    >\n      {item.type === ResultItemType.FLAG ||\n      item.type === ResultItemType.LANG ? (\n        <span className=\"body-mini flex-1 pl-6\">{item.data}</span>\n      ) : item.type === ResultItemType.CODE ? (\n        <span className=\"-ml-4\">\n          <CodeResult\n            snippets={snippets}\n            lang={item.language}\n            relative_path={item.relativePath}\n            repo_ref={item.repoRef}\n            isFirst={false}\n            index={`option-${index}`}\n          />\n        </span>\n      ) : item.type === ResultItemType.FILE ? (\n        <>\n          <span className=\"body-mini flex-1\">{item.relativePath}</span>\n          <span className=\"p-1 bg-bg-base rounded-sm body-mini text-label-muted group-hover:bg-bg-base-hover group-hover:text-label-title group-focus:bg-bg-base-hover group-focus:text-label-title transition duration-150 ease-in-slow\">\n            <Trans>File</Trans>\n          </span>\n        </>\n      ) : item.type === ResultItemType.REPO ? (\n        <>\n          <span className=\"body-mini flex-1\">{item.repoName}</span>\n          <span className=\"p-1 bg-bg-base rounded-sm body-mini text-label-muted group-hover:bg-bg-base-hover group-hover:text-label-title group-focus:bg-bg-base-hover group-focus:text-label-title transition duration-150 ease-in-slow\">\n            <Trans>Repository</Trans>\n          </span>\n        </>\n      ) : null}\n    </li>\n  );\n};\n\nexport default memo(AutocompleteMenuItem);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/RegexSearchPanel/Results/CodeLine.tsx",
    "content": "import React, { memo, useCallback, useContext, useMemo } from 'react';\nimport { TabTypesEnum } from '../../../../types/general';\nimport { CodeIcon } from '../../../../icons';\nimport { TabsContext } from '../../../../context/tabsContext';\nimport { getPrismLanguage, tokenizeCode } from '../../../../utils/prism';\nimport { HighlightMap, Range, TokensLine } from '../../../../types/results';\nimport { Token } from '../../../../types/prism';\nimport CodeToken from '../../../../components/Code/CodeToken';\nimport { useArrowNavigationItemProps } from '../../../../hooks/useArrowNavigationItemProps';\n\ntype Props = {\n  path: string;\n  repoRef: string;\n  lineStart: number;\n  lineEnd: number;\n  code: string;\n  language: string;\n  highlights: Range[];\n  index: string;\n};\n\nconst noOp = () => {};\n\nconst CodeLine = ({\n  path,\n  repoRef,\n  lineStart,\n  lineEnd,\n  code,\n  language,\n  highlights,\n  index,\n}: Props) => {\n  const { openNewTab } = useContext(TabsContext.Handlers);\n\n  const onClick = useCallback(() => {\n    openNewTab({\n      type: TabTypesEnum.FILE,\n      path,\n      repoRef,\n      scrollToLine: `${lineStart}_${lineEnd}`,\n    });\n  }, [path, lineEnd, lineStart, repoRef, openNewTab]);\n  const { isFocused, props } = useArrowNavigationItemProps<HTMLLIElement>(\n    index,\n    onClick,\n  );\n\n  const lang = useMemo(\n    () => getPrismLanguage(language) || 'plaintext',\n    [language],\n  );\n  const tokens = useMemo(() => tokenizeCode(code, lang), [code, lang]);\n\n  const hlRangesMap = useMemo(() => {\n    const hl = new Set();\n    highlights?.map((range) => {\n      for (let i = range.start; i < range.end; i++) {\n        hl.add(i);\n      }\n    });\n    return hl;\n  }, [highlights]);\n\n  const getMap = (tokens: Token[]): HighlightMap[] => {\n    const highlightMaps: HighlightMap[] = [];\n    tokens.forEach((token) => {\n      highlightMaps.push(...getToken(token));\n    });\n\n    return highlightMaps.map((item, i) => {\n      if (item.highlight) {\n        item.startHl = !highlightMaps[i - 1]?.highlight;\n        item.endHl = !highlightMaps[i + 1]?.highlight;\n      }\n\n      return item;\n    });\n  };\n\n  const getToken = (token: Token): HighlightMap[] => {\n    if (!highlights) {\n      return [{ highlight: false, token }];\n    }\n    const highlightMap: HighlightMap[] = [];\n\n    let byteIndex = 0;\n    token.content.split('').forEach((char) => {\n      const pos = token.byteRange.start + byteIndex;\n      const existing = highlightMap[highlightMap.length - 1];\n\n      if (hlRangesMap.has(pos)) {\n        if (!existing || !existing.highlight) {\n          highlightMap.push({\n            highlight: true,\n            token: {\n              ...token,\n              content: char,\n            },\n          });\n        } else {\n          existing.token.content += char;\n        }\n      } else {\n        if (!existing || existing.highlight) {\n          highlightMap.push({\n            highlight: false,\n            token: {\n              ...token,\n              content: char,\n            },\n          });\n        } else {\n          existing.token.content += char;\n        }\n      }\n      byteIndex += new TextEncoder().encode(char).length;\n    });\n\n    return highlightMap;\n  };\n\n  const tokensMap = useMemo((): TokensLine[] => {\n    const lines = tokens\n      .map((line) => getMap(line))\n      .map((l): TokensLine => ({ tokens: l, lineNumber: null }));\n    let currentLine = lineStart;\n    for (let i = 0; i < lines.length; i++) {\n      lines[i].lineNumber = currentLine + 1;\n      currentLine++;\n    }\n    return lines;\n  }, [tokens, lineStart]);\n\n  const lineToRender = useMemo(() => {\n    const ltr = tokensMap.find((l) => !!l.tokens.find((t) => t.highlight));\n    const firstHighlightIndex = ltr?.tokens.findIndex((t) => t.highlight) || 0;\n    if (ltr) {\n      ltr.tokens = ltr.tokens.slice(Math.max(firstHighlightIndex - 2, 0));\n    }\n    return ltr;\n  }, [tokensMap]);\n\n  return (\n    <li\n      className={`flex min-w-full w-max pl-16 pr-4 h-7 items-center gap-3 text-label-base whitespace-nowrap cursor-pointer ${\n        isFocused ? 'bg-bg-shade-hover' : ''\n      }`}\n      {...props}\n    >\n      <CodeIcon sizeClassName=\"w-3.5 h-3.5\" />\n      <p className={`code-mini prism-code language-${lang} ellipsis`}>\n        {(lineToRender || tokensMap[0]).tokens.map((token, index) => (\n          <CodeToken\n            key={index}\n            token={token.token}\n            highlight={token.highlight}\n            startHl={token.startHl}\n            endHl={token.endHl}\n            onClick={noOp}\n          />\n        ))}\n      </p>\n    </li>\n  );\n};\n\nexport default memo(CodeLine);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/RegexSearchPanel/Results/CodeResult.tsx",
    "content": "import React, { memo, useCallback, useState } from 'react';\nimport { SnippetItem } from '../../../../types/api';\nimport { ChevronRightIcon } from '../../../../icons';\nimport FileIcon from '../../../../components/FileIcon';\nimport { useArrowNavigationItemProps } from '../../../../hooks/useArrowNavigationItemProps';\nimport CodeLine from './CodeLine';\n\ntype Props = {\n  relative_path: string;\n  repo_ref: string;\n  lang: string;\n  snippets: SnippetItem[];\n  index: string;\n  isFirst: boolean;\n};\n\nconst CodeResult = ({\n  relative_path,\n  repo_ref,\n  lang,\n  snippets,\n  index,\n  isFirst,\n}: Props) => {\n  const [isExpanded, setIsExpanded] = useState(true);\n\n  const onClick = useCallback(() => {\n    setIsExpanded((prev) => !prev);\n  }, []);\n\n  const { isFocused, props } = useArrowNavigationItemProps<HTMLDivElement>(\n    index,\n    onClick,\n  );\n\n  return (\n    <div className=\"relative flex flex-col\">\n      <span className=\"absolute top-7 bottom-0 left-11.5 w-px bg-bg-border\" />\n      <div\n        className={`flex w-max min-w-full items-center gap-3 whitespace-nowrap body-mini text-label-title h-7 flex-shrink-0 ${\n          isFirst ? 'scroll-mt-10' : ''\n        } ${isFocused ? 'bg-bg-shade-hover' : ''} pl-10 pr-4 cursor-pointer`}\n        {...props}\n      >\n        <ChevronRightIcon\n          sizeClassName=\"w-3.5 h-3.5\"\n          className={`${\n            isExpanded ? 'rotate-90' : 'rotate-0'\n          } transition-transform duration-150 ease-in-out`}\n        />\n        <FileIcon filename={relative_path} noMargin />\n        {/*<BreadcrumbsPathContainer*/}\n        {/*  path={relative_path}*/}\n        {/*  onClick={handleClick}*/}\n        {/*  repo={repo_ref}*/}\n        {/*/>*/}\n        <div>{relative_path}</div>\n      </div>\n      <ul className=\"\">\n        {isExpanded\n          ? snippets.map((s, i) => (\n              <CodeLine\n                key={i}\n                code={s.data}\n                path={relative_path}\n                language={lang}\n                repoRef={repo_ref}\n                lineStart={s.line_range.start}\n                lineEnd={s.line_range.end}\n                highlights={s.highlights}\n                index={`${index}-${i}`}\n              />\n            ))\n          : null}\n      </ul>\n    </div>\n  );\n};\n\nexport default memo(CodeResult);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/RegexSearchPanel/Results/FileResult.tsx",
    "content": "import React, {\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useState,\n} from 'react';\nimport FileIcon from '../../../../components/FileIcon';\nimport { TabTypesEnum } from '../../../../types/general';\nimport { TabsContext } from '../../../../context/tabsContext';\nimport { DirectoryEntry, RepoFileNameItem } from '../../../../types/api';\nimport { FolderIcon } from '../../../../icons';\nimport { getFolderContent } from '../../../../services/api';\nimport RepoEntry from '../../NavPanel/Repo/RepoEntry';\nimport { useArrowNavigationItemProps } from '../../../../hooks/useArrowNavigationItemProps';\n\ntype Props = {\n  relative_path: RepoFileNameItem;\n  repo_ref: string;\n  is_dir: boolean;\n  index: string;\n  isFirst: boolean;\n};\n\nconst FileResult = ({\n  relative_path,\n  repo_ref,\n  is_dir,\n  index,\n  isFirst,\n}: Props) => {\n  const { openNewTab } = useContext(TabsContext.Handlers);\n  const [isExpanded, setIsExpanded] = useState(false);\n  const [files, setFiles] = useState<DirectoryEntry[]>([]);\n\n  const fetchFiles = useCallback(\n    async (path?: string) => {\n      const resp = await getFolderContent(repo_ref, path);\n      if (!resp.entries) {\n        return [];\n      }\n      return resp?.entries.sort((a, b) => {\n        if ((a.entry_data === 'Directory') === (b.entry_data === 'Directory')) {\n          return a.name?.toLowerCase() < b.name?.toLowerCase() ? -1 : 1;\n        } else {\n          return a.entry_data === 'Directory' ? -1 : 1;\n        }\n      });\n    },\n    [repo_ref],\n  );\n\n  useEffect(() => {\n    if (isExpanded && !files.length) {\n      fetchFiles(relative_path.text).then(setFiles);\n    }\n  }, [fetchFiles, files, isExpanded, relative_path.text]);\n\n  const onClick = useCallback(() => {\n    if (is_dir) {\n      setIsExpanded((prev) => !prev);\n    } else {\n      openNewTab({\n        type: TabTypesEnum.FILE,\n        path: relative_path.text,\n        repoRef: repo_ref,\n      });\n    }\n  }, [relative_path, repo_ref, is_dir, openNewTab]);\n\n  const { isFocused, props } = useArrowNavigationItemProps(index, onClick);\n\n  return (\n    <span\n      className={`flex flex-col flex-shrink-0 ${\n        isExpanded ? '' : 'h-7 overflow-hidden'\n      }`}\n    >\n      <span\n        className={`flex items-center w-max gap-3 body-mini text-label-title h-7 flex-shrink-0 cursor-pointer  ${\n          isFirst ? 'scroll-mt-10' : ''\n        } scroll-ml-10 ${isFocused ? 'bg-bg-shade-hover' : ''} pl-10 pr-4`}\n        {...props}\n      >\n        {is_dir ? (\n          <FolderIcon sizeClassName=\"w-4 h-4\" />\n        ) : (\n          <FileIcon filename={relative_path.text} noMargin />\n        )}\n        {/*<BreadcrumbsPathContainer*/}\n        {/*  path={is_dir ? relative_path.text.slice(0, -1) : relative_path.text}*/}\n        {/*  onClick={handleClick}*/}\n        {/*  repo={repo_ref}*/}\n        {/*/>*/}\n        <span>{relative_path.text}</span>\n      </span>\n      {isExpanded && (\n        <div\n          className={`-ml-3 ${\n            isExpanded ? 'overflow-auto' : 'overflow-hidden'\n          }`}\n        >\n          {files.map((f, fi) => (\n            <RepoEntry\n              key={f.name}\n              name={f.name}\n              indexed={\n                f.entry_data !== 'Directory' ? f.entry_data.File.indexed : true\n              }\n              isDirectory={f.entry_data === 'Directory'}\n              level={3}\n              fetchFiles={fetchFiles}\n              fullPath={f.name}\n              repoRef={repo_ref}\n              index={`${index}-${fi}`}\n              lastIndex={''}\n            />\n          ))}\n        </div>\n      )}\n    </span>\n  );\n};\n\nexport default memo(FileResult);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/RegexSearchPanel/Results/RepoResult.tsx",
    "content": "import React, { memo, useCallback, useEffect, useState } from 'react';\nimport GitHubIcon from '../../../../icons/GitHubIcon';\nimport { HardDriveIcon } from '../../../../icons';\nimport { splitPath } from '../../../../utils';\nimport { DirectoryEntry } from '../../../../types/api';\nimport { getFolderContent } from '../../../../services/api';\nimport RepoEntry from '../../NavPanel/Repo/RepoEntry';\nimport { useArrowNavigationItemProps } from '../../../../hooks/useArrowNavigationItemProps';\n\ntype Props = {\n  repoRef: string;\n  index: string;\n  isExpandable?: boolean;\n};\n\nconst RepoResult = ({ repoRef, isExpandable, index }: Props) => {\n  const [isExpanded, setIsExpanded] = useState(false);\n  const [files, setFiles] = useState<DirectoryEntry[]>([]);\n\n  const fetchFiles = useCallback(\n    async (path?: string) => {\n      const resp = await getFolderContent(repoRef, path);\n      if (!resp.entries) {\n        return [];\n      }\n      return resp?.entries.sort((a, b) => {\n        if ((a.entry_data === 'Directory') === (b.entry_data === 'Directory')) {\n          return a.name?.toLowerCase() < b.name?.toLowerCase() ? -1 : 1;\n        } else {\n          return a.entry_data === 'Directory' ? -1 : 1;\n        }\n      });\n    },\n    [repoRef],\n  );\n\n  useEffect(() => {\n    if (isExpanded && !files.length) {\n      fetchFiles().then(setFiles);\n    }\n  }, [fetchFiles, files, isExpanded]);\n\n  const onClick = useCallback(() => {\n    if (isExpandable) {\n      setIsExpanded((prev) => !prev);\n    }\n  }, [isExpandable]);\n\n  const { isFocused, props } = useArrowNavigationItemProps<HTMLAnchorElement>(\n    index,\n    onClick,\n  );\n\n  return (\n    <span\n      className={`flex flex-col flex-shrink-0 ${\n        isExpanded ? '' : 'h-10 overflow-hidden'\n      }`}\n    >\n      <a\n        href=\"#\"\n        className={`h-10 flex-shrink-0 ${\n          isExpandable\n            ? isFocused\n              ? 'bg-bg-sub-hover'\n              : 'bg-bg-sub'\n            : 'bg-bg-sub'\n        } flex items-center gap-3 px-4 body-s-b text-label-title`}\n        {...(isExpandable ? props : {})}\n      >\n        {repoRef.startsWith('github.com/') ? (\n          <GitHubIcon sizeClassName=\"w-3 h-3\" />\n        ) : (\n          <HardDriveIcon sizeClassName=\"w-3 h-3\" />\n        )}\n        {splitPath(repoRef)\n          .slice(repoRef.startsWith('github.com/') ? -2 : -1)\n          .join('/')}\n      </a>\n      {isExpanded && (\n        <div className={isExpanded ? 'overflow-auto' : 'overflow-hidden'}>\n          {files.map((f, fi) => (\n            <RepoEntry\n              key={f.name}\n              name={f.name}\n              indexed={\n                f.entry_data !== 'Directory' ? f.entry_data.File.indexed : true\n              }\n              isDirectory={f.entry_data === 'Directory'}\n              level={1}\n              fetchFiles={fetchFiles}\n              fullPath={f.name}\n              repoRef={repoRef}\n              index={`${index}-${fi}`}\n              lastIndex={''}\n            />\n          ))}\n        </div>\n      )}\n    </span>\n  );\n};\n\nexport default memo(RepoResult);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/RegexSearchPanel/index.tsx",
    "content": "import React, {\n  ChangeEvent,\n  FormEvent,\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { CloseSignIcon, RegexSearchIcon } from '../../../icons';\nimport Button from '../../../components/Button';\nimport useKeyboardNavigation from '../../../hooks/useKeyboardNavigation';\nimport { search } from '../../../services/api';\nimport {\n  CodeItem,\n  DirectoryItem,\n  FileItem,\n  FileResItem,\n  RepoItem,\n} from '../../../types/api';\nimport { ProjectContext } from '../../../context/projectContext';\nimport { CommandBarContext } from '../../../context/commandBarContext';\nimport { regexToggleShortcut } from '../../../consts/shortcuts';\nimport { UIContext } from '../../../context/uiContext';\nimport { ArrowNavigationContext } from '../../../context/arrowNavigationContext';\nimport CodeResult from './Results/CodeResult';\nimport RepoResult from './Results/RepoResult';\nimport FileResult from './Results/FileResult';\n\ntype Props = {\n  projectId?: string;\n  isRegexEnabled?: boolean;\n};\n\n// const getAutocompleteThrottled = throttle(\n//   async (\n//     query: string,\n//     setOptions: (o: SuggestionType[]) => void,\n//   ): Promise<void> => {\n//     const newOptions = await getAutocomplete(query);\n//     setOptions(mapResults(newOptions));\n//   },\n//   100,\n//   { trailing: true, leading: false },\n// );\n\ntype ResultType = CodeItem | RepoItem | FileResItem | DirectoryItem | FileItem;\n\nconst RegexSearchPanel = ({ projectId, isRegexEnabled }: Props) => {\n  const { t } = useTranslation();\n  // const { openNewTab } = useContext(TabsContext.Handlers);\n  const [inputValue, setInputValue] = useState('');\n  // const [options, setOptions] = useState<SuggestionType[]>([]);\n  const [results, setResults] = useState<Record<string, ResultType[]>>({});\n  const [resultsRaw, setResultsRaw] = useState<ResultType[]>([]);\n  const inputRef = useRef<HTMLInputElement>(null);\n  const { setIsRegexSearchEnabled } = useContext(ProjectContext.RegexSearch);\n  const { isVisible } = useContext(CommandBarContext.General);\n  const { isLeftSidebarFocused, setIsLeftSidebarFocused } = useContext(\n    UIContext.Focus,\n  );\n  const { isVisible: isCommandBarVisible } = useContext(\n    CommandBarContext.General,\n  );\n  const { setFocusedIndex, focusedIndex } = useContext(ArrowNavigationContext);\n\n  const onChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n    setInputValue(e.target.value);\n    setFocusedIndex('input');\n  }, []);\n\n  const onClear = useCallback(() => {\n    setInputValue('');\n    if (!inputValue) {\n      setIsRegexSearchEnabled(false);\n    }\n  }, [inputValue]);\n\n  // const {\n  //   isOpen,\n  //   getMenuProps,\n  //   getInputProps,\n  //   getItemProps,\n  //   closeMenu,\n  //   highlightedIndex,\n  // } = useCombobox({\n  //   inputValue,\n  //   onStateChange: async (state) => {\n  //     if (\n  //       state.type === useCombobox.stateChangeTypes.ItemClick ||\n  //       state.type === useCombobox.stateChangeTypes.InputKeyDownEnter\n  //     ) {\n  //       if (state.selectedItem?.type === ResultItemType.FLAG) {\n  //         const words = inputValue.split(' ');\n  //         words[words.length - 1] =\n  //           state.selectedItem?.data || words[words.length - 1];\n  //         const newInputValue = words.join(' ') + ':';\n  //         setInputValue(newInputValue);\n  //       } else if (state.selectedItem?.type === ResultItemType.LANG) {\n  //         setInputValue(\n  //           (prev) =>\n  //             prev.split(':').slice(0, -1).join(':') +\n  //             ':' +\n  //             (state.selectedItem as LangResult)?.data,\n  //         );\n  //       } else {\n  //         if (\n  //           state.selectedItem?.type === ResultItemType.FILE ||\n  //           state.selectedItem?.type === ResultItemType.CODE\n  //         ) {\n  //           openNewTab({\n  //             type: TabTypesEnum.FILE,\n  //             branch: null,\n  //             repoRef: state.selectedItem.repoRef,\n  //             path: state.selectedItem.relativePath,\n  //           });\n  //         }\n  //       }\n  //       inputRef.current?.focus();\n  //     } else if (state.type === useCombobox.stateChangeTypes.InputChange) {\n  //       if (state.inputValue === '') {\n  //         setInputValue(state.inputValue);\n  //         setOptions([]);\n  //         return;\n  //       }\n  //       if (!state.inputValue) {\n  //         return;\n  //       }\n  //       let autocompleteQuery = state.inputValue;\n  //       getAutocompleteThrottled(autocompleteQuery, setOptions);\n  //     }\n  //   },\n  //   items: options,\n  //   itemToString(item) {\n  //     return (\n  //       (item?.type === ResultItemType.FLAG ||\n  //       item?.type === ResultItemType.LANG\n  //         ? item?.data\n  //         : '') || ''\n  //     );\n  //   },\n  // });\n\n  useEffect(() => {\n    if (focusedIndex === 'input') {\n      inputRef.current?.focus();\n    }\n  }, [focusedIndex]);\n\n  const onSubmit = useCallback(\n    async (e: FormEvent) => {\n      e.preventDefault();\n      if (focusedIndex === 'input') {\n        if (projectId) {\n          const data = await search(projectId, inputValue);\n          const newResults: Record<string, ResultType[]> = {};\n          data.data.forEach((d) => {\n            if (!newResults[d.data.repo_ref]) {\n              newResults[d.data.repo_ref] = [d];\n            } else {\n              newResults[d.data.repo_ref].push(d);\n            }\n          });\n          setResults(newResults);\n          setResultsRaw(data.data);\n          // closeMenu();\n        }\n      }\n    },\n    [inputValue, focusedIndex],\n  );\n\n  const handleKeyEvent = useCallback(\n    (e: KeyboardEvent) => {\n      if (e.key === 'Escape') {\n        e.preventDefault();\n        e.stopPropagation();\n        if (focusedIndex === 'input') {\n          onClear();\n        } else {\n          setFocusedIndex('input');\n        }\n      }\n    },\n    [focusedIndex, onClear],\n  );\n  useKeyboardNavigation(\n    handleKeyEvent,\n    isVisible ||\n      !isRegexEnabled ||\n      !isLeftSidebarFocused ||\n      isCommandBarVisible,\n  );\n\n  useEffect(() => {\n    if (isRegexEnabled) {\n      setIsLeftSidebarFocused(true);\n    }\n  }, [isRegexEnabled]);\n\n  return !isRegexEnabled ? null : (\n    <div className=\"flex flex-col h-full flex-1 overflow-auto\">\n      <form\n        className=\"flex items-center h-10 flex-shrink-0 px-4 py-1.5 gap-3 border-b border-bg-border text-label-title\"\n        onSubmit={onSubmit}\n      >\n        <RegexSearchIcon sizeClassName=\"w-3.5 h-3.5\" />\n        <input\n          //{...getInputProps({\n          //  onChange,\n          // })}\n          className=\"bg-transparent flex-1 placeholder:text-label-muted body-mini focus:outline-0 focus:outline-none ellipsis\"\n          placeholder={t('Files, paths or folders...')}\n          value={inputValue}\n          onChange={onChange}\n          autoFocus\n          type=\"search\"\n          autoCorrect=\"off\"\n          autoComplete=\"off\"\n          ref={inputRef}\n          id=\"regex-search\"\n          data-node-index=\"input\"\n        />\n        <Button\n          variant=\"danger\"\n          size=\"mini\"\n          onlyIcon\n          title={inputValue ? t('Clear input') : t('Close search')}\n          shortcut={inputValue ? undefined : regexToggleShortcut}\n          type=\"button\"\n          onClick={onClear}\n        >\n          <CloseSignIcon sizeClassName=\"w-3 h-3\" />\n        </Button>\n      </form>\n      {/*<AutocompleteMenu*/}\n      {/*  getMenuProps={getMenuProps}*/}\n      {/*  getItemProps={getItemProps}*/}\n      {/*  isOpen={isOpen && !!options.length}*/}\n      {/*  options={options}*/}\n      {/*  highlightedIndex={highlightedIndex}*/}\n      {/*/>*/}\n      {!!Object.keys(results).length && (\n        <ul className=\"flex-1 flex flex-col overflow-y-auto\">\n          {Object.keys(results).map((repoRef, repoIndex) => (\n            <li key={repoRef} className=\"relative flex flex-col\">\n              <span className=\"absolute top-10 bottom-0 left-5 w-px bg-bg-border\" />\n              <RepoResult\n                repoRef={repoRef}\n                index={repoIndex.toString()}\n                isExpandable={results[repoRef][0].kind === 'repository_result'}\n              />\n              <ul className=\"flex flex-col\">\n                {results[repoRef].map((r, i) => (\n                  <li key={i} className=\"flex flex-col\">\n                    {r.kind === 'snippets' ? (\n                      <CodeResult\n                        {...r.data}\n                        isFirst={i === 0}\n                        index={`${repoIndex}-code-${i}`}\n                      />\n                    ) : r.kind === 'file_result' ? (\n                      <FileResult\n                        {...r.data}\n                        isFirst={i === 0}\n                        index={`${repoIndex}-file-${i}`}\n                      />\n                    ) : null}\n                  </li>\n                ))}\n              </ul>\n            </li>\n          ))}\n        </ul>\n      )}\n    </div>\n  );\n};\n\nexport default memo(RegexSearchPanel);\n"
  },
  {
    "path": "client/src/Project/LeftSidebar/index.tsx",
    "content": "import React, {\n  memo,\n  useCallback,\n  useContext,\n  MouseEvent,\n  useMemo,\n} from 'react';\nimport useResizeableWidth from '../../hooks/useResizeableWidth';\nimport { LEFT_SIDEBAR_WIDTH_KEY } from '../../services/storage';\nimport ProjectsDropdown from '../../components/Header/ProjectsDropdown';\nimport { ChevronDownIcon } from '../../icons';\nimport Dropdown from '../../components/Dropdown';\nimport { DeviceContext } from '../../context/deviceContext';\nimport { ProjectContext } from '../../context/projectContext';\nimport { UIContext } from '../../context/uiContext';\nimport { checkEventKeys } from '../../utils/keyboardUtils';\nimport useKeyboardNavigation from '../../hooks/useKeyboardNavigation';\nimport { CommandBarContext } from '../../context/commandBarContext';\nimport { useArrowNavigation } from '../../hooks/useArrowNavigation';\nimport { ArrowNavigationContext } from '../../context/arrowNavigationContext';\nimport { noOp } from '../../utils';\nimport RegexSearchPanel from './RegexSearchPanel';\nimport NavPanel from './NavPanel';\n\ntype Props = {};\n\nconst LeftSidebar = ({}: Props) => {\n  const { os } = useContext(DeviceContext);\n  const { project } = useContext(ProjectContext.Current);\n  const { isRegexSearchEnabled } = useContext(ProjectContext.RegexSearch);\n  const { setIsLeftSidebarFocused, isLeftSidebarFocused } = useContext(\n    UIContext.Focus,\n  );\n  const { isVisible: isCommandBarVisible } = useContext(\n    CommandBarContext.General,\n  );\n  const { focusedIndex, setFocusedIndex, handleArrowKey, navContainerRef } =\n    useArrowNavigation();\n\n  const { panelRef, dividerRef } = useResizeableWidth(\n    true,\n    LEFT_SIDEBAR_WIDTH_KEY,\n    20,\n    40,\n  );\n\n  const handleKeyEvent = useCallback(\n    (e: KeyboardEvent) => {\n      if (checkEventKeys(e, ['cmd', '0'])) {\n        e.preventDefault();\n        e.stopPropagation();\n        setIsLeftSidebarFocused(true);\n      }\n      if (isLeftSidebarFocused) {\n        handleArrowKey(e);\n      }\n    },\n    [isLeftSidebarFocused, handleArrowKey],\n  );\n  useKeyboardNavigation(handleKeyEvent, isCommandBarVisible);\n\n  const handleClick = useCallback((e: MouseEvent) => {\n    e.stopPropagation();\n    setIsLeftSidebarFocused(true);\n  }, []);\n\n  const contextValue = useMemo(\n    () => ({\n      focusedIndex,\n      setFocusedIndex,\n      handleClose: noOp,\n    }),\n    [focusedIndex],\n  );\n\n  return (\n    <div\n      className=\"h-full relative z-10 min-w-[204px] flex-shrink-0 overflow-hidden flex flex-col\"\n      ref={panelRef}\n    >\n      <div className=\"w-ful flex gap-4 hover:bg-bg-base-hover border-b border-bg-border h-10 overflow-hidden\">\n        {os.type === 'Darwin' ? <span className=\"w-16 flex-shrink-0\" /> : ''}\n        <Dropdown\n          DropdownComponent={ProjectsDropdown}\n          dropdownPlacement=\"bottom-start\"\n          containerClassName=\"flex-1 overflow-hidden\"\n          appendTo={document.body}\n        >\n          <div className=\"flex-1 flex px-4 items-center text-left h-10 gap-4 border-r border-bg-border overflow-hidden\">\n            <p className=\"flex-1 body-s-b ellipsis\">\n              {project?.name || 'Default project'}\n            </p>\n            <ChevronDownIcon raw sizeClassName=\"w-3.5 h-3.5\" />\n          </div>\n        </Dropdown>\n      </div>\n      <div\n        onClick={handleClick}\n        className=\"flex-1 overflow-auto\"\n        ref={navContainerRef}\n      >\n        <ArrowNavigationContext.Provider value={contextValue}>\n          <RegexSearchPanel\n            projectId={project?.id}\n            isRegexEnabled={isRegexSearchEnabled}\n          />\n          {!isRegexSearchEnabled && <NavPanel />}\n        </ArrowNavigationContext.Provider>\n      </div>\n      <div\n        ref={dividerRef}\n        className=\"absolute top-0 right-0 transform group translate-x-1/2 w-2.5 h-full bottom-0 cursor-col-resize flex-shrink-0 z-10\"\n      >\n        <div className=\"mx-auto w-0.5 h-full bg-bg-border group-hover:bg-brand-default\" />\n      </div>\n    </div>\n  );\n};\n\nexport default memo(LeftSidebar);\n"
  },
  {
    "path": "client/src/Project/RightTab.tsx",
    "content": "import React, { memo } from 'react';\nimport useResizeableWidth from '../hooks/useResizeableWidth';\nimport { RIGHT_SIDEBAR_WIDTH_KEY } from '../services/storage';\nimport { TabType } from '../types/general';\nimport CurrentTabContent from './CurrentTabContent';\n\ntype Props = {\n  onDropToRight: (tab: TabType) => void;\n  moveToAnotherSide: (tab: TabType) => void;\n};\n\nconst RightTab = ({ onDropToRight, moveToAnotherSide }: Props) => {\n  const { panelRef, dividerRef } = useResizeableWidth(\n    false,\n    RIGHT_SIDEBAR_WIDTH_KEY,\n    40,\n    60,\n    15,\n  );\n\n  return (\n    <div ref={panelRef} className=\"overflow-hidden relative flex-shrink-0\">\n      <div\n        ref={dividerRef}\n        className=\"absolute top-0 left-0 transform group -translate-x-1/2 w-2.5 h-full bottom-0 cursor-col-resize flex-shrink-0 z-10\"\n      >\n        <div className=\"mx-auto w-0.5 h-full bg-bg-border group-hover:bg-brand-default\" />\n      </div>\n      <CurrentTabContent\n        side=\"right\"\n        onDrop={onDropToRight}\n        moveToAnotherSide={moveToAnotherSide}\n      />\n    </div>\n  );\n};\n\nexport default memo(RightTab);\n"
  },
  {
    "path": "client/src/Project/TutorialCards.tsx",
    "content": "import React, {\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport {\n  ChatBubblesIcon,\n  CodeLineWithSparkleIcon,\n  FileWithSparksIcon,\n  RefIcon,\n} from '../icons';\nimport MultiKeyHint from '../components/KeyboardHint/MultiKey';\nimport { explainFileShortcut, newChatTabShortcut } from '../consts/shortcuts';\nimport Button from '../components/Button';\nimport { TabsContext } from '../context/tabsContext';\nimport { TabTypesEnum } from '../types/general';\nimport { CommandBarContext } from '../context/commandBarContext';\nimport { UIContext } from '../context/uiContext';\n\ntype Props = {};\n\nconst cards = [\n  {\n    Icon: ChatBubblesIcon,\n    title: 'Ask your first question',\n    description: (\n      <>\n        Create a new conversation with bloop by hitting{' '}\n        <MultiKeyHint shortcut={newChatTabShortcut} variant=\"outlined\" /> on\n        your keyboard or by pressing the <MultiKeyHint shortcut={['+']} /> in\n        the header bar.\n      </>\n    ),\n    btnTitle: 'New conversation',\n  },\n  {\n    Icon: FileWithSparksIcon,\n    title: 'Explain a file',\n    description: (\n      <>\n        To begin, open a file from the sidebar on the left. Once you have a file\n        open, you can ask bloop to quickly explain it by hitting{' '}\n        <MultiKeyHint shortcut={explainFileShortcut} variant=\"outlined\" /> on{' '}\n        {/* eslint-disable-next-line react/no-unescaped-entities */}\n        your keyboard or by selecting \"Explain file\" from the{' '}\n        <MultiKeyHint shortcut={['⋯']} /> popup menu.\n      </>\n    ),\n    btnTitle: 'Explain current file',\n  },\n  {\n    Icon: CodeLineWithSparkleIcon,\n    title: 'Explain code',\n    description: (\n      <>\n        Use your cursor to select any piece of code within a file and ask bloop{' '}\n        {/* eslint-disable-next-line react/no-unescaped-entities */}\n        to explain it by pressing \"Explain\" in the floating toolbar.\n      </>\n    ),\n  },\n  {\n    Icon: RefIcon,\n    title: 'Navigate your codebase',\n    description:\n      'Click on an identifier and jump to its references and definition in a heart beat.',\n  },\n];\n\nconst TutorialCards = ({}: Props) => {\n  useTranslation();\n  const [step, setStep] = useState(0);\n  const { openNewTab } = useContext(TabsContext.Handlers);\n  const { tabItems } = useContext(CommandBarContext.FocusedTab);\n  const { onBoardingState, setOnBoardingState } = useContext(\n    UIContext.Onboarding,\n  );\n  const [isManualControl, setIsManualControl] = useState(false);\n\n  const onSkip = useCallback(() => {\n    setOnBoardingState({\n      isChatOpened: true,\n      isFileExplained: true,\n      isCodeExplained: true,\n      isCodeNavigated: true,\n      isCommandBarTutorialFinished: true,\n    });\n  }, []);\n\n  useEffect(() => {\n    if (!isManualControl) {\n      if (step === 0 && onBoardingState.isChatOpened) {\n        setStep(1);\n      } else if (step === 1 && onBoardingState.isFileExplained) {\n        setStep(2);\n      } else if (step === 2 && onBoardingState.isCodeExplained) {\n        setStep(3);\n      } else if (step === 3 && onBoardingState.isCodeNavigated) {\n        onSkip();\n      }\n    }\n  }, [onBoardingState, step, onSkip, isManualControl]);\n\n  const handleNext = useCallback(() => {\n    setStep((prev) => prev + 1);\n  }, []);\n\n  const handleBack = useCallback((i: number) => {\n    setStep(i);\n    setIsManualControl(true);\n  }, []);\n\n  const { Icon, title, description, btnTitle } = useMemo(() => {\n    return cards[step];\n  }, [step]);\n\n  const explainCurrentFile = useMemo(() => {\n    return tabItems.find((i) => i.key === 'explain_file')?.onClick;\n  }, [tabItems]);\n\n  const onBtnClick = useCallback(\n    (e: React.MouseEvent) => {\n      if (btnTitle === 'New conversation') {\n        openNewTab({ type: TabTypesEnum.CHAT });\n        handleNext();\n      } else if (btnTitle === 'Explain current file' && explainCurrentFile) {\n        explainCurrentFile(e);\n        handleNext();\n      }\n    },\n    [btnTitle, handleNext, explainCurrentFile],\n  );\n\n  return (\n    <div className=\"absolute right-0 bottom-0 px-8 py-8 w-full max-w-[26rem] select-none\">\n      <div className=\"flex flex-col gap-4 p-4 rounded-md border border-bg-border bg-bg-base shadow-high\">\n        <div className=\"flex flex-col gap-1.5 items-end w-full\">\n          <div className=\"flex items-start gap-3 w-full\">\n            {/*// @ts-ignore*/}\n            <Icon sizeClassName=\"w-4 h-4\" className=\"text-label-muted\" />\n            <p className=\"flex-1 text-label-title body-s-b\">\n              <Trans>{title}</Trans>\n            </p>\n            <div className=\"flex gap-1 items-start\">\n              {cards.map((c, i) => (\n                <button\n                  className={`w-1.5 h-1.5 rounded-full ${\n                    step === i ? 'bg-label-link' : 'bg-label-faint'\n                  }`}\n                  onClick={() => handleBack(i)}\n                  key={i}\n                />\n              ))}\n            </div>\n          </div>\n          <p className=\"pl-7 leading-5 body-s text-label-base\">\n            <Trans>{description}</Trans>\n          </p>\n        </div>\n        <div className=\"pl-7 flex justify-between items-start gap-1\">\n          {!!btnTitle ? (\n            <Button\n              variant=\"brand-default\"\n              size=\"mini\"\n              onClick={onBtnClick}\n              disabled={step === 1 && !explainCurrentFile}\n            >\n              <Trans>{btnTitle}</Trans>\n            </Button>\n          ) : (\n            <span />\n          )}\n          {step < cards.length - 1 ? (\n            <div className=\"flex items-center gap-3\">\n              <button className=\"body-mini text-label-faint\" onClick={onSkip}>\n                <Trans>Skip</Trans>\n              </button>\n              <Button variant=\"secondary\" size=\"mini\" onClick={handleNext}>\n                <Trans>Next</Trans>\n              </Button>\n            </div>\n          ) : (\n            <Button variant=\"danger\" size=\"mini\" onClick={onSkip}>\n              <Trans>Dismiss tutorial</Trans>\n            </Button>\n          )}\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default memo(TutorialCards);\n"
  },
  {
    "path": "client/src/Project/index.tsx",
    "content": "import React, { memo, useCallback, useContext, useMemo } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { useDragLayer } from 'react-dnd';\nimport { ProjectContext } from '../context/projectContext';\nimport { TabsContext } from '../context/tabsContext';\nimport { TabType, TabTypesEnum } from '../types/general';\nimport ChatsContextProvider from '../context/providers/ChatsContextProvider';\nimport { UIContext } from '../context/uiContext';\nimport { checkEventKeys } from '../utils/keyboardUtils';\nimport useKeyboardNavigation from '../hooks/useKeyboardNavigation';\nimport StudiosContextProvider from '../context/providers/StudiosContextProvider';\nimport LeftSidebar from './LeftSidebar';\nimport CurrentTabContent from './CurrentTabContent';\nimport EmptyProject from './EmptyProject';\nimport DropTarget from './CurrentTabContent/DropTarget';\nimport RightTab from './RightTab';\nimport ChatPersistentState from './CurrentTabContent/ChatTab/ChatPersistentState';\nimport StudioPersistentState from './CurrentTabContent/StudioTab/StudioPersistentState';\nimport TutorialCards from './TutorialCards';\n\ntype Props = {};\n\nconst Project = ({}: Props) => {\n  useTranslation();\n  const { project } = useContext(ProjectContext.Current);\n  const { rightTabs, leftTabs } = useContext(TabsContext.All);\n  const { setIsLeftSidebarFocused } = useContext(UIContext.Focus);\n  const { onBoardingState } = useContext(UIContext.Onboarding);\n  const {\n    setActiveRightTab,\n    setActiveLeftTab,\n    setLeftTabs,\n    setFocusedPanel,\n    setRightTabs,\n  } = useContext(TabsContext.Handlers);\n  const { isDragging } = useDragLayer((monitor) => ({\n    isDragging: monitor.isDragging(),\n  }));\n\n  const onDropToRight = useCallback((tab: TabType) => {\n    setRightTabs((prev) =>\n      prev.find((t) => t.key === tab.key)\n        ? prev\n        : [\n            ...prev,\n            tab.type === TabTypesEnum.FILE && tab.isTemp\n              ? { ...tab, isTemp: false }\n              : tab,\n          ],\n    );\n    setLeftTabs((prev) => {\n      const newTabs = prev.filter((s) => s.key !== tab.key);\n      setActiveLeftTab(newTabs[newTabs.length - 1]);\n      return newTabs;\n    });\n    setActiveRightTab(tab);\n    setFocusedPanel('right');\n  }, []);\n\n  const onDropToLeft = useCallback((tab: TabType) => {\n    setLeftTabs((prev) =>\n      prev.find((t) => t.key === tab.key)\n        ? prev\n        : [\n            ...prev,\n            tab.type === TabTypesEnum.FILE && tab.isTemp\n              ? { ...tab, isTemp: false }\n              : tab,\n          ],\n    );\n    setRightTabs((prev) => {\n      const newTabs = prev.filter((s) => s.key !== tab.key);\n      setActiveRightTab(newTabs[newTabs.length - 1]);\n      return newTabs;\n    });\n    setActiveLeftTab(tab);\n    setFocusedPanel('left');\n  }, []);\n\n  const shouldShowTutorial = useMemo(() => {\n    return (\n      onBoardingState.isCommandBarTutorialFinished &&\n      (!onBoardingState.isFileExplained ||\n        !onBoardingState.isCodeExplained ||\n        !onBoardingState.isCodeNavigated ||\n        !onBoardingState.isChatOpened)\n    );\n  }, [onBoardingState]);\n\n  const handleKeyEvent = useCallback(\n    (e: KeyboardEvent) => {\n      if (checkEventKeys(e, ['cmd', '1'])) {\n        e.preventDefault();\n        e.stopPropagation();\n        setFocusedPanel('left');\n        setIsLeftSidebarFocused(false);\n      } else if (checkEventKeys(e, ['cmd', '2']) && rightTabs.length) {\n        e.preventDefault();\n        e.stopPropagation();\n        setFocusedPanel('right');\n        setIsLeftSidebarFocused(false);\n      }\n    },\n    [rightTabs.length],\n  );\n  useKeyboardNavigation(handleKeyEvent);\n\n  return !project?.repos?.length ? (\n    <EmptyProject />\n  ) : (\n    <div className=\"w-screen h-screen flex relative overflow-hidden scrollbar-hide\">\n      <LeftSidebar />\n      <ChatsContextProvider>\n        <StudiosContextProvider>\n          <div className=\"flex-1 overflow-hidden relative\">\n            <CurrentTabContent\n              side=\"left\"\n              onDrop={onDropToLeft}\n              moveToAnotherSide={onDropToRight}\n              shouldStretch\n            />\n            {!rightTabs.length && isDragging ? (\n              <DropTarget onDrop={onDropToRight} />\n            ) : null}\n          </div>\n          {!!rightTabs.length && (\n            <RightTab\n              onDropToRight={onDropToRight}\n              moveToAnotherSide={onDropToLeft}\n            />\n          )}\n          {shouldShowTutorial && <TutorialCards />}\n          {[...leftTabs, ...rightTabs].map((t, i) =>\n            t.type === TabTypesEnum.CHAT ? (\n              <ChatPersistentState\n                key={t.key}\n                tabKey={t.key}\n                tabTitle={t.title}\n                conversationId={t.conversationId}\n                initialQuery={t.initialQuery}\n                side={i < leftTabs.length ? 'left' : 'right'}\n              />\n            ) : t.type === TabTypesEnum.STUDIO ? (\n              <StudioPersistentState\n                key={t.key}\n                tabKey={t.key}\n                side={i < leftTabs.length ? 'left' : 'right'}\n              />\n            ) : null,\n          )}\n        </StudiosContextProvider>\n      </ChatsContextProvider>\n    </div>\n  );\n};\n\nexport default memo(Project);\n"
  },
  {
    "path": "client/src/ProjectSettings/General.tsx",
    "content": "import React, {\n  ChangeEvent,\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useState,\n} from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport TextInput from '../components/TextInput';\nimport { ProjectContext } from '../context/projectContext';\nimport Button from '../components/Button';\nimport { deleteProject, updateProject } from '../services/api';\nimport { UIContext } from '../context/uiContext';\n\ntype Props = {};\n\nconst General = ({}: Props) => {\n  const { t } = useTranslation();\n  const { project, refreshCurrentProject, setCurrentProjectId } = useContext(\n    ProjectContext.Current,\n  );\n  const { refreshAllProjects, projects } = useContext(ProjectContext.All);\n  const { setProjectSettingsOpen } = useContext(UIContext.ProjectSettings);\n  const [name, setName] = useState(project?.name || '');\n\n  useEffect(() => {\n    setName(project?.name || '');\n  }, [project?.name]);\n\n  const handleChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n    setName(e.target.value);\n  }, []);\n\n  const handleSubmit = useCallback(async () => {\n    if (project?.id && name) {\n      await updateProject(project?.id, { name });\n      refreshCurrentProject();\n    }\n  }, [project?.id, name, refreshCurrentProject]);\n\n  const handleDelete = useCallback(async () => {\n    if (project?.id) {\n      await deleteProject(project?.id);\n      if (projects.length > 1) {\n        setCurrentProjectId(\n          projects.find((p) => p.id !== project.id)?.id || '',\n        );\n      }\n      refreshAllProjects();\n      refreshCurrentProject();\n      setProjectSettingsOpen(false);\n    }\n  }, [project?.id, projects, refreshCurrentProject]);\n\n  return (\n    <div className=\"w-[36.25rem] flex flex-col flex-2\">\n      <div className=\"flex flex-col gap-3 \">\n        <p className=\"body-m text-label-title\">\n          <Trans>General</Trans>\n        </p>\n        <p className=\"body-s-b text-label-muted\">\n          <Trans>Manage your general project settings</Trans>\n        </p>\n      </div>\n      <hr className=\"border-bg-divider my-8\" />\n      <div className=\"flex flex-col gap-5 max-w-[25rem]\">\n        <TextInput\n          value={name}\n          name={'projectName'}\n          onChange={handleChange}\n          label={t('Project title')}\n          onBlur={handleSubmit}\n          onSubmit={handleSubmit}\n        />\n      </div>\n      <hr className=\"border-bg-divider my-8\" />\n      <div className=\"flex items-start gap-8 w-full\">\n        <p className=\"body-s text-label-base\">\n          <Trans values={{ projectName: project?.name }}>\n            Permanently delete{' '}\n            <span className=\"body-s-b text-label-title\">\n              {'{{projectName}}'}\n            </span>{' '}\n            and remove all the data associated to it. Repositories will remain\n            accessible in your GitHub account.\n          </Trans>\n        </p>\n        <Button variant=\"danger\" size=\"small\" onClick={handleDelete}>\n          <Trans>Delete project</Trans>\n        </Button>\n      </div>\n    </div>\n  );\n};\n\nexport default memo(General);\n"
  },
  {
    "path": "client/src/ProjectSettings/Templates/ActionsDropdown.tsx",
    "content": "import { memo } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport DropdownSection from '../../components/Dropdown/Section';\nimport SectionItem from '../../components/Dropdown/Section/SectionItem';\nimport { PencilIcon, TrashCanIcon } from '../../icons';\n\ntype Props = {\n  isDefault: boolean;\n  handleEdit: () => void;\n  handleDelete: () => void;\n};\n\nconst ActionsDropdown = ({ handleEdit, handleDelete, isDefault }: Props) => {\n  const { t } = useTranslation();\n\n  return (\n    <div>\n      <DropdownSection borderBottom={!isDefault}>\n        <SectionItem\n          label={t('Edit template')}\n          index={'edit-template'}\n          onClick={handleEdit}\n          icon={<PencilIcon sizeClassName=\"w-4 h-4\" />}\n        />\n      </DropdownSection>\n      {!isDefault && (\n        <DropdownSection>\n          <SectionItem\n            label={t('Delete template')}\n            index={'delete-template'}\n            onClick={handleDelete}\n            icon={<TrashCanIcon sizeClassName=\"w-4 h-4\" />}\n          />\n        </DropdownSection>\n      )}\n    </div>\n  );\n};\n\nexport default memo(ActionsDropdown);\n"
  },
  {
    "path": "client/src/ProjectSettings/Templates/TemplateItem.tsx",
    "content": "import React, { memo, useCallback, useMemo } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { StudioTemplateType } from '../../types/api';\nimport { MoreHorizontalIcon, TemplatesIcon } from '../../icons';\nimport Button from '../../components/Button';\nimport Dropdown from '../../components/Dropdown';\nimport { deleteTemplate } from '../../services/api';\nimport ActionsDropdown from './ActionsDropdown';\n\ntype Props = StudioTemplateType & {\n  refetchTemplates: () => void;\n  handleEdit: (t: StudioTemplateType) => void;\n};\n\nconst TemplateItem = ({\n  name,\n  content,\n  id,\n  is_default,\n  refetchTemplates,\n  handleEdit,\n  modified_at,\n}: Props) => {\n  const { t } = useTranslation();\n\n  const handleDelete = useCallback(async () => {\n    await deleteTemplate(id);\n    refetchTemplates();\n  }, [id]);\n\n  const dropdownComponentProps = useMemo(() => {\n    return {\n      isDefault: is_default,\n      handleDelete,\n      handleEdit: () =>\n        handleEdit({ id, name, content, is_default, modified_at }),\n    };\n  }, [is_default, handleDelete, handleEdit, id]);\n\n  return (\n    <div className=\"flex p-4 items-center gap-3 bg-bg-sub overflow-hidden hover:bg-bg-sub-hover\">\n      <div className=\"flex items-center justify-center w-10 h-10 rounded-6 bg-bg-shade flex-shrink-0\">\n        <TemplatesIcon sizeClassName=\"w-4.5 h-4.5\" />\n      </div>\n      <div className=\"flex flex-col gap-1.5 flex-1 ellipsis\">\n        <p className=\"body-s-b text-label-title\">{name}</p>\n        <p className=\"body-mini text-label-base ellipsis\">{content}</p>\n      </div>\n      <div>\n        <Dropdown\n          DropdownComponent={ActionsDropdown}\n          dropdownComponentProps={dropdownComponentProps}\n          appendTo={document.body}\n          dropdownPlacement=\"bottom-end\"\n        >\n          <Button\n            variant=\"tertiary\"\n            size=\"mini\"\n            onlyIcon\n            title={t('More actions')}\n          >\n            <MoreHorizontalIcon sizeClassName=\"w-3.5 h-3.5\" />\n          </Button>\n        </Dropdown>\n      </div>\n    </div>\n  );\n};\n\nexport default memo(TemplateItem);\n"
  },
  {
    "path": "client/src/ProjectSettings/Templates/index.tsx",
    "content": "import React, {\n  ChangeEvent,\n  memo,\n  useCallback,\n  useEffect,\n  useState,\n} from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport TextInput from '../../components/TextInput';\nimport Button from '../../components/Button';\nimport { getTemplates, patchTemplate, postTemplate } from '../../services/api';\nimport { PlusSignIcon } from '../../icons';\nimport { StudioTemplateType } from '../../types/api';\nimport ArrowLeft from '../../icons/ArrowLeft';\nimport TemplateItem from './TemplateItem';\n\ntype Props = {};\n\nconst Templates = ({}: Props) => {\n  const { t } = useTranslation();\n  const [templates, setTemplates] = useState<StudioTemplateType[]>([]);\n  const [isEditMode, setIsEditMode] = useState(false);\n  const [templateToEdit, setTemplateToEdit] = useState('');\n  const [newName, setNewName] = useState('');\n  const [newContent, setNewContent] = useState('');\n\n  const refetchTemplates = useCallback(() => {\n    getTemplates().then(setTemplates);\n  }, []);\n\n  useEffect(() => {\n    refetchTemplates();\n  }, []);\n\n  const handleAddNew = useCallback(() => {\n    setIsEditMode(true);\n    setTemplateToEdit('');\n  }, []);\n\n  const handleEdit = useCallback((temp: StudioTemplateType) => {\n    setIsEditMode(true);\n    setTemplateToEdit(temp.id);\n    setNewName(temp.name);\n    setNewContent(temp.content);\n  }, []);\n\n  const handleCancel = useCallback(() => {\n    setIsEditMode(false);\n    setTemplateToEdit('');\n    setNewName('');\n    setNewContent('');\n  }, []);\n\n  const handleSubmit = useCallback(async () => {\n    if (!templateToEdit) {\n      await postTemplate(newName, newContent);\n    } else {\n      await patchTemplate(templateToEdit, {\n        name: newName,\n        content: newContent,\n      });\n    }\n    refetchTemplates();\n    handleCancel();\n  }, [newContent, newName, templateToEdit, handleCancel]);\n\n  const handleChangeName = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n    setNewName(e.target.value);\n  }, []);\n\n  const handleChangeContent = useCallback(\n    (e: ChangeEvent<HTMLTextAreaElement>) => {\n      setNewContent(e.target.value);\n    },\n    [],\n  );\n\n  return (\n    <div className=\"w-[36.25rem] flex flex-col flex-2 gap-8 items-start\">\n      {isEditMode ? (\n        <>\n          <Button variant=\"secondary\" onClick={handleCancel}>\n            <ArrowLeft sizeClassName=\"w-4.5 h-4.5\" />\n            <Trans>All templates</Trans>\n          </Button>\n          <p className=\"select-none title-m\">\n            <Trans>{templateToEdit ? 'Edit' : 'Create'} template</Trans>\n          </p>\n          <hr className=\"border-bg-divider w-full\" />\n          <TextInput\n            value={newName}\n            name={'name'}\n            onChange={handleChangeName}\n            label={t('Template title')}\n            placeholder={t('Give your template a title')}\n          />\n          <TextInput\n            value={newContent}\n            name={'content'}\n            onChange={handleChangeContent}\n            label={t('Prompt')}\n            placeholder={t('Write your prompt...')}\n            multiline\n          />\n          <div className=\"w-full flex items-center justify-end gap-3\">\n            <Button variant=\"tertiary\" onClick={handleCancel}>\n              <Trans>Cancel</Trans>\n            </Button>\n            <Button onClick={handleSubmit}>\n              <Trans>Save changes</Trans>\n            </Button>\n          </div>\n        </>\n      ) : (\n        <>\n          <div className=\"flex flex-col gap-3 \">\n            <p className=\"body-m text-label-title\">\n              <Trans>Studio</Trans>\n            </p>\n            <p className=\"body-s-b text-label-muted\">\n              <Trans>Manage your studio settings.</Trans>\n            </p>\n          </div>\n          <hr className=\"border-bg-divider w-full\" />\n          <div className=\"w-full flex items-start justify-between gap-2\">\n            <div className=\"flex flex-col gap-3 \">\n              <p className=\"body-m text-label-title\">\n                <Trans>Prompt templates</Trans>\n              </p>\n              <p className=\"body-s-b text-label-muted\">\n                <Trans>\n                  Write studio prompts faster with pre-written templates\n                </Trans>\n              </p>\n            </div>\n            <Button onClick={handleAddNew}>\n              <PlusSignIcon sizeClassName=\"w-4.5 h-4.5\" />\n              <Trans>New</Trans>\n            </Button>\n          </div>\n          <div className=\"w-full flex flex-col rounded-md border border-bg-border overflow-hidden\">\n            {templates.map((t) => (\n              <TemplateItem\n                {...t}\n                key={t.id}\n                refetchTemplates={refetchTemplates}\n                handleEdit={handleEdit}\n              />\n            ))}\n          </div>\n        </>\n      )}\n    </div>\n  );\n};\n\nexport default memo(Templates);\n"
  },
  {
    "path": "client/src/ProjectSettings/index.tsx",
    "content": "import React, { memo, useCallback, useContext } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { UIContext } from '../context/uiContext';\nimport Header from '../components/Header';\nimport useKeyboardNavigation from '../hooks/useKeyboardNavigation';\nimport { ProjectSettingSections } from '../types/general';\nimport SectionsNav from '../components/SectionsNav';\nimport { CodeStudioIcon, ShapesIcon } from '../icons';\nimport General from './General';\nimport Templates from './Templates';\n\ntype Props = {};\n\nconst ProjectSettings = ({}: Props) => {\n  const { t } = useTranslation();\n  const {\n    isProjectSettingsOpen,\n    setProjectSettingsOpen,\n    projectSettingsSection,\n    setProjectSettingsSection,\n  } = useContext(UIContext.ProjectSettings);\n\n  const handleKeyEvent = useCallback((e: KeyboardEvent) => {\n    if (e.key === 'Escape') {\n      e.preventDefault();\n      e.stopPropagation();\n      setProjectSettingsOpen(false);\n    }\n  }, []);\n  useKeyboardNavigation(handleKeyEvent, !isProjectSettingsOpen);\n\n  return isProjectSettingsOpen ? (\n    <div className=\"fixed top-0 bottom-0 left-0 right-0 bg-bg-sub select-none z-40\">\n      <Header type=\"project-settings\" />\n      <div className=\"mx-auto my-8 px-3 flex max-w-6xl items-start justify-start w-full gap-13\">\n        <SectionsNav<ProjectSettingSections>\n          activeItem={projectSettingsSection}\n          sections={[\n            {\n              title: t('Project'),\n              Icon: ShapesIcon,\n              items: [\n                {\n                  type: ProjectSettingSections.GENERAL,\n                  onClick: setProjectSettingsSection,\n                  label: t('General'),\n                },\n              ],\n            },\n            {\n              title: t('Studio'),\n              Icon: CodeStudioIcon,\n              items: [\n                {\n                  type: ProjectSettingSections.TEMPLATES,\n                  onClick: setProjectSettingsSection,\n                  label: t('Templates'),\n                },\n              ],\n            },\n          ]}\n        />\n        {projectSettingsSection === ProjectSettingSections.GENERAL ? (\n          <General />\n        ) : projectSettingsSection === ProjectSettingSections.TEMPLATES ? (\n          <Templates />\n        ) : null}\n        <div className=\"w-56 flex-1 hidden lg:block\" />\n      </div>\n    </div>\n  ) : null;\n};\n\nexport default memo(ProjectSettings);\n"
  },
  {
    "path": "client/src/Settings/General/index.tsx",
    "content": "import React, {\n  ChangeEvent,\n  memo,\n  useCallback,\n  useMemo,\n  useState,\n} from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport TextInput from '../../components/TextInput';\nimport {\n  getJsonFromStorage,\n  saveJsonToStorage,\n  USER_DATA_FORM,\n} from '../../services/storage';\nimport { EMAIL_REGEX } from '../../consts/validations';\n\ntype Props = {};\n\ntype Form = {\n  firstName: string;\n  lastName: string;\n  email: string;\n  emailError?: string;\n};\n\nconst GeneralSettings = ({}: Props) => {\n  const { t } = useTranslation();\n  const savedForm: Form | null = useMemo(\n    () => getJsonFromStorage(USER_DATA_FORM),\n    [],\n  );\n  const [form, setForm] = useState<Form>({\n    firstName: savedForm?.firstName || '',\n    lastName: savedForm?.lastName || '',\n    email: savedForm?.email || '',\n    emailError: '',\n  });\n\n  const onChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n    setForm((prev) => {\n      const newForm = {\n        ...prev,\n        [e.target.name]: e.target.value,\n        emailError: e.target.name === 'email' ? '' : prev.emailError,\n      };\n      saveJsonToStorage(USER_DATA_FORM, newForm);\n      return newForm;\n    });\n  }, []);\n\n  return (\n    <div className=\"w-[36.25rem] flex flex-col flex-2\">\n      <div className=\"flex flex-col gap-3 \">\n        <p className=\"body-m text-label-title\">\n          <Trans>General</Trans>\n        </p>\n        <p className=\"body-s-b text-label-muted\">\n          <Trans>Manage your general account settings</Trans>\n        </p>\n      </div>\n      <hr className=\"border-bg-divider my-8\" />\n      <div className=\"flex flex-col gap-5 max-w-[25rem]\">\n        <TextInput\n          value={form.firstName}\n          name={'firstName'}\n          onChange={onChange}\n          label={t('First name')}\n        />\n        <TextInput\n          value={form.lastName}\n          name={'lastName'}\n          onChange={onChange}\n          label={t('Last name')}\n        />\n      </div>\n      <hr className=\"border-bg-divider my-8\" />\n      <div className=\"flex flex-col gap-5 max-w-[25rem]\">\n        <TextInput\n          value={form.email}\n          name={'email'}\n          onChange={onChange}\n          label={t('Email address')}\n          onBlur={() => {\n            if (!EMAIL_REGEX.test(form.email)) {\n              setForm((prev) => ({\n                ...prev,\n                emailError: t('Email is not valid'),\n              }));\n            }\n          }}\n          error={form.emailError}\n        />\n      </div>\n    </div>\n  );\n};\n\nexport default memo(GeneralSettings);\n"
  },
  {
    "path": "client/src/Settings/Preferences/ChatInputTypeDropdown.tsx",
    "content": "import { memo, useContext } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport DropdownSection from '../../components/Dropdown/Section';\nimport SectionItem from '../../components/Dropdown/Section/SectionItem';\nimport { UIContext } from '../../context/uiContext';\n\ntype Props = {};\n\nconst ChatInputTypeDropdown = ({}: Props) => {\n  const { t } = useTranslation();\n  const { chatInputType, setChatInputType } = useContext(\n    UIContext.ChatInputType,\n  );\n  return (\n    <div>\n      <DropdownSection borderBottom>\n        <SectionItem\n          label={t('Default')}\n          index={'default-input'}\n          isSelected={chatInputType === 'default'}\n          onClick={() => setChatInputType('default')}\n          description={t('Recommended: The classic input')}\n        />\n      </DropdownSection>\n      <DropdownSection>\n        <SectionItem\n          label={t('Simplified')}\n          index={'simple-input'}\n          isSelected={chatInputType === 'simplified'}\n          onClick={() => setChatInputType('simplified')}\n          description={t(\n            'Fallback: Use if experiencing problems with the default one',\n          )}\n        />\n      </DropdownSection>\n    </div>\n  );\n};\n\nexport default memo(ChatInputTypeDropdown);\n"
  },
  {
    "path": "client/src/Settings/Preferences/LanguageDropdown.tsx",
    "content": "import { memo, useContext } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport SectionItem from '../../components/Dropdown/Section/SectionItem';\nimport { LocaleContext } from '../../context/localeContext';\nimport { localesMap } from '../../consts/general';\nimport { LocaleType } from '../../types/general';\nimport { UIContext } from '../../context/uiContext';\n\ntype Props = {};\n\nconst LanguageDropdown = ({}: Props) => {\n  useTranslation();\n  const { locale, setLocale } = useContext(LocaleContext);\n  const { setChatInputType } = useContext(UIContext.ChatInputType);\n\n  return (\n    <div>\n      <div className=\"flex flex-col p-1 items-start\">\n        {(Object.keys(localesMap) as LocaleType[]).map((k) => (\n          <SectionItem\n            key={k}\n            index={`lang-${k}`}\n            isSelected={locale === k}\n            onClick={() => {\n              if (k === 'zhCN') {\n                setChatInputType('simplified');\n              }\n              setLocale(k);\n            }}\n            label={localesMap[k].name}\n            icon={<span>{localesMap[k].icon}</span>}\n          />\n        ))}\n      </div>\n    </div>\n  );\n};\n\nexport default memo(LanguageDropdown);\n"
  },
  {
    "path": "client/src/Settings/Preferences/ThemeDropdown.tsx",
    "content": "import { memo, useContext } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport SectionItem from '../../components/Dropdown/Section/SectionItem';\nimport {\n  MacintoshIcon,\n  ThemeBlackIcon,\n  ThemeDarkIcon,\n  ThemeLightIcon,\n} from '../../icons';\nimport { UIContext } from '../../context/uiContext';\n\ntype Props = {};\n\nconst ThemeDropdown = ({}: Props) => {\n  const { t } = useTranslation();\n  const { theme, setTheme } = useContext(UIContext.Theme);\n\n  return (\n    <div>\n      <div className=\"flex flex-col p-1 items-start border-b border-bg-border\">\n        <SectionItem\n          index={'theme-system'}\n          isSelected={theme === 'system'}\n          onClick={() => setTheme('system')}\n          label={t('System preferences')}\n          icon={<MacintoshIcon sizeClassName=\"w-4 h-4\" />}\n        />\n      </div>\n      <div className=\"flex flex-col p-1 items-start\">\n        <SectionItem\n          index={'theme-light'}\n          isSelected={theme === 'light'}\n          onClick={() => setTheme('light')}\n          label={t('Light')}\n          icon={<ThemeLightIcon sizeClassName=\"w-4 h-4\" />}\n        />\n        <SectionItem\n          index={'theme-dark'}\n          isSelected={theme === 'dark'}\n          onClick={() => setTheme('dark')}\n          label={t('Dark')}\n          icon={<ThemeDarkIcon sizeClassName=\"w-4 h-4\" />}\n        />\n        <SectionItem\n          index={'theme-black'}\n          isSelected={theme === 'black'}\n          onClick={() => setTheme('black')}\n          label={t('Black')}\n          icon={<ThemeBlackIcon sizeClassName=\"w-4 h-4\" />}\n        />\n      </div>\n    </div>\n  );\n};\n\nexport default memo(ThemeDropdown);\n"
  },
  {
    "path": "client/src/Settings/Preferences/index.tsx",
    "content": "import React, { memo, useContext } from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport Dropdown from '../../components/Dropdown';\nimport Button from '../../components/Button';\nimport {\n  ChevronDownIcon,\n  MacintoshIcon,\n  ThemeBlackIcon,\n  ThemeDarkIcon,\n  ThemeLightIcon,\n} from '../../icons';\nimport { UIContext } from '../../context/uiContext';\nimport { localesMap, themesMap } from '../../consts/general';\nimport { LocaleContext } from '../../context/localeContext';\nimport ThemeDropdown from './ThemeDropdown';\nimport LanguageDropdown from './LanguageDropdown';\nimport ChatInputTypeDropdown from './ChatInputTypeDropdown';\n\ntype Props = {};\n\nexport const themeIconsMap = {\n  light: <ThemeLightIcon sizeClassName=\"w-4.5 h-4.5\" />,\n  dark: <ThemeDarkIcon sizeClassName=\"w-4.5 h-4.5\" />,\n  black: <ThemeBlackIcon sizeClassName=\"w-4.5 h-4.5\" />,\n  system: <MacintoshIcon sizeClassName=\"w-4.5 h-4.5\" />,\n};\nconst Preferences = ({}: Props) => {\n  useTranslation();\n  const { theme } = useContext(UIContext.Theme);\n  const { locale } = useContext(LocaleContext);\n  const { chatInputType } = useContext(UIContext.ChatInputType);\n\n  return (\n    <div className=\"w-[36.25rem] flex flex-col flex-2\">\n      <div className=\"flex flex-col gap-3 \">\n        <p className=\"body-m text-label-title\">\n          <Trans>Preferences</Trans>\n        </p>\n        <p className=\"body-s-b text-label-muted\">\n          <Trans>Manage your preferences</Trans>\n        </p>\n      </div>\n      <hr className=\"border-bg-divider my-8\" />\n      <div className=\"flex items-start gap-8 w-full justify-between\">\n        <div className=\"flex flex-col gap-2\">\n          <p className=\"body-base-b text-label-title\">\n            <Trans>Theme</Trans>\n          </p>\n          <p className=\"body-s-b text-label-muted\">\n            <Trans>Select the interface colour scheme</Trans>\n          </p>\n        </div>\n        <Dropdown\n          DropdownComponent={ThemeDropdown}\n          size=\"small\"\n          dropdownPlacement=\"bottom-end\"\n        >\n          <Button variant=\"secondary\">\n            {themeIconsMap[theme]}\n            <Trans>{themesMap[theme]}</Trans>\n            <ChevronDownIcon sizeClassName=\"w-4 h-4\" />\n          </Button>\n        </Dropdown>\n      </div>\n      <hr className=\"border-bg-divider my-8\" />\n      <div className=\"flex items-start gap-8 w-full justify-between\">\n        <div className=\"flex flex-col gap-2\">\n          <p className=\"body-base-b text-label-title\">\n            <Trans>Language</Trans>\n          </p>\n          <p className=\"body-s-b text-label-muted\">\n            <Trans>Select the interface language</Trans>\n          </p>\n        </div>\n        <Dropdown\n          DropdownComponent={LanguageDropdown}\n          size=\"small\"\n          dropdownPlacement=\"bottom-end\"\n        >\n          <Button variant=\"secondary\">\n            <span>{localesMap[locale].icon}</span>\n            {localesMap[locale].name}\n            <ChevronDownIcon sizeClassName=\"w-4 h-4\" />\n          </Button>\n        </Dropdown>\n      </div>\n      <hr className=\"border-bg-divider my-8\" />\n      <div className=\"flex items-start gap-8 w-full justify-between\">\n        <div className=\"flex flex-col gap-2\">\n          <p className=\"body-base-b text-label-title\">\n            <Trans>Conversation input</Trans>\n          </p>\n          <p className=\"body-s-b text-label-muted\">\n            <Trans>Select the input type to use in conversations</Trans>\n          </p>\n        </div>\n        <Dropdown\n          DropdownComponent={ChatInputTypeDropdown}\n          size=\"auto\"\n          dropdownPlacement=\"bottom-end\"\n        >\n          <Button variant=\"secondary\">\n            {chatInputType === 'default' ? (\n              <Trans>Default</Trans>\n            ) : (\n              <Trans>Simplified</Trans>\n            )}\n            <ChevronDownIcon sizeClassName=\"w-4 h-4\" />\n          </Button>\n        </Dropdown>\n      </div>\n    </div>\n  );\n};\n\nexport default memo(Preferences);\n"
  },
  {
    "path": "client/src/Settings/index.tsx",
    "content": "import React, { memo, useCallback, useContext, useMemo } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { UIContext } from '../context/uiContext';\nimport Header from '../components/Header';\nimport useKeyboardNavigation from '../hooks/useKeyboardNavigation';\nimport { SettingSections } from '../types/general';\nimport SectionsNav from '../components/SectionsNav';\nimport { CogIcon } from '../icons';\nimport General from './General';\nimport Preferences from './Preferences';\n\ntype Props = {};\n\nconst Settings = ({}: Props) => {\n  const { t } = useTranslation();\n  const {\n    isSettingsOpen,\n    setSettingsOpen,\n    settingsSection,\n    setSettingsSection,\n  } = useContext(UIContext.Settings);\n\n  const handleKeyEvent = useCallback((e: KeyboardEvent) => {\n    if (e.key === 'Escape') {\n      e.preventDefault();\n      e.stopPropagation();\n      setSettingsOpen(false);\n    }\n  }, []);\n  useKeyboardNavigation(handleKeyEvent, !isSettingsOpen);\n\n  const settingsSections = useMemo(() => {\n    return [\n      {\n        title: t('Account settings'),\n        Icon: CogIcon,\n        items: [\n          {\n            type: SettingSections.GENERAL,\n            label: t('General'),\n            onClick: setSettingsSection,\n          },\n          {\n            type: SettingSections.PREFERENCES,\n            label: t('Preferences'),\n            onClick: setSettingsSection,\n          },\n        ],\n      },\n    ];\n  }, [t]);\n\n  return isSettingsOpen ? (\n    <div className=\"fixed top-0 bottom-0 left-0 right-0 bg-bg-sub select-none z-40\">\n      <Header type=\"settings\" />\n      <div className=\"mx-auto my-8 px-3 flex max-w-6xl items-start justify-start w-full gap-13\">\n        <SectionsNav<SettingSections>\n          sections={settingsSections}\n          activeItem={settingsSection}\n        />\n        {settingsSection === SettingSections.GENERAL ? (\n          <General />\n        ) : settingsSection === SettingSections.PREFERENCES ? (\n          <Preferences />\n        ) : null}\n        <div className=\"w-56 flex-1 hidden lg:block\" />\n      </div>\n    </div>\n  ) : null;\n};\n\nexport default memo(Settings);\n"
  },
  {
    "path": "client/src/components/Badge/index.tsx",
    "content": "import { memo } from 'react';\n\ntype Props = {\n  type?:\n    | 'outlined'\n    | 'filled'\n    | 'green'\n    | 'green-subtle'\n    | 'red'\n    | 'red-subtle'\n    | 'yellow'\n    | 'yellow-subtle'\n    | 'blue'\n    | 'blue-subtle'\n    | 'studio';\n  size?: 'large' | 'small' | 'mini';\n  Icon?: (props: {\n    raw?: boolean | undefined;\n    sizeClassName?: string | undefined;\n    className?: string | undefined;\n  }) => JSX.Element;\n  text: string;\n};\n\nconst typeMap = {\n  outlined: 'border border-bg-border text-label-base',\n  filled: 'bg-bg-border text-label-base',\n  green: 'bg-green text-label-control',\n  'green-subtle': 'bg-green-subtle text-green',\n  red: 'bg-red text-label-control',\n  'red-subtle': 'bg-red-subtle text-red',\n  yellow: 'bg-yellow text-label-control',\n  'yellow-subtle': 'bg-yellow-subtle text-yellow',\n  blue: 'bg-blue text-label-control',\n  'blue-subtle': 'bg-blue-subtle text-blue',\n  studio:\n    'bg-[linear-gradient(110deg,#D92037_1.23%,#D9009D_77.32%)] text-label-control',\n};\n\nconst sizeMap = {\n  large: { pill: 'h-7 px-2.5 gap-1.5 body-s', icon: 'w-4 h-4' },\n  small: { pill: 'h-6 px-2 gap-1 body-mini', icon: 'w-3.5 h-3.5' },\n  mini: { pill: 'h-5 px-1.5 gap-1 body-tiny', icon: 'w-3 h-3' },\n};\n\nconst Badge = ({ type = 'outlined', size = 'small', Icon, text }: Props) => {\n  return (\n    <div\n      className={`inline-flex items-center flex-shrink-0 rounded-full ${typeMap[type]} ${sizeMap[size].pill}`}\n    >\n      {!!Icon && <Icon sizeClassName={sizeMap[size].icon} />}\n      <p>{text}</p>\n    </div>\n  );\n};\n\nexport default memo(Badge);\n"
  },
  {
    "path": "client/src/components/Breadcrumbs/BreadcrumbSection.tsx",
    "content": "import { memo, MouseEvent, ReactElement, useCallback } from 'react';\nimport { Range } from '../../types/results';\n\ntype HighlightedString = {\n  label: string;\n  highlight?: Range;\n};\n\ntype ItemElement = {\n  label: ReactElement<any, any>;\n  highlight?: never;\n};\n\ntype Props = {\n  icon?: ReactElement<any, any>;\n  onClick?: (e: MouseEvent<HTMLButtonElement>) => void;\n  isLast?: boolean;\n  limitSectionWidth?: boolean;\n  nonInteractive?: boolean;\n  type: 'link' | 'button';\n} & (HighlightedString | ItemElement);\n\nconst typeMap = {\n  link: {\n    default: 'text-label-base hover:text-bg-main active:text-bg-main',\n    nonInteractive: 'text-label-base',\n    isLast: 'text-label-title',\n  },\n  button: {\n    default:\n      'px-2 py-1 rounded-4 hover:bg-bg-base-hover text-label-base hover:text-label-title',\n    nonInteractive: 'px-2 py-1 rounded-4 text-label-base',\n    isLast: 'text-label-base px-2 py-1 rounded-4',\n  },\n};\n\nconst BreadcrumbSection = ({\n  icon,\n  label,\n  onClick,\n  isLast,\n  highlight,\n  type,\n  limitSectionWidth,\n  nonInteractive,\n}: Props) => {\n  const getHighlight = useCallback(() => {\n    if (highlight) {\n      const left = label.substring(0, highlight.start);\n      const search = label.substring(highlight.start, highlight.end + 1);\n      const right = label.substring(highlight.end + 1);\n      return (\n        <span>\n          {left}\n          <span className=\"bg-bg-highlight/25 rounded-4 text-label-base\">\n            {search}\n          </span>\n          {right}\n        </span>\n      );\n    }\n    return label;\n  }, [highlight, label]);\n  return (\n    <button\n      className={`flex items-center gap-1 ${\n        nonInteractive ? '' : 'cursor-pointer'\n      } ${\n        nonInteractive\n          ? typeMap[type].nonInteractive\n          : isLast\n          ? typeMap[type].isLast\n          : typeMap[type].default\n      } ${\n        limitSectionWidth ? 'max-w-[8rem] ellipsis' : ''\n      } transition-all duration-300 ease-in-bounce flex-shrink-0`}\n      onClick={onClick}\n    >\n      {icon}\n      <span\n        className={`whitespace-nowrap ${limitSectionWidth ? 'ellipsis' : ''}`}\n      >\n        {getHighlight()}\n      </span>\n    </button>\n  );\n};\n\nexport default memo(BreadcrumbSection);\n"
  },
  {
    "path": "client/src/components/Breadcrumbs/BreadcrumbsCollapsed.tsx",
    "content": "import React, { memo, useCallback, useEffect, useRef, useState } from 'react';\nimport Tippy from '@tippyjs/react/headless';\nimport { MoreHorizontalIcon } from '../../icons';\nimport { animationDuration } from '../Dropdown';\nimport { useOnClickOutside } from '../../hooks/useOnClickOutsideHook';\nimport DropdownSection from '../Dropdown/Section';\nimport SectionItem from '../Dropdown/Section/SectionItem';\nimport { PathParts } from './index';\n\ntype Props = {\n  items: PathParts[];\n  type: 'link' | 'button';\n};\n\nconst typeMap = {\n  link: {\n    default: 'bg-none text-label-muted hover:text-bg-main active:text-bg-main',\n    isHiddenClicked: 'text-label-title hover:text-bg-main active:text-bg-main',\n  },\n  button: {\n    default:\n      'px-2 py-1 rounded-4 hover:bg-bg-base-hover text-label-base hover:text-label-title',\n    isHiddenClicked: 'text-label-title bg-bg-base-hover px-2 py-1 rounded-4',\n  },\n};\n\nconst BreadcrumbsCollapsed = ({ items, type }: Props) => {\n  const [isHiddenClicked, setIsHiddenClicked] = useState(false);\n  const contextMenuRef = useRef(null);\n  const buttonRef = useRef(null);\n\n  const handleClose = useCallback((e: MouseEvent) => {\n    e.stopPropagation();\n    setIsHiddenClicked(false);\n  }, []);\n  useOnClickOutside(contextMenuRef, handleClose, buttonRef);\n\n  const handleToggle = useCallback((e?: React.MouseEvent) => {\n    e?.stopPropagation();\n    setIsHiddenClicked((prev) => !prev);\n  }, []);\n\n  useEffect(() => {\n    return () => {\n      setIsHiddenClicked(false);\n    };\n  }, []);\n\n  const renderContent = useCallback(() => {\n    return isHiddenClicked ? (\n      <div className=\"bg-bg-shade p-1 rounded\">\n        <DropdownSection>\n          {items.map((part, i) => (\n            <SectionItem\n              key={i}\n              index={`b-${i}`}\n              onClick={part?.onClick}\n              label={part.label}\n              icon={part.icon}\n            />\n          ))}\n        </DropdownSection>\n      </div>\n    ) : null;\n  }, [isHiddenClicked]);\n\n  const noPropagate = useCallback(\n    (e?: React.MouseEvent) => e?.stopPropagation(),\n    [],\n  );\n\n  return (\n    <span className=\"relative\" onClick={noPropagate} ref={contextMenuRef}>\n      <Tippy\n        visible={isHiddenClicked}\n        interactive\n        appendTo={document.body}\n        duration={animationDuration}\n        animation\n        render={renderContent}\n      >\n        <span>\n          <button\n            ref={buttonRef}\n            className={`p-0 outline-0 outline-none focus:outline-none border-0 flex items-center ${\n              isHiddenClicked\n                ? typeMap[type].isHiddenClicked\n                : typeMap[type].default\n            }`}\n            onClick={handleToggle}\n          >\n            <MoreHorizontalIcon sizeClassName=\"w-3.5 h-3.5\" />\n          </button>\n        </span>\n      </Tippy>\n    </span>\n  );\n};\n\nexport default memo(BreadcrumbsCollapsed);\n"
  },
  {
    "path": "client/src/components/Breadcrumbs/PathContainer.tsx",
    "content": "import React, { memo, useMemo } from 'react';\nimport {\n  breadcrumbsItemPath,\n  isWindowsPath,\n  splitPath,\n  splitPathForBreadcrumbs,\n} from '../../utils';\nimport { FileTreeFileType } from '../../types';\nimport Breadcrumbs, { PathParts } from './index';\n\ntype BProps = React.ComponentProps<typeof Breadcrumbs>;\n\ntype Props = {\n  path: string;\n  repoRef?: string;\n  onClick?: (path: string, fileType?: FileTreeFileType) => void;\n  shouldGoToFile?: boolean;\n  nonInteractive?: boolean;\n  allowOverflow?: boolean;\n  scrollContainerRef?: React.MutableRefObject<HTMLDivElement | null>;\n} & Omit<BProps, 'pathParts'>;\n\nconst BreadcrumbsPathContainer = ({\n  path,\n  onClick,\n  shouldGoToFile,\n  allowOverflow,\n  scrollContainerRef,\n  repoRef,\n  ...rest\n}: Props) => {\n  const pathParts: PathParts[] = useMemo(() => {\n    const pieces = splitPathForBreadcrumbs(path, (e, item, index, pParts) => {\n      if (onClick) {\n        e?.stopPropagation();\n      }\n      const isLastPart = index === pParts.length - 1;\n      const newPath = breadcrumbsItemPath(\n        pParts,\n        index,\n        isWindowsPath(path),\n        isLastPart,\n      );\n      onClick?.(\n        newPath,\n        isLastPart ? FileTreeFileType.FILE : FileTreeFileType.DIR,\n      );\n      if (!isLastPart) {\n        // navigateRepoPath(repo, newPath);\n      }\n      if (shouldGoToFile && isLastPart) {\n        // navigateFullResult(path);\n      }\n    });\n    if (repoRef) {\n      pieces.unshift({ label: splitPath(repoRef).pop() || '' });\n    }\n    return pieces;\n  }, [path, shouldGoToFile, onClick, repoRef]);\n\n  return (\n    <div\n      className={`${\n        allowOverflow ? 'overflow-auto' : 'overflow-hidden'\n      } w-full`}\n      ref={scrollContainerRef}\n    >\n      <Breadcrumbs\n        {...rest}\n        pathParts={pathParts}\n        path={path}\n        allowOverflow={allowOverflow}\n      />\n    </div>\n  );\n};\n\nexport default memo(BreadcrumbsPathContainer);\n"
  },
  {
    "path": "client/src/components/Breadcrumbs/index.tsx",
    "content": "import React, {\n  Fragment,\n  MouseEvent,\n  ReactElement,\n  useCallback,\n  useEffect,\n  useRef,\n  useState,\n  ClipboardEvent,\n  memo,\n} from 'react';\nimport { Range } from '../../types/results';\nimport { copyToClipboard, isWindowsPath } from '../../utils';\nimport BreadcrumbSection from './BreadcrumbSection';\nimport BreadcrumbsCollapsed from './BreadcrumbsCollapsed';\n\ntype HighlightedString = {\n  label: string;\n  highlight?: Range;\n};\n\ntype ItemElement = {\n  label: ReactElement<any, any>;\n  highlight?: never;\n};\n\nexport type PathParts = {\n  icon?: ReactElement<any, any>;\n  link?: string;\n  onClick?: (e?: MouseEvent) => void;\n  underline?: boolean;\n} & (HighlightedString | ItemElement);\n\ntype Props = {\n  pathParts: PathParts[];\n  path: string;\n  separator?: string;\n  limitSectionWidth?: boolean;\n  allowOverflow?: boolean;\n  nonInteractive?: boolean;\n  type?: 'link' | 'button';\n  activeStyle?: 'primary' | 'secondary';\n};\n\nconst Breadcrumbs = ({\n  pathParts,\n  separator = '/',\n  type = 'link',\n  limitSectionWidth,\n  nonInteractive,\n  path,\n  allowOverflow,\n}: Props) => {\n  const containerRef = useRef<HTMLDivElement>(null);\n  const [formattedPathParts, setFormattedPathParts] =\n    useState<(PathParts | PathParts[])[]>(pathParts);\n\n  useEffect(() => {\n    if (!containerRef.current || allowOverflow) {\n      setFormattedPathParts(pathParts);\n      return;\n    }\n    const parentWidth = containerRef.current.parentElement!.clientWidth;\n    const containerWidth = containerRef.current.scrollWidth;\n    const widthDiff = containerWidth - parentWidth;\n    if (widthDiff <= 0) {\n      setFormattedPathParts(pathParts);\n      return;\n    }\n    const partsToShow = [];\n    let widthToHide = widthDiff;\n    const parts = Array.from(containerRef.current.children).filter(\n      (p) => p.getAttribute('data-role') !== 'separator',\n    );\n    for (let i = 0; i < pathParts.length; i++) {\n      if (widthToHide <= 0) {\n        partsToShow.push(...pathParts.slice(i));\n        break;\n      }\n      if (i === 0 || pathParts[i].highlight || i === pathParts.length - 1) {\n        partsToShow.push(pathParts[i]);\n        continue;\n      }\n      if (Array.isArray(partsToShow[partsToShow.length - 1])) {\n        // @ts-ignore just checked that it is an array above\n        partsToShow[partsToShow.length - 1].push(pathParts[i]);\n        widthToHide -= parts[i].clientWidth;\n      } else {\n        partsToShow.push([pathParts[i]]);\n        widthToHide -=\n          parts[i].clientWidth - Math.min(parts[i].clientWidth, 24);\n      }\n    }\n    setFormattedPathParts(partsToShow);\n  }, [pathParts, allowOverflow]);\n\n  const onCopy = useCallback(\n    (e: ClipboardEvent<HTMLDivElement>) => {\n      e.preventDefault();\n      copyToClipboard(\n        document\n          .getSelection()\n          ?.toString()\n          .split(separator)\n          .map((part) => part.trim())\n          .join(isWindowsPath(path) ? '\\\\' : '/') || '',\n      );\n    },\n    [path],\n  );\n\n  return (\n    <div\n      className=\"flex items-center body-s flex-shrink-0 gap-1.5\"\n      onCopy={onCopy}\n    >\n      {/* this div is hidden and used only to calculate the full width of breadcrumbs before truncation */}\n      <div\n        className=\"fixed top-full opacity-0 left-0 flex flex-nowrap items-center body-s flex-shrink-0 gap-1.5 select-none\"\n        ref={containerRef}\n      >\n        {pathParts.map((p, i) => (\n          <Fragment key={i}>\n            <span className={`flex items-center gap-1 flex-shrink-0`}>\n              {/*// @ts-ignore*/}\n              <BreadcrumbSection\n                icon={p.icon}\n                label={p.label}\n                onClick={p.onClick}\n                highlight={p.highlight}\n                isLast={i == formattedPathParts.length - 1}\n                type={type}\n                limitSectionWidth={limitSectionWidth}\n              />\n            </span>\n            {i !== formattedPathParts.length - 1 && (\n              <span className=\"text-label-muted\" data-role=\"separator\">\n                {separator}\n              </span>\n            )}\n          </Fragment>\n        ))}\n      </div>\n      {formattedPathParts.map((p, i) => (\n        <Fragment key={i + (Array.isArray(p) ? 'array' : 'item')}>\n          {Array.isArray(p) ? (\n            <BreadcrumbsCollapsed items={p} type={type} />\n          ) : (\n            <span className={`flex items-center gap-1 flex-shrink-0`}>\n              {/*// @ts-ignore*/}\n              <BreadcrumbSection\n                icon={p.icon}\n                label={p.label}\n                onClick={p.onClick}\n                highlight={p.highlight}\n                isLast={i == formattedPathParts.length - 1}\n                type={type}\n                limitSectionWidth={limitSectionWidth}\n                nonInteractive={nonInteractive}\n              />\n            </span>\n          )}\n          {i !== formattedPathParts.length - 1 && (\n            <span className=\"text-label-muted\" data-role=\"separator\">\n              {separator}\n            </span>\n          )}\n        </Fragment>\n      ))}\n    </div>\n  );\n};\n\nexport default memo(Breadcrumbs);\n"
  },
  {
    "path": "client/src/components/Button/KeyHintButton.tsx",
    "content": "import { ButtonHTMLAttributes, DetailedHTMLProps, memo } from 'react';\nimport useShortcuts from '../../hooks/useShortcuts';\n\ntype Props = {\n  text: string;\n  shortcut: string[];\n};\n\nconst KeyHintButton = ({\n  text,\n  shortcut,\n  ...btnProps\n}: DetailedHTMLProps<\n  ButtonHTMLAttributes<HTMLButtonElement>,\n  HTMLButtonElement\n> &\n  Props) => {\n  const keys = useShortcuts(shortcut);\n  return (\n    <button\n      type=\"button\"\n      className={`flex items-center gap-1 h-7 p-1 pl-2 rounded-6 body-mini group\n      bg-bg-base text-label-muted ${\n        !btnProps.disabled\n          ? 'hover:bg-bg-base-hover hover:text-label-title cursor-pointer'\n          : 'cursor-default'\n      }\n      \n      transition-all ease-in-out duration-150\n      outline-0 outline-none focus:outline-0 focus:outline-none`}\n      {...btnProps}\n    >\n      <span>{text}</span>\n      {keys?.map((k) => (\n        <span\n          key={k}\n          className={`min-w-[1.25rem] h-5 flex items-center justify-center px-1 rounded \n          transition-all ease-in-out duration-150\n          bg-bg-base-hover ${\n            !btnProps.disabled ? 'group-hover:bg-bg-border-hover' : ''\n          } body-mini text-label-base`}\n        >\n          {k}\n        </span>\n      ))}\n    </button>\n  );\n};\n\nexport default memo(KeyHintButton);\n"
  },
  {
    "path": "client/src/components/Button/index.tsx",
    "content": "import {\n  ButtonHTMLAttributes,\n  DetailedHTMLProps,\n  ReactNode,\n  PropsWithChildren,\n  forwardRef,\n  useMemo,\n} from 'react';\nimport { TippyProps } from '@tippyjs/react/headless';\nimport Tooltip from '../Tooltip';\n\ntype Props = {\n  children: ReactNode;\n  variant?:\n    | 'brand-default'\n    | 'primary'\n    | 'secondary'\n    | 'tertiary'\n    | 'tertiary-active'\n    | 'ghost'\n    | 'studio'\n    | 'danger';\n  size?: 'mini' | 'small' | 'medium' | 'large';\n  className?: string;\n} & (OnlyIconProps | TextBtnProps);\n\ntype OnlyIconProps = {\n  onlyIcon: true;\n  title: string;\n  shortcut?: string[];\n  tooltipPlacement?: TippyProps['placement'];\n  tooltipClassName?: string;\n};\n\ntype TextBtnProps = {\n  onlyIcon?: false;\n  tooltipPlacement?: TippyProps['placement'];\n  tooltipClassName?: string;\n  title?: string;\n  shortcut?: string[];\n};\n\nconst variantStylesMap = {\n  'brand-default':\n    'text-label-control border border-brand-default bg-brand-default shadow-low ' +\n    'hover:bg-brand-default-hover ' +\n    'focus:bg-brand-default-hover focus:shadow-rings-blue ' +\n    'disabled:bg-bg-base disabled:border-none disabled:text-label-faint disabled:shadow-none ' +\n    'disabled:hover:bg-bg-base',\n  primary:\n    'text-label-contrast border border-bg-contrast bg-bg-contrast shadow-low ' +\n    'hover:bg-bg-contrast-hover hover:border-bg-contrast-hover ' +\n    'disabled:bg-bg-base disabled:border-none disabled:text-label-faint disabled:shadow-none ' +\n    'disabled:hover:bg-bg-base',\n  secondary:\n    'text-label-base border border-bg-border bg-bg-base shadow-low ' +\n    'hover:text-label-title hover:bg-bg-base-hover hover:border-bg-border-hover ' +\n    'disabled:text-label-faint disabled:shadow-none disabled:border-transparent' +\n    'disabled:hover:text-label-faint disabled:hover:bg-bg-base disabled:hover:border-transparent',\n  tertiary:\n    'text-label-muted bg-transparent ' +\n    'hover:text-label-title hover:bg-bg-base-hover ' +\n    'disabled:text-label-faint ' +\n    'disabled:hover:text-label-faint disabled:hover:bg-transparent',\n  'tertiary-active':\n    'text-label-title bg-bg-base-hover disabled:text-label-faint',\n  danger:\n    'text-red border border-bg-border bg-bg-base shadow-low ' +\n    'hover:bg-bg-base-hover hover:border-bg-border-hover ' +\n    'disabled:text-label-faint disabled:shadow-none disabled:bg-bg-base disabled:border-transparent' +\n    'disabled:hover:text-label-faint disabled:hover:bg-bg-base disabled:hover:border-transparent',\n  ghost:\n    'text-label-muted bg-transparent ' +\n    'hover:text-label-title ' +\n    'disabled:text-label-faint ' +\n    'disabled:hover:text-label-faint',\n  studio:\n    'text-label-control bg-brand-studio border border-brand-studio shadow-low ' +\n    'hover:bg-brand-studio-hover ' +\n    'disabled:text-label-faint disabled:bg-bg-base disabled:bg-transparent disabled:shadow-none' +\n    'disabled:hover:bg-bg-base',\n};\n\nconst sizeMap = {\n  mini: {\n    default: 'h-6 px-1.5 gap-1 body-mini-b rounded',\n    square: 'h-6 w-6 rounded',\n  },\n  small: {\n    default: 'h-7 px-2 gap-1 body-mini-b rounded',\n    square: 'h-7 w-8 rounded',\n  },\n  medium: {\n    default: 'h-8 px-2.5 gap-1.5 body-s-b rounded-6',\n    square: 'h-8 w-10 rounded-6',\n  },\n  large: {\n    default: 'h-9 px-3 gap-2 body-base-b rounded-6',\n    square: 'h-9 w-9 rounded-6',\n  },\n};\n\n// eslint-disable-next-line react/display-name\nconst Button = forwardRef<\n  HTMLButtonElement,\n  PropsWithChildren<\n    DetailedHTMLProps<\n      ButtonHTMLAttributes<HTMLButtonElement>,\n      HTMLButtonElement\n    > &\n      Props\n  >\n>(\n  (\n    {\n      children,\n      variant = 'primary',\n      size = 'medium',\n      onlyIcon,\n      className,\n      title,\n      tooltipPlacement,\n      tooltipClassName,\n      type = 'button',\n      shortcut,\n      ...rest\n    },\n    ref,\n  ) => {\n    const buttonClassName = useMemo(\n      () =>\n        `py-0 focus:outline-none outline-none outline-0 flex items-center justify-center flex-grow-0 flex-shrink-0 ${\n          variantStylesMap[variant]\n        } ${onlyIcon ? sizeMap[size].square : sizeMap[size].default} ${\n          className || ''\n        } select-none transition-all duration-150 ease-in-out`,\n      [variant, className, size, onlyIcon],\n    );\n    return (onlyIcon && !rest.disabled) || title ? (\n      <Tooltip\n        text={title}\n        placement={tooltipPlacement}\n        shortcut={shortcut}\n        wrapperClassName={tooltipClassName}\n      >\n        <button {...rest} type={type} ref={ref} className={buttonClassName}>\n          {children}\n        </button>\n      </Tooltip>\n    ) : (\n      <button {...rest} type={type} ref={ref} className={buttonClassName}>\n        {children}\n      </button>\n    );\n  },\n);\n\nexport default Button;\n"
  },
  {
    "path": "client/src/components/Checkbox/Checkbox.stories.tsx",
    "content": "import { useState } from 'react';\nimport Checkbox from './index';\n\nexport default {\n  title: 'components/Checkbox',\n  component: Checkbox,\n};\n\nexport const WithDescription = () => {\n  const [checked, setChecked] = useState(false);\n  return (\n    <div className=\"flex flex-col gap-4\">\n      <Checkbox\n        label=\"Add label\"\n        description=\"Add description\"\n        checked={checked}\n        onChange={setChecked}\n      />\n      <Checkbox\n        label=\"Intermediary\"\n        description=\"Intermediary\"\n        checked={checked}\n        onChange={setChecked}\n        intermediary\n      />\n    </div>\n  );\n};\n\nexport const WithoutDescription = () => {\n  const [checked, setChecked] = useState(false);\n  return (\n    <div>\n      <Checkbox label=\"Add label\" checked={checked} onChange={setChecked} />\n    </div>\n  );\n};\n\nexport const Disabled = () => {\n  return (\n    <div>\n      <Checkbox\n        label=\"Add label\"\n        description=\"Add description\"\n        disabled\n        checked={false}\n        onChange={() => {}}\n      />\n    </div>\n  );\n};\n"
  },
  {
    "path": "client/src/components/Checkbox/index.tsx",
    "content": "import { ReactNode } from 'react';\nimport { CheckIcon } from '../../icons';\n\ntype Props = {\n  disabled?: boolean;\n  intermediary?: boolean;\n  checked: boolean;\n  id?: string;\n  label: string | ReactNode;\n  description?: string;\n  labelClassName?: string;\n  boxClassName?: string;\n  isBoxAtTop?: boolean;\n  onChange: (b: boolean) => void;\n};\n\nconst Checkbox = ({\n  disabled,\n  checked,\n  id,\n  label,\n  onChange,\n  description,\n  intermediary,\n  labelClassName,\n  boxClassName,\n  isBoxAtTop,\n}: Props) => {\n  return (\n    <label\n      className={`${\n        disabled ? 'text-label-muted' : 'text-label-title cursor-pointer'\n      } flex gap-2 ${\n        description || isBoxAtTop ? 'items-start' : 'items-center'\n      } group-custom w-full focus:outline-none focus:outline-0 outline-none`}\n      onClick={(e) => {\n        if (!disabled) {\n          e.stopPropagation();\n          onChange(!checked);\n        }\n      }}\n    >\n      <div\n        role=\"checkbox\"\n        tabIndex={0}\n        id={id}\n        className={`border ${\n          checked || intermediary\n            ? 'bg-bg-main text-label-control border-bg-main'\n            : disabled\n            ? 'text-transparent border-bg-base'\n            : 'text-transparent group-custom-hover:text-bg-border-hover border-bg-border hover:border-bg-border-hover'\n        } ${\n          disabled\n            ? 'bg-bg-base'\n            : 'group-custom-active:text-label-control group-custom-active:bg-bg-main group-custom-active:border-bg-main group-custom-active:shadow-rings-blue'\n        } w-4 h-4 flex flex-shrink-0 items-center justify-center rounded-sm relative ${\n          description ? 'top-[2px]' : ''\n        } transition-all duration-150 ease-in-bounce\n         focus:outline-none focus:outline-0 outline-none ${boxClassName}`}\n        aria-checked={checked}\n      >\n        {intermediary ? (\n          <svg\n            viewBox=\"0 0 8 2\"\n            fill=\"none\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n            className=\"flex-shrink-0 w-[8px] h-[2px]\"\n          >\n            <path\n              d=\"M1.17157 1H6.82843\"\n              stroke=\"currentColor\"\n              strokeWidth=\"1.5\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            />\n          </svg>\n        ) : (\n          <CheckIcon raw />\n        )}\n      </div>\n      <div className=\"flex flex-col gap-1 body-s w-full overflow-hidden\">\n        <span className={labelClassName}>{label}</span>\n        {description ? (\n          <span className={disabled ? 'text-label-muted' : 'text-label-base'}>\n            {description}\n          </span>\n        ) : null}\n      </div>\n    </label>\n  );\n};\n\nexport default Checkbox;\n"
  },
  {
    "path": "client/src/components/Chips/FileChip.tsx",
    "content": "import React, {\n  Dispatch,\n  MutableRefObject,\n  SetStateAction,\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport { FileHighlightsType } from '../../types/general';\nimport { highlightColors } from '../../consts/code';\nimport FileIcon from '../FileIcon';\n\ntype Props = {\n  onClick: () => void;\n  fileName: string;\n  filePath: string;\n  skipIcon?: boolean;\n  lines?: [number, number];\n  fileChips?: MutableRefObject<HTMLButtonElement[]>;\n  setFileHighlights?: Dispatch<SetStateAction<FileHighlightsType>>;\n  setHoveredLines?: Dispatch<SetStateAction<[number, number] | null>>;\n};\n\nconst FileChip = ({\n  onClick,\n  fileName,\n  filePath,\n  skipIcon,\n  lines,\n  fileChips,\n  setFileHighlights,\n  setHoveredLines,\n}: Props) => {\n  const ref = useRef<HTMLButtonElement>(null);\n  const [isHovered, setHovered] = useState(false);\n  const [, setRendered] = useState(false);\n\n  useEffect(() => {\n    // a hack to make this component rerender once when fileChips are updated\n    setRendered(true);\n  }, []);\n\n  useEffect(() => {\n    let chip = ref.current;\n    if (chip && fileChips) {\n      fileChips.current.push(chip);\n    }\n\n    return () => {\n      if (chip && fileChips) {\n        const index = fileChips.current.indexOf(chip);\n        if (index !== -1) {\n          fileChips.current.splice(index, 1);\n        }\n      }\n    };\n  }, []);\n\n  const index =\n    ref.current && fileChips ? fileChips.current.indexOf(ref.current) : -1;\n\n  // useEffect(() => {\n  //   if (lines && index === 0 && filePath) {\n  //     if (fileChips?.current?.length === 1) {\n  //       onClick();\n  //     }\n  //   }\n  // }, [index, lines, filePath]);\n\n  useEffect(() => {\n    if (lines && index > -1 && setFileHighlights) {\n      setFileHighlights((prev) => {\n        const newHighlights = JSON.parse(JSON.stringify(prev));\n        if (!newHighlights[filePath]) {\n          newHighlights[filePath] = [];\n        }\n        newHighlights[filePath][index] = {\n          lines,\n          color: `rgb(${highlightColors[index % highlightColors.length].join(\n            ', ',\n          )})`,\n          index,\n        };\n        if (JSON.stringify(prev) === JSON.stringify(newHighlights)) {\n          return prev;\n        }\n        return newHighlights;\n      });\n    }\n  }, [lines, filePath, index]);\n\n  useEffect(() => {\n    if (setHoveredLines && lines && index > -1) {\n      setHoveredLines((prev) => {\n        if (\n          isHovered &&\n          (!prev || prev[0] !== lines[0] || prev[1] !== lines[1])\n        ) {\n          return lines;\n        }\n        if (\n          !isHovered &&\n          prev &&\n          prev[0] === lines[0] &&\n          prev[1] === lines[1]\n        ) {\n          return null;\n        }\n        return prev;\n      });\n    }\n  }, [isHovered]);\n\n  const lineStyle = useMemo(() => {\n    return {\n      backgroundColor: `rgb(${\n        index > -1\n          ? highlightColors[index % highlightColors.length].join(', ')\n          : ''\n      })`,\n    };\n  }, [index]);\n\n  const onMouseLeave = useCallback(() => {\n    if (setHoveredLines) {\n      setHovered(false);\n    }\n  }, [setHoveredLines]);\n\n  const onMouseEnter = useCallback(() => {\n    if (setHoveredLines) {\n      setHovered(true);\n    }\n  }, [setHoveredLines]);\n\n  return (\n    <button\n      className={`h-5 inline-flex items-center bg-bg-base rounded overflow-hidden \n                text-label-title border border-bg-border\n                hover:border-bg-border-hover hover:bg-bg-base-hover\n                cursor-pointer align-middle ellipsis`}\n      ref={ref}\n      onClick={onClick}\n      onMouseLeave={onMouseLeave}\n      onMouseEnter={onMouseEnter}\n    >\n      {!!lines && (\n        <span className=\"w-0.5 h-3.5 ml-1 rounded-px\" style={lineStyle} />\n      )}\n      <span className=\"flex gap-1 px-1 items-center body-s ellipsis\">\n        {!skipIcon && (\n          <span className=\"scale-75 -mx-0.5\">\n            <FileIcon filename={fileName} noMargin />\n          </span>\n        )}\n        <span className=\"ellipsis\">{fileName}</span>\n      </span>\n    </button>\n  );\n};\n\nexport default FileChip;\n"
  },
  {
    "path": "client/src/components/Code/CodeBlockSearch/index.tsx",
    "content": "import React, { memo, useCallback, useMemo, useState, MouseEvent } from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport FileIcon from '../../FileIcon';\nimport { ResultClick, Snippet } from '../../../types/results';\nimport Button from '../../Button';\nimport CodeFragment from '../CodeFragment';\nimport BreadcrumbsPathContainer from '../../Breadcrumbs/PathContainer';\nimport { FileTreeFileType } from '../../../types';\n\ntype Props = {\n  snippets: Snippet[];\n  language: string;\n  filePath: string;\n  branch?: string;\n  repoRef: string;\n  collapsed?: boolean;\n  onClick?: ResultClick;\n  hideDropdown?: boolean;\n  hideMatchCounter?: boolean;\n};\n\nconst PREVIEW_NUM = 3;\n\nconst countHighlights = (snippets: Snippet[]) => {\n  return snippets.reduce((acc: number, item) => {\n    return acc + (item.highlights?.length || 0);\n  }, 0);\n};\n\nconst CodeBlockSearch = ({\n  snippets,\n  language,\n  filePath,\n  collapsed,\n  onClick,\n  repoRef,\n  hideDropdown,\n  hideMatchCounter,\n}: Props) => {\n  const { t } = useTranslation();\n  const [isExpanded, setExpanded] = useState(false);\n\n  const handleMouseUp = useCallback(\n    (startLine?: number, endLine?: number) => {\n      if (!document.getSelection()?.toString()) {\n        onClick?.(\n          repoRef,\n          filePath,\n          startLine !== undefined && endLine !== undefined\n            ? [startLine, endLine]\n            : undefined,\n        );\n      }\n    },\n    [onClick, repoRef, filePath],\n  );\n\n  const totalMatches = useMemo(() => {\n    return countHighlights(snippets);\n  }, [snippets]);\n\n  const hiddenMatches = useMemo(() => {\n    if (snippets.length > PREVIEW_NUM) {\n      return countHighlights(snippets.slice(PREVIEW_NUM));\n    }\n    return 0;\n  }, [snippets]);\n\n  const onBreadcrumbClick = useCallback(\n    (path: string, type?: FileTreeFileType) => {\n      type === FileTreeFileType.FILE ? handleMouseUp() : {};\n    },\n    [handleMouseUp],\n  );\n\n  const renderedSnippets = useMemo(() => {\n    return (isExpanded ? snippets : snippets.slice(0, PREVIEW_NUM)).map(\n      (snippet, index) => (\n        <span\n          key={index}\n          onMouseUp={() =>\n            handleMouseUp(\n              snippet.lineStart,\n              snippet.lineStart !== undefined\n                ? snippet.lineStart + snippet.code.split('\\n').length\n                : undefined,\n            )\n          }\n        >\n          <CodeFragment\n            lineStart={snippet.lineStart}\n            code={snippet.code}\n            language={language}\n            highlights={snippet.highlights}\n          />\n          {index !== snippets.length - 1 ? (\n            collapsed ? (\n              <span className=\"w-full border-t border-bg-border block my-2\" />\n            ) : (\n              <pre className={`bg-bg-sub my-0 px-2`}>\n                <table>\n                  <tbody>\n                    <tr className=\"token-line\">\n                      <td\n                        className={`${\n                          snippet.symbols?.length ? 'w-5' : 'w-0 px-1'\n                        }  text-center`}\n                      />\n                      <td className=\"text-label-muted min-w-6 text-right text-l select-none\">\n                        ..\n                      </td>\n                    </tr>\n                  </tbody>\n                </table>\n              </pre>\n            )\n          ) : (\n            ''\n          )}\n        </span>\n      ),\n    );\n  }, [isExpanded, snippets, handleMouseUp, language, collapsed]);\n\n  const toggleExpanded = useCallback((e: MouseEvent) => {\n    e.stopPropagation();\n    setExpanded((prev) => !prev);\n  }, []);\n\n  return (\n    <div className=\"w-full border border-bg-border rounded-4\">\n      <div className=\"w-full flex justify-between rounded-tl-4 rounded-tr-4 bg-bg-shade px-3 h-13 border-b border-bg-border gap-2\">\n        <div className=\"flex items-center gap-2 max-w-[calc(100%-120px)] w-full\">\n          <FileIcon filename={filePath} />\n          <BreadcrumbsPathContainer\n            path={filePath}\n            onClick={onBreadcrumbClick}\n          />\n        </div>\n        <div className=\"flex gap-2 items-center flex-shrink-0\">\n          {!hideMatchCounter ? (\n            <span className=\"body-s text-label-title\">\n              <Trans count={totalMatches}># match</Trans>\n            </span>\n          ) : (\n            ''\n          )}\n        </div>\n      </div>\n\n      <div\n        className={`bg-bg-sub text-label-muted text-xs border-bg-border ${\n          collapsed ? 'py-2' : 'py-4'\n        } ${onClick ? 'cursor-pointer' : ''} w-full overflow-auto`}\n      >\n        <div>{renderedSnippets}</div>\n        {snippets.length > PREVIEW_NUM && (\n          <div\n            className={`${\n              isExpanded ? 'mt-2' : 'mt-[-38px] pt-6'\n            } mb-1 relative flex justify-center align-center bg-gradient-to-b from-transparent via-bg-sub/90 to-bg-sub`}\n          >\n            <Button variant=\"secondary\" size=\"small\" onClick={toggleExpanded}>\n              {isExpanded\n                ? t('Show less')\n                : t(`Show # more match`, { count: hiddenMatches })}\n            </Button>\n          </div>\n        )}\n      </div>\n    </div>\n  );\n};\nexport default memo(CodeBlockSearch);\n"
  },
  {
    "path": "client/src/components/Code/CodeDiff/index.tsx",
    "content": "import React, { ChangeEvent, useCallback, useEffect, useState } from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport FileIcon from '../../FileIcon';\nimport { getFileExtensionForLang, getPrettyLangName } from '../../../utils';\nimport CopyButton from '../../MarkdownWithCode/CopyButton';\nimport { DiffChunkType } from '../../../types/general';\nimport Button from '../../Button';\nimport BreadcrumbsPathContainer from '../../Breadcrumbs/PathContainer';\nimport { PencilIcon, TrashCanIcon } from '../../../icons';\nimport CodeFragment from '../CodeFragment';\n\ntype Props = DiffChunkType & {\n  onClick: (d: DiffChunkType) => void;\n  language: string;\n  filePath: string;\n  onDiffRemoved: (i: number) => void;\n  onDiffChanged: (i: number, p: string) => void;\n  i: number;\n};\n\nconst CodeDiff = ({\n  hunks,\n  language,\n  filePath,\n  onClick,\n  file,\n  repo,\n  branch,\n  raw_patch,\n  lang,\n  i,\n  onDiffChanged,\n  onDiffRemoved,\n}: Props) => {\n  const [isEditMode, setIsEditMode] = useState(false);\n  const [editedValue, setEditedValue] = useState(\n    raw_patch.split('\\n').slice(2, -1).join('\\n'),\n  );\n  const { t } = useTranslation();\n\n  useEffect(() => {\n    setEditedValue(raw_patch.split('\\n').slice(2, -1).join('\\n'));\n  }, [raw_patch]);\n\n  const handleClick = useCallback(() => {\n    onClick({ hunks, repo, branch, file, lang, raw_patch });\n  }, [hunks, repo, branch, file, lang, raw_patch]);\n\n  const onEditClick = useCallback((e: React.MouseEvent) => {\n    e.stopPropagation();\n    setIsEditMode(true);\n  }, []);\n\n  const onCancelEdit = useCallback((e: React.MouseEvent) => {\n    e.stopPropagation();\n    setIsEditMode(false);\n  }, []);\n\n  const handleChange = useCallback((e: ChangeEvent<HTMLTextAreaElement>) => {\n    setEditedValue(e.target.value);\n  }, []);\n\n  const onSaveEdit = useCallback(\n    (e: React.MouseEvent) => {\n      e.stopPropagation();\n      onDiffChanged(\n        i,\n        `--- ${repo}:${filePath}\n+++ ${repo}:${filePath}\n${editedValue}\n`,\n      );\n      setIsEditMode(false);\n    },\n    [i, editedValue, onDiffChanged, repo],\n  );\n\n  const onRemove = useCallback(\n    (e: React.MouseEvent) => {\n      e.stopPropagation();\n      onDiffRemoved(i);\n    },\n    [i, onDiffRemoved],\n  );\n\n  return (\n    <div\n      onClick={handleClick}\n      className={`my-4 block bg-bg-sub text-xs border-bg-border border rounded-md relative group-code cursor-pointer`}\n    >\n      <div\n        className={`bg-bg-shade border-bg-border border-b rounded-t-md p-2 flex items-center justify-between gap-2`}\n      >\n        <div className=\"flex items-center gap-2 overflow-hidden flex-1\">\n          <FileIcon\n            filename={filePath || getFileExtensionForLang(language, true)}\n            noMargin\n          />\n          {filePath ? (\n            <BreadcrumbsPathContainer\n              path={filePath}\n              repoRef={repo}\n              nonInteractive\n            />\n          ) : (\n            <span className=\"body-mini-b\">\n              {getPrettyLangName(language) || language}\n            </span>\n          )}\n        </div>\n        <div className=\"flex items-center gap-2\">\n          <CopyButton isInHeader code={raw_patch} btnVariant=\"tertiary\" />\n          {repo.startsWith('local//') ? (\n            isEditMode ? (\n              <>\n                <Button size=\"mini\" variant=\"secondary\" onClick={onCancelEdit}>\n                  <Trans>Cancel</Trans>\n                </Button>\n                <Button size=\"mini\" onClick={onSaveEdit}>\n                  <Trans>Save</Trans>\n                </Button>\n              </>\n            ) : (\n              <>\n                <Button\n                  variant=\"secondary\"\n                  size=\"mini\"\n                  onlyIcon\n                  title={t('Edit')}\n                  onClick={onEditClick}\n                >\n                  <PencilIcon\n                    sizeClassName=\"w-3.5 h-3.5\"\n                    className=\"text-label-muted\"\n                  />\n                </Button>\n                <Button\n                  variant=\"secondary\"\n                  size=\"mini\"\n                  onlyIcon\n                  title={t('Remove')}\n                  onClick={onRemove}\n                >\n                  <TrashCanIcon\n                    sizeClassName=\"w-3.5 h-3.5\"\n                    className=\"text-label-muted\"\n                  />\n                </Button>\n              </>\n            )\n          ) : null}\n        </div>\n      </div>\n      <div className={`overflow-auto py-2`}>\n        {isEditMode ? (\n          <textarea\n            className={`px-2 w-full bg-transparent outline-none focus:outline-0 resize-none body-s placeholder:text-label-base`}\n            value={editedValue}\n            onChange={handleChange}\n            rows={Math.min(10, raw_patch.split('\\n').length)}\n          />\n        ) : (\n          hunks.map((h, index) => (\n            <>\n              <CodeFragment\n                key={h.line_start}\n                showLines\n                code={h.patch.slice(0, -1)}\n                language={language}\n                isDiff\n                lineStart={h.line_start - 1}\n              />\n              {index !== hunks.length - 1 ? (\n                <pre className={`bg-bg-sub my-0 px-2`}>\n                  <table>\n                    <tbody>\n                      <tr className=\"token-line\">\n                        <td className=\"text-label-muted min-w-6 text-right text-l select-none\">\n                          ..\n                        </td>\n                      </tr>\n                    </tbody>\n                  </table>\n                </pre>\n              ) : null}\n            </>\n          ))\n        )}\n      </div>\n    </div>\n  );\n};\n\nexport default CodeDiff;\n"
  },
  {
    "path": "client/src/components/Code/CodeFragment/index.tsx",
    "content": "import React, { memo, useMemo } from 'react';\nimport CodeLine from '../CodeLine';\nimport CodeToken from '../CodeToken';\nimport { HighlightMap, Range, TokensLine } from '../../../types/results';\nimport { getPrismLanguage, tokenizeCode } from '../../../utils/prism';\nimport { Token } from '../../../types/prism';\n\ntype Props = {\n  code: string;\n  language: string;\n  lineStart?: number;\n  highlights?: Range[];\n  showLines?: boolean;\n  removePaddings?: boolean;\n  lineHoverEffect?: boolean;\n  isDiff?: boolean;\n  canWrap?: boolean;\n  highlightColor?: string;\n};\n\nconst CodeFragment = ({\n  code,\n  language,\n  lineStart = 0,\n  showLines = true,\n  highlights,\n  removePaddings,\n  lineHoverEffect,\n  highlightColor,\n  isDiff,\n  canWrap,\n}: Props) => {\n  const lang = useMemo(\n    () => getPrismLanguage(language) || 'plaintext',\n    [language],\n  );\n  const tokens = useMemo(() => tokenizeCode(code, lang), [code, lang]);\n\n  const hlRangesMap = useMemo(() => {\n    const hl = new Set();\n    highlights?.map((range) => {\n      for (let i = range.start; i < range.end; i++) {\n        hl.add(i);\n      }\n    });\n    return hl;\n  }, [highlights]);\n\n  const getMap = (tokens: Token[]): HighlightMap[] => {\n    const highlightMaps: HighlightMap[] = [];\n    tokens.forEach((token) => {\n      highlightMaps.push(...getToken(token));\n    });\n\n    return highlightMaps.map((item, i) => {\n      if (item.highlight) {\n        item.startHl = !highlightMaps[i - 1]?.highlight;\n        item.endHl = !highlightMaps[i + 1]?.highlight;\n      }\n\n      return item;\n    });\n  };\n\n  const getToken = (token: Token): HighlightMap[] => {\n    if (!highlights) {\n      return [{ highlight: false, token }];\n    }\n    const highlightMap: HighlightMap[] = [];\n\n    let byteIndex = 0;\n    token.content.split('').forEach((char) => {\n      const pos = token.byteRange.start + byteIndex;\n      const existing = highlightMap[highlightMap.length - 1];\n\n      if (hlRangesMap.has(pos)) {\n        if (!existing || !existing.highlight) {\n          highlightMap.push({\n            highlight: true,\n            token: {\n              ...token,\n              content: char,\n            },\n          });\n        } else {\n          existing.token.content += char;\n        }\n      } else {\n        if (!existing || existing.highlight) {\n          highlightMap.push({\n            highlight: false,\n            token: {\n              ...token,\n              content: char,\n            },\n          });\n        } else {\n          existing.token.content += char;\n        }\n      }\n      byteIndex += new TextEncoder().encode(char).length;\n    });\n\n    return highlightMap;\n  };\n\n  const tokensMap = useMemo((): TokensLine[] => {\n    const lines = tokens\n      .map((line) => getMap(line))\n      .map((l): TokensLine => ({ tokens: l, lineNumber: null }));\n    let currentLine = lineStart;\n    for (let i = 0; i < lines.length; i++) {\n      if (\n        isDiff &&\n        (lines[i].tokens[0]?.token.content === '-' ||\n          lines[i].tokens[1]?.token.content === '-')\n      ) {\n        continue;\n      }\n      lines[i].lineNumber = currentLine + 1;\n      currentLine++;\n    }\n    return lines;\n  }, [tokens, lineStart, isDiff]);\n\n  const lineNumbersAdd = useMemo(() => {\n    let curr = lineStart;\n    return tokensMap.map((line) => {\n      if (\n        line.tokens[0]?.token?.content === '-' ||\n        line.tokens[1]?.token?.content === '-'\n      ) {\n        return null;\n      } else {\n        curr++;\n        return curr;\n      }\n    });\n  }, [tokensMap, lineStart]);\n  const lineNumbersRemove = useMemo(() => {\n    let curr = lineStart;\n    return tokensMap.map((line) => {\n      if (\n        line.tokens[0]?.token?.content === '+' ||\n        line.tokens[1]?.token?.content === '+'\n      ) {\n        return null;\n      } else {\n        curr++;\n        return curr;\n      }\n    });\n  }, [tokensMap, lineStart]);\n  const codeLines = useMemo(\n    () =>\n      tokensMap.map((line, lineNumber) => (\n        <CodeLine\n          key={lineNumber}\n          lineNumber={lineStart + lineNumber}\n          lineNumberToShow={line.lineNumber}\n          lineNumbersDiff={\n            isDiff\n              ? [lineNumbersRemove[lineNumber], lineNumbersAdd[lineNumber]]\n              : null\n          }\n          showLineNumbers={showLines}\n          hoverEffect={lineHoverEffect}\n          isNewLine={\n            isDiff &&\n            (line.tokens[0]?.token?.content === '+' ||\n              line.tokens[1]?.token?.content === '+')\n          }\n          isRemovedLine={\n            isDiff &&\n            (line.tokens[0]?.token?.content === '-' ||\n              line.tokens[1]?.token?.content === '-')\n          }\n        >\n          {line.tokens.map((token, index) => (\n            <CodeToken\n              key={index}\n              token={token.token}\n              highlight={token.highlight}\n              startHl={token.startHl}\n              endHl={token.endHl}\n              onClick={() => {}}\n            />\n          ))}\n        </CodeLine>\n      )),\n    [tokensMap, showLines, highlights, highlightColor, removePaddings],\n  );\n  return (\n    <div>\n      <pre\n        className={`prism-code language-${lang} text-label-base my-0 ${\n          removePaddings ? '' : 'px-2'\n        } ${canWrap && codeLines.length < 2 ? '!whitespace-pre-wrap' : ''}`}\n      >\n        <div className=\"flex flex-col\">{codeLines}</div>\n      </pre>\n    </div>\n  );\n};\n\nexport default memo(CodeFragment);\n"
  },
  {
    "path": "client/src/components/Code/CodeFull/SelectionPopup.tsx",
    "content": "import { memo, useCallback, useContext, MouseEvent } from 'react';\nimport { AnimatePresence, motion } from 'framer-motion';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { DeviceContext } from '../../../context/deviceContext';\nimport {\n  CloseSignIcon,\n  CodeLineWithSparkleIcon,\n  LinkChainIcon,\n} from '../../../icons';\nimport { TabTypesEnum } from '../../../types/general';\nimport { TabsContext } from '../../../context/tabsContext';\nimport Button from '../../Button';\nimport { copyToClipboard } from '../../../utils';\nimport { UIContext } from '../../../context/uiContext';\n\ntype Props = {\n  selectedLines?: [number, number];\n  closePopup: () => void;\n  path: string;\n  repoRef: string;\n  branch?: string | null;\n  side: 'left' | 'right';\n};\n\nconst initial = { opacity: 0, transform: 'translate(-50%, 1rem)' };\nconst animate = { opacity: 1, transform: 'translate(-50%, 0rem)' };\nconst exit = { opacity: 0, transform: 'translate(-50%, 1rem)' };\n\nconst SelectionPopup = ({\n  selectedLines,\n  closePopup,\n  path,\n  repoRef,\n  branch,\n  side,\n}: Props) => {\n  const { t } = useTranslation();\n  const { isSelfServe } = useContext(DeviceContext);\n  const { openNewTab } = useContext(TabsContext.Handlers);\n  const { setOnBoardingState } = useContext(UIContext.Onboarding);\n\n  const handleExplain = useCallback(\n    (e: MouseEvent) => {\n      e.stopPropagation();\n      if (selectedLines) {\n        openNewTab(\n          {\n            type: TabTypesEnum.CHAT,\n            initialQuery: {\n              path,\n              repoRef,\n              branch,\n              lines: selectedLines,\n            },\n          },\n          side === 'left' ? 'right' : 'left',\n        );\n      }\n      closePopup();\n      setOnBoardingState((prev) =>\n        prev.isCodeExplained ? prev : { ...prev, isCodeExplained: true },\n      );\n    },\n    [path, repoRef, branch, selectedLines, side, closePopup],\n  );\n\n  const handleCopyLink = useCallback(\n    (e: MouseEvent) => {\n      e.stopPropagation();\n      if (selectedLines) {\n        const url = new URL(window.location.href);\n        url.searchParams.set('scrollToLine', selectedLines.join('_'));\n        copyToClipboard(url.toString());\n      }\n      closePopup?.();\n    },\n    [selectedLines],\n  );\n\n  return (\n    <AnimatePresence>\n      {selectedLines && (\n        <motion.div\n          className=\"absolute z-[120] top-4 left-1/2\"\n          initial={initial}\n          animate={animate}\n          exit={exit}\n        >\n          <div className=\"flex gap-1 items-center py-1 bg-bg-base border border-bg-border rounded-full px-2 shadow-medium overflow-hidden select-none\">\n            <div className=\"text-label-title body-mini\">\n              <Trans\n                values={{\n                  start: selectedLines[0] + 1,\n                  end: selectedLines[1] + 1,\n                }}\n              >\n                Selected lines # - #.\n              </Trans>\n            </div>\n            <div className=\"h-4 w-px bg-bg-border\" />\n            {/*{selectedLinesLength > 1000 ? (*/}\n            {/*  <button*/}\n            {/*    className=\"h-8 flex items-center justify-center gap-1 px-2 caption text-label-muted\"*/}\n            {/*    disabled*/}\n            {/*  >*/}\n            {/*    <div className=\"w-4 h-4\">*/}\n            {/*      <InfoIcon raw />*/}\n            {/*    </div>*/}\n            {/*    <Trans>Select less code</Trans>*/}\n            {/*  </button>*/}\n            {/*) : (*/}\n            {/*  <>*/}\n            {isSelfServe && (\n              <Button variant=\"tertiary\" size=\"mini\" onClick={handleCopyLink}>\n                <LinkChainIcon sizeClassName=\"w-3.5 h-3.5\" />\n                <Trans>Copy link</Trans>\n              </Button>\n            )}\n            <Button variant=\"tertiary\" size=\"mini\" onClick={handleExplain}>\n              <CodeLineWithSparkleIcon sizeClassName=\"w-3.5 h-3.5\" />\n              <Trans>Explain</Trans>\n            </Button>\n            {/*  </>*/}\n            {/*)}*/}\n            <Button\n              variant=\"tertiary\"\n              size=\"mini\"\n              onClick={closePopup}\n              onlyIcon\n              title={t('Close')}\n            >\n              <CloseSignIcon sizeClassName=\"w-3.5 h-3.5\" />\n            </Button>\n          </div>\n        </motion.div>\n      )}\n    </AnimatePresence>\n  );\n};\n\nexport default memo(SelectionPopup);\n"
  },
  {
    "path": "client/src/components/Code/CodeFull/Token.tsx",
    "content": "import { memo, useCallback, useEffect, useState } from 'react';\nimport CodeToken from '../CodeToken';\nimport { Token as TokenType } from '../../../types/prism';\nimport { Range } from '../../../types/results';\n\ntype Props = {\n  token: TokenType;\n  lineHoverRanges?: Range[];\n  getHoverableContent: (\n    hoverableRange: Range,\n    tokenRange: Range,\n    lineNumber: number,\n  ) => void;\n  lineNumber: number;\n  isEditingRanges?: boolean;\n};\n\nconst tokenHoverable = (tokenPosition: Range, ranges: Range[]) => {\n  if (!ranges) {\n    return;\n  }\n  for (let range of ranges) {\n    if (range.start >= tokenPosition.start && range.end <= tokenPosition.end) {\n      return {\n        start: range.start,\n        end: range.end,\n      };\n    }\n  }\n};\n\nconst Token = ({\n  token,\n  lineHoverRanges = [],\n  getHoverableContent,\n  lineNumber,\n  isEditingRanges,\n}: Props) => {\n  const [hoverableRange, setHoverableRange] = useState(\n    tokenHoverable(token.byteRange, lineHoverRanges),\n  );\n\n  useEffect(() => {\n    setHoverableRange(tokenHoverable(token.byteRange, lineHoverRanges));\n  }, [token, lineHoverRanges]);\n\n  const onClick = useCallback(() => {\n    if (\n      !document.getSelection()?.toString() &&\n      hoverableRange &&\n      !isEditingRanges\n    ) {\n      getHoverableContent(hoverableRange!, token.byteRange, lineNumber);\n    }\n  }, [getHoverableContent, hoverableRange, lineNumber, isEditingRanges]);\n\n  return (\n    <CodeToken\n      token={token}\n      isHoverable={!!hoverableRange && !isEditingRanges}\n      onClick={onClick}\n    />\n  );\n};\n\nexport default memo(Token);\n"
  },
  {
    "path": "client/src/components/Code/CodeFull/VirtualizedCode.tsx",
    "content": "import { Align, FixedSizeList } from 'react-window';\nimport React, { memo, useEffect, useMemo, useRef } from 'react';\nimport { Token as TokenType } from '../../../types/prism';\nimport { Range } from '../../../types/results';\nimport { getOffsetForIndexAndAlignment } from '../../../utils/scrollUtils';\nimport CodeLine from '../CodeLine';\nimport { useDiffLines } from '../../../hooks/useDiffLines';\nimport Token from './Token';\n\ntype Props = {\n  hoverableRanges?: Record<number, Range[]>;\n  tokens: TokenType[][];\n  scrollToIndex?: number[];\n  width: number;\n  height: number;\n  getHoverableContent: (\n    hoverableRange: Range,\n    tokenRange: Range,\n    lineNumber: number,\n  ) => void;\n  highlights?: (\n    | { lines: [number, number]; color: string; index: number }\n    | undefined\n  )[];\n  hoveredLines?: [number, number] | null;\n  isDiff?: boolean;\n  searchTerm?: string;\n};\n\nconst VirtualizedCode = ({\n  tokens,\n  hoverableRanges,\n  scrollToIndex,\n  width,\n  height,\n  getHoverableContent,\n  highlights,\n  hoveredLines,\n  isDiff,\n  searchTerm,\n}: Props) => {\n  const ref = useRef<FixedSizeList>(null);\n  const listProps = useMemo(\n    () => ({\n      width,\n      height,\n      itemSize: 20,\n      itemCount: tokens.length + 2, // to add padding at the bottom\n      overscanCount: 5,\n    }),\n    [width, height, tokens.length],\n  );\n  const { lineNumbersAdd, lineNumbersRemove } = useDiffLines(tokens, !isDiff);\n\n  useEffect(() => {\n    if (scrollToIndex && ref.current) {\n      let scrollToItem = scrollToIndex[0];\n      let align: Align = 'center';\n      let multiline = scrollToIndex[1] && scrollToIndex[0] !== scrollToIndex[1];\n      if (multiline && scrollToIndex[1] - scrollToIndex[0] < 8) {\n        scrollToItem =\n          scrollToIndex[0] +\n          Math.floor((scrollToIndex[1] - scrollToIndex[0]) / 2);\n      } else if (multiline) {\n        align = 'start';\n      }\n      scrollToItem = Math.max(0, Math.min(scrollToItem, tokens.length - 1));\n      const scrollOffset = getOffsetForIndexAndAlignment(\n        listProps,\n        scrollToItem,\n        align,\n        0,\n      );\n      ref.current.scrollTo(scrollOffset);\n    }\n  }, [scrollToIndex, listProps]);\n\n  return (\n    <FixedSizeList ref={ref} {...listProps}>\n      {({ index, style }) => {\n        let highlightForLine = highlights?.findIndex(\n          (h) => h && index >= h.lines[0] && index <= h.lines[1],\n        );\n        if (highlightForLine && highlightForLine < 0) {\n          highlightForLine = undefined;\n        }\n        return index < tokens.length ? (\n          <CodeLine\n            key={index.toString()}\n            lineNumber={index}\n            showLineNumbers\n            hoverEffect\n            style={style}\n            searchTerm={searchTerm}\n            shouldHighlight={\n              (!!scrollToIndex &&\n                index >= scrollToIndex[0] &&\n                index <= scrollToIndex[1]) ||\n              (highlights && highlightForLine !== undefined)\n            }\n            highlightColor={\n              highlights && highlightForLine !== undefined\n                ? highlights[highlightForLine]?.color\n                : undefined\n            }\n            hoveredBackground={\n              !!hoveredLines &&\n              index >= hoveredLines[0] &&\n              index <= hoveredLines[1]\n            }\n            isNewLine={\n              isDiff &&\n              (tokens[index][0]?.content === '+' ||\n                tokens[index][1]?.content === '+')\n            }\n            isRemovedLine={\n              isDiff &&\n              (tokens[index][0]?.content === '-' ||\n                tokens[index][1]?.content === '-')\n            }\n            lineNumbersDiff={\n              isDiff ? [lineNumbersRemove[index], lineNumbersAdd[index]] : null\n            }\n          >\n            {tokens[index].map((token, i) => (\n              <Token\n                key={`cell-${index}-${i}`}\n                lineHoverRanges={hoverableRanges?.[index]}\n                token={token}\n                getHoverableContent={getHoverableContent}\n                lineNumber={index}\n              />\n            ))}\n          </CodeLine>\n        ) : (\n          <div className=\"w-fll h-5\" />\n        );\n      }}\n    </FixedSizeList>\n  );\n};\n\nexport default memo(VirtualizedCode);\n"
  },
  {
    "path": "client/src/components/Code/CodeFull/index.tsx",
    "content": "import React, {\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport { Range, TokenInfoWrapped } from '../../../types/results';\nimport { getPrismLanguage, tokenizeCode } from '../../../utils/prism';\nimport { findElementInCurrentTab } from '../../../utils/domUtils';\nimport { getTokenInfo } from '../../../services/api';\nimport { mapTokenInfo } from '../../../mappers/results';\nimport { TabsContext } from '../../../context/tabsContext';\nimport { TabTypesEnum } from '../../../types/general';\nimport { useOnClickOutside } from '../../../hooks/useOnClickOutsideHook';\nimport RefsDefsPopup from '../../RefsDefsPopup';\nimport { copyToClipboard, getSelectionLines } from '../../../utils';\nimport SearchOnPage from '../../SearchOnPage';\nimport { useCodeSearch } from '../../../hooks/useCodeSearch';\nimport SelectionPopup from './SelectionPopup';\nimport VirtualizedCode from './VirtualizedCode';\n\ntype Props = {\n  code: string;\n  language: string;\n  hoverableRanges?: Record<number, Range[]>;\n  relativePath: string;\n  repoRef: string;\n  isDiff?: boolean;\n  scrollToLine?: string;\n  branch?: string | null;\n  tokenRange?: string;\n  highlights?: (\n    | { lines: [number, number]; color: string; index: number }\n    | undefined\n  )[];\n  hoveredLines?: [number, number] | null;\n  side: 'left' | 'right';\n  width: number;\n  height: number;\n  isSearchDisabled?: boolean;\n};\n\nconst CodeFull = ({\n  code,\n  isDiff,\n  hoverableRanges,\n  repoRef,\n  relativePath,\n  branch,\n  language,\n  scrollToLine,\n  tokenRange,\n  highlights,\n  hoveredLines,\n  side,\n  width,\n  height,\n  isSearchDisabled,\n}: Props) => {\n  const { openNewTab } = useContext(TabsContext.Handlers);\n  const [tokenInfo, setTokenInfo] = useState<TokenInfoWrapped>({\n    data: { references: [], definitions: [] },\n    hoverableRange: null,\n    tokenRange: null,\n    isLoading: false,\n    lineNumber: -1,\n  });\n  const popupRef = useRef<HTMLDivElement>(null);\n  const codeRef = useRef<HTMLPreElement>(null);\n  const [isPopupVisible, setPopupVisible] = useState(false);\n  const [selectionPopupData, setSelectionPopupData] = useState<{\n    selectedLines: [number, number];\n  } | null>(null);\n  const [popupPosition, setPopupPosition] = useState<{\n    top: number;\n    left?: number;\n    right?: number;\n  } | null>(null);\n  useOnClickOutside(popupRef, () => setPopupVisible(false));\n  const scrollLineNumber = useMemo(\n    () =>\n      scrollToLine?.split('_').map((s) => Number(s)) as\n        | [number, number]\n        | undefined,\n    [scrollToLine],\n  );\n  const [scrollToIndex, setScrollToIndex] = useState(\n    scrollLineNumber || undefined,\n  );\n  useEffect(() => {\n    setScrollToIndex(scrollLineNumber || undefined);\n  }, [scrollLineNumber]);\n\n  const {\n    handleSearchCancel,\n    isSearchActive,\n    setSearchTerm,\n    searchTerm,\n    searchResults,\n    setCurrentResult,\n    currentResult,\n    deferredSearchTerm,\n  } = useCodeSearch({\n    code,\n    setScrollToIndex,\n    isDisabled: isSearchDisabled,\n  });\n\n  const lang = useMemo(\n    () => getPrismLanguage(language) || 'plaintext',\n    [language],\n  );\n  const tokens = useMemo(() => tokenizeCode(code, lang), [code, lang]);\n\n  const getHoverableContent = useCallback(\n    (hoverableRange: Range, tokenRange: Range, lineNumber?: number) => {\n      if (hoverableRange && relativePath) {\n        setTokenInfo({\n          data: { references: [], definitions: [] },\n          hoverableRange,\n          tokenRange,\n          lineNumber,\n          isLoading: true,\n        });\n        getTokenInfo(\n          relativePath,\n          repoRef,\n          hoverableRange.start,\n          hoverableRange.end,\n          branch ? branch : undefined,\n        )\n          .then((data) => {\n            setTokenInfo({\n              data: mapTokenInfo(data.data, relativePath),\n              hoverableRange,\n              tokenRange,\n              lineNumber,\n              isLoading: false,\n            });\n          })\n          .catch(() => {\n            setTokenInfo({\n              data: { references: [], definitions: [] },\n              hoverableRange,\n              tokenRange,\n              lineNumber,\n              isLoading: false,\n            });\n          });\n      }\n    },\n    [relativePath, branch],\n  );\n\n  useEffect(() => {\n    if (tokenRange) {\n      const [start, end] = tokenRange.split('_').map((l) => Number(l));\n      getHoverableContent({ start, end }, { start, end });\n    }\n  }, [tokenRange, getHoverableContent]);\n\n  const handleRefsDefsClick = useCallback(\n    (lineNum: number, filePath: string, tokenRange: string) => {\n      setTokenInfo({\n        data: { references: [], definitions: [] },\n        hoverableRange: null,\n        tokenRange: null,\n        isLoading: false,\n        lineNumber: -1,\n      });\n      openNewTab({\n        type: TabTypesEnum.FILE,\n        path: filePath,\n        repoRef,\n        branch,\n        scrollToLine: `${lineNum}_${lineNum}`,\n        tokenRange,\n      });\n    },\n    [openNewTab, repoRef, branch],\n  );\n  useEffect(() => {\n    if (tokenInfo.tokenRange) {\n      let tokenElem = findElementInCurrentTab(\n        `[data-byte-range=\"${tokenInfo.tokenRange.start}-${tokenInfo.tokenRange.end}\"]`,\n      );\n      if (tokenElem && tokenElem instanceof HTMLElement) {\n        const box = tokenElem.getBoundingClientRect();\n        setPopupPosition({\n          top: box.top - 72 + 10,\n          ...(tokenElem.offsetLeft > tokenElem.offsetParent!.scrollWidth / 2\n            ? {\n                right:\n                  tokenElem.offsetParent!.clientWidth -\n                  (tokenElem.offsetLeft + tokenElem.offsetWidth),\n              }\n            : { left: tokenElem.offsetLeft + 12 }),\n        });\n        setPopupVisible(true);\n      }\n    } else {\n      setPopupPosition(null);\n    }\n  }, [tokenInfo]);\n\n  useEffect(() => {\n    setPopupPosition(null);\n  }, []);\n\n  useEffect(() => {\n    let startLine: number | null;\n    const handleWindowMouseDown = (e: MouseEvent) => {\n      if (e.target) {\n        startLine = getSelectionLines(e.target as HTMLElement);\n      }\n    };\n    const handleWindowMouseUp = (e: MouseEvent) => {\n      setTimeout(() => {\n        const selection = window.getSelection();\n        if (selection?.toString()) {\n          const endLine = getSelectionLines(e.target as HTMLElement);\n          if (endLine !== null && startLine !== null) {\n            setSelectionPopupData({\n              selectedLines: [startLine, endLine].sort((a, b) => a - b) as [\n                number,\n                number,\n              ],\n            });\n          }\n        } else {\n          setSelectionPopupData(null);\n        }\n      }, 50);\n    };\n\n    const handleScroll = () => {\n      setSelectionPopupData(null);\n    };\n\n    codeRef.current?.addEventListener('mousedown', handleWindowMouseDown);\n    codeRef.current?.addEventListener('mouseup', handleWindowMouseUp);\n    codeRef.current?.parentElement?.parentElement?.addEventListener(\n      'scroll',\n      handleScroll,\n    );\n\n    return () => {\n      codeRef.current?.removeEventListener('mousedown', handleWindowMouseDown);\n      codeRef.current?.removeEventListener('mouseup', handleWindowMouseUp);\n      codeRef.current?.parentElement?.parentElement?.removeEventListener(\n        'scroll',\n        handleScroll,\n      );\n    };\n  }, []);\n\n  const closeSelectionPopup = useCallback(() => {\n    setSelectionPopupData(null);\n  }, []);\n\n  const handleCopy = useCallback(\n    (e: React.ClipboardEvent<HTMLPreElement>) => {\n      if (selectionPopupData?.selectedLines) {\n        const codeToCopy = code\n          .split('\\n')\n          .slice(\n            selectionPopupData.selectedLines[0],\n            selectionPopupData.selectedLines[1],\n          )\n          .join('\\n');\n        if (codeToCopy) {\n          e.preventDefault();\n          copyToClipboard(codeToCopy);\n        }\n      }\n    },\n    [selectionPopupData?.selectedLines],\n  );\n\n  return (\n    <div className=\"\">\n      <SearchOnPage\n        handleSearch={setSearchTerm}\n        isSearchActive={isSearchActive}\n        resultNum={searchResults.length}\n        onCancel={handleSearchCancel}\n        currentResult={currentResult}\n        setCurrentResult={setCurrentResult}\n        searchValue={searchTerm}\n        containerClassName=\"absolute top-2 right-2 w-80 max-w-[calc(100%-1rem)]\"\n      />\n      <pre\n        className={`prism-code language-${lang} w-full h-full code-s`}\n        ref={codeRef}\n        onCopy={handleCopy}\n      >\n        <code>\n          <VirtualizedCode\n            getHoverableContent={getHoverableContent}\n            hoverableRanges={hoverableRanges}\n            highlights={highlights}\n            hoveredLines={hoveredLines}\n            scrollToIndex={scrollToIndex}\n            tokens={tokens}\n            isDiff={isDiff}\n            width={width}\n            height={height}\n            searchTerm={deferredSearchTerm}\n          />\n        </code>\n      </pre>\n      {!!popupPosition && isPopupVisible && (\n        <div className=\"absolute max-w-sm\" style={popupPosition} ref={popupRef}>\n          <RefsDefsPopup\n            placement={popupPosition.right ? 'bottom-end' : 'bottom-start'}\n            data={tokenInfo}\n            onRefDefClick={handleRefsDefsClick}\n            language={language}\n            relativePath={relativePath}\n          />\n        </div>\n      )}\n      <SelectionPopup\n        closePopup={closeSelectionPopup}\n        path={relativePath}\n        repoRef={repoRef}\n        branch={branch}\n        side={side}\n        selectedLines={selectionPopupData?.selectedLines}\n      />\n    </div>\n  );\n};\n\nexport default memo(CodeFull);\n"
  },
  {
    "path": "client/src/components/Code/CodeFullSelectable/CodeContainer.tsx",
    "content": "import React, {\n  Dispatch,\n  Fragment,\n  memo,\n  MutableRefObject,\n  SetStateAction,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport { Token as TokenType } from '../../../types/prism';\nimport { hashCode, mergeRanges } from '../../../utils';\nimport {\n  findElementInCurrentTab,\n  isFocusInInput,\n} from '../../../utils/domUtils';\nimport { CODE_LINE_HEIGHT } from '../../../consts/code';\nimport useKeyboardNavigation from '../../../hooks/useKeyboardNavigation';\nimport { Range } from '../../../types/results';\nimport { TabsContext } from '../../../context/tabsContext';\nimport { UIContext } from '../../../context/uiContext';\nimport { CommandBarContext } from '../../../context/commandBarContext';\nimport SelectionHandler from './SelectionHandler';\nimport SelectionRect from './SelectionRect';\nimport LinesContainer from './LazyLinesContainer';\n\ntype Props = {\n  relativePath: string;\n  tokens: TokenType[][];\n  searchTerm: string;\n  scrollToIndex?: number[];\n  currentSelection: [number, number][];\n  setCurrentSelection: Dispatch<SetStateAction<[number, number][]>>;\n  scrollContainerRef: MutableRefObject<HTMLPreElement | null>;\n  hoverableRanges?: Record<number, Range[]>;\n  getHoverableContent: (\n    hoverableRange: Range,\n    tokenRange: Range,\n    lineNumber: number,\n  ) => void;\n  highlights?: (\n    | { lines: [number, number]; color: string; index: number }\n    | undefined\n  )[];\n  hoveredLines?: [number, number] | null;\n  isDiff?: boolean;\n  isEditingRanges?: boolean;\n  side: 'left' | 'right';\n};\n\nconst CodeContainerSelectable = ({\n  tokens,\n  setCurrentSelection,\n  relativePath,\n  searchTerm,\n  scrollToIndex,\n  currentSelection,\n  scrollContainerRef,\n  getHoverableContent,\n  hoverableRanges,\n  hoveredLines,\n  isDiff,\n  highlights,\n  isEditingRanges,\n  side,\n}: Props) => {\n  const pathHash = useMemo(\n    () => (relativePath ? hashCode(relativePath) : ''),\n    [relativePath],\n  ); // To tell if code has changed\n  const ref = useRef<HTMLDivElement>(null);\n  const [currentlySelectingRange, setCurrentlySelectingRange] = useState<\n    null | [number, number]\n  >(null);\n  const [modifyingRange, setModifyingRange] = useState(-1);\n  const [shouldScroll, setShouldScroll] = useState<'top' | 'bottom' | false>(\n    false,\n  );\n  const [currentFocusedRange, setCurrentFocusedRange] = useState(-1);\n  const { focusedPanel } = useContext(TabsContext.FocusedPanel);\n  const { isLeftSidebarFocused } = useContext(UIContext.Focus);\n  const { isVisible: isCommandBarVisible } = useContext(\n    CommandBarContext.General,\n  );\n\n  useEffect(() => {\n    if (scrollToIndex && ref.current) {\n      let scrollToItem = scrollToIndex[0];\n      scrollToItem = Math.max(0, Math.min(scrollToItem, tokens.length - 1));\n      setTimeout(\n        () => {\n          const line = findElementInCurrentTab(\n            `[data-path=\"${relativePath}\"] [data-line-number=\"${scrollToItem}\"]`,\n          );\n          line?.scrollIntoView({\n            behavior: 'smooth',\n            block: 'center',\n          });\n        },\n        scrollToItem < 300\n          ? 500\n          : scrollToItem < 500\n          ? 800\n          : scrollToItem < 1000\n          ? 1000\n          : 1500,\n      );\n    }\n  }, [scrollToIndex, tokens.length]);\n\n  let scrollMomentum = 1;\n\n  useEffect(() => {\n    const scrollingFunc = () => {\n      scrollMomentum++;\n      if (scrollContainerRef.current && shouldScroll) {\n        const scollMomentumSmooth = Math.floor(scrollMomentum / 4);\n        scrollContainerRef.current.scroll({\n          top:\n            scrollContainerRef.current?.scrollTop +\n            (shouldScroll === 'top'\n              ? -CODE_LINE_HEIGHT * scollMomentumSmooth\n              : CODE_LINE_HEIGHT * scollMomentumSmooth),\n        });\n      }\n    };\n    let intervalId: number;\n    if (shouldScroll) {\n      intervalId = window.setInterval(scrollingFunc, 50);\n    }\n    return () => {\n      clearInterval(intervalId);\n    };\n  }, [shouldScroll]);\n\n  useEffect(() => {\n    const handleMouseMove = (e: MouseEvent) => {\n      if (scrollContainerRef.current) {\n        const containerBox = scrollContainerRef.current.getBoundingClientRect();\n        const isAtContainerTop =\n          e.clientY - CODE_LINE_HEIGHT <= containerBox.top;\n        const isAtContainerBottom =\n          e.clientY + CODE_LINE_HEIGHT >=\n          containerBox.top + containerBox.height;\n        if (isAtContainerTop || isAtContainerBottom) {\n          setShouldScroll(isAtContainerTop ? 'top' : 'bottom');\n        } else {\n          setShouldScroll(false);\n        }\n      }\n    };\n    if (!!currentlySelectingRange) {\n      document.body.addEventListener('mousemove', handleMouseMove);\n    } else {\n      setShouldScroll(false);\n    }\n    return () => {\n      document.body.removeEventListener('mousemove', handleMouseMove);\n    };\n  }, [!!currentlySelectingRange]);\n\n  const handleAddRange = useCallback(() => {\n    setCurrentlySelectingRange((prev) => {\n      if (prev) {\n        onNewRange(prev);\n      }\n      return null;\n    });\n  }, []);\n\n  const onNewRange = useCallback((range: [number, number]) => {\n    setCurrentSelection((prev) => {\n      const newRanges = JSON.parse(JSON.stringify(prev));\n      return mergeRanges([...newRanges, range]);\n    });\n  }, []);\n\n  const updateRange = useCallback((i: number, newRange: [number, number]) => {\n    setCurrentSelection((prev) => {\n      const newRanges = JSON.parse(JSON.stringify(prev));\n      newRanges[i] = newRange;\n      return mergeRanges(newRanges);\n    });\n  }, []);\n\n  const deleteRange = useCallback((i: number) => {\n    setCurrentSelection((prev) => {\n      const newRanges = JSON.parse(JSON.stringify(prev));\n      newRanges.splice(i, 1);\n      return mergeRanges(newRanges);\n    });\n  }, []);\n\n  const invertRanges = useCallback(() => {\n    setCurrentSelection((prev) => {\n      const totalLines = tokens.length; // assuming tokens.length gives the total number of lines\n      let newRanges: [number, number][] = [];\n      let lastEnd = 0;\n\n      // Sort the ranges by their start index\n      const sortedRanges = [...prev].sort((a, b) => a[0] - b[0]);\n\n      sortedRanges.forEach((range) => {\n        // If there is a gap between the last range and this one, add it to newRanges\n        if (range[0] > lastEnd) {\n          newRanges.push([lastEnd, range[0] - 1]);\n        }\n        // Update lastEnd to be the end of this range\n        lastEnd = range[1] + 1;\n      });\n\n      // If there is a gap between the last range and the end of the code, add it to newRanges\n      if (lastEnd < totalLines) {\n        newRanges.push([lastEnd, totalLines - 1]);\n      }\n\n      return newRanges;\n    });\n  }, [tokens.length]);\n\n  const handleKeyEvent = useCallback(\n    (e: KeyboardEvent) => {\n      if (\n        !isFocusInInput() &&\n        !e.shiftKey &&\n        !e.ctrlKey &&\n        !e.metaKey &&\n        !e.altKey &&\n        currentSelection.length > 1\n      ) {\n        if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {\n          e.preventDefault();\n          setCurrentFocusedRange((prev) => {\n            const newIndex =\n              e.key === 'ArrowDown'\n                ? prev >= currentSelection.length - 1\n                  ? 0\n                  : prev + 1\n                : prev <= 0\n                ? currentSelection.length - 1\n                : prev - 1;\n            const lineNum = currentSelection[newIndex][0];\n            if (lineNum > -1) {\n              const line = findElementInCurrentTab(\n                `[data-line-number=\"${lineNum}\"]`,\n              );\n              line?.scrollIntoView({\n                behavior: 'smooth',\n                block: 'start',\n              });\n            }\n            return newIndex;\n          });\n        }\n      }\n    },\n    [currentSelection],\n  );\n  useKeyboardNavigation(\n    handleKeyEvent,\n    focusedPanel !== side || isLeftSidebarFocused || isCommandBarVisible,\n  );\n\n  return (\n    <div ref={ref} className=\"relative pb-16 overflow-auto\">\n      {currentSelection.map((r, i) => (\n        <Fragment key={i}>\n          {isEditingRanges && (\n            <SelectionHandler\n              initialRange={r}\n              updateRange={updateRange}\n              setCurrentlySelectingRange={setCurrentlySelectingRange}\n              deleteRange={deleteRange}\n              i={i}\n              setModifyingRange={setModifyingRange}\n              fileLinesNum={tokens.length}\n            />\n          )}\n          <SelectionRect\n            range={r}\n            i={i}\n            deleteRange={deleteRange}\n            invertRanges={invertRanges}\n            isEditingRanges={isEditingRanges}\n          />\n        </Fragment>\n      ))}\n      <div className=\"overflow-x-auto relative\">\n        {!!currentlySelectingRange && (\n          <SelectionRect range={currentlySelectingRange} isTemporary />\n        )}\n        <LinesContainer\n          items={tokens}\n          pathHash={pathHash}\n          handleAddRange={handleAddRange}\n          searchTerm={searchTerm}\n          setCurrentlySelectingRange={setCurrentlySelectingRange}\n          modifyingRange={modifyingRange}\n          scrollToIndex={scrollToIndex}\n          getHoverableContent={getHoverableContent}\n          hoverableRanges={hoverableRanges}\n          highlights={highlights}\n          hoveredLines={hoveredLines}\n          isDiff={isDiff}\n          isEditingRanges={isEditingRanges}\n        />\n      </div>\n    </div>\n  );\n};\n\nexport default memo(CodeContainerSelectable);\n"
  },
  {
    "path": "client/src/components/Code/CodeFullSelectable/LazyLinesContainer.tsx",
    "content": "import React, {\n  Dispatch,\n  memo,\n  SetStateAction,\n  useEffect,\n  useState,\n} from 'react';\nimport { Token as TokenType } from '../../../types/prism';\nimport CodeLine from '../CodeLine';\nimport { Range } from '../../../types/results';\nimport { useDiffLines } from '../../../hooks/useDiffLines';\nimport Token from '../CodeFull/Token';\n\ntype Props = {\n  items: TokenType[][];\n  pathHash: string | number;\n  searchTerm: string;\n  modifyingRange: number;\n  handleAddRange: () => void;\n  setCurrentlySelectingRange: Dispatch<SetStateAction<null | [number, number]>>;\n  scrollToIndex?: number[];\n  hoverableRanges?: Record<number, Range[]>;\n  getHoverableContent: (\n    hoverableRange: Range,\n    tokenRange: Range,\n    lineNumber: number,\n  ) => void;\n  highlights?: (\n    | { lines: [number, number]; color: string; index: number }\n    | undefined\n  )[];\n  hoveredLines?: [number, number] | null;\n  isDiff?: boolean;\n  isEditingRanges?: boolean;\n};\n\nconst LazyLinesContainer = ({\n  items,\n  pathHash,\n  searchTerm,\n  modifyingRange,\n  handleAddRange,\n  setCurrentlySelectingRange,\n  scrollToIndex,\n  hoverableRanges,\n  hoveredLines,\n  getHoverableContent,\n  highlights,\n  isDiff,\n  isEditingRanges,\n}: Props) => {\n  const [renderedItems, setRenderedItems] = useState<TokenType[][]>(\n    items.length > 300 ? [] : items,\n  );\n  const [showAllItems, setShowAllItems] = useState(false);\n  const { lineNumbersAdd, lineNumbersRemove } = useDiffLines(items, !isDiff);\n\n  useEffect(() => {\n    let animationFrameId: number;\n    if (items.length > 300) {\n      // Simulate a delay before showing all items\n      const delay = 500; // Adjust the delay as needed\n      let startTime: number;\n\n      const animateItems = (timestamp: number) => {\n        if (!startTime) {\n          startTime = timestamp;\n        }\n\n        const progress = timestamp - startTime;\n        const itemsToShow = Math.min(\n          Math.floor((progress / delay) * items.length),\n          items.length,\n        );\n\n        setRenderedItems(items.slice(0, itemsToShow));\n\n        if (itemsToShow < items.length) {\n          requestAnimationFrame(animateItems);\n        } else {\n          setShowAllItems(true);\n        }\n      };\n\n      animationFrameId = requestAnimationFrame(animateItems);\n    }\n    return () => {\n      if (animationFrameId) {\n        cancelAnimationFrame(animationFrameId);\n      }\n    };\n  }, [items]);\n\n  return (\n    <>\n      {renderedItems.map((line, index) => {\n        let highlightForLine = highlights?.findIndex(\n          (h) => h && index >= h.lines[0] && index <= h.lines[1],\n        );\n        if (highlightForLine && highlightForLine < 0) {\n          highlightForLine = undefined;\n        }\n        return (\n          <CodeLine\n            key={pathHash + '-' + index.toString()}\n            lineNumber={index}\n            handleAddRange={handleAddRange}\n            searchTerm={searchTerm}\n            setCurrentlySelectingRange={setCurrentlySelectingRange}\n            isSelectionDisabled={modifyingRange > -1}\n            fileLinesNum={items.length}\n            showLineNumbers\n            hoverEffect\n            shouldHighlight={\n              (!!scrollToIndex &&\n                index >= scrollToIndex[0] &&\n                index <= scrollToIndex[1]) ||\n              (highlights && highlightForLine !== undefined)\n            }\n            highlightColor={\n              highlights && highlightForLine !== undefined\n                ? highlights[highlightForLine]?.color\n                : undefined\n            }\n            hoveredBackground={\n              !!hoveredLines &&\n              index >= hoveredLines[0] &&\n              index <= hoveredLines[1]\n            }\n            isNewLine={\n              isDiff && (line[0]?.content === '+' || line[1]?.content === '+')\n            }\n            isRemovedLine={\n              isDiff && (line[0]?.content === '-' || line[1]?.content === '-')\n            }\n            lineNumbersDiff={\n              isDiff ? [lineNumbersRemove[index], lineNumbersAdd[index]] : null\n            }\n            isEditingRanges={isEditingRanges}\n          >\n            {line.map((token, i) => (\n              <Token\n                key={`cell-${index}-${i}`}\n                lineHoverRanges={hoverableRanges?.[index]}\n                token={token}\n                getHoverableContent={getHoverableContent}\n                lineNumber={index}\n                isEditingRanges={isEditingRanges}\n              />\n            ))}\n          </CodeLine>\n        );\n      })}\n    </>\n  );\n};\n\nexport default memo(LazyLinesContainer);\n"
  },
  {
    "path": "client/src/components/Code/CodeFullSelectable/SelectionHandler.tsx",
    "content": "import React, {\n  useState,\n  useRef,\n  useEffect,\n  memo,\n  Dispatch,\n  SetStateAction,\n  useDeferredValue,\n} from 'react';\nimport { CODE_LINE_HEIGHT } from '../../../consts/code';\n\ntype Props = {\n  initialRange: [number, number];\n  updateRange: (i: number, range: [number, number]) => void;\n  i: number;\n  deleteRange: (i: number) => void;\n  setCurrentlySelectingRange: Dispatch<SetStateAction<null | [number, number]>>;\n  setModifyingRange: Dispatch<SetStateAction<number>>;\n  fileLinesNum: number;\n};\n\nconst SelectionHandler = ({\n  initialRange,\n  updateRange,\n  i,\n  deleteRange,\n  setCurrentlySelectingRange,\n  setModifyingRange,\n  fileLinesNum,\n}: Props) => {\n  const [isDraggingStart, setIsDraggingStart] = useState(false);\n  const [isDraggingEnd, setIsDraggingEnd] = useState(false);\n  const [range, setRange] = useState(initialRange);\n  const deferredRange = useDeferredValue(range);\n  const startHandlerRef = useRef<HTMLDivElement>(null);\n  const endHandlerRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    setRange(initialRange);\n  }, [initialRange]);\n\n  const handleMouseDownStart = (\n    e: React.MouseEvent<HTMLDivElement, MouseEvent>,\n  ) => {\n    e.preventDefault();\n    e.stopPropagation();\n    setIsDraggingStart(true);\n    setModifyingRange(i);\n  };\n  const handleMouseDownEnd = (\n    e: React.MouseEvent<HTMLDivElement, MouseEvent>,\n  ) => {\n    e.preventDefault();\n    e.stopPropagation();\n    setIsDraggingEnd(true);\n    setModifyingRange(i);\n  };\n\n  useEffect(() => {\n    if (isDraggingEnd || isDraggingStart) {\n      setCurrentlySelectingRange(deferredRange);\n    }\n  }, [deferredRange, isDraggingEnd, isDraggingStart]);\n\n  useEffect(() => {\n    const handleMouseMove = (e: MouseEvent) => {\n      if (isDraggingStart && startHandlerRef.current) {\n        const deltaY =\n          e.clientY - startHandlerRef.current.getBoundingClientRect().top;\n        const newRange: [number, number] = [\n          Math.max(\n            0,\n            Math.min(\n              Math.max(range[0] + Math.round(deltaY / CODE_LINE_HEIGHT), 0),\n              range[1] + 1,\n            ),\n          ),\n          range[1],\n        ];\n        setRange(newRange);\n        // setCurrentlySelectingRange(() => newRange);\n      }\n    };\n    const handleMouseUp = (e: MouseEvent) => {\n      e.preventDefault();\n      e.stopPropagation();\n      setIsDraggingStart(false);\n      if (range[0] > range[1]) {\n        deleteRange(i);\n      } else {\n        updateRange(i, range);\n      }\n      setCurrentlySelectingRange(() => null);\n      setModifyingRange(-1);\n    };\n    if (isDraggingStart) {\n      document.addEventListener('mousemove', handleMouseMove);\n      document.addEventListener('mouseup', handleMouseUp);\n    } else {\n      document.removeEventListener('mousemove', handleMouseMove);\n      document.removeEventListener('mouseup', handleMouseUp);\n    }\n    return () => {\n      document.removeEventListener('mousemove', handleMouseMove);\n      document.removeEventListener('mouseup', handleMouseUp);\n    };\n  }, [isDraggingStart, i, range]);\n\n  useEffect(() => {\n    const handleMouseMove = (e: MouseEvent) => {\n      if (isDraggingEnd && endHandlerRef.current) {\n        const deltaY =\n          e.clientY - endHandlerRef.current.getBoundingClientRect().top;\n        const newRange: [number, number] = [\n          range[0],\n          Math.min(\n            fileLinesNum - 1,\n            Math.max(\n              range[1] + Math.round(deltaY / CODE_LINE_HEIGHT),\n              range[0] - 1,\n            ),\n          ),\n        ];\n        setRange(newRange);\n        // setCurrentlySelectingRange(() => newRange);\n      }\n    };\n    const handleMouseUp = (e: MouseEvent) => {\n      e.preventDefault();\n      e.stopPropagation();\n      setIsDraggingEnd(false);\n      if (range[0] > range[1]) {\n        deleteRange(i);\n      } else {\n        updateRange(i, range);\n      }\n      setCurrentlySelectingRange(() => null);\n      setModifyingRange(-1);\n    };\n    if (isDraggingEnd) {\n      document.addEventListener('mousemove', handleMouseMove);\n      document.addEventListener('mouseup', handleMouseUp);\n    } else {\n      document.removeEventListener('mousemove', handleMouseMove);\n      document.removeEventListener('mouseup', handleMouseUp);\n    }\n    return () => {\n      document.removeEventListener('mousemove', handleMouseMove);\n      document.removeEventListener('mouseup', handleMouseUp);\n    };\n  }, [isDraggingEnd, i, range, fileLinesNum]);\n\n  return (\n    <>\n      <div\n        ref={startHandlerRef}\n        className={`absolute -left-0.5 w-5 h-1 bg-bg-border-selected rounded-tl rounded-tr cursor-row-resize z-20`}\n        style={{\n          top: `${Math.max(range[0] * CODE_LINE_HEIGHT - 2, 0)}px`,\n        }}\n        onMouseDown={handleMouseDownStart}\n      />\n      <div\n        ref={endHandlerRef}\n        className={`absolute -left-0.5 w-5 h-1 bg-bg-border-selected rounded-bl rounded-br cursor-row-resize z-20`}\n        style={{\n          top: `${Math.min(\n            range[1] * CODE_LINE_HEIGHT + CODE_LINE_HEIGHT,\n            fileLinesNum * 20 - 4,\n          )}px`,\n        }}\n        onMouseDown={handleMouseDownEnd}\n      />\n    </>\n  );\n};\n\nexport default memo(SelectionHandler);\n"
  },
  {
    "path": "client/src/components/Code/CodeFullSelectable/SelectionHint.tsx",
    "content": "import React, { memo, useCallback } from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { FileIcon, RangeIcon } from '../../../icons';\nimport KeyboardHint from '../../KeyboardHint';\n\ntype Props = {\n  currentSelection: [number, number][];\n  setCurrentSelection: (s: [number, number][]) => void;\n};\n\nconst SelectionHint = ({ currentSelection, setCurrentSelection }: Props) => {\n  useTranslation();\n\n  const clearSelection = useCallback(() => {\n    setCurrentSelection([]);\n  }, []);\n\n  return (\n    <div className=\"absolute bottom-14 left-1/2 transform -translate-x-1/2 rounded-full flex h-8 items-center gap-2 p-2 pr-2.5 border border-bg-border bg-bg-base shadow-float body-mini text-label-title flex-shrink-0 w-fit z-20\">\n      {!currentSelection.length ? (\n        <FileIcon sizeClassName=\"w-4.5 h-4.5\" />\n      ) : (\n        <RangeIcon sizeClassName=\"w-4.5 h-4.5\" />\n      )}\n      <p className=\"pointer-events-none select-none cursor-default\">\n        {!currentSelection.length ? (\n          <Trans>The whole file will be used as context.</Trans>\n        ) : (\n          <Trans>Selected ranges will be used as context.</Trans>\n        )}\n      </p>\n      {currentSelection?.length > 1 && (\n        <div className=\"pointer-events-none select-none cursor-default flex gap-1 items-center\">\n          <Trans>\n            <KeyboardHint shortcut=\"↑\" />\n            <KeyboardHint shortcut=\"↓\" /> to navigate.\n          </Trans>\n        </div>\n      )}\n      {!!currentSelection.length && (\n        <>\n          <div className=\"w-px h-2.5 bg-bg-border flex-shrink-0\" />\n          <button\n            type=\"button\"\n            className=\"body-mini-b text-label-muted\"\n            onClick={clearSelection}\n          >\n            <Trans>Clear ranges</Trans>\n          </button>\n        </>\n      )}\n    </div>\n  );\n};\n\nexport default memo(SelectionHint);\n"
  },
  {
    "path": "client/src/components/Code/CodeFullSelectable/SelectionRect.tsx",
    "content": "import React, { memo, useMemo } from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { CODE_LINE_HEIGHT } from '../../../consts/code';\nimport Button from '../../Button';\n\ntype TemporaryRangeProps = {\n  range: [number, number];\n  isTemporary: true;\n  i?: never;\n  deleteRange?: never;\n  invertRanges?: never;\n  isEditingRanges?: boolean;\n};\n\ntype StaticRangeProps = {\n  range: [number, number];\n  i: number;\n  deleteRange: (i: number) => void;\n  invertRanges: () => void;\n  isTemporary?: false;\n  isEditingRanges?: boolean;\n};\n\ntype Props = TemporaryRangeProps | StaticRangeProps;\n\nconst SelectionRect = ({\n  range,\n  isTemporary,\n  deleteRange,\n  invertRanges,\n  i,\n  isEditingRanges,\n}: Props) => {\n  useTranslation();\n  const style = useMemo(() => {\n    return {\n      top: range[0] * CODE_LINE_HEIGHT,\n      height: (range[1] - range[0] + 1) * CODE_LINE_HEIGHT,\n    };\n  }, [range]);\n  return (\n    <div\n      className=\"absolute left-0 right-0 z-10 group pointer-events-none\"\n      style={style}\n    >\n      <div className=\"w-full h-full bg-bg-selected z-10\" />\n      {!isTemporary && isEditingRanges && (\n        <div className=\"absolute top-2 right-2 z-20 flex gap-1 items-center pointer-events-auto\">\n          <Button size=\"mini\" variant=\"secondary\" onClick={invertRanges}>\n            <Trans>Invert</Trans>\n          </Button>\n          <Button\n            size=\"mini\"\n            variant=\"secondary\"\n            onClick={() => deleteRange(i)}\n          >\n            <Trans>Clear range</Trans>\n          </Button>\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport default memo(SelectionRect);\n"
  },
  {
    "path": "client/src/components/Code/CodeFullSelectable/index.tsx",
    "content": "import React, {\n  Dispatch,\n  memo,\n  SetStateAction,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport { getPrismLanguage, tokenizeCode } from '../../../utils/prism';\nimport SearchOnPage from '../../SearchOnPage';\nimport { useCodeSearch } from '../../../hooks/useCodeSearch';\nimport { Range, TokenInfoWrapped } from '../../../types/results';\nimport { TabsContext } from '../../../context/tabsContext';\nimport { useOnClickOutside } from '../../../hooks/useOnClickOutsideHook';\nimport { getTokenInfo } from '../../../services/api';\nimport { mapTokenInfo } from '../../../mappers/results';\nimport { TabTypesEnum } from '../../../types/general';\nimport { findElementInCurrentTab } from '../../../utils/domUtils';\nimport { getSelectionLines } from '../../../utils';\nimport RefsDefsPopup from '../../RefsDefsPopup';\nimport SelectionPopup from '../CodeFull/SelectionPopup';\nimport CodeContainer from './CodeContainer';\nimport SelectionHint from './SelectionHint';\n\ntype Props = {\n  code: string;\n  language: string;\n  relativePath: string;\n  currentSelection: [number, number][];\n  setCurrentSelection: Dispatch<SetStateAction<[number, number][]>>;\n  isSearchDisabled?: boolean;\n  hoverableRanges?: Record<number, Range[]>;\n  repoRef: string;\n  isDiff?: boolean;\n  scrollToLine?: string;\n  branch?: string | null;\n  tokenRange?: string;\n  highlights?: (\n    | { lines: [number, number]; color: string; index: number }\n    | undefined\n  )[];\n  hoveredLines?: [number, number] | null;\n  side: 'left' | 'right';\n  isEditingRanges?: boolean;\n};\n\nconst CodeFullSelectable = ({\n  language,\n  code,\n  relativePath,\n  currentSelection,\n  setCurrentSelection,\n  isSearchDisabled,\n  isEditingRanges,\n  hoverableRanges,\n  repoRef,\n  branch,\n  scrollToLine,\n  tokenRange,\n  hoveredLines,\n  side,\n  isDiff,\n  highlights,\n}: Props) => {\n  const { openNewTab } = useContext(TabsContext.Handlers);\n  const [tokenInfo, setTokenInfo] = useState<TokenInfoWrapped>({\n    data: { references: [], definitions: [] },\n    hoverableRange: null,\n    tokenRange: null,\n    isLoading: false,\n    lineNumber: -1,\n  });\n  const popupRef = useRef<HTMLDivElement>(null);\n  const codeRef = useRef<HTMLPreElement>(null);\n  const [isPopupVisible, setPopupVisible] = useState(false);\n  const [selectionPopupData, setSelectionPopupData] = useState<{\n    selectedLines: [number, number];\n  } | null>(null);\n  const [popupPosition, setPopupPosition] = useState<{\n    top: number;\n    left?: number;\n    right?: number;\n  } | null>(null);\n  useOnClickOutside(popupRef, () => setPopupVisible(false));\n\n  const scrollLineNumber = useMemo(\n    () =>\n      scrollToLine?.split('_').map((s) => Number(s)) as\n        | [number, number]\n        | undefined,\n    [scrollToLine],\n  );\n  const [scrollToIndex, setScrollToIndex] = useState(\n    scrollLineNumber || undefined,\n  );\n  useEffect(() => {\n    setScrollToIndex(scrollLineNumber || undefined);\n  }, [scrollLineNumber]);\n\n  const {\n    handleSearchCancel,\n    isSearchActive,\n    setSearchTerm,\n    searchTerm,\n    searchResults,\n    setCurrentResult,\n    currentResult,\n    deferredSearchTerm,\n  } = useCodeSearch({\n    code,\n    setScrollToIndex,\n    isDisabled: isSearchDisabled,\n  });\n\n  const lang = useMemo(\n    () => getPrismLanguage(language) || 'plaintext',\n    [language],\n  );\n  const tokens = useMemo(() => tokenizeCode(code, lang), [code, lang]);\n\n  const getHoverableContent = useCallback(\n    (hoverableRange: Range, tokenRange: Range, lineNumber?: number) => {\n      if (hoverableRange && relativePath) {\n        setTokenInfo({\n          data: { references: [], definitions: [] },\n          hoverableRange,\n          tokenRange,\n          lineNumber,\n          isLoading: true,\n        });\n        getTokenInfo(\n          relativePath,\n          repoRef,\n          hoverableRange.start,\n          hoverableRange.end,\n          branch ? branch : undefined,\n        )\n          .then((data) => {\n            setTokenInfo({\n              data: mapTokenInfo(data.data, relativePath),\n              hoverableRange,\n              tokenRange,\n              lineNumber,\n              isLoading: false,\n            });\n          })\n          .catch(() => {\n            setTokenInfo({\n              data: { references: [], definitions: [] },\n              hoverableRange,\n              tokenRange,\n              lineNumber,\n              isLoading: false,\n            });\n          });\n      }\n    },\n    [relativePath, branch],\n  );\n\n  useEffect(() => {\n    if (tokenRange) {\n      const [start, end] = tokenRange.split('_').map((l) => Number(l));\n      getHoverableContent({ start, end }, { start, end });\n    }\n  }, [tokenRange, getHoverableContent]);\n\n  const handleRefsDefsClick = useCallback(\n    (lineNum: number, filePath: string, tokenRange: string) => {\n      setTokenInfo({\n        data: { references: [], definitions: [] },\n        hoverableRange: null,\n        tokenRange: null,\n        isLoading: false,\n        lineNumber: -1,\n      });\n      openNewTab({\n        type: TabTypesEnum.FILE,\n        path: filePath,\n        repoRef,\n        branch,\n        scrollToLine: `${lineNum}_${lineNum}`,\n        tokenRange,\n      });\n    },\n    [openNewTab, repoRef, branch],\n  );\n  useEffect(() => {\n    if (tokenInfo.tokenRange) {\n      let tokenElem = findElementInCurrentTab(\n        `[data-byte-range=\"${tokenInfo.tokenRange.start}-${tokenInfo.tokenRange.end}\"]`,\n      );\n      if (tokenElem && tokenElem instanceof HTMLElement) {\n        const box = tokenElem.getBoundingClientRect();\n        setPopupPosition({\n          top: box.top - 72 + 10,\n          ...(tokenElem.offsetLeft > tokenElem.offsetParent!.scrollWidth / 2\n            ? {\n                right:\n                  tokenElem.offsetParent!.clientWidth -\n                  (tokenElem.offsetLeft + tokenElem.offsetWidth),\n              }\n            : { left: tokenElem.offsetLeft + 12 }),\n        });\n        setPopupVisible(true);\n      }\n    } else {\n      setPopupPosition(null);\n    }\n  }, [tokenInfo]);\n\n  useEffect(() => {\n    setPopupPosition(null);\n  }, []);\n\n  useEffect(() => {\n    let startLine: number | null;\n    const handleWindowMouseDown = (e: MouseEvent) => {\n      if (e.target) {\n        startLine = getSelectionLines(e.target as HTMLElement);\n      }\n    };\n    const handleWindowMouseUp = (e: MouseEvent) => {\n      setTimeout(() => {\n        const selection = window.getSelection();\n        if (selection?.toString()) {\n          const endLine = getSelectionLines(e.target as HTMLElement);\n          if (endLine !== null && startLine !== null) {\n            setSelectionPopupData({\n              selectedLines: [startLine, endLine].sort((a, b) => a - b) as [\n                number,\n                number,\n              ],\n            });\n          }\n        } else {\n          setSelectionPopupData(null);\n        }\n      }, 50);\n    };\n\n    const handleScroll = () => {\n      setSelectionPopupData(null);\n    };\n\n    codeRef.current?.addEventListener('mousedown', handleWindowMouseDown);\n    codeRef.current?.addEventListener('mouseup', handleWindowMouseUp);\n    codeRef.current?.parentElement?.parentElement?.addEventListener(\n      'scroll',\n      handleScroll,\n    );\n\n    return () => {\n      codeRef.current?.removeEventListener('mousedown', handleWindowMouseDown);\n      codeRef.current?.removeEventListener('mouseup', handleWindowMouseUp);\n      codeRef.current?.parentElement?.parentElement?.removeEventListener(\n        'scroll',\n        handleScroll,\n      );\n    };\n  }, []);\n\n  const closeSelectionPopup = useCallback(() => {\n    setSelectionPopupData(null);\n  }, []);\n\n  return (\n    <div className=\"overflow-auto\">\n      <SearchOnPage\n        handleSearch={setSearchTerm}\n        isSearchActive={isSearchActive}\n        resultNum={searchResults.length}\n        onCancel={handleSearchCancel}\n        currentResult={currentResult}\n        setCurrentResult={setCurrentResult}\n        searchValue={searchTerm}\n        containerClassName=\"absolute top-0 -right-4\"\n      />\n      <pre\n        className={`prism-code language-${lang} w-full h-full code-s overflow-auto`}\n        ref={codeRef}\n      >\n        <code>\n          <CodeContainer\n            key={relativePath}\n            relativePath={relativePath}\n            tokens={tokens}\n            setCurrentSelection={setCurrentSelection}\n            currentSelection={currentSelection}\n            searchTerm={deferredSearchTerm}\n            scrollToIndex={scrollToIndex}\n            scrollContainerRef={codeRef}\n            getHoverableContent={getHoverableContent}\n            hoverableRanges={hoverableRanges}\n            highlights={highlights}\n            hoveredLines={hoveredLines}\n            isDiff={isDiff}\n            isEditingRanges={isEditingRanges}\n            side={side}\n          />\n        </code>\n      </pre>\n      {!!popupPosition && isPopupVisible && !isEditingRanges && (\n        <div className=\"absolute max-w-sm\" style={popupPosition} ref={popupRef}>\n          <RefsDefsPopup\n            placement={popupPosition.right ? 'bottom-end' : 'bottom-start'}\n            data={tokenInfo}\n            onRefDefClick={handleRefsDefsClick}\n            language={language}\n            relativePath={relativePath}\n          />\n        </div>\n      )}\n      {!isEditingRanges && (\n        <SelectionPopup\n          closePopup={closeSelectionPopup}\n          path={relativePath}\n          repoRef={repoRef}\n          branch={branch}\n          side={side}\n          selectedLines={selectionPopupData?.selectedLines}\n        />\n      )}\n      {isEditingRanges && (\n        <SelectionHint\n          currentSelection={currentSelection}\n          setCurrentSelection={setCurrentSelection}\n        />\n      )}\n    </div>\n  );\n};\n\nexport default memo(CodeFullSelectable);\n"
  },
  {
    "path": "client/src/components/Code/CodeLine.tsx",
    "content": "import React, {\n  ReactNode,\n  useRef,\n  memo,\n  useMemo,\n  CSSProperties,\n  useEffect,\n  useState,\n  useCallback,\n} from 'react';\nimport { markNode, unmark } from '../../utils/textSearch';\nimport { CODE_LINE_HEIGHT } from '../../consts/code';\n\ntype Props = {\n  lineNumber: number;\n  lineNumberToShow?: number | null;\n  lineNumbersDiff?: [number | null, number | null] | null;\n  children: ReactNode;\n  showLineNumbers?: boolean;\n  hoverEffect?: boolean;\n  isNewLine?: boolean;\n  isRemovedLine?: boolean;\n  shouldHighlight?: boolean;\n  hoveredBackground?: boolean;\n  highlightColor?: string | null;\n  style?: CSSProperties;\n  searchTerm?: string;\n  isSelectionDisabled?: boolean;\n  setCurrentlySelectingRange?: (range: [number, number] | null) => void;\n  handleAddRange?: () => void;\n  fileLinesNum?: number;\n  isEditingRanges?: boolean;\n};\n\nconst CodeLine = ({\n  lineNumber,\n  showLineNumbers,\n  children,\n  hoverEffect,\n  isNewLine,\n  isRemovedLine,\n  lineNumberToShow = lineNumber + 1,\n  lineNumbersDiff,\n  shouldHighlight,\n  highlightColor,\n  hoveredBackground,\n  style,\n  searchTerm,\n  isSelectionDisabled,\n  setCurrentlySelectingRange,\n  handleAddRange,\n  fileLinesNum,\n  isEditingRanges,\n}: Props) => {\n  const [isDragging, setIsDragging] = useState(false);\n  const codeRef = useRef<HTMLTableCellElement>(null);\n  const ref = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    if (codeRef.current && searchTerm) {\n      markNode(\n        codeRef.current,\n        new RegExp(\n          searchTerm.replace(/[-[\\]{}()*+?.,\\\\^$|#\\s]/g, '\\\\$&'),\n          'gi',\n        ),\n      );\n    }\n    return () => {\n      if (codeRef.current) {\n        unmark(codeRef.current);\n      }\n    };\n  }, [searchTerm]);\n\n  const styleCombined = useMemo(\n    () => ({\n      ...style,\n      borderLeft: `3px solid ${\n        shouldHighlight ? highlightColor || 'rgb(var(--yellow))' : 'transparent'\n      }`,\n    }),\n    [shouldHighlight, highlightColor, style],\n  );\n\n  useEffect(() => {\n    const handleMouseMove = (e: MouseEvent) => {\n      if (isDragging && ref.current) {\n        const deltaY = e.clientY - ref.current.getBoundingClientRect().top;\n        setCurrentlySelectingRange?.([\n          Math.max(\n            Math.min(\n              lineNumber,\n              lineNumber + Math.ceil(deltaY / CODE_LINE_HEIGHT) - 1,\n            ),\n            0,\n          ),\n          Math.min(\n            Math.max(\n              lineNumber,\n              lineNumber + Math.ceil(deltaY / CODE_LINE_HEIGHT) - 1,\n            ),\n            (fileLinesNum || 0) - 1,\n          ),\n        ]);\n      }\n    };\n    const handleMouseUp = (e: MouseEvent) => {\n      e.preventDefault();\n      e.stopPropagation();\n      setIsDragging(false);\n      handleAddRange?.();\n    };\n    if (isDragging) {\n      document.addEventListener('mousemove', handleMouseMove);\n      document.addEventListener('mouseup', handleMouseUp);\n    } else {\n      document.removeEventListener('mousemove', handleMouseMove);\n      document.removeEventListener('mouseup', handleMouseUp);\n    }\n    return () => {\n      document.removeEventListener('mousemove', handleMouseMove);\n      document.removeEventListener('mouseup', handleMouseUp);\n    };\n  }, [isDragging, lineNumber, fileLinesNum]);\n\n  const handleMouseDown = useCallback(\n    (e: React.MouseEvent) => {\n      if (isEditingRanges) {\n        e.preventDefault();\n        if (!isSelectionDisabled) {\n          setIsDragging(true);\n        }\n      }\n    },\n    [isSelectionDisabled, isEditingRanges],\n  );\n\n  return (\n    <div\n      className={`flex w-full flex-1 transition-all duration-150 ease-in-bounce group ${\n        isNewLine ? 'bg-green-subtle' : isRemovedLine ? 'bg-red-subtle' : ''\n      } ${hoveredBackground ? 'bg-bg-base-hover' : ''} ${\n        isEditingRanges\n          ? isSelectionDisabled\n            ? 'cursor-row-resize'\n            : 'cursor-ns-resize'\n          : ''\n      }`}\n      data-line-number={lineNumber}\n      style={styleCombined}\n      onMouseDown={handleMouseDown}\n      ref={ref}\n    >\n      {showLineNumbers &&\n        (lineNumbersDiff ? (\n          lineNumbersDiff.map((ln, i) => (\n            <div\n              key={i}\n              data-line={ln}\n              className={`min-w-[27px] text-right select-none pr-0 leading-5 ${\n                hoverEffect ? 'group-hover:text-label-base' : ''\n              } before:content-[attr(data-line)] ${\n                isRemovedLine\n                  ? 'text-label-base'\n                  : isNewLine\n                  ? 'text-label-base'\n                  : 'text-label-muted'\n              }`}\n            />\n          ))\n        ) : (\n          <div\n            data-line={lineNumberToShow}\n            className={`min-w-[27px] text-right select-none pr-0 leading-5 ${\n              hoverEffect ? 'group-hover:text-label-base' : ''\n            }\n           ${!lineNumberToShow ? '' : 'before:content-[attr(data-line)]'} ${\n             isRemovedLine\n               ? 'text-label-base'\n               : isNewLine\n               ? 'text-label-base'\n               : 'text-label-muted'\n           }`}\n          />\n        ))}\n      <div className={`${showLineNumbers ? 'pl-2' : ''} flex-1`} ref={codeRef}>\n        {children}\n      </div>\n    </div>\n  );\n};\n\nexport default memo(CodeLine);\n"
  },
  {
    "path": "client/src/components/Code/CodeToken.tsx",
    "content": "import { memo } from 'react';\nimport { Token } from '../../types/prism';\nimport { Range } from '../../types/results';\n\ntype Props = {\n  token: Token;\n  highlights?: Range[];\n  highlight?: boolean;\n  startHl?: boolean;\n  endHl?: boolean;\n  isHoverable?: boolean;\n  onClick?: () => void;\n};\n\nconst CodeToken = ({\n  token,\n  highlight,\n  startHl,\n  endHl,\n  onClick,\n  isHoverable,\n}: Props) => {\n  return (\n    <span\n      data-byte-range={`${token.byteRange?.start}-${token.byteRange?.end}`}\n      className={`token ${isHoverable ? 'cursor-pointer' : ''} ${token.types\n        .filter((t) => t !== 'table')\n        .join(' ')}`}\n      onClick={onClick}\n    >\n      <span\n        className={`${highlight ? `bg-yellow/16 py-0.5` : ''} ${\n          startHl ? 'rounded-l pl-[2px]' : ''\n        } ${endHl ? 'rounded-r pr-[2px]' : ''}`}\n      >\n        {token.content}\n      </span>\n    </span>\n  );\n};\n\nexport default memo(CodeToken);\n"
  },
  {
    "path": "client/src/components/Dropdown/Section/SectionItem.tsx",
    "content": "import React, {\n  memo,\n  ReactElement,\n  useCallback,\n  MouseEvent,\n  useRef,\n  useEffect,\n  useContext,\n} from 'react';\nimport { CheckIcon } from '../../../icons';\nimport { checkEventKeys } from '../../../utils/keyboardUtils';\nimport useShortcuts from '../../../hooks/useShortcuts';\nimport useKeyboardNavigation from '../../../hooks/useKeyboardNavigation';\nimport { ArrowNavigationContext } from '../../../context/arrowNavigationContext';\n\ntype Props = {\n  icon?: ReactElement;\n  customRightElement?: ReactElement;\n  label: string | ReactElement;\n  description?: string | ReactElement;\n  shortcut?: string[];\n  isSelected?: boolean;\n  onClick?: (e?: MouseEvent) => void;\n  color?: 'shade' | 'base';\n  index: string;\n  clickCallback?: () => void;\n};\n\nconst SectionItem = ({\n  icon,\n  label,\n  description,\n  shortcut,\n  onClick,\n  isSelected,\n  color = 'shade',\n  customRightElement,\n  index,\n}: Props) => {\n  const shortcutKeys = useShortcuts(shortcut);\n  const ref = useRef<HTMLAnchorElement>(null);\n  const { setFocusedIndex, focusedIndex, handleClose } = useContext(\n    ArrowNavigationContext,\n  );\n\n  useEffect(() => {\n    if (focusedIndex === index) {\n      ref.current?.scrollIntoView({ block: 'nearest' });\n    }\n  }, [focusedIndex === index]);\n\n  const handleKeyEvent = useCallback(\n    (e: KeyboardEvent) => {\n      if (onClick) {\n        if (checkEventKeys(e, shortcut) || checkEventKeys(e, ['entr'])) {\n          e.preventDefault();\n          e.stopPropagation();\n          onClick();\n          handleClose();\n          return;\n        }\n      }\n    },\n    [shortcut, handleClose, onClick],\n  );\n  useKeyboardNavigation(handleKeyEvent, !onClick || focusedIndex !== index);\n\n  const handleMouseMove = useCallback(\n    (e: React.MouseEvent) => {\n      if (e.movementX || e.movementY) {\n        setFocusedIndex(index);\n      }\n    },\n    [index, setFocusedIndex],\n  );\n\n  return (\n    <a\n      href=\"#\"\n      onClick={onClick}\n      ref={ref}\n      className={`w-full text-left rounded-6 ${\n        description ? 'py-2' : 'h-8'\n      } px-2 overflow-hidden ${\n        focusedIndex === index\n          ? `text-label-title ${\n              color === 'shade' ? 'bg-bg-shade-hover' : 'bg-bg-base-hover'\n            }`\n          : `text-label-muted ${\n              color === 'shade' ? 'bg-bg-shade' : 'bg-bg-base'\n            }`\n      } focus:outline-0 focus:outline-none`}\n      data-node-index={index}\n      onMouseMove={handleMouseMove}\n    >\n      <span className=\"flex items-center gap-2 h-full overflow-hidden\">\n        {icon}\n        <span\n          className=\"flex-1 flex flex-col gap-1 ellipsis\"\n          title={typeof label === 'string' ? label : undefined}\n        >\n          <span className=\"body-s text-label-title ellipsis\">{label}</span>\n          {!!description && (\n            <span className=\"body-mini text-label-base\">{description}</span>\n          )}\n        </span>\n        {!!shortcutKeys && (\n          <span className=\"body-mini-b text-label-muted\">\n            {shortcutKeys?.join(' ')}\n          </span>\n        )}\n        {isSelected && (\n          <CheckIcon\n            sizeClassName=\"w-4 h-4\"\n            className=\"text-bg-border-selected\"\n          />\n        )}\n        {customRightElement}\n      </span>\n    </a>\n  );\n};\n\nexport default memo(SectionItem);\n"
  },
  {
    "path": "client/src/components/Dropdown/Section/SectionLabel.tsx",
    "content": "import { memo } from 'react';\n\ntype Props = { text: string };\n\nconst SectionLabel = ({ text }: Props) => {\n  return (\n    <div className=\"flex h-8 px-2 gap-2 items-center rounded\">\n      <p className=\"text-label-base body-mini-b ellipsis\">{text}</p>\n    </div>\n  );\n};\n\nexport default memo(SectionLabel);\n"
  },
  {
    "path": "client/src/components/Dropdown/Section/index.tsx",
    "content": "import { memo, PropsWithChildren } from 'react';\n\ntype Props = {\n  borderBottom?: boolean;\n};\n\nconst DropdownSection = ({\n  children,\n  borderBottom,\n}: PropsWithChildren<Props>) => {\n  return (\n    <div\n      className={`flex flex-col p-1 items-start overflow-hidden ${\n        borderBottom ? 'border-b border-bg-border' : ''\n      }`}\n    >\n      {children}\n    </div>\n  );\n};\n\nexport default memo(DropdownSection);\n"
  },
  {
    "path": "client/src/components/Dropdown/index.tsx",
    "content": "import React, {\n  memo,\n  PropsWithChildren,\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport Tippy, { TippyProps } from '@tippyjs/react/headless';\nimport { Placement } from 'tippy.js';\nimport { useOnClickOutside } from '../../hooks/useOnClickOutsideHook';\nimport useKeyboardNavigation from '../../hooks/useKeyboardNavigation';\nimport { useArrowNavigation } from '../../hooks/useArrowNavigation';\nimport { ArrowNavigationContext } from '../../context/arrowNavigationContext';\n\ntype Props = {\n  dropdownPlacement?: TippyProps['placement'];\n  appendTo?: TippyProps['appendTo'];\n  size?: 'small' | 'medium' | 'large' | 'auto';\n  DropdownComponent: (props: any) => JSX.Element | null;\n  dropdownComponentProps?: Record<string, any>;\n  containerClassName?: string;\n  onVisibilityChange?: (isVisible: boolean) => void;\n  color?: 'shade' | 'base';\n};\n\nconst sizesMap = {\n  small: 'w-52',\n  medium: 'w-72',\n  large: 'w-100',\n  auto: 'min-w-sm',\n};\n\nconst transformOriginMap = {\n  bottom: 'origin-top',\n  'bottom-start': 'origin-top-left',\n  'bottom-end': 'origin-top-right',\n  auto: 'origin-center',\n  'auto-start': 'origin-top-left',\n  'auto-end': 'origin-top-right',\n  top: 'origin-bottom',\n  'top-start': 'origin-bottom-left',\n  'top-end': 'origin-bottom-right',\n  right: 'origin-left',\n  'right-start': 'origin-top-left',\n  'right-end': 'origin-bottom-left',\n  left: 'origin-right',\n  'left-start': 'origin-top-right',\n  'left-end': 'origin-bottom-right',\n};\n\nexport const animationDuration: [number, number] = [150, 200];\n\nconst Dropdown = ({\n  children,\n  dropdownPlacement = 'bottom-start',\n  appendTo = 'parent',\n  size = 'medium',\n  color = 'shade',\n  DropdownComponent,\n  dropdownComponentProps,\n  containerClassName,\n  onVisibilityChange,\n}: PropsWithChildren<Props>) => {\n  const [isVisible, setIsVisible] = useState(false);\n  const contextMenuRef = useRef(null);\n  const { focusedIndex, setFocusedIndex, handleArrowKey, navContainerRef } =\n    useArrowNavigation();\n  const buttonRef = useRef(null);\n\n  const handleClose = useCallback((e?: React.MouseEvent | MouseEvent) => {\n    e?.stopPropagation();\n    setIsVisible(false);\n  }, []);\n  useOnClickOutside(contextMenuRef, handleClose, buttonRef);\n\n  const handleToggle = useCallback(() => {\n    setIsVisible((prev) => !prev);\n  }, []);\n\n  useEffect(() => {\n    onVisibilityChange?.(isVisible);\n\n    return () => {\n      onVisibilityChange?.(false);\n    };\n  }, [isVisible]);\n\n  useKeyboardNavigation(handleArrowKey, !isVisible);\n\n  const contextValue = useMemo(\n    () => ({\n      focusedIndex,\n      setFocusedIndex,\n      handleClose,\n    }),\n    [focusedIndex, handleClose],\n  );\n\n  const handleKeyEvent = useCallback((e: KeyboardEvent) => {\n    if (e.key === 'Escape') {\n      e.preventDefault();\n      e.stopPropagation();\n      setIsVisible(false);\n    }\n  }, []);\n  useKeyboardNavigation(handleKeyEvent, !isVisible, true);\n\n  const renderContent = useCallback(\n    (attr: {\n      'data-placement': Placement;\n      'data-reference-hidden'?: string;\n      'data-escaped'?: string;\n    }) => {\n      return (\n        <div\n          id=\"dropdown\"\n          ref={appendTo !== 'parent' ? contextMenuRef : null}\n          className={`${isVisible ? '' : 'scale-0 opacity-0'} ${\n            transformOriginMap[attr['data-placement']]\n          } max-h-96 overflow-auto transition-transform duration-150\n       rounded-md border border-bg-border ${\n         color === 'base' ? 'bg-bg-base' : 'bg-bg-shade'\n       } shadow-high ${sizesMap[size]} select-none`}\n          onClick={handleClose}\n        >\n          {isVisible && (\n            <div ref={navContainerRef} className=\"flex flex-col gap-1\">\n              <ArrowNavigationContext.Provider value={contextValue}>\n                <DropdownComponent\n                  {...dropdownComponentProps}\n                  handleClose={handleClose}\n                />\n              </ArrowNavigationContext.Provider>\n            </div>\n          )}\n        </div>\n      );\n    },\n    [\n      DropdownComponent,\n      dropdownComponentProps,\n      isVisible,\n      dropdownPlacement,\n      contextValue,\n    ],\n  );\n\n  return (\n    <div\n      ref={appendTo === 'parent' ? contextMenuRef : null}\n      className={containerClassName}\n    >\n      <Tippy\n        visible={isVisible}\n        placement={dropdownPlacement}\n        interactive\n        appendTo={appendTo}\n        duration={animationDuration}\n        animation\n        render={renderContent}\n      >\n        <span>\n          <a\n            onClick={handleToggle}\n            className=\"flex cursor-pointer\"\n            ref={buttonRef}\n          >\n            {children}\n          </a>\n        </span>\n      </Tippy>\n    </div>\n  );\n};\n\nexport default memo(Dropdown);\n"
  },
  {
    "path": "client/src/components/FileIcon/index.tsx",
    "content": "// @ts-ignore\nimport * as icons from 'file-icons-js';\nimport { useMemo } from 'react';\ntype Props = { filename: string; noMargin?: boolean };\n\nconst FileIcon = ({ filename, noMargin }: Props) => {\n  const iconClassName = useMemo(() => {\n    try {\n      return (\n        icons.getClassWithColor(filename) || icons.getClassWithColor('.txt')\n      );\n    } catch (err) {\n      console.log(err);\n      return 'text-icon medium-blue';\n    }\n  }, [filename]);\n  return (\n    <span\n      className={`text-left w-4 h-4 ${\n        noMargin ? '' : 'mr-1'\n      } file-icon flex items-center flex-shrink-0 ${iconClassName} `}\n    />\n  );\n};\n\nexport default FileIcon;\n"
  },
  {
    "path": "client/src/components/Header/HeaderRightPart.tsx",
    "content": "import React, { memo, useCallback, useContext } from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport Button from '../Button';\nimport { KLetterIcon, PersonIcon } from '../../icons';\nimport Dropdown from '../Dropdown';\nimport { CommandBarContext } from '../../context/commandBarContext';\nimport useShortcuts from '../../hooks/useShortcuts';\nimport UserDropdown from './UserDropdown';\n\ntype Props = {};\n\nconst HeaderRightPart = ({}: Props) => {\n  useTranslation();\n  const { setIsVisible } = useContext(CommandBarContext.Handlers);\n\n  const openCommandBar = useCallback(() => {\n    setIsVisible(true);\n  }, []);\n\n  const shortcut = useShortcuts(['cmd', 'K']);\n\n  return (\n    <div className=\"flex pl-2 pr-4 items-center gap-2 h-full\">\n      <Button variant=\"tertiary\" size=\"mini\" onClick={openCommandBar}>\n        <KLetterIcon sizeClassName=\"w-3.5 h-3.5\" className=\"-translate-y-px\" />\n        <Trans>Actions</Trans>\n        <span className=\"text-label-faint\">{shortcut?.join(' ')}</span>\n      </Button>\n      <Dropdown DropdownComponent={UserDropdown} dropdownPlacement=\"bottom-end\">\n        <PersonIcon sizeClassName=\"w-3.5 h-3.5\" className=\"text-label-base\" />\n      </Dropdown>\n    </div>\n  );\n};\n\nexport default memo(HeaderRightPart);\n"
  },
  {
    "path": "client/src/components/Header/ProjectsDropdown.tsx",
    "content": "import { memo, useCallback, useContext } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport SectionLabel from '../Dropdown/Section/SectionLabel';\nimport SectionItem from '../Dropdown/Section/SectionItem';\nimport { CogIcon, PlusSignIcon, ShapesIcon } from '../../icons';\nimport { ProjectContext } from '../../context/projectContext';\nimport { CommandBarContext } from '../../context/commandBarContext';\nimport {\n  CommandBarStepEnum,\n  ProjectSettingSections,\n} from '../../types/general';\nimport { UIContext } from '../../context/uiContext';\nimport FileIcon from '../FileIcon';\nimport { getFileExtensionForLang } from '../../utils';\nimport { TabsContext } from '../../context/tabsContext';\nimport { newProjectShortcut } from '../../consts/shortcuts';\n\ntype Props = {};\n\nconst ProjectsDropdown = ({}: Props) => {\n  const { t } = useTranslation();\n  const { project, setCurrentProjectId } = useContext(ProjectContext.Current);\n  const { projects } = useContext(ProjectContext.All);\n  const { setChosenStep, setIsVisible } = useContext(\n    CommandBarContext.Handlers,\n  );\n  const { setProjectSettingsSection, setProjectSettingsOpen } = useContext(\n    UIContext.ProjectSettings,\n  );\n  const { setLeftTabs, setRightTabs, setActiveRightTab, setActiveLeftTab } =\n    useContext(TabsContext.Handlers);\n\n  const createNewProject = useCallback(() => {\n    setChosenStep({ id: CommandBarStepEnum.CREATE_PROJECT });\n    setIsVisible(true);\n  }, []);\n\n  const openProjectSettings = useCallback(() => {\n    setProjectSettingsOpen(true);\n    setProjectSettingsSection(ProjectSettingSections.GENERAL);\n  }, []);\n\n  const handleProjectSwitch = useCallback((projectId: string) => {\n    setCurrentProjectId(projectId);\n    setLeftTabs([]);\n    setRightTabs([]);\n    setActiveLeftTab(null);\n    setActiveRightTab(null);\n  }, []);\n\n  return (\n    <div className=\"\">\n      <div className=\"flex flex-col p-1 items-start border-b border-bg-border\">\n        <SectionLabel text={t('Projects')} />\n        {projects.map((p) => (\n          <SectionItem\n            key={p.id}\n            index={`proj-${p.id}`}\n            isSelected={p.id === project?.id}\n            onClick={() => handleProjectSwitch(p.id)}\n            label={p.name}\n            icon={\n              p.most_common_langs?.[0] ? (\n                <FileIcon\n                  filename={getFileExtensionForLang(p.most_common_langs[0])}\n                  noMargin\n                />\n              ) : (\n                <ShapesIcon sizeClassName=\"w-4 h-4\" />\n              )\n            }\n          />\n        ))}\n      </div>\n      <div className=\"flex flex-col p-1 items-start border-b border-bg-border\">\n        <SectionItem\n          onClick={openProjectSettings}\n          index={'proj-settings'}\n          label={t('Project settings')}\n          icon={<CogIcon sizeClassName=\"w-4 h-4\" />}\n          shortcut={['option', 'P']}\n        />\n      </div>\n      <div className=\"flex flex-col p-1 items-start\">\n        <SectionItem\n          onClick={createNewProject}\n          index={'new-proj'}\n          label={t('New project')}\n          icon={<PlusSignIcon sizeClassName=\"w-4 h-4\" />}\n          shortcut={newProjectShortcut}\n        />\n      </div>\n    </div>\n  );\n};\n\nexport default memo(ProjectsDropdown);\n"
  },
  {
    "path": "client/src/components/Header/UserDropdown.tsx",
    "content": "import React, {\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useState,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport SectionItem from '../Dropdown/Section/SectionItem';\nimport { CogIcon, DocumentsIcon } from '../../icons';\nimport { UIContext } from '../../context/uiContext';\nimport { DeviceContext } from '../../context/deviceContext';\nimport { SettingSections } from '../../types/general';\n\ntype Props = {};\nconst discordLink = 'https://discord.com/invite/kZEgj5pyjm';\n\nconst UserDropdown = ({}: Props) => {\n  const { t } = useTranslation();\n  const { openLink } = useContext(DeviceContext);\n  const { setSettingsOpen, setSettingsSection } = useContext(\n    UIContext.Settings,\n  );\n  const { setProjectSettingsOpen } = useContext(UIContext.ProjectSettings);\n\n  const openGeneralSettings = useCallback(() => {\n    setSettingsSection(SettingSections.GENERAL);\n    setProjectSettingsOpen(false);\n    setSettingsOpen(true);\n  }, []);\n\n  return (\n    <div className=\"\">\n      <div className=\"flex flex-col p-1 items-start border-y border-bg-border\">\n        <SectionItem\n          icon={<CogIcon raw sizeClassName=\"w-4 h-4\" />}\n          label={t('Settings')}\n          shortcut={['option', 'A']}\n          index={'settings'}\n          onClick={openGeneralSettings}\n        />\n        <SectionItem\n          icon={<DocumentsIcon raw sizeClassName=\"w-4 h-4\" />}\n          label={t('Docs')}\n          shortcut={['option', 'D']}\n          index={'docs'}\n          onClick={() => openLink('https://bloop.ai/docs')}\n        />\n      </div>\n      <div className=\"flex flex-col p-1 items-start\">\n        <SectionItem\n          label={t('Join Discord')}\n          index={'discord'}\n          onClick={() => openLink(discordLink)}\n        />\n        <SectionItem\n          label={t('Follow us on Twitter')}\n          index={'twitter'}\n          onClick={() => openLink('https://twitter.com/bloopdotai')}\n        />\n      </div>\n    </div>\n  );\n};\n\nexport default memo(UserDropdown);\n"
  },
  {
    "path": "client/src/components/Header/index.tsx",
    "content": "import React, { memo, useCallback, useContext } from 'react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { ArrowLeftIcon, ChevronDownIcon } from '../../icons';\nimport { DeviceContext } from '../../context/deviceContext';\nimport Button from '../Button';\nimport Dropdown from '../Dropdown';\nimport { ProjectContext } from '../../context/projectContext';\nimport { UIContext } from '../../context/uiContext';\nimport ProjectsDropdown from './ProjectsDropdown';\nimport HeaderRightPart from './HeaderRightPart';\n\ntype Props = {\n  type?: 'default' | 'settings' | 'project-settings';\n};\n\nconst Header = ({ type = 'default' }: Props) => {\n  const { t } = useTranslation();\n  const { os } = useContext(DeviceContext);\n  const { project } = useContext(ProjectContext.Current);\n  const { setSettingsOpen } = useContext(UIContext.Settings);\n  const { setProjectSettingsOpen } = useContext(UIContext.ProjectSettings);\n\n  const closeSettings = useCallback(() => {\n    setSettingsOpen(false);\n    setProjectSettingsOpen(false);\n  }, []);\n\n  return (\n    <div\n      className=\"w-screen h-10 flex items-center justify-between border-b border-bg-border bg-bg-base select-none\"\n      data-tauri-drag-region\n    >\n      <div className=\"flex h-full\">\n        {os.type === 'Darwin' && window.innerHeight !== screen.height ? (\n          <span className=\"w-16\" />\n        ) : (\n          ''\n        )}\n        {type === 'settings' ? (\n          <div className=\"flex items-center gap-2 pl-4\">\n            <Button\n              onlyIcon\n              title={t('Back')}\n              onClick={closeSettings}\n              variant=\"tertiary\"\n              size=\"small\"\n            >\n              <ArrowLeftIcon sizeClassName=\"w-5 h-5\" />\n            </Button>\n            <p className=\"body-mini-b text-label-title\">\n              <Trans>Account settings</Trans>\n            </p>\n          </div>\n        ) : type === 'project-settings' ? (\n          <div className=\"flex items-center gap-4 pl-4\">\n            <Button\n              onlyIcon\n              title={t('Back')}\n              onClick={closeSettings}\n              variant=\"tertiary\"\n              size=\"small\"\n            >\n              <ArrowLeftIcon sizeClassName=\"w-5 h-5\" />\n            </Button>\n            <p className=\"body-mini-b text-label-title\">\n              {project?.name || 'Default project'}\n            </p>\n            <p className=\"body-s-b text-label-muted\">›</p>\n            <p className=\"body-mini-b text-label-title\">\n              <Trans>Project settings</Trans>\n            </p>\n          </div>\n        ) : (\n          <Dropdown\n            DropdownComponent={ProjectsDropdown}\n            dropdownPlacement=\"bottom-start\"\n          >\n            <div className=\"flex px-4 items-center text-left h-10 gap-4 border-r border-bg-border hover:bg-bg-base-hover ellipsis\">\n              <p className=\"flex-1 body-s-b ellipsis\">\n                {project?.name || 'Default project'}\n              </p>\n              <ChevronDownIcon raw sizeClassName=\"w-3.5 h-3.5\" />\n            </div>\n          </Dropdown>\n        )}\n      </div>\n      <HeaderRightPart />\n    </div>\n  );\n};\n\nexport default memo(Header);\n"
  },
  {
    "path": "client/src/components/IpynbRenderer/IpynbCell.tsx",
    "content": "import React from 'react';\nimport ReactMarkdown from 'react-markdown';\nimport remarkMath from 'remark-math';\nimport rehypeMathJax from 'rehype-mathjax';\nimport sanitizeHtml from 'sanitize-html';\nimport Convert from 'ansi-to-html';\nimport { IpynbCellType } from '../../types/general';\nimport { escapeHtml } from '../../utils';\nimport CodeFragment from '../Code/CodeFragment';\n\nconst convertAnsi = new Convert();\n\ntype CellProps = {\n  cell: IpynbCellType;\n  seq: number;\n};\n\nconst Cell: React.FC<CellProps> = ({ cell, seq }) => {\n  if (!cell.outputs?.length && !cell.source?.length && !cell.input?.length) {\n    return null;\n  }\n\n  return (\n    <div className=\"\">\n      <div className=\"flex gap-2\">\n        {cell.cell_type === 'code' ? (\n          <div className=\"prompt input_prompt\">\n            <div className=\"body-mini flex-shrink-0 w-13 mt-1\">\n              In [{cell.execution_count || cell.prompt_number || ' '}]:\n            </div>\n          </div>\n        ) : null}\n        <div className=\"overflow-auto w-full\">\n          {(() => {\n            let source = '';\n            if (cell.input) {\n              source = stringify(cell.input);\n            } else if (cell.source) {\n              source = stringify(cell.source);\n            }\n            if (cell.cell_type === 'markdown') {\n              return (\n                <div className=\"markdown ipynb-markdown body-s\">\n                  <ReactMarkdown\n                    remarkPlugins={[remarkMath]}\n                    rehypePlugins={[rehypeMathJax]}\n                  >\n                    {embedAttachments(source, cell.attachments)}\n                  </ReactMarkdown>\n                </div>\n              );\n            }\n            if (cell.cell_type === 'code') {\n              return (\n                <div className=\"input_area border rounded-md border-bg-border\">\n                  <div\n                    className=\"p-1 code-s\"\n                    onDoubleClick={(e) => {\n                      const selection = window.getSelection();\n                      const range = document.createRange();\n                      range.selectNodeContents(e.currentTarget);\n                      selection?.removeAllRanges();\n                      selection?.addRange(range);\n                    }}\n                  >\n                    {source && (\n                      <CodeFragment\n                        code={source}\n                        language={cell.language || 'python'}\n                        showLines={false}\n                        removePaddings\n                      />\n                    )}\n                  </div>\n                </div>\n              );\n            }\n            if (cell.cell_type === 'heading') {\n              return (\n                <div className=\"markdown ipynb-markdown\">\n                  {cell.level === 1 ? (\n                    <h1>{source}</h1>\n                  ) : cell.level === 2 ? (\n                    <h2>{source}</h2>\n                  ) : cell.level === 3 ? (\n                    <h3>{source}</h3>\n                  ) : (\n                    <h4>{source}</h4>\n                  )}\n                </div>\n              );\n            }\n          })()}\n        </div>\n      </div>\n\n      <div className=\"output_wrapper\">\n        <div className=\"output body-mini\">\n          {(cell.outputs || []).map((output, j) => (\n            <div className=\"flex gap-2 mt-2\" key={j}>\n              <div className=\"prompt output_prompt\">\n                {output.execution_count || output.prompt_number ? (\n                  <div className=\"body-mini flex-shrink-0 w-13 mt-1\">\n                    Out [{output.execution_count || output.prompt_number}]:\n                  </div>\n                ) : (\n                  <div className=\"body-mini flex-shrink-0 w-13 mt-1\" />\n                )}\n              </div>\n              <div className=\"overflow-auto w-full\">\n                {(() => {\n                  if (output.data == null) {\n                    if (output.latex) {\n                      return (\n                        <div className=\"markdown body-s ipynb-markdown\">\n                          <ReactMarkdown\n                            remarkPlugins={[remarkMath]}\n                            rehypePlugins={[rehypeMathJax]}\n                          >\n                            {stringify(output.latex)}\n                          </ReactMarkdown>\n                        </div>\n                      );\n                    }\n                    if (output.png) {\n                      return (\n                        <div className=\"output_png output_subarea\">\n                          <img\n                            src={`data:image/png;base64,${output.png}`}\n                            alt=\"output png\"\n                          />\n                        </div>\n                      );\n                    }\n                    if (output.jpeg) {\n                      return (\n                        <div className=\"output_jpeg output_subarea\">\n                          <img\n                            src={`data:image/jpeg;base64,${output.jpeg}`}\n                            alt=\"output jpeg\"\n                          />\n                        </div>\n                      );\n                    }\n                    if (output.gif) {\n                      return (\n                        <div className=\"output_gif output_subarea\">\n                          <img\n                            src={`data:image/gif;base64,${output.gif}`}\n                            alt=\"output gif\"\n                          />\n                        </div>\n                      );\n                    }\n                    if (output.svg) {\n                      return (\n                        <div\n                          className=\"output_svg output_subarea\"\n                          dangerouslySetInnerHTML={{\n                            __html: sanitizeHtml(stringify(output.svg)),\n                          }}\n                        ></div>\n                      );\n                    }\n                    if (output.html) {\n                      return (\n                        <div\n                          className=\"output_html output_subarea markdown ipynb-markdown\"\n                          dangerouslySetInnerHTML={{\n                            __html: sanitizeHtml(stringify(output.html)),\n                          }}\n                        ></div>\n                      );\n                    }\n                    if (output.text) {\n                      return (\n                        <div\n                          className={`output_subarea output_text ${\n                            output.stream === 'stderr' ||\n                            output.name === 'stderr' ||\n                            output.output_type === 'stderr'\n                              ? 'bg-bg-danger/30'\n                              : ''\n                          } output_${output.output_type} output_${\n                            output.stream\n                          } output-${output.name}`}\n                        >\n                          <pre\n                            dangerouslySetInnerHTML={{\n                              __html: convertAnsi.toHtml(\n                                escapeHtml(stringify(output.text)),\n                              ),\n                            }}\n                          />\n                        </div>\n                      );\n                    }\n                    if (output.traceback) {\n                      return (\n                        <div className=\"output_subarea bg-bg-danger/30 overflow-auto\">\n                          <pre\n                            dangerouslySetInnerHTML={{\n                              __html: convertAnsi.toHtml(\n                                stringify(output.traceback),\n                              ),\n                            }}\n                          />\n                        </div>\n                      );\n                    }\n                    return null;\n                  }\n                  if (output.data['text/latex']) {\n                    return (\n                      <div className=\"markdown body-s ipynb-markdown\">\n                        <ReactMarkdown\n                          remarkPlugins={[remarkMath]}\n                          rehypePlugins={[rehypeMathJax]}\n                        >\n                          {stringify(output.data['text/latex'])}\n                        </ReactMarkdown>\n                      </div>\n                    );\n                  }\n                  if (output.data['text/html']) {\n                    const html = stringify(output.data['text/html']);\n                    return (\n                      <div\n                        className=\"output_html rendered_html output_subarea markdown ipynb-markdown\"\n                        dangerouslySetInnerHTML={{\n                          __html: sanitizeHtml(stringify(html)),\n                        }}\n                      ></div>\n                    );\n                  }\n                  if (output.data['image/png']) {\n                    return (\n                      <div className=\"output_png output_subarea\">\n                        <img\n                          src={`data:image/png;base64,${output.data['image/png']}`}\n                          alt=\"output png\"\n                        />\n                      </div>\n                    );\n                  }\n                  if (output.data['image/jpeg']) {\n                    return (\n                      <div className=\"output_jpeg output_subarea\">\n                        <img\n                          src={`data:image/jpeg;base64,${output.data['image/jpeg']}`}\n                          alt=\"output jpeg\"\n                        />\n                      </div>\n                    );\n                  }\n                  if (output.data['image/gif']) {\n                    return (\n                      <div className=\"output_gif output_subarea\">\n                        <img\n                          src={`data:image/gif;base64,${output.data['image/gif']}`}\n                          alt=\"output gif\"\n                        />\n                      </div>\n                    );\n                  }\n                  if (output.data['image/svg+xml']) {\n                    return (\n                      <div\n                        className=\"output_svg output_subarea\"\n                        dangerouslySetInnerHTML={{\n                          __html: sanitizeHtml(\n                            stringify(output.data['image/svg+xml']),\n                          ),\n                        }}\n                      ></div>\n                    );\n                  }\n                  if (output.data['text/plain']) {\n                    return (\n                      <div className=\"output_text output_subarea output_execute_result\">\n                        <pre className={``}>{output.data['text/plain']}</pre>\n                      </div>\n                    );\n                  }\n                })()}\n              </div>\n            </div>\n          ))}\n        </div>\n      </div>\n    </div>\n  );\n};\n\nconst embedAttachments = (\n  source: string,\n  attachments: IpynbCellType['attachments'] = {},\n) => {\n  Object.entries(attachments).map(([name, mimes]) => {\n    const mime = [...Object.keys(mimes)][0];\n    if (mime == null) {\n      return;\n    }\n    const data = `data:${mime};base64,${mimes[mime]}`;\n    const re = new RegExp(`attachment:${name}`, 'g');\n    source = source.replace(re, data);\n  });\n  return source;\n};\n\nconst stringify = (output: string | string[]): string => {\n  if (Array.isArray(output)) {\n    return output.filter((l) => !l.startsWith('<!--====-->')).join('');\n  }\n  return output;\n};\n\nexport default Cell;\n"
  },
  {
    "path": "client/src/components/IpynbRenderer/index.tsx",
    "content": "import { useMemo } from 'react';\nimport { IpynbCellType } from '../../types/general';\nimport Cell from './IpynbCell';\n\ntype Props = {\n  data: string;\n};\n\nconst IpynbRenderer = ({ data }: Props) => {\n  const cells = useMemo(() => {\n    const ipynb = JSON.parse(data);\n    return ipynb.cells || ipynb.worksheets?.[0]?.cells || [];\n  }, [data]);\n  return (\n    <div className=\"pb-14 pl-2 overflow-auto flex flex-col gap-4\">\n      {cells.map((cell: IpynbCellType, i: number) => {\n        return <Cell key={i} cell={cell} seq={i + 1} />;\n      })}\n    </div>\n  );\n};\n\nexport default IpynbRenderer;\n"
  },
  {
    "path": "client/src/components/KeyboardHint/MultiKey.tsx",
    "content": "import { memo } from 'react';\nimport useShortcuts from '../../hooks/useShortcuts';\n\ntype Props = {\n  shortcut: string[];\n  variant?: 'outlined' | 'filled';\n};\n\nconst MultiKeyHint = ({ shortcut, variant = 'filled' }: Props) => {\n  const keys = useShortcuts(shortcut);\n\n  return (\n    <span\n      className={`min-w-[1.25rem] h-5 inline-flex items-center justify-center px-1 rounded ${\n        variant === 'filled'\n          ? 'bg-bg-base-hover'\n          : 'bg-bg-base border border-bg-border'\n      } shadow-low body-mini text-label-base`}\n    >\n      {keys?.join(' ')}\n    </span>\n  );\n};\n\nexport default memo(MultiKeyHint);\n"
  },
  {
    "path": "client/src/components/KeyboardHint/index.tsx",
    "content": "import { memo } from 'react';\nimport useShortcuts from '../../hooks/useShortcuts';\n\ntype Props = {\n  shortcut: string;\n};\n\nconst KeyboardHint = ({ shortcut }: Props) => {\n  const key = useShortcuts([shortcut]);\n  return (\n    <div className=\"min-w-[1.25rem] h-5 flex items-center justify-center px-1 rounded bg-bg-base-hover body-mini text-label-base\">\n      {key}\n    </div>\n  );\n};\n\nexport default memo(KeyboardHint);\n"
  },
  {
    "path": "client/src/components/Loaders/LiteLoader.tsx",
    "content": "import { LiteLoaderIcon } from '../../icons';\n\ntype Props = {\n  sizeClassName?: string;\n};\n\nconst LiteLoaderContainer = ({ sizeClassName = 'w-5 h-5' }: Props) => {\n  return (\n    <div className={`${sizeClassName} animate-spin-slow flex-shrink-0`}>\n      <LiteLoaderIcon raw />\n    </div>\n  );\n};\n\nexport default LiteLoaderContainer;\n"
  },
  {
    "path": "client/src/components/Loaders/SpinnerLoader.tsx",
    "content": "import SpinLoader from '../../icons/SpinLoader';\n\ntype Props = {\n  sizeClassName?: string;\n  colorClassName?: string;\n};\n\nconst SpinLoaderContainer = ({ sizeClassName, colorClassName }: Props) => {\n  return (\n    <div\n      className={`${sizeClassName} animate-spin-slow flex-shrink-0 ${\n        colorClassName || 'text-label-base'\n      }`}\n    >\n      <SpinLoader raw />\n    </div>\n  );\n};\n\nexport default SpinLoaderContainer;\n"
  },
  {
    "path": "client/src/components/MarkdownWithCode/CodeRenderer.tsx",
    "content": "import { memo, ReactNode, useCallback, useContext, useMemo } from 'react';\nimport { TabTypesEnum } from '../../types/general';\nimport { TabsContext } from '../../context/tabsContext';\nimport CodeWithBreadcrumbs from './CodeWithBreadcrumbs';\nimport NewCode from './NewCode';\n\ntype Props = {\n  children: ReactNode[];\n  className?: string;\n  propsJSON: string;\n  inline?: boolean;\n  isCodeStudio?: boolean;\n  side: 'left' | 'right';\n};\n\nconst CodeRenderer = ({\n  className,\n  children,\n  inline,\n  propsJSON,\n  isCodeStudio,\n  side,\n}: Props) => {\n  const { openNewTab } = useContext(TabsContext.Handlers);\n  const matchLang = useMemo(\n    () =>\n      /lang:(\\w+)/.exec(className || '') ||\n      /language:(\\w+)/.exec(className || '') ||\n      /language-(\\w+)/.exec(className || ''),\n    [className],\n  );\n  const matchType = useMemo(\n    () => /language-type:(\\w+)/.exec(className || ''),\n    [className],\n  );\n  const matchPath = useMemo(\n    () => /path:(.*?)(,|$)/.exec(className || ''),\n    [className],\n  );\n  const [repoRef, filePath] = useMemo(\n    () => matchPath?.[1].split(':') || [],\n    [matchPath],\n  );\n  const matchLines = useMemo(\n    () => /lines:(.+)/.exec(className || ''),\n    [className],\n  );\n  const code = useMemo(\n    () =>\n      typeof children[0] === 'string' ? children[0].replace(/\\n$/, '') : '',\n    [children],\n  );\n  const lines = useMemo(\n    () => matchLines?.[1].split('-').map((l) => Number(l)) || [],\n    [matchLines],\n  );\n  const colorPreview = useMemo(\n    () =>\n      children[0] &&\n      children.length === 1 &&\n      typeof children[0] === 'string' &&\n      /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(children[0]) ? (\n        <span\n          className=\"w-3 h-3 inline-block\"\n          style={{ backgroundColor: children[0] }}\n        />\n      ) : null,\n    [children],\n  );\n\n  const onClick = useCallback(\n    (path?: string, linesToGo?: string) => {\n      if (repoRef && filePath) {\n        openNewTab(\n          {\n            type: TabTypesEnum.FILE,\n            path: path || filePath,\n            repoRef,\n            scrollToLine:\n              linesToGo || lines\n                ? `${lines[0]}_${lines[1] ?? lines[0]}`\n                : undefined,\n          },\n          side === 'left' ? 'right' : 'left',\n        );\n      }\n    },\n    [openNewTab, filePath, repoRef, lines, side],\n  );\n\n  return (\n    <>\n      {!inline &&\n      (matchType?.[1] || matchLang?.[1]) &&\n      typeof children[0] === 'string' ? (\n        matchType?.[1] === 'Quoted' || (isCodeStudio && matchPath?.[1]) ? (\n          <CodeWithBreadcrumbs\n            code={code}\n            repoRef={filePath ? repoRef : ''}\n            language={matchLang?.[1] || ''}\n            filePath={filePath || repoRef || ''}\n            onResultClick={onClick}\n            startLine={lines[0] ? lines[0] : null}\n            isCodeStudio={isCodeStudio}\n          />\n        ) : (\n          <NewCode\n            code={code}\n            language={matchLang?.[1] || ''}\n            filePath={filePath || ''}\n            isCodeStudio={isCodeStudio}\n          />\n        )\n      ) : colorPreview ? (\n        <span className=\"inline-flex gap-1.5 items-center\">\n          {colorPreview}\n          <code {...JSON.parse(propsJSON)} className={className}>\n            {children}\n          </code>\n        </span>\n      ) : (\n        <code {...JSON.parse(propsJSON)} className={className}>\n          {children}\n        </code>\n      )}\n    </>\n  );\n};\n\nexport default memo(CodeRenderer);\n"
  },
  {
    "path": "client/src/components/MarkdownWithCode/CodeWithBreadcrumbs.tsx",
    "content": "import React, { useCallback, MouseEvent } from 'react';\nimport FileIcon from '../FileIcon';\nimport { FileTreeFileType } from '../../types';\nimport CodeFragment from '../Code/CodeFragment';\nimport BreadcrumbsPathContainer from '../Breadcrumbs/PathContainer';\nimport CopyButton from './CopyButton';\n\ntype Props = {\n  filePath: string;\n  onResultClick: (path: string, lines?: string) => void;\n  startLine: number | null;\n  language: string;\n  code: string;\n  repoRef?: string;\n  isCodeStudio?: boolean;\n};\n\nconst CodeWithBreadcrumbs = ({\n  filePath,\n  onResultClick,\n  startLine,\n  language,\n  code,\n  repoRef,\n  isCodeStudio,\n}: Props) => {\n  const handleResultClick = useCallback(\n    (e: MouseEvent) => {\n      if (!document.getSelection()?.toString()) {\n        e.stopPropagation();\n        onResultClick(\n          filePath,\n          startLine\n            ? `${Math.max(startLine, 0)}_${\n                startLine + code.split('\\n').length - 1\n              }`\n            : undefined,\n        );\n      }\n    },\n    [filePath, startLine, code, onResultClick],\n  );\n  const onBreadcrumbClick = useCallback(\n    (path: string, type?: FileTreeFileType) => {\n      type === FileTreeFileType.FILE ? onResultClick(path) : {};\n    },\n    [onResultClick],\n  );\n\n  return (\n    <div\n      className={`${\n        isCodeStudio ? ' code-mini my-4' : ' text-sm'\n      } border border-bg-border bg-bg-sub rounded-md flex-1 overflow-x-auto cursor-pointer`}\n      onClick={handleResultClick}\n    >\n      <div\n        className={`flex items-center justify-between gap-2 w-full border-b border-bg-border bg-bg-base p-2 cursor-pointer overflow-hidden`}\n      >\n        <div className={`flex items-center gap-2 w-full cursor-pointer`}>\n          <FileIcon filename={filePath} />\n          <BreadcrumbsPathContainer\n            path={filePath}\n            repoRef={repoRef}\n            onClick={onBreadcrumbClick}\n          />\n          <CopyButton code={code} isInHeader btnVariant=\"tertiary\" />\n        </div>\n      </div>\n      <div className=\"relative\">\n        <div className={`relative overflow-x-auto py-4 code-mini`}>\n          <CodeFragment\n            code={code}\n            language={language}\n            showLines={startLine !== null}\n            lineStart={startLine || 0}\n          />\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default CodeWithBreadcrumbs;\n"
  },
  {
    "path": "client/src/components/MarkdownWithCode/CopyButton.tsx",
    "content": "import React, { memo, useCallback, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport Button from '../Button';\nimport { copyToClipboard } from '../../utils';\nimport { CheckIcon, CopyTextIcon } from '../../icons';\n\ntype Props = {\n  isInHeader?: boolean;\n  code: string;\n  className?: string;\n  btnVariant?: 'tertiary' | 'secondary';\n};\n\nconst CopyButton = ({\n  isInHeader,\n  code,\n  className,\n  btnVariant = 'secondary',\n}: Props) => {\n  const { t } = useTranslation();\n  const [codeCopied, setCodeCopied] = useState(false);\n\n  const onClick = useCallback(\n    (e: React.MouseEvent) => {\n      e.stopPropagation();\n      copyToClipboard(code);\n      setCodeCopied(true);\n      setTimeout(() => setCodeCopied(false), 2000);\n    },\n    [code],\n  );\n\n  return (\n    <div\n      className={`${\n        isInHeader\n          ? ''\n          : code.split('\\n').length > 1\n          ? 'absolute top-4 right-4 opacity-0 group-code-hover:opacity-100 transition-opacity'\n          : 'absolute top-2.5 right-2.5 opacity-0 group-code-hover:opacity-100 transition-opacity'\n      } ${className}`}\n    >\n      <Button\n        variant={btnVariant}\n        size={isInHeader ? 'mini' : 'small'}\n        onClick={onClick}\n        onlyIcon\n        title={codeCopied ? t('Copied') : t('Copy')}\n      >\n        {codeCopied ? (\n          <CheckIcon sizeClassName=\"w-3.5 h-3.5\" />\n        ) : (\n          <CopyTextIcon sizeClassName=\"w-3.5 h-3.5\" />\n        )}\n      </Button>\n    </div>\n  );\n};\n\nexport default memo(CopyButton);\n"
  },
  {
    "path": "client/src/components/MarkdownWithCode/DiffCode.tsx",
    "content": "import React, { useCallback, useMemo, useState } from 'react';\nimport Button from '../Button';\nimport { Clipboard } from '../../icons';\nimport { copyToClipboard } from '../../utils';\nimport FileIcon from '../FileIcon';\nimport { FileTreeFileType } from '../../types';\nimport CodeFragment from '../Code/CodeFragment';\nimport BreadcrumbsPathContainer from '../Breadcrumbs/PathContainer';\nimport { MessageResultModify } from '../../types/general';\n\ntype Props = {\n  data: MessageResultModify['Modify'];\n};\n\nconst DiffCode = ({ data }: Props) => {\n  const [showRaw, setShowRaw] = useState(false);\n  // const { openFileModal } = useContext(FileModalContext);\n\n  const rawCode = useMemo(\n    () =>\n      data.diff.lines\n        ?.filter((l) => !l.startsWith('-'))\n        .map((l) => (l.startsWith('+') ? ' ' + l.slice(1) : l))\n        .join('\\n'),\n    [data.diff.lines],\n  );\n\n  const onResultClick = useCallback(() => {\n    // openFileModal(data.path);\n  }, [data.path]);\n  const onBreadcrumbClick = useCallback(\n    (path: string, type?: FileTreeFileType) => {\n      type === FileTreeFileType.FILE ? onResultClick() : {};\n    },\n    [onResultClick],\n  );\n\n  return (\n    <div className=\"text-sm border border-bg-border rounded-md\">\n      <div className=\"w-full bg-bg-base px-3 h-13 border-b border-bg-border flex items-center justify-between\">\n        <div\n          className=\"flex items-center gap-2 max-w-[calc(100%-120px)] w-full cursor-pointer\"\n          onClick={onResultClick}\n        >\n          <FileIcon filename={data.path} />\n          <BreadcrumbsPathContainer\n            path={data.path}\n            onClick={onBreadcrumbClick}\n          />\n        </div>\n        <div className=\"flex items-center justify-center p-0.5 gap-0.5 bg-bg-sub rounded-4\">\n          <button\n            className={`px-2 h-6 rounded-4 body-mini flex items-center justify-center outline-none focus:outline-none focus:border-bg-border ${\n              !showRaw\n                ? 'text-label-title bg-bg-base border-bg-border shadow-low'\n                : 'text-label-base border-transparent'\n            } transition-all duration-150 ease-in-bounce border`}\n            onClick={() => setShowRaw(false)}\n          >\n            Diff\n          </button>\n          <button\n            className={`px-2 h-6 rounded-4 body-mini flex items-center justify-center outline-none focus:outline-none focus:border-bg-border ${\n              showRaw\n                ? 'text-label-title bg-bg-base border-bg-border shadow-low'\n                : 'text-label-base border-transparent'\n            } transition-all duration-150 ease-in-bounce border`}\n            onClick={() => setShowRaw(true)}\n          >\n            Raw\n          </button>\n        </div>\n      </div>\n      {data.diff?.lines ? (\n        <div className=\"relative py-4\">\n          <div className=\"overflow-auto\">\n            <CodeFragment\n              lineStart={data.diff.header?.old_start}\n              code={showRaw ? rawCode : data.diff.lines?.join('\\n')}\n              language={data.language}\n              isDiff\n              removePaddings\n            />\n          </div>\n          <div className=\"absolute top-4 right-4\">\n            <Button\n              variant=\"secondary\"\n              size=\"small\"\n              onClick={() => copyToClipboard(rawCode)}\n            >\n              <Clipboard />\n              Copy\n            </Button>\n          </div>\n        </div>\n      ) : null}\n    </div>\n  );\n};\n\nexport default DiffCode;\n"
  },
  {
    "path": "client/src/components/MarkdownWithCode/FolderChip.tsx",
    "content": "import React, { useCallback } from 'react';\nimport { ArrowOutIcon, FolderIcon } from '../../icons';\nimport { getFolderContent } from '../../services/api';\nimport OverflowTracker from '../OverflowTracker';\nimport RepoEntry from '../../Project/LeftSidebar/NavPanel/Repo/RepoEntry';\n\ntype Props = {\n  onClick: () => void;\n  path: string;\n  repoRef?: string;\n};\n\nconst FolderChip = ({ onClick, path, repoRef }: Props) => {\n  const fetchFiles = useCallback(\n    async (path?: string) => {\n      if (!repoRef) {\n        return [];\n      }\n      const resp = await getFolderContent(repoRef, path);\n      if (!resp.entries?.length) {\n        return [];\n      }\n      return resp?.entries.sort((a, b) => {\n        if ((a.entry_data === 'Directory') === (b.entry_data === 'Directory')) {\n          return a.name?.toLowerCase() < b.name?.toLowerCase() ? -1 : 1;\n        } else {\n          return a.entry_data === 'Directory' ? -1 : 1;\n        }\n      });\n    },\n    [repoRef],\n  );\n\n  return (\n    <>\n      <button\n        className={`inline-flex items-center bg-chat-bg-shade rounded-4 overflow-hidden \n                text-label-base hover:text-label-title border border-transparent hover:border-chat-bg-border \n                cursor-pointer align-middle ellipsis`}\n        onClick={onClick}\n      >\n        <span className=\"flex gap-1 px-1 py-0.5 items-center border-r border-chat-bg-border code-s ellipsis\">\n          <FolderIcon raw sizeClassName=\"w-3.5 h-3.5\" />\n          <span className=\"ellipsis\">{path.slice(0, -1)}</span>\n        </span>\n        <span className=\"p-1 inline-flex items-center justify-center\">\n          <ArrowOutIcon sizeClassName=\"w-3.5 h-3.5\" />\n        </span>\n      </button>\n      <div\n        className={\n          'w-full flex flex-col my-1 folder-chip text-sm border border-bg-border rounded-md overflow-auto max-h-80 p-1'\n        }\n      >\n        <OverflowTracker className=\"auto-fade-vertical\">\n          <RepoEntry\n            name={path}\n            isDirectory\n            level={0}\n            fetchFiles={fetchFiles}\n            fullPath={path}\n            defaultOpen\n            indexed\n            repoRef={repoRef || ''}\n            lastIndex={''}\n            index={'0'}\n          />\n        </OverflowTracker>\n      </div>\n    </>\n  );\n};\n\nexport default FolderChip;\n"
  },
  {
    "path": "client/src/components/MarkdownWithCode/LinkRenderer.tsx",
    "content": "import {\n  Dispatch,\n  memo,\n  MutableRefObject,\n  ReactElement,\n  ReactNode,\n  SetStateAction,\n  useCallback,\n  useContext,\n  useMemo,\n} from 'react';\nimport { FileHighlightsType, TabTypesEnum } from '../../types/general';\nimport { TabsContext } from '../../context/tabsContext';\nimport FileChip from '../Chips/FileChip';\nimport { splitPath } from '../../utils';\nimport FolderChip from './FolderChip';\n\ntype Props = {\n  href?: string;\n  children: ReactNode[];\n  fileChips: MutableRefObject<never[]>;\n  singleFileExplanation?: boolean;\n  setFileHighlights: Dispatch<SetStateAction<FileHighlightsType>>;\n  setHoveredLines: Dispatch<SetStateAction<[number, number] | null>>;\n  side: 'left' | 'right';\n};\n\nconst LinkRenderer = ({\n  href,\n  children,\n  fileChips,\n  singleFileExplanation,\n  setFileHighlights,\n  setHoveredLines,\n  side,\n}: Props) => {\n  const { openNewTab } = useContext(TabsContext.Handlers);\n  const [filePath, lines] = useMemo(() => href?.split('#') || [], [href]);\n  const [repo, path] = useMemo(() => filePath?.split(':') || [], [filePath]);\n  const [start, end] = useMemo(\n    () => lines?.split('-').map((l) => Number(l.slice(1))) || [],\n    [lines],\n  );\n  const fileName = useMemo(() => {\n    let f: string = '';\n    if (children?.[0]) {\n      if (typeof children[0] === 'string') {\n        f = children?.[0];\n      }\n      const child = children[0] as ReactElement;\n      if (child?.props && typeof child.props.children?.[0] === 'string') {\n        f = child.props.children?.[0];\n      }\n    }\n    // if the link is of type repo_ref:file_path.ext\n    if (new RegExp('.*/.*:.*').test(f)) {\n      f = splitPath(f).pop() || f;\n    }\n    return f;\n  }, [children]);\n\n  const linesToUse: [number, number] | undefined = useMemo(() => {\n    return singleFileExplanation && start > -1\n      ? [start, end ?? start]\n      : undefined;\n  }, [singleFileExplanation, start, end]);\n\n  const handleClickFile = useCallback(() => {\n    if (repo && path) {\n      openNewTab(\n        {\n          type: TabTypesEnum.FILE,\n          repoRef: repo,\n          path,\n          scrollToLine: `${start}_${end ?? start}`,\n        },\n        side === 'left' ? 'right' : 'left',\n      );\n    }\n  }, [start, end, path, repo, side]);\n\n  const handleClickFolder = useCallback(() => {\n    // if (repoName) {\n    //   navigateRepoPath(repoName, filePath);\n    // }\n  }, [path]);\n\n  return (\n    <>\n      {filePath.endsWith('/') ? (\n        <FolderChip\n          onClick={handleClickFolder}\n          path={path || filePath || ''}\n          repoRef={repo}\n        />\n      ) : (\n        <FileChip\n          fileName={fileName || path || ''}\n          filePath={path}\n          skipIcon={!!fileName && fileName !== path}\n          fileChips={fileChips}\n          onClick={handleClickFile}\n          lines={linesToUse}\n          setFileHighlights={setFileHighlights}\n          setHoveredLines={setHoveredLines}\n        />\n      )}\n    </>\n  );\n};\n\nexport default memo(LinkRenderer);\n"
  },
  {
    "path": "client/src/components/MarkdownWithCode/NewCode.tsx",
    "content": "import { getFileExtensionForLang, getPrettyLangName } from '../../utils';\nimport FileIcon from '../FileIcon';\nimport CodeFragment from '../Code/CodeFragment';\nimport CopyButton from './CopyButton';\n\ntype Props = {\n  code: string;\n  language: string;\n  filePath: string;\n  isSummary?: boolean;\n  isCodeStudio?: boolean;\n};\n\nconst NewCode = ({\n  code,\n  language,\n  isSummary,\n  isCodeStudio,\n  filePath,\n}: Props) => {\n  return (\n    <div\n      className={`${\n        !isSummary ? (isCodeStudio ? ' text-xs' : ' text-sm') : 'text-sm'\n      } bg-bg-sub my-4 border border-bg-border rounded-md relative group-code`}\n    >\n      <div\n        className={`border-bg-border border-b bg-bg-base rounded-t-md p-2 flex items-center justify-between gap-2`}\n      >\n        <div className=\"flex items-center gap-2 overflow-hidden flex-1\">\n          <FileIcon\n            filename={filePath || getFileExtensionForLang(language, true)}\n            noMargin\n          />\n          {filePath ? (\n            <span>BreadcrumbsPath nonInteractive</span>\n          ) : (\n            <span className=\"body-mini-b\">\n              {getPrettyLangName(language) || language}\n            </span>\n          )}\n        </div>\n        <CopyButton isInHeader code={code} />\n      </div>\n      <div\n        className={`overflow-auto ${isCodeStudio ? 'p-2' : 'py-2 code-mini'}`}\n      >\n        <CodeFragment\n          showLines={false}\n          code={code}\n          language={language}\n          canWrap\n        />\n      </div>\n    </div>\n  );\n};\n\nexport default NewCode;\n"
  },
  {
    "path": "client/src/components/MarkdownWithCode/index.tsx",
    "content": "import ReactMarkdown from 'react-markdown';\nimport {\n  AnchorHTMLAttributes,\n  DetailedHTMLProps,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n} from 'react';\nimport { ReactMarkdownProps } from 'react-markdown/lib/complex-types';\nimport { CodeProps } from 'react-markdown/lib/ast-to-react';\nimport { FileHighlightsContext } from '../../context/fileHighlightsContext';\nimport LinkRenderer from './LinkRenderer';\nimport CodeRenderer from './CodeRenderer';\n\ntype Props = {\n  markdown: string;\n  singleFileExplanation?: boolean;\n  isCodeStudio?: boolean;\n  side: 'left' | 'right';\n};\n\nconst MarkdownWithCode = ({\n  markdown,\n  singleFileExplanation,\n  isCodeStudio,\n  side,\n}: Props) => {\n  const fileChips = useRef([]);\n  const { setFileHighlights, setHoveredLines } = useContext(\n    FileHighlightsContext.Setters,\n  );\n\n  useEffect(() => {\n    return () => {\n      setFileHighlights({});\n      setHoveredLines(null);\n    };\n  }, []);\n\n  const components = useMemo(() => {\n    return {\n      a(\n        props: Omit<\n          DetailedHTMLProps<\n            AnchorHTMLAttributes<HTMLAnchorElement>,\n            HTMLAnchorElement\n          >,\n          'ref'\n        > &\n          ReactMarkdownProps,\n      ) {\n        return (\n          <LinkRenderer\n            href={(props.node.properties?.href as string) || props.href}\n            fileChips={fileChips}\n            singleFileExplanation={singleFileExplanation}\n            setFileHighlights={setFileHighlights}\n            setHoveredLines={setHoveredLines}\n            side={side}\n          >\n            {props.children}\n          </LinkRenderer>\n        );\n      },\n      code({ node, inline, className, children, ...props }: CodeProps) {\n        return (\n          <CodeRenderer\n            inline={inline}\n            className={className}\n            propsJSON={JSON.stringify(props)}\n            isCodeStudio={isCodeStudio}\n            side={side}\n          >\n            {children}\n          </CodeRenderer>\n        );\n      },\n    };\n  }, [singleFileExplanation]);\n\n  return <ReactMarkdown components={components}>{markdown}</ReactMarkdown>;\n};\n\nexport default MarkdownWithCode;\n"
  },
  {
    "path": "client/src/components/Modal/index.tsx",
    "content": "import React, { memo, PropsWithChildren, useCallback } from 'react';\nimport { AnimatePresence, motion } from 'framer-motion';\nimport { MODAL_APPEAR_ANIMATION } from '../../consts/animations';\nimport { isFocusInInput } from '../../utils/domUtils';\nimport useKeyboardNavigation from '../../hooks/useKeyboardNavigation';\n\ntype Props = {\n  isVisible: boolean;\n  noBg?: boolean;\n  onClose?: () => void;\n  containerClassName?: string;\n  customKeyHandler?: boolean;\n};\n\nconst backdropHidden = {\n  opacity: 0,\n};\n\nconst backdropVisible = {\n  opacity: 1,\n};\n\nconst initialModalStyles = {\n  top: '50%',\n  right: '50%',\n  transform: 'translate(50%, -45%)',\n  opacity: 0,\n};\n\nconst modalAnimation = {\n  top: '50%',\n  right: '50%',\n  transform: 'translate(50%, -50%)',\n  opacity: 1,\n};\n\nconst Modal = ({\n  onClose,\n  children,\n  isVisible,\n  noBg,\n  containerClassName = '',\n  customKeyHandler,\n}: PropsWithChildren<Props>) => {\n  const handleKeyEvent = useCallback((e: KeyboardEvent) => {\n    if (!isFocusInInput() && e.key === 'Escape') {\n      e.preventDefault();\n      e.stopPropagation();\n      onClose?.();\n    }\n  }, []);\n  useKeyboardNavigation(handleKeyEvent, customKeyHandler || !isVisible);\n\n  return (\n    <>\n      <AnimatePresence>\n        {isVisible && (\n          <motion.div\n            className={`fixed top-0 bottom-0 left-0 right-0 bg-bg-sub/50 ${\n              onClose ? 'cursor-alias' : ''\n            } z-50`}\n            initial={backdropHidden}\n            animate={backdropVisible}\n            exit={backdropHidden}\n            onClick={onClose}\n            transition={MODAL_APPEAR_ANIMATION}\n          />\n        )}\n      </AnimatePresence>\n      <AnimatePresence>\n        {isVisible && (\n          <motion.div\n            className={`fixed flex flex-col rounded-xl ${\n              noBg\n                ? ''\n                : 'bg-bg-base border border-bg-border backdrop-blur-8 shadow-float overflow-auto'\n            } ${containerClassName} z-70`}\n            animate={modalAnimation}\n            initial={initialModalStyles}\n            exit={initialModalStyles}\n            role=\"dialog\"\n            aria-modal=\"true\"\n            transition={MODAL_APPEAR_ANIMATION}\n          >\n            {children}\n          </motion.div>\n        )}\n      </AnimatePresence>\n    </>\n  );\n};\n\nexport default memo(Modal);\n"
  },
  {
    "path": "client/src/components/OverflowTracker/index.tsx",
    "content": "import {\n  ComponentProps,\n  memo,\n  RefObject,\n  useCallback,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n} from 'react';\n\nconst edges = [\n  { position: 'top', insetStyles: { left: 0, top: 0, right: 0 } },\n  { position: 'right', insetStyles: { right: 0, top: 0, bottom: 0 } },\n  { position: 'bottom', insetStyles: { left: 0, bottom: 0, right: 0 } },\n  { position: 'left', insetStyles: { left: 0, top: 0, bottom: 0 } },\n];\n\nconst OverflowTracker = ({\n  children,\n  attrRef,\n  ...otherProps\n}: {\n  attrRef?: RefObject<HTMLElement>;\n} & ComponentProps<'div'>) => {\n  const wrapperRef = useRef<HTMLDivElement>(null);\n  const edgesRef = useRef<Map<string, HTMLDivElement> | null>(null);\n\n  const getMap = useCallback(() => {\n    if (!edgesRef.current) {\n      // Initialize the Map on first usage.\n      edgesRef.current = new Map();\n    }\n    return edgesRef.current;\n  }, []);\n\n  useLayoutEffect(() => {\n    const attrTarget = (attrRef || wrapperRef).current;\n    const wrapper = wrapperRef.current;\n    const edges = edgesRef.current;\n\n    if (!wrapper || !edges || !attrTarget) return;\n\n    const resizeObserver = new IntersectionObserver(\n      (entries) => {\n        for (const entry of entries) {\n          const attr = `data-overflow-${entry.target.getAttribute(\n            'data-overflow-tracker',\n          )}`;\n          if (entry.intersectionRatio > 0) {\n            attrTarget.removeAttribute(attr);\n          } else {\n            attrTarget.setAttribute(attr, '');\n          }\n        }\n      },\n      {\n        root: wrapper,\n        // Since the edge element <div /> has no area (width or height is always 0)\n        // we need to set the rootMargin to 1px to make sure\n        // that intersection changes are always detected\n        rootMargin: '1px',\n      },\n    );\n\n    // Observe edge trackers\n    const edgeElements = Array.from(edges.values());\n    edgeElements.forEach((edgeTracker) => resizeObserver.observe(edgeTracker));\n    // Clean up the observer when the component unmounts\n    return () => {\n      edgeElements.forEach((edgeTracker) =>\n        resizeObserver.unobserve(edgeTracker),\n      );\n      resizeObserver.disconnect();\n    };\n  }, [attrRef]);\n\n  const renderedEdges = useMemo(() => {\n    return edges.map(({ position, insetStyles }) => (\n      <div\n        key={position}\n        data-overflow-tracker={position}\n        style={insetStyles}\n        className=\"absolute\"\n        ref={(node) => {\n          const map = getMap();\n          if (node) {\n            map.set(position, node);\n          } else {\n            map.delete(position);\n          }\n        }}\n      />\n    ));\n  }, [edges, getMap]);\n\n  return (\n    <div ref={wrapperRef} {...otherProps}>\n      <div className=\"flex\">\n        <div className=\"inline-block relative\">\n          {children}\n          {renderedEdges}\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default memo(OverflowTracker);\n"
  },
  {
    "path": "client/src/components/RefsDefsPopup/Badge.tsx",
    "content": "import { Trans } from 'react-i18next';\nimport { useCallback } from 'react';\nimport Tooltip from '../Tooltip';\nimport { TokenInfoType } from '../../types/results';\nimport { DefIcon, RefIcon } from '../../icons';\nimport { TypeMap } from './index';\n\ntype Props = {\n  type: TokenInfoType;\n  onClick: (type: TokenInfoType) => void;\n  active?: boolean;\n  disabled?: boolean;\n  tooltipText: string;\n};\n\nconst colorMap = {\n  reference: 'text-red',\n  definition: 'text-green',\n  mod: 'text-violet',\n  ret: 'text-sky',\n};\n\nconst TooltipCodeBadge = ({\n  type,\n  onClick,\n  active,\n  disabled,\n  tooltipText,\n}: Props) => {\n  const handleClick = useCallback(() => {\n    if (!disabled) {\n      onClick(type);\n    }\n  }, [disabled, type, onClick]);\n\n  return (\n    <Tooltip text={tooltipText} placement={'top'}>\n      <div\n        className={`flex items-center justify-center gap-1 px-1.5 h-6 rounded ${\n          disabled || !active ? 'bg-transparent' : 'bg-bg-base-hover'\n        } ${disabled ? 'cursor-default' : 'cursor-pointer'} select-none group`}\n        onClick={handleClick}\n      >\n        {type === TypeMap.DEF ? (\n          <DefIcon\n            sizeClassName=\"w-3.5 h-3.5\"\n            className={disabled ? 'text-label-muted' : colorMap[type]}\n          />\n        ) : (\n          <RefIcon\n            sizeClassName=\"w-3.5 h-3.5\"\n            className={disabled ? 'text-label-muted' : colorMap[type]}\n          />\n        )}\n        <span\n          className={`capitalize body-mini-b ${\n            disabled || !active ? 'text-label-muted' : 'text-label-title'\n          }`}\n        >\n          <Trans>{type}</Trans>\n        </span>\n      </div>\n    </Tooltip>\n  );\n};\n\nexport default TooltipCodeBadge;\n"
  },
  {
    "path": "client/src/components/RefsDefsPopup/RefDefFileItem.tsx",
    "content": "import React, { useCallback, useMemo, useState } from 'react';\nimport { Trans } from 'react-i18next';\nimport { ArrowTriangleBottomIcon } from '../../icons';\nimport { RefDefDataItem } from '../../types/api';\nimport { TokenInfoType } from '../../types/results';\nimport BreadcrumbsPathContainer from '../Breadcrumbs/PathContainer';\nimport RefDefFileLine from './RefDefFileLine';\n\ntype Props = {\n  file: string;\n  data: RefDefDataItem[];\n  onRefDefClick: (\n    lineNum: number,\n    filePath: string,\n    tokenRange: string,\n  ) => void;\n  language: string;\n  kind: TokenInfoType;\n  relativePath: string;\n};\n\nconst RefDefFileItem = ({\n  file,\n  data,\n  onRefDefClick,\n  language,\n  kind,\n  relativePath,\n}: Props) => {\n  const [isOpen, setOpen] = useState(true);\n\n  const toggleOpen = useCallback(() => {\n    setOpen((prev) => !prev);\n  }, []);\n\n  const style = useMemo(() => {\n    return {\n      maxHeight: isOpen ? 40 * data.length : 0,\n      animationDuration: data.length * 0.01 + 's',\n    };\n  }, [data.length, isOpen]);\n\n  return (\n    <div className=\"[&:not(:last-child)]:border-b border-bg-border\" key={file}>\n      <div\n        className={`h-10 flex-shrink-0 px-4 flex items-center gap-4 cursor-pointer w-full ${\n          isOpen ? 'text-label-title' : 'text-label-muted'\n        }`}\n        onClick={toggleOpen}\n      >\n        <ArrowTriangleBottomIcon\n          sizeClassName=\"w-2 h-2\"\n          className={`flex-shrink-0 ${\n            isOpen ? 'rotate-0' : '-rotate-90'\n          } transition-all duration-150`}\n        />\n        <div className=\"flex-1 overflow-hidden body-s-b\">\n          {file === relativePath ? (\n            <p className=\"select-none text-left\">\n              <Trans>In this file</Trans>\n            </p>\n          ) : (\n            <BreadcrumbsPathContainer\n              path={file}\n              activeStyle=\"secondary\"\n              separator=\"›\"\n            />\n          )}\n        </div>\n        <p className=\"select-none body-s-b\">{data.length}</p>\n      </div>\n      <div style={style} className=\"transition-all ease-linear overflow-hidden\">\n        {data.map((line, i) => (\n          <RefDefFileLine\n            key={i}\n            onRefDefClick={onRefDefClick}\n            file={file}\n            language={language}\n            kind={kind}\n            snippet={line.snippet}\n            range={line.range}\n          />\n        ))}\n      </div>\n    </div>\n  );\n};\n\nexport default RefDefFileItem;\n"
  },
  {
    "path": "client/src/components/RefsDefsPopup/RefDefFileLine.tsx",
    "content": "import React, { memo, useCallback } from 'react';\nimport { DefIcon, RefIcon } from '../../icons';\nimport CodeFragment from '../Code/CodeFragment';\nimport { Range, TokenInfoType } from '../../types/results';\nimport { TypeMap } from './index';\n\ntype Props = {\n  kind: TokenInfoType;\n  onRefDefClick: (\n    lineNum: number,\n    filePath: string,\n    tokenRange: string,\n  ) => void;\n  snippet: {\n    data: string;\n    highlights: Range[];\n    tokenRange?: Range | undefined;\n    symbols: never[];\n    line_range: Range;\n  };\n  file: string;\n  range: {\n    start: { byte: number; line: number; column: number };\n    end: { byte: number; line: number; column: number };\n  };\n  language: string;\n};\n\nconst RefDefFileLine = ({\n  kind,\n  onRefDefClick,\n  snippet,\n  file,\n  range,\n  language,\n}: Props) => {\n  const onClick = useCallback(() => {\n    onRefDefClick(\n      snippet.line_range.start,\n      file,\n      `${range.start?.byte}_${range.end?.byte}`,\n    );\n  }, [onRefDefClick, snippet, file, kind, range]);\n\n  return (\n    <div\n      className=\"h-10 flex-shrink-0 pr-3 pl-10 code-mini flex gap-1.5 items-center cursor-pointer overflow-auto hover:bg-bg-shade-hover\"\n      onClick={onClick}\n    >\n      {kind === TypeMap.DEF ? (\n        <DefIcon sizeClassName=\"w-3.5 h-3.5\" className=\"text-label-muted\" />\n      ) : (\n        <RefIcon sizeClassName=\"w-3.5 h-3.5\" className=\"text-label-muted\" />\n      )}\n      <CodeFragment\n        code={snippet.data}\n        lineStart={snippet.line_range.start}\n        highlights={snippet.highlights}\n        language={language}\n        removePaddings\n        lineHoverEffect\n      />\n    </div>\n  );\n};\n\nexport default memo(RefDefFileLine);\n"
  },
  {
    "path": "client/src/components/RefsDefsPopup/index.tsx",
    "content": "import React, {\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport { TippyProps } from '@tippyjs/react';\nimport { Trans, useTranslation } from 'react-i18next';\nimport { TokenInfoType, TokenInfoWrapped } from '../../types/results';\nimport LiteLoaderContainer from '../Loaders/LiteLoader';\nimport { UIContext } from '../../context/uiContext';\nimport RefDefFileItem from './RefDefFileItem';\nimport Badge from './Badge';\n\nconst positionMap = {\n  left: { tail: 'left-1', fixBorder: 'left-[8.38px]' },\n  center: {\n    tail: 'left-1/2 -translate-x-1/2',\n    fixBorder: 'left-[13px] left-1/2 -translate-x-1/2 transform',\n  },\n  right: { tail: 'right-2', fixBorder: 'right-[12.3px]' },\n};\n\nconst tailStyles = {\n  top: {\n    tail: 'bg-bg-shade top-2',\n    fixture: 'border-b-[1px] border-bg-border top-[10px]',\n  },\n  bottom: {\n    tail: 'bg-bg-shade bottom-2',\n    fixture: 'border-t-[1px] border-bg-border bottom-[10px]',\n  },\n};\n\nconst getTailPosition = (\n  placement: TippyProps['placement'],\n): {\n  horizontal: 'left' | 'center' | 'right';\n  vertical: 'bottom' | 'top';\n} => {\n  return {\n    horizontal: placement?.endsWith('start')\n      ? 'left'\n      : placement?.endsWith('end')\n      ? 'right'\n      : 'center',\n    vertical: placement?.startsWith('top') ? 'bottom' : 'top',\n  };\n};\n\ntype Props = {\n  placement: TippyProps['placement'];\n  data: TokenInfoWrapped;\n  onRefDefClick: (\n    lineNum: number,\n    filePath: string,\n    tokenRange: string,\n  ) => void;\n  language: string;\n  relativePath: string;\n};\n\nexport const TypeMap = {\n  REF: 'reference',\n  DEF: 'definition',\n} as const;\n\nconst RefsDefsPopup = ({\n  placement,\n  data,\n  onRefDefClick,\n  language,\n  relativePath,\n}: Props) => {\n  const { t } = useTranslation();\n  const [filters, setFilters] = useState<TokenInfoType>(\n    !data.data?.definitions?.length ? TypeMap.REF : TypeMap.DEF,\n  );\n  const { setOnBoardingState } = useContext(UIContext.Onboarding);\n\n  useEffect(() => {\n    setFilters(!data.data?.definitions?.length ? TypeMap.REF : TypeMap.DEF);\n  }, [data.data]);\n\n  const toggleFilter = useCallback((type: TokenInfoType) => {\n    setFilters(type);\n  }, []);\n\n  const tailPosition = useMemo(() => getTailPosition(placement), [placement]);\n\n  const handleClickOnRefDef = useCallback(\n    (lineNum: number, filePath: string, tokenRange: string) => {\n      onRefDefClick(lineNum, filePath, tokenRange);\n      setOnBoardingState((prev) =>\n        prev.isCodeNavigated ? prev : { ...prev, isCodeNavigated: true },\n      );\n    },\n    [onRefDefClick],\n  );\n\n  return (\n    <div className=\"relative py-2.5 w-fit z-10 drop-shadow-md\">\n      <span\n        className={`absolute ${\n          positionMap[tailPosition.horizontal].tail\n        } w-5 h-5 border border-bg-border ${\n          tailStyles[tailPosition.vertical].tail\n        } transform rotate-45 box-border z-[-1] rounded-sm`}\n      />\n\n      <div className=\"flex flex-col w-96 rounded-lg overflow-hidden border border-bg-border bg-bg-shade z-10\">\n        <span\n          className={`absolute ${\n            positionMap[tailPosition.horizontal].fixBorder\n          } w-[11.52px] h-[1px] bg-bg-shade ${\n            tailStyles[tailPosition.vertical].fixture\n          } border-l-[1px] border-r-[1px] border-b-transparent border-l-bg-border-hover border-r-bg-border`}\n        />\n        <div className=\"bg-bg-base h-10 flex-shrink-0 px-2.5 border-b border-bg-border flex items-center gap-2 shadow-low\">\n          <Badge\n            type={TypeMap.DEF}\n            onClick={toggleFilter}\n            active={filters === TypeMap.DEF}\n            disabled={!data.data.definitions?.length}\n            tooltipText={t('The line of code where identifier is defined')}\n          />\n          <Badge\n            type={TypeMap.REF}\n            onClick={toggleFilter}\n            active={filters === TypeMap.REF}\n            disabled={!data.data.references?.length}\n            tooltipText={t(\n              'The line of code where the identifier is referenced',\n            )}\n          />\n        </div>\n        {!data.data?.references?.length && !data.data?.definitions?.length ? (\n          <div className=\"bg-bg-sub rounded-b p-8 flex flex-col items-center gap-3 text-center text-label-base select-none\">\n            {data.isLoading ? (\n              <>\n                <LiteLoaderContainer />\n                <p className=\"body-s\">\n                  <Trans>Searching...</Trans>\n                </p>\n              </>\n            ) : (\n              <>\n                <p className=\"body-s text-label-title\">\n                  <Trans>No references or definitions found</Trans>\n                </p>\n                <p className=\"body-mini text-label-muted\">\n                  <Trans>\n                    We weren&apos;t able to identify any references at the\n                    moment\n                  </Trans>\n                </p>\n              </>\n            )}\n          </div>\n        ) : (\n          <div className=\"overflow-auto max-h-80\">\n            {data.data[\n              filters === TypeMap.DEF ? 'definitions' : 'references'\n            ].map((item, i) => (\n              <RefDefFileItem\n                onRefDefClick={handleClickOnRefDef}\n                data={item.data}\n                file={item.file}\n                language={language}\n                key={item.file + i}\n                relativePath={relativePath}\n                kind={filters === TypeMap.DEF ? TypeMap.DEF : TypeMap.REF}\n              />\n            ))}\n          </div>\n        )}\n      </div>\n    </div>\n  );\n};\n\nexport default RefsDefsPopup;\n"
  },
  {
    "path": "client/src/components/ScrollToBottom/Composer.tsx",
    "content": "import React, {\n  PropsWithChildren,\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n} from 'react';\nimport useStateRef from '../../hooks/useStateRef';\nimport InternalContext from './InternalContext';\nimport SpineTo from './SpineTo';\nimport EventSpy from './EventSpy';\nimport FunctionContext from './FunctionContext';\n\nconst DEFAULT_SCROLLER = () => Infinity;\nconst MIN_CHECK_INTERVAL = 17; // 1 frame\nconst NEAR_END_THRESHOLD = 1;\nconst SCROLL_DECISION_DURATION = 34; // 2 frames\n\nfunction setImmediateInterval(fn: () => void, ms: number) {\n  fn();\n\n  return setInterval(fn, ms);\n}\n\nfunction computeViewState({\n  target: { offsetHeight, scrollHeight, scrollTop },\n}: {\n  target: {\n    offsetHeight: number;\n    scrollHeight: number;\n    scrollTop: number;\n  };\n}) {\n  const atBottom = scrollHeight - scrollTop - offsetHeight < NEAR_END_THRESHOLD;\n  const atTop = scrollTop < NEAR_END_THRESHOLD;\n\n  const atEnd = atBottom;\n  const atStart = atBottom;\n\n  return {\n    atBottom,\n    atEnd,\n    atStart,\n    atTop,\n  };\n}\n\nconst Composer = ({ children }: PropsWithChildren) => {\n  const ignoreScrollEventBeforeRef = useRef(0);\n  const initialScrollBehaviorRef = useRef<false | 'auto' | 'smooth'>('auto');\n  const [animateTo, setAnimateTo, animateToRef] = useStateRef('100%');\n  const [target, setTarget, targetRef] = useStateRef(null);\n\n  // Internal context\n  const animateFromRef = useRef(0);\n  const offsetHeightRef = useRef(0);\n  const scrollHeightRef = useRef(0);\n\n  const [_, setSticky, stickyRef] = useStateRef(true);\n\n  // High-rate state context\n  const scrollPositionObserversRef = useRef<\n    ((opts: { scrollTop: number }) => void)[]\n  >([]);\n\n  const handleSpineToEnd = useCallback(() => {\n    const { current: animateTo } = animateToRef;\n\n    ignoreScrollEventBeforeRef.current = Date.now();\n\n    // handleScrollEnd may end at a position which should lose stickiness.\n    // In that case, we will need to set sticky to false to stop the interval check.\n    // Test case:\n    // 1. Add a scroller that always return 0\n    // 2. Show a panel with mode === MODE_BOTTOM\n    // 3. Programmatically scroll to 0 (set element.scrollTop = 0)\n    // Expected: it should not repetitively call scrollTo(0)\n    //           it should set stickiness to false\n\n    animateTo === '100%' || setSticky(false);\n    setAnimateTo(null);\n  }, [animateToRef, ignoreScrollEventBeforeRef, setAnimateTo, setSticky]);\n\n  // Function context\n  const scrollTo = useCallback(\n    (\n      nextAnimateTo: string | number,\n      { behavior }: { behavior?: 'smooth' | 'auto' } = {},\n    ) => {\n      const { current: target } = targetRef;\n\n      if (behavior === 'auto') {\n        // Stop any existing animation\n        handleSpineToEnd();\n\n        if (target) {\n          // Jump to the scroll position\n          target.scrollTop =\n            nextAnimateTo === '100%'\n              ? target.scrollHeight - target.offsetHeight\n              : nextAnimateTo;\n        }\n      } else {\n        setAnimateTo(nextAnimateTo);\n      }\n\n      // This is for handling a case. When calling scrollTo('100%', { behavior: 'auto' }) multiple times, it would lose stickiness.\n      if (nextAnimateTo === '100%') {\n        setSticky(true);\n      }\n    },\n    [handleSpineToEnd, setAnimateTo, setSticky, targetRef],\n  );\n\n  const scrollToBottom = useCallback(\n    ({ behavior }: { behavior?: 'smooth' | 'auto' } = {}) => {\n      scrollTo('100%', { behavior: behavior || 'smooth' });\n    },\n    [scrollTo],\n  );\n\n  const scrollToSticky = useCallback(() => {\n    const { current: target } = targetRef;\n\n    if (target) {\n      if (initialScrollBehaviorRef.current === 'auto') {\n        target.scrollTop = target.scrollHeight - target.offsetHeight;\n        initialScrollBehaviorRef.current = false;\n\n        return;\n      }\n\n      // This is very similar to scrollToEnd().\n      // Instead of scrolling to end, it will call props.scroller() to determines how far it should scroll.\n      // This function could be called while it is auto-scrolling.\n\n      const { offsetHeight, scrollHeight, scrollTop } = target;\n\n      const maxValue = Math.max(0, scrollHeight - offsetHeight - scrollTop);\n\n      const rawNextValue = DEFAULT_SCROLLER();\n\n      const nextValue = Math.max(0, Math.min(maxValue, rawNextValue));\n\n      let nextAnimateTo;\n\n      if (nextValue !== maxValue) {\n        nextAnimateTo = scrollTop + nextValue;\n      } else {\n        // When scrolling to bottom, we should scroll to \"100%\".\n        // Otherwise, if we scroll to any number, it will lose stickiness when elements are adding too fast.\n        // \"100%\" is a special argument intended to make sure stickiness is not lost while new elements are being added.\n        nextAnimateTo = '100%';\n      }\n\n      scrollTo(nextAnimateTo, { behavior: 'smooth' });\n    }\n  }, [animateFromRef, scrollTo, targetRef]);\n\n  const handleScroll = useCallback(\n    ({ timeStampLow }: { timeStampLow: number }) => {\n      const { current: animateTo } = animateToRef;\n      const { current: target } = targetRef;\n\n      const animating = animateTo !== null;\n\n      // Currently, there are no reliable way to check if the \"scroll\" event is trigger due to\n      // user gesture, programmatic scrolling, or Chrome-synthesized \"scroll\" event to compensate size change.\n      // Thus, we use our best-effort to guess if it is triggered by user gesture, and disable sticky if it is heading towards the start direction.\n\n      if (timeStampLow <= ignoreScrollEventBeforeRef.current || !target) {\n        // Since we debounce \"scroll\" event, this handler might be called after spineTo.onEnd (a.k.a. artificial scrolling).\n        // We should ignore debounced event fired after scrollEnd, because without skipping them, the userInitiatedScroll calculated below will not be accurate.\n        // Thus, on a fast machine, adding elements super fast will lose the \"stickiness\".\n\n        return;\n      }\n\n      const { atEnd } = computeViewState({\n        target,\n      });\n\n      // Chrome will emit \"synthetic\" scroll event if the container is resized or an element is added\n      // We need to ignore these \"synthetic\" events\n      // Repro: In playground, press 4-1-5-1-1 (small, add one, normal, add one, add one)\n      //        Nomatter how fast or slow the sequence is being pressed, it should still stick to the bottom\n      const { offsetHeight: nextOffsetHeight, scrollHeight: nextScrollHeight } =\n        target;\n      const { current: offsetHeight } = offsetHeightRef;\n      const { current: scrollHeight } = scrollHeightRef;\n      const offsetHeightChanged = nextOffsetHeight !== offsetHeight;\n      const scrollHeightChanged = nextScrollHeight !== scrollHeight;\n\n      if (offsetHeightChanged) {\n        offsetHeightRef.current = nextOffsetHeight;\n      }\n\n      if (scrollHeightChanged) {\n        scrollHeightRef.current = nextScrollHeight;\n      }\n\n      // Sticky means:\n      // - If it is scrolled programatically, we are still in sticky mode\n      // - If it is scrolled by the user, then sticky means if we are at the end\n\n      // Only update stickiness if the scroll event is not due to synthetic scroll done by Chrome\n      if (!offsetHeightChanged && !scrollHeightChanged) {\n        // We are sticky if we are animating to the end, or we are already at the end.\n        // We can be \"animating but not sticky\" by calling \"scrollTo(100)\" where the container scrollHeight is 200px.\n        const nextSticky = (animating && animateTo === '100%') || atEnd;\n\n        if (stickyRef.current !== nextSticky) {\n          setSticky(nextSticky);\n        }\n      } else if (stickyRef.current) {\n        scrollToSticky();\n      }\n\n      const { scrollTop: actualScrollTop } = target;\n\n      scrollPositionObserversRef.current.forEach((observer) =>\n        observer({ scrollTop: actualScrollTop }),\n      );\n    },\n    [\n      animateToRef,\n      ignoreScrollEventBeforeRef,\n      offsetHeightRef,\n      scrollHeightRef,\n      scrollPositionObserversRef,\n      scrollToSticky,\n      setSticky,\n      stickyRef,\n      targetRef,\n    ],\n  );\n\n  useEffect(() => {\n    if (target) {\n      let stickyButNotAtEndSince: false | number = false;\n\n      const timeout = setImmediateInterval(\n        () => {\n          const { current: target } = targetRef;\n          const animating = animateToRef.current !== null;\n\n          if (stickyRef.current) {\n            if (!computeViewState({ target }).atEnd) {\n              if (!stickyButNotAtEndSince) {\n                stickyButNotAtEndSince = Date.now();\n              } else if (\n                Date.now() - stickyButNotAtEndSince >\n                SCROLL_DECISION_DURATION\n              ) {\n                // Quirks: In Firefox, after user scroll down, Firefox do two things:\n                //         1. Set to a new \"scrollTop\"\n                //         2. Fire \"scroll\" event\n                //         For what we observed, #1 is fired about 20ms before #2. There is a chance that this stickyCheckTimeout is being scheduled between 1 and 2.\n                //         That means, if we just look at #1 to decide if we should scroll, we will always scroll, in oppose to the user's intention.\n                // Repro: Open Firefox, set checkInterval to a lower number, and try to scroll by dragging the scroll handler. It will jump back.\n\n                // The \"animating\" check will make sure stickiness is not lost when elements are adding at a very fast pace.\n                if (!animating) {\n                  animateFromRef.current = target.scrollTop;\n                  scrollToSticky();\n                }\n\n                stickyButNotAtEndSince = false;\n              }\n            } else {\n              stickyButNotAtEndSince = false;\n            }\n          } else if (\n            target.scrollHeight <= target.offsetHeight &&\n            !stickyRef.current\n          ) {\n            // When the container is emptied, we will set sticky back to true.\n\n            setSticky(true);\n          }\n        },\n        Math.max(MIN_CHECK_INTERVAL, 100) || MIN_CHECK_INTERVAL,\n      );\n\n      return () => clearInterval(timeout);\n    }\n  }, [animateToRef, scrollToSticky, setSticky, stickyRef, target, targetRef]);\n\n  const internalContext = useMemo(\n    () => ({\n      setTarget,\n      target,\n    }),\n    [setTarget, target],\n  );\n\n  const functionContext = useMemo(\n    () => ({\n      scrollToBottom,\n    }),\n    [scrollToBottom],\n  );\n\n  useEffect(() => {\n    // We need to update the \"scrollHeight\" value to latest when the user do a focus inside the box.\n    //\n    // This is because:\n    // - In our code that mitigate Chrome synthetic scrolling, that code will look at whether \"scrollHeight\" value is latest or not.\n    // - That code only run on \"scroll\" event.\n    // - That means, on every \"scroll\" event, if the \"scrollHeight\" value is not latest, we will skip modifying the stickiness.\n    // - That means, if the user \"focus\" to an element that cause the scroll view to scroll to the bottom, the user agent will fire \"scroll\" event.\n    //   Since the \"scrollHeight\" is not latest value, this \"scroll\" event will be ignored and stickiness will not be modified.\n    // - That means, if the user \"focus\" to a newly added element that is at the end of the scroll view, the \"scroll to bottom\" button will continue to show.\n    //\n    // Repro in Chrome:\n    // 1. Fill up a scroll view\n    // 2. Scroll up, the \"scroll to bottom\" button should show up\n    // 3. Click \"Add a button\"\n    // 4. Click on the scroll view (to pseudo-focus on it)\n    // 5. Press TAB, the scroll view will be at the bottom\n    //\n    // Expect:\n    // - The \"scroll to bottom\" button should be gone.\n    if (target) {\n      const handleFocus = () => {\n        scrollHeightRef.current = target.scrollHeight;\n      };\n\n      target.addEventListener('focus', handleFocus, {\n        capture: true,\n        passive: true,\n      });\n\n      return () => target.removeEventListener('focus', handleFocus);\n    }\n  }, [target]);\n\n  return (\n    <InternalContext.Provider value={internalContext}>\n      <FunctionContext.Provider value={functionContext}>\n        {children}\n        {target && (\n          <EventSpy\n            debounce={17}\n            name=\"scroll\"\n            onEvent={handleScroll}\n            target={target}\n          />\n        )}\n        {target && animateTo !== null && (\n          <SpineTo\n            name=\"scrollTop\"\n            onEnd={handleSpineToEnd}\n            target={target}\n            value={animateTo}\n          />\n        )}\n      </FunctionContext.Provider>\n    </InternalContext.Provider>\n  );\n};\n\nexport default Composer;\n"
  },
  {
    "path": "client/src/components/ScrollToBottom/EventSpy.ts",
    "content": "import { useCallback, useLayoutEffect, useMemo, useRef } from 'react';\n\nimport debounceFn from './debounce';\n\ntype Props = {\n  debounce: number;\n  name: string;\n  onEvent: (p: { timeStampLow: number }) => void;\n  target: HTMLElement;\n};\n\nconst EventSpy = ({ debounce = 200, name, onEvent, target }: Props) => {\n  // We need to save the \"onEvent\" to ref.\n  // This is because \"onEvent\" may change from time to time, but debounce may still fire to the older callback.\n  const onEventRef = useRef<(p: { timeStampLow: number }) => void>();\n\n  onEventRef.current = onEvent;\n\n  const debouncer = useMemo(\n    () =>\n      debounceFn((event: any) => {\n        const { current } = onEventRef;\n\n        current && current(event);\n      }, debounce),\n    [debounce, onEventRef],\n  );\n\n  const handleEvent = useCallback(\n    (event: any) => {\n      event.timeStampLow = Date.now();\n\n      debouncer(event);\n    },\n    [debouncer],\n  );\n\n  useLayoutEffect(() => {\n    target.addEventListener(name, handleEvent, { passive: true });\n    handleEvent({ target, type: name });\n\n    return () => target.removeEventListener(name, handleEvent);\n  }, [name, handleEvent, target]);\n\n  return null;\n};\n\nexport default EventSpy;\n"
  },
  {
    "path": "client/src/components/ScrollToBottom/FunctionContext.ts",
    "content": "import React from 'react';\n\nconst context = React.createContext({\n  scrollToBottom: (b: { behavior?: 'smooth' | 'auto' }) => {},\n});\n\ncontext.displayName = 'ScrollToBottomFunctionContext';\n\nexport default context;\n"
  },
  {
    "path": "client/src/components/ScrollToBottom/InternalContext.ts",
    "content": "import React from 'react';\n\nconst context = React.createContext<{\n  setTarget: (v: any) => void;\n  target: HTMLDivElement | null;\n}>({\n  setTarget: (nextValue: any) => {},\n  target: null,\n});\n\ncontext.displayName = 'ScrollToBottomInternalContext';\n\nexport default context;\n"
  },
  {
    "path": "client/src/components/ScrollToBottom/Panel.tsx",
    "content": "import React, {\n  forwardRef,\n  PropsWithChildren,\n  useContext,\n  useImperativeHandle,\n} from 'react';\nimport InternalContext from './InternalContext';\n\nconst Panel = forwardRef(function PanelWithRef(\n  { children, className }: PropsWithChildren<{ className?: string }>,\n  forwardedRef,\n) {\n  const { setTarget, target } = useContext(InternalContext);\n\n  useImperativeHandle(forwardedRef, () => target, [target]);\n\n  return (\n    <div\n      className={`${className || ''} h-full w-full overflow-y-auto`}\n      ref={setTarget}\n    >\n      {children}\n    </div>\n  );\n});\n\nexport default Panel;\n"
  },
  {
    "path": "client/src/components/ScrollToBottom/SpineTo.tsx",
    "content": "import { useCallback, useLayoutEffect, useRef } from 'react';\n\nfunction squareStepper(current: number, to: number) {\n  const sign = Math.sign(to - current);\n  const step = Math.sqrt(Math.abs(to - current));\n  const next = current + step * sign;\n\n  if (sign > 0) {\n    return Math.min(to, next);\n  }\n\n  return Math.max(to, next);\n}\n\nfunction step(\n  from: number,\n  to: number,\n  stepper: (c: number, to: number) => number,\n  index: number,\n) {\n  let next = from;\n\n  for (let i = 0; i < index; i++) {\n    next = stepper(next, to);\n  }\n\n  return next;\n}\n\ntype Props = {\n  name: string;\n  onEnd: (b: boolean) => void;\n  target: any;\n  value: number | '100%';\n};\n\nconst SpineTo = ({ name, onEnd, target, value }: Props) => {\n  const animator = useRef<number>();\n\n  const animate = useCallback(\n    (\n      name: string,\n      from: number,\n      to: number | string,\n      index: number,\n      start = Date.now(),\n    ) => {\n      if (to === '100%' || typeof to === 'number') {\n        if (animator.current) {\n          cancelAnimationFrame(animator.current);\n        }\n\n        animator.current = requestAnimationFrame(() => {\n          if (target) {\n            const toNumber =\n              to === '100%' ? target.scrollHeight - target.offsetHeight : to;\n            let nextValue = step(\n              from,\n              toNumber,\n              squareStepper,\n              (Date.now() - start) / 5,\n            );\n\n            if (Math.abs(toNumber - nextValue) < 1.5) {\n              nextValue = toNumber;\n            }\n\n            target[name] = nextValue;\n\n            if (toNumber === nextValue) {\n              onEnd && onEnd(true);\n            } else {\n              animate(name, from, to, index + 1, start);\n            }\n          }\n        });\n      }\n    },\n    [animator, onEnd, target],\n  );\n\n  const handleCancelAnimation = useCallback(() => {\n    if (animator.current) {\n      cancelAnimationFrame(animator.current);\n    }\n    onEnd && onEnd(false);\n  }, [onEnd]);\n\n  useLayoutEffect(() => {\n    animate(name, target[name], value, 1);\n\n    if (target) {\n      target.addEventListener('pointerdown', handleCancelAnimation, {\n        passive: true,\n      });\n      target.addEventListener('wheel', handleCancelAnimation, {\n        passive: true,\n      });\n\n      return () => {\n        target.removeEventListener('pointerdown', handleCancelAnimation);\n        target.removeEventListener('wheel', handleCancelAnimation);\n        if (animator.current) {\n          cancelAnimationFrame(animator.current);\n        }\n      };\n    }\n\n    return () => animator.current && cancelAnimationFrame(animator.current);\n  }, [animate, animator, handleCancelAnimation, name, target, value]);\n\n  return null;\n};\n\nexport default SpineTo;\n"
  },
  {
    "path": "client/src/components/ScrollToBottom/debounce.ts",
    "content": "export default function (fn: (args: any) => void, ms: number) {\n  if (!ms) {\n    return fn;\n  }\n\n  let last = 0;\n  let timeout: number | null = null;\n\n  return (...args: any[]) => {\n    const now = Date.now();\n\n    if (now - last > ms) {\n      // @ts-ignore\n      fn(...args);\n      last = now;\n    } else {\n      if (timeout) {\n        clearTimeout(timeout);\n      }\n\n      timeout = window.setTimeout(\n        () => {\n          // @ts-ignore\n          fn(...args);\n          last = Date.now();\n        },\n        Math.max(0, ms - now + last),\n      );\n    }\n  };\n}\n"
  },
  {
    "path": "client/src/components/ScrollToBottom/index.tsx",
    "content": "import React, { memo, MutableRefObject, PropsWithChildren } from 'react';\nimport Composer from './Composer';\nimport Panel from './Panel';\n\ntype CoreProps = {\n  className: string;\n  wrapperRef?: MutableRefObject<HTMLDivElement | null>;\n};\n\nconst BasicScrollToBottomCore = ({\n  children,\n  className,\n  wrapperRef,\n}: PropsWithChildren<CoreProps>) => {\n  return (\n    <div className={`relative ${className}`}>\n      <Panel ref={wrapperRef}>{children}</Panel>\n    </div>\n  );\n};\n\ntype Props = {\n  className: string;\n  wrapperRef?: MutableRefObject<HTMLDivElement | null>;\n};\n\nconst BasicScrollToBottom = ({\n  children,\n  className,\n  wrapperRef,\n}: PropsWithChildren<Props>) => (\n  <Composer>\n    <BasicScrollToBottomCore className={className} wrapperRef={wrapperRef}>\n      {children}\n    </BasicScrollToBottomCore>\n  </Composer>\n);\n\nexport default memo(BasicScrollToBottom);\n"
  },
  {
    "path": "client/src/components/SearchOnPage/index.tsx",
    "content": "import React, {\n  ChangeEvent,\n  Dispatch,\n  memo,\n  SetStateAction,\n  useCallback,\n} from 'react';\nimport TextInput from '../TextInput';\nimport { ChevronDownIcon, ChevronUpIcon } from '../../icons';\n\ntype Props = {\n  handleSearch: (v: string) => void;\n  isSearchActive: boolean;\n  resultNum: number;\n  currentResult: number;\n  setCurrentResult: Dispatch<SetStateAction<number>>;\n  onCancel?: () => void;\n  searchValue: string;\n  containerClassName: string;\n};\n\nconst SearchOnPage = ({\n  handleSearch,\n  isSearchActive,\n  resultNum,\n  onCancel,\n  currentResult,\n  setCurrentResult,\n  searchValue,\n  containerClassName,\n}: Props) => {\n  const handleChange = useCallback(\n    (e: ChangeEvent<HTMLInputElement>) => {\n      handleSearch(e.target.value);\n    },\n    [handleSearch],\n  );\n\n  return isSearchActive ? (\n    <div\n      className={`z-50 bg-bg-sub/80 ${containerClassName}`}\n      style={{\n        backdropFilter: 'blur(1px)',\n        WebkitBackdropFilter: 'blur(1px)',\n      }}\n    >\n      <TextInput\n        type=\"search\"\n        id=\"app-search\"\n        name=\"app-search\"\n        autoFocus\n        value={searchValue}\n        onChange={handleChange}\n        forceClear\n        inputClassName=\"pr-24\"\n        onEscape={onCancel}\n      />\n      <div className=\"flex items-center absolute top-0 right-9 body-mini text-label-base\">\n        {resultNum ? (\n          <span>\n            {currentResult}/{resultNum}\n          </span>\n        ) : null}\n        <button\n          className=\"p-2 hover:text-label-title disabled:hover:text-label-base flex items-center \"\n          onClick={() =>\n            setCurrentResult((prev) => (prev > 1 ? prev - 1 : resultNum))\n          }\n          disabled={!searchValue}\n        >\n          <ChevronUpIcon sizeClassName=\"w-3.5 h-3.5\" />\n        </button>\n        <button\n          className=\"p-2 hover:text-label-title disabled:hover:text-label-base flex items-center \"\n          onClick={() =>\n            setCurrentResult((prev) => (prev < resultNum ? prev + 1 : 1))\n          }\n          disabled={!searchValue}\n        >\n          <ChevronDownIcon sizeClassName=\"w-3.5 h-3.5\" />\n        </button>\n      </div>\n    </div>\n  ) : null;\n};\n\nexport default memo(SearchOnPage);\n"
  },
  {
    "path": "client/src/components/SectionsNav/SectionButton.tsx",
    "content": "import React, { memo, useCallback } from 'react';\nimport { SettingsTypesSections } from '../../types/general';\n\ntype Props<T> = {\n  type: T;\n  label: string;\n  isActive: boolean;\n  handleClick: (t: T) => void;\n};\n\nconst SectionButton = <T extends SettingsTypesSections>({\n  type,\n  label,\n  handleClick,\n  isActive,\n}: Props<T>) => {\n  const onClick = useCallback(() => {\n    handleClick(type);\n  }, [type]);\n  return (\n    <div className=\"w-full pl-4\">\n      <button\n        onClick={onClick}\n        className={`h-8 px-2 rounded-6 body-s-b flex items-center ${\n          isActive ? 'bg-bg-shade text-label-title' : 'text-label-base'\n        } w-full text-left`}\n      >\n        {label}\n      </button>\n    </div>\n  );\n};\n\nexport default memo(SectionButton) as typeof SectionButton;\n"
  },
  {
    "path": "client/src/components/SectionsNav/index.tsx",
    "content": "import React, { Fragment, memo } from 'react';\nimport { SettingsTypesSections } from '../../types/general';\nimport SectionButton from './SectionButton';\n\ninterface Props<T> {\n  sections: {\n    Icon: (props: {\n      raw?: boolean | undefined;\n      sizeClassName?: string | undefined;\n      className?: string | undefined;\n    }) => JSX.Element;\n    title: string;\n    items: {\n      type: T;\n      label: string;\n      onClick: (t: T) => void;\n    }[];\n  }[];\n  activeItem: T;\n}\n\nfunction SectionsNav<T extends SettingsTypesSections>({\n  sections,\n  activeItem,\n}: Props<T>) {\n  return (\n    <div className=\"flex flex-col gap-1 flex-1\">\n      {sections.map(({ title, Icon, items }) => (\n        <Fragment key={title}>\n          <div className=\"flex items-center gap-2 h-9 text-label-muted w-56\">\n            <Icon sizeClassName=\"w-4 h-4\" />\n            <p className=\"body-s-b\">{title}</p>\n          </div>\n          {items.map((item) => (\n            <SectionButton<T>\n              key={item.label}\n              type={item.type}\n              label={item.label}\n              isActive={item.type === activeItem}\n              handleClick={item.onClick}\n            />\n          ))}\n        </Fragment>\n      ))}\n    </div>\n  );\n}\n\nconst genericMemo: <T>(\n  component: T,\n  propsAreEqual?: (\n    prevProps: React.PropsWithChildren<T>,\n    nextProps: React.PropsWithChildren<T>,\n  ) => boolean,\n) => T = memo;\n\nexport default genericMemo(SectionsNav);\n"
  },
  {
    "path": "client/src/components/TextField/index.tsx",
    "content": "import React from 'react';\n\ntype Props = {\n  value: string | React.ReactElement;\n  active?: boolean;\n  icon?: React.ReactElement;\n  className?: string;\n};\n\nconst TextField = ({ icon, value, className, active = true }: Props) => {\n  return (\n    <span className={`flex items-center gap-2 ${className ? className : ''}`}>\n      {icon}\n      <span className=\"ellipsis w-full leading-normal\">{value}</span>\n    </span>\n  );\n};\nexport default TextField;\n"
  },
  {
    "path": "client/src/components/TextInput/ClearButton/index.tsx",
    "content": "import { ButtonHTMLAttributes, DetailedHTMLProps } from 'react';\nimport { CloseSignIcon } from '../../../icons';\n\nconst Button = ({\n  className,\n  ...props\n}: DetailedHTMLProps<\n  ButtonHTMLAttributes<HTMLButtonElement>,\n  HTMLButtonElement\n>) => {\n  return (\n    <button\n      {...props}\n      type=\"button\"\n      className={`flex items-center justify-center outline-none outline-0 focus:outline-none w-5 mx-2.5 flex-shrink-0 ${\n        className || ''\n      } h-5 text-label-title p-0 border-none transition-all duration-300 ease-in-bounce bg-bg-base hover:bg-bg-base-hover rounded-xl`}\n    >\n      <CloseSignIcon sizeClassName=\"w-3.5 h-3.5\" />\n    </button>\n  );\n};\n\nexport default Button;\n"
  },
  {
    "path": "client/src/components/TextInput/RegexButton/index.tsx",
    "content": "import { useTranslation } from 'react-i18next';\nimport { RegexIcon } from '../../../icons';\nimport Tooltip from '../../Tooltip';\n\ntype Props = {\n  active: boolean;\n  clasName?: string;\n  onClick?: () => void;\n};\nconst RegexButton = ({ active, clasName, onClick }: Props) => {\n  const { t } = useTranslation();\n  return (\n    <Tooltip text={t('Search using RegExp')} placement={'bottom-end'}>\n      <button\n        onClick={onClick}\n        className={`\n       hover:text-label-title active:text-label-title\n      rounded-4 focus:outline-none outline-none outline-0 flex items-center p-1 \n     flex-grow-0 flex-shrink-0 h-6 w-6 justify-center transition-all duration-150 ease-in-bounce select-none ${clasName}\n     ${\n       active\n         ? 'bg-bg-main text-label-title hover:bg-bg-main'\n         : 'bg-transparent hover:bg-bg-base text-label-muted'\n     }`}\n      >\n        <RegexIcon raw sizeClassName=\"h-3 w-3\" />\n      </button>\n    </Tooltip>\n  );\n};\nexport default RegexButton;\n"
  },
  {
    "path": "client/src/components/TextInput/index.tsx",
    "content": "import {\n  ChangeEvent,\n  ForwardedRef,\n  forwardRef,\n  HTMLInputTypeAttribute,\n  KeyboardEvent,\n  ReactElement,\n  useRef,\n} from 'react';\nimport { CheckIcon, MagnifyToolIcon, MailIcon } from '../../icons';\nimport ClearButton from './ClearButton';\nimport RegexButton from './RegexButton';\n\ntype Props = {\n  value: string;\n  placeholder?: string;\n  label?: string;\n  helperText?: string;\n  id?: string;\n  name: string;\n  error?: string | null;\n  success?: boolean;\n  disabled?: boolean;\n  type?: HTMLInputTypeAttribute;\n  onSubmit?: (e: KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => void;\n  onEscape?: () => void;\n  onBlur?: () => void;\n  autoFocus?: boolean;\n  inputClassName?: string;\n  forceClear?: boolean;\n  noBorder?: boolean;\n  startIcon?: ReactElement;\n  endIcon?: ReactElement;\n  size?: 'small' | 'medium' | 'large';\n};\n\ntype SingleLineProps = Props & {\n  multiline?: false;\n  onChange: (e: ChangeEvent<HTMLInputElement>) => void;\n};\n\ntype MultilineProps = Props & {\n  onChange: (e: ChangeEvent<HTMLTextAreaElement>) => void;\n  multiline: true;\n};\n\nconst borderMap = {\n  filled: {\n    default:\n      'border-transparent hover:border-bg-border-hover focus-within:border-bg-border-hover',\n    error: 'border-bg-danger',\n    success: 'border-bg-border-hover',\n    disabled: 'border-bg-base',\n  },\n  outlined: {\n    default:\n      'border-bg-border hover:border-bg-border-hover focus-within:border-bg-border-hover',\n    error: 'border-bg-danger',\n    success: 'border-bg-border-hover',\n    disabled: 'border-bg-base',\n  },\n};\n\nconst sizesMap = {\n  small: { label: 'body-mini-b', container: 'h-7 px-2' },\n  medium: { label: 'body-s-b', container: 'h-8 pl-2.5 pr-2' },\n  large: { label: 'body-s-b', container: 'h-9 pl-3 pr-2.5' },\n};\n\nconst TextInput = forwardRef(function TextInputWithRef(\n  {\n    value,\n    onChange,\n    placeholder,\n    label,\n    helperText,\n    id,\n    name,\n    error,\n    success,\n    disabled,\n    type,\n    onSubmit,\n    onBlur,\n    multiline,\n    autoFocus,\n    inputClassName,\n    forceClear,\n    onEscape,\n    startIcon,\n    endIcon,\n    noBorder,\n    size = 'medium',\n  }: Props & (SingleLineProps | MultilineProps),\n  ref: ForwardedRef<HTMLInputElement>,\n) {\n  const inputRef = useRef<HTMLInputElement>(null);\n\n  const handleEnter = (\n    e: KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>,\n  ) => {\n    if (e.key === 'Enter' && onSubmit) {\n      e.preventDefault();\n      onSubmit(e);\n    } else if (e.key === 'Escape' && onEscape) {\n      e.stopPropagation();\n      onEscape();\n    }\n  };\n\n  return (\n    <div\n      className={`flex flex-col gap-2 w-full ${\n        disabled ? 'text-label-base' : 'text-label-title'\n      } body-base`}\n    >\n      {label || helperText ? (\n        <div className={`flex justify-between items-center w-full`}>\n          <label className={`${sizesMap[size].label}`}>{label}</label>\n          <span className={`text-label-base body-mini`}>{helperText}</span>\n        </div>\n      ) : null}\n      <div\n        className={`group border border-bg-border rounded bg-bg-base ${\n          multiline ? 'p-2' : sizesMap[size].container\n        } flex box-border items-center transition-all duration-150 ease-in-out relative`}\n      >\n        {type === 'email' || type === 'search' || startIcon ? (\n          <span\n            className={`w-3.5 mr-2.5 flex items-center flex-shrink-0 ${\n              disabled ? 'text-label-muted' : 'text-label-base'\n            } group-hover:text-label-title group-focus-within:text-label-title transition-all duration-150 ease-in-out`}\n          >\n            {startIcon ||\n              (type === 'email' ? (\n                <MailIcon sizeClassName=\"w-3.5 h-3.5\" />\n              ) : (\n                <MagnifyToolIcon sizeClassName=\"w-3.5 h-3.5\" />\n              ))}\n          </span>\n        ) : null}\n        {multiline ? (\n          <textarea\n            value={value}\n            onChange={onChange}\n            placeholder={placeholder}\n            id={id}\n            name={name}\n            disabled={disabled}\n            rows={6}\n            onBlur={onBlur}\n            autoComplete=\"off\"\n            spellCheck=\"false\"\n            className={`bg-transparent resize-none border-none focus:outline-none w-full \n            placeholder:text-label-muted disabled:placeholder:text-label-faint\n            transition-all duration-150 ease-in-out outline-none outline-0`}\n            onKeyDown={handleEnter}\n          />\n        ) : (\n          <input\n            value={value}\n            onChange={onChange}\n            placeholder={placeholder}\n            id={id}\n            name={name}\n            type={type}\n            disabled={disabled}\n            ref={ref || inputRef}\n            onBlur={onBlur}\n            autoComplete=\"off\"\n            spellCheck=\"false\"\n            className={`bg-transparent border-none focus:outline-none w-full\n            transition-all duration-150 ease-in-out outline-none outline-0 ${inputClassName}\n            placeholder:text-label-muted disabled:placeholder:text-label-faint`}\n            onKeyDown={handleEnter}\n            autoFocus={autoFocus}\n          />\n        )}\n        {(value || forceClear) && !multiline && !endIcon ? (\n          <ClearButton\n            tabIndex={-1}\n            onClick={() => {\n              if (!value && onEscape) {\n                onEscape();\n              } else {\n                onChange({\n                  target: { value: '', name },\n                } as ChangeEvent<HTMLInputElement>);\n                // @ts-ignore\n                (ref || inputRef).current?.focus();\n              }\n            }}\n            className={success ? 'group-focus-within:flex hidden' : 'flex'}\n          />\n        ) : null}\n        {success ? (\n          <span\n            className=\"w-5 mr-2.5 flex items-center group-focus-within:hidden text-bg-success right-0\n          top-1/2 -translate-y-1/2 absolute\"\n          >\n            <CheckIcon sizeClassName=\"w-5 h-5\" />\n          </span>\n        ) : null}\n        {endIcon}\n      </div>\n      {error ? <span className=\"text-red body-mini\">{error}</span> : null}\n    </div>\n  );\n});\n\nexport default TextInput;\n"
  },
  {
    "path": "client/src/components/TokenUsage/index.tsx",
    "content": "import { memo } from 'react';\n\ntype Props = {\n  percent: number;\n  sizeClassName?: string;\n};\n\ntype UsageProps = {\n  percent: number;\n};\n\nconst Usage = ({ percent }: UsageProps) => {\n  const r = 7;\n  const C = 2 * Math.PI * r;\n\n  function setPercentage(percentage: number) {\n    const filled = C * (percentage / 100);\n    const unfilled = C - filled;\n    return `${filled} ${unfilled}`;\n  }\n\n  const strokeDashArray = setPercentage(percent > 100 ? 100 : percent);\n\n  const map = [\n    { fill: 'fill-green', stroke: 'stroke-green' },\n    { fill: 'fill-yellow', stroke: 'stroke-yellow' },\n    { fill: 'fill-orange-600', stroke: 'stroke-orange-600' },\n    { fill: 'fill-red', stroke: 'stroke-red' },\n    { fill: 'fill-red', stroke: 'stroke-red' },\n  ];\n\n  const index = Math.max(Math.min(Math.floor(percent / 25), map.length - 1), 0);\n\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 20 20\"\n      fill=\"none\"\n      key=\"usage-med\"\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M10 16C13.3137 16 16 13.3137 16 10C16 6.6863 13.3137 4 10 4C6.6863 4 4 6.6863 4 10C4 13.3137 6.6863 16 10 16ZM10 18C14.4183 18 18 14.4183 18 10C18 5.58173 14.4183 2 10 2C5.58173 2 2 5.58173 2 10C2 14.4183 5.58173 18 10 18Z\"\n        className=\"fill-bg-border-hover\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M8.5267 8.5267C8.9174 8.136 9.4473 7.9165 9.99984 7.9165C10.5524 7.9165 11.0823 8.136 11.473 8.5267C11.8637 8.9174 12.0832 9.4473 12.0832 9.99984C12.0832 10.5524 11.8637 11.0823 11.473 11.473C11.0823 11.8637 10.5524 12.0832 9.99984 12.0832C9.4473 12.0832 8.9174 11.8637 8.5267 11.473C8.136 11.0823 7.9165 10.5524 7.9165 9.99984C7.9165 9.4473 8.136 8.9174 8.5267 8.5267Z\"\n        className={map[index]?.fill}\n      />\n      <mask id=\"path-3-inside-1_10650_70292\" fill=\"white\">\n        <path d=\"M10 2.99702C10 2.44638 10.4481 1.9936 10.9945 2.06205C11.8767 2.17257 12.7367 2.42971 13.5383 2.82502C14.6389 3.36775 15.5998 4.15638 16.3468 5.12991C17.0938 6.10343 17.6069 7.23576 17.8463 8.43928C18.0857 9.6428 18.045 10.8853 17.7274 12.0706C17.4098 13.2558 16.8238 14.3522 16.0147 15.2748C15.2056 16.1974 14.1952 16.9214 13.0615 17.391C11.9278 17.8606 10.7012 18.0631 9.47677 17.9829C8.58491 17.9244 7.71154 17.7171 6.89224 17.3717C6.38485 17.1578 6.22319 16.5416 6.49851 16.0648C6.77383 15.5879 7.38198 15.4333 7.89779 15.626C8.44498 15.8305 9.02076 15.9547 9.60719 15.9931C10.5265 16.0534 11.4473 15.9013 12.2984 15.5488C13.1495 15.1962 13.9081 14.6526 14.5155 13.96C15.1229 13.2674 15.5629 12.4443 15.8013 11.5545C16.0398 10.6646 16.0703 9.73183 15.8906 8.82829C15.7108 7.92475 15.3257 7.07467 14.7649 6.3438C14.204 5.61293 13.4826 5.02087 12.6564 4.61341C12.1293 4.35348 11.5686 4.17313 10.9925 4.0766C10.4494 3.98561 10 3.54765 10 2.99702Z\" />\n      </mask>\n      <circle\n        cx=\"50%\"\n        cy=\"50%\"\n        r=\"7\"\n        fill=\"transparent\"\n        className={map[index]?.stroke}\n        strokeWidth=\"2\"\n        strokeDasharray={strokeDashArray}\n        strokeDashoffset=\"10.9955\"\n      ></circle>\n      <defs>\n        <radialGradient\n          id=\"paint0_radial_10650_70292\"\n          cx=\"0\"\n          cy=\"0\"\n          r=\"1\"\n          gradientUnits=\"userSpaceOnUse\"\n          gradientTransform=\"translate(5 6.5) rotate(66.5014) scale(12.5399)\"\n        >\n          <stop stopColor=\"#C7374D\" />\n          <stop offset=\"1\" stopColor=\"#C73790\" />\n        </radialGradient>\n      </defs>\n    </svg>\n  );\n};\n\nconst TokenUsage = ({ percent, sizeClassName }: Props) => {\n  return (\n    <div className={sizeClassName || 'w-5 h-5'}>\n      <Usage percent={percent} />\n    </div>\n  );\n};\n\nexport default memo(TokenUsage);\n"
  },
  {
    "path": "client/src/components/Tooltip/index.tsx",
    "content": "import React, { PropsWithChildren, useRef } from 'react';\nimport Tippy, { TippyProps } from '@tippyjs/react/headless';\nimport useShortcuts from '../../hooks/useShortcuts';\n\ntype Props = {\n  text: string | React.ReactNode;\n  shortcut?: string[];\n  placement: TippyProps['placement'];\n  delay?: TippyProps['delay'];\n  wrapperClassName?: string;\n  variant?: 'contrast' | 'standard';\n  appendTo?: TippyProps['appendTo'];\n};\n\nconst Tooltip = ({\n  text,\n  placement,\n  children,\n  delay,\n  shortcut,\n  wrapperClassName,\n  variant = 'contrast',\n  appendTo,\n}: PropsWithChildren<Props>) => {\n  const ref = useRef<HTMLDivElement>(null);\n  const shortcutSymbols = useShortcuts(shortcut);\n\n  return (\n    <Tippy\n      placement={placement}\n      delay={delay}\n      appendTo={appendTo}\n      render={(attrs) => (\n        <span\n          {...attrs}\n          className={`inline-flex items-center gap-1.5 flex-shrink-0 rounded ${\n            variant === 'contrast'\n              ? `w-fit h-6 bg-bg-contrast text-label-contrast pl-1.5 ${\n                  shortcutSymbols ? 'pr-0.5' : 'pr-1.5'\n                }`\n              : 'bg-bg-shade text-label-base border border-bg-border p-3'\n          } shadow-medium body-mini z-50`}\n        >\n          {text}\n          {!!shortcutSymbols && (\n            <span\n              className={`inline-flex h-5 px-1 flex-shrink-0 items-center justify-center gap-1 \n              rounded border border-bg-border bg-bg-base shadow-low text-label-base body-mini`}\n            >\n              {shortcutSymbols.join(' ')}\n            </span>\n          )}\n        </span>\n      )}\n    >\n      <div ref={ref} className={wrapperClassName}>\n        {children}\n      </div>\n    </Tippy>\n  );\n};\nexport default Tooltip;\n"
  },
  {
    "path": "client/src/consts/animations.ts",
    "content": "export const MODAL_APPEAR_ANIMATION = {\n  duration: 0.15,\n  ease: 'easeOut',\n};\n"
  },
  {
    "path": "client/src/consts/code.ts",
    "content": "export const MAX_LINES_BEFORE_VIRTUALIZE = 1200;\n\nexport const highlightColors = [\n  [253, 201, 0],\n  [14, 164, 233],\n  [236, 72, 153],\n  [132, 204, 23],\n  [139, 92, 246],\n  [20, 184, 166],\n  [247, 129, 102],\n  [23, 185, 120],\n  [204, 110, 23],\n  [251, 161, 183],\n  [174, 216, 204],\n  [246, 250, 112],\n  [66, 72, 116],\n  [232, 69, 69],\n  [134, 150, 254],\n  [236, 179, 101],\n  [145, 49, 117],\n  [0, 234, 211],\n];\n\nexport const CODE_LINE_HEIGHT = 20;\n"
  },
  {
    "path": "client/src/consts/codeStudio.ts",
    "content": "export const TOKEN_LIMIT = 21000;\n"
  },
  {
    "path": "client/src/consts/commandBar.ts",
    "content": "export const INITIAL = 'initial';\nexport const PRIVATE_REPOS = 'private_repos';\nexport const PUBLIC_REPOS = 'public_repos';\nexport const LOCAL_REPOS = 'local_repos';\nexport const DOCUMENTATION = 'documentation';\n"
  },
  {
    "path": "client/src/consts/general.ts",
    "content": "import { LocaleType, SyncStatus } from '../types/general';\n\nexport const themesMap = {\n  system: 'System Preference',\n  dark: 'Dark',\n  light: 'Light',\n  black: 'Black',\n};\n\nexport const localesMap: Record<LocaleType, { name: string; icon: string }> = {\n  en: { name: 'English', icon: '🇬🇧' },\n  ja: { name: '日本', icon: '🇯🇵' },\n  zhCN: { name: '简体中文', icon: '🇨🇳' },\n  es: { name: 'Español', icon: '🇪🇸' },\n  it: { name: 'Italiano', icon: '🇮🇹' },\n  zhTW: { name: '繁體中文', icon: '🇹🇼' },\n};\n\nexport const repoStatusMap = {\n  [SyncStatus.Error]: { text: 'Error', color: 'bg-red-500' },\n  [SyncStatus.Removed]: { text: 'Removed', color: 'bg-red-500' },\n  [SyncStatus.Uninitialized]: { text: 'Not synced', color: 'bg-bg-shade' },\n  [SyncStatus.Queued]: { text: 'Queued...', color: 'bg-bg-shade' },\n  [SyncStatus.Cancelled]: { text: 'Cancelled', color: 'bg-bg-shade' },\n  [SyncStatus.Cancelling]: { text: 'Cancelling...', color: 'bg-yellow' },\n  [SyncStatus.Indexing]: { text: 'Indexing', color: 'bg-yellow' },\n  [SyncStatus.Syncing]: { text: 'Cloning', color: 'bg-yellow' },\n  [SyncStatus.Done]: { text: 'Last updated ', color: 'bg-green-500' },\n  [SyncStatus.RemoteRemoved]: { text: 'Remote removed ', color: 'bg-red-500' },\n};\n"
  },
  {
    "path": "client/src/consts/shortcuts.ts",
    "content": "export const regexToggleShortcut = ['cmd', '/'];\nexport const newChatTabShortcut = ['cmd', 'N'];\nexport const newStudioTabShortcut = ['cmd', 'shift', 'N'];\nexport const explainFileShortcut = ['cmd', 'E'];\nexport const addToStudioShortcut = ['cmd', 'shift', '+'];\nexport const removeFromStudioShortcut = ['cmd', 'shift', '-'];\nexport const useTemplateShortcut = ['cmd', 'T'];\nexport const openInSplitViewShortcut = ['cmd', ']'];\nexport const escapeShortcut = ['Esc'];\nexport const saveShortcut = ['cmd', 'S'];\nexport const selectLinesShortcut = ['cmd', 'L'];\nexport const newProjectShortcut = ['option', 'N'];\nexport const closeTabShortcut = ['cmd', 'W'];\n"
  },
  {
    "path": "client/src/consts/tutorialSteps.ts",
    "content": "export const tutorialSteps = [\n  {\n    title: 'Adding repository',\n    description: 'Projects require at least one synced repository.',\n    hint: ['Start by selecting ', 'Add new repository'],\n  },\n  {\n    title: 'Repository types',\n    description:\n      'You can add 3 types of repositories, private, public and local.',\n    hint: ['Start by selecting a type repository you would like to index.'],\n  },\n  {\n    title: 'Indexing repositories',\n    description:\n      'Bloop needs to index your repository first. This process takes a few seconds and happens only one time per repository.',\n    hint: [\n      'Start by selecting a repository and pressing Enter (↵) on your keyboard.',\n    ],\n  },\n  {\n    title: 'Indexing in progress',\n    description:\n      '{{repoName}} is currently indexing as soon as it is finished you will be able to add it to your project.',\n    hint: [],\n  },\n  {\n    title: 'Add to project',\n    description:\n      '{{repoName}} has finished indexing and you can use it in your projects now.',\n    hint: ['Start by selecting again and pressing Enter (↵) on your keyboard.'],\n  },\n];\n"
  },
  {
    "path": "client/src/consts/validations.ts",
    "content": "export const EMAIL_REGEX = /^[\\w-.+]+@([\\w-]+\\.)+[\\w-]{2,8}$/;\n"
  },
  {
    "path": "client/src/context/arrowNavigationContext.ts",
    "content": "import { createContext } from 'react';\n\nexport const ArrowNavigationContext = createContext({\n  focusedIndex: '',\n  setFocusedIndex: (s: string) => {},\n  handleClose: () => {},\n});\n"
  },
  {
    "path": "client/src/context/chatsContext.tsx",
    "content": "import { createContext, Dispatch, SetStateAction } from 'react';\nimport { ChatMessage, InputValueType, ParsedQueryType } from '../types/general';\n\nexport type ChatContext = {\n  conversation: ChatMessage[];\n  setConversation: Dispatch<SetStateAction<ChatMessage[]>>;\n  inputValue: InputValueType;\n  setInputValue: Dispatch<SetStateAction<InputValueType>>;\n  selectedLines: [number, number] | null;\n  setSelectedLines: Dispatch<SetStateAction<[number, number] | null>>;\n  submittedQuery: InputValueType;\n  setSubmittedQuery: Dispatch<SetStateAction<InputValueType>>;\n  isLoading: boolean;\n  hideMessagesFrom: null | number;\n  queryIdToEdit: string;\n  inputImperativeValue: InputValueType;\n  threadId: string;\n  setThreadId: Dispatch<SetStateAction<string>>;\n  stopGenerating: () => void;\n  onMessageEditCancel: () => void;\n  onMessageEdit: (parentQueryId: string, i: number) => void;\n  setInputValueImperatively: (value: ParsedQueryType[] | string) => void;\n  isDeprecatedModalOpen: boolean;\n  closeDeprecatedModal: () => void;\n};\n\ntype ContextType = {\n  chats: Record<string, ChatContext>;\n  setChats: Dispatch<SetStateAction<Record<string, ChatContext>>>;\n};\n\nexport const ChatsContext = createContext<ContextType>({\n  chats: {},\n  setChats: () => {},\n});\n"
  },
  {
    "path": "client/src/context/commandBarContext.ts",
    "content": "import { createContext, Dispatch, SetStateAction } from 'react';\nimport {\n  CommandBarActiveStepType,\n  CommandBarItemInvisibleType,\n  CommandBarItemGeneralType,\n  CommandBarStepEnum,\n} from '../types/general';\n\ntype FooterValuesContext = {\n  focusedItem: (CommandBarItemGeneralType | CommandBarItemInvisibleType) | null;\n};\n\ntype HandlersContext = {\n  setFocusedItem: Dispatch<\n    SetStateAction<\n      CommandBarItemGeneralType | CommandBarItemInvisibleType | null\n    >\n  >;\n  setChosenStep: Dispatch<SetStateAction<CommandBarActiveStepType>>;\n  setIsVisible: Dispatch<SetStateAction<boolean>>;\n  setFocusedTabItems: Dispatch<SetStateAction<CommandBarItemGeneralType[]>>;\n};\n\ntype GeneralContextType = {\n  isVisible: boolean;\n};\n\nexport const CommandBarContext = {\n  FooterValues: createContext<FooterValuesContext>({\n    focusedItem: null,\n  }),\n  Handlers: createContext<HandlersContext>({\n    setChosenStep: () => {},\n    setFocusedItem: () => {},\n    setIsVisible: () => {},\n    setFocusedTabItems: () => {},\n  }),\n  General: createContext<GeneralContextType>({\n    isVisible: false,\n  }),\n  CurrentStep: createContext<{ chosenStep: CommandBarActiveStepType }>({\n    chosenStep: {\n      id: CommandBarStepEnum.INITIAL,\n    },\n  }),\n  FocusedTab: createContext<{\n    tabItems: CommandBarItemGeneralType[];\n  }>({\n    tabItems: [],\n  }),\n};\n"
  },
  {
    "path": "client/src/context/deviceContext.ts",
    "content": "import { createContext } from 'react';\n\nexport type DeviceContextType = {\n  openFolderInExplorer: (p: string) => void;\n  openLink: (p: string) => void;\n  chooseFolder: (conf: {\n    defaultPath?: string;\n    directory?: boolean;\n    multiple?: boolean;\n  }) => Promise<null | string | string[]>;\n  homeDir: string;\n  listen: (\n    e: string,\n    cb: (event: { payload: { message: string } }) => void,\n  ) => void;\n  os: {\n    arch: string;\n    type: string;\n    platform: string;\n    version: string;\n  };\n  invokeTauriCommand: (c: string, payload?: any) => Promise<any>;\n  relaunch: () => void;\n  release: string;\n  isSelfServe: boolean;\n  showNativeMessage: (m: string, options?: any) => Promise<void> | void;\n};\n\nexport const DeviceContext = createContext<DeviceContextType>({\n  openFolderInExplorer: (p) => {},\n  openLink: (p) => {},\n  chooseFolder: (conf) => Promise.resolve(null),\n  homeDir: '$HOME',\n  listen: () => {},\n  os: {\n    arch: '',\n    type: '',\n    platform: '',\n    version: '',\n  },\n  invokeTauriCommand: () => Promise.resolve(''),\n  relaunch: () => {},\n  release: '0.0.0',\n  isSelfServe: false,\n  showNativeMessage: () => Promise.resolve(),\n});\n"
  },
  {
    "path": "client/src/context/fileHighlightsContext.ts",
    "content": "import React, { createContext } from 'react';\nimport { FileHighlightsType } from '../types/general';\n\ntype ContextTypeValues = {\n  fileHighlights: FileHighlightsType;\n  hoveredLines: [number, number] | null;\n};\ntype ContextTypeSetters = {\n  setFileHighlights: React.Dispatch<React.SetStateAction<FileHighlightsType>>;\n  setHoveredLines: React.Dispatch<\n    React.SetStateAction<[number, number] | null>\n  >;\n};\n\nexport const FileHighlightsContext = {\n  Values: createContext<ContextTypeValues>({\n    fileHighlights: {},\n    hoveredLines: null,\n  }),\n  Setters: createContext<ContextTypeSetters>({\n    setFileHighlights: () => {},\n    setHoveredLines: () => {},\n  }),\n};\n"
  },
  {
    "path": "client/src/context/localeContext.ts",
    "content": "import React, { createContext } from 'react';\nimport { LocaleType } from '../types/general';\n\ntype ContextType = {\n  locale: LocaleType;\n  setLocale: React.Dispatch<React.SetStateAction<LocaleType>>;\n};\n\nexport const LocaleContext = createContext<ContextType>({\n  locale: 'en',\n  setLocale: () => {},\n});\n"
  },
  {
    "path": "client/src/context/projectContext.ts",
    "content": "import { createContext, Dispatch, SetStateAction } from 'react';\nimport { ProjectFullType, ProjectShortType } from '../types/api';\n\nexport const ProjectContext = {\n  Current: createContext<{\n    project: ProjectFullType | null;\n    isReposLoaded: boolean;\n    isChatsLoaded: boolean;\n    isStudiosLoaded: boolean;\n    isDocsLoaded: boolean;\n    isLoading: boolean;\n    setCurrentProjectId: (id: string) => void;\n    refreshCurrentProject: () => void;\n    refreshCurrentProjectRepos: () => void;\n    refreshCurrentProjectConversations: () => void;\n    refreshCurrentProjectStudios: () => void;\n    refreshCurrentProjectDocs: () => void;\n  }>({\n    project: null,\n    isReposLoaded: false,\n    isChatsLoaded: false,\n    isStudiosLoaded: false,\n    isDocsLoaded: false,\n    isLoading: true,\n    setCurrentProjectId: (id: string) => {},\n    refreshCurrentProject: () => {},\n    refreshCurrentProjectRepos: () => {},\n    refreshCurrentProjectConversations: () => {},\n    refreshCurrentProjectStudios: () => {},\n    refreshCurrentProjectDocs: () => {},\n  }),\n  All: createContext<{\n    projects: ProjectShortType[];\n    refreshAllProjects: () => void;\n  }>({\n    projects: [],\n    refreshAllProjects: () => {},\n  }),\n  RegexSearch: createContext<{\n    isRegexSearchEnabled: boolean;\n    setIsRegexSearchEnabled: Dispatch<SetStateAction<boolean>>;\n  }>({\n    isRegexSearchEnabled: false,\n    setIsRegexSearchEnabled: () => {},\n  }),\n};\n"
  },
  {
    "path": "client/src/context/providers/ChatsContextProvider.tsx",
    "content": "import { memo, PropsWithChildren, useMemo, useState } from 'react';\nimport { ChatContext, ChatsContext } from '../chatsContext';\n\ntype Props = {};\n\nconst ChatsContextProvider = ({ children }: PropsWithChildren<Props>) => {\n  const [chats, setChats] = useState<Record<string, ChatContext>>({});\n\n  const contextValue = useMemo(\n    () => ({\n      chats,\n      setChats,\n    }),\n    [chats],\n  );\n  return (\n    <ChatsContext.Provider value={contextValue}>\n      {children}\n    </ChatsContext.Provider>\n  );\n};\n\nexport default memo(ChatsContextProvider);\n"
  },
  {
    "path": "client/src/context/providers/CommandBarContextProvider.tsx",
    "content": "import { memo, PropsWithChildren, useMemo, useState } from 'react';\nimport { CommandBarContext } from '../commandBarContext';\nimport {\n  CommandBarActiveStepType,\n  CommandBarItemGeneralType,\n  CommandBarItemInvisibleType,\n  CommandBarStepEnum,\n} from '../../types/general';\n\ntype Props = {};\n\nconst CommandBarContextProvider = ({ children }: PropsWithChildren<Props>) => {\n  const [focusedItem, setFocusedItem] = useState<\n    CommandBarItemGeneralType | CommandBarItemInvisibleType | null\n  >(null);\n  const [isVisible, setIsVisible] = useState(false);\n  const [chosenStep, setChosenStep] = useState<CommandBarActiveStepType>({\n    id: CommandBarStepEnum.INITIAL,\n  });\n  const [focusedTabItems, setFocusedTabItems] = useState<\n    CommandBarItemGeneralType[]\n  >([]);\n\n  const currentStepContextValue = useMemo(() => ({ chosenStep }), [chosenStep]);\n\n  const footerValuesContextValue = useMemo(() => {\n    return {\n      focusedItem,\n    };\n  }, [focusedItem]);\n\n  const handlersContextValue = useMemo(() => {\n    return {\n      setFocusedItem,\n      setChosenStep,\n      setIsVisible,\n      setFocusedTabItems,\n    };\n  }, []);\n\n  const generalContextValue = useMemo(() => {\n    return {\n      isVisible,\n    };\n  }, [isVisible]);\n\n  const tabContextValue = useMemo(\n    () => ({\n      tabItems: focusedTabItems,\n    }),\n    [focusedTabItems],\n  );\n\n  return (\n    <CommandBarContext.Handlers.Provider value={handlersContextValue}>\n      <CommandBarContext.General.Provider value={generalContextValue}>\n        <CommandBarContext.CurrentStep.Provider value={currentStepContextValue}>\n          <CommandBarContext.FooterValues.Provider\n            value={footerValuesContextValue}\n          >\n            <CommandBarContext.FocusedTab.Provider value={tabContextValue}>\n              {children}\n            </CommandBarContext.FocusedTab.Provider>\n          </CommandBarContext.FooterValues.Provider>\n        </CommandBarContext.CurrentStep.Provider>\n      </CommandBarContext.General.Provider>\n    </CommandBarContext.Handlers.Provider>\n  );\n};\n\nexport default memo(CommandBarContextProvider);\n"
  },
  {
    "path": "client/src/context/providers/DeviceContextProvider.tsx",
    "content": "import { memo, PropsWithChildren } from 'react';\nimport { DeviceContext, DeviceContextType } from '../deviceContext';\n\ntype Props = {\n  deviceContextValue: DeviceContextType;\n};\n\nexport const DeviceContextProvider = memo(\n  ({ children, deviceContextValue }: PropsWithChildren<Props>) => {\n    return (\n      <DeviceContext.Provider value={deviceContextValue}>\n        {children}\n      </DeviceContext.Provider>\n    );\n  },\n);\n\nDeviceContextProvider.displayName = 'DeviceContextProvider';\n"
  },
  {
    "path": "client/src/context/providers/FileHighlightsContextProvider.tsx",
    "content": "import React, { memo, PropsWithChildren, useMemo, useState } from 'react';\nimport { FileHighlightsContext } from '../fileHighlightsContext';\nimport { FileHighlightsType } from '../../types/general';\n\nexport const FileHighlightsContextProvider = memo(\n  ({ children }: PropsWithChildren) => {\n    const [fileHighlights, setFileHighlights] = useState<FileHighlightsType>(\n      {},\n    );\n    const [hoveredLines, setHoveredLines] = useState<[number, number] | null>(\n      null,\n    );\n\n    const valuesContextValue = useMemo(\n      () => ({\n        fileHighlights,\n        hoveredLines,\n      }),\n      [fileHighlights, hoveredLines],\n    );\n\n    const settersContextValue = useMemo(\n      () => ({\n        setFileHighlights,\n        setHoveredLines,\n      }),\n      [],\n    );\n    return (\n      <FileHighlightsContext.Setters.Provider value={settersContextValue}>\n        <FileHighlightsContext.Values.Provider value={valuesContextValue}>\n          {children}\n        </FileHighlightsContext.Values.Provider>\n      </FileHighlightsContext.Setters.Provider>\n    );\n  },\n);\n\nFileHighlightsContextProvider.displayName = 'FileHighlightsContextProvider';\n"
  },
  {
    "path": "client/src/context/providers/ProjectContextProvider.tsx",
    "content": "import {\n  memo,\n  PropsWithChildren,\n  useCallback,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { useLocation, useNavigate } from 'react-router-dom';\nimport { ProjectContext } from '../projectContext';\nimport {\n  getPlainFromStorage,\n  PROJECT_KEY,\n  savePlainToStorage,\n} from '../../services/storage';\nimport {\n  createProject,\n  getAllProjects,\n  getCodeStudios,\n  getProject,\n  getProjectConversations,\n  getProjectDocs,\n  getProjectRepos,\n} from '../../services/api';\nimport { ProjectFullType, ProjectShortType } from '../../types/api';\nimport { filterOutDuplicates } from '../../utils/mappers';\n\ntype Props = {};\n\nconst ProjectContextProvider = ({ children }: PropsWithChildren<Props>) => {\n  const { t } = useTranslation();\n  const [currentProjectId, setCurrentProjectId] = useState(\n    getPlainFromStorage(PROJECT_KEY) || '',\n  );\n  const [project, setProject] = useState<ProjectFullType | null>(null);\n  const [projects, setProjects] = useState<ProjectShortType[]>([]);\n  const [isReposLoaded, setIsReposLoaded] = useState(false);\n  const [isChatsLoaded, setIsChatsLoaded] = useState(false);\n  const [isStudiosLoaded, setIsStudiosLoaded] = useState(false);\n  const [isDocsLoaded, setIsDocsLoaded] = useState(false);\n  const [isRegexSearchEnabled, setIsRegexSearchEnabled] = useState(false);\n  const [isLoading, setIsLoading] = useState(true);\n  const navigate = useNavigate();\n  const location = useLocation();\n\n  const refreshCurrentProjectRepos = useCallback(async () => {\n    getProjectRepos(currentProjectId).then((r) => {\n      setProject((prev) => (prev ? { ...prev, repos: r } : null));\n      setIsReposLoaded(true);\n    });\n    refreshCurrentProjectStudios();\n  }, [currentProjectId]);\n  const refreshCurrentProjectConversations = useCallback(async () => {\n    getProjectConversations(currentProjectId).then((r) => {\n      setProject((prev) => (prev ? { ...prev, conversations: r } : null));\n      setIsChatsLoaded(true);\n    });\n  }, [currentProjectId]);\n  const refreshCurrentProjectStudios = useCallback(async () => {\n    getCodeStudios(currentProjectId).then((r) => {\n      setProject((prev) =>\n        prev\n          ? {\n              ...prev,\n              // as a precaution filter out duplicated studios\n              studios: filterOutDuplicates(r, 'id'),\n            }\n          : null,\n      );\n      setIsStudiosLoaded(true);\n    });\n  }, [currentProjectId]);\n  const refreshCurrentProjectDocs = useCallback(async () => {\n    getProjectDocs(currentProjectId).then((r) => {\n      setProject((prev) => (prev ? { ...prev, docs: r } : null));\n      setIsDocsLoaded(true);\n    });\n  }, [currentProjectId]);\n\n  useEffect(() => {\n    if (\n      currentProjectId &&\n      !isLoading &&\n      location.pathname !== `/project/${currentProjectId}`\n    ) {\n      navigate(`/project/${currentProjectId}`);\n    }\n  }, [currentProjectId]);\n\n  useEffect(() => {\n    setIsReposLoaded(false);\n    setIsChatsLoaded(false);\n    setIsStudiosLoaded(false);\n    setIsDocsLoaded(false);\n  }, [currentProjectId]);\n\n  useEffect(() => {\n    if (location.pathname === '/') {\n      setIsLoading(false);\n      return;\n    }\n    if (isLoading && projects?.length) {\n      const firstPart = decodeURIComponent(\n        location.pathname.slice(1).split('/')[1],\n      );\n      const proj = projects.find((p) => p.id.toString() === firstPart);\n      if (proj) {\n        setCurrentProjectId(proj.id);\n      }\n      setIsLoading(false);\n    }\n  }, [projects, isLoading]);\n\n  const refreshCurrentProject = useCallback(() => {\n    if (currentProjectId) {\n      getProject(currentProjectId)\n        .then((p) => {\n          setProject((prev) => ({\n            ...prev,\n            ...p,\n            repos: prev?.repos || [],\n            studios: prev?.studios || [],\n            conversations: prev?.conversations || [],\n            docs: prev?.docs || [],\n          }));\n          refreshCurrentProjectRepos();\n          refreshCurrentProjectConversations();\n          refreshCurrentProjectStudios();\n          refreshCurrentProjectDocs();\n        })\n        .catch((err) => {\n          console.log(err);\n          setCurrentProjectId('');\n        });\n    }\n  }, [currentProjectId]);\n\n  useEffect(() => {\n    if (currentProjectId) {\n      savePlainToStorage(PROJECT_KEY, currentProjectId);\n      refreshCurrentProject();\n    }\n  }, [currentProjectId, refreshCurrentProject]);\n\n  const refreshAllProjects = useCallback(() => {\n    getAllProjects().then((p) => {\n      setProjects(p);\n      if (!currentProjectId && p[0]) {\n        setCurrentProjectId(p[0].id);\n      }\n      if (!p.length) {\n        createProject(t('Default project')).then((newId) => {\n          setCurrentProjectId(newId);\n          getAllProjects().then((p) => {\n            setProjects(p);\n          });\n        });\n      }\n    });\n  }, []);\n\n  useEffect(() => {\n    refreshAllProjects();\n  }, []);\n\n  const currentValue = useMemo(\n    () => ({\n      project,\n      isReposLoaded,\n      isChatsLoaded,\n      isStudiosLoaded,\n      isDocsLoaded,\n      setCurrentProjectId,\n      refreshCurrentProjectRepos,\n      refreshCurrentProjectConversations,\n      refreshCurrentProjectStudios,\n      refreshCurrentProjectDocs,\n      refreshCurrentProject,\n      isLoading,\n    }),\n    [\n      project,\n      isReposLoaded,\n      isChatsLoaded,\n      isStudiosLoaded,\n      isDocsLoaded,\n      refreshCurrentProjectRepos,\n      refreshCurrentProjectConversations,\n      refreshCurrentProjectStudios,\n      refreshCurrentProjectDocs,\n      refreshCurrentProject,\n      isLoading,\n    ],\n  );\n\n  const allValue = useMemo(\n    () => ({\n      projects,\n      refreshAllProjects,\n    }),\n    [projects, refreshAllProjects],\n  );\n\n  const regexValue = useMemo(\n    () => ({\n      isRegexSearchEnabled,\n      setIsRegexSearchEnabled,\n    }),\n    [isRegexSearchEnabled],\n  );\n\n  return (\n    <ProjectContext.Current.Provider value={currentValue}>\n      <ProjectContext.All.Provider value={allValue}>\n        <ProjectContext.RegexSearch.Provider value={regexValue}>\n          {children}\n        </ProjectContext.RegexSearch.Provider>\n      </ProjectContext.All.Provider>\n    </ProjectContext.Current.Provider>\n  );\n};\n\nexport default memo(ProjectContextProvider);\n"
  },
  {
    "path": "client/src/context/providers/RepositoriesContextProvider.tsx",
    "content": "import {\n  memo,\n  PropsWithChildren,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { toast } from 'sonner';\nimport { Trans } from 'react-i18next/TransWithoutContext';\nimport { RepositoriesContext } from '../repositoriesContext';\nimport { IndexingStatusType, SyncStatus } from '../../types/general';\nimport { splitPath } from '../../utils';\nimport { RepositoryIcon } from '../../icons';\nimport { ProjectContext } from '../projectContext';\nimport {\n  addRepoToProject,\n  API_BASE_URL,\n  getIndexedRepos,\n} from '../../services/api';\n\ntype Props = {};\n\nconst RepositoriesContextProvider = ({\n  children,\n}: PropsWithChildren<Props>) => {\n  const { t } = useTranslation();\n  const { project, refreshCurrentProjectRepos } = useContext(\n    ProjectContext.Current,\n  );\n  const [indexingStatus, setIndexingStatus] = useState<IndexingStatusType>({});\n  const eventSourceRef = useRef<EventSource | null>(null);\n\n  const startEventSource = useCallback(() => {\n    eventSourceRef.current?.close();\n    eventSourceRef.current = new EventSource(`${API_BASE_URL}/repos/status`);\n    eventSourceRef.current.onmessage = (ev) => {\n      const data = JSON.parse(ev.data);\n      if (data.ev?.status_change === SyncStatus.Done) {\n        if (!data.rsync) {\n          const onClick = () => {\n            if (project?.id) {\n              return addRepoToProject(project.id, data.ref).finally(() => {\n                refreshCurrentProjectRepos();\n              });\n            }\n          };\n          toast(t('Repository indexed'), {\n            id: `${data.ref}-indexed`,\n            description: (\n              <Trans\n                values={{\n                  repoName: splitPath(data.ref)\n                    .slice(data.ref.startsWith('github.com/') ? -2 : -1)\n                    .join('/'),\n                }}\n              >\n                <span className=\"text-label-base body-s-b\">\n                  {'{{repoName}}'}\n                </span>{' '}\n                has finished indexing and can be added to your projects. Click\n                the button to below to add it to the current project.\n              </Trans>\n            ),\n            icon: <RepositoryIcon sizeClassName=\"w-4 h-4\" />,\n            unstyled: true,\n            action: {\n              label: 'Add to project',\n              onClick,\n            },\n          });\n        }\n        if (project?.repos.find((r) => r.repo.ref === data.ref)) {\n          refreshCurrentProjectRepos();\n        }\n      }\n      if (data.ev?.status_change) {\n        setIndexingStatus((prev) => ({\n          ...prev,\n          [data.ref]: {\n            ...(prev[data.ref] ? prev[data.ref] : {}),\n            status: data.ev?.status_change,\n          },\n        }));\n      }\n      if (data.ev?.index_percent) {\n        setIndexingStatus((prev) => ({\n          ...prev,\n          [data.ref]: {\n            ...(prev[data.ref] ? prev[data.ref] : {}),\n            percentage: data.ev?.index_percent,\n            branch: data.b?.select[0],\n          },\n        }));\n      }\n    };\n  }, [project?.repos]);\n\n  useEffect(() => {\n    startEventSource();\n    const intervalId = window.setInterval(startEventSource, 10 * 60 * 1000);\n    return () => {\n      clearInterval(intervalId);\n      eventSourceRef.current?.close();\n    };\n  }, [startEventSource]);\n\n  useEffect(() => {\n    getIndexedRepos().then((repos) => {\n      const reposInProgress = repos.list.filter(\n        (r) => r.sync_status !== SyncStatus.Done,\n      );\n      setIndexingStatus((prev) => {\n        const newRepos = JSON.parse(JSON.stringify(prev));\n        reposInProgress.forEach((r) => {\n          newRepos[r.ref] = {\n            ...(newRepos[r.ref] || {}),\n            status: r.sync_status,\n          };\n        });\n        return newRepos;\n      });\n    });\n  }, []);\n\n  const contextValue = useMemo(() => {\n    return {\n      indexingStatus,\n      setIndexingStatus,\n    };\n  }, [indexingStatus]);\n\n  return (\n    <RepositoriesContext.Provider value={contextValue}>\n      {children}\n    </RepositoriesContext.Provider>\n  );\n};\n\nexport default memo(RepositoriesContextProvider);\n"
  },
  {
    "path": "client/src/context/providers/StudiosContextProvider.tsx",
    "content": "import { memo, PropsWithChildren, useMemo, useState } from 'react';\nimport { StudiosContext, StudioContext } from '../studiosContext';\n\ntype Props = {};\n\nconst StudiosContextProvider = ({ children }: PropsWithChildren<Props>) => {\n  const [studios, setStudios] = useState<Record<string, StudioContext>>({});\n\n  const contextValue = useMemo(\n    () => ({\n      studios,\n      setStudios,\n    }),\n    [studios],\n  );\n  return (\n    <StudiosContext.Provider value={contextValue}>\n      {children}\n    </StudiosContext.Provider>\n  );\n};\n\nexport default memo(StudiosContextProvider);\n"
  },
  {
    "path": "client/src/context/providers/TabsContextProvider.tsx",
    "content": "import {\n  memo,\n  PropsWithChildren,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { TabsContext } from '../tabsContext';\nimport {\n  ChatTabType,\n  DocTabType,\n  FileTabType,\n  StudioTabType,\n  TabType,\n  TabTypesEnum,\n} from '../../types/general';\nimport { ProjectContext } from '../projectContext';\nimport { CommandBarContext } from '../commandBarContext';\nimport { checkEventKeys } from '../../utils/keyboardUtils';\nimport useKeyboardNavigation from '../../hooks/useKeyboardNavigation';\nimport { openTabsCache } from '../../services/cache';\nimport { RECENT_FILES_KEY, updateArrayInStorage } from '../../services/storage';\nimport { UIContext } from '../uiContext';\nimport { closeTabShortcut } from '../../consts/shortcuts';\n\ntype Props = {};\n\nconst TabsContextProvider = ({ children }: PropsWithChildren<Props>) => {\n  const {\n    project,\n    isReposLoaded,\n    isChatsLoaded,\n    isStudiosLoaded,\n    isDocsLoaded,\n    isLoading: isLoadingProjects,\n  } = useContext(ProjectContext.Current);\n  const { setFocusedTabItems } = useContext(CommandBarContext.Handlers);\n  const { setOnBoardingState } = useContext(UIContext.Onboarding);\n  const [leftTabs, setLeftTabs] = useState<TabType[]>([]);\n  const [rightTabs, setRightTabs] = useState<TabType[]>([]);\n  const [isLoading, setIsLoading] = useState(true);\n  const [activeLeftTab, setActiveLeftTab] = useState<TabType | null>(null);\n  const [activeRightTab, setActiveRightTab] = useState<TabType | null>(null);\n  const [focusedPanel, setFocusedPanel] = useState<'left' | 'right'>('left');\n  const [searchParams, setSearchParams] = useSearchParams();\n\n  useEffect(() => {\n    openTabsCache.tabs = [...leftTabs, ...rightTabs];\n  }, [leftTabs, rightTabs]);\n\n  const openNewTab = useCallback(\n    (\n      data:\n        | Omit<FileTabType, 'key'>\n        | Omit<ChatTabType, 'key'>\n        | Omit<DocTabType, 'key'>\n        | Omit<StudioTabType, 'key'>,\n      forceSide?: 'left' | 'right',\n    ) => {\n      let sideForNewTab =\n        (!forceSide && focusedPanel === 'left') || forceSide === 'left'\n          ? 'left'\n          : 'right';\n      if (\n        data.type === TabTypesEnum.FILE &&\n        leftTabs.length === 1 &&\n        [TabTypesEnum.CHAT, TabTypesEnum.STUDIO].includes(leftTabs[0].type) &&\n        !rightTabs.length\n      ) {\n        sideForNewTab = 'left';\n        setRightTabs(leftTabs);\n        setActiveRightTab(leftTabs[0]);\n        setLeftTabs(() => []);\n      }\n      if (data.type === TabTypesEnum.CHAT) {\n        setOnBoardingState((prev) =>\n          prev.isChatOpened ? prev : { ...prev, isChatOpened: true },\n        );\n      }\n      if (\n        data.type === TabTypesEnum.CHAT &&\n        leftTabs.length === 1 &&\n        leftTabs[0].type === TabTypesEnum.FILE &&\n        !rightTabs.length\n      ) {\n        sideForNewTab = 'right';\n      }\n      const setTabsAction =\n        sideForNewTab === 'left' ? setLeftTabs : setRightTabs;\n      const setActiveTabAction =\n        sideForNewTab === 'left' ? setActiveLeftTab : setActiveRightTab;\n      setTabsAction((prev) => {\n        const newTab: TabType =\n          data.type === TabTypesEnum.FILE\n            ? {\n                path: data.path,\n                repoRef: data.repoRef,\n                key: `${data.repoRef}-${data.path}`,\n                scrollToLine: data.scrollToLine,\n                branch: data.branch,\n                type: TabTypesEnum.FILE,\n                tokenRange: data.tokenRange,\n                isTemp:\n                  !data.tokenRange && !data.scrollToLine && !data.studioId,\n                studioId: data.studioId,\n                initialRanges: data.initialRanges,\n                isFileInContext: data.isFileInContext,\n              }\n            : data.type === TabTypesEnum.CHAT\n            ? {\n                type: TabTypesEnum.CHAT,\n                key: Date.now().toString(),\n                initialQuery: data.initialQuery,\n                conversationId: data.conversationId,\n                title: data.title,\n              }\n            : data.type === TabTypesEnum.STUDIO\n            ? {\n                type: TabTypesEnum.STUDIO,\n                key: data.studioId.toString(),\n                studioId: data.studioId.toString(),\n                snapshot: data.snapshot,\n              }\n            : {\n                ...data,\n                type: TabTypesEnum.DOC,\n                key: 'doc-' + data.docId + data.relativeUrl,\n              };\n        if (newTab.type === TabTypesEnum.FILE) {\n          updateArrayInStorage(\n            RECENT_FILES_KEY,\n            `${newTab.repoRef}:${newTab.path}:${newTab.branch || ''}`,\n          );\n        }\n        const previousTab = prev.find((t) =>\n          newTab.type === TabTypesEnum.CHAT && newTab.conversationId\n            ? t.type === TabTypesEnum.CHAT &&\n              t.conversationId === newTab.conversationId\n            : (t.key === newTab.key && t.type === newTab.type) ||\n              (t.type === TabTypesEnum.FILE && t.isTemp),\n        );\n        if (!previousTab) {\n          setActiveTabAction(newTab);\n          return [...prev, newTab];\n        } else {\n          if (previousTab.type == TabTypesEnum.FILE && previousTab.isTemp) {\n            setActiveTabAction(newTab);\n            const newTabs = [...prev];\n            newTabs[newTabs.length - 1] = newTab;\n            return newTabs;\n          } else if (\n            previousTab.type === TabTypesEnum.FILE &&\n            newTab.type === TabTypesEnum.FILE &&\n            (previousTab.scrollToLine !== newTab.scrollToLine ||\n              previousTab.tokenRange !== newTab.tokenRange ||\n              previousTab.studioId !== newTab.studioId)\n          ) {\n            const previousTabIndex = prev.findIndex(\n              (t) => t.key === newTab.key,\n            );\n            const newTabs = [...prev];\n            const t = {\n              ...previousTab,\n              scrollToLine: newTab.scrollToLine,\n              tokenRange: newTab.tokenRange,\n              studioId: newTab.studioId,\n            };\n            newTabs[previousTabIndex] = t;\n            setActiveTabAction(t);\n            return newTabs;\n          } else if (\n            previousTab.type === TabTypesEnum.DOC &&\n            newTab.type === TabTypesEnum.DOC &&\n            (previousTab.studioId !== newTab.studioId ||\n              previousTab.initialSections != newTab.initialSections)\n          ) {\n            const previousTabIndex = prev.findIndex(\n              (t) => t.key === newTab.key,\n            );\n            const newTabs = [...prev];\n            const t = {\n              ...previousTab,\n              studioId: newTab.studioId,\n              initialSections: newTab.initialSections,\n            };\n            newTabs[previousTabIndex] = t;\n            setActiveTabAction(t);\n            return newTabs;\n          } else if (\n            previousTab.type === TabTypesEnum.STUDIO &&\n            newTab.type === TabTypesEnum.STUDIO &&\n            previousTab.snapshot !== newTab.snapshot\n          ) {\n            const previousTabIndex = prev.findIndex(\n              (t) => t.key === newTab.key,\n            );\n            const newTabs = [...prev];\n            const t = {\n              ...previousTab,\n              snapshot: newTab.snapshot,\n            };\n            newTabs[previousTabIndex] = t;\n            setActiveTabAction(t);\n            return newTabs;\n          } else {\n            setActiveTabAction(previousTab);\n          }\n        }\n        return prev;\n      });\n    },\n    [focusedPanel, leftTabs, rightTabs],\n  );\n\n  useEffect(() => {\n    if (!rightTabs.length) {\n      setFocusedPanel('left');\n    }\n  }, [rightTabs]);\n\n  useEffect(() => {\n    if (!leftTabs.length && rightTabs.length) {\n      setLeftTabs(rightTabs);\n      setRightTabs([]);\n      setActiveLeftTab(activeRightTab);\n      setActiveRightTab(null);\n    }\n  }, [rightTabs, leftTabs, activeRightTab]);\n\n  useEffect(() => {\n    if (!isLoadingProjects && !isLoading) {\n      const activeTab =\n        focusedPanel === 'left' ? activeLeftTab : activeRightTab;\n      if (!activeTab) {\n        setFocusedTabItems([]);\n        setSearchParams('', { replace: true });\n      } else {\n        const newParams: Record<string, string> = {};\n        if (activeTab.type === TabTypesEnum.FILE) {\n          newParams.path = activeTab.path;\n          newParams.repoRef = activeTab.repoRef;\n          if (activeTab.branch) {\n            newParams.branch = activeTab.branch;\n          }\n          if (activeTab.scrollToLine) {\n            newParams.scrollToLine = activeTab.scrollToLine;\n          }\n          if (activeTab.tokenRange) {\n            newParams.tokenRange = activeTab.tokenRange;\n          }\n        } else if (activeTab.type === TabTypesEnum.CHAT) {\n          if (activeTab.conversationId) {\n            newParams.conversationId = activeTab.conversationId;\n          }\n          if (activeTab.title) {\n            newParams.title = activeTab.title;\n          }\n        } else if (activeTab.type === TabTypesEnum.STUDIO) {\n          if (activeTab.studioId) {\n            newParams.studioId = activeTab.studioId;\n          }\n          if (activeTab.title) {\n            newParams.title = activeTab.title;\n          }\n        } else if (activeTab.type === TabTypesEnum.DOC) {\n          if (activeTab.docId) {\n            newParams.docId = activeTab.docId;\n          }\n          if (activeTab.title) {\n            newParams.title = activeTab.title;\n          }\n          if (activeTab.favicon) {\n            newParams.favicon = activeTab.favicon;\n          }\n          if (activeTab.relativeUrl) {\n            newParams.relativeUrl = activeTab.relativeUrl;\n          }\n        }\n        setSearchParams(newParams, { replace: true });\n      }\n    }\n  }, [\n    focusedPanel,\n    activeLeftTab,\n    activeRightTab,\n    isLoadingProjects,\n    isLoading,\n  ]);\n\n  useEffect(() => {\n    if (!isLoadingProjects) {\n      const path = searchParams.get('path');\n      const conversationId = searchParams.get('conversationId');\n      const studioId = searchParams.get('studioId');\n      const docId = searchParams.get('docId');\n      if (!activeLeftTab && (path || conversationId || studioId || docId)) {\n        const repoRef = searchParams.get('repoRef');\n        if (path && repoRef) {\n          openNewTab({\n            type: TabTypesEnum.FILE,\n            path,\n            repoRef,\n            branch: searchParams.get('branch'),\n            scrollToLine: searchParams.get('scrollToLine') || undefined,\n            tokenRange: searchParams.get('tokenRange') || undefined,\n          });\n        } else if (conversationId) {\n          openNewTab({\n            type: TabTypesEnum.CHAT,\n            conversationId,\n            title: searchParams.get('title') || undefined,\n          });\n        } else if (docId) {\n          openNewTab({\n            type: TabTypesEnum.DOC,\n            docId,\n            title: searchParams.get('title') || undefined,\n            favicon: searchParams.get('favicon') || undefined,\n            relativeUrl: searchParams.get('relativeUrl')!,\n          });\n        } else if (studioId) {\n          openNewTab({\n            type: TabTypesEnum.STUDIO,\n            studioId,\n            title: searchParams.get('title') || undefined,\n          });\n        }\n      }\n      setIsLoading(false);\n    }\n  }, [isLoadingProjects]);\n\n  const closeTab = useCallback((key: string, side: 'left' | 'right') => {\n    const setTabsAction = side === 'left' ? setLeftTabs : setRightTabs;\n    const setActiveTabAction =\n      side === 'left' ? setActiveLeftTab : setActiveRightTab;\n    setTabsAction((prevTabs) => {\n      const newTabs = prevTabs.filter((t) => t.key !== key);\n      setActiveTabAction((prev) => {\n        if (!newTabs.length) {\n          return null;\n        }\n        const prevIndex = prev\n          ? prevTabs.findIndex((t) => t.key === prev.key)\n          : -1;\n        if (key === prev?.key) {\n          return prevIndex > 0\n            ? prevTabs[prevIndex - 1]\n            : prevTabs[prevIndex + 1];\n        }\n        return prev;\n      });\n      return newTabs;\n    });\n  }, []);\n\n  const closeCurrentTab = useCallback(() => {\n    if (focusedPanel === 'left' && activeLeftTab) {\n      closeTab(activeLeftTab.key, focusedPanel);\n    } else if (focusedPanel === 'right' && activeRightTab) {\n      closeTab(activeRightTab.key, focusedPanel);\n    }\n  }, [activeLeftTab, activeRightTab, focusedPanel, closeTab]);\n\n  const updateTabProperty = useCallback(\n    <\n      T extends ChatTabType | FileTabType | StudioTabType | DocTabType,\n      K extends keyof T,\n    >(\n      tabKey: string,\n      objectKey: K,\n      newValue: T[K],\n      side: 'left' | 'right',\n    ) => {\n      const setTabsAction = side === 'left' ? setLeftTabs : setRightTabs;\n      const setActiveTabAction =\n        side === 'left' ? setActiveLeftTab : setActiveRightTab;\n      setTabsAction((prevTabs) => {\n        const newTabs = [...prevTabs];\n        const oldTabIndex = prevTabs.findIndex((t) => t.key === tabKey);\n        const newTab = { ...newTabs[oldTabIndex], [objectKey]: newValue };\n        newTabs[oldTabIndex] = newTab;\n        setActiveTabAction((prev) =>\n          prev?.key === newTab.key ? newTab : prev,\n        );\n        return newTabs;\n      });\n    },\n    [],\n  );\n\n  const checkIfTabsShouldClose = useCallback(\n    (checkIfTabShouldClose: (t: TabType | null) => boolean) => {\n      if (!isLoading && !isLoadingProjects) {\n        setActiveLeftTab((prev) => (checkIfTabShouldClose(prev) ? null : prev));\n        setActiveRightTab((prev) =>\n          checkIfTabShouldClose(prev) ? null : prev,\n        );\n        setLeftTabs((prev) => prev.filter((t) => !checkIfTabShouldClose(t)));\n        setRightTabs((prev) => prev.filter((t) => !checkIfTabShouldClose(t)));\n      }\n    },\n    [isLoading, isLoadingProjects],\n  );\n\n  useEffect(() => {\n    if (isReposLoaded && project?.repos && !isLoading && !isLoadingProjects) {\n      const checkIfTabShouldClose = (tab: TabType | null) =>\n        tab?.type === TabTypesEnum.FILE &&\n        !project.repos.find((r) => r.repo.ref === tab.repoRef);\n      checkIfTabsShouldClose(checkIfTabShouldClose);\n    }\n  }, [isReposLoaded, project?.repos, checkIfTabsShouldClose]);\n\n  useEffect(() => {\n    if (\n      isChatsLoaded &&\n      project?.conversations &&\n      !isLoading &&\n      !isLoadingProjects\n    ) {\n      const checkIfTabShouldClose = (tab: TabType | null) =>\n        tab?.type === TabTypesEnum.CHAT &&\n        !!tab.conversationId &&\n        !project.conversations.find((r) => r.id === tab.conversationId);\n      checkIfTabsShouldClose(checkIfTabShouldClose);\n    }\n  }, [isChatsLoaded, project?.conversations, checkIfTabsShouldClose]);\n\n  useEffect(() => {\n    if (\n      isStudiosLoaded &&\n      project?.studios &&\n      !isLoading &&\n      !isLoadingProjects\n    ) {\n      const checkIfTabShouldClose = (tab: TabType | null) =>\n        tab?.type === TabTypesEnum.STUDIO &&\n        !project.studios.find(\n          (r) => r.id.toString() === tab.studioId.toString(),\n        );\n      checkIfTabsShouldClose(checkIfTabShouldClose);\n    }\n  }, [isStudiosLoaded, project?.studios, checkIfTabsShouldClose]);\n\n  useEffect(() => {\n    if (isDocsLoaded && project?.docs && !isLoading && !isLoadingProjects) {\n      const checkIfTabShouldClose = (tab: TabType | null) =>\n        tab?.type === TabTypesEnum.DOC &&\n        !project.docs.find((r) => r.id === tab.docId);\n      checkIfTabsShouldClose(checkIfTabShouldClose);\n    }\n  }, [isDocsLoaded, project?.docs, checkIfTabsShouldClose]);\n\n  const handlersContextValue = useMemo(\n    () => ({\n      closeTab,\n      closeCurrentTab,\n      openNewTab,\n      setActiveLeftTab,\n      setActiveRightTab,\n      setFocusedPanel,\n      setLeftTabs,\n      setRightTabs,\n      updateTabProperty,\n    }),\n    [closeTab, openNewTab, updateTabProperty, closeCurrentTab],\n  );\n\n  const handleKeyEvent = useCallback(\n    (e: KeyboardEvent) => {\n      if (checkEventKeys(e, closeTabShortcut)) {\n        e.preventDefault();\n        e.stopPropagation();\n        closeTab(\n          focusedPanel === 'right' && activeRightTab?.key\n            ? activeRightTab.key\n            : activeLeftTab?.key || '',\n          focusedPanel,\n        );\n      }\n    },\n    [closeTab, focusedPanel, activeLeftTab, activeRightTab],\n  );\n  useKeyboardNavigation(handleKeyEvent);\n\n  const allContextValue = useMemo(\n    () => ({\n      leftTabs,\n      rightTabs,\n    }),\n    [leftTabs, rightTabs],\n  );\n\n  const currentLeftContextValue = useMemo(\n    () => ({\n      tab: activeLeftTab,\n    }),\n    [activeLeftTab],\n  );\n\n  const currentRightContextValue = useMemo(\n    () => ({\n      tab: activeRightTab,\n    }),\n    [activeRightTab],\n  );\n\n  const focusedPanelContextValue = useMemo(\n    () => ({\n      focusedPanel,\n    }),\n    [focusedPanel],\n  );\n\n  return (\n    <TabsContext.Handlers.Provider value={handlersContextValue}>\n      <TabsContext.All.Provider value={allContextValue}>\n        <TabsContext.CurrentLeft.Provider value={currentLeftContextValue}>\n          <TabsContext.CurrentRight.Provider value={currentRightContextValue}>\n            <TabsContext.FocusedPanel.Provider value={focusedPanelContextValue}>\n              {children}\n            </TabsContext.FocusedPanel.Provider>\n          </TabsContext.CurrentRight.Provider>\n        </TabsContext.CurrentLeft.Provider>\n      </TabsContext.All.Provider>\n    </TabsContext.Handlers.Provider>\n  );\n};\n\nexport default memo(TabsContextProvider);\n"
  },
  {
    "path": "client/src/context/providers/UIContextProvider.tsx",
    "content": "import React, {\n  memo,\n  PropsWithChildren,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n} from 'react';\nimport { UIContext } from '../uiContext';\nimport { usePersistentState } from '../../hooks/usePersistentState';\nimport {\n  CHAT_INPUT_TYPE_KEY,\n  getPlainFromStorage,\n  ONBOARDING_DONE_KEY,\n  savePlainToStorage,\n  THEME,\n} from '../../services/storage';\nimport { Theme } from '../../types';\nimport {\n  ChatInputType,\n  ProjectSettingSections,\n  SettingSections,\n} from '../../types/general';\nimport { LocaleContext } from '../localeContext';\n\ntype Props = {};\n\nexport const UIContextProvider = memo(\n  ({ children }: PropsWithChildren<Props>) => {\n    const [isSettingsOpen, setSettingsOpen] = useState(false);\n    const [settingsSection, setSettingsSection] = useState(\n      SettingSections.GENERAL,\n    );\n    const [isProjectSettingsOpen, setProjectSettingsOpen] = useState(false);\n    const [projectSettingsSection, setProjectSettingsSection] = useState(\n      ProjectSettingSections.GENERAL,\n    );\n    const [isBugReportModalOpen, setBugReportModalOpen] = useState(false);\n    const [onBoardingState, setOnBoardingState] = usePersistentState(\n      {},\n      'onBoardingState',\n    );\n    const { locale } = useContext(LocaleContext);\n    const [shouldShowWelcome, setShouldShowWelcome] = useState(\n      !getPlainFromStorage(ONBOARDING_DONE_KEY),\n    );\n    const [theme, setTheme] = useState<Theme>(\n      (getPlainFromStorage(THEME) as 'system' | null) || 'system',\n    );\n    const [chatInputType, setChatInputType] = useState<ChatInputType>(\n      (getPlainFromStorage(CHAT_INPUT_TYPE_KEY) as 'default' | null) ||\n        locale === 'zhCN'\n        ? 'simplified'\n        : 'default',\n    );\n    const [isLeftSidebarFocused, setIsLeftSidebarFocused] = useState(false);\n\n    useEffect(() => {\n      if (!['dark', 'light', 'black', 'system'].includes(theme)) {\n        setTheme('system');\n      } else {\n        savePlainToStorage(THEME, theme);\n        document.body.dataset.theme = theme;\n      }\n    }, [theme]);\n\n    useEffect(() => {\n      savePlainToStorage(CHAT_INPUT_TYPE_KEY, chatInputType);\n    }, [chatInputType]);\n\n    const settingsContextValue = useMemo(\n      () => ({\n        isSettingsOpen,\n        setSettingsOpen,\n        settingsSection,\n        setSettingsSection,\n      }),\n      [isSettingsOpen, settingsSection],\n    );\n\n    const projectSettingsContextValue = useMemo(\n      () => ({\n        isProjectSettingsOpen,\n        setProjectSettingsOpen,\n        projectSettingsSection,\n        setProjectSettingsSection,\n      }),\n      [isProjectSettingsOpen, projectSettingsSection],\n    );\n\n    const onboardingContextValue = useMemo(\n      () => ({\n        onBoardingState,\n        setOnBoardingState,\n        shouldShowWelcome,\n        setShouldShowWelcome,\n      }),\n      [onBoardingState, shouldShowWelcome],\n    );\n\n    const bugReportContextValue = useMemo(\n      () => ({\n        isBugReportModalOpen,\n        setBugReportModalOpen,\n      }),\n      [isBugReportModalOpen],\n    );\n\n    const themeContextValue = useMemo(\n      () => ({\n        theme,\n        setTheme,\n      }),\n      [theme],\n    );\n\n    const focusContextValue = useMemo(\n      () => ({\n        isLeftSidebarFocused,\n        setIsLeftSidebarFocused,\n      }),\n      [isLeftSidebarFocused],\n    );\n\n    const chatInputTypeContextValue = useMemo(\n      () => ({\n        chatInputType,\n        setChatInputType,\n      }),\n      [chatInputType],\n    );\n\n    return (\n      <UIContext.Settings.Provider value={settingsContextValue}>\n        <UIContext.ProjectSettings.Provider value={projectSettingsContextValue}>\n          <UIContext.Onboarding.Provider value={onboardingContextValue}>\n            <UIContext.BugReport.Provider value={bugReportContextValue}>\n              <UIContext.Theme.Provider value={themeContextValue}>\n                <UIContext.ChatInputType.Provider\n                  value={chatInputTypeContextValue}\n                >\n                  <UIContext.Focus.Provider value={focusContextValue}>\n                    {children}\n                  </UIContext.Focus.Provider>\n                </UIContext.ChatInputType.Provider>\n              </UIContext.Theme.Provider>\n            </UIContext.BugReport.Provider>\n          </UIContext.Onboarding.Provider>\n        </UIContext.ProjectSettings.Provider>\n      </UIContext.Settings.Provider>\n    );\n  },\n);\n\nUIContextProvider.displayName = 'UIContextProvider';\n"
  },
  {
    "path": "client/src/context/repositoriesContext.ts",
    "content": "import { createContext, Dispatch, SetStateAction } from 'react';\nimport { IndexingStatusType } from '../types/general';\n\ntype ContextType = {\n  indexingStatus: IndexingStatusType;\n  setIndexingStatus: Dispatch<SetStateAction<IndexingStatusType>>;\n};\n\nexport const RepositoriesContext = createContext<ContextType>({\n  indexingStatus: {},\n  setIndexingStatus: () => {},\n});\n"
  },
  {
    "path": "client/src/context/studiosContext.tsx",
    "content": "import { createContext, Dispatch, SetStateAction } from 'react';\nimport {\n  StudioConversationMessage,\n  StudioConversationMessageAuthor,\n} from '../types/general';\nimport { CodeStudioTokenCountType, GeneratedCodeDiff } from '../types/api';\n\nexport type StudioContext = {\n  conversation: StudioConversationMessage[];\n  setConversation: Dispatch<SetStateAction<StudioConversationMessage[]>>;\n  inputValue: string;\n  setInputValue: Dispatch<SetStateAction<string>>;\n  tokenCount: CodeStudioTokenCountType | null;\n  setTokenCount: Dispatch<SetStateAction<CodeStudioTokenCountType | null>>;\n  onMessageChange: (message: string, i?: number) => void;\n  onMessageRemoved: (i: number, andSubsequent?: boolean) => void;\n  diff: GeneratedCodeDiff | null;\n  setDiff: Dispatch<SetStateAction<GeneratedCodeDiff | null>>;\n  onDiffRemoved: (i: number) => void;\n  onDiffChanged: (i: number, v: string) => void;\n  isDiffApplyError: boolean;\n  isDiffApplied: boolean;\n  waitingForDiff: boolean;\n  isDiffGenFailed: boolean;\n  inputAuthor: StudioConversationMessageAuthor;\n  onSubmit: () => void;\n  isLoading: boolean;\n  handleCancel: () => void;\n  clearConversation: () => void;\n  handleCancelDiff: () => void;\n  handleApplyChanges: () => void;\n  handleConfirmDiff: () => void;\n  refetchCodeStudio: () => void;\n};\n\ntype ContextType = {\n  studios: Record<string, StudioContext>;\n  setStudios: Dispatch<SetStateAction<Record<string, StudioContext>>>;\n};\n\nexport const StudiosContext = createContext<ContextType>({\n  studios: {},\n  setStudios: () => {},\n});\n"
  },
  {
    "path": "client/src/context/tabsContext.tsx",
    "content": "import { createContext, Dispatch, SetStateAction } from 'react';\nimport {\n  ChatTabType,\n  DocTabType,\n  FileTabType,\n  StudioTabType,\n  TabType,\n} from '../types/general';\n\ntype HandlersContextType = {\n  openNewTab: (\n    data:\n      | Omit<FileTabType, 'key'>\n      | Omit<ChatTabType, 'key'>\n      | Omit<StudioTabType, 'key'>\n      | Omit<DocTabType, 'key'>,\n    forceSide?: 'left' | 'right',\n  ) => void;\n  closeTab: (key: string, side: 'left' | 'right') => void;\n  closeCurrentTab: () => void;\n  setActiveLeftTab: Dispatch<SetStateAction<TabType | null>>;\n  setActiveRightTab: Dispatch<SetStateAction<TabType | null>>;\n  setFocusedPanel: (panel: 'left' | 'right') => void;\n  setLeftTabs: Dispatch<SetStateAction<TabType[]>>;\n  setRightTabs: Dispatch<SetStateAction<TabType[]>>;\n  updateTabProperty: <\n    T extends ChatTabType | FileTabType | StudioTabType | DocTabType,\n    K extends keyof T,\n  >(\n    tabKey: string,\n    objectKey: K,\n    newValue: T[K],\n    side: 'left' | 'right',\n  ) => void;\n};\n\nexport const TabsContext = {\n  Handlers: createContext<HandlersContextType>({\n    openNewTab: () => {},\n    closeTab: (key: string, side: 'left' | 'right') => {},\n    closeCurrentTab: () => {},\n    setActiveLeftTab: () => {},\n    setActiveRightTab: () => {},\n    setFocusedPanel: (panel: 'left' | 'right') => {},\n    setLeftTabs: () => {},\n    setRightTabs: () => {},\n    updateTabProperty: () => {},\n  }),\n  All: createContext<{\n    leftTabs: TabType[];\n    rightTabs: TabType[];\n  }>({\n    leftTabs: [],\n    rightTabs: [],\n  }),\n  CurrentLeft: createContext<{ tab: TabType | null }>({\n    tab: null,\n  }),\n  CurrentRight: createContext<{ tab: TabType | null }>({\n    tab: null,\n  }),\n  FocusedPanel: createContext<{ focusedPanel: 'left' | 'right' }>({\n    focusedPanel: 'left',\n  }),\n};\n"
  },
  {
    "path": "client/src/context/uiContext.ts",
    "content": "import { createContext, Dispatch, SetStateAction } from 'react';\nimport { Theme } from '../types';\nimport {\n  ChatInputType,\n  OnboardingStateType,\n  ProjectSettingSections,\n  SettingSections,\n} from '../types/general';\n\nexport const UIContext = {\n  Settings: createContext({\n    isSettingsOpen: false,\n    setSettingsOpen: (b: boolean) => {},\n    settingsSection: SettingSections.GENERAL,\n    setSettingsSection: (s: SettingSections) => {},\n  }),\n  ProjectSettings: createContext({\n    isProjectSettingsOpen: false,\n    setProjectSettingsOpen: (b: boolean) => {},\n    projectSettingsSection: ProjectSettingSections.GENERAL,\n    setProjectSettingsSection: (s: ProjectSettingSections) => {},\n  }),\n  Onboarding: createContext<{\n    onBoardingState: OnboardingStateType;\n    setOnBoardingState: Dispatch<SetStateAction<OnboardingStateType>>;\n  }>({\n    onBoardingState: {},\n    setOnBoardingState: () => {},\n  }),\n  BugReport: createContext({\n    isBugReportModalOpen: false,\n    setBugReportModalOpen: (b: boolean) => {},\n  }),\n  Theme: createContext({\n    theme: 'system' as Theme,\n    setTheme: (t: Theme) => {},\n  }),\n  Focus: createContext({\n    isLeftSidebarFocused: false,\n    setIsLeftSidebarFocused: (b: boolean) => {},\n  }),\n  ChatInputType: createContext({\n    chatInputType: 'default' as ChatInputType,\n    setChatInputType: (t: ChatInputType) => {},\n  }),\n};\n"
  },
  {
    "path": "client/src/file-icons.css",
    "content": "/*\n | File Icons\n | @link https://github.com/file-icons\n | @author Daniel Brooker https://github.com/DanBrooker\n */\n\n/* ----------------------------[ Colors ]---------------------------------- */\n\n/*============================================================================*\n\tPALETTE\n\tBase16 colours from https://github.com/chriskempson/base16\n/*============================================================================*/\n.light-red:before {\n    color: #c97071;\n}\n.medium-red:before {\n    color: #ac4142;\n}\n.dark-red:before {\n    color: #742c2d;\n}\n.light-green:before {\n    color: #a6ba7b;\n}\n.medium-green:before {\n    color: #90a959;\n}\n.dark-green:before {\n    color: #66783e;\n}\n.light-yellow:before {\n    color: #fae0bc;\n}\n.medium-yellow:before {\n    color: #ee9e2e;\n}\n.dark-yellow:before {\n    color: #d88511;\n}\n.light-blue:before {\n    color: #6098b0;\n}\n.medium-blue:before {\n    color: #6a9fb5;\n}\n.dark-blue:before {\n    color: #46788d;\n}\n.light-maroon:before {\n    color: #be7953;\n}\n.medium-maroon:before {\n    color: #8f5536;\n}\n.dark-maroon:before {\n    color: #573421;\n}\n.light-purple:before {\n    color: #c7a4c0;\n}\n.medium-purple:before {\n    color: #aa759f;\n}\n.dark-purple:before {\n    color: #825078;\n}\n.light-orange:before {\n    color: #d99762;\n}\n.medium-orange:before {\n    color: #d28445;\n}\n.dark-orange:before {\n    color: #a35f27;\n}\n.light-cyan:before {\n    color: #6bb0a4;\n}\n.medium-cyan:before {\n    color: #75b5aa;\n}\n.dark-cyan:before {\n    color: #4d9085;\n}\n.light-pink:before {\n    color: #ff4ddb;\n}\n.medium-pink:before {\n    color: #ff00cc;\n}\n.dark-pink:before {\n    color: #b3008f;\n}\n.theme-colour-check {\n    background: #ffffff;\n}\n\n/* ----------------------------[ Fonts ]---------------------------------- */\n\n@font-face {\n    font-family: FontAwesome;\n    font-weight: normal;\n    font-style: normal;\n    src: url(\"fonts/fontawesome.woff2\");\n}\n\n@font-face {\n    font-family: Mfizz;\n    src: url(\"fonts/mfixx.woff2\");\n    font-weight: normal;\n    font-style: normal;\n}\n\n@font-face {\n    font-family: Devicons;\n    src: url(\"fonts/devopicons.woff2\");\n    font-weight: normal;\n    font-style: normal;\n}\n\n@font-face {\n    font-family: file-icons;\n    src: url(\"fonts/file-icons.woff2\");\n    font-weight: normal;\n    font-style: normal;\n}\n\n@font-face {\n    font-family: octicons;\n    src: url(\"fonts/octicons.woff2\");\n    font-weight: normal;\n    font-style: normal;\n}\n\n@font-face {\n    font-family: seti;\n    src: url(\"fonts/seti.woff2\");\n    font-weight: normal;\n    font-style: normal;\n}\n\n/* ----------------------------[ Icons ]---------------------------------- */\n\n.icon:before{\n    font-weight: normal;\n    font-style: normal;\n    text-align: center;\n    width: 16px;\n    line-height: 1;\n    position: relative;\n    display: inline-block;\n    -webkit-font-smoothing: antialiased;\n}\n\n.file-icon:before {\n    display: block;\n    width: 24px;\n    text-align: center;\n}\n\n/*============================================================================*\n  Octicons\n  https://github.com/github/octicons\n/*============================================================================*/\n\n.book-icon:before         { font-family: octicons; font-size: 16px; top: 1px; content: \"\\f007\"; }\n.brew-icon:before         { font-family: octicons; font-size: 16px; top: 1px; content: \"\\f069\"; font-size: 15px; left: 1px; }\n.code-icon:before         { font-family: octicons; font-size: 16px; top: 1px; content: \"\\f05f\"; }\n.gear-icon:before         { font-family: octicons; font-size: 16px; top: 1px; content: \"\\f02f\"; }\n.git-commit-icon:before   { font-family: octicons; font-size: 16px; top: 1px; content: \"\\f01f\"; }\n.git-merge-icon:before    { font-family: octicons; font-size: 16px; top: 1px; content: \"\\f023\"; }\n.key-icon:before          { font-family: octicons; font-size: 16px; top: 1px; content: \"\\f049\"; }\n.link-icon:before         { font-family: octicons; font-size: 16px; top: 1px; content: \"\\f0b0\"; }\n.package-icon:before      { font-family: octicons; font-size: 16px; top: 1px; content: \"\\f0c4\"; }\n.secret-icon:before       { font-family: octicons; font-size: 16px; top: 1px; content: \"\\f08c\"; }\n.squirrel-icon:before     { font-family: octicons; font-size: 15px; top: 1px; content: \"\\f0b2\"; }\n.text-icon:before         { font-family: octicons; font-size: 16px; top: 1px; content: \"\\f011\"; width: 14px; }\n\n\n\n\n/*============================================================================*\n  FontAwesome\n  http://fortawesome.github.io/Font-Awesome/cheatsheet\n/*============================================================================*/\n\n.android-icon:before      { font-family: FontAwesome; font-size: 13px; content: \"\\f17b\"; font-size: 16px; top: 1px; }\n.at-icon:before           { font-family: FontAwesome; font-size: 13px; content: \"\\f1fa\"; font-size: 15px; top: 1px; }\n.bullhorn-icon:before     { font-family: FontAwesome; font-size: 13px; content: \"\\f0a1\"; font-size: 16px; top: 2px; }\n.calc-icon:before         { font-family: FontAwesome; font-size: 13px; content: \"\\f1ec\"; font-size: 14px; }\n.circle-icon:before       { font-family: FontAwesome; font-size: 13px; content: \"\\f111\"; font-size: 16px; top: 1px; }\n.earth-icon:before        { font-family: FontAwesome; font-size: 13px; content: \"\\f0ac\"; font-size: 15px; }\n.gears-icon:before        { font-family: FontAwesome; font-size: 13px; content: \"\\f085\"; font-size: 15px; }\n.mobile-icon:before       { font-family: FontAwesome; font-size: 13px; content: \"\\f10b\"; font-size: 20px; top: 2px; }\n.moon-icon:before         { font-family: FontAwesome; font-size: 13px; content: \"\\f186\"; font-size: 16px; top: 1px; }\n.music-icon:before        { font-family: FontAwesome; font-size: 13px; content: \"\\f001\"; font-size: 15px; }\n.print-icon:before        { font-family: FontAwesome; font-size: 13px; content: \"\\f02f\"; font-size: 15px; top: 2px; }\n.recycle-icon:before      { font-family: FontAwesome; font-size: 13px; content: \"\\f1b8\"; font-size: 15px; top: 2px; }\n.rss-icon:before          { font-family: FontAwesome; font-size: 13px; content: \"\\f143\"; font-size: 16px; top: 2px; }\n.sourcemap-icon:before    { font-family: FontAwesome; font-size: 13px; content: \"\\f279\"; font-size: 14px; }\n.sun-icon:before          { font-family: FontAwesome; font-size: 13px; content: \"\\f185\"; font-size: 14px; -webkit-font-smoothing: subpixel-antialiased; }\n.toc-icon:before          { font-family: FontAwesome; font-size: 13px; content: \"\\f03a\"; font-size: 15px; top: 2px; }\n\n\n\n/*============================================================================*\n  Mfizz\n  http://mfizz.com/oss/font-mfizz\n/*============================================================================*/\n\n.apache-icon:before       { font-family: Mfizz; font-size: 14px; content: \"\\f102\"; top: 3px; font-size: 15px; }\n.archlinux-icon:before    { font-family: Mfizz; font-size: 14px; content: \"A\";     top: 1px; font-size: 15px; }\n.debian-icon:before       { font-family: Mfizz; font-size: 14px; content: \"\\f111\"; top: 1px; }\n.msql-icon:before         { font-family: Mfizz; font-size: 14px; content: \"\\f136\"; top: 2px; font-size: 15px; text-shadow: 0 0 0; }\n.objc-icon:before         { font-family: Mfizz; font-size: 14px; content: \"\\f13e\"; top: 2px; font-size: 16px; }\n.osx-icon:before          { font-family: Mfizz; font-size: 14px; content: \"\\f141\"; top: 1px; }\n.red-hat-icon:before      { font-family: Mfizz; font-size: 14px; content: \"\\f14e\"; top: 2px; }\n.sql-icon:before          { font-family: Mfizz; font-size: 14px; content: \"\\f10e\"; top: 1px; }\n.tt-icon:before           { font-family: Mfizz; font-size: 14px; content: \"TT\";    }\n.x11-icon:before          { font-family: Mfizz; font-size: 14px; content: \"\\f16e\"; top: 1px; font-size: 13px; }\n\n\n\n/*============================================================================*\n  Devicons\n  http://vorillaz.github.io/devicons\n/*============================================================================*/\n\n.angular-icon:before      { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e653\"; }\n.appcelerator-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e6ab\"; }\n.appstore-icon:before     { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e613\"; }\n.asp-icon:before          { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e67f\"; }\n.atom-icon:before         { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e664\"; -webkit-font-smoothing: subpixel-antialiased; }\n.backbone-icon:before     { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e652\"; }\n.bootstrap-icon:before    { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e647\"; font-size: 15px; top: 2px; }\n.chrome-icon:before       { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e643\"; }\n.compass-icon:before      { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e661\"; font-size: 14px; top: 2px; }\n.dojo-icon:before         { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e61c\"; font-size: 16px; top: 4px; transform: scale(1.2); -webkit-font-smoothing: subpixel-antialiased; }\n.dropbox-icon:before      { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e607\"; }\n.eclipse-icon:before      { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e69e\"; }\n.erlang-icon:before       { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e6b1\"; }\n.extjs-icon:before        { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e68e\"; }\n.jquery-icon:before       { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e650\"; font-size: 15px; top: 2px; }\n.jqueryui-icon:before     { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e654\"; font-size: 15px; top: 2px; }\n.laravel-icon:before      { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e63f\"; -webkit-font-smoothing: subpixel-antialiased; }\n.materialize-icon:before  { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e6b6\"; transform: scale(1.2); -webkit-font-smoothing: subpixel-antialiased; }\n.modernizr-icon:before    { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e620\"; }\n.mootools-icon:before     { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e68f\"; text-shadow: 0 0 0; }\n.node-icon:before         { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e618\"; }\n.pod-icon:before          { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e669\"; font-size: 15px; top: 2px; }\n.raphael-icon:before      { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e65f\"; font-size: 15px; }\n.requirejs-icon:before    { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e670\"; }\n.sencha-icon:before       { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e68c\"; }\n.snapsvg-icon:before      { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e65e\"; }\n.travis-icon:before       { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e67e\"; font-size: 15px; top: 2px; }\n.uikit-icon:before        { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e673\"; font-size: 15px; top: 2px; }\n.unity3d-icon:before      { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e621\"; }\n.vim-icon:before          { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e6c5\"; }\n.vs-icon:before           { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e60c\"; font-size: 14px; top: 2px; }\n.yeoman-icon:before       { font-family: Devicons; font-size: 16px; top: 3px; content: \"\\e67a\"; }\n\n\n\n\n/*============================================================================*\n  Custom file icons\n  See https://github.com/file-icons/source/#adding-new-icons\n/*============================================================================*/\n\n.abap-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e92b\"; top: 2px; }\n.access-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e9ea\"; top: 2px; }\n.ada-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e90b\"; top: 3px; font-size: 17px; }\n.ae-icon:before            { font-family: file-icons; font-size: 15px; content: \"\\e9f3\"; top: 2px; }\n.ahk-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e932\"; top: 2px; }\n.ai-icon:before            { font-family: file-icons; font-size: 15px; content: \"\\e6b4\"; top: 2px; }\n.alloy-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e935\"; top: 2px; }\n.alpine-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e9ff\"; top: 2px; font-size: 16px; }\n.ampl-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e94e\"; top: 3px; font-size: 16px; left: 1px; }\n.amx-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e99b\"; top: 3px; font-size: 16px; }\n.ant-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e93e\"; top: 4px; font-size: 18px; transform: scale(1.1); }\n.antlr-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e92c\"; top: 3px; }\n.api-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e92d\"; top: 2px; }\n.apl-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\234b\"; top: 2px; }\n.appveyor-icon:before      { font-family: file-icons; font-size: 15px; content: \"\\e923\"; top: 2px; }\n.arc-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e92f\"; top: 2px; }\n.arduino-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e930\"; top: 3px; font-size: 16px; }\n.arttext-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\24d0\"; top: 2px; }\n.as-icon:before            { font-family: file-icons; font-size: 15px; content: \"\\e92e\"; top: 1px; font-size: 14px; }\n.asciidoc-icon:before      { font-family: file-icons; font-size: 15px; content: \"\\e918\"; top: 1px; font-size: 14px; }\n.ats-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e934\"; top: 2px; }\n.audacity-icon:before      { font-family: file-icons; font-size: 15px; content: \"\\e9f9\"; top: 2px; }\n.augeas-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e931\"; top: 2px; }\n.autoit-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e933\"; top: 2px; font-size: 16px; }\n.bibtex-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e601\"; top: 2px; font-size: 16px; -webkit-font-smoothing: subpixel-antialiased; }\n.blender-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e9fa\"; top: 2px; }\n.bluespec-icon:before      { font-family: file-icons; font-size: 15px; content: \"\\e93c\"; top: 1px; font-size: 13px; left: 1px; }\n.boo-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e939\"; top: 2px; }\n.boot-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\f103\"; top: 2px; font-size: 16px; }\n.brain-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e93a\"; top: 2px; }\n.brakeman-icon:before      { font-family: file-icons; font-size: 15px; content: \"\\e9d6\"; top: 2px; }\n.bro-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e93b\"; top: 3px; font-size: 16px; }\n.broccoli-icon:before      { font-family: file-icons; font-size: 15px; content: \"\\e922\"; top: 1px; font-size: 14px; }\n.byond-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e962\"; top: 2px; }\n.cabal-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e9c2\"; top: 2px; }\n.cakefile-icon:before      { font-family: file-icons; font-size: 15px; content: \"\\e924\"; top: 2px; }\n.cc-icon:before            { font-family: file-icons; font-size: 15px; content: \"\\e9d5\"; top: 2px; font-size: 16px; }\n.ceylon-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e94f\"; top: 2px; }\n.chai-icon:before          { font-family: file-icons; font-size: 15px; content: \"c\";     top: 3px; font-size: 16px; }\n.chapel-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e950\"; top: 2px; }\n.chartjs-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\ea0b\"; top: 2px; }\n.chuck-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e943\"; top: 2px; }\n.circleci-icon:before      { font-family: file-icons; font-size: 15px; content: \"\\ea12\"; top: 2px; font-size: 14px; }\n.cirru-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e951\"; top: 2px; text-shadow: 0 0 0; }\n.cl-icon:before            { font-family: file-icons; font-size: 15px; content: \"\\e972\"; top: 2px; text-shadow: 0 0 0; }\n.clarion-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e952\"; top: 1px; font-size: 14px; left: 1px; }\n.clean-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e95b\"; top: 2px; font-size: 16px; }\n.click-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e95c\"; top: 2px; }\n.clips-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e940\"; top: 3px; font-size: 18px; }\n.cljs-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\f104\"; top: 2px; }\n.cmake-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e93f\"; top: 1px; font-size: 14px; }\n.codecov-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\2602\"; top: 2px; }\n.composer-icon:before      { font-family: file-icons; font-size: 15px; content: \"\\e683\"; top: 3px; font-size: 17px; }\n.cordova-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\ea11\"; top: 2px; }\n.coq-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e95f\"; top: 2px; font-size: 16px; left: 1px; }\n.cp-icon:before            { font-family: file-icons; font-size: 15px; content: \"\\e942\"; top: 3px; font-size: 17px; }\n.creole-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e95e\"; top: 2px; }\n.csound-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e9f0\"; top: 2px; }\n.csscript-icon:before      { font-family: file-icons; font-size: 15px; content: \"\\e9e2\"; top: 2px; }\n.cucumber-icon:before      { font-family: file-icons; font-size: 15px; content: \"\\f02b\"; top: 3px; }\n.cython-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e963\"; top: 2px; }\n.d3-icon:before            { font-family: file-icons; font-size: 15px; content: \"\\ea10\"; top: 2px; }\n.darcs-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e964\"; top: 2px; }\n.dashboard-icon:before     { font-family: file-icons; font-size: 15px; content: \"\\f07d\"; top: 2px; font-size: 13px; }\n.diff-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e960\"; top: 2px; }\n.doxygen-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e928\"; top: 1px; font-size: 13px; }\n.doge-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e946\"; top: 2px; }\n.dyalog-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e90c\"; top: 1px; font-size: 14px; left: 1px; }\n.dylib-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\ea15\"; top: 2px; }\n.e-icon:before             { font-family: file-icons; font-size: 15px; content: \"E\";     top: 1px; font-size: 14px; }\n.eagle-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e965\"; top: 2px; }\n.ec-icon:before            { font-family: file-icons; font-size: 15px; content: \"\\e9c9\"; top: 2px; }\n.ecere-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e966\"; top: 3px; font-size: 16px; }\n.eiffel-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e967\"; top: 2px; font-size: 16px; }\n.electron-icon:before      { font-family: file-icons; font-size: 15px; content: \"\\ea27\"; top: 3px; font-size: 16px; text-shadow: 0 0 0; }\n.em-icon:before            { font-family: file-icons; font-size: 15px; content: \"\\e968\"; top: 3px; font-size: 16px; }\n.ember-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e61b\"; top: 2px; font-size: 14px; }\n.emacs-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e926\"; top: 2px; }\n.eq-icon:before            { font-family: file-icons; font-size: 15px; content: \"\\ea0a\"; top: 5px; }\n.fabfile-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e94b\"; top: 2px; font-size: 16px; }\n.factor-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e96a\"; top: 3px; font-size: 18px; left: -2px; transform: scale(1.2); }\n.fancy-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e96b\"; top: 2px; font-size: 16px; }\n.fantom-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e96f\"; top: 2px; left: 1px; }\n.fbx-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e9fc\"; top: 2px; }\n.ff-icon:before            { font-family: file-icons; font-size: 15px; content: \"\\fb00\"; top: 3px; }\n.finder-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e9e9\"; top: 3px; font-size: 16px; }\n.flow-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e921\"; top: 1px; }\n.flux-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e969\"; top: 2px; }\n.fortran-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e90a\"; top: 1px; font-size: 14px; left: 1px; }\n.freemarker-icon:before    { font-family: file-icons; font-size: 15px; content: \"\\e970\"; top: 2px; font-size: 16px; left: 1px; }\n.frege-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e96e\"; top: 2px; font-size: 16px; left: 1px; }\n.fuelux-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\ea09\"; top: 3px; font-size: 16px; left: 2px; transform: scale(1.15); text-shadow: 0 0 0; }\n.gams-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e973\"; top: 2px; left: 1px; }\n.gap-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e971\"; top: 3px; font-size: 16px; left: 1px; }\n.gdb-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\ea08\"; top: 3px; font-size: 16px; transform: scale(1.15); text-shadow: 0 0 0; }\n.genshi-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e976\"; top: 3px; }\n.gentoo-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e96d\"; top: 1px; font-size: 14px; left: 1px; }\n.gf-icon:before            { font-family: file-icons; font-size: 15px; content: \"\\e978\"; top: 2px; }\n.glade-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e938\"; top: 2px; }\n.glyphs-icon:before        { font-family: file-icons; font-size: 15px; content: \"G\";     top: 3px; }\n.gml-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e975\"; top: 3px; font-size: 16px; }\n.gn-icon:before            { font-family: file-icons; font-size: 15px; content: \"\\ea25\"; top: 2px; }\n.gnu-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e679\"; top: 2px; font-size: 16px; text-shadow: 0 0 0; }\n.golo-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e979\"; top: 2px; }\n.gosu-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e97a\"; top: 2px; }\n.graphviz-icon:before      { font-family: file-icons; font-size: 15px; content: \"\\e97d\"; top: 4px; font-size: 17px; left:  1px; }\n.groovy-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e904\"; top: 4px; font-size: 17px; left: -1px; }\n.harbour-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e97b\"; top: 2px; font-size: 16px; text-shadow: 0 0 0; }\n.hashicorp-icon:before     { font-family: file-icons; font-size: 15px; content: \"\\e97e\"; top: 2px; }\n.hy-icon:before            { font-family: file-icons; font-size: 15px; content: \"\\e97f\"; top: 2px; }\n.idl-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e947\"; top: 3px; font-size: 18px; }\n.idris-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e983\"; top: 2px; font-size: 16px; -webkit-font-smoothing: subpixel-antialiased; }\n.igorpro-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e980\"; top: 2px; font-size: 16px; -webkit-font-smoothing: subpixel-antialiased; }\n.indesign-icon:before      { font-family: file-icons; font-size: 15px; content: \"\\e9f4\"; top: 2px; }\n.inform7-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e984\"; top: 2px; font-size: 16px; text-shadow: 0 0 0; }\n.inno-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e985\"; top: 2px; }\n.io-icon:before            { font-family: file-icons; font-size: 15px; content: \"\\e981\"; top: 1px; font-size: 13px; -webkit-font-smoothing: subpixel-antialiased; }\n.ioke-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e982\"; top: 2px; }\n.isabelle-icon:before      { font-family: file-icons; font-size: 15px; content: \"\\e945\"; top: 2px; font-size: 16px; }\n.j-icon:before             { font-family: file-icons; font-size: 15px; content: \"\\e937\"; top: 1px; font-size: 13px; }\n.jake-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e948\"; top: 3px; font-size: 16px; }\n.jsonld-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e958\"; top: 3px; font-size: 17px; }\n.jsx-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e9e6\"; top: 1px; font-size: 14px; }\n.keynote-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e9e5\"; top: 2px; }\n.khronos-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e9f8\"; top: 2px; }\n.kivy-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e901\"; top: 2px; }\n.knockout-icon:before      { font-family: file-icons; font-size: 15px; content: \"\\4B\";   top: 2px; }\n.krl-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e988\"; top: 1px; font-size: 14px; }\n.labview-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e98a\"; top: 2px; font-size: 16px; }\n.lasso-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e98c\"; top: 2px; left: 1px; }\n.leaflet-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\ea07\"; top: 2px; }\n.lean-icon:before          { font-family: file-icons; font-size: 15px; content: \"L\";     top: 1px; font-size: 13px; }\n.lein-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\f105\"; top: 3px; font-size: 16px; text-shadow: 0 0 0; transform: scale(1.15); }\n.lfe-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e94c\"; top: 2px; font-size: 16px; }\n.lightwave-icon:before     { font-family: file-icons; font-size: 15px; content: \"\\e9fb\"; top: 2px; }\n.lisp-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e908\"; top: 3px; font-size: 17px; }\n.llvm-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e91d\"; top: 3px; font-size: 17px; }\n.logtalk-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e98d\"; top: 2px; text-shadow: 0 0 0; }\n.lookml-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e98e\"; top: 2px; font-size: 16px; text-shadow: 0 0 0; }\n.lsl-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e98b\"; top: 1px; }\n.mako-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e98f\"; top: 4px; font-size: 16px; }\n.mapbox-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e941\"; top: 1px; font-size: 13px; }\n.marko-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e920\"; top: 4px; font-size: 18px; left: -1px; transform: scale(1.05); }\n.mathematica-icon:before   { font-family: file-icons; font-size: 15px; content: \"\\e990\"; top: 2px; font-size: 16px; }\n.mathjax-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\ea06\"; top: 2px; }\n.matlab-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e991\"; top: 2px; }\n.max-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e993\"; top: 2px; }\n.maxscript-icon:before     { font-family: file-icons; font-size: 15px; content: \"\\e900\"; top: 2px; }\n.maya-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e9f6\"; top: 2px; font-size: 16px; }\n.manpage-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e936\"; top: 3px; }\n.mediawiki-icon:before     { font-family: file-icons; font-size: 15px; content: \"\\e954\"; top: 2px; font-size: 16px; }\n.mercury-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e994\"; top: 3px; font-size: 16px; transform: scale(1.2); }\n.metal-icon:before         { font-family: file-icons; font-size: 15px; content: \"M\";     top: 1px; left: 1px; }\n.meteor-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e6a5\"; top: 1px; }\n.minecraft-icon:before     { font-family: file-icons; font-size: 15px; content: \"\\e9dc\"; top: 2px; }\n.mirah-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e995\"; top: 2px; }\n.mocha-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\26fe\"; top: 2px; font-size: 17px; }\n.model-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e9e8\"; top: 2px; font-size: 16px; }\n.modula2-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e996\"; top: 2px; }\n.monkey-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e997\"; top: 3px; font-size: 18px; left: -1px; }\n.mruby-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\ea18\"; top: 2px; }\n.mupad-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e9ca\"; top: 3px; font-size: 16px; }\n.nant-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e9e1\"; top: 3px; transform: scale(1.2); }\n.neko-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\ea05\"; top: 2px; }\n.netlogo-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e99c\"; top: 2px; left: 1px; }\n.newrelic-icon:before      { font-family: file-icons; font-size: 15px; content: \"\\e9d7\"; top: 2px; }\n.nginx-icon:before         { font-family: file-icons; font-size: 15px; content:\"\\f146b\"; top: 2px; }\n.nib-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\2712\"; top: 2px; }\n.nit-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e999\"; top: 2px; }\n.nix-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e99a\"; top: 3px; font-size: 16px; }\n.nmap-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e94d\"; top: 3px; font-size: 16px; transform: scale(1.1); }\n.nodemon-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\ea26\"; top: 2px; }\n.normalize-icon:before     { font-family: file-icons; font-size: 15px; content: \"\\ea04\"; top: 3px; font-size: 16px; }\n.nsis-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\ea1e\"; top: 3px; font-size: 16px; }\n.numpy-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e99d\"; top: 2px; font-size: 14px; }\n.nuget-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e9d9\"; top: 2px; }\n.objj-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e99e\"; top: 2px; }\n.onenote-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e9eb\"; top: 2px; }\n.ooc-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e9cb\"; top: 2px; }\n.opa-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\2601\"; top: 2px; }\n.opencl-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e99f\"; top: 2px; font-size: 16px; }\n.openoffice-icon:before    { font-family: file-icons; font-size: 15px; content: \"\\e9e4\"; top: 2px; }\n.org-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e917\"; top: 1px; font-size: 14px; left: 1px; }\n.owl-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e957\"; top: 2px; }\n.ox-icon:before            { font-family: file-icons; font-size: 15px; content: \"\\e9a1\"; top: 3px; font-size: 16px; text-shadow: 0 0 0; }\n.oxygene-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e9bf\"; top: 2px; }\n.oz-icon:before            { font-family: file-icons; font-size: 15px; content: \"\\e9be\"; top: 2px; }\n.pan-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e9bd\"; top: 2px; }\n.papyrus-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e9bc\"; top: 2px; }\n.parrot-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e9bb\"; top: 3px; font-size: 16px; }\n.pascal-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e92a\"; top: 2px; }\n.patch-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e961\"; top: 2px; }\n.pawn-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\265f\"; top: 1px; font-size: 14px; }\n.perl6-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e96c\"; top: 2px; }\n.phalcon-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e94a\"; top: 2px; }\n.pickle-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e9c4\"; top: 2px; }\n.pike-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e9b9\"; top: 4px; font-size: 16px; -webkit-font-smoothing: subpixel-antialiased; transform: scale(1.15); }\n.pogo-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e9b8\"; top: 3px; font-size: 14px; -webkit-font-smoothing: subpixel-antialiased; }\n.pony-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e9b7\"; top: 3px; font-size: 16px; }\n.pointwise-icon:before     { font-family: file-icons; font-size: 15px; content: \"\\e977\"; top: 2px; }\n.postcss-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e910\"; top: 2px; font-size: 14px; }\n.postscript-icon:before    { font-family: file-icons; font-size: 15px; content: \"\\e955\"; top: 2px; left: 1px; }\n.povray-icon:before        { font-family: file-icons; font-size: 15px; content: \"P\";     top: 2px; left: 1px; }\n.powerbuilder-icon:before  { font-family: file-icons; font-size: 15px; content: \"\\ea14\"; }\n.powerpoint-icon:before    { font-family: file-icons; font-size: 15px; content: \"\\e9ec\"; top: 2px; }\n.premiere-icon:before      { font-family: file-icons; font-size: 15px; content: \"\\e9f5\"; top: 2px; }\n.processing-icon:before    { font-family: file-icons; font-size: 15px; content: \"\\e9a0\"; top: 2px; }\n.progress-icon:before      { font-family: file-icons; font-size: 15px; content: \"\\e9c0\"; top: 2px; font-size: 16px; transform: scale(1.2); }\n.propeller-icon:before     { font-family: file-icons; font-size: 15px; content: \"\\e9b5\"; top: 3px; font-size: 16px; }\n.protractor-icon:before    { font-family: file-icons; font-size: 15px; content: \"\\e9de\"; top: 3px; }\n.pug-alt-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e9d0\"; top: 3px; font-size: 16px; }\n.purebasic-icon:before     { font-family: file-icons; font-size: 15px; content: \"\\01b5\"; top: 2px; }\n.racket-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e9b1\"; top: 2px; left: 1px; }\n.raml-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e913\"; top: 1px; font-size: 14px; }\n.rascal-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\ea24\"; top: 2px; }\n.rdoc-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e9b0\"; top: 2px; left: 1px; }\n.rebol-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e9ae\"; top: 1px; font-size: 13px; }\n.red-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e9ad\"; top: 3px; font-size: 16px; }\n.regex-icon:before         { font-family: file-icons; font-size: 15px; content: \"*\";     top: 1px; font-size: 12px; left: 1px; }\n.rexx-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\ea16\"; top: 2px; font-size: 14px; left: 1px; }\n.riot-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e919\"; top: 4px; font-size: 18px; }\n.robot-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e9ac\"; top: 2px; font-size: 14px; }\n.rst-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e9cc\"; top: 3px; font-size: 16px; }\n.sage-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e9ab\"; top: 3px; font-size: 16px; -webkit-font-smoothing: subpixel-antialiased; }\n.saltstack-icon:before     { font-family: file-icons; font-size: 15px; content: \"\\e915\"; top: 2px; font-size: 14px; }\n.sas-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e95a\"; top: 2px; }\n.scd-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e9a2\"; top: 2px; }\n.scad-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e911\"; top: 2px; font-size: 14px; }\n.scheme-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\03bb\"; top: 2px; }\n.scilab-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e9a9\"; top: 3px; font-size: 18px; left: -1px; -webkit-font-smoothing: subpixel-antialiased; }\n.scrutinizer-icon:before   { font-family: file-icons; font-size: 15px; content: \"\\e9d4\"; top: 2px; font-size: 14px; }\n.self-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e9a8\"; top: 3px; font-size: 16px; text-shadow: 0 0 0; transform: scale(1.2); }\n.sf-icon:before            { font-family: file-icons; font-size: 15px; content: \"\\e9db\"; top: 2px; }\n.shen-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e9a7\"; top: 2px; font-size: 16px; }\n.shuriken-icon:before      { font-family: file-icons; font-size: 15px; content: \"\\272b\"; top: 2px; font-size: 14px; }\n.sigils-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\1f764\";top: 3px; font-size: 16px; text-shadow: 0 0 0; }\n.silverstripe-icon:before  { font-family: file-icons; font-size: 15px; content: \"\\e800\"; top: 2px; }\n.sketch-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e927\"; top: 2px; }\n.slash-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e9a6\"; top: 2px; }\n.snyk-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\ea1c\"; top: 2px; font-size: 16px; }\n.sparql-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e959\"; top: 2px; }\n.sqf-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e9a5\"; top: 1px; text-shadow: 0 0 0; }\n.sqlite-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e9dd\"; top: 3px; }\n.stan-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e9a4\"; top: 2px; }\n.stata-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e9a3\"; top: 2px; }\n.storyist-icon:before      { font-family: file-icons; font-size: 15px; content: \"\\e9ef\"; top: 2px; font-size: 16px; }\n.strings-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e9e0\"; top: 2px; }\n.svn-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\ea17\"; top: 2px; }\n.sysverilog-icon:before    { font-family: file-icons; font-size: 15px; content: \"\\e9c3\"; top: 2px; }\n.tag-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\f015\"; top: 2px; font-size: 14px; }\n.tcl-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e956\"; top: 2px; font-size: 16px; }\n.tern-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\1f54a\";top: 4px; font-size: 16px; }\n.textile-icon:before       { font-family: file-icons; font-size: 15px; content: \"t\";     top: 2px; }\n.textmate-icon:before      { font-family: file-icons; font-size: 15px; content: \"\\2122\"; top: 2px; font-size: 16px; }\n.thor-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e9d8\"; top: 2px; }\n.tsx-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e9e7\"; top: 1px; font-size: 14px; }\n.turing-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e9b6\"; top: 2px; }\n.txl-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e9c1\"; top: 2px; }\n.typedoc-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e9fe\"; top: 2px; }\n.typings-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e9df\"; top: 2px; }\n.uno-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\e9b3\"; top: 2px; }\n.unreal-icon:before        { font-family: file-icons; font-size: 15px; content: \"u\";     top: 2px; }\n.urweb-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e9ba\"; top: 4px; font-size: 18px; left: -1px; text-shadow: 0 0 0; }\n.wercker-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\ea19\"; top: 2px; }\n.v8-icon:before            { font-family: file-icons; font-size: 15px; content: \"\\ea1f\"; top: 3px; font-size: 16px; }\n.vagrant-icon:before       { font-family: file-icons; font-size: 15px; content: \"V\";     top: 2px; font-size: 14px; }\n.varnish-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e9b4\"; top: 1px; font-size: 14px; }\n.verilog-icon:before       { font-family: file-icons; font-size: 15px; content: \"\\e949\"; top: 2px; }\n.vhdl-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e9aa\"; top: 2px; }\n.x10-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\2169\"; top: 2px; }\n.xmos-icon:before          { font-family: file-icons; font-size: 15px; content: \"X\";     top: 1px; font-size: 14px; }\n.xojo-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\e9af\"; top: 2px; }\n.xpages-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e9c5\"; top: 2px; }\n.xtend-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e9c6\"; top: 2px; }\n.yang-icon:before          { font-family: file-icons; font-size: 15px; content: \"\\262f\"; top: 2px; }\n.yui-icon:before           { font-family: file-icons; font-size: 15px; content: \"\\ea00\"; top: 2px; }\n.zbrush-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e9f2\"; top: 2px; font-size: 16px; }\n.zephir-icon:before        { font-family: file-icons; font-size: 15px; content: \"\\e9c7\"; top: 2px; -webkit-font-smoothing: subpixel-antialiased; }\n.zimpl-icon:before         { font-family: file-icons; font-size: 15px; content: \"\\e9c8\"; top: 2px; font-size: 16px; left: 1px; }\n\n/*============================================================================*\n  Seti icons\n  https://github.com/jesseweed/seti-ui\n/*============================================================================*/\n.r-icon:before             { font-family: seti; content: \"\\E001\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.apple-icon:before         { font-family: seti; content: \"\\E002\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.audio-icon:before         { font-family: seti; content: \"\\E005\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.babel-icon:before         { font-family: seti; content: \"\\E006\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.binary-icon:before        { font-family: seti; content: \"\\E004\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.bower-icon:before         { font-family: seti; content: \"\\E009\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.csharp-icon:before        { font-family: seti; content: \"\\E00B\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.c-icon:before             { font-family: seti; content: \"\\E00C\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.cake-icon:before          { font-family: seti; content: \"\\E00D\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.cakephp-icon:before       { font-family: seti; content: \"\\E00E\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.coffee-icon:before        { font-family: seti; content: \"\\E011\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.js-icon:before            { font-family: seti; content: \"\\E051\"; font-size: 24px; display: block; position: relative; left: -4px; }\n._1c-icon:before           { font-family: seti; content: \"\\E00A\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.clojure-icon:before       { font-family: seti; content: \"\\E013\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.cf-icon:before            { font-family: seti; content: \"\\E018\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.config-icon:before        { font-family: seti; content: \"\\E019\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.cpp-icon:before           { font-family: seti; content: \"\\E01A\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.crystal-icon:before       { font-family: seti; content: \"\\E01B\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.css3-icon:before          { font-family: seti; content: \"\\E01D\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.graph-icon:before         { font-family: seti; content: \"\\E01E\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.nvidia-icon:before        { font-family: seti; content: \"\\E01F\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.dlang-icon:before         { font-family: seti; content: \"\\E020\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.dart-icon:before          { font-family: seti; content: \"\\E021\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.dbase-icon:before         { font-family: seti; content: \"\\E022\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.default-icon:before       { font-family: seti; content: \"\\E023\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.docker-icon:before        { font-family: seti; content: \"\\E025\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.editorconfig-icon:before  { font-family: seti; content: \"\\E026\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.html5-icon:before         { font-family: seti; content: \"\\E027\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.elixir-icon:before        { font-family: seti; content: \"\\E028\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.elm-icon:before           { font-family: seti; content: \"\\E02A\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.eslint-icon:before        { font-family: seti; content: \"\\E02C\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.fsharp-icon:before        { font-family: seti; content: \"\\E02E\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.font-icon:before          { font-family: seti; content: \"\\E033\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.git-icon:before           { font-family: seti; content: \"\\E034\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.github-icon:before        { font-family: seti; content: \"\\E037\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.go-icon:before            { font-family: seti; content: \"\\E039\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.godot-icon:before         { font-family: seti; content: \"\\E03B\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.gradle-icon:before        { font-family: seti; content: \"\\E03C\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.graphql-icon:before       { font-family: seti; content: \"\\E03E\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.grunt-icon:before         { font-family: seti; content: \"\\E03F\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.gulp-icon:before          { font-family: seti; content: \"\\E040\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.hack-icon:before          { font-family: seti; content: \"\\E041\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.haml-icon:before          { font-family: seti; content: \"\\E042\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.haskell-icon:before       { font-family: seti; content: \"\\E044\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.haxe-icon:before          { font-family: seti; content: \"\\E045\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.heroku-icon:before        { font-family: seti; content: \"\\E046\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.image-icon:before         { font-family: seti; content: \"\\E04C\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.ionic-icon:before         { font-family: seti; content: \"\\E04E\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.jade-icon:before          { font-family: seti; content: \"\\E04F\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.java-icon:before          { font-family: seti; content: \"\\E050\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.jenkins-icon:before       { font-family: seti; content: \"\\E052\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.jinja-icon:before         { font-family: seti; content: \"\\E053\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.database-icon:before      { font-family: seti; content: \"\\E022\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.julia-icon:before         { font-family: seti; content: \"\\E056\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.karma-icon:before         { font-family: seti; content: \"\\E057\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.kotlin-icon:before        { font-family: seti; content: \"\\E058\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.shopify-icon:before       { font-family: seti; content: \"\\E05B\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.ls-icon:before            { font-family: seti; content: \"\\E05C\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.lua-icon:before           { font-family: seti; content: \"\\E05E\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.checklist-icon:before     { font-family: seti; content: \"\\E05F\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.markdown-icon:before      { font-family: seti; content: \"\\E060\"; font-size: 24px; display: block; position: relative; left: -4px; }\n._1c-alt-icon:before       { font-family: seti; content: \"\\E062\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.mustache-icon:before      { font-family: seti; content: \"\\E063\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.nimrod-icon:before        { font-family: seti; content: \"\\E065\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.jupyter-icon:before       { font-family: seti; content: \"\\E066\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.npm-icon:before           { font-family: seti; content: \"\\E067\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.nunjucks-icon:before      { font-family: seti; content: \"\\E069\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.ocaml-icon:before         { font-family: seti; content: \"\\E06A\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.pdf-icon:before           { font-family: seti; content: \"\\E06D\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.perl-icon:before          { font-family: seti; content: \"\\E06E\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.psd-icon:before           { font-family: seti; content: \"\\E06F\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.php-icon:before           { font-family: seti; content: \"\\E070\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.powershell-icon:before    { font-family: seti; content: \"\\E074\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.prolog-icon:before        { font-family: seti; content: \"\\E077\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.pug-icon:before           { font-family: seti; content: \"\\E078\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.puppet-icon:before        { font-family: seti; content: \"\\E079\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.purescript-icon:before    { font-family: seti; content: \"\\E07A\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.python-icon:before        { font-family: seti; content: \"\\E07B\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.rails-icon:before         { font-family: seti; content: \"\\E07C\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.react-icon:before         { font-family: seti; content: \"\\E07D\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.reason-icon:before        { font-family: seti; content: \"\\E07E\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.rollup-icon:before        { font-family: seti; content: \"\\E080\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.ruby-icon:before          { font-family: seti; content: \"\\E081\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.rust-icon:before          { font-family: seti; content: \"\\E082\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.sass-icon:before          { font-family: seti; content: \"\\E084\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.sbt-icon:before           { font-family: seti; content: \"\\E085\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.scala-icon:before         { font-family: seti; content: \"\\E086\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.terminal-icon:before      { font-family: seti; content: \"\\E089\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.smarty-icon:before        { font-family: seti; content: \"\\E08B\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.stylelint-icon:before     { font-family: seti; content: \"\\E08D\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.stylus-icon:before        { font-family: seti; content: \"\\E08E\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.sublime-icon:before       { font-family: seti; content: \"\\E08F\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.svg-icon:before           { font-family: seti; content: \"\\E091\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.swift-icon:before         { font-family: seti; content: \"\\E092\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.terraform-icon:before     { font-family: seti; content: \"\\E093\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.tex-icon:before           { font-family: seti; content: \"\\E094\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.twig-icon:before          { font-family: seti; content: \"\\E098\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.ts-icon:before            { font-family: seti; content: \"\\E099\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.gnome-icon:before         { font-family: seti; content: \"\\E09A\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.video-icon:before         { font-family: seti; content: \"\\E09B\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.vue-icon:before           { font-family: seti; content: \"\\E09C\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.webpack-icon:before       { font-family: seti; content: \"\\E09F\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.windows-icon:before       { font-family: seti; content: \"\\E0A1\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.word-icon:before          { font-family: seti; content: \"\\E0A2\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.excel-icon:before         { font-family: seti; content: \"\\E0A3\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.yarn-icon:before          { font-family: seti; content: \"\\E0A5\"; font-size: 24px; display: block; position: relative; left: -4px; }\n.zip-icon:before           { font-family: seti; content: \"\\E0A8\"; font-size: 24px; display: block; position: relative; left: -4px; }\n"
  },
  {
    "path": "client/src/global.d.ts",
    "content": "export {};\n"
  },
  {
    "path": "client/src/hooks/useArrowNavigation.ts",
    "content": "import { useCallback, useRef, useState } from 'react';\n\nexport const useArrowNavigation = () => {\n  const [focusedIndex, setFocusedIndex] = useState('');\n  const ref = useRef<HTMLDivElement>(null);\n\n  const handleArrowKey = useCallback((e: KeyboardEvent) => {\n    if ((e.key === 'ArrowDown' || e.key === 'ArrowUp') && ref.current) {\n      e.preventDefault();\n      e.stopPropagation();\n      // eslint-disable-next-line no-undef\n      const nodes: NodeListOf<HTMLElement> =\n        ref.current.querySelectorAll('[data-node-index]');\n      setFocusedIndex((prev) => {\n        const prevIndex = Array.from(nodes).findIndex(\n          (n) => n.dataset.nodeIndex === prev,\n        );\n        if (prevIndex > -1) {\n          const newIndex =\n            e.key === 'ArrowDown'\n              ? prevIndex < nodes.length - 1\n                ? prevIndex + 1\n                : 0\n              : prevIndex > 0\n              ? prevIndex - 1\n              : nodes.length - 1;\n          return nodes[newIndex]?.dataset?.nodeIndex || '';\n        }\n        return nodes[0]?.dataset?.nodeIndex || '';\n      });\n    }\n  }, []);\n\n  return {\n    focusedIndex,\n    setFocusedIndex,\n    handleArrowKey,\n    navContainerRef: ref,\n  };\n};\n"
  },
  {
    "path": "client/src/hooks/useArrowNavigationItemProps.ts",
    "content": "import React, { useCallback, useContext, useEffect, useRef } from 'react';\nimport { ArrowNavigationContext } from '../context/arrowNavigationContext';\nimport { UIContext } from '../context/uiContext';\nimport { CommandBarContext } from '../context/commandBarContext';\nimport { useEnterKey } from './useEnterKey';\n\nexport const useArrowNavigationItemProps = <T extends HTMLElement>(\n  index: string,\n  onClick: () => void,\n) => {\n  const { setFocusedIndex, focusedIndex } = useContext(ArrowNavigationContext);\n  const { isLeftSidebarFocused } = useContext(UIContext.Focus);\n  const { isVisible: isCommandBarVisible } = useContext(\n    CommandBarContext.General,\n  );\n  const ref = useRef<T>(null);\n\n  const handleMouseMove = useCallback(\n    (e: React.MouseEvent) => {\n      if (e.movementX || e.movementY) {\n        setFocusedIndex(index);\n      }\n    },\n    [index, setFocusedIndex],\n  );\n  useEffect(() => {\n    if (focusedIndex === index) {\n      ref.current?.scrollIntoView({ block: 'nearest' });\n    }\n  }, [focusedIndex, index]);\n\n  useEnterKey(\n    onClick,\n    focusedIndex !== index || !isLeftSidebarFocused || isCommandBarVisible,\n  );\n\n  return {\n    props: {\n      'data-node-index': index,\n      onMouseMove: handleMouseMove,\n      onClick,\n      ref,\n    },\n    isFocused: isLeftSidebarFocused && focusedIndex === index,\n    isLeftSidebarFocused,\n    focusedIndex,\n  };\n};\n"
  },
  {
    "path": "client/src/hooks/useCodeSearch.ts",
    "content": "import { useCallback, useDeferredValue, useEffect, useState } from 'react';\nimport { checkEventKeys } from '../utils/keyboardUtils';\nimport useKeyboardNavigation from './useKeyboardNavigation';\n\ntype Props = {\n  setScrollToIndex: (i?: [number, number]) => void;\n  isDisabled?: boolean;\n  code: string;\n};\nexport const useCodeSearch = ({\n  setScrollToIndex,\n  isDisabled,\n  code,\n}: Props) => {\n  const [isSearchActive, setSearchActive] = useState(false);\n  const [searchResults, setSearchResults] = useState<number[]>([]);\n  const [currentResult, setCurrentResult] = useState(0);\n  const [searchTerm, setSearchTerm] = useState('');\n  const deferredSearchTerm = useDeferredValue(searchTerm);\n\n  const handleKeyEvent = useCallback(\n    (e: KeyboardEvent) => {\n      if (checkEventKeys(e, ['cmd', 'F'])) {\n        e.preventDefault();\n        e.stopPropagation();\n        setSearchActive((prev) => !prev);\n        return false;\n      } else if (e.key === 'Escape') {\n        e.preventDefault();\n        e.stopPropagation();\n        setSearchActive((prev) => {\n          if (prev) {\n            e.preventDefault();\n          }\n          return false;\n        });\n        setScrollToIndex(undefined);\n        setSearchTerm('');\n      } else if (e.key === 'Enter') {\n        const isNext = !e.shiftKey;\n        setCurrentResult((prev) =>\n          isNext\n            ? prev < searchResults.length\n              ? prev + 1\n              : 1\n            : prev > 1\n            ? prev - 1\n            : searchResults.length,\n        );\n      }\n    },\n    [searchResults],\n  );\n  useKeyboardNavigation(handleKeyEvent, isDisabled);\n\n  useEffect(() => {\n    if (deferredSearchTerm === '') {\n      setSearchResults([]);\n      setCurrentResult(0);\n      return;\n    }\n    const lines = code.split('\\n');\n    const results = lines.reduce(function (prev: number[], cur, i) {\n      if (cur?.toLowerCase().includes(deferredSearchTerm?.toLowerCase())) {\n        prev.push(i);\n      }\n      return prev;\n    }, []);\n    const currentlyHighlightedLine = searchResults[currentResult - 1];\n    const indexInNewResults = results.indexOf(currentlyHighlightedLine);\n    setSearchResults(results);\n    setCurrentResult(indexInNewResults >= 0 ? indexInNewResults + 1 : 1);\n  }, [deferredSearchTerm]);\n\n  useEffect(() => {\n    if (searchResults[currentResult - 1]) {\n      setScrollToIndex([\n        searchResults[currentResult - 1],\n        searchResults[currentResult - 1],\n      ]);\n    }\n  }, [currentResult, searchResults]);\n\n  const handleSearchCancel = useCallback(() => {\n    setSearchTerm('');\n    setSearchActive(false);\n    setScrollToIndex(undefined);\n  }, []);\n\n  return {\n    setSearchTerm,\n    handleSearchCancel,\n    isSearchActive,\n    searchResults,\n    currentResult,\n    setCurrentResult,\n    searchTerm,\n    deferredSearchTerm,\n  };\n};\n"
  },
  {
    "path": "client/src/hooks/useComponentWillMount.ts",
    "content": "import { useRef } from 'react';\n\nexport const useComponentWillMount = (cb: () => void) => {\n  const willMount = useRef(true);\n\n  if (willMount.current) cb();\n\n  willMount.current = false;\n};\n"
  },
  {
    "path": "client/src/hooks/useDiffLines.ts",
    "content": "import { useMemo } from 'react';\nimport { Token } from '../types/prism';\n\nexport const useDiffLines = (tokens: Token[][], isDisabled?: boolean) => {\n  const lineNumbersAdd = useMemo(() => {\n    if (isDisabled) {\n      return [];\n    }\n    let curr = 0;\n    return tokens.map((line) => {\n      if (line[0]?.content === '-' || line[1]?.content === '-') {\n        return null;\n      } else {\n        curr++;\n        return curr;\n      }\n    });\n  }, [tokens, isDisabled]);\n  const lineNumbersRemove = useMemo(() => {\n    if (isDisabled) {\n      return [];\n    }\n    let curr = 0;\n    return tokens.map((line) => {\n      if (line[0]?.content === '+' || line[1]?.content === '+') {\n        return null;\n      } else {\n        curr++;\n        return curr;\n      }\n    });\n  }, [tokens, isDisabled]);\n\n  return { lineNumbersAdd, lineNumbersRemove };\n};\n"
  },
  {
    "path": "client/src/hooks/useEnterKey.ts",
    "content": "import { useCallback } from 'react';\nimport useKeyboardNavigation from './useKeyboardNavigation';\n\nexport const useEnterKey = (handleKey: () => void, isDisabled?: boolean) => {\n  const handleKeyEvent = useCallback(\n    (e: KeyboardEvent) => {\n      if (e.key === 'Enter') {\n        e.preventDefault();\n        e.stopPropagation();\n        handleKey();\n      }\n    },\n    [handleKey],\n  );\n  useKeyboardNavigation(handleKeyEvent, isDisabled);\n};\n"
  },
  {
    "path": "client/src/hooks/useGlobalShortcuts.ts",
    "content": "import { useContext, useMemo } from 'react';\nimport { UIContext } from '../context/uiContext';\nimport {\n  CommandBarStepEnum,\n  ProjectSettingSections,\n  SettingSections,\n} from '../types/general';\nimport { CommandBarContext } from '../context/commandBarContext';\nimport { DeviceContext } from '../context/deviceContext';\nimport { ProjectContext } from '../context/projectContext';\nimport { newProjectShortcut, regexToggleShortcut } from '../consts/shortcuts';\nimport { TabsContext } from '../context/tabsContext';\n\nexport const useGlobalShortcuts = () => {\n  const { setChosenStep, setIsVisible } = useContext(\n    CommandBarContext.Handlers,\n  );\n  const { setProjectSettingsSection, setProjectSettingsOpen } = useContext(\n    UIContext.ProjectSettings,\n  );\n  const { setSettingsSection, setSettingsOpen } = useContext(\n    UIContext.Settings,\n  );\n  const { setBugReportModalOpen } = useContext(UIContext.BugReport);\n  const { openLink } = useContext(DeviceContext);\n  const { setIsRegexSearchEnabled } = useContext(ProjectContext.RegexSearch);\n  const {\n    setLeftTabs,\n    setRightTabs,\n    setActiveRightTab,\n    setActiveLeftTab,\n    setFocusedPanel,\n  } = useContext(TabsContext.Handlers);\n\n  const toggleTheme = useMemo(() => {\n    return {\n      shortcut: ['option', '1'],\n      action: () => {\n        setChosenStep({ id: CommandBarStepEnum.TOGGLE_THEME });\n        setIsVisible(true);\n      },\n    };\n  }, []);\n\n  const openPrivateRepos = useMemo(() => {\n    return {\n      shortcut: ['cmd', 'P'],\n      action: () => {\n        setChosenStep({ id: CommandBarStepEnum.PRIVATE_REPOS });\n        setIsVisible(true);\n      },\n    };\n  }, []);\n\n  const openPublicRepos = useMemo(() => {\n    return {\n      shortcut: ['cmd', 'shift', 'P'],\n      action: () => {\n        setChosenStep({ id: CommandBarStepEnum.PUBLIC_REPOS });\n        setIsVisible(true);\n      },\n    };\n  }, []);\n\n  const openLocalRepos = useMemo(() => {\n    return {\n      shortcut: ['cmd', 'shift', 'O'],\n      action: () => {\n        setChosenStep({ id: CommandBarStepEnum.LOCAL_REPOS });\n        setIsVisible(true);\n      },\n    };\n  }, []);\n\n  const openAddDocs = useMemo(() => {\n    return {\n      shortcut: ['cmd', 'shift', 'D'],\n      action: () => {\n        setChosenStep({ id: CommandBarStepEnum.DOCS });\n        setIsVisible(true);\n      },\n    };\n  }, []);\n\n  const openManageRepos = useMemo(() => {\n    return {\n      shortcut: ['option', 'R'],\n      action: () => {\n        setChosenStep({ id: CommandBarStepEnum.MANAGE_REPOS });\n        setIsVisible(true);\n      },\n    };\n  }, []);\n\n  const openSearchFiles = useMemo(() => {\n    return {\n      shortcut: ['option', 'F'],\n      action: () => {\n        setChosenStep({ id: CommandBarStepEnum.SEARCH_FILES });\n        setIsVisible(true);\n      },\n    };\n  }, []);\n\n  const createNewProject = useMemo(() => {\n    return {\n      shortcut: newProjectShortcut,\n      action: () => {\n        setChosenStep({ id: CommandBarStepEnum.CREATE_PROJECT });\n        setIsVisible(true);\n      },\n    };\n  }, []);\n\n  const openProjectSettings = useMemo(() => {\n    return {\n      shortcut: ['option', 'P'],\n      action: () => {\n        setProjectSettingsSection(ProjectSettingSections.GENERAL);\n        setProjectSettingsOpen(true);\n        setIsVisible(false);\n      },\n    };\n  }, []);\n\n  const openSettings = useMemo(() => {\n    return {\n      shortcut: ['option', 'A'],\n      action: () => {\n        setSettingsSection(SettingSections.GENERAL);\n        setSettingsOpen(true);\n        setIsVisible(false);\n      },\n    };\n  }, []);\n\n  const openAppDocs = useMemo(() => {\n    return {\n      shortcut: ['option', 'D'],\n      action: () => {\n        openLink('https://bloop.ai/docs');\n      },\n    };\n  }, []);\n\n  const reportABug = useMemo(() => {\n    return {\n      shortcut: ['option', 'B'],\n      action: () => {\n        setBugReportModalOpen(true);\n        setIsVisible(false);\n      },\n    };\n  }, []);\n\n  const toggleRegex = useMemo(() => {\n    return {\n      shortcut: regexToggleShortcut,\n      action: () => {\n        setIsRegexSearchEnabled((prev) => !prev);\n        setIsVisible(false);\n      },\n    };\n  }, []);\n\n  const closeAllTabs = useMemo(() => {\n    return {\n      shortcut: ['cmd', 'shift', 'W'],\n      action: () => {\n        setLeftTabs([]);\n        setRightTabs([]);\n        setActiveRightTab(null);\n        setActiveLeftTab(null);\n        setFocusedPanel('left');\n        setIsVisible(false);\n      },\n    };\n  }, []);\n\n  return {\n    toggleTheme,\n    openPrivateRepos,\n    openPublicRepos,\n    openLocalRepos,\n    openAddDocs,\n    openManageRepos,\n    createNewProject,\n    openProjectSettings,\n    openSettings,\n    openAppDocs,\n    reportABug,\n    toggleRegex,\n    openSearchFiles,\n    closeAllTabs,\n  };\n};\n"
  },
  {
    "path": "client/src/hooks/useKeyboardNavigation.ts",
    "content": "import { useEffect } from 'react';\n\nconst useKeyboardNavigation = (\n  handleKeyEvent: (e: KeyboardEvent) => void,\n  disabled?: boolean,\n  onCapture?: boolean,\n) => {\n  useEffect(() => {\n    if (!disabled) {\n      window.addEventListener('keydown', handleKeyEvent, onCapture);\n    }\n\n    return () => {\n      window.removeEventListener('keydown', handleKeyEvent, onCapture);\n    };\n  }, [handleKeyEvent, disabled, onCapture]);\n};\n\nexport default useKeyboardNavigation;\n"
  },
  {
    "path": "client/src/hooks/useNavPanel.ts",
    "content": "import {\n  Dispatch,\n  MouseEvent,\n  SetStateAction,\n  useCallback,\n  useEffect,\n} from 'react';\nimport { useArrowNavigationItemProps } from './useArrowNavigationItemProps';\n\nexport const useNavPanel = (\n  index: string,\n  setExpanded: Dispatch<SetStateAction<string>>,\n  isExpanded: boolean,\n) => {\n  const onClick = useCallback(() => {\n    setExpanded((prev) => (prev === index ? '' : index));\n  }, [index]);\n\n  useEffect(() => {\n    if (isExpanded) {\n      // containerRef.current?.scrollIntoView({ block: 'nearest' });\n    }\n  }, [isExpanded]);\n\n  const noPropagate = useCallback((e?: MouseEvent) => {\n    e?.stopPropagation();\n  }, []);\n\n  const { isFocused, isLeftSidebarFocused, props } =\n    useArrowNavigationItemProps<HTMLAnchorElement>(index, onClick);\n\n  return {\n    noPropagate,\n    itemProps: {\n      ...props,\n      className: `h-10 flex items-center gap-3 px-4 ellipsis ${\n        isExpanded ? 'sticky z-10 top-0 left-0' : ''\n      } ${\n        isFocused ? 'bg-bg-sub-hover' : 'bg-bg-sub'\n      } outline-0 outline-none focus:outline-0 focus:outline-none`,\n      tabIndex: 0,\n      role: 'button',\n    },\n  };\n};\n"
  },
  {
    "path": "client/src/hooks/useOnClickOutsideHook.ts",
    "content": "import React from 'react';\n\nexport function useOnClickOutside(\n  ref: React.MutableRefObject<HTMLElement | null>,\n  handler: (e: MouseEvent) => void,\n  excludeRef?: React.MutableRefObject<HTMLElement | null>,\n) {\n  React.useEffect(\n    () => {\n      const listener = (event: MouseEvent) => {\n        // Do nothing if clicking ref's element or descendent elements\n        if (\n          !ref?.current ||\n          ref.current.contains(event.target as Node) ||\n          excludeRef?.current?.contains(event.target as Node)\n        ) {\n          return;\n        }\n        handler(event);\n      };\n      document.addEventListener('mousedown', listener);\n      return () => {\n        document.removeEventListener('mousedown', listener);\n      };\n    },\n    // Add ref and handler to effect dependencies\n    // It's worth noting that because passed in handler is a new ...\n    // ... function on every render that will cause this effect ...\n    // ... callback/cleanup to run every render. It's not a big deal ...\n    // ... but to optimize you can wrap handler in useCallback before ...\n    // ... passing it into this hook.\n    [ref, handler, excludeRef],\n  );\n}\n"
  },
  {
    "path": "client/src/hooks/usePersistentState.tsx",
    "content": "import React, { useEffect, useState } from 'react';\nimport { getJsonFromStorage, saveJsonToStorage } from '../services/storage';\n\nexport const usePersistentState = <T,>(\n  defaultValue: T,\n  key: string,\n): [T, React.Dispatch<React.SetStateAction<T>>] => {\n  const [value, setValue] = useState<T>(() => {\n    const storedValue = getJsonFromStorage<T>(key);\n\n    return storedValue !== null ? storedValue : defaultValue;\n  });\n  useEffect(() => {\n    saveJsonToStorage(key, value);\n  }, [key, value]);\n  return [value, setValue];\n};\n"
  },
  {
    "path": "client/src/hooks/useResizeableWidth.ts",
    "content": "import { useEffect, useRef } from 'react';\n\nconst useResizeableWidth = (\n  isLeftSidebar: boolean,\n  localStorageKey: string,\n  defaultWidth: number,\n  maxWidth: number,\n  minWidth = 5,\n) => {\n  const panelRef = useRef<HTMLDivElement>(null);\n  const dividerRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    const panel = panelRef.current;\n    const divider = dividerRef.current;\n\n    const savedPanelSize = Number(localStorage.getItem(localStorageKey));\n    if (panel) {\n      if (!savedPanelSize) {\n        panel.style.width = `${defaultWidth}%`;\n      } else {\n        panel.style.width = `${Math.min(savedPanelSize, maxWidth)}%`;\n        if (savedPanelSize > maxWidth) {\n          localStorage.setItem(localStorageKey, maxWidth.toString());\n        }\n      }\n    }\n\n    const handleMouseDown = (e: MouseEvent) => {\n      e.preventDefault();\n\n      const handleMouseMove = (e: MouseEvent) => {\n        if (panel && divider) {\n          const containerWidth =\n            panel.parentElement?.getBoundingClientRect().width ||\n            window.innerWidth;\n          let newPanelWidth =\n            ((isLeftSidebar ? e.clientX : containerWidth - e.clientX) /\n              containerWidth) *\n            100;\n          newPanelWidth = Math.max(minWidth, Math.min(newPanelWidth, maxWidth));\n          panel.style.width = `${newPanelWidth}%`;\n\n          localStorage.setItem(localStorageKey, newPanelWidth.toString());\n        }\n      };\n\n      const handleMouseUp = () => {\n        document.removeEventListener('mousemove', handleMouseMove);\n        document.removeEventListener('mouseup', handleMouseUp);\n      };\n\n      document.addEventListener('mousemove', handleMouseMove);\n      document.addEventListener('mouseup', handleMouseUp);\n    };\n\n    const handleDoubleClick = () => {\n      if (panel) {\n        panel.style.width = `${defaultWidth}%`;\n\n        localStorage.setItem(localStorageKey, defaultWidth.toString());\n      }\n    };\n\n    dividerRef.current?.addEventListener('mousedown', handleMouseDown);\n    dividerRef.current?.addEventListener('dblclick', handleDoubleClick);\n\n    return () => {\n      dividerRef.current?.removeEventListener('mousedown', handleMouseDown);\n      dividerRef.current?.removeEventListener('dblclick', handleDoubleClick);\n    };\n  }, []);\n\n  return { panelRef, dividerRef };\n};\n\nexport default useResizeableWidth;\n"
  },
  {
    "path": "client/src/hooks/useScrollToBottom.ts",
    "content": "import { useCallback, useEffect, useRef, useState } from 'react';\nimport { ChatMessageAuthor } from '../types/general';\n\nconst useScrollToBottom = (contentArray: any) => {\n  const messagesRef = useRef<HTMLDivElement>(null);\n  const [userScrolledUp, setUserScrolledUp] = useState(false);\n\n  const handleScroll = useCallback(() => {\n    if (messagesRef.current) {\n      const scrollTop = messagesRef.current.scrollTop;\n      const scrollHeight = messagesRef.current.scrollHeight;\n      const clientHeight = messagesRef.current.clientHeight;\n\n      setUserScrolledUp(scrollTop < scrollHeight - clientHeight);\n    }\n  }, []);\n\n  const scrollToBottom = useCallback(() => {\n    if (messagesRef.current) {\n      messagesRef.current?.scrollTo({\n        left: 0,\n        top: messagesRef.current.scrollHeight,\n        behavior: 'smooth',\n      });\n    }\n  }, []);\n\n  useEffect(() => {\n    if (\n      !userScrolledUp ||\n      contentArray[contentArray.length - 1]?.author === ChatMessageAuthor.User\n    ) {\n      scrollToBottom();\n    }\n  }, [contentArray, scrollToBottom, userScrolledUp]);\n\n  return { messagesRef, handleScroll, scrollToBottom };\n};\n\nexport default useScrollToBottom;\n"
  },
  {
    "path": "client/src/hooks/useShortcuts.ts",
    "content": "import { useContext, useMemo } from 'react';\nimport { DeviceContext } from '../context/deviceContext';\nimport { mapShortcuts } from '../utils/keyboardUtils';\n\nconst useShortcuts = (shortcut?: string[]) => {\n  const { os } = useContext(DeviceContext);\n\n  const shortcutKeys = useMemo(() => {\n    return mapShortcuts(shortcut, os.type);\n  }, [os, shortcut]);\n\n  return shortcutKeys;\n};\n\nexport default useShortcuts;\n"
  },
  {
    "path": "client/src/hooks/useStateRef.ts",
    "content": "import { useCallback, useRef, useState } from 'react';\n\nexport default function useStateRef(initialState: any) {\n  const [state, setState] = useState(initialState);\n  const ref = useRef();\n  const setValue = useCallback(\n    (nextValue: any) => {\n      if (typeof nextValue === 'function') {\n        setValue((state: any) => {\n          nextValue = nextValue(state);\n\n          ref.current = nextValue;\n\n          return nextValue;\n        });\n      } else {\n        ref.current = nextValue;\n\n        setValue(nextValue);\n      }\n    },\n    [ref],\n  );\n\n  ref.current = state;\n\n  return [state, setState, ref];\n}\n"
  },
  {
    "path": "client/src/i18n.ts",
    "content": "import i18n from 'i18next';\nimport { initReactI18next } from 'react-i18next';\nimport backend from 'i18next-http-backend';\nimport en from './locales/en.json';\nimport ja from './locales/ja.json';\nimport zhCN from './locales/zh-CN.json';\nimport es from './locales/es.json';\nimport it from './locales/it.json';\nimport zhTW from './locales/zh-TW.json';\nimport { getPlainFromStorage, LANGUAGE_KEY } from './services/storage';\n\n// the translations\n// (tip move them in a JSON file and import them,\n// or even better, manage them separated from your code: https://react.i18next.com/guides/multiple-translation-files)\nconst resources = {\n  en: {\n    translation: en,\n  },\n  ja: {\n    translation: ja,\n  },\n  zhCN: {\n    translation: zhCN,\n  },\n  es: {\n    translation: es,\n  },\n  it: {\n    translation: it,\n  },\n  zhTW: {\n    translation: zhTW,\n  },\n};\n\ni18n\n  .use(backend)\n  .use(initReactI18next) // passes i18n down to react-i18next\n  .init({\n    resources,\n    lng: getPlainFromStorage(LANGUAGE_KEY) || 'en', // language to use, more information here: https://www.i18next.com/overview/configuration-options#languages-namespaces-resources\n    // you can use the i18n.changeLanguage function to change the language manually: https://www.i18next.com/overview/api#changelanguage\n    // if you're using a language detector, do not define the lng option\n    saveMissing: true,\n    interpolation: {\n      escapeValue: false, // react already safes from xss\n    },\n    backend: {\n      addPath: 'http://localhost:3000/locales/add/{{lng}}/{{ns}}',\n      crossOrigin: true,\n    },\n  });\n\nexport default i18n;\n"
  },
  {
    "path": "client/src/icons/ArrowHistory.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M7 2.91667C4.74484 2.91667 2.91667 4.74484 2.91667 7C2.91667 8.38691 3.60807 9.61316 4.66667 10.3516V8.75C4.66667 8.42783 4.92783 8.16667 5.25 8.16667C5.57217 8.16667 5.83333 8.42783 5.83333 8.75V11.6667C5.83333 11.9888 5.57217 12.25 5.25 12.25H2.33333C2.01117 12.25 1.75 11.9888 1.75 11.6667C1.75 11.3445 2.01117 11.0833 2.33333 11.0833H3.70004C2.5111 10.1213 1.75 8.64988 1.75 7C1.75 4.10051 4.10051 1.75 7 1.75C7.69514 1.75 8.3601 1.88541 8.96888 2.13184C9.2675 2.25272 9.4116 2.5928 9.29071 2.89143C9.16983 3.19005 8.82975 3.33415 8.53113 3.21327C8.05906 3.02218 7.54257 2.91667 7 2.91667Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M7.58333 12.25C7.9055 12.25 8.16667 11.9888 8.16667 11.6667C8.16667 11.3445 7.9055 11.0833 7.58333 11.0833C7.26117 11.0833 7 11.3445 7 11.6667C7 11.9888 7.26117 12.25 7.58333 12.25Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M12.25 6.41666C12.25 6.09449 11.9888 5.83333 11.6667 5.83333C11.3445 5.83333 11.0833 6.09449 11.0833 6.41666C11.0833 6.73882 11.3445 6.99999 11.6667 6.99999C11.9888 6.99999 12.25 6.73882 12.25 6.41666Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M11.6255 8.32297C11.9045 8.48405 12.0001 8.84081 11.839 9.11981C11.678 9.39882 11.3212 9.49441 11.0422 9.33333C10.7632 9.17225 10.6676 8.81548 10.8287 8.53648C10.9898 8.25748 11.3465 8.16188 11.6255 8.32297Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M10.1311 11.2549C10.4101 11.0939 10.5057 10.7371 10.3446 10.4581C10.1835 10.1791 9.82678 10.0835 9.54777 10.2446C9.26877 10.4057 9.17317 10.7624 9.33426 11.0414C9.49534 11.3204 9.8521 11.416 10.1311 11.2549Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M11.0407 4.66666C10.7617 4.82774 10.405 4.73215 10.2439 4.45314C10.0828 4.17414 10.1784 3.81738 10.4574 3.65629C10.7364 3.49521 11.0931 3.5908 11.2542 3.86981C11.4153 4.14881 11.3197 4.50557 11.0407 4.66666Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/ArrowLeft.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M17 10L3 10M3 10L7.35484 14M3 10L7.35484 6\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/ArrowOut.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M10.2428 4.37465L4.08362 10.5351L3.46484 9.9164L9.62403 3.75599L10.2428 4.37465Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M3.64606 3.64551H9.9169C10.1585 3.64551 10.3544 3.84138 10.3544 4.08301V10.3538H9.4794V4.52051H3.64606V3.64551Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/ArrowTriangleBottom.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 8 8\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M1.40436 2.29536C1.17992 1.84517 1.51573 1.33228 2.00612 1.33228L5.99372 1.33228C6.48411 1.33228 6.81992 1.84517 6.59547 2.29536L4.60167 6.2946C4.35505 6.78928 3.64478 6.78928 3.39817 6.2946L1.40436 2.29536Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/BloopLogo.tsx",
    "content": "const BloopLogo = () => (\n  <svg\n    width=\"44\"\n    height=\"44\"\n    viewBox=\"0 0 44 44\"\n    fill=\"none\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n  >\n    <rect\n      x=\"0.261905\"\n      y=\"0.261905\"\n      width=\"43.4762\"\n      height=\"43.4762\"\n      rx=\"10.1533\"\n      fill=\"url(#paint0_linear_8633_246841)\"\n    />\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M27.2171 21.9872C27.5622 22.8484 27.7302 23.77 27.7111 24.6973C27.7051 25.8144 27.4424 26.9153 26.9431 27.9153C26.4439 28.9153 25.7214 29.7878 24.8312 30.4658C21.3431 33.1583 17.8432 31.6985 16.4504 29.9687C16.2527 30.1659 16.0858 30.3567 15.9215 30.5444C15.7694 30.7182 15.6196 30.8894 15.4499 31.0606H13.0957V11H17.9453V16.1776L17.8275 18.6626C18.5887 17.7938 22.5123 16.3067 25.7022 19.6841C26.3568 20.3427 26.872 21.126 27.2171 21.9872ZM22.6087 26.1681C22.8996 25.7325 23.0545 25.2207 23.0538 24.6973C23.0527 23.9963 22.7728 23.3244 22.2755 22.8291C21.7782 22.3338 21.1042 22.0556 20.4014 22.0556C19.8767 22.0556 19.3637 22.2109 18.9275 22.5018C18.4912 22.7926 18.1512 23.206 17.9506 23.6897C17.75 24.1733 17.6977 24.7054 17.8003 25.2187C17.903 25.732 18.156 26.2034 18.5273 26.5732C18.8987 26.9431 19.3716 27.1947 19.8864 27.2963C20.4011 27.398 20.9346 27.345 21.4192 27.1442C21.9037 26.9434 22.3177 26.6037 22.6087 26.1681Z\"\n      fill=\"#F9F9F9\"\n    />\n    <path\n      d=\"M29.925 30.7687C30.3617 31.0589 30.8749 31.2134 31.3997 31.2126C32.1024 31.2116 32.7761 30.9324 33.2726 30.4364C33.7692 29.9404 34.0481 29.2681 34.0481 28.5671C34.0481 28.0437 33.8924 27.5321 33.6008 27.0969C33.3092 26.6618 32.8947 26.3227 32.4098 26.1226C31.925 25.9225 31.3915 25.8703 30.8768 25.9727C30.3622 26.0751 29.8896 26.3274 29.5189 26.6978C29.1481 27.0682 28.8958 27.54 28.7939 28.0534C28.692 28.5668 28.7451 29.0989 28.9464 29.5822C29.1478 30.0656 29.4883 30.4785 29.925 30.7687Z\"\n      fill=\"#F9F9F9\"\n    />\n    <rect\n      x=\"0.261905\"\n      y=\"0.261905\"\n      width=\"43.4762\"\n      height=\"43.4762\"\n      rx=\"10.1533\"\n      stroke=\"url(#paint1_linear_8633_246841)\"\n      strokeWidth=\"0.52381\"\n    />\n    <defs>\n      <linearGradient\n        id=\"paint0_linear_8633_246841\"\n        x1=\"7.07143\"\n        y1=\"45.5714\"\n        x2=\"44.1322\"\n        y2=\"2.79704\"\n        gradientUnits=\"userSpaceOnUse\"\n      >\n        <stop stopColor=\"#1A1B1D\" />\n        <stop offset=\"1\" stopColor=\"#1D2125\" />\n      </linearGradient>\n      <linearGradient\n        id=\"paint1_linear_8633_246841\"\n        x1=\"44\"\n        y1=\"0\"\n        x2=\"25.9286\"\n        y2=\"26.4524\"\n        gradientUnits=\"userSpaceOnUse\"\n      >\n        <stop stopColor=\"#27272D\" />\n        <stop offset=\"1\" stopColor=\"#27272D\" stopOpacity=\"0\" />\n      </linearGradient>\n    </defs>\n  </svg>\n);\n\nexport default BloopLogo;\n"
  },
  {
    "path": "client/src/icons/Branch.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M4.33333 2.66683C3.78105 2.66683 3.33333 3.11454 3.33333 3.66683C3.33333 4.21911 3.78105 4.66683 4.33333 4.66683C4.88562 4.66683 5.33333 4.21911 5.33333 3.66683C5.33333 3.11454 4.88562 2.66683 4.33333 2.66683ZM2 3.66683C2 2.37817 3.04467 1.3335 4.33333 1.3335C5.622 1.3335 6.66667 2.37817 6.66667 3.66683C6.66667 4.72384 5.96383 5.61669 5 5.90354V10.0968C5.96383 10.3836 6.66667 11.2765 6.66667 12.3335C6.66667 13.6222 5.622 14.6668 4.33333 14.6668C3.04467 14.6668 2 13.6222 2 12.3335C2 11.2765 2.70284 10.3836 3.66667 10.0968V5.90354C2.70284 5.61669 2 4.72384 2 3.66683ZM10.1381 1.52876C10.3984 1.78911 10.3984 2.21122 10.1381 2.47157L9.60948 3.00016H10.3333C11.4379 3.00016 12.3333 3.89559 12.3333 5.00016V10.0968C13.2972 10.3836 14 11.2765 14 12.3335C14 13.6222 12.9553 14.6668 11.6667 14.6668C10.378 14.6668 9.33333 13.6222 9.33333 12.3335C9.33333 11.2765 10.0362 10.3836 11 10.0968V5.00016C11 4.63197 10.7015 4.3335 10.3333 4.3335H9.60948L10.1381 4.86209C10.3984 5.12244 10.3984 5.54455 10.1381 5.8049C9.87772 6.06525 9.45561 6.06525 9.19526 5.8049L7.5286 4.13823C7.26825 3.87788 7.26825 3.45577 7.5286 3.19542L9.19526 1.52876C9.45561 1.26841 9.87772 1.26841 10.1381 1.52876ZM4.33333 11.3335C3.78105 11.3335 3.33333 11.7812 3.33333 12.3335C3.33333 12.8858 3.78105 13.3335 4.33333 13.3335C4.88562 13.3335 5.33333 12.8858 5.33333 12.3335C5.33333 11.7812 4.88562 11.3335 4.33333 11.3335ZM11.6667 11.3335C11.1144 11.3335 10.6667 11.7812 10.6667 12.3335C10.6667 12.8858 11.1144 13.3335 11.6667 13.3335C12.219 13.3335 12.6667 12.8858 12.6667 12.3335C12.6667 11.7812 12.219 11.3335 11.6667 11.3335Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Broom.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M12.6627 1.42112C12.9828 1.60301 13.0949 2.00997 12.913 2.3301L9.80732 7.79625L9.98261 7.86005C10.9964 8.22904 11.8071 9.23471 11.6385 10.4359C11.4145 12.0313 10.7331 13.1828 9.47548 14.4672C9.28405 14.6627 8.99258 14.7214 8.7404 14.6152L4.97791 13.0308C4.9069 13.0009 4.87594 12.9172 4.9104 12.8482L5.48858 11.6919C5.54589 11.5773 5.42428 11.4557 5.30967 11.513L3.56554 12.3854C3.53072 12.4029 3.49002 12.4042 3.45414 12.3891L2.40794 11.9485C2.18177 11.8533 2.02621 11.6415 2.003 11.3971C1.97979 11.1528 2.09268 10.9155 2.29688 10.7794C3.2684 10.1317 4.04518 9.49755 4.62872 8.57956C5.33304 7.47156 6.73159 6.67678 8.14931 7.19278L8.53659 7.33374L11.7537 1.67144C11.9356 1.35131 12.3425 1.23924 12.6627 1.42112Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Bug.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M11.3333 4.66683H11.9999C12.2739 4.66683 12.5092 4.83206 12.6118 5.0683L13.6054 4.70697C13.9514 4.58114 14.334 4.75965 14.4598 5.10567C14.5856 5.45169 14.4071 5.8342 14.0611 5.96003L12.6666 6.46711V8.00016H13.9999C14.3681 8.00016 14.6666 8.29864 14.6666 8.66683C14.6666 9.03502 14.3681 9.3335 13.9999 9.3335H12.6666V10.0002C12.6666 10.2866 12.6408 10.567 12.5914 10.8392L14.0611 11.3736C14.4071 11.4995 14.5856 11.882 14.4598 12.228C14.334 12.574 13.9514 12.7525 13.6054 12.6267L12.1668 12.1036C11.4933 13.4352 10.2002 14.4002 8.66659 14.6196V8.66683C8.66659 8.29864 8.36811 8.00016 7.99992 8.00016C7.63173 8.00016 7.33325 8.29864 7.33325 8.66683V14.6196C5.79964 14.4002 4.50656 13.4352 3.83304 12.1036L2.39441 12.6267C2.04839 12.7525 1.66588 12.574 1.54006 12.228C1.41423 11.882 1.59273 11.4995 1.93876 11.3736L3.40847 10.8392C3.35906 10.567 3.33325 10.2866 3.33325 10.0002V9.3335H1.99992C1.63173 9.3335 1.33325 9.03502 1.33325 8.66683C1.33325 8.29864 1.63173 8.00016 1.99992 8.00016H3.33325V6.46711L1.93876 5.96003C1.59273 5.8342 1.41423 5.45169 1.54006 5.10567C1.66588 4.75965 2.04839 4.58114 2.39441 4.70697L3.38808 5.0683C3.49062 4.83206 3.72597 4.66683 3.99992 4.66683H4.66659C4.66659 2.82588 6.15897 1.3335 7.99992 1.3335C9.84087 1.3335 11.3333 2.82588 11.3333 4.66683ZM7.99992 2.66683C6.89535 2.66683 5.99992 3.56226 5.99992 4.66683H9.99992C9.99992 3.56226 9.10449 2.66683 7.99992 2.66683Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/ChatBubbles.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M12.6683 2C13.7729 2 14.6683 2.89543 14.6683 4V8C14.6683 9.10457 13.7729 10 12.6683 10H12.0016V10.6667C12.0016 11.7712 11.1062 12.6667 10.0016 12.6667H7.17438L4.32539 14.2494C4.1189 14.3642 3.86713 14.361 3.66354 14.2412C3.45996 14.1215 3.33496 13.9029 3.33496 13.6667V12.6667C2.23039 12.6667 1.33496 11.7712 1.33496 10.6667V6.66667C1.33496 5.5621 2.23039 4.66667 3.33496 4.66667H4.00163V4C4.00163 2.89543 4.89706 2 6.00163 2H12.6683ZM5.33496 4.66667H10.0016C11.1062 4.66667 12.0016 5.5621 12.0016 6.66667V8.66667H12.6683C13.0365 8.66667 13.335 8.36819 13.335 8V4C13.335 3.63181 13.0365 3.33333 12.6683 3.33333H6.00163C5.63344 3.33333 5.33496 3.63181 5.33496 4V4.66667Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Check.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M14.3936 2.12867C14.6907 2.34607 14.7554 2.7632 14.538 3.06035L6.73407 13.727C6.62696 13.8734 6.46482 13.9699 6.28504 13.9941C6.10527 14.0183 5.92338 13.9682 5.78135 13.8554L1.58527 10.522C1.29697 10.293 1.24892 9.87366 1.47794 9.58537C1.70696 9.29707 2.12633 9.24902 2.41462 9.47804L6.06756 12.3799L13.4619 2.27307C13.6793 1.97592 14.0964 1.91127 14.3936 2.12867Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/CheckList.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M5.3084 3.03369C5.56613 3.22699 5.61836 3.59263 5.42506 3.85036L3.67506 6.18369C3.48931 6.43137 3.14242 6.49079 2.88482 6.31906L2.00982 5.73572C1.74176 5.55702 1.66933 5.19484 1.84803 4.92678C2.02674 4.65873 2.38891 4.58629 2.65697 4.765L3.07282 5.04223L4.49173 3.15036C4.68503 2.89263 5.05066 2.84039 5.3084 3.03369ZM7.00006 4.66703C7.00006 4.34486 7.26123 4.08369 7.5834 4.08369H11.6667C11.9889 4.08369 12.2501 4.34486 12.2501 4.66703C12.2501 4.98919 11.9889 5.25036 11.6667 5.25036H7.5834C7.26123 5.25036 7.00006 4.98919 7.00006 4.66703ZM5.3084 7.70036C5.56613 7.89366 5.61836 8.25929 5.42506 8.51703L3.67506 10.8504C3.48931 11.098 3.14242 11.1575 2.88482 10.9857L2.00982 10.4024C1.74176 10.2237 1.66933 9.86151 1.84803 9.59345C2.02674 9.32539 2.38891 9.25296 2.65697 9.43166L3.07282 9.7089L4.49173 7.81703C4.68503 7.55929 5.05066 7.50706 5.3084 7.70036ZM7.00006 9.33369C7.00006 9.01153 7.26123 8.75036 7.5834 8.75036H11.6667C11.9889 8.75036 12.2501 9.01153 12.2501 9.33369C12.2501 9.65586 11.9889 9.91703 11.6667 9.91703H7.5834C7.26123 9.91703 7.00006 9.65586 7.00006 9.33369Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/CheckmarkInSquare.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M8.7587 3H15.2413C16.0463 2.99999 16.7106 2.99998 17.2518 3.0442C17.8139 3.09012 18.3306 3.18868 18.816 3.43598C19.5686 3.81947 20.1805 4.43139 20.564 5.18404C20.8113 5.66938 20.9099 6.18608 20.9558 6.74818C21 7.28937 21 7.95372 21 8.75868V15.2413C21 16.0463 21 16.7106 20.9558 17.2518C20.9099 17.8139 20.8113 18.3306 20.564 18.816C20.1805 19.5686 19.5686 20.1805 18.816 20.564C18.3306 20.8113 17.8139 20.9099 17.2518 20.9558C16.7106 21 16.0463 21 15.2413 21H8.75868C7.95372 21 7.28937 21 6.74818 20.9558C6.18608 20.9099 5.66938 20.8113 5.18404 20.564C4.43139 20.1805 3.81947 19.5686 3.43598 18.816C3.18868 18.3306 3.09012 17.8139 3.0442 17.2518C2.99998 16.7106 2.99999 16.0463 3 15.2413V8.75869C2.99999 7.95373 2.99998 7.28936 3.0442 6.74818C3.09012 6.18608 3.18868 5.66938 3.43598 5.18404C3.81947 4.43139 4.43139 3.81947 5.18404 3.43598C5.66938 3.18868 6.18608 3.09012 6.74818 3.0442C7.28936 2.99998 7.95375 2.99999 8.7587 3ZM16.2039 10.4571C16.5944 10.0666 16.5944 9.43343 16.2039 9.0429C15.8133 8.65238 15.1802 8.65238 14.7897 9.0429L10.9968 12.8358L9.70387 11.5429C9.31334 11.1524 8.68018 11.1524 8.28966 11.5429C7.89913 11.9334 7.89913 12.5666 8.28966 12.9571L10.2897 14.9571C10.6802 15.3476 11.3133 15.3476 11.7039 14.9571L16.2039 10.4571Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/ChevronDown.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M14.45 9.6C14.7814 9.35147 14.8485 8.88137 14.6 8.55C14.3515 8.21863 13.8814 8.15147 13.55 8.4L14.45 9.6ZM10 12L9.55 12.6C9.81667 12.8 10.1833 12.8 10.45 12.6L10 12ZM6.45 8.4C6.11863 8.15147 5.64853 8.21863 5.4 8.55C5.15147 8.88137 5.21863 9.35147 5.55 9.6L6.45 8.4ZM13.55 8.4L9.55 11.4L10.45 12.6L14.45 9.6L13.55 8.4ZM10.45 11.4L6.45 8.4L5.55 9.6L9.55 12.6L10.45 11.4Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/ChevronRight.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M5.42085 4.25411C5.64866 4.0263 6.01801 4.0263 6.24581 4.25411L8.57915 6.58744C8.80695 6.81525 8.80695 7.18459 8.57915 7.4124L6.24581 9.74573C6.01801 9.97354 5.64866 9.97354 5.42085 9.74573C5.19305 9.51793 5.19305 9.14858 5.42085 8.92077L7.34171 6.99992L5.42085 5.07906C5.19305 4.85126 5.19305 4.48191 5.42085 4.25411Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/ChevronUp.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M14.45 10.4C14.7814 10.6485 14.8485 11.1186 14.6 11.45C14.3515 11.7814 13.8814 11.8485 13.55 11.6L14.45 10.4ZM10 8L9.55 7.4C9.81667 7.2 10.1833 7.2 10.45 7.4L10 8ZM6.45 11.6C6.11863 11.8485 5.64853 11.7814 5.4 11.45C5.15147 11.1186 5.21863 10.6485 5.55 10.4L6.45 11.6ZM13.55 11.6L9.55 8.6L10.45 7.4L14.45 10.4L13.55 11.6ZM10.45 8.6L6.45 11.6L5.55 10.4L9.55 7.4L10.45 8.6Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Clipboard.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M13 3H14.5C15.8807 3 17 4.11929 17 5.5V15.5C17 16.8807 15.8807 18 14.5 18H5.5C4.11929 18 3 16.8807 3 15.5V5.5C3 4.11929 4.11929 3 5.5 3H7\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n    />\n    <path\n      d=\"M7.5 11.0952L9.5 13L13 9.5\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n    <path\n      d=\"M6.75 3C6.75 2.30964 7.30964 1.75 8 1.75H12C12.6904 1.75 13.25 2.30964 13.25 3V5.38197C13.25 5.56781 13.0544 5.68869 12.8882 5.60557L10.7826 4.55279C10.29 4.30645 9.71005 4.30645 9.21738 4.55279L7.1118 5.60557C6.94558 5.68869 6.75 5.56781 6.75 5.38197V3Z\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/CloseSign.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M5.05026 14.9498L14.9498 5.05027\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n    <path\n      d=\"M5.05026 5.05029L14.9498 14.9498\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/CloseSignInCircle.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M1.16675 6.99996C1.16675 3.7783 3.77842 1.16663 7.00008 1.16663C10.2217 1.16663 12.8334 3.7783 12.8334 6.99996C12.8334 10.2216 10.2217 12.8333 7.00008 12.8333C3.77842 12.8333 1.16675 10.2216 1.16675 6.99996ZM5.66256 4.83748C5.43475 4.60967 5.06541 4.60967 4.8376 4.83748C4.6098 5.06529 4.6098 5.43463 4.8376 5.66244L6.17512 6.99996L4.8376 8.33748C4.6098 8.56529 4.6098 8.93463 4.8376 9.16244C5.06541 9.39024 5.43475 9.39024 5.66256 9.16244L7.00008 7.82492L8.3376 9.16244C8.56541 9.39024 8.93476 9.39024 9.16256 9.16244C9.39037 8.93463 9.39037 8.56529 9.16256 8.33748L7.82504 6.99996L9.16256 5.66244C9.39037 5.43463 9.39037 5.06529 9.16256 4.83748C8.93476 4.60967 8.56541 4.60967 8.3376 4.83748L7.00008 6.175L5.66256 4.83748Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Code.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M1.75 4.08333C1.75 2.79467 2.79467 1.75 4.08333 1.75H9.91667C11.2053 1.75 12.25 2.79467 12.25 4.08333V9.91667C12.25 11.2053 11.2053 12.25 9.91667 12.25H4.08333C2.79467 12.25 1.75 11.2053 1.75 9.91667V4.08333ZM6.24581 5.12919C6.47362 5.35699 6.47362 5.72634 6.24581 5.95415L5.19996 7L6.24581 8.04585C6.47362 8.27366 6.47362 8.64301 6.24581 8.87081C6.01801 9.09862 5.64866 9.09862 5.42085 8.87081L4.375 7.82496C3.91939 7.36935 3.91939 6.63065 4.375 6.17504L5.42085 5.12919C5.64866 4.90138 6.01801 4.90138 6.24581 5.12919ZM8.57915 5.12919C8.35134 4.90138 7.98199 4.90138 7.75419 5.12919C7.52638 5.35699 7.52638 5.72634 7.75419 5.95415L8.80004 7L7.75419 8.04585C7.52638 8.27366 7.52638 8.64301 7.75419 8.87081C7.98199 9.09862 8.35134 9.09862 8.57915 8.87081L9.625 7.82496C10.0806 7.36935 10.0806 6.63065 9.625 6.17504L8.57915 5.12919Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/CodeLineWithSparkle.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M1.16675 2.91683C1.16675 2.59466 1.42792 2.3335 1.75008 2.3335H7.58342C7.90558 2.3335 8.16675 2.59466 8.16675 2.91683C8.16675 3.239 7.90558 3.50016 7.58342 3.50016H1.75008C1.42792 3.50016 1.16675 3.239 1.16675 2.91683ZM9.91675 2.91683C9.91675 2.59466 10.1779 2.3335 10.5001 2.3335H12.2501C12.5722 2.3335 12.8334 2.59466 12.8334 2.91683C12.8334 3.239 12.5722 3.50016 12.2501 3.50016H10.5001C10.1779 3.50016 9.91675 3.239 9.91675 2.91683ZM1.16675 7.00016C1.16675 6.678 1.42792 6.41683 1.75008 6.41683H4.66675C4.98891 6.41683 5.25008 6.678 5.25008 7.00016C5.25008 7.32233 4.98891 7.5835 4.66675 7.5835H1.75008C1.42792 7.5835 1.16675 7.32233 1.16675 7.00016ZM1.16675 11.0835C1.16675 10.7613 1.42792 10.5002 1.75008 10.5002H5.83342C6.15558 10.5002 6.41675 10.7613 6.41675 11.0835C6.41675 11.4057 6.15558 11.6668 5.83342 11.6668H1.75008C1.42792 11.6668 1.16675 11.4057 1.16675 11.0835Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M10.5834 6.79183C10.5834 6.58472 10.4155 6.41683 10.2084 6.41683C10.0013 6.41683 9.83342 6.58472 9.83342 6.79183C9.83342 7.5273 9.67051 7.97128 9.40419 8.2376C9.13787 8.50392 8.69389 8.66683 7.95842 8.66683C7.75131 8.66683 7.58342 8.83472 7.58342 9.04183C7.58342 9.24894 7.75131 9.41683 7.95842 9.41683C8.69389 9.41683 9.13787 9.57974 9.40419 9.84606C9.67051 10.1124 9.83342 10.5564 9.83342 11.2918C9.83342 11.4989 10.0013 11.6668 10.2084 11.6668C10.4155 11.6668 10.5834 11.4989 10.5834 11.2918C10.5834 10.5564 10.7463 10.1124 11.0126 9.84606C11.279 9.57974 11.7229 9.41683 12.4584 9.41683C12.6655 9.41683 12.8334 9.24894 12.8334 9.04183C12.8334 8.83472 12.6655 8.66683 12.4584 8.66683C11.7229 8.66683 11.279 8.50392 11.0126 8.2376C10.7463 7.97128 10.5834 7.5273 10.5834 6.79183Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/CodeStudio.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M8.52687 2.27685C8.91757 1.88615 9.44747 1.66666 10 1.66666C10.5525 1.66666 11.0824 1.88615 11.4731 2.27685C11.8638 2.66755 12.0833 3.19746 12.0833 3.74999C12.0833 4.30252 11.8638 4.83243 11.4731 5.22313C11.0824 5.61383 10.5525 5.83332 10 5.83332C9.44747 5.83332 8.91757 5.61383 8.52687 5.22313C8.13617 4.83243 7.91667 4.30252 7.91667 3.74999C7.91667 3.19746 8.13617 2.66755 8.52687 2.27685ZM5.05209 5.83332C5.05209 5.40185 5.40187 5.05207 5.83334 5.05207H5.84376C6.27523 5.05207 6.62501 5.40185 6.62501 5.83332C6.62501 6.2648 6.27523 6.61457 5.84376 6.61457H5.83334C5.40187 6.61457 5.05209 6.2648 5.05209 5.83332ZM13.3854 5.83332C13.3854 5.40185 13.7352 5.05207 14.1667 5.05207H14.1771C14.6086 5.05207 14.9583 5.40185 14.9583 5.83332C14.9583 6.2648 14.6086 6.61457 14.1771 6.61457H14.1667C13.7352 6.61457 13.3854 6.2648 13.3854 5.83332ZM2.27687 8.52685C2.66757 8.13615 3.19747 7.91666 3.75001 7.91666C4.30254 7.91666 4.83244 8.13615 5.22314 8.52685C5.61385 8.91755 5.83334 9.44745 5.83334 9.99999C5.83334 10.5525 5.61385 11.0824 5.22314 11.4731C4.83244 11.8638 4.30254 12.0833 3.75001 12.0833C3.19747 12.0833 2.66757 11.8638 2.27687 11.4731C1.88617 11.0824 1.66667 10.5525 1.66667 9.99999C1.66667 9.44745 1.88617 8.91755 2.27687 8.52685ZM8.52687 8.52685C8.91757 8.13615 9.44747 7.91666 10 7.91666C10.5525 7.91666 11.0824 8.13615 11.4731 8.52685C11.8638 8.91755 12.0833 9.44745 12.0833 9.99999C12.0833 10.5525 11.8638 11.0824 11.4731 11.4731C11.0824 11.8638 10.5525 12.0833 10 12.0833C9.44747 12.0833 8.91757 11.8638 8.52687 11.4731C8.13617 11.0824 7.91667 10.5525 7.91667 9.99999C7.91667 9.44745 8.13617 8.91755 8.52687 8.52685ZM14.7769 8.52685C15.1676 8.13615 15.6975 7.91666 16.25 7.91666C16.8025 7.91666 17.3324 8.13615 17.7231 8.52685C18.1138 8.91755 18.3333 9.44746 18.3333 9.99999C18.3333 10.5525 18.1138 11.0824 17.7231 11.4731C17.3324 11.8638 16.8025 12.0833 16.25 12.0833C15.6975 12.0833 15.1676 11.8638 14.7769 11.4731C14.3862 11.0824 14.1667 10.5525 14.1667 9.99999C14.1667 9.44746 14.3862 8.91755 14.7769 8.52685ZM5.05209 14.1667C5.05209 13.7352 5.40187 13.3854 5.83334 13.3854H5.84376C6.27523 13.3854 6.62501 13.7352 6.62501 14.1667C6.62501 14.5981 6.27523 14.9479 5.84376 14.9479H5.83334C5.40187 14.9479 5.05209 14.5981 5.05209 14.1667ZM13.3854 14.1667C13.3854 13.7352 13.7352 13.3854 14.1667 13.3854H14.1771C14.6086 13.3854 14.9583 13.7352 14.9583 14.1667C14.9583 14.5981 14.6086 14.9479 14.1771 14.9479H14.1667C13.7352 14.9479 13.3854 14.5981 13.3854 14.1667ZM8.52687 14.7768C8.91757 14.3861 9.44747 14.1667 10 14.1667C10.5525 14.1667 11.0824 14.3861 11.4731 14.7768C11.8638 15.1676 12.0833 15.6975 12.0833 16.25C12.0833 16.8025 11.8638 17.3324 11.4731 17.7231C11.0824 18.1138 10.5525 18.3333 10 18.3333C9.44747 18.3333 8.91757 18.1138 8.52687 17.7231C8.13616 17.3324 7.91667 16.8025 7.91667 16.25C7.91667 15.6975 8.13616 15.1676 8.52687 14.7768Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/Class.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M3.35356 6.64642L2.06066 5.35353L5.35356 2.06065L6.64645 3.35354L3.35356 6.64642ZM5 1L1 4.99998V5.70708L3 7.70707H3.70711L4.85355 6.56063V12.3535L5.35355 12.8535H10.0097V13.3741L11.343 14.7074H12.0501L14.7168 12.0407V11.3336L13.3835 10.0003H12.6763L10.8231 11.8535H5.85355V7.89355H10.0097V8.37401L11.343 9.70734H12.0501L14.7168 7.04068V6.33357L13.3835 5.00024H12.6763L10.863 6.81356H5.85355V5.56064L7.70711 3.70709V2.99999L5.70711 1H5ZM11.0703 8.02046L11.6966 8.64668L13.6561 6.68713L13.0299 6.0609L11.0703 8.02046ZM11.0703 13.0205L11.6966 13.6467L13.6561 11.6872L13.0299 11.061L11.0703 13.0205Z\"\n        fill=\"#D67E00\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/Color.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M6.6 11.7673C7.32871 11.0383 7.32871 9.85638 6.6 9.12742C5.87083 8.39845 4.68913 8.39845 3.95996 9.12742C2.91812 10.2249 1.10411 9.93901 1.0129 8.42853C1.00434 8.28679 1 8.1439 1 8C1 4.13399 4.13405 1 8 1C11.866 1 15 4.13399 15 8C15 11.866 11.866 15 8 15C7.79774 15 7.59749 14.9914 7.39959 14.9746C5.80646 14.8393 5.4986 12.9263 6.6 11.7673ZM6.96675 13.5434C6.90251 13.2464 6.98821 12.815 7.31819 12.4632C8.42629 11.3429 8.42264 9.53623 7.30723 8.42043L7.30701 8.42021C6.18734 7.30085 4.37262 7.30085 3.25295 8.42021L3.24371 8.42945L3.23471 8.43893C2.93903 8.7504 2.57091 8.82599 2.3325 8.77668C2.21936 8.75328 2.15021 8.70667 2.10979 8.66161C2.07393 8.62162 2.02146 8.54016 2.01108 8.36826C2.00373 8.24656 2 8.12378 2 8C2 4.68628 4.68633 2 8 2C11.3137 2 14 4.68628 14 8C14 11.3137 11.3137 14 8 14C7.82602 14 7.654 13.9926 7.48421 13.9782C7.28992 13.9617 7.18488 13.8987 7.12414 13.8429C7.05831 13.7824 6.99767 13.6864 6.96675 13.5434ZM5 6C5.55228 6 6 5.55228 6 5C6 4.44772 5.55228 4 5 4C4.44772 4 4 4.44772 4 5C4 5.55228 4.44772 6 5 6ZM12 11C12 11.5523 11.5523 12 11 12C10.4477 12 10 11.5523 10 11C10 10.4477 10.4477 10 11 10C11.5523 10 12 10.4477 12 11ZM8 5C8.55228 5 9 4.55228 9 4C9 3.44772 8.55228 3 8 3C7.44772 3 7 3.44772 7 4C7 4.55228 7.44772 5 8 5ZM13 8C13 8.55228 12.5523 9 12 9C11.4477 9 11 8.55228 11 8C11 7.44772 11.4477 7 12 7C12.5523 7 13 7.44772 13 8ZM11 6C11.5523 6 12 5.55228 12 5C12 4.44772 11.5523 4 11 4C10.4477 4 10 4.44772 10 5C10 5.55228 10.4477 6 11 6Z\"\n        fill=\"#424242\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/Constant.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M4.00024 6H12.0002V7H4.00024V6ZM12.0002 9H4.00024V10H12.0002V9Z\"\n        fill=\"#424242\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M1.00024 4L2.00024 3H14.0002L15.0002 4V12L14.0002 13H2.00024L1.00024 12V4ZM2.00024 4V12H14.0002V4H2.00024Z\"\n        fill=\"#424242\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/Enum.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M14 2H8L7 3V6H8V3H14V8H10V9H14L15 8V3L14 2ZM9 6H13V7H9.41L9 6.59V6ZM7 7H2L1 8V13L2 14H8L9 13V8L8 7H7ZM8 13H2V8H8V9V13ZM3 9H7V10H3V9ZM3 11H7V12H3V11ZM9 4H13V5H9V4Z\"\n        fill=\"#EE9D28\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/Event.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M7.41379 1.55996L8.31177 1H11.6059L12.4243 2.57465L10.2358 6H12.0176L12.7365 7.69512L5.61967 15L4.01699 13.837L6.11967 10H4.89822L4.00024 8.55996L7.41379 1.55996ZM7.78058 9L4.90078 14.3049L12.0176 7H8.31177L11.6059 2H8.31177L4.89822 9H7.78058Z\"\n        fill=\"#D67E00\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/Field.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M1 6.39443L1.55279 5.5L8.55279 2H9.44721L14.4472 4.5L15 5.39443V9.89443L14.4472 10.7889L7.44721 14.2889H6.55279L1.55279 11.7889L1 10.8944V6.39443ZM6.5 13.1444L2 10.8944V7.17094L6.5 9.21639V13.1444ZM7.5 13.1444L14 9.89443V6.17954L7.5 9.21287V13.1444ZM9 2.89443L2.33728 6.22579L6.99725 8.34396L13.6706 5.22973L9 2.89443Z\"\n        fill=\"#007ACC\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/File.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        d=\"M13.8502 4.44L10.5702 1.14L10.2202 1H2.50024L2.00024 1.5V14.5L2.50024 15H13.5002L14.0002 14.5V4.8L13.8502 4.44ZM13.0002 5H10.0002V2L13.0002 5ZM3.00024 14V2H9.00024V5.5L9.50024 6H13.0002V14H3.00024Z\"\n        fill=\"#424242\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/Folder.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        d=\"M14.5002 3H7.71021L6.86023 2.15002L6.51025 2H1.51025L1.01025 2.5V6.5V13.5L1.51025 14H14.5103L15.0103 13.5V9V3.5L14.5002 3ZM13.9902 11.49V13H1.99023V11.49V7.48999V7H6.48022L6.8302 6.84998L7.69019 5.98999H14.0002V7.48999L13.9902 11.49ZM13.9902 5H7.49023L7.14026 5.15002L6.28027 6.01001H2.00024V3.01001H6.29028L7.14026 3.85999L7.50024 4.01001H14.0002L13.9902 5Z\"\n        fill=\"#424242\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/Interface.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M11.5 5C10.1193 5 9 6.11929 9 7.5C9 8.88071 10.1193 10 11.5 10C12.8807 10 14 8.88071 14 7.5C14 6.11929 12.8807 5 11.5 5ZM8.03544 8C8.27806 9.69615 9.73676 11 11.5 11C13.433 11 15 9.433 15 7.5C15 5.567 13.433 4 11.5 4C9.73676 4 8.27806 5.30385 8.03544 7H4.93699C4.71497 6.13739 3.93192 5.5 3 5.5C1.89543 5.5 1 6.39543 1 7.5C1 8.60457 1.89543 9.5 3 9.5C3.93192 9.5 4.71497 8.86261 4.93699 8H8.03544Z\"\n        fill=\"#007ACC\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/Keyword.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        d=\"M15 4H10V3H15V4ZM14 7H12V8H14V7ZM10 7H1V8H10V7ZM12 13H1V14H12V13ZM7 10H1V11H7V10ZM15 10H10V11H15V10ZM8 2V5H1V2H8ZM7 3H2V4H7V3Z\"\n        fill=\"#424242\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/Method.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M2 4.85749L2.4855 4L7.4855 1H8.5145L13.5145 4L14 4.85749V10.8575L13.5145 11.715L8.5145 14.715H7.4855L2.4855 11.715L2 10.8575V4.85749ZM7.5 13.5575L3 10.8575V5.69975L7.5 8.1543V13.5575ZM8.5 13.5575L13 10.8575V5.69975L8.5 8.1543V13.5575ZM8 1.85749L3.25913 4.70201L8 7.28794L12.7409 4.70201L8 1.85749Z\"\n        fill=\"#652D90\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/Module.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M6 2.98361V2.97184V2H5.91083C5.59743 2 5.29407 2.06161 5.00128 2.18473C4.70818 2.30798 4.44942 2.48474 4.22578 2.71498C4.00311 2.94422 3.83792 3.19498 3.73282 3.46766L3.73233 3.46898C3.63382 3.7352 3.56814 4.01201 3.53533 4.29917L3.53519 4.30053C3.50678 4.5805 3.4987 4.86844 3.51084 5.16428C3.52272 5.45379 3.52866 5.74329 3.52866 6.03279C3.52866 6.23556 3.48974 6.42594 3.412 6.60507L3.4116 6.60601C3.33687 6.78296 3.23423 6.93866 3.10317 7.07359C2.97644 7.20405 2.82466 7.31055 2.64672 7.3925C2.4706 7.46954 2.28497 7.5082 2.08917 7.5082H2V7.6V8.4V8.4918H2.08917C2.28465 8.4918 2.47001 8.53238 2.64601 8.61334L2.64742 8.61396C2.82457 8.69157 2.97577 8.79762 3.10221 8.93161L3.10412 8.93352C3.23428 9.0637 3.33659 9.21871 3.41129 9.39942L3.41201 9.40108C3.48986 9.58047 3.52866 9.76883 3.52866 9.96721C3.52866 10.2567 3.52272 10.5462 3.51084 10.8357C3.4987 11.1316 3.50677 11.4215 3.53516 11.7055L3.53535 11.7072C3.56819 11.9903 3.63387 12.265 3.73232 12.531L3.73283 12.5323C3.83793 12.805 4.00311 13.0558 4.22578 13.285C4.44942 13.5153 4.70818 13.692 5.00128 13.8153C5.29407 13.9384 5.59743 14 5.91083 14H6V13.2V13.0164H5.91083C5.71095 13.0164 5.52346 12.9777 5.34763 12.9008C5.17396 12.8191 5.02194 12.7126 4.89086 12.5818C4.76386 12.4469 4.66104 12.2911 4.58223 12.1137C4.50838 11.9346 4.47134 11.744 4.47134 11.541C4.47134 11.3127 4.4753 11.0885 4.48321 10.8686C4.49125 10.6411 4.49127 10.4195 4.48324 10.2039C4.47914 9.98246 4.46084 9.76883 4.42823 9.56312C4.39513 9.35024 4.33921 9.14757 4.26039 8.95536C4.18091 8.76157 4.07258 8.57746 3.93616 8.40298C3.82345 8.25881 3.68538 8.12462 3.52283 8C3.68538 7.87538 3.82345 7.74119 3.93616 7.59702C4.07258 7.42254 4.18091 7.23843 4.26039 7.04464C4.33913 6.85263 4.39513 6.65175 4.42826 6.44285C4.46082 6.2333 4.47914 6.01973 4.48324 5.80219C4.49127 5.58262 4.49125 5.36105 4.48321 5.13749C4.4753 4.9134 4.47134 4.68725 4.47134 4.45902C4.47134 4.26019 4.50833 4.07152 4.58238 3.89205C4.66135 3.71034 4.76421 3.55475 4.89086 3.42437C5.02193 3.28942 5.17461 3.18275 5.34802 3.10513C5.5238 3.02427 5.71113 2.98361 5.91083 2.98361H6ZM10 13.0164V13.0282V14H10.0892C10.4026 14 10.7059 13.9384 10.9987 13.8153C11.2918 13.692 11.5506 13.5153 11.7742 13.285C11.9969 13.0558 12.1621 12.805 12.2672 12.5323L12.2677 12.531C12.3662 12.2648 12.4319 11.988 12.4647 11.7008L12.4648 11.6995C12.4932 11.4195 12.5013 11.1316 12.4892 10.8357C12.4773 10.5462 12.4713 10.2567 12.4713 9.96721C12.4713 9.76444 12.5103 9.57406 12.588 9.39493L12.5884 9.39399C12.6631 9.21704 12.7658 9.06134 12.8968 8.92642C13.0236 8.79595 13.1753 8.68945 13.3533 8.6075C13.5294 8.53046 13.715 8.4918 13.9108 8.4918H14V8.4V7.6V7.5082H13.9108C13.7153 7.5082 13.53 7.46762 13.354 7.38666L13.3526 7.38604C13.1754 7.30844 13.0242 7.20238 12.8978 7.06839L12.8959 7.06648C12.7657 6.9363 12.6634 6.78129 12.5887 6.60058L12.588 6.59892C12.5101 6.41953 12.4713 6.23117 12.4713 6.03279C12.4713 5.74329 12.4773 5.45379 12.4892 5.16428C12.5013 4.86842 12.4932 4.57848 12.4648 4.29454L12.4646 4.29285C12.4318 4.00971 12.3661 3.73502 12.2677 3.46897L12.2672 3.46766C12.1621 3.19499 11.9969 2.94422 11.7742 2.71498C11.5506 2.48474 11.2918 2.30798 10.9987 2.18473C10.7059 2.06161 10.4026 2 10.0892 2H10V2.8V2.98361H10.0892C10.2891 2.98361 10.4765 3.0223 10.6524 3.09917C10.826 3.18092 10.9781 3.28736 11.1091 3.41823C11.2361 3.55305 11.339 3.70889 11.4178 3.88628C11.4916 4.0654 11.5287 4.25596 11.5287 4.45902C11.5287 4.68727 11.5247 4.91145 11.5168 5.13142C11.5088 5.35894 11.5087 5.58049 11.5168 5.79605C11.5209 6.01754 11.5392 6.23117 11.5718 6.43688C11.6049 6.64976 11.6608 6.85243 11.7396 7.04464C11.8191 7.23843 11.9274 7.42254 12.0638 7.59702C12.1765 7.74119 12.3146 7.87538 12.4772 8C12.3146 8.12462 12.1765 8.25881 12.0638 8.40298C11.9274 8.57746 11.8191 8.76157 11.7396 8.95536C11.6609 9.14737 11.6049 9.34825 11.5717 9.55715C11.5392 9.7667 11.5209 9.98027 11.5168 10.1978C11.5087 10.4174 11.5087 10.6389 11.5168 10.8625C11.5247 11.0866 11.5287 11.3128 11.5287 11.541C11.5287 11.7398 11.4917 11.9285 11.4176 12.1079C11.3386 12.2897 11.2358 12.4452 11.1091 12.5756C10.9781 12.7106 10.8254 12.8173 10.652 12.8949C10.4762 12.9757 10.2889 13.0164 10.0892 13.0164H10Z\"\n        fill=\"#424242\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/Multiple.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M7.35642 2.97558C7.20974 3.08558 7.16573 3.19939 7.16573 3.28298C7.16573 3.36657 7.20974 3.48037 7.35642 3.59038C7.50324 3.7005 7.729 3.7834 7.99962 3.7834C8.27024 3.7834 8.49599 3.7005 8.64282 3.59038C8.78949 3.48037 8.83351 3.36657 8.83351 3.28298C8.83351 3.19939 8.78949 3.08558 8.64282 2.97558C8.49599 2.86546 8.27024 2.78256 7.99962 2.78256C7.729 2.78256 7.50324 2.86546 7.35642 2.97558ZM6.75642 2.17558C7.09236 1.92362 7.53355 1.78256 7.99962 1.78256C8.46569 1.78256 8.90687 1.92362 9.24282 2.17558C9.57892 2.42765 9.83351 2.81406 9.83351 3.28298C9.83351 3.7519 9.57892 4.13831 9.24282 4.39038C8.90687 4.64234 8.46569 4.7834 7.99962 4.7834C7.53355 4.7834 7.09236 4.64234 6.75642 4.39038C6.42032 4.13831 6.16573 3.7519 6.16573 3.28298C6.16573 2.81406 6.42032 2.42765 6.75642 2.17558Z\"\n        fill=\"#0EA4E9\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M7.35642 6.97729C7.20974 7.08729 7.16573 7.20109 7.16573 7.28469C7.16573 7.36828 7.20974 7.48208 7.35642 7.59209C7.50324 7.70221 7.729 7.7851 7.99962 7.7851C8.27024 7.7851 8.49599 7.70221 8.64282 7.59209C8.78949 7.48208 8.83351 7.36828 8.83351 7.28469C8.83351 7.20109 8.78949 7.08729 8.64282 6.97729C8.49599 6.86717 8.27024 6.78427 7.99962 6.78427C7.729 6.78427 7.50324 6.86717 7.35642 6.97729ZM6.75642 6.17729C7.09236 5.92533 7.53355 5.78427 7.99962 5.78427C8.46569 5.78427 8.90687 5.92533 9.24282 6.17729C9.57892 6.42936 9.83351 6.81577 9.83351 7.28469C9.83351 7.75361 9.57892 8.14002 9.24282 8.39209C8.90687 8.64405 8.46569 8.7851 7.99962 8.7851C7.53355 8.7851 7.09236 8.64405 6.75642 8.39209C6.42032 8.14002 6.16573 7.75361 6.16573 7.28469C6.16573 6.81577 6.42032 6.42936 6.75642 6.17729Z\"\n        fill=\"#0EA4E9\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M11.3581 4.64294C11.2114 4.75294 11.1674 4.86674 11.1674 4.95034C11.1674 5.03393 11.2114 5.14773 11.3581 5.25774C11.5049 5.36786 11.7307 5.45075 12.0013 5.45075C12.2719 5.45075 12.4977 5.36786 12.6445 5.25774C12.7912 5.14773 12.8352 5.03393 12.8352 4.95034C12.8352 4.86674 12.7912 4.75294 12.6445 4.64294C12.4977 4.53282 12.2719 4.44992 12.0013 4.44992C11.7307 4.44992 11.5049 4.53282 11.3581 4.64294ZM10.7581 3.84294C11.094 3.59098 11.5352 3.44992 12.0013 3.44992C12.4674 3.44992 12.9085 3.59098 13.2445 3.84294C13.5806 4.09501 13.8352 4.48142 13.8352 4.95034C13.8352 5.41926 13.5806 5.80566 13.2445 6.05774C12.9085 6.3097 12.4674 6.45075 12.0013 6.45075C11.5352 6.45075 11.094 6.3097 10.7581 6.05774C10.422 5.80566 10.1674 5.41926 10.1674 4.95034C10.1674 4.48142 10.422 4.09501 10.7581 3.84294Z\"\n        fill=\"#0EA4E9\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M11.3581 4.64294C11.2114 4.75294 11.1674 4.86674 11.1674 4.95034C11.1674 5.03393 11.2114 5.14773 11.3581 5.25774C11.5049 5.36786 11.7307 5.45075 12.0013 5.45075C12.2719 5.45075 12.4977 5.36786 12.6445 5.25774C12.7912 5.14773 12.8352 5.03393 12.8352 4.95034C12.8352 4.86674 12.7912 4.75294 12.6445 4.64294C12.4977 4.53282 12.2719 4.44992 12.0013 4.44992C11.7307 4.44992 11.5049 4.53282 11.3581 4.64294ZM10.7581 3.84294C11.094 3.59098 11.5352 3.44992 12.0013 3.44992C12.4674 3.44992 12.9085 3.59098 13.2445 3.84294C13.5806 4.09501 13.8352 4.48142 13.8352 4.95034C13.8352 5.41926 13.5806 5.80566 13.2445 6.05774C12.9085 6.3097 12.4674 6.45075 12.0013 6.45075C11.5352 6.45075 11.094 6.3097 10.7581 6.05774C10.422 5.80566 10.1674 5.41926 10.1674 4.95034C10.1674 4.48142 10.422 4.09501 10.7581 3.84294Z\"\n        fill=\"#0EA4E9\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M3.35475 4.64294C3.20808 4.75294 3.16406 4.86674 3.16406 4.95034C3.16406 5.03393 3.20808 5.14773 3.35475 5.25774C3.50158 5.36786 3.72733 5.45075 3.99795 5.45075C4.26857 5.45075 4.49433 5.36786 4.64115 5.25774C4.78783 5.14773 4.83184 5.03393 4.83184 4.95034C4.83184 4.86674 4.78783 4.75294 4.64115 4.64294C4.49433 4.53282 4.26857 4.44992 3.99795 4.44992C3.72733 4.44992 3.50158 4.53282 3.35475 4.64294ZM2.75475 3.84294C3.09069 3.59098 3.53188 3.44992 3.99795 3.44992C4.46402 3.44992 4.90521 3.59098 5.24115 3.84294C5.57725 4.09501 5.83184 4.48142 5.83184 4.95034C5.83184 5.41926 5.57725 5.80566 5.24115 6.05774C4.90521 6.3097 4.46402 6.45075 3.99795 6.45075C3.53188 6.45075 3.09069 6.3097 2.75475 6.05774C2.41865 5.80566 2.16406 5.41926 2.16406 4.95034C2.16406 4.48142 2.41865 4.09501 2.75475 3.84294Z\"\n        fill=\"#0EA4E9\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M3.16406 7.11813V10.9529C3.16406 11.2687 3.34252 11.5575 3.62502 11.6987L7.62669 13.6995C7.86145 13.8169 8.13778 13.8169 8.37255 13.6995L12.3742 11.6987C12.6567 11.5575 12.8352 11.2687 12.8352 10.9529V7.11813H13.8352V10.9529C13.8352 11.6475 13.4427 12.2825 12.8214 12.5931L8.81976 14.594C8.30347 14.8521 7.69577 14.8521 7.17948 14.594L3.17781 12.5931C2.55652 12.2825 2.16406 11.6475 2.16406 10.9529V7.11813H3.16406Z\"\n        fill=\"#0EA4E9\"\n      />\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M8.49962 9.78506V13.7851L7.99962 14.285L7.49962 13.7851V9.78506H8.49962Z\"\n        fill=\"#0EA4E9\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/Operator.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M2.87313 1.10023C3.20793 1.23579 3.4757 1.498 3.61826 1.82988C3.69056 1.99959 3.72699 2.18242 3.72526 2.36688C3.72642 2.54999 3.69 2.7314 3.61826 2.89988C3.51324 3.14567 3.33807 3.35503 3.11466 3.50177C2.89126 3.64851 2.62955 3.72612 2.36226 3.72488C2.17948 3.72592 1.99842 3.68951 1.83026 3.61788C1.58322 3.51406 1.37252 3.33932 1.22478 3.11575C1.07704 2.89219 0.99891 2.62984 1.00026 2.36188C0.999374 2.17921 1.03543 1.99825 1.10626 1.82988C1.24362 1.50314 1.50353 1.24323 1.83026 1.10588C2.16357 0.966692 2.53834 0.964661 2.87313 1.10023ZM2.57526 2.86488C2.70564 2.80913 2.80951 2.70526 2.86526 2.57488C2.89314 2.50838 2.90742 2.43698 2.90726 2.36488C2.90838 2.2654 2.88239 2.1675 2.8321 2.08167C2.7818 1.99584 2.70909 1.92531 2.62176 1.87767C2.53443 1.83002 2.43577 1.80705 2.33638 1.81121C2.23698 1.81537 2.1406 1.8465 2.05755 1.90128C1.97451 1.95606 1.90794 2.03241 1.865 2.12215C1.82205 2.21188 1.80434 2.31161 1.81376 2.41065C1.82319 2.50968 1.85939 2.60428 1.9185 2.6843C1.9776 2.76433 2.05738 2.82675 2.14926 2.86488C2.28574 2.92089 2.43878 2.92089 2.57526 2.86488ZM6.43019 1.1095L1.10992 6.42977L1.79581 7.11567L7.11608 1.7954L6.43019 1.1095ZM11.5002 8.99999H12.5002V11.5H15.0002V12.5H12.5002V15H11.5002V12.5H9.00024V11.5H11.5002V8.99999ZM5.76801 9.52509L6.47512 10.2322L4.70735 12L6.47512 13.7677L5.76801 14.4748L4.00024 12.7071L2.23248 14.4748L1.52537 13.7677L3.29314 12L1.52537 10.2322L2.23248 9.52509L4.00024 11.2929L5.76801 9.52509ZM7.11826 5.32988C7.01466 5.08268 6.83997 4.87183 6.61636 4.72406C6.39275 4.57629 6.13028 4.49826 5.86226 4.49988C5.6796 4.49899 5.49864 4.53505 5.33026 4.60588C5.00353 4.74323 4.74362 5.00314 4.60626 5.32988C4.53612 5.49478 4.49922 5.67191 4.49766 5.8511C4.4961 6.0303 4.52992 6.20804 4.59718 6.37414C4.66443 6.54024 4.76381 6.69143 4.88961 6.81906C5.0154 6.94669 5.16515 7.04823 5.33026 7.11788C5.49892 7.18848 5.67993 7.22484 5.86276 7.22484C6.0456 7.22484 6.22661 7.18848 6.39526 7.11788C6.64225 7.01388 6.85295 6.83913 7.00082 6.61563C7.1487 6.39213 7.22713 6.12987 7.22626 5.86188C7.22679 5.67905 7.19005 5.49803 7.11826 5.32988ZM6.36526 6.07488C6.3379 6.13937 6.29854 6.19808 6.24926 6.24788C6.19932 6.29724 6.14066 6.33691 6.07626 6.36488C6.00878 6.39297 5.93635 6.40725 5.86326 6.40688C5.79015 6.40744 5.71769 6.39315 5.65026 6.36488C5.58565 6.33729 5.52693 6.29757 5.47726 6.24788C5.42715 6.19856 5.38738 6.13975 5.36026 6.07488C5.30425 5.9384 5.30425 5.78536 5.36026 5.64888C5.41561 5.51846 5.51965 5.41477 5.65026 5.35988C5.71761 5.33126 5.79008 5.31663 5.86326 5.31688C5.93642 5.31685 6.00884 5.33147 6.07626 5.35988C6.14062 5.38749 6.19928 5.42682 6.24926 5.47588C6.2981 5.52603 6.33741 5.58465 6.36526 5.64888C6.39364 5.7163 6.40827 5.78872 6.40827 5.86188C6.40827 5.93503 6.39364 6.00745 6.36526 6.07488ZM14.0002 3H10.0002V4H14.0002V3Z\"\n        fill=\"#424242\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/Property.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        d=\"M2.80723 14.9754C2.57119 14.9721 2.33826 14.9211 2.12247 14.8254C1.90667 14.7297 1.71248 14.5913 1.55158 14.4186C1.2385 14.1334 1.04433 13.7408 1.00775 13.3189C0.966225 12.8828 1.09269 12.4473 1.36133 12.1013C2.56779 10.8289 4.9473 8.4494 6.67811 6.75479C6.30983 5.75887 6.32704 4.66127 6.72637 3.67739C7.05474 2.85876 7.63869 2.16805 8.39129 1.70807C8.9817 1.31706 9.66031 1.07944 10.3657 1.01673C11.0711 0.954022 11.7809 1.06819 12.4311 1.34892L13.0482 1.6162L10.1824 4.56738L11.4371 5.82582L14.3809 2.94887L14.6482 3.56788C14.8735 4.08976 14.993 4.65119 14.9997 5.21961C15.0064 5.78802 14.9002 6.35211 14.6872 6.87915C14.476 7.40029 14.1623 7.87368 13.7647 8.27122C13.5394 8.49169 13.2904 8.68653 13.0222 8.85218C12.4673 9.22275 11.8324 9.45636 11.1697 9.5338C10.5069 9.61124 9.83521 9.5303 9.20982 9.29764C8.11194 10.4113 5.37142 13.1704 3.89119 14.5522C3.59426 14.8219 3.20832 14.9726 2.80723 14.9754ZM10.7448 1.92802C10.087 1.92637 9.44359 2.12018 8.89614 2.48485C8.68265 2.6152 8.48437 2.76897 8.30498 2.9433C7.82789 3.42423 7.50926 4.03953 7.39182 4.70669C7.27437 5.37385 7.36374 6.06098 7.64792 6.67591L7.78342 6.97288L7.55048 7.20025C5.81224 8.89672 3.28146 11.4201 2.06479 12.7045C1.95646 12.8658 1.91012 13.0608 1.93435 13.2535C1.95857 13.4463 2.05171 13.6238 2.19657 13.7532C2.28005 13.8462 2.38177 13.9211 2.49541 13.9731C2.59557 14.0184 2.70383 14.043 2.81373 14.0455C2.98064 14.0413 3.14044 13.977 3.26383 13.8646C4.83687 12.3964 7.87622 9.32641 8.76807 8.42435L8.9973 8.19326L9.29242 8.32783C9.80618 8.56732 10.3731 8.66985 10.9382 8.62545C11.5033 8.58106 12.0473 8.39125 12.5174 8.07447C12.7313 7.9426 12.9296 7.78694 13.1085 7.61045C13.4183 7.30153 13.6631 6.93374 13.8286 6.52874C13.994 6.12375 14.0767 5.68974 14.0719 5.25228C14.0719 5.03662 14.0505 4.82148 14.0078 4.61007L11.4306 7.12508L8.87944 4.57759L11.3944 1.98834C11.1804 1.94674 10.9628 1.92653 10.7448 1.92802Z\"\n        fill=\"#424242\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/Reference.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M8.06065 3.85356L5.91421 6L5.2071 5.29289L6.49999 4H3.5C3.10218 4 2.72064 4.15804 2.43934 4.43934C2.15804 4.72065 2 5.10218 2 5.5C2 5.89783 2.15804 6.27936 2.43934 6.56066C2.72064 6.84197 3.10218 7 3.5 7H4V8H3.5C2.83696 8 2.20107 7.73661 1.73223 7.26777C1.26339 6.79893 1 6.16305 1 5.5C1 4.83696 1.26339 4.20108 1.73223 3.73224C2.20107 3.2634 2.83696 3 3.5 3H6.49999L6.49999 3H6.49996L6 2.50004V2.50001L5.2071 1.70711L5.91421 1L8.06065 3.14645L8.06065 3.85356ZM5 6.50003L5.91421 7.41424L6 7.32845V14H14V7H10V3H9.06065V2.73227L8.32838 2H11.2L11.5 2.1L14.9 5.6L15 6V14.5L14.5 15H5.5L5 14.5V9.00003V6.50003ZM11 3V6H13.9032L11 3Z\"\n        fill=\"#424242\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/Snippet.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M2.5 1L2 1.5V13H3V2H14V13H15V1.5L14.5 1H2.5ZM2 15V14H3V15H2ZM5 14.0001H4V15.0001H5V14.0001ZM6 14.0001H7V15.0001H6V14.0001ZM9 14.0001H8V15.0001H9V14.0001ZM10 14.0001H11V15.0001H10V14.0001ZM15 15.0001V14.0001H14V15.0001H15ZM12 14.0001H13V15.0001H12V14.0001Z\"\n        fill=\"#424242\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/Struct.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M2.00024 2L1.00024 3V6L2.00024 7H14.0002L15.0002 6V3L14.0002 2H2.00024ZM2.00024 3H3.00024H13.0002H14.0002V4V5V6H13.0002H3.00024H2.00024V5V4V3ZM1.00024 10L2.00024 9H5.00024L6.00024 10V13L5.00024 14H2.00024L1.00024 13V10ZM3.00024 10H2.00024V11V12V13H3.00024H4.00024H5.00024V12V11V10H4.00024H3.00024ZM10.0002 10L11.0002 9H14.0002L15.0002 10V13L14.0002 14H11.0002L10.0002 13V10ZM12.0002 10H11.0002V11V12V13H12.0002H13.0002H14.0002V12V11V10H13.0002H12.0002Z\"\n        fill=\"#424242\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/Symbol.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M11.0003 6H10.0003V5.5C10.0003 5.22386 9.7764 5 9.50026 5H8.47926V10.5C8.47926 10.7761 8.70312 11 8.97926 11H9.47926V12H6.47926V11H6.97926C7.2554 11 7.47926 10.7761 7.47926 10.5V5H6.50026C6.22412 5 6.00026 5.22386 6.00026 5.5V6H5.00026V4H11.0003V6ZM13.9145 8.0481L12.4522 6.58581L13.1593 5.87871L14.9751 7.69454V8.40165L13.2074 10.1694L12.5003 9.46231L13.9145 8.0481ZM3.54835 9.4623L2.08605 8.00002L3.50026 6.58581L2.79316 5.8787L1.02539 7.64647V8.35357L2.84124 10.1694L3.54835 9.4623Z\"\n        fill=\"#424242\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/Text.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M7.22289 10.933C7.54863 11.1254 7.92163 11.2231 8.29989 11.215C8.63777 11.2218 8.97254 11.1492 9.27721 11.003C9.58188 10.8567 9.84792 10.6409 10.0539 10.373C10.5091 9.76519 10.7402 9.01867 10.7079 8.25998C10.7412 7.58622 10.5374 6.9221 10.1319 6.38298C9.93575 6.14161 9.68577 5.94957 9.402 5.82228C9.11824 5.69498 8.80858 5.63597 8.49789 5.64997C8.07522 5.64699 7.65994 5.76085 7.29789 5.97898C7.18304 6.04807 7.0749 6.12775 6.97489 6.21698V3.47498H5.98389V11.1H6.97889V10.756C7.05516 10.8217 7.13677 10.8809 7.22289 10.933ZM7.84981 6.70006C8.03598 6.62105 8.23807 6.58677 8.43989 6.59998C8.61257 6.59452 8.78404 6.63054 8.93994 6.70501C9.09583 6.77948 9.23161 6.89023 9.33589 7.02798C9.59253 7.39053 9.7184 7.82951 9.69289 8.27297C9.71972 8.79748 9.57969 9.31701 9.29289 9.75698C9.18822 9.91527 9.04546 10.0447 8.87773 10.1335C8.70999 10.2223 8.52264 10.2675 8.33289 10.265C8.14934 10.2732 7.9663 10.24 7.79734 10.1678C7.62838 10.0956 7.47784 9.98628 7.35689 9.84797C7.10152 9.55957 6.96501 9.18506 6.97489 8.79998V8.19998C6.96299 7.78332 7.10263 7.3765 7.36789 7.05498C7.49858 6.90064 7.66364 6.77908 7.84981 6.70006ZM3.28902 5.67499C2.97011 5.67933 2.65388 5.734 2.35202 5.83699C2.06417 5.92293 1.79347 6.05828 1.55202 6.23699L1.45202 6.31399V7.51399L1.87502 7.15499C2.24579 6.80478 2.73133 6.60146 3.24102 6.58299C3.36593 6.57164 3.4917 6.59147 3.60706 6.64068C3.72243 6.6899 3.82377 6.76697 3.90202 6.86499C4.0522 7.0971 4.13239 7.36754 4.13302 7.64399L2.90002 7.82499C2.39435 7.87781 1.91525 8.07772 1.52202 8.39999C1.36697 8.55181 1.24339 8.73271 1.15835 8.93235C1.07331 9.13199 1.02848 9.34644 1.02644 9.56343C1.0244 9.78042 1.06517 9.99568 1.14644 10.1969C1.2277 10.3981 1.34786 10.5813 1.50002 10.736C1.6687 10.8904 1.86622 11.01 2.08125 11.0879C2.29627 11.1659 2.52456 11.2005 2.75302 11.19C3.147 11.1931 3.53278 11.0774 3.86002 10.858C3.96153 10.7897 4.0572 10.7131 4.14602 10.629V11.073H5.08702V7.71499C5.12137 7.17422 4.9543 6.63988 4.61802 6.21499C4.44979 6.03285 4.24348 5.89003 4.01378 5.7967C3.78407 5.70336 3.53661 5.66181 3.28902 5.67499ZM4.14602 8.71599C4.16564 9.13435 4.02592 9.54459 3.75502 9.864C3.63689 10.0005 3.48998 10.1092 3.32486 10.1821C3.15973 10.2551 2.98049 10.2906 2.80002 10.286C2.69049 10.2945 2.58035 10.2812 2.47599 10.2469C2.37163 10.2125 2.27511 10.1579 2.19202 10.086C2.06079 9.93455 1.98856 9.74088 1.98856 9.54049C1.98856 9.34011 2.06079 9.14644 2.19202 8.99499C2.47322 8.82131 2.79233 8.71837 3.12202 8.69499L4.14202 8.54699L4.14602 8.71599ZM12.4588 11.0325C12.766 11.1638 13.0983 11.2261 13.4322 11.215C13.927 11.227 14.4153 11.1006 14.8422 10.85L14.9652 10.775L14.9782 10.768V9.61504L14.5322 9.93504C14.216 10.1592 13.8356 10.2747 13.4482 10.264C13.2497 10.2719 13.052 10.2342 12.8703 10.1538C12.6886 10.0733 12.5278 9.95232 12.4002 9.80004C12.1144 9.42453 11.9725 8.95911 12.0002 8.48804C11.9737 7.98732 12.1352 7.49475 12.4532 7.10704C12.5934 6.94105 12.7695 6.80914 12.9682 6.7213C13.167 6.63346 13.3831 6.592 13.6002 6.60004C13.9439 6.59844 14.2808 6.69525 14.5712 6.87904L15.0002 7.14404V5.97004L14.8312 5.89704C14.4626 5.73432 14.0641 5.6502 13.6612 5.65004C13.2999 5.63991 12.9406 5.70762 12.6078 5.84859C12.2749 5.98956 11.9763 6.20048 11.7322 6.46704C11.2261 7.02683 10.9581 7.76186 10.9852 8.51604C10.9567 9.22346 11.1955 9.91569 11.6542 10.455C11.8769 10.704 12.1516 10.9012 12.4588 11.0325Z\"\n        fill=\"#424242\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/TypeParameter.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M11.0003 6H10.0003V5.5C10.0003 5.22386 9.7764 5 9.50026 5H8.47926V10.5C8.47926 10.7761 8.70312 11 8.97926 11H9.47926V12H6.47926V11H6.97926C7.2554 11 7.47926 10.7761 7.47926 10.5V5H6.50026C6.22412 5 6.00026 5.22386 6.00026 5.5V6H5.00026V4H11.0003V6ZM13.9145 8.0481L12.4522 6.58581L13.1593 5.87871L14.9751 7.69454V8.40165L13.2074 10.1694L12.5003 9.46231L13.9145 8.0481ZM3.54835 9.4623L2.08605 8.00002L3.50026 6.58581L2.79316 5.8787L1.02539 7.64647V8.35357L2.84124 10.1694L3.54835 9.4623Z\"\n        fill=\"#424242\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/Unit.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M4 1L3 2V14L4 15H12L13 14V2L12 1H4ZM4 3V2H12V14H4V13H6V12H4V10H8V9H4V7H6V6H4V4H8V3H4Z\"\n        fill=\"#424242\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/Value.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M14 2H8L7 3V6H8V3H14V8H10V9H14L15 8V3L14 2ZM9 6H13V7H9.41L9 6.59V6ZM7 7H2L1 8V13L2 14H8L9 13V8L8 7H7ZM8 13H2V8H8V9V13ZM3 9H7V10H3V9ZM3 11H7V12H3V11ZM9 4H13V5H9V4Z\"\n        fill=\"#EE9D28\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/CodeSymbols/Variable.tsx",
    "content": "const Icon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M2 5H4V4H1.5L1 4.5V12.5L1.5 13H4V12H2V5ZM14.5 4H12V5H14V12H12V13H14.5L15 12.5V4.5L14.5 4ZM11.76 6.56995L12 7V9.51001L11.7 9.95996L7.19995 11.96H6.73999L4.23999 10.46L4 10.03V7.53003L4.30005 7.06995L8.80005 5.06995H9.26001L11.76 6.56995ZM5 9.70996L6.5 10.61V9.28003L5 8.38V9.70996ZM5.57996 7.56006L7.03003 8.43005L10.42 6.93005L8.96997 6.06006L5.57996 7.56006ZM7.53003 10.73L11.03 9.17004V7.77002L7.53003 9.31995V10.73Z\"\n        fill=\"#007ACC\"\n      />\n    </svg>\n  );\n};\n\nexport default Icon;\n"
  },
  {
    "path": "client/src/icons/Cog.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M2 5.77975C2 5.05719 2.38973 4.39082 3.01949 4.03659L7.01949 1.78663C7.62831 1.44417 8.37169 1.44417 8.98051 1.78663L12.9805 4.0366C13.6103 4.39084 14 5.05721 14 5.77976L14 10.2202C14 10.9427 13.6103 11.6091 12.9805 11.9633L8.98048 14.2133C8.37168 14.5557 7.62832 14.5557 7.01951 14.2133L3.01954 11.9635C2.38975 11.6093 2 10.9429 2 10.2203V5.77975ZM5.66669 7.99984C5.66669 6.71117 6.71136 5.6665 8.00002 5.6665C9.28869 5.6665 10.3334 6.71117 10.3334 7.99984C10.3334 9.2885 9.28869 10.3332 8.00002 10.3332C6.71136 10.3332 5.66669 9.2885 5.66669 7.99984Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/ColorSwitch.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M2.9165 1.16675C1.95001 1.16675 1.1665 1.95025 1.1665 2.91675V9.62508C1.1665 11.397 2.60292 12.8334 4.37484 12.8334C4.97916 12.8334 5.54447 12.6663 6.02707 12.3758L11.7886 9.04942C12.6256 8.56617 12.9124 7.49588 12.4291 6.65887L10.9708 4.13296C10.8349 3.89758 10.6517 3.70493 10.44 3.56103C10.2961 3.3489 10.1031 3.16527 9.86733 3.02912L7.34142 1.57078C7.10564 1.43465 6.8502 1.35941 6.59454 1.34076C6.36392 1.22922 6.10524 1.16675 5.83317 1.16675H2.9165ZM11.2053 8.03905L7.89072 9.9527L10.4372 5.54207L11.4188 7.2422C11.5799 7.52121 11.4843 7.87797 11.2053 8.03905ZM9.49751 4.83633L7.58317 8.15205V3.05751L9.28399 4.03948C9.38099 4.09548 9.45468 4.17382 9.50398 4.26416C9.59906 4.43838 9.60271 4.65411 9.49751 4.83633ZM4.37484 10.6459C4.93863 10.6459 5.39567 10.1889 5.39567 9.62508C5.39567 9.06129 4.93863 8.60425 4.37484 8.60425C3.81105 8.60425 3.354 9.06129 3.354 9.62508C3.354 10.1889 3.81105 10.6459 4.37484 10.6459Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Conversation.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <g clipPath=\"url(#clip0_8389_231408)\">\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M3.60434 14.1667C3.23638 14.1667 3.02305 13.9188 3.02305 13.5377V12.0188H2.67411C0.955865 12.0188 0 11.1048 0 9.51301V4.99878C0 3.4078 0.955865 2.5 2.67411 2.5H10.6584C12.3766 2.5 13.3333 3.41323 13.3333 4.99878V9.51301C13.3333 11.0986 12.3766 12.0188 10.6584 12.0188H6.48517L4.35349 13.7794C4.04341 14.0334 3.86233 14.1667 3.60434 14.1667Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M13.3333 5.83333H9.3416C7.62336 5.83333 6.66667 6.74656 6.66667 8.33212V12.0188L10.6584 12.0188C12.3766 12.0188 13.3333 11.0986 13.3333 9.51301V5.83333Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M15 5.83333V9.51301C15 10.6451 14.6496 11.7561 13.7839 12.5728C12.932 13.3764 11.8029 13.6854 10.6584 13.6854H7.08445L6.83675 13.89C7.18229 14.8283 8.04518 15.3521 9.3416 15.3521H13.5148L15.6465 17.1127C15.9566 17.3668 16.1377 17.5 16.3957 17.5C16.7636 17.5 16.977 17.2521 16.977 16.871V15.3521H17.3259C19.0441 15.3521 20 14.4381 20 12.8463V8.33212C20 6.74114 19.0441 5.83333 17.3259 5.83333H15Z\"\n        fill=\"currentColor\"\n      />\n    </g>\n    <defs>\n      <clipPath id=\"clip0_8389_231408\">\n        <rect width=\"20\" height=\"20\" fill=\"white\" />\n      </clipPath>\n    </defs>\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/CopyText.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M4.08325 1.16667C3.11675 1.16667 2.33325 1.95017 2.33325 2.91667V8.75001C2.33325 9.7165 3.11675 10.5 4.08325 10.5H4.66659V11.0833C4.66659 12.0498 5.45009 12.8333 6.41659 12.8333H9.91659C10.8831 12.8333 11.6666 12.0498 11.6666 11.0833V5.25001C11.6666 4.28351 10.8831 3.50001 9.91659 3.50001H9.33325V2.91667C9.33325 1.95017 8.54975 1.16667 7.58325 1.16667H4.08325ZM6.41659 3.50001H8.16659V2.91667C8.16659 2.59451 7.90542 2.33334 7.58325 2.33334H4.08325C3.76109 2.33334 3.49992 2.59451 3.49992 2.91667V8.75001C3.49992 9.07217 3.76109 9.33334 4.08325 9.33334H4.66659V5.25001C4.66659 4.28351 5.45009 3.50001 6.41659 3.50001Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/DateTimeCalendar.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M5.25006 8.16611C5.25006 5.91095 7.07823 4.08278 9.3334 4.08278C11.5886 4.08278 13.4167 5.91095 13.4167 8.16611C13.4167 10.4213 11.5886 12.2494 9.3334 12.2494C7.07823 12.2494 5.25006 10.4213 5.25006 8.16611ZM9.3334 6.41611C9.65556 6.41611 9.91673 6.67728 9.91673 6.99944V7.92449L10.7181 8.72585C10.9459 8.95366 10.9459 9.32301 10.7181 9.55081C10.4903 9.77862 10.1209 9.77862 9.89314 9.55081L8.92092 8.57859C8.81152 8.46919 8.75006 8.32082 8.75006 8.16611V6.99944C8.75006 6.67728 9.01123 6.41611 9.3334 6.41611Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M0.829125 4.87794C0.661294 3.92612 1.29684 3.01847 2.24865 2.85064L7.4189 1.93899C8.21397 1.7988 8.97822 2.21917 9.30619 2.91619C7.76586 2.92432 6.3825 3.59579 5.42723 4.6595L0.93042 5.45241L0.829125 4.87794Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M4.5514 5.9986L1.13301 6.60135L1.63948 9.47371C1.80731 10.4255 2.71497 11.0611 3.66678 10.8932L4.73777 10.7044C4.32147 9.9521 4.08449 9.08678 4.08449 8.16611C4.08449 7.39323 4.2515 6.65935 4.5514 5.9986Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Def.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M5 1C2.79086 1 1 2.79086 1 5V15C1 17.2091 2.79086 19 5 19H15C17.2091 19 19 17.2091 19 15V5C19 2.79086 17.2091 1 15 1H5ZM6.33237 5.60139C6.26839 5.53647 6.18162 5.5 6.09114 5.5C6.04634 5.5 6.00198 5.50895 5.96059 5.52635C5.9192 5.54375 5.8816 5.56924 5.84992 5.60139C5.81824 5.63353 5.79311 5.67169 5.77597 5.71369C5.75882 5.75568 5.75 5.8007 5.75 5.84615V14.1538C5.75 14.2457 5.78594 14.3337 5.84992 14.3986C5.91389 14.4635 6.00067 14.5 6.09114 14.5C6.18162 14.5 6.26839 14.4635 6.33237 14.3986C6.39634 14.3337 6.43228 14.2457 6.43228 14.1538V11.6546L7.43388 11.4008C8.30287 11.1804 9.22087 11.2827 10.022 11.6892L10.0711 11.7142C10.9992 12.185 12.0605 12.3107 13.0705 12.0695L14.4869 11.7312C14.568 11.7119 14.6393 11.6631 14.6874 11.5941C14.7355 11.525 14.7571 11.4405 14.748 11.3565C14.5772 9.77092 14.5764 8.17125 14.7457 6.58554C14.7515 6.53056 14.7442 6.47497 14.7245 6.42342C14.7048 6.37187 14.6732 6.32587 14.6323 6.28927C14.5915 6.25266 14.5426 6.22651 14.4897 6.21302C14.4369 6.19952 14.3816 6.19907 14.3286 6.21169L12.9144 6.54954C12.0598 6.75361 11.1616 6.64713 10.3763 6.24862L10.3272 6.22369C9.38033 5.74334 8.29533 5.62252 7.26831 5.88308L6.43228 6.09538V5.84615C6.43228 5.75435 6.39634 5.6663 6.33237 5.60139Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Documents.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M6.66675 1.3335C5.56218 1.3335 4.66675 2.22893 4.66675 3.3335C3.56218 3.3335 2.66675 4.22893 2.66675 5.3335V12.6668C2.66675 13.7714 3.56218 14.6668 4.66675 14.6668H10.0001C11.1046 14.6668 12.0001 13.7714 12.0001 12.6668C13.1046 12.6668 14.0001 11.7714 14.0001 10.6668V6.00016H11.3334C10.2288 6.00016 9.33341 5.10473 9.33341 4.00016V1.3335H6.66675ZM10.6667 12.6668H6.66675C5.56218 12.6668 4.66675 11.7714 4.66675 10.6668V4.66683C4.29856 4.66683 4.00008 4.96531 4.00008 5.3335V12.6668C4.00008 13.035 4.29856 13.3335 4.66675 13.3335H10.0001C10.3683 13.3335 10.6667 13.035 10.6667 12.6668Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M10.6667 1.72402L13.6096 4.66683H11.3334C10.9652 4.66683 10.6667 4.36835 10.6667 4.00016V1.72402Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/DoorOut.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M4 3.33333C3.63181 3.33333 3.33333 3.63181 3.33333 4L3.33333 12C3.33333 12.3682 3.63181 12.6667 4 12.6667H7.5C7.86819 12.6667 8.16667 12.9651 8.16667 13.3333C8.16667 13.7015 7.86819 14 7.5 14H4C2.89543 14 2 13.1046 2 12L2 4C2 2.89543 2.89543 2 4 2L7.5 2C7.86819 2 8.16667 2.29848 8.16667 2.66667C8.16667 3.03486 7.86819 3.33333 7.5 3.33333L4 3.33333ZM9.86193 4.5286C10.1223 4.26825 10.5444 4.26825 10.8047 4.5286L13.8047 7.52859C14.0651 7.78894 14.0651 8.21105 13.8047 8.4714L10.8047 11.4714C10.5444 11.7318 10.1223 11.7318 9.86193 11.4714C9.60158 11.2111 9.60158 10.7889 9.86193 10.5286L11.7239 8.66666H5.83333C5.46514 8.66666 5.16667 8.36818 5.16667 7.99999C5.16667 7.6318 5.46514 7.33333 5.83333 7.33333L11.7239 7.33333L9.86193 5.47141C9.60158 5.21106 9.60158 4.78895 9.86193 4.5286Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/DoubleChevronIn.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M4.3573 11.3927C4.52815 11.5635 4.80516 11.5635 4.97602 11.3927L6.99999 9.36872L9.02396 11.3927C9.19482 11.5635 9.47183 11.5635 9.64268 11.3927C9.81354 11.2218 9.81354 10.9448 9.64268 10.774L7.30935 8.44064C7.13849 8.26979 6.86149 8.26979 6.69063 8.44064L4.3573 10.774C4.18644 10.9448 4.18644 11.2218 4.3573 11.3927Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M6.69063 5.55936C6.86149 5.73022 7.13849 5.73022 7.30935 5.55936L9.64268 3.22603C9.81354 3.05517 9.81354 2.77816 9.64268 2.60731C9.47183 2.43645 9.19482 2.43645 9.02396 2.60731L6.99999 4.63128L4.97602 2.60731C4.80516 2.43645 4.52815 2.43645 4.3573 2.60731C4.18644 2.77816 4.18644 3.05517 4.3573 3.22603L6.69063 5.55936Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/DoubleChevronOut.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M6.69063 2.60733C6.86149 2.43647 7.13849 2.43647 7.30935 2.60733L9.64268 4.94066C9.81354 5.11152 9.81354 5.38853 9.64268 5.55938C9.47183 5.73023 9.19482 5.73023 9.02396 5.55938L6.99999 3.53541L4.97602 5.55938C4.80516 5.73023 4.52815 5.73023 4.3573 5.55938C4.18644 5.38853 4.18644 5.11152 4.3573 4.94066L6.69063 2.60733ZM4.3573 8.44066C4.52815 8.26981 4.80516 8.26981 4.97602 8.44066L6.99999 10.4646L9.02396 8.44066C9.19482 8.26981 9.47183 8.26981 9.64268 8.44066C9.81354 8.61152 9.81354 8.88853 9.64268 9.05938L7.30935 11.3927C7.13849 11.5636 6.86149 11.5636 6.69063 11.3927L4.3573 9.05938C4.18644 8.88853 4.18644 8.61152 4.3573 8.44066Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/EyeCut.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M3.08766 1.91083C2.76223 1.58539 2.23459 1.58539 1.90915 1.91083C1.58371 2.23626 1.58371 2.7639 1.90915 3.08934L4.25527 5.43545C3.70594 5.87575 3.18352 6.38335 2.69477 6.95677C2.31033 7.4078 1.94679 7.89946 1.60727 8.43111C0.995944 9.3884 0.996612 10.613 1.60769 11.5698C4.12502 15.5113 7.96936 17.2394 11.6778 16.5006C12.5901 16.3188 13.4832 15.9888 14.3341 15.5143L16.9092 18.0893C17.2346 18.4148 17.7622 18.4148 18.0877 18.0893C18.4131 17.7639 18.4131 17.2363 18.0877 16.9108L15.742 14.5651C16.7216 13.7799 17.6177 12.7794 18.3893 11.5719C19.0007 10.6149 19.0007 9.38522 18.3893 8.42821C16.2557 5.08901 13.1697 3.33339 9.99992 3.33341C8.50536 3.33343 7.02942 3.72374 5.66316 4.48632L3.08766 1.91083ZM8.61136 7.43452L12.5655 11.3886C12.7894 10.9758 12.9166 10.5028 12.9166 10.0001C12.9166 8.38925 11.6108 7.08341 9.99993 7.08341C9.49723 7.08341 9.02423 7.21059 8.61136 7.43452ZM4.48224 7.47209C4.79264 7.15812 5.11347 6.87508 5.44225 6.62243L13.0985 14.2786C12.6647 14.4872 12.223 14.6498 11.7776 14.7675L4.48224 7.47209ZM3.40401 8.75087L9.64326 14.9901C7.2321 14.8645 4.79884 13.4699 3.01231 10.6727C2.75031 10.2625 2.75026 9.73792 3.01194 9.32815C3.13941 9.12856 3.27019 8.93615 3.40401 8.75087Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/File.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 18 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M3 4.5C3 2.84315 4.34315 1.5 6 1.5H9.12868C9.72542 1.5 10.2977 1.73705 10.7197 2.15901L10.4687 2.40996L10.7197 2.15901L14.341 5.78033C14.7629 6.20229 15 6.77458 15 7.37132V13.5C15 15.1569 13.6569 16.5 12 16.5H6C4.34315 16.5 3 15.1569 3 13.5V4.5ZM9 3H6C5.17157 3 4.5 3.67157 4.5 4.5V13.5C4.5 14.3284 5.17157 15 6 15H12C12.8284 15 13.5 14.3284 13.5 13.5V7.5H11.25C10.0074 7.5 9 6.49264 9 5.25V3ZM12.4393 6H11.25C10.8358 6 10.5 5.66421 10.5 5.25V4.06066L12.4393 6Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/FileWithSparks.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M2.66675 4.00016C2.66675 2.5274 3.86066 1.3335 5.33341 1.3335H10.6667C12.1395 1.3335 13.3334 2.5274 13.3334 4.00016V8.00016C13.3334 8.36835 13.0349 8.66683 12.6667 8.66683C12.2986 8.66683 12.0001 8.36835 12.0001 8.00016V4.00016C12.0001 3.26378 11.4031 2.66683 10.6667 2.66683H5.33341C4.59704 2.66683 4.00008 3.26378 4.00008 4.00016V12.0002C4.00008 12.7365 4.59703 13.3335 5.33341 13.3335H7.33341C7.7016 13.3335 8.00008 13.632 8.00008 14.0002C8.00008 14.3684 7.7016 14.6668 7.33341 14.6668H5.33341C3.86066 14.6668 2.66675 13.4729 2.66675 12.0002V4.00016Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M10.762 9.0954C10.762 8.85871 10.5701 8.66683 10.3334 8.66683C10.0967 8.66683 9.90484 8.85871 9.90484 9.0954C9.90484 9.93594 9.71866 10.4433 9.4143 10.7477C9.10993 11.0521 8.60253 11.2383 7.76199 11.2383C7.52529 11.2383 7.33341 11.4301 7.33341 11.6668C7.33341 11.9035 7.52529 12.0954 7.76199 12.0954C8.60253 12.0954 9.10993 12.2816 9.4143 12.5859C9.71866 12.8903 9.90484 13.3977 9.90484 14.2383C9.90484 14.475 10.0967 14.6668 10.3334 14.6668C10.5701 14.6668 10.762 14.475 10.762 14.2383C10.762 13.3977 10.9482 12.8903 11.2525 12.5859C11.5569 12.2816 12.0643 12.0954 12.9048 12.0954C13.1415 12.0954 13.3334 11.9035 13.3334 11.6668C13.3334 11.4301 13.1415 11.2383 12.9048 11.2383C12.0643 11.2383 11.5569 11.0521 11.2525 10.7477C10.9482 10.4433 10.762 9.93594 10.762 9.0954Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Filter.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M15.839 5.98444C16.6834 5.01364 15.9939 3.5 14.7072 3.5H5.29279C4.00613 3.5 3.31661 5.01364 4.16103 5.98444L7.90902 10.2933C8.22563 10.6573 8.4 11.1235 8.4 11.6059V15.3187C8.4 15.7015 8.61853 16.0507 8.96281 16.218L10.1628 16.8014C10.827 17.1242 11.6 16.6405 11.6 15.902V11.6059C11.6 11.1235 11.7744 10.6573 12.091 10.2933L15.839 5.98444Z\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Folder.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M2.91675 1.75C1.95025 1.75 1.16675 2.5335 1.16675 3.5V9.91667C1.16675 10.8832 1.95025 11.6667 2.91675 11.6667H11.0834C12.0499 11.6667 12.8334 10.8832 12.8334 9.91667V5.25C12.8334 4.2835 12.0499 3.5 11.0834 3.5L7.31227 3.5L6.66512 2.52927C6.34056 2.04243 5.79415 1.75 5.20903 1.75H2.91675Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/GitHubIcon.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst Icon = (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <g clipPath=\"url(#clip0_122_8976)\">\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M10 0C4.475 0 0 4.475 0 10C0 14.425 2.8625 18.1625 6.8375 19.4875C7.3375 19.575 7.525 19.275 7.525 19.0125C7.525 18.775 7.5125 17.9875 7.5125 17.15C5 17.6125 4.35 16.5375 4.15 15.975C4.0375 15.6875 3.55 14.8 3.125 14.5625C2.775 14.375 2.275 13.9125 3.1125 13.9C3.9 13.8875 4.4625 14.625 4.65 14.925C5.55 16.4375 6.9875 16.0125 7.5625 15.75C7.65 15.1 7.9125 14.6625 8.2 14.4125C5.975 14.1625 3.65 13.3 3.65 9.475C3.65 8.3875 4.0375 7.4875 4.675 6.7875C4.575 6.5375 4.225 5.5125 4.775 4.1375C4.775 4.1375 5.6125 3.875 7.525 5.1625C8.325 4.9375 9.175 4.825 10.025 4.825C10.875 4.825 11.725 4.9375 12.525 5.1625C14.4375 3.8625 15.275 4.1375 15.275 4.1375C15.825 5.5125 15.475 6.5375 15.375 6.7875C16.0125 7.4875 16.4 8.375 16.4 9.475C16.4 13.3125 14.0625 14.1625 11.8375 14.4125C12.2 14.725 12.5125 15.325 12.5125 16.2625C12.5125 17.6 12.5 18.675 12.5 19.0125C12.5 19.275 12.6875 19.5875 13.1875 19.4875C17.1375 18.1625 20 14.4125 20 10C20 4.475 15.525 0 10 0Z\"\n        fill=\"currentColor\"\n      />\n    </g>\n    <defs>\n      <clipPath id=\"clip0_122_8976\">\n        <rect width=\"20\" height=\"20\" fill=\"currentColor\" />\n      </clipPath>\n    </defs>\n  </svg>\n);\n\nexport default IconWrapper(Icon);\n"
  },
  {
    "path": "client/src/icons/Globe.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M10 17.25C14.0041 17.25 17.25 14.0041 17.25 10C17.25 5.99594 14.0041 2.75 10 2.75C5.99594 2.75 2.75 5.99594 2.75 10C2.75 14.0041 5.99594 17.25 10 17.25Z\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n    <path\n      d=\"M13.25 10C13.25 14.5 11.2426 17.25 10 17.25C8.7574 17.25 6.75 14.5 6.75 10C6.75 5.5 8.7574 2.75 10 2.75C11.2426 2.75 13.25 5.5 13.25 10Z\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n    <path\n      d=\"M3 10H10H17\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/HardDrive.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M15.0001 14.5834C15.0001 15.0436 14.627 15.4167 14.1667 15.4167C13.7065 15.4167 13.3334 15.0436 13.3334 14.5834C13.3334 14.1231 13.7065 13.75 14.1667 13.75C14.627 13.75 15.0001 14.1231 15.0001 14.5834Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M15.0001 14.5834C15.0001 15.0436 14.627 15.4167 14.1667 15.4167C13.7065 15.4167 13.3334 15.0436 13.3334 14.5834C13.3334 14.1231 13.7065 13.75 14.1667 13.75C14.627 13.75 15.0001 14.1231 15.0001 14.5834Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M4.96075 17.9167C3.57721 17.9167 2.38901 17.0869 1.87411 15.9015C1.51474 15.0742 1.6939 14.1388 1.87787 13.2566L3.40041 4.17011C3.60223 2.96563 4.64476 2.08325 5.86603 2.08325H14.1341C15.3554 2.08325 16.3979 2.96563 16.5998 4.17011L18.1223 13.2566C18.3063 14.1388 18.4854 15.0742 18.1261 15.9015C17.6112 17.0869 16.423 17.9167 15.0394 17.9167H4.96075ZM15.0394 12.9167H4.96075C4.03304 12.9167 3.28098 13.6629 3.28098 14.5834C3.28098 15.5038 4.03304 16.25 4.96075 16.25H15.0394C15.9671 16.25 16.7192 15.5038 16.7192 14.5834C16.7192 13.6629 15.9671 12.9167 15.0394 12.9167Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/InfoBadge.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M7.00008 1.16602C3.77842 1.16602 1.16675 3.77769 1.16675 6.99935C1.16675 10.221 3.77842 12.8327 7.00008 12.8327C10.2217 12.8327 12.8334 10.221 12.8334 6.99935C12.8334 3.77769 10.2217 1.16602 7.00008 1.16602ZM5.83342 6.41602C5.83342 6.09385 6.09458 5.83268 6.41675 5.83268H7.00008C7.32225 5.83268 7.58342 6.09385 7.58342 6.41602V9.33268C7.58342 9.65485 7.32225 9.91602 7.00008 9.91602C6.67792 9.91602 6.41675 9.65485 6.41675 9.33268V6.99935C6.09458 6.99935 5.83342 6.73818 5.83342 6.41602ZM7.00008 4.08268C6.67792 4.08268 6.41675 4.34385 6.41675 4.66602C6.41675 4.98818 6.67792 5.24935 7.00008 5.24935C7.32225 5.24935 7.58342 4.98818 7.58342 4.66602C7.58342 4.34385 7.32225 4.08268 7.00008 4.08268Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/KLetter.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M6 2C3.79086 2 2 3.79086 2 6V18C2 20.2091 3.79086 22 6 22H18C20.2091 22 22 20.2091 22 18V6C22 3.79086 20.2091 2 18 2H6ZM16.3216 8.01296C16.8026 7.52549 16.7692 6.76634 16.2469 6.31735C15.7246 5.86836 14.9112 5.89956 14.4302 6.38703L10.5714 10.2973V7.2C10.5714 6.53726 9.99578 6.00001 9.28571 6.00001C8.57564 6.00001 8.00001 6.53726 8.00001 7.2V16.7999C8.00001 17.4627 8.57564 17.9999 9.28571 17.9999C9.99578 17.9999 10.5714 17.4627 10.5714 16.7999V13.8398L11.2133 13.1893L14.6848 17.5189C15.1103 18.0495 15.9161 18.1578 16.4846 17.7607C17.0531 17.3636 17.169 16.6116 16.7436 16.081L12.9853 11.3937L16.3216 8.01296Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Like.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M4.58959 6.32711C4.58466 6.34807 4.57839 6.36895 4.57073 6.38967C4.24495 7.27034 4.06966 8.2923 4.07066 9.3414C4.07066 10.1027 4.16078 10.8262 4.32297 11.4839C4.32455 11.4903 4.32601 11.4967 4.32735 11.5032C4.33693 11.5416 4.34674 11.5798 4.35681 11.6177H4.3971C4.69372 11.6176 4.98843 11.6654 5.26989 11.7593L7.17985 12.3997C7.46137 12.4933 7.75639 12.5413 8.05263 12.5413H10.5158C10.8949 12.5413 11.2623 12.3893 11.5002 12.0925C12.554 10.7811 13.1275 9.14636 13.125 7.46148C13.125 7.19363 13.1109 6.9301 13.0833 6.67025C13.0164 6.04158 12.4528 5.61426 11.8235 5.61426H9.90616C9.52711 5.61426 9.29834 5.16846 9.46149 4.82488C9.74546 4.22453 9.90493 3.55276 9.90493 2.84342C9.90493 2.47599 9.75954 2.1236 9.50073 1.86379C9.24193 1.60397 8.89091 1.45801 8.52491 1.45801C8.40291 1.45801 8.2859 1.50666 8.19963 1.59327C8.11337 1.67987 8.0649 1.79733 8.0649 1.91981V2.30958C8.06496 2.66224 7.99793 3.01168 7.8674 3.3391C7.68156 3.80644 7.29699 4.15865 6.85355 4.39509C6.17255 4.75972 5.57488 5.26305 5.09877 5.87287C4.95644 6.05478 4.78361 6.21155 4.58959 6.32711Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M3.29689 6.915C3.38483 6.56408 3.12706 6.20801 2.76217 6.20801H2.54143C2.03665 6.20801 1.56399 6.45848 1.37857 6.88643C1.05376 7.63949 0.875002 8.45946 0.875002 9.31813C0.874433 9.97518 0.980832 10.6287 1.19071 11.2572C1.34766 11.7272 1.84698 12.0136 2.38569 12.0136H3.07023C3.21655 12.0136 3.32251 11.8753 3.28778 11.7344C3.2861 11.7276 3.28456 11.7208 3.28316 11.7139C3.10332 10.9773 3.00546 10.1772 3.00544 9.34213C3.00467 8.51045 3.10361 7.68626 3.29689 6.915Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/LinkChain.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M10.8118 3.18803C9.67194 2.04822 7.82393 2.04822 6.68411 3.18803L6.6814 3.19075L6.24305 3.62335C6.01375 3.84966 5.64441 3.84723 5.41811 3.61793C5.19181 3.38863 5.19424 3.01929 5.42354 2.79299L5.86057 2.36167C7.45611 0.767646 10.0418 0.768117 11.6367 2.36308C13.2317 3.95805 13.2322 6.54373 11.6381 8.13927C11.6376 8.13973 11.6372 8.14018 11.6367 8.14064L11.2068 8.57626C10.9805 8.80556 10.6112 8.80799 10.3819 8.58169C10.1526 8.35538 10.1501 7.98605 10.3764 7.75675L10.8118 7.31567C11.9516 6.17585 11.9516 4.32785 10.8118 3.18803ZM3.61805 5.41799C3.84735 5.64429 3.84978 6.01363 3.62348 6.24293L3.18816 6.684C2.04835 7.82382 2.04834 9.67182 3.18816 10.8116C4.32798 11.9515 6.17599 11.9515 7.3158 10.8116L7.31852 10.8089L7.75687 10.3763C7.98617 10.15 8.35551 10.1524 8.58181 10.3817C8.80811 10.611 8.80568 10.9804 8.57638 11.2067L8.14076 11.6366C8.14029 11.6371 8.13982 11.6375 8.13935 11.638C6.54381 13.232 3.95816 13.2316 2.3632 11.6366C0.768238 10.0416 0.767768 7.45598 2.36179 5.86044L2.79311 5.42341C3.01941 5.19411 3.38875 5.19169 3.61805 5.41799ZM8.57911 5.42069C8.80691 5.6485 8.80691 6.01784 8.57911 6.24565L6.24577 8.57898C6.01797 8.80679 5.64862 8.80679 5.42081 8.57898C5.19301 8.35118 5.19301 7.98183 5.42081 7.75403L7.75415 5.42069C7.98195 5.19289 8.3513 5.19289 8.57911 5.42069Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/LiteLoader.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M10.875 2.25C10.875 1.62868 11.3787 1.125 12 1.125C18.0061 1.125 22.875 5.9939 22.875 12C22.875 12.6213 22.3713 13.125 21.75 13.125C21.1287 13.125 20.625 12.6213 20.625 12C20.625 7.23654 16.7635 3.375 12 3.375C11.3787 3.375 10.875 2.87132 10.875 2.25Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M3.375 12C3.375 16.7635 7.23655 20.625 12 20.625C12.6213 20.625 13.125 21.1287 13.125 21.75C13.125 22.3713 12.6213 22.875 12 22.875C5.9939 22.875 1.125 18.0061 1.125 12C1.125 11.3787 1.62868 10.875 2.25 10.875C2.87132 10.875 3.375 11.3787 3.375 12Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/LogoFull.tsx",
    "content": "const LogoFull = () => (\n  <svg viewBox=\"0 0 83 28\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <g clipPath=\"url(#clip0_624_66850)\">\n      <path\n        d=\"M71.8267 8.91071C67.9767 5.9327 64.1138 7.54719 62.5718 9.45922C62.1552 9.04266 61.8603 8.64422 61.467 8.25353H58.8538V28H64.207V23.4851L64.075 21.9741C64.947 23.0711 69.0427 24.5355 72.5278 21.1151C73.3621 20.3086 74.0126 19.3316 74.4349 18.2507C74.8572 17.1699 75.0414 16.0107 74.9748 14.8522C74.9083 13.6937 74.5926 12.5632 74.0493 11.5378C73.506 10.5125 72.7478 9.61637 71.8267 8.91071ZM66.9107 18.2432C66.3329 18.2391 65.7692 18.064 65.2907 17.74C64.8122 17.416 64.4403 16.9575 64.222 16.4225C64.0037 15.8874 63.9487 15.2997 64.064 14.7335C64.1792 14.1672 64.4595 13.6477 64.8696 13.2405C65.2796 12.8334 65.8011 12.5567 66.3681 12.4455C66.9352 12.3342 67.5225 12.3934 68.056 12.6154C68.5895 12.8375 69.0453 13.2126 69.3659 13.6933C69.6866 14.1741 69.8577 14.739 69.8577 15.3169C69.8577 15.7021 69.7817 16.0834 69.634 16.4391C69.4863 16.7949 69.2698 17.1179 68.997 17.3898C68.7241 17.6616 68.4003 17.877 68.0441 18.0234C67.6879 18.1699 67.3062 18.2445 66.9211 18.2432H66.9107Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M16.1345 15.141C16.1564 14.1147 15.9717 13.0945 15.5912 12.141C15.2107 11.1876 14.6423 10.3205 13.9198 9.5912C10.3803 5.87322 6.07762 7.52135 5.2238 8.48383L5.35317 5.73092V0H0V22.2044H2.61578C3.00647 21.8137 3.30142 21.4282 3.71798 20.9987C5.27038 22.9107 9.1229 24.5252 12.9728 21.5472C13.9555 20.7931 14.7517 19.8232 15.2999 18.7124C15.8481 17.6016 16.1336 16.3797 16.1345 15.141V15.141ZM10.9935 15.141C10.9935 15.7198 10.8219 16.2855 10.5004 16.7668C10.1788 17.248 9.72181 17.623 9.1871 17.8445C8.6524 18.066 8.06402 18.124 7.49639 18.011C6.92875 17.8981 6.40734 17.6194 5.99809 17.2102C5.58885 16.8009 5.31015 16.2795 5.19724 15.7119C5.08433 15.1443 5.14228 14.5559 5.36376 14.0212C5.58524 13.4865 5.96031 13.0295 6.44153 12.7079C6.92275 12.3864 7.48851 12.2147 8.06727 12.2147C8.84336 12.2147 9.58767 12.523 10.1364 13.0718C10.6852 13.6206 10.9935 14.3649 10.9935 15.141Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M21.141 22.2044H17.5317V0.292358H22.8823V20.4735L21.141 22.2044Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M79.5498 22.3725C78.9708 22.3731 78.4047 22.2018 77.9231 21.8805C77.4415 21.5591 77.066 21.1021 76.8442 20.5673C76.6224 20.0325 76.5642 19.4439 76.6771 18.876C76.7899 18.3082 77.0687 17.7865 77.4781 17.3771C77.8875 16.9677 78.4091 16.689 78.977 16.5761C79.5449 16.4633 80.1335 16.5215 80.6683 16.7433C81.2031 16.9651 81.6601 17.3405 81.9814 17.8222C82.3028 18.3038 82.474 18.8699 82.4735 19.4489C82.4735 20.2243 82.1655 20.9679 81.6172 21.5162C81.0689 22.0645 80.3252 22.3725 79.5498 22.3725V22.3725Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M49.4955 7.27557C47.9141 7.27557 46.3681 7.74459 45.0533 8.6233C43.7384 9.50202 42.7137 10.7509 42.1087 12.2121C41.5038 13.6733 41.3458 15.281 41.6547 16.832C41.9636 18.383 42.7255 19.8075 43.8441 20.9254C44.9628 22.0433 46.3878 22.8043 47.939 23.1122C49.4901 23.4201 51.0978 23.2611 52.5586 22.6552C54.0193 22.0493 55.2676 21.0237 56.1455 19.7083C57.0233 18.3929 57.4913 16.8467 57.4903 15.2652C57.489 13.1458 56.646 11.1136 55.1469 9.61537C53.6477 8.11717 51.615 7.27557 49.4955 7.27557V7.27557ZM49.4955 18.1915C48.9174 18.1915 48.3522 18.0201 47.8715 17.6989C47.3907 17.3778 47.016 16.9213 46.7946 16.3873C46.5733 15.8532 46.5152 15.2654 46.6278 14.6984C46.7404 14.1313 47.0186 13.6104 47.4272 13.2014C47.8359 12.7924 48.3566 12.5137 48.9235 12.4006C49.4905 12.2875 50.0783 12.3451 50.6125 12.5659C51.1468 12.7868 51.6036 13.1612 51.9252 13.6416C52.2468 14.1221 52.4187 14.6871 52.4192 15.2652C52.4199 15.6495 52.3447 16.0301 52.1981 16.3853C52.0514 16.7404 51.8361 17.0632 51.5645 17.335C51.2929 17.6068 50.9704 17.8224 50.6153 17.9694C50.2603 18.1164 49.8798 18.1918 49.4955 18.1915V18.1915Z\"\n        fill=\"currentColor\"\n      />\n      <path\n        d=\"M32.2406 7.27557C30.6591 7.27506 29.1129 7.74365 27.7977 8.62205C26.4825 9.50045 25.4574 10.7492 24.852 12.2103C24.2467 13.6714 24.0883 15.2793 24.3969 16.8304C24.7055 18.3816 25.4672 19.8064 26.5857 20.9245C27.7043 22.0427 29.1293 22.8039 30.6805 23.112C32.2318 23.4202 33.8396 23.2612 35.3005 22.6554C36.7614 22.0496 38.0098 21.0241 38.8878 19.7086C39.7658 18.3931 40.2339 16.8468 40.2328 15.2652C40.2315 13.1462 39.3889 11.1144 37.8903 9.61629C36.3917 8.11816 34.3596 7.27626 32.2406 7.27557V7.27557ZM32.2406 18.1915C31.6624 18.192 31.0969 18.021 30.6159 17.7002C30.1348 17.3794 29.7597 16.9231 29.5379 16.389C29.3161 15.855 29.2577 15.2672 29.37 14.7C29.4823 14.1327 29.7603 13.6115 30.1688 13.2023C30.5774 12.793 31.0981 12.5141 31.6651 12.4008C32.2321 12.2875 32.82 12.3449 33.3545 12.5657C33.8889 12.7865 34.3458 13.1608 34.6675 13.6414C34.9892 14.1219 35.1612 14.687 35.1617 15.2652C35.1627 15.6494 35.0879 16.0299 34.9415 16.3851C34.7952 16.7402 34.5801 17.063 34.3087 17.3349C34.0373 17.6067 33.715 17.8223 33.3601 17.9693C33.0052 18.1163 32.6247 18.1918 32.2406 18.1915V18.1915Z\"\n        fill=\"currentColor\"\n      />\n    </g>\n    <defs>\n      <clipPath id=\"clip0_624_66850\">\n        <rect width=\"82.4735\" height=\"28\" fill=\"currentColor\" />\n      </clipPath>\n    </defs>\n  </svg>\n);\n\nexport default LogoFull;\n"
  },
  {
    "path": "client/src/icons/Macintosh.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M2.66663 2.66683C2.66663 1.93045 3.26358 1.3335 3.99996 1.3335H12C12.7363 1.3335 13.3333 1.93045 13.3333 2.66683V11.3335C13.3333 11.827 13.0652 12.2579 12.6666 12.4885V13.3335C12.6666 14.0699 12.0697 14.6668 11.3333 14.6668H4.66663C3.93025 14.6668 3.33329 14.0699 3.33329 13.3335V12.4885C2.93476 12.2579 2.66663 11.827 2.66663 11.3335V2.66683ZM12 2.66683H3.99996V11.3335H12V2.66683ZM4.66663 4.00016C4.66663 3.63197 4.9651 3.3335 5.33329 3.3335H10.6666C11.0348 3.3335 11.3333 3.63197 11.3333 4.00016V8.00016C11.3333 8.36835 11.0348 8.66683 10.6666 8.66683H5.33329C4.9651 8.66683 4.66663 8.36835 4.66663 8.00016V4.00016ZM8.66663 10.0002C8.66663 9.63197 8.9651 9.3335 9.33329 9.3335H10.6666C11.0348 9.3335 11.3333 9.63197 11.3333 10.0002C11.3333 10.3684 11.0348 10.6668 10.6666 10.6668H9.33329C8.9651 10.6668 8.66663 10.3684 8.66663 10.0002Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Magazine.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M14.7416 1.59556V1.25427C14.7416 0.409557 14.3133 0 13.5594 0C13.3024 0 12.994 0.0597271 12.6257 0.179181C10.0386 1.0239 7.67419 1.56144 4.64164 1.56144H3.95631C3.03113 1.56144 2.5 2.09898 2.5 2.93516V17.5768C2.5 18.2423 2.64563 18.6434 3.27956 18.9506C4.74443 19.5819 6.93746 20 9.19046 20C11.5891 20 14.2876 19.6417 16.2236 18.703C17.2516 18.2167 17.5 17.7645 17.5 16.7065V3.60922C17.5 2.22697 16.8832 1.59556 15.5468 1.59556H14.7416ZM14.7416 2.96929V14.8635C14.7416 15.6144 14.5617 15.9216 13.9534 16.3823C12.4805 17.5066 9.36165 18.4669 6.00218 18.5461C7.06496 18.6886 8.1105 18.7458 9.19046 18.7458C11.4521 18.7458 13.8507 18.2679 15.5554 17.5256C16.0437 17.3123 16.1208 17.1246 16.1208 16.6127V3.60922C16.1208 3.17406 15.9323 2.96929 15.5554 2.96929H14.7416ZM5.83239 8.16553C5.72102 8.16553 5.62678 8.10581 5.62678 7.96929V5.17066C5.62678 5.0512 5.71245 4.95735 5.83239 4.94881C7.76842 4.87202 9.49887 4.53072 11.2721 3.89078C11.3663 3.84813 11.5463 3.89078 11.5463 4.08703V7.00513C11.5463 7.08192 11.5035 7.15871 11.4263 7.19284C9.88436 7.80718 7.84552 8.16553 5.83239 8.16553Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/MagnifyTool.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <circle cx=\"9.5\" cy=\"9.5\" r=\"6\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n    <path\n      d=\"M13.9581 13.5153L16.7866 16.3437\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/MailIcon.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <rect\n      x=\"2.75\"\n      y=\"4.75\"\n      width=\"14.5\"\n      height=\"10.5\"\n      rx=\"2.25\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n    />\n    <path\n      d=\"M4 6L8.8906 9.2604C9.5624 9.70827 10.4376 9.70827 11.1094 9.2604L16 6\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n    <rect\n      x=\"2.75\"\n      y=\"4.75\"\n      width=\"14.5\"\n      height=\"10.5\"\n      rx=\"2.25\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n    />\n    <path\n      d=\"M4 6L8.8906 9.2604C9.5624 9.70827 10.4376 9.70827 11.1094 9.2604L16 6\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n    <rect\n      x=\"2.75\"\n      y=\"4.75\"\n      width=\"14.5\"\n      height=\"10.5\"\n      rx=\"2.25\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n    />\n    <path\n      d=\"M4 6L8.8906 9.2604C9.5624 9.70827 10.4376 9.70827 11.1094 9.2604L16 6\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n    <rect\n      x=\"2.75\"\n      y=\"4.75\"\n      width=\"14.5\"\n      height=\"10.5\"\n      rx=\"2.25\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n    />\n    <path\n      d=\"M4 6L8.8906 9.2604C9.5624 9.70827 10.4376 9.70827 11.1094 9.2604L16 6\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n    <rect\n      x=\"2.75\"\n      y=\"4.75\"\n      width=\"14.5\"\n      height=\"10.5\"\n      rx=\"2.25\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n    />\n    <path\n      d=\"M4 6L8.8906 9.2604C9.5624 9.70827 10.4376 9.70827 11.1094 9.2604L16 6\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/MoreHorizontal.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M5.70801 9.99999C5.70801 10.1878 5.63341 10.3678 5.50064 10.5006C5.36786 10.6334 5.18778 10.708 5.00001 10.708C4.81223 10.708 4.63215 10.6334 4.49938 10.5006C4.3666 10.3678 4.29201 10.1878 4.29201 9.99999C4.29201 9.81222 4.3666 9.63214 4.49938 9.49936C4.63215 9.36658 4.81223 9.29199 5.00001 9.29199C5.18778 9.29199 5.36786 9.36658 5.50064 9.49936C5.63341 9.63214 5.70801 9.81222 5.70801 9.99999ZM10.708 9.99999C10.708 10.093 10.6897 10.185 10.6541 10.2709C10.6185 10.3568 10.5664 10.4349 10.5006 10.5006C10.4349 10.5664 10.3568 10.6185 10.2709 10.6541C10.185 10.6897 10.093 10.708 10 10.708C9.90703 10.708 9.81497 10.6897 9.72907 10.6541C9.64317 10.6185 9.56512 10.5664 9.49938 10.5006C9.43363 10.4349 9.38148 10.3568 9.3459 10.2709C9.31032 10.185 9.29201 10.093 9.29201 9.99999C9.29201 9.81222 9.3666 9.63214 9.49938 9.49936C9.63215 9.36658 9.81223 9.29199 10 9.29199C10.1878 9.29199 10.3679 9.36658 10.5006 9.49936C10.6334 9.63214 10.708 9.81222 10.708 9.99999ZM15.2709 10.6541C15.1851 10.6897 15.093 10.708 15 10.708C14.8122 10.708 14.6322 10.6334 14.4994 10.5006C14.3666 10.3678 14.292 10.1878 14.292 9.99999C14.292 9.81222 14.3666 9.63214 14.4994 9.49936C14.6322 9.36659 14.8122 9.29199 15 9.29199C15.093 9.29199 15.1851 9.31031 15.2709 9.34589C15.3568 9.38147 15.4349 9.43362 15.5006 9.49936C15.5664 9.56511 15.6185 9.64316 15.6541 9.72905C15.6897 9.81495 15.708 9.90701 15.708 9.99999C15.708 10.093 15.6897 10.185 15.6541 10.2709C15.6185 10.3568 15.5664 10.4349 15.5006 10.5006C15.4349 10.5664 15.3568 10.6185 15.2709 10.6541Z\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Pencil.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M8.80012 1.75C9.25573 1.29439 9.99443 1.29439 10.45 1.75L12.2501 3.55005C12.7057 4.00566 12.7057 4.74435 12.2501 5.19996L11.325 6.125L7.87508 2.67504L8.80012 1.75Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M7.05012 3.5L1.50846 9.04167C1.28966 9.26046 1.16675 9.55721 1.16675 9.86663V11.6667C1.16675 12.311 1.68908 12.8333 2.33341 12.8333H4.13346C4.44288 12.8333 4.73962 12.7104 4.95842 12.4916L10.5001 6.94996L7.05012 3.5Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Person.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M3.75 14.8C3.75 12.5632 5.56325 10.75 7.8 10.75H12.2C14.4368 10.75 16.25 12.5632 16.25 14.8C16.25 16.1531 15.1531 17.25 13.8 17.25H6.2C4.8469 17.25 3.75 16.1531 3.75 14.8Z\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n    />\n    <circle cx=\"10\" cy=\"5\" r=\"2.25\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/PlusSign.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M3.56201 7.99991C3.56201 7.63172 3.86049 7.33325 4.22868 7.33325H7.33325V4.22868C7.33325 3.86049 7.63172 3.56201 7.99991 3.56201C8.3681 3.56201 8.66658 3.86049 8.66658 4.22868L8.66658 7.33325H11.7712C12.1393 7.33325 12.4378 7.63172 12.4378 7.99991C12.4378 8.3681 12.1393 8.66658 11.7712 8.66658H8.66658L8.66658 11.7712C8.66658 12.1393 8.3681 12.4378 7.99991 12.4378C7.63172 12.4378 7.33325 12.1393 7.33325 11.7712V8.66658L4.22868 8.66658C3.86049 8.66658 3.56201 8.3681 3.56201 7.99991Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Prompt.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M9.33342 0.875C9.65558 0.875 9.91675 1.13617 9.91675 1.45833V2.33333H11.0834C12.0499 2.33333 12.8334 3.11684 12.8334 4.08333V9.91667C12.8334 10.8832 12.0499 11.6667 11.0834 11.6667H9.91675V12.5417C9.91675 12.8638 9.65558 13.125 9.33342 13.125C9.01125 13.125 8.75008 12.8638 8.75008 12.5417V1.45833C8.75008 1.13617 9.01125 0.875 9.33342 0.875Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M1.16675 4.08333C1.16675 3.11684 1.95025 2.33333 2.91675 2.33333H7.58342V11.6667H2.91675C1.95025 11.6667 1.16675 10.8832 1.16675 9.91667V4.08333ZM4.49589 5.12919C4.26809 4.90138 3.89874 4.90138 3.67094 5.12919C3.44313 5.35699 3.44313 5.72634 3.67094 5.95415L4.71679 7L3.67094 8.04586C3.44313 8.27366 3.44313 8.64301 3.67094 8.87081C3.89874 9.09862 4.26809 9.09862 4.49589 8.87081L5.95423 7.41248C6.18203 7.18467 6.18203 6.81533 5.95423 6.58752L4.49589 5.12919Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Range.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 18 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M1.5 8.37205V5.04223C1.5 4.41522 2.18519 4.05027 2.67893 4.41429L4.93715 6.0792C5.35428 6.38674 5.35428 7.02755 4.93715 7.33509L2.67893 9C2.18519 9.36402 1.5 8.99906 1.5 8.37205Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M6.75008 4.5C6.75008 4.08579 7.08586 3.75 7.50008 3.75H15.7501C16.1643 3.75 16.5001 4.08579 16.5001 4.5C16.5001 4.91421 16.1643 5.25 15.7501 5.25H7.50008C7.08586 5.25 6.75008 4.91421 6.75008 4.5ZM6.75008 9C6.75008 8.58579 7.08586 8.25 7.50008 8.25H12.7501C13.1643 8.25 13.5001 8.58579 13.5001 9C13.5001 9.41421 13.1643 9.75 12.7501 9.75H7.50008C7.08586 9.75 6.75008 9.41421 6.75008 9ZM6.75008 13.5C6.75008 13.0858 7.08586 12.75 7.50008 12.75H15.7501C16.1643 12.75 16.5001 13.0858 16.5001 13.5C16.5001 13.9142 16.1643 14.25 15.7501 14.25H7.50008C7.08586 14.25 6.75008 13.9142 6.75008 13.5Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Ref.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M4 1C2.34315 1 1 2.34315 1 4V13C1 14.6569 2.34315 16 4 16H5.5V10C5.5 7.51472 7.51472 5.5 10 5.5H16V4C16 2.34315 14.6569 1 13 1H4Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M10 7C8.34315 7 7 8.34315 7 10V16C7 17.6569 8.34315 19 10 19H16C17.6569 19 19 17.6569 19 16V10C19 8.34315 17.6569 7 16 7H10ZM10.3882 10.0676C10.3456 10.0243 10.2877 10 10.2274 10C10.1976 10 10.168 10.006 10.1404 10.0176C10.1128 10.0292 10.0877 10.0462 10.0666 10.0676C10.0455 10.089 10.0287 10.1145 10.0173 10.1425C10.0059 10.1705 10 10.2005 10 10.2308V15.7692C10 15.8304 10.024 15.8891 10.0666 15.9324C10.1093 15.9757 10.1671 16 10.2274 16C10.2877 16 10.3456 15.9757 10.3882 15.9324C10.4309 15.8891 10.4549 15.8304 10.4549 15.7692V14.1031L11.1226 13.9338C11.7019 13.7869 12.3139 13.8552 12.848 14.1262L12.8808 14.1428C13.4994 14.4567 14.207 14.5405 14.8803 14.3797L15.8246 14.1542C15.8787 14.1413 15.9262 14.1087 15.9583 14.0627C15.9904 14.0167 16.0047 13.9603 15.9986 13.9043C15.8848 12.8473 15.8843 11.7808 15.9971 10.7237C16.001 10.687 15.9961 10.65 15.983 10.6156C15.9699 10.5812 15.9488 10.5506 15.9216 10.5262C15.8943 10.5018 15.8617 10.4843 15.8265 10.4753C15.7913 10.4663 15.7544 10.466 15.7191 10.4745L14.7763 10.6997C14.2065 10.8357 13.6077 10.7648 13.0842 10.4991L13.0515 10.4825C12.4202 10.1622 11.6969 10.0817 11.0122 10.2554L10.4549 10.3969V10.2308C10.4549 10.1696 10.4309 10.1109 10.3882 10.0676Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Refresh.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M4.69523 4.66667C5.54931 3.84276 6.72632 3.33333 8 3.33333C10.5773 3.33333 12.6667 5.42267 12.6667 8C12.6667 8.36819 12.9651 8.66667 13.3333 8.66667C13.7015 8.66667 14 8.36819 14 8C14 4.68629 11.3137 2 8 2C6.48234 2 5.07187 2.56419 3.99992 3.49613V2.66667C3.99992 2.29848 3.70144 2 3.33325 2C2.96506 2 2.66659 2.29848 2.66659 2.66667V5C2.66659 5.55228 3.1143 6 3.66659 6H5.83325C6.20144 6 6.49992 5.70152 6.49992 5.33333C6.49992 4.96514 6.20144 4.66667 5.83325 4.66667H4.69523Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M3.33333 8C3.33333 7.63181 3.03486 7.33333 2.66667 7.33333C2.29848 7.33333 2 7.63181 2 8C2 11.3137 4.68629 14 8 14C9.52131 14 10.9349 13.4331 12.0078 12.4971V13.3333C12.0078 13.7015 12.3063 14 12.6745 14C13.0427 14 13.3411 13.7015 13.3411 13.3333V11C13.3411 10.4477 12.8934 10 12.3411 10H10.0078C9.63962 10 9.34115 10.2985 9.34115 10.6667C9.34115 11.0349 9.63962 11.3333 10.0078 11.3333H11.3048C10.4507 12.1572 9.27368 12.6667 8 12.6667C5.42267 12.6667 3.33333 10.5773 3.33333 8Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Regex.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 14 14\" fill=\"none\">\n    <path\n      d=\"M3.79183 11.6667C3.79183 12.3111 3.2695 12.8334 2.62516 12.8334C1.98083 12.8334 1.4585 12.3111 1.4585 11.6667C1.4585 11.0224 1.98083 10.5001 2.62516 10.5001C3.2695 10.5001 3.79183 11.0224 3.79183 11.6667Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M7.87516 1.16675H9.62516V9.33342H7.87516V1.16675Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M4.37516 5.83342V4.08342L12.5418 4.08341V5.83341L4.37516 5.83342Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M5.31795 2.76009L6.25094 1.8271L11.8489 7.42503L10.9159 8.35802L5.31795 2.76009Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M6.25106 8.35815L5.31807 7.42517L10.916 1.82724L11.849 2.76023L6.25106 8.35815Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/RegexSearch.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width=\"14\"\n    height=\"14\"\n    viewBox=\"0 0 14 14\"\n    fill=\"none\"\n  >\n    <path\n      d=\"M6.41667 2.33341C6.41667 2.01125 6.15419 1.74626 5.83449 1.78604C3.53192 2.07258 1.75 4.03657 1.75 6.41675C1.75 8.99408 3.83934 11.0834 6.41667 11.0834C7.49508 11.0834 8.48806 10.7176 9.27829 10.1033L11.2542 12.0792C11.482 12.307 11.8513 12.307 12.0791 12.0792C12.3069 11.8514 12.3069 11.4821 12.0791 11.2543L10.1033 9.27837C10.472 8.80396 10.7513 8.25647 10.9155 7.66128C11.0109 7.31588 10.7308 7.00008 10.3725 7.00008C10.0814 7.00008 9.83828 7.21262 9.74915 7.48967C9.29614 8.89781 7.9754 9.91675 6.41667 9.91675C4.48367 9.91675 2.91667 8.34975 2.91667 6.41675C2.91667 4.68165 4.17924 3.24144 5.83578 2.96473C6.15354 2.91165 6.41667 2.65558 6.41667 2.33341Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M9.58333 1.16675H10.5833V5.83342H9.58333V1.16675Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M7.58333 3.83342V2.83342L12.25 2.83341V3.83341L7.58333 3.83342Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M8.12207 2.07723L8.65521 1.54409L11.854 4.74291L11.3209 5.27605L8.12207 2.07723Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M8.65527 5.27612L8.12214 4.74299L11.321 1.54417L11.8541 2.07731L8.65527 5.27612Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Repository.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M4.0835 1.16675C3.117 1.16675 2.3335 1.95025 2.3335 2.91675V11.0834C2.3335 12.0499 3.117 12.8334 4.0835 12.8334H11.0835C11.4057 12.8334 11.6668 12.5722 11.6668 12.2501V1.75008C11.6668 1.42792 11.4057 1.16675 11.0835 1.16675H4.0835ZM4.0835 10.5001H10.5002V11.6667H4.0835C3.76133 11.6667 3.50016 11.4056 3.50016 11.0834C3.50016 10.7612 3.76133 10.5001 4.0835 10.5001Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Run.tsx",
    "content": "import React from 'react';\nimport IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M11.5254 4.55596C12.5123 4.55596 13.3034 3.76485 13.3034 2.77799C13.3034 1.79112 12.5123 1 11.5254 1C10.5386 1 9.74747 1.79112 9.74747 2.77799C9.74747 3.76485 10.5386 4.55596 11.5254 4.55596ZM8.73614 11.6516L11.8598 13.4051L9.84534 15.1341C9.41308 15.5012 9.42124 15.9824 9.68222 16.276C9.95136 16.5777 10.4326 16.6511 10.8811 16.2678L13.6949 13.87C14.0783 13.5437 14.0211 12.8423 13.5563 12.5406L10.4978 10.5342L11.1013 8.42185C11.1829 8.12008 11.5336 8.0793 11.713 8.34029L12.6102 9.65337C12.8223 9.95519 13.2056 10.0775 13.5563 9.91435L16.1417 8.7644C16.5984 8.5605 16.7697 8.16086 16.574 7.74491C16.37 7.33712 15.9786 7.21478 15.5218 7.41868L13.4747 8.32398L12.0638 6.37473C11.3297 5.3634 10.4162 4.94744 8.91557 5.06163L6.05286 5.26552C5.59613 5.29815 5.28621 5.59176 5.22096 6.0648L4.83763 8.9683C4.76423 9.47397 5.01706 9.82466 5.46563 9.8736C5.92237 9.91435 6.21598 9.65337 6.28122 9.14773L6.63192 6.77436L7.78191 6.69281C8.03473 6.66834 8.28757 6.84776 8.19785 7.16584L7.52907 9.49844C7.13759 10.8768 8.03473 11.2519 8.73614 11.6516ZM3.27986 18.6983C3.58162 18.9511 4.04651 18.9837 4.46246 18.5841L7.10496 15.966C7.32517 15.7458 7.37411 15.6969 7.51276 15.3544L8.62196 12.4835L8.35281 12.3367C7.73296 12.0023 7.30071 11.6598 7.00709 11.2927L6.03654 14.7263L3.29617 17.4585C2.86391 17.8826 2.94547 18.4046 3.27986 18.6983Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Send.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg\n    width=\"20\"\n    height=\"20\"\n    viewBox=\"0 0 20 20\"\n    fill=\"none\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n  >\n    <path\n      d=\"M15.8758 8.25412C17.2416 9.01736 17.2416 10.9826 15.8758 11.7459L4.97564 17.8371C3.6425 18.5821 2 17.6184 2 16.0913L2 12.5L6.9215 10.462C7.33309 10.2915 7.33309 9.70849 6.9215 9.53804L2 7.5L2 3.90875C2 2.38157 3.6425 1.41787 4.97564 2.16286L15.8758 8.25412Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Shapes.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M4.66667 2C5.03486 2 5.33333 2.29848 5.33333 2.66667V4H6.66667C7.03486 4 7.33333 4.29848 7.33333 4.66667C7.33333 5.03486 7.03486 5.33333 6.66667 5.33333H5.33333V6.66667C5.33333 7.03486 5.03486 7.33333 4.66667 7.33333C4.29848 7.33333 4 7.03486 4 6.66667V5.33333H2.66667C2.29848 5.33333 2 5.03486 2 4.66667C2 4.29848 2.29848 4 2.66667 4H4V2.66667C4 2.29848 4.29848 2 4.66667 2ZM8.66667 4.66667C8.66667 3.19391 9.86057 2 11.3333 2C12.8061 2 14 3.19391 14 4.66667C14 6.13943 12.8061 7.33333 11.3333 7.33333C9.86057 7.33333 8.66667 6.13943 8.66667 4.66667ZM11.3333 3.33333C10.597 3.33333 10 3.93029 10 4.66667C10 5.40305 10.597 6 11.3333 6C12.0697 6 12.6667 5.40305 12.6667 4.66667C12.6667 3.93029 12.0697 3.33333 11.3333 3.33333ZM2 10.3333C2 9.41286 2.74619 8.66667 3.66667 8.66667H5.66667C6.58714 8.66667 7.33333 9.41286 7.33333 10.3333V12.3333C7.33333 13.2538 6.58714 14 5.66667 14H3.66667C2.74619 14 2 13.2538 2 12.3333V10.3333ZM3.66667 10C3.48257 10 3.33333 10.1492 3.33333 10.3333V12.3333C3.33333 12.5174 3.48257 12.6667 3.66667 12.6667H5.66667C5.85076 12.6667 6 12.5174 6 12.3333V10.3333C6 10.1492 5.85076 10 5.66667 10H3.66667ZM9.44755 9.44737C9.7079 9.18702 10.13 9.18702 10.3904 9.44737L11.3332 10.3902L12.276 9.44737C12.5363 9.18702 12.9584 9.18702 13.2188 9.44737C13.4791 9.70772 13.4791 10.1298 13.2188 10.3902L12.276 11.333L13.2188 12.2758C13.4791 12.5361 13.4791 12.9583 13.2188 13.2186C12.9584 13.479 12.5363 13.479 12.276 13.2186L11.3332 12.2758L10.3904 13.2186C10.13 13.479 9.7079 13.479 9.44755 13.2186C9.1872 12.9583 9.1872 12.5361 9.44755 12.2758L10.3904 11.333L9.44755 10.3902C9.1872 10.1298 9.1872 9.70772 9.44755 9.44737Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/SpinLoader.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 18 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <rect\n      opacity=\"0.65\"\n      x=\"8.1665\"\n      y=\"0.666504\"\n      width=\"1.66667\"\n      height=\"4.16667\"\n      rx=\"0.833333\"\n      fill=\"currentColor\"\n    />\n    <rect\n      opacity=\"0.1\"\n      x=\"8.1665\"\n      y=\"13.1665\"\n      width=\"1.66667\"\n      height=\"4.16667\"\n      rx=\"0.833333\"\n      fill=\"currentColor\"\n    />\n    <rect\n      opacity=\"0.95\"\n      x=\"17.3333\"\n      y=\"8.1665\"\n      width=\"1.66667\"\n      height=\"4.16667\"\n      rx=\"0.833333\"\n      transform=\"rotate(90 17.3333 8.1665)\"\n      fill=\"currentColor\"\n    />\n    <rect\n      opacity=\"0.35\"\n      x=\"4.83325\"\n      y=\"8.1665\"\n      width=\"1.66667\"\n      height=\"4.16667\"\n      rx=\"0.833333\"\n      transform=\"rotate(90 4.83325 8.1665)\"\n      fill=\"currentColor\"\n    />\n    <rect\n      opacity=\"0.75\"\n      x=\"12.4449\"\n      y=\"1.3667\"\n      width=\"1.66667\"\n      height=\"4.16667\"\n      rx=\"0.833333\"\n      transform=\"rotate(30 12.4449 1.3667)\"\n      fill=\"currentColor\"\n    />\n    <rect\n      opacity=\"0.15\"\n      x=\"6.19495\"\n      y=\"12.1919\"\n      width=\"1.66667\"\n      height=\"4.16667\"\n      rx=\"0.833333\"\n      transform=\"rotate(30 6.19495 12.1919)\"\n      fill=\"currentColor\"\n    />\n    <rect\n      opacity=\"0.05\"\n      x=\"16.6333\"\n      y=\"12.4448\"\n      width=\"1.66667\"\n      height=\"4.16667\"\n      rx=\"0.833333\"\n      transform=\"rotate(120 16.6333 12.4448)\"\n      fill=\"currentColor\"\n    />\n    <rect\n      opacity=\"0.45\"\n      x=\"5.80811\"\n      y=\"6.19482\"\n      width=\"1.66667\"\n      height=\"4.16667\"\n      rx=\"0.833333\"\n      transform=\"rotate(120 5.80811 6.19482)\"\n      fill=\"currentColor\"\n    />\n    <rect\n      opacity=\"0.85\"\n      x=\"15.8\"\n      y=\"4.11133\"\n      width=\"1.66667\"\n      height=\"4.16667\"\n      rx=\"0.833333\"\n      transform=\"rotate(60 15.8 4.11133)\"\n      fill=\"currentColor\"\n    />\n    <rect\n      opacity=\"0.25\"\n      x=\"4.97485\"\n      y=\"10.3613\"\n      width=\"1.66667\"\n      height=\"4.16667\"\n      rx=\"0.833333\"\n      transform=\"rotate(60 4.97485 10.3613)\"\n      fill=\"currentColor\"\n    />\n    <rect\n      opacity=\"0.1\"\n      x=\"13.8882\"\n      y=\"15.8003\"\n      width=\"1.66667\"\n      height=\"4.16667\"\n      rx=\"0.833333\"\n      transform=\"rotate(150 13.8882 15.8003)\"\n      fill=\"currentColor\"\n    />\n    <rect\n      opacity=\"0.55\"\n      x=\"7.63818\"\n      y=\"4.9751\"\n      width=\"1.66667\"\n      height=\"4.16667\"\n      rx=\"0.833333\"\n      transform=\"rotate(150 7.63818 4.9751)\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/SplitView.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 18 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M0.75 5.25C0.75 4.00736 1.8581 3 3.225 3H14.775C16.1419 3 17.25 4.00736 17.25 5.25V12.75C17.25 13.9926 16.1419 15 14.775 15H3.225C1.8581 15 0.75 13.9926 0.75 12.75V5.25ZM3.225 4.125C2.54155 4.125 1.9875 4.62868 1.9875 5.25V12.75C1.9875 13.3713 2.54155 13.875 3.225 13.875H8.625V4.125H3.225Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/StudioCloseSign.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M6.82165 1.82214C7.13421 1.50958 7.55814 1.33398 8.00016 1.33398C8.44219 1.33398 8.86611 1.50958 9.17867 1.82214C9.49124 2.1347 9.66683 2.55862 9.66683 3.00065C9.66683 3.44268 9.49124 3.8666 9.17867 4.17916C8.86611 4.49172 8.44219 4.66732 8.00016 4.66732C7.55814 4.66732 7.13421 4.49172 6.82165 4.17916C6.50909 3.8666 6.3335 3.44268 6.3335 3.00065C6.3335 2.55862 6.50909 2.1347 6.82165 1.82214ZM4.04183 4.66732C4.04183 4.32214 4.32165 4.04232 4.66683 4.04232H4.67516C5.02034 4.04232 5.30016 4.32214 5.30016 4.66732C5.30016 5.0125 5.02034 5.29232 4.67516 5.29232H4.66683C4.32165 5.29232 4.04183 5.0125 4.04183 4.66732ZM10.7085 4.66732C10.7085 4.32214 10.9883 4.04232 11.3335 4.04232H11.3418C11.687 4.04232 11.9668 4.32214 11.9668 4.66732C11.9668 5.0125 11.687 5.29232 11.3418 5.29232H11.3335C10.9883 5.29232 10.7085 5.0125 10.7085 4.66732ZM1.82165 6.82214C2.13421 6.50958 2.55814 6.33398 3.00016 6.33398C3.44219 6.33398 3.86611 6.50958 4.17867 6.82214C4.49124 7.1347 4.66683 7.55862 4.66683 8.00065C4.66683 8.44268 4.49124 8.8666 4.17867 9.17916C3.86611 9.49172 3.44219 9.66732 3.00016 9.66732C2.55814 9.66732 2.13421 9.49172 1.82165 9.17916C1.50909 8.8666 1.3335 8.44268 1.3335 8.00065C1.3335 7.55862 1.50909 7.1347 1.82165 6.82214ZM4.04183 11.334C4.04183 10.9888 4.32165 10.709 4.66683 10.709H4.67516C5.02034 10.709 5.30016 10.9888 5.30016 11.334C5.30016 11.6792 5.02034 11.959 4.67516 11.959H4.66683C4.32165 11.959 4.04183 11.6792 4.04183 11.334Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M7.26937 7.26986C7.52358 7.01564 7.93575 7.01564 8.18996 7.26986L10.3335 9.41339L12.477 7.26986C12.7312 7.01564 13.1434 7.01564 13.3976 7.26986C13.6518 7.52407 13.6518 7.93623 13.3976 8.19045L11.2541 10.334L13.3976 12.4775C13.6518 12.7317 13.6518 13.1439 13.3976 13.3981C13.1434 13.6523 12.7312 13.6523 12.477 13.3981L10.3335 11.2546L8.18996 13.3981C7.93575 13.6523 7.52358 13.6523 7.26937 13.3981C7.01515 13.1439 7.01515 12.7317 7.26937 12.4775L9.4129 10.334L7.26937 8.19045C7.01515 7.93623 7.01515 7.52407 7.26937 7.26986Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/StudioPlusSign.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M6.82165 1.82116C7.13421 1.5086 7.55813 1.33301 8.00016 1.33301C8.44219 1.33301 8.86611 1.5086 9.17867 1.82116C9.49123 2.13372 9.66683 2.55765 9.66683 2.99967C9.66683 3.4417 9.49123 3.86563 9.17867 4.17819C8.86611 4.49075 8.44219 4.66634 8.00016 4.66634C7.55813 4.66634 7.13421 4.49075 6.82165 4.17819C6.50909 3.86563 6.3335 3.4417 6.3335 2.99967C6.3335 2.55765 6.50909 2.13372 6.82165 1.82116ZM4.04183 4.66634C4.04183 4.32116 4.32165 4.04134 4.66683 4.04134H4.67516C5.02034 4.04134 5.30016 4.32116 5.30016 4.66634C5.30016 5.01152 5.02034 5.29134 4.67516 5.29134H4.66683C4.32165 5.29134 4.04183 5.01152 4.04183 4.66634ZM10.7085 4.66634C10.7085 4.32116 10.9883 4.04134 11.3335 4.04134H11.3418C11.687 4.04134 11.9668 4.32116 11.9668 4.66634C11.9668 5.01152 11.687 5.29134 11.3418 5.29134H11.3335C10.9883 5.29134 10.7085 5.01152 10.7085 4.66634ZM1.82165 6.82116C2.13421 6.5086 2.55814 6.33301 3.00016 6.33301C3.44219 6.33301 3.86611 6.5086 4.17867 6.82116C4.49123 7.13372 4.66683 7.55765 4.66683 7.99967C4.66683 8.4417 4.49123 8.86562 4.17867 9.17819C3.86611 9.49075 3.44219 9.66634 3.00016 9.66634C2.55814 9.66634 2.13421 9.49075 1.82165 9.17819C1.50909 8.86562 1.3335 8.4417 1.3335 7.99967C1.3335 7.55765 1.50909 7.13372 1.82165 6.82116ZM4.04183 11.333C4.04183 10.9878 4.32165 10.708 4.66683 10.708H4.67516C5.02034 10.708 5.30016 10.9878 5.30016 11.333C5.30016 11.6782 5.02034 11.958 4.67516 11.958H4.66683C4.32165 11.958 4.04183 11.6782 4.04183 11.333Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M6.00016 10.333C6.00016 9.97349 6.29161 9.68205 6.65112 9.68205H9.68254V6.65063C9.68254 6.29112 9.97398 5.99967 10.3335 5.99967C10.693 5.99967 10.9845 6.29112 10.9845 6.65063V9.68205H14.0159C14.3754 9.68205 14.6668 9.97349 14.6668 10.333C14.6668 10.6925 14.3754 10.984 14.0159 10.984H10.9845V14.0154C10.9845 14.3749 10.693 14.6663 10.3335 14.6663C9.97398 14.6663 9.68254 14.3749 9.68254 14.0154V10.984H6.65112C6.29161 10.984 6.00016 10.6925 6.00016 10.333Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Template.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M3.33333 4.69696C3.33333 3.02337 4.65989 1.66666 6.29629 1.66666H13.7037C15.3401 1.66666 16.6667 3.02337 16.6667 4.69696V12.4677C16.6667 13.2714 16.3545 14.0421 15.7988 14.6104L13.0265 17.4458C12.4708 18.0141 11.7172 18.3333 10.9314 18.3333H6.29629C4.65989 18.3333 3.33333 16.9766 3.33333 15.303V4.69696ZM6.29629 3.18181C5.47809 3.18181 4.81481 3.86016 4.81481 4.69696V15.303C4.81481 16.1398 5.47809 16.8182 6.29629 16.8182H10.7407V13.7879C10.7407 12.9511 11.404 12.2727 12.2222 12.2727H15.1852V4.69696C15.1852 3.86016 14.5219 3.18181 13.7037 3.18181H6.29629Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Templates.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 18 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M6 16.5C4.34315 16.5 3 15.1569 3 13.5H4.5C4.5 14.3284 5.17157 15 6 15V16.5Z\"\n      fill=\"currentColor\"\n    />\n    <path d=\"M4.5 12H3V9.75H4.5V12Z\" fill=\"currentColor\" />\n    <path d=\"M4.5 8.25H3V6H4.5V8.25Z\" fill=\"currentColor\" />\n    <path\n      d=\"M4.5 4.5H3C3 2.84315 4.34315 1.5 6 1.5H12C13.6569 1.5 15 2.84315 15 4.5H13.5C13.5 3.67157 12.8284 3 12 3H6C5.17157 3 4.5 3.67157 4.5 4.5Z\"\n      fill=\"currentColor\"\n    />\n    <path d=\"M13.5 6V9H15V6H13.5Z\" fill=\"currentColor\" />\n    <path d=\"M7.5 16.5H9.75V15H7.5V16.5Z\" fill=\"currentColor\" />\n    <path\n      d=\"M12.375 16.5C12.7892 16.5 13.125 16.1642 13.125 15.75V12.75H14.25C14.6642 12.75 15 12.4142 15 12C15 11.5858 14.6642 11.25 14.25 11.25H10.5C10.0858 11.25 9.75 11.5858 9.75 12C9.75 12.4142 10.0858 12.75 10.5 12.75H11.625V15.75C11.625 16.1642 11.9608 16.5 12.375 16.5Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/ThemeBlack.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <rect\n      x=\"1.5\"\n      y=\"3.5\"\n      width=\"21\"\n      height=\"17\"\n      rx=\"2.5\"\n      fill=\"#181A1F\"\n      stroke=\"#292B30\"\n    />\n    <path\n      d=\"M15.3841 15.9999C15.0096 15.9999 14.6723 15.9332 14.3721 15.7998C14.0745 15.6639 13.8385 15.4638 13.6641 15.1995C13.4922 14.9353 13.4062 14.6095 13.4062 14.2221C13.4062 13.8886 13.4678 13.6128 13.591 13.3948C13.7141 13.1767 13.8821 13.0023 14.0951 12.8715C14.308 12.7406 14.5478 12.6419 14.8146 12.5752C15.084 12.5059 15.3623 12.4559 15.6497 12.4251C15.996 12.3892 16.2769 12.3571 16.4924 12.3289C16.7079 12.2981 16.8644 12.2519 16.9618 12.1904C17.0619 12.1262 17.1119 12.0275 17.1119 11.8941V11.871C17.1119 11.5811 17.026 11.3566 16.8541 11.1976C16.6822 11.0385 16.4347 10.959 16.1114 10.959C15.7702 10.959 15.4996 11.0334 15.2995 11.1822C15.102 11.331 14.9686 11.5067 14.8993 11.7093L13.5987 11.5246C13.7013 11.1655 13.8706 10.8653 14.1066 10.6242C14.3426 10.3805 14.6312 10.1983 14.9724 10.0778C15.3136 9.95463 15.6907 9.89307 16.1037 9.89307C16.3885 9.89307 16.672 9.92642 16.9542 9.99312C17.2363 10.0598 17.4942 10.1701 17.7276 10.324C17.9611 10.4754 18.1483 10.6819 18.2894 10.9436C18.4331 11.2053 18.5049 11.5323 18.5049 11.9248V15.8806H17.1658V15.0687H17.1196C17.035 15.2329 16.9157 15.3868 16.7617 15.5305C16.6104 15.6716 16.4193 15.7857 16.1884 15.8729C15.9601 15.9576 15.692 15.9999 15.3841 15.9999ZM15.7459 14.9763C16.0255 14.9763 16.2679 14.9212 16.4731 14.8109C16.6784 14.698 16.8361 14.5492 16.9465 14.3645C17.0593 14.1798 17.1158 13.9784 17.1158 13.7604V13.0639C17.0722 13.0998 16.9978 13.1331 16.8926 13.1639C16.79 13.1947 16.6745 13.2216 16.5463 13.2447C16.418 13.2678 16.291 13.2883 16.1653 13.3063C16.0396 13.3242 15.9306 13.3396 15.8382 13.3525C15.6304 13.3807 15.4444 13.4269 15.2803 13.491C15.1161 13.5551 14.9865 13.6449 14.8916 13.7604C14.7967 13.8732 14.7492 14.0195 14.7492 14.199C14.7492 14.4556 14.8429 14.6493 15.0301 14.7801C15.2174 14.9109 15.456 14.9763 15.7459 14.9763Z\"\n      fill=\"#F9FBFE\"\n    />\n    <path\n      d=\"M7.02383 15.8808H5.5L8.27444 8H10.0369L12.8151 15.8808H11.2913L9.18643 9.61618H9.12486L7.02383 15.8808ZM7.07385 12.7908H11.2297V13.9375H7.07385V12.7908Z\"\n      fill=\"#F9FBFE\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/ThemeDark.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <rect\n      x=\"1.5\"\n      y=\"3.5\"\n      width=\"21\"\n      height=\"17\"\n      rx=\"2.5\"\n      fill=\"#121826\"\n      stroke=\"#252E3C\"\n    />\n    <path\n      d=\"M15.3841 15.9999C15.0096 15.9999 14.6723 15.9332 14.3721 15.7998C14.0745 15.6639 13.8385 15.4638 13.6641 15.1995C13.4922 14.9353 13.4062 14.6095 13.4062 14.2221C13.4062 13.8886 13.4678 13.6128 13.591 13.3948C13.7141 13.1767 13.8821 13.0023 14.0951 12.8715C14.308 12.7406 14.5478 12.6419 14.8146 12.5752C15.084 12.5059 15.3623 12.4559 15.6497 12.4251C15.996 12.3892 16.2769 12.3571 16.4924 12.3289C16.7079 12.2981 16.8644 12.2519 16.9618 12.1904C17.0619 12.1262 17.1119 12.0275 17.1119 11.8941V11.871C17.1119 11.5811 17.026 11.3566 16.8541 11.1976C16.6822 11.0385 16.4347 10.959 16.1114 10.959C15.7702 10.959 15.4996 11.0334 15.2995 11.1822C15.102 11.331 14.9686 11.5067 14.8993 11.7093L13.5987 11.5246C13.7013 11.1655 13.8706 10.8653 14.1066 10.6242C14.3426 10.3805 14.6312 10.1983 14.9724 10.0778C15.3136 9.95463 15.6907 9.89307 16.1037 9.89307C16.3885 9.89307 16.672 9.92642 16.9542 9.99312C17.2363 10.0598 17.4942 10.1701 17.7276 10.324C17.9611 10.4754 18.1483 10.6819 18.2894 10.9436C18.4331 11.2053 18.5049 11.5323 18.5049 11.9248V15.8806H17.1658V15.0687H17.1196C17.035 15.2329 16.9157 15.3868 16.7617 15.5305C16.6104 15.6716 16.4193 15.7857 16.1884 15.8729C15.9601 15.9576 15.692 15.9999 15.3841 15.9999ZM15.7459 14.9763C16.0255 14.9763 16.2679 14.9212 16.4731 14.8109C16.6784 14.698 16.8361 14.5492 16.9465 14.3645C17.0593 14.1798 17.1158 13.9784 17.1158 13.7604V13.0639C17.0722 13.0998 16.9978 13.1331 16.8926 13.1639C16.79 13.1947 16.6745 13.2216 16.5463 13.2447C16.418 13.2678 16.291 13.2883 16.1653 13.3063C16.0396 13.3242 15.9306 13.3396 15.8382 13.3525C15.6304 13.3807 15.4444 13.4269 15.2803 13.491C15.1161 13.5551 14.9865 13.6449 14.8916 13.7604C14.7967 13.8732 14.7492 14.0195 14.7492 14.199C14.7492 14.4556 14.8429 14.6493 15.0301 14.7801C15.2174 14.9109 15.456 14.9763 15.7459 14.9763Z\"\n      fill=\"#F0F3F8\"\n    />\n    <path\n      d=\"M7.02383 15.8808H5.5L8.27444 8H10.0369L12.8151 15.8808H11.2913L9.18643 9.61618H9.12486L7.02383 15.8808ZM7.07385 12.7908H11.2297V13.9375H7.07385V12.7908Z\"\n      fill=\"#F0F3F8\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/ThemeLight.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <rect\n      x=\"1.5\"\n      y=\"3.5\"\n      width=\"21\"\n      height=\"17\"\n      rx=\"2.5\"\n      fill=\"white\"\n      stroke=\"#D8DCE2\"\n    />\n    <path\n      d=\"M15.3841 15.9999C15.0096 15.9999 14.6723 15.9332 14.3721 15.7998C14.0745 15.6639 13.8385 15.4638 13.6641 15.1995C13.4922 14.9353 13.4062 14.6095 13.4062 14.2221C13.4062 13.8886 13.4678 13.6128 13.591 13.3948C13.7141 13.1767 13.8821 13.0023 14.0951 12.8715C14.308 12.7406 14.5478 12.6419 14.8146 12.5752C15.084 12.5059 15.3623 12.4559 15.6497 12.4251C15.996 12.3892 16.2769 12.3571 16.4924 12.3289C16.7079 12.2981 16.8644 12.2519 16.9618 12.1904C17.0619 12.1262 17.1119 12.0275 17.1119 11.8941V11.871C17.1119 11.5811 17.026 11.3566 16.8541 11.1976C16.6822 11.0385 16.4347 10.959 16.1114 10.959C15.7702 10.959 15.4996 11.0334 15.2995 11.1822C15.102 11.331 14.9686 11.5067 14.8993 11.7093L13.5987 11.5246C13.7013 11.1655 13.8706 10.8653 14.1066 10.6242C14.3426 10.3805 14.6312 10.1983 14.9724 10.0778C15.3136 9.95463 15.6907 9.89307 16.1037 9.89307C16.3885 9.89307 16.672 9.92642 16.9542 9.99312C17.2363 10.0598 17.4942 10.1701 17.7276 10.324C17.9611 10.4754 18.1483 10.6819 18.2894 10.9436C18.4331 11.2053 18.5049 11.5323 18.5049 11.9248V15.8806H17.1658V15.0687H17.1196C17.035 15.2329 16.9157 15.3868 16.7617 15.5305C16.6104 15.6716 16.4193 15.7857 16.1884 15.8729C15.9601 15.9576 15.692 15.9999 15.3841 15.9999ZM15.7459 14.9763C16.0255 14.9763 16.2679 14.9212 16.4731 14.8109C16.6784 14.698 16.8361 14.5492 16.9465 14.3645C17.0593 14.1798 17.1158 13.9784 17.1158 13.7604V13.0639C17.0722 13.0998 16.9978 13.1331 16.8926 13.1639C16.79 13.1947 16.6745 13.2216 16.5463 13.2447C16.418 13.2678 16.291 13.2883 16.1653 13.3063C16.0396 13.3242 15.9306 13.3396 15.8382 13.3525C15.6304 13.3807 15.4444 13.4269 15.2803 13.491C15.1161 13.5551 14.9865 13.6449 14.8916 13.7604C14.7967 13.8732 14.7492 14.0195 14.7492 14.199C14.7492 14.4556 14.8429 14.6493 15.0301 14.7801C15.2174 14.9109 15.456 14.9763 15.7459 14.9763Z\"\n      fill=\"#040711\"\n    />\n    <path\n      d=\"M7.02383 15.8808H5.5L8.27444 8H10.0369L12.8151 15.8808H11.2913L9.18643 9.61618H9.12486L7.02383 15.8808ZM7.07385 12.7908H11.2297V13.9375H7.07385V12.7908Z\"\n      fill=\"#040711\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/TooltipTailBottom.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg\n    width=\"12\"\n    height=\"5\"\n    viewBox=\"0 0 12 5\"\n    fill=\"none\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n  >\n    <path\n      d=\"M0 0H11.3137L7.07107 4.24264C6.29002 5.02369 5.02369 5.02369 4.24264 4.24264L0 0Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/TooltipTailLeft.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg\n    width=\"5\"\n    height=\"13\"\n    viewBox=\"0 0 4 13\"\n    fill=\"none\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n  >\n    <path\n      d=\"M4.82843 12.1569L4.82843 0.843144L0.58579 5.08578C-0.195259 5.86683 -0.195259 7.13316 0.58579 7.91421L4.82843 12.1569Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/TooltipTailRight.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg\n    width=\"6\"\n    height=\"13\"\n    viewBox=\"0 0 6 13\"\n    fill=\"none\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n  >\n    <path\n      d=\"M0.82843 12.1569L0.82843 0.843144L5.07107 5.08578C5.85212 5.86683 5.85212 7.13316 5.07107 7.91421L0.82843 12.1569Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/TooltipTailTop.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg\n    width=\"12\"\n    height=\"5\"\n    viewBox=\"0 0 12 4\"\n    fill=\"none\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n  >\n    <path\n      d=\"M0 4.82843H11.3137L7.07107 0.58579C6.29002 -0.195259 5.02369 -0.195259 4.24264 0.58579L0 4.82843Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/TrashCan.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M4.32677 2.91663C4.77678 1.88674 5.804 1.16663 7.00055 1.16663C8.1971 1.16663 9.22432 1.88674 9.67434 2.91663H12.25C12.5722 2.91663 12.8333 3.17779 12.8333 3.49996C12.8333 3.82213 12.5722 4.08329 12.25 4.08329H11.6291L11.191 10.6552C11.1092 11.8809 10.0912 12.8333 8.86279 12.8333H5.13722C3.90881 12.8333 2.89077 11.8809 2.80906 10.6552L2.37093 4.08329H1.75001C1.42784 4.08329 1.16667 3.82213 1.16667 3.49996C1.16667 3.17779 1.42784 2.91663 1.75001 2.91663H4.32677ZM5.69603 2.91663C6.01665 2.55853 6.4826 2.33329 7.00055 2.33329C7.51851 2.33329 7.98445 2.55853 8.30507 2.91663H5.69603Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Unlike.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M9.41041 7.67288C9.41534 7.65193 9.42161 7.63105 9.42927 7.61033C9.75505 6.72966 9.93034 5.7077 9.92934 4.6586C9.92934 3.89731 9.83922 3.17378 9.67703 2.51612C9.67545 2.5097 9.67399 2.50326 9.67265 2.49681C9.66307 2.45837 9.65326 2.4202 9.64319 2.38227H9.6029C9.30628 2.38244 9.01157 2.33462 8.73011 2.24065L6.82016 1.60028C6.53863 1.50669 6.24361 1.45866 5.94737 1.45866H3.48417C3.10513 1.45866 2.73773 1.61075 2.49976 1.90753C1.446 3.21892 0.87247 4.85364 0.875007 6.53852C0.875007 6.80637 0.889113 7.0699 0.916715 7.32975C0.983569 7.95842 1.54723 8.38574 2.17652 8.38574H4.09384C4.47289 8.38574 4.70166 8.83154 4.53851 9.17512C4.25454 9.77547 4.09507 10.4472 4.09507 11.1566C4.09507 11.524 4.24046 11.8764 4.49927 12.1362C4.75807 12.396 5.10909 12.542 5.47509 12.542C5.59709 12.542 5.7141 12.4933 5.80037 12.4067C5.88663 12.3201 5.9351 12.2027 5.9351 12.0802V11.6904C5.93504 11.3378 6.00207 10.9883 6.1326 10.6609C6.31844 10.1936 6.70301 9.84135 7.14645 9.60491C7.82745 9.24028 8.42512 8.73695 8.90123 8.12713C9.04356 7.94522 9.21639 7.78845 9.41041 7.67288Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M10.7031 7.085C10.6152 7.43592 10.8729 7.79199 11.2378 7.79199H11.4586C11.9633 7.79199 12.436 7.54152 12.6214 7.11357C12.9462 6.36051 13.125 5.54054 13.125 4.68187C13.1256 4.02482 13.0192 3.37134 12.8093 2.74282C12.6523 2.27284 12.153 1.98643 11.6143 1.98643H10.9298C10.7834 1.98643 10.6775 2.12473 10.7122 2.26558C10.7139 2.2724 10.7154 2.27923 10.7168 2.28607C10.8967 3.02269 10.9945 3.82279 10.9946 4.65787C10.9953 5.48955 10.8964 6.31374 10.7031 7.085Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Walk.tsx",
    "content": "import React from 'react';\nimport IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      d=\"M5.83704 18.2395L8.24162 15.3795C8.47328 15.0999 8.51323 15.036 8.60909 14.7405L8.78484 14.1972L7.49868 12.5836L7.10723 14.389L4.73461 17.201C3.98367 18.1037 5.22191 18.9824 5.83704 18.2395ZM11.6847 17.9998C12.18 18.9984 13.6499 18.3833 13.1147 17.2968L11.485 13.9975C11.3572 13.7498 11.1894 13.4782 11.0376 13.2625L10.0071 11.8006L10.079 11.585C10.3506 10.7541 10.4545 10.2589 10.5184 9.436L10.6782 7.12728C10.758 6.03284 10.111 5.20202 8.98456 5.20202C8.14575 5.20202 7.57057 5.62542 6.79567 6.38434L5.57341 7.59062C5.17398 7.98207 5.04616 8.30161 5.00622 8.82088L4.86242 10.7062C4.82248 11.1855 5.08611 11.521 5.52548 11.537C5.97284 11.5689 6.24445 11.3054 6.2844 10.7941L6.46814 8.72501L7.05131 8.19776C7.267 8.00603 7.53862 8.13385 7.53063 8.34955L7.40281 10.099C7.33091 10.9858 7.53862 11.4092 8.14575 12.1681L9.76744 14.1972C9.92722 14.4049 9.9432 14.4848 10.0151 14.6126L11.6847 17.9998ZM14.4647 8.31759H12.6194L11.461 7.03142L11.3252 8.95668L11.7886 9.42005C12.0442 9.67568 12.2679 9.75552 12.7472 9.75552H14.4647C14.9441 9.75552 15.2636 9.48394 15.2636 9.03657C15.2636 8.60518 14.9361 8.31759 14.4647 8.31759ZM9.87928 4.48304C10.8379 4.48304 11.6128 3.70016 11.6128 2.74153C11.6128 1.7749 10.8379 1 9.87928 1C8.91266 1 8.13777 1.7749 8.13777 2.74153C8.13777 3.70016 8.91266 4.48304 9.87928 4.48304Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Wallet.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M4.33333 2C3.04467 2 2 3.04467 2 4.33333V11.3333C2 12.8061 3.19391 14 4.66667 14H12C13.1046 14 14 13.1046 14 12V7.33333C14 6.22876 13.1046 5.33333 12 5.33333H11.3333V3.60784C11.3333 2.71986 10.6135 2 9.72549 2H4.33333ZM10 5.33333V3.60784C10 3.45624 9.8771 3.33333 9.72549 3.33333H4.33333C3.78105 3.33333 3.33333 3.78105 3.33333 4.33333C3.33333 4.88562 3.78105 5.33333 4.33333 5.33333H10ZM10.3333 10.5C10.7936 10.5 11.1667 10.1269 11.1667 9.66667C11.1667 9.20643 10.7936 8.83333 10.3333 8.83333C9.8731 8.83333 9.5 9.20643 9.5 9.66667C9.5 10.1269 9.8731 10.5 10.3333 10.5Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/WarningSign.tsx",
    "content": "import IconWrapper from './Wrapper';\n\nconst RawIcon = (\n  <svg viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path\n      fillRule=\"evenodd\"\n      clipRule=\"evenodd\"\n      d=\"M5.48777 2.01666C6.16305 0.85903 7.83571 0.859027 8.51099 2.01666L12.6048 9.03459C13.2853 10.2012 12.4438 11.6664 11.0932 11.6664H2.90559C1.55496 11.6664 0.713431 10.2012 1.39398 9.03459L5.48777 2.01666ZM6.99996 4.66638C7.32213 4.66638 7.58329 4.92755 7.58329 5.24972V6.99972C7.58329 7.32188 7.32213 7.58305 6.99996 7.58305C6.67779 7.58305 6.41663 7.32188 6.41663 6.99972V5.24972C6.41663 4.92755 6.67779 4.66638 6.99996 4.66638ZM6.27079 8.74972C6.27079 8.34701 6.59725 8.02055 6.99996 8.02055C7.40267 8.02055 7.72913 8.34701 7.72913 8.74972C7.72913 9.15242 7.40267 9.47888 6.99996 9.47888C6.59725 9.47888 6.27079 9.15242 6.27079 8.74972Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nexport default IconWrapper(RawIcon);\n"
  },
  {
    "path": "client/src/icons/Wrapper.tsx",
    "content": "import { ReactNode } from 'react';\n\nconst IconWrapper =\n  (icon: ReactNode) =>\n  //eslint-disable-next-line\n  ({ sizeClassName, className }: { raw?: boolean; sizeClassName?: string; className?: string }) =>\n    sizeClassName ? (\n      <span\n        className={`${sizeClassName} inline-block flex-shrink-0 flex-grow-0 ${\n          className || ''\n        }`}\n      >\n        {icon}\n      </span>\n    ) : (\n      <>{icon}</>\n    );\n\nexport default IconWrapper;\n"
  },
  {
    "path": "client/src/icons/index.ts",
    "content": "export { default as ArrowHistoryIcon } from './ArrowHistory';\nexport { default as ArrowLeftIcon } from './ArrowLeft';\nexport { default as ArrowOutIcon } from './ArrowOut';\nexport { default as ArrowTriangleBottomIcon } from './ArrowTriangleBottom';\nexport { default as BloopLogo } from './BloopLogo';\nexport { default as BranchIcon } from './Branch';\nexport { default as BroomIcon } from './Broom';\nexport { default as BugIcon } from './Bug';\nexport { default as ChatBubblesIcon } from './ChatBubbles';\nexport { default as CheckIcon } from './Check';\nexport { default as CheckListIcon } from './CheckList';\nexport { default as CheckmarkInSquareIcon } from './CheckmarkInSquare';\nexport { default as ChevronDownIcon } from './ChevronDown';\nexport { default as ChevronRightIcon } from './ChevronRight';\nexport { default as ChevronUpIcon } from './ChevronUp';\nexport { default as Clipboard } from './Clipboard';\nexport { default as CloseSignIcon } from './CloseSign';\nexport { default as CloseSignInCircleIcon } from './CloseSignInCircle';\nexport { default as CodeIcon } from './Code';\nexport { default as CodeLineWithSparkleIcon } from './CodeLineWithSparkle';\nexport { default as CodeStudioIcon } from './CodeStudio';\nexport { default as CogIcon } from './Cog';\nexport { default as ColorSwitchIcon } from './ColorSwitch';\nexport { default as CopyTextIcon } from './CopyText';\nexport { default as DateTimeCalendarIcon } from './DateTimeCalendar';\nexport { default as DefIcon } from './Def';\nexport { default as DocumentsIcon } from './Documents';\nexport { default as DoorOutIcon } from './DoorOut';\nexport { default as DoubleChevronInIcon } from './DoubleChevronIn';\nexport { default as DoubleChevronOutIcon } from './DoubleChevronOut';\nexport { default as EyeCutIcon } from './EyeCut';\nexport { default as FolderIcon } from './Folder';\nexport { default as FileIcon } from './File';\nexport { default as FileWithSparksIcon } from './FileWithSparks';\nexport { default as GitHubLogo } from './GitHubIcon';\nexport { default as GlobeIcon } from './Globe';\nexport { default as HardDriveIcon } from './HardDrive';\nexport { default as InfoBadgeIcon } from './InfoBadge';\nexport { default as KLetterIcon } from './KLetter';\nexport { default as LikeIcon } from './Like';\nexport { default as LinkChainIcon } from './LinkChain';\nexport { default as LiteLoaderIcon } from './LiteLoader';\nexport { default as LogoFull } from './LogoFull';\nexport { default as MacintoshIcon } from './Macintosh';\nexport { default as MagazineIcon } from './Magazine';\nexport { default as MagnifyToolIcon } from './MagnifyTool';\nexport { default as MailIcon } from './MailIcon';\nexport { default as MoreHorizontalIcon } from './MoreHorizontal';\nexport { default as PencilIcon } from './Pencil';\nexport { default as PersonIcon } from './Person';\nexport { default as PlusSignIcon } from './PlusSign';\nexport { default as PromptIcon } from './Prompt';\nexport { default as RangeIcon } from './Range';\nexport { default as RefIcon } from './Ref';\nexport { default as RefreshIcon } from './Refresh';\nexport { default as RegexIcon } from './Regex';\nexport { default as RegexSearchIcon } from './RegexSearch';\nexport { default as RepositoryIcon } from './Repository';\nexport { default as RunIcon } from './Run';\nexport { default as ShapesIcon } from './Shapes';\nexport { default as SplitViewIcon } from './SplitView';\nexport { default as StudioCloseSignIcon } from './StudioCloseSign';\nexport { default as StudioPlusSignIcon } from './StudioPlusSign';\nexport { default as TemplateIcon } from './Template';\nexport { default as TemplatesIcon } from './Templates';\nexport { default as ThemeBlackIcon } from './ThemeBlack';\nexport { default as ThemeDarkIcon } from './ThemeDark';\nexport { default as ThemeLightIcon } from './ThemeLight';\nexport { default as TooltipTailBottom } from './TooltipTailBottom';\nexport { default as TooltipTailLeft } from './TooltipTailLeft';\nexport { default as TooltipTailRight } from './TooltipTailRight';\nexport { default as TooltipTailTop } from './TooltipTailTop';\nexport { default as TrashCanIcon } from './TrashCan';\nexport { default as UnlikeIcon } from './Unlike';\nexport { default as WalkIcon } from './Walk';\nexport { default as WalletIcon } from './Wallet';\nexport { default as WarningSignIcon } from './WarningSign';\n"
  },
  {
    "path": "client/src/index.css",
    "content": "@import \"./file-icons.css\";\n\n@import \"themes/default-dark.css\";\n@import \"themes/default-light.css\";\n\n@font-face {\n  font-family: Menlo;\n  font-weight: normal;\n  font-style: normal;\n  src: url(\"fonts/Menlo-Regular.ttf\");\n}\n\n@font-face {\n  font-family: Inter;\n  font-weight: 100;\n  font-style: normal;\n  src: url(\"fonts/Inter/Inter-Thin.ttf\");\n}\n\n@font-face {\n  font-family: Inter;\n  font-weight: 200;\n  font-style: normal;\n  src: url(\"fonts/Inter/Inter-ExtraLight.ttf\");\n}\n\n@font-face {\n  font-family: Inter;\n  font-weight: 300;\n  font-style: normal;\n  src: url(\"fonts/Inter/Inter-Light.ttf\");\n}\n\n@font-face {\n  font-family: Inter;\n  font-weight: 400;\n  font-style: normal;\n  src: url(\"fonts/Inter/Inter-Regular.ttf\");\n}\n\n@font-face {\n  font-family: Inter;\n  font-weight: 500;\n  font-style: normal;\n  src: url(\"fonts/Inter/Inter-Medium.ttf\");\n}\n\n@font-face {\n  font-family: Inter;\n  font-weight: 600;\n  font-style: normal;\n  src: url(\"fonts/Inter/Inter-SemiBold.ttf\");\n}\n\n@font-face {\n  font-family: Inter;\n  font-weight: 700;\n  font-style: normal;\n  src: url(\"fonts/Inter/Inter-Bold.ttf\");\n}\n\n@font-face {\n  font-family: Inter;\n  font-weight: 800;\n  font-style: normal;\n  src: url(\"fonts/Inter/Inter-ExtraBold.ttf\");\n}\n\n@font-face {\n  font-family: Inter;\n  font-weight: 900;\n  font-style: normal;\n  src: url(\"fonts/Inter/Inter-Black.ttf\");\n}\n\n@font-face {\n  font-family: \"Fira Code\";\n  font-weight: normal;\n  font-style: normal;\n  src: url(\"fonts/FiraCode/FiraCode-Regular.ttf\");\n}\n\n@font-face {\n  font-family: \"Fira Code\";\n  font-weight: 300;\n  font-style: normal;\n  src: url(\"fonts/FiraCode/FiraCode-Light.ttf\");\n}\n\n@font-face {\n  font-family: \"Fira Code\";\n  font-weight: 400;\n  font-style: normal;\n  src: url(\"fonts/FiraCode/FiraCode-Regular.ttf\");\n}\n\n@font-face {\n  font-family: \"Fira Code\";\n  font-weight: 500;\n  font-style: normal;\n  src: url(\"fonts/FiraCode/FiraCode-Medium.ttf\");\n}\n\n@font-face {\n  font-family: \"Fira Code\";\n  font-weight: 600;\n  font-style: normal;\n  src: url(\"fonts/FiraCode/FiraCode-SemiBold.ttf\");\n}\n\n@font-face {\n  font-family: \"Fira Code\";\n  font-weight: 700;\n  font-style: normal;\n  src: url(\"fonts/FiraCode/FiraCode-Bold.ttf\");\n}\n\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n\n@layer base {\n  body {\n    --default-black-bg-sub: 11, 11, 15; /* #0B0B0F */\n    --default-black-bg-sub-hover: 29, 29, 39; /* #1D1D27 */\n    --default-black-bg-base: 24, 26, 31; /* #181A1F */\n    --default-black-bg-base-hover: 38, 39, 46; /* #26272E */\n    --default-black-bg-selected: 94, 135, 203; /* #5E87CB 12% */\n    --default-black-bg-shade: 31, 31, 42; /* #1F1F2A */\n    --default-black-bg-shade-hover: 37, 37, 52; /* #252534 */\n    --default-black-bg-border: 41, 43, 48; /* #292B30 */\n    --default-black-bg-border-hover: 60, 63, 72; /* #3C3F48 */\n    --default-black-bg-border-selected: 94, 135, 203; /* #5E87CB */\n    --default-black-bg-divider: 29, 30, 34; /* #1D1E22 */\n    --default-black-bg-contrast: 240, 243, 248; /* #F0F3F8 */\n    --default-black-bg-contrast-hover: 222, 227, 234; /* #DEE3EA */\n\n    --default-black-label-title: 249, 251, 254; /* #F9FBFE */\n    --default-black-label-base: 166, 176, 187; /* #A6B0BB */\n    --default-black-label-muted: 111, 117, 123; /* #6F757B */\n    --default-black-label-faint: 62, 64, 70; /* #3E4046 */\n    --default-black-label-link: 70, 128, 224; /* #4680E0 */\n    --default-black-label-contrast: 11, 11, 15; /* #0B0B0F */\n    --default-black-label-control: 254, 254, 255; /* #FEFEFF */\n\n    --default-black-sub-surface: 11, 11, 15; /* #0B0B0F, 75% */\n    --default-black-shade-surface: 31, 31, 42; /* #1F1F2A, 75% */\n    --default-black-base-surface: 24, 26, 31; /* #181A1F, 75% */\n\n    --default-black-brand-default: 91, 110, 221; /* #5B6EDD */\n    --default-black-brand-default-hover: 99, 121, 249; /* #6379F9 */\n    --default-black-brand-default-subtitle: 39, 45, 80; /* #272D50 */\n    --default-black-brand-studio: 199, 54, 115; /* #C73673 */\n    --default-black-brand-studio-hover: 240, 54, 132; /* #F03684 */\n    --default-black-brand-studio-subtle: 68, 25, 43; /* #44192B */\n\n    --default-black-green: 112, 161, 102; /* #70A166 */\n    --default-black-green-subtle: 28, 44, 28; /* #1C2C1C */\n    --default-black-green-subtle-hover: 35, 56, 35; /* #233823 */\n    --default-black-red: 197, 92, 87; /* #C55C57 */\n    --default-black-red-subtle: 51, 26, 26; /* #331A1A */\n    --default-black-red-subtle-hover: 68, 34, 34; /* #442222 */\n    --default-black-yellow: 234, 184, 92; /* #EAB85C */\n    --default-black-yellow-subtle: 45, 29, 8; /* #2D1D08 */\n    --default-black-yellow-subtle-hover: 65, 42, 12; /* #412A0C */\n    --default-black-blue: 65, 111, 229; /* #416FE5 */\n    --default-black-blue-subtle: 23, 35, 57; /* #172339 */\n    --default-black-blue-subtle-hover: 29, 44, 71; /* #1D2C47 */\n\n    --default-black-line-select: 25, 50, 82; /* #193252 */\n\n\n    --default-dark-bg-sub: 17, 19, 28; /* #11131C */\n    --default-dark-bg-sub-hover: 21, 24, 35; /* #151823 */\n    --default-dark-bg-base: 18, 24, 38; /* #121826 */\n    --default-dark-bg-base-hover: 26, 33, 49; /* #1A2131 */\n    --default-dark-bg-selected: 94, 135, 203; /* #5E87CB 12% */\n    --default-dark-bg-shade: 28, 31, 45; /* #1C1F2D */\n    --default-dark-bg-shade-hover: 34, 38, 55; /* #222637 */\n    --default-dark-bg-border: 37, 46, 60; /* #252E3C */\n    --default-dark-bg-border-hover: 46, 56, 73; /* #2E3849 */\n    --default-dark-bg-border-selected: 94, 135, 203; /* #5E87CB */\n    --default-dark-bg-divider: 25, 32, 44; /* #19202C */\n    --default-dark-bg-contrast: 240, 243, 248; /* #F0F3F8 */\n    --default-dark-bg-contrast-hover: 229, 233, 239; /* #E5E9EF */\n\n    --default-dark-label-title: 240, 243, 248; /* #F0F3F8 */\n    --default-dark-label-base: 157, 163, 174; /* #9DA3AE */\n    --default-dark-label-muted: 108, 114, 127; /* #6C727F */\n    --default-dark-label-faint: 57, 65, 80; /* #394150 */\n    --default-dark-label-link: 81, 118, 181; /* #5176B5 */\n    --default-dark-label-contrast: 4, 7, 17; /* #040711 */\n    --default-dark-label-control: 254, 254, 255; /* #FEFEFF */\n\n    --default-dark-sub-surface: 17, 19, 28; /* #11131C, 75% */\n    --default-dark-shade-surface: 28, 31, 45; /* #1C1F2D, 75% */\n    --default-dark-base-surface: 18, 24, 38; /* #121826, 75% */\n\n    --default-dark-brand-default: 91, 110, 221; /* #5B6EDD */\n    --default-dark-brand-default-hover: 99, 121, 249; /* #6379F9 */\n    --default-dark-brand-default-subtitle: 39, 45, 80; /* #272D50 */\n    --default-dark-brand-studio: 199, 54, 115; /* #C73673 */\n    --default-dark-brand-studio-hover: 240, 54, 132; /* #F03684 */\n    --default-dark-brand-studio-subtle: 68, 25, 43; /* #44192B */\n\n    --default-dark-green: 112, 161, 102; /* #70A166 */\n    --default-dark-green-subtle: 28, 44, 28; /* #1C2C1C */\n    --default-dark-green-subtle-hover: 35, 56, 35; /* #233823 */\n    --default-dark-red: 197, 92, 87; /* #C55C57 */\n    --default-dark-red-subtle: 51, 26, 26; /* #331A1A */\n    --default-dark-red-subtle-hover: 68, 34, 34; /* #442222 */\n    --default-dark-yellow: 234, 184, 92; /* #EAB85C */\n    --default-dark-yellow-subtle: 45, 29, 8; /* #2D1D08 */\n    --default-dark-yellow-subtle-hover: 65, 42, 12; /* #412A0C */\n    --default-dark-blue: 65, 111, 229; /* #416FE5 */\n    --default-dark-blue-subtle: 23, 35, 57; /* #172339 */\n    --default-dark-blue-subtle-hover: 29, 44, 71; /* #1D2C47 */\n\n    --default-dark-line-select: 25, 50, 82; /* #193252 */\n\n\n    --default-light-bg-sub: 250, 250, 250; /* #FAFAFA */\n    --default-light-bg-sub-hover: 241, 241, 242; /* #F1F1F2 */\n    --default-light-bg-base: 255, 255, 255; /* #FFFFFF */\n    --default-light-bg-base-hover: 234, 237, 243; /* #EAEDF3 */\n    --default-light-bg-selected: 94, 149, 241; /* #5E95F1 12% */\n    --default-light-bg-shade: 241, 243, 248; /* #F1F3F8 */\n    --default-light-bg-shade-hover: 231, 234, 242; /* #E7EAF2 */\n    --default-light-bg-border: 216, 220, 226; /* #D8DCE2 */\n    --default-light-bg-border-hover: 186, 190, 197; /* #BABEC5 */\n    --default-light-bg-border-selected: 94, 149, 241; /* #5E95F1 */\n    --default-light-bg-divider: 234, 236, 241; /* #EAECF1 */\n    --default-light-bg-contrast: 39, 39, 41; /* #272729 */\n    --default-light-bg-contrast-hover: 25, 25, 28; /* #19191C */\n\n    --default-light-label-title: 4, 7, 17; /* #040711 */\n    --default-light-label-base: 56, 64, 77; /* #38404D */\n    --default-light-label-muted: 108, 114, 127; /* #6C727F */\n    --default-light-label-faint: 157, 163, 174; /* #9DA3AE */\n    --default-light-label-link: 70, 128, 224; /* #4680E0 */\n    --default-light-label-contrast: 255, 255, 255; /* #FFFFFF */\n    --default-light-label-control: 255, 255, 255; /* #FFFFFF */\n\n    --default-light-sub-surface: 250, 250, 250; /* #FAFAFA, 75% */\n    --default-light-shade-surface: 247, 248, 251; /* #F7F8FB, 75% */\n    --default-light-base-surface: 255, 255, 255; /* #FFFFFF, 75% */\n\n    --default-light-brand-default: 91, 110, 221; /* #5B6EDD */\n    --default-light-brand-default-hover: 74, 93, 204; /* #4A5DCC */\n    --default-light-brand-default-subtitle: 224, 229, 252; /* #E0E5FC */\n    --default-light-brand-studio: 199, 54, 115; /* #C73673 */\n    --default-light-brand-studio-hover: 181, 41, 100; /* #B52964 */\n    --default-light-brand-studio-subtle: 255, 207, 227; /* #FFCFE3 */\n\n    --default-light-green: 120, 194, 106; /* #78C26A */\n    --default-light-green-subtle: 226, 245, 221; /* #E2F5DD */\n    --default-light-green-subtle-hover: 216, 236, 210; /* #D8ECD2 */\n    --default-light-red: 221, 79, 72; /* #DD4F48 */\n    --default-light-red-subtle: 248, 224, 220; /* #F8E0DC */\n    --default-light-red-subtle-hover: 238, 194, 186; /* #EEC2BA */\n    --default-light-yellow: 237, 171, 50; /* #EDAB32 */\n    --default-light-yellow-subtle: 246, 237, 208; /* #F6EDD0 */\n    --default-light-yellow-subtle-hover: 240, 231, 199; /* #F0E7C7 */\n    --default-light-blue: 49, 104, 246; /* #3168F6 */\n    --default-light-blue-subtle: 224, 234, 246; /* #E0EAF6 */\n    --default-light-blue-subtle-hover: 221, 233, 247; /* #DDE9F7 */\n\n    --default-light-line-select: 190, 218, 247; /* #BEDAF7 */\n\n    --default-dark-shadow-rings-blue: 0px 0px 0px 2px rgba(194, 197, 255, 0.16);\n    --default-dark-shadow-rings-gray: 0px 0px 0px 2px rgba(49, 50, 51, 0.16);\n    --default-dark-shadow-float: 0px 4px 32px 0px rgba(0, 0, 0, 0.35);\n    --default-dark-shadow-high: 0px 8px 24px 0px rgba(0,0,0,0.35);\n    --default-dark-shadow-medium: 0px 8px 24px 0px rgba(0,0,0,0.35);\n    --default-dark-shadow-low: 0px 8px 24px 0px rgba(0,0,0,0.35);\n\n    --default-dark-onboarding-chats-img: url(\"../public/chatsImage-dark.png\");\n    --default-dark-bloop-head-img: url(\"../public/bloopHeadMascot.png\");\n\n    --default-light-shadow-rings-blue: 0px 0px 0px 2px rgba(194, 197, 255, 0.08);\n    --default-light-shadow-rings-gray: 0px 0px 0px 2px rgba(49, 50, 51, 0.08);\n    --default-light-shadow-float: 0px 4px 32px 0px rgba(0,0,0,0.16);\n    --default-light-shadow-high: 0px 8px 24px 0px rgba(0,0,0,0.08);\n    --default-light-shadow-medium: 0px 1px 8px 0px rgba(0,0,0,0.12);\n    --default-light-shadow-low: 0px 1px 2px 0px rgba(0,0,0,0.08);\n\n    --default-light-onboarding-chats-img: url(\"../public/chatsImage-light.png\");\n    --default-light-bloop-head-img: url(\"../public/bloopHeadMascotLight.png\");\n  }\n  body[data-theme=\"dark\"], body:not([data-theme]) {\n    --bg-sub: var(--default-dark-bg-sub);\n    --bg-sub-hover: var(--default-dark-bg-sub-hover);\n    --bg-base: var(--default-dark-bg-base);\n    --bg-base-hover: var(--default-dark-bg-base-hover);\n    --bg-selected: var(--default-dark-bg-selected);\n    --bg-shade: var(--default-dark-bg-shade);\n    --bg-shade-hover: var(--default-dark-bg-shade-hover);\n    --bg-border: var(--default-dark-bg-border);\n    --bg-border-hover: var(--default-dark-bg-border-hover);\n    --bg-border-selected: var(--default-dark-bg-border-selected);\n    --bg-divider: var(--default-dark-bg-divider);\n    --bg-contrast: var(--default-dark-bg-contrast);\n    --bg-contrast-hover: var(--default-dark-bg-contrast-hover);\n\n    --label-title: var(--default-dark-label-title);\n    --label-base: var(--default-dark-label-base);\n    --label-muted: var(--default-dark-label-muted);\n    --label-faint: var(--default-dark-label-faint);\n    --label-link: var(--default-dark-label-link);\n    --label-contrast: var(--default-dark-label-contrast);\n    --label-control: var(--default-dark-label-control);\n\n    --sub-surface: var(--default-dark-sub-surface);\n    --shade-surface: var(--default-dark-shade-surface);\n    --base-surface: var(--default-dark-base-surface);\n\n    --brand-default: var(--default-dark-brand-default);\n    --brand-default-hover: var(--default-dark-brand-default-hover);\n    --brand-default-subtitle: var(--default-dark-brand-default-subtitle);\n    --brand-studio: var(--default-dark-brand-studio);\n    --brand-studio-hover: var(--default-dark-brand-studio-hover);\n    --brand-studio-subtle: var(--default-dark-brand-studio-subtle);\n\n    --green: var(--default-dark-green);\n    --green-subtle: var(--default-dark-green-subtle);\n    --green-subtle-hover: var(--default-dark-green-subtle-hover);\n    --red: var(--default-dark-red);\n    --red-subtle: var(--default-dark-red-subtle);\n    --red-subtle-hover: var(--default-dark-red-subtle-hover);\n    --yellow: var(--default-dark-yellow);\n    --yellow-subtle: var(--default-dark-yellow-subtle);\n    --yellow-subtle-hover: var(--default-dark-yellow-subtle-hover);\n    --blue: var(--default-dark-blue);\n    --blue-subtle: var(--default-dark-blue-subtle);\n    --blue-subtle-hover: var(--default-dark-blue-subtle-hover);\n\n    --line-select: var(--default-dark-line-select);\n\n    --shadow-rings-blue: var(--default-dark-shadow-rings-blue);\n    --shadow-rings-gray: var(--default-dark-shadow-rings-gray);\n    --shadow-float: var(--default-dark-shadow-float);\n    --shadow-high: var(--default-dark-shadow-high);\n    --shadow-medium: var(--default-dark-shadow-medium);\n    --shadow-low: var(--default-dark-shadow-low);\n\n    --onboarding-chats-img: var(--default-dark-onboarding-chats-img);\n    --bloop-head-img: var(--default-dark-bloop-head-img);\n  }\n  body[data-theme=\"black\"] {\n    --bg-sub: var(--default-black-bg-sub);\n    --bg-sub-hover: var(--default-black-bg-sub-hover);\n    --bg-base: var(--default-black-bg-base);\n    --bg-base-hover: var(--default-black-bg-base-hover);\n    --bg-selected: var(--default-black-bg-selected);\n    --bg-shade: var(--default-black-bg-shade);\n    --bg-shade-hover: var(--default-black-bg-shade-hover);\n    --bg-border: var(--default-black-bg-border);\n    --bg-border-hover: var(--default-black-bg-border-hover);\n    --bg-border-selected: var(--default-black-bg-border-selected);\n    --bg-divider: var(--default-black-bg-divider);\n    --bg-contrast: var(--default-black-bg-contrast);\n    --bg-contrast-hover: var(--default-black-bg-contrast-hover);\n\n    --label-title: var(--default-black-label-title);\n    --label-base: var(--default-black-label-base);\n    --label-muted: var(--default-black-label-muted);\n    --label-faint: var(--default-black-label-faint);\n    --label-link: var(--default-black-label-link);\n    --label-contrast: var(--default-black-label-contrast);\n    --label-control: var(--default-black-label-control);\n\n    --sub-surface: var(--default-black-sub-surface);\n    --shade-surface: var(--default-black-shade-surface);\n    --base-surface: var(--default-black-base-surface);\n\n    --brand-default: var(--default-black-brand-default);\n    --brand-default-hover: var(--default-black-brand-default-hover);\n    --brand-default-subtitle: var(--default-black-brand-default-subtitle);\n    --brand-studio: var(--default-black-brand-studio);\n    --brand-studio-hover: var(--default-black-brand-studio-hover);\n    --brand-studio-subtle: var(--default-black-brand-studio-subtle);\n\n    --green: var(--default-black-green);\n    --green-subtle: var(--default-black-green-subtle);\n    --green-subtle-hover: var(--default-black-green-subtle-hover);\n    --red: var(--default-black-red);\n    --red-subtle: var(--default-black-red-subtle);\n    --red-subtle-hover: var(--default-black-red-subtle-hover);\n    --yellow: var(--default-black-yellow);\n    --yellow-subtle: var(--default-black-yellow-subtle);\n    --yellow-subtle-hover: var(--default-black-yellow-subtle-hover);\n    --blue: var(--default-black-blue);\n    --blue-subtle: var(--default-black-blue-subtle);\n    --blue-subtle-hover: var(--default-black-blue-subtle-hover);\n\n    --line-select: var(--default-black-line-select);\n\n    --shadow-rings-blue: var(--default-dark-shadow-rings-blue);\n    --shadow-rings-gray: var(--default-dark-shadow-rings-gray);\n    --shadow-float: var(--default-dark-shadow-float);\n    --shadow-high: var(--default-dark-shadow-high);\n    --shadow-medium: var(--default-dark-shadow-medium);\n    --shadow-low: var(--default-dark-shadow-low);\n\n    --onboarding-chats-img: var(--default-dark-onboarding-chats-img);\n    --bloop-head-img: var(--default-dark-bloop-head-img);\n  }\n  @media (prefers-color-scheme: dark) {\n    body[data-theme=\"system\"] {\n      --bg-sub: var(--default-dark-bg-sub);\n      --bg-sub-hover: var(--default-dark-bg-sub-hover);\n      --bg-base: var(--default-dark-bg-base);\n      --bg-base-hover: var(--default-dark-bg-base-hover);\n      --bg-selected: var(--default-dark-bg-selected);\n      --bg-shade: var(--default-dark-bg-shade);\n      --bg-shade-hover: var(--default-dark-bg-shade-hover);\n      --bg-border: var(--default-dark-bg-border);\n      --bg-border-hover: var(--default-dark-bg-border-hover);\n      --bg-border-selected: var(--default-dark-bg-border-selected);\n      --bg-divider: var(--default-dark-bg-divider);\n      --bg-contrast: var(--default-dark-bg-contrast);\n      --bg-contrast-hover: var(--default-dark-bg-contrast-hover);\n\n      --label-title: var(--default-dark-label-title);\n      --label-base: var(--default-dark-label-base);\n      --label-muted: var(--default-dark-label-muted);\n      --label-faint: var(--default-dark-label-faint);\n      --label-link: var(--default-dark-label-link);\n      --label-contrast: var(--default-dark-label-contrast);\n      --label-control: var(--default-dark-label-control);\n\n      --sub-surface: var(--default-dark-sub-surface);\n      --shade-surface: var(--default-dark-shade-surface);\n      --base-surface: var(--default-dark-base-surface);\n\n      --brand-default: var(--default-dark-brand-default);\n      --brand-default-hover: var(--default-dark-brand-default-hover);\n      --brand-default-subtitle: var(--default-dark-brand-default-subtitle);\n      --brand-studio: var(--default-dark-brand-studio);\n      --brand-studio-hover: var(--default-dark-brand-studio-hover);\n      --brand-studio-subtle: var(--default-dark-brand-studio-subtle);\n\n      --green: var(--default-dark-green);\n      --green-subtle: var(--default-dark-green-subtle);\n      --green-subtle-hover: var(--default-dark-green-subtle-hover);\n      --red: var(--default-dark-red);\n      --red-subtle: var(--default-dark-red-subtle);\n      --red-subtle-hover: var(--default-dark-red-subtle-hover);\n      --yellow: var(--default-dark-yellow);\n      --yellow-subtle: var(--default-dark-yellow-subtle);\n      --yellow-subtle-hover: var(--default-dark-yellow-subtle-hover);\n      --blue: var(--default-dark-blue);\n      --blue-subtle: var(--default-dark-blue-subtle);\n      --blue-subtle-hover: var(--default-dark-blue-subtle-hover);\n\n      --line-select: var(--default-dark-line-select);\n\n      --shadow-rings-blue: var(--default-dark-shadow-rings-blue);\n      --shadow-rings-gray: var(--default-dark-shadow-rings-gray);\n      --shadow-float: var(--default-dark-shadow-float);\n      --shadow-high: var(--default-dark-shadow-high);\n      --shadow-medium: var(--default-dark-shadow-medium);\n      --shadow-low: var(--default-dark-shadow-low);\n\n      --onboarding-chats-img: var(--default-dark-onboarding-chats-img);\n      --bloop-head-img: var(--default-dark-bloop-head-img);\n    }\n  }\n  body[data-theme=\"light\"] {\n    --bg-sub: var(--default-light-bg-sub);\n    --bg-sub-hover: var(--default-light-bg-sub-hover);\n    --bg-base: var(--default-light-bg-base);\n    --bg-base-hover: var(--default-light-bg-base-hover);\n    --bg-selected: var(--default-light-bg-selected);\n    --bg-shade: var(--default-light-bg-shade);\n    --bg-shade-hover: var(--default-light-bg-shade-hover);\n    --bg-border: var(--default-light-bg-border);\n    --bg-border-hover: var(--default-light-bg-border-hover);\n    --bg-border-selected: var(--default-light-bg-border-selected);\n    --bg-divider: var(--default-light-bg-divider);\n    --bg-contrast: var(--default-light-bg-contrast);\n    --bg-contrast-hover: var(--default-light-bg-contrast-hover);\n\n    --label-title: var(--default-light-label-title);\n    --label-base: var(--default-light-label-base);\n    --label-muted: var(--default-light-label-muted);\n    --label-faint: var(--default-light-label-faint);\n    --label-link: var(--default-light-label-link);\n    --label-contrast: var(--default-light-label-contrast);\n    --label-control: var(--default-light-label-control);\n\n    --sub-surface: var(--default-light-sub-surface);\n    --shade-surface: var(--default-light-shade-surface);\n    --base-surface: var(--default-light-base-surface);\n\n    --brand-default: var(--default-light-brand-default);\n    --brand-default-hover: var(--default-light-brand-default-hover);\n    --brand-default-subtitle: var(--default-light-brand-default-subtitle);\n    --brand-studio: var(--default-light-brand-studio);\n    --brand-studio-hover: var(--default-light-brand-studio-hover);\n    --brand-studio-subtle: var(--default-light-brand-studio-subtle);\n\n    --green: var(--default-light-green);\n    --green-subtle: var(--default-light-green-subtle);\n    --green-subtle-hover: var(--default-light-green-subtle-hover);\n    --red: var(--default-light-red);\n    --red-subtle: var(--default-light-red-subtle);\n    --red-subtle-hover: var(--default-light-red-subtle-hover);\n    --yellow: var(--default-light-yellow);\n    --yellow-subtle: var(--default-light-yellow-subtle);\n    --yellow-subtle-hover: var(--default-light-yellow-subtle-hover);\n    --blue: var(--default-light-blue);\n    --blue-subtle: var(--default-light-blue-subtle);\n    --blue-subtle-hover: var(--default-light-blue-subtle-hover);\n\n    --line-select: var(--default-light-line-select);\n\n    --shadow-rings-blue: var(--default-light-shadow-rings-blue);\n    --shadow-rings-gray: var(--default-light-shadow-rings-gray);\n    --shadow-float: var(--default-light-shadow-float);\n    --shadow-high: var(--default-light-shadow-high);\n    --shadow-medium: var(--default-light-shadow-medium);\n    --shadow-low: var(--default-light-shadow-low);\n\n    --onboarding-chats-img: var(--default-dark-onboarding-chats-img);\n    --bloop-head-img: var(--default-light-bloop-head-img);\n  }\n  @media (prefers-color-scheme: light) {\n    body[data-theme=\"system\"] {\n      --bg-sub: var(--default-light-bg-sub);\n      --bg-sub-hover: var(--default-light-bg-sub-hover);\n      --bg-base: var(--default-light-bg-base);\n      --bg-base-hover: var(--default-light-bg-base-hover);\n      --bg-selected: var(--default-light-bg-selected);\n      --bg-shade: var(--default-light-bg-shade);\n      --bg-shade-hover: var(--default-light-bg-shade-hover);\n      --bg-border: var(--default-light-bg-border);\n      --bg-border-hover: var(--default-light-bg-border-hover);\n      --bg-border-selected: var(--default-light-bg-border-selected);\n      --bg-divider: var(--default-light-bg-divider);\n      --bg-contrast: var(--default-light-bg-contrast);\n      --bg-contrast-hover: var(--default-light-bg-contrast-hover);\n\n      --label-title: var(--default-light-label-title);\n      --label-base: var(--default-light-label-base);\n      --label-muted: var(--default-light-label-muted);\n      --label-faint: var(--default-light-label-faint);\n      --label-link: var(--default-light-label-link);\n      --label-contrast: var(--default-light-label-contrast);\n      --label-control: var(--default-light-label-control);\n\n      --sub-surface: var(--default-light-sub-surface);\n      --shade-surface: var(--default-light-shade-surface);\n      --base-surface: var(--default-light-base-surface);\n\n      --brand-default: var(--default-light-brand-default);\n      --brand-default-hover: var(--default-light-brand-default-hover);\n      --brand-default-subtitle: var(--default-light-brand-default-subtitle);\n      --brand-studio: var(--default-light-brand-studio);\n      --brand-studio-hover: var(--default-light-brand-studio-hover);\n      --brand-studio-subtle: var(--default-light-brand-studio-subtle);\n\n      --green: var(--default-light-green);\n      --green-subtle: var(--default-light-green-subtle);\n      --green-subtle-hover: var(--default-light-green-subtle-hover);\n      --red: var(--default-light-red);\n      --red-subtle: var(--default-light-red-subtle);\n      --red-subtle-hover: var(--default-light-red-subtle-hover);\n      --yellow: var(--default-light-yellow);\n      --yellow-subtle: var(--default-light-yellow-subtle);\n      --yellow-subtle-hover: var(--default-light-yellow-subtle-hover);\n      --blue: var(--default-light-blue);\n      --blue-subtle: var(--default-light-blue-subtle);\n      --blue-subtle-hover: var(--default-light-blue-subtle-hover);\n\n      --line-select: var(--default-light-line-select);\n\n      --shadow-rings-blue: var(--default-light-shadow-rings-blue);\n      --shadow-rings-gray: var(--default-light-shadow-rings-gray);\n      --shadow-float: var(--default-light-shadow-float);\n      --shadow-high: var(--default-light-shadow-high);\n      --shadow-medium: var(--default-light-shadow-medium);\n      --shadow-low: var(--default-light-shadow-low);\n\n      --onboarding-chats-img: var(--default-dark-onboarding-chats-img);\n      --bloop-head-img: var(--default-light-bloop-head-img);\n    }\n  }\n\n  .inline-container {\n    container-type: inline-size;\n  }\n\n  @container (max-width: 519px) {\n    .wrap-in-sm-container {\n      @apply flex-wrap py-2 h-auto;\n    }\n  }\n\n  @container (min-width: 520px) {\n    .wrap-in-sm-container {\n      @apply h-10;\n    }\n  }\n\n  @container (max-width: 749px) {\n    .wrap-in-md-container {\n      @apply flex-wrap py-2 h-auto;\n    }\n  }\n\n  @container (min-width: 750px) {\n    .wrap-in-md-container {\n      @apply h-10;\n    }\n  }\n}\n\nbody {\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  background-color: rgb(var(--bg-sub));\n  color: rgb(var(--label-title));\n  overflow: hidden;\n  font-family: 'Inter', sans-serif;\n}\n\ninput[type=\"search\" i]::-webkit-search-cancel-button {\n  display: none;\n}\n\ninput::placeholder {\n  font-family: 'Inter', sans-serif;\n  font-style: normal;\n  font-weight: 400;\n  font-size: 0.875rem;\n  letter-spacing: -0.02em;\n}\n\nh1, .h1 {\n  font-family: 'Inter', sans-serif;\n  font-style: normal;\n  font-weight: 600;\n  font-size: 3rem;\n  line-height: 110%;\n  letter-spacing: -0.02em;\n\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  user-select: none;\n  cursor: default;\n}\n\nh2, .h2 {\n  font-family: 'Inter', sans-serif;\n  font-style: normal;\n  font-weight: 600;\n  font-size: 2.375rem;\n  line-height: 110%;\n  letter-spacing: -0.02em;\n\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  user-select: none;\n  cursor: default;\n}\n\nh3, .h3 {\n  font-family: 'Inter', sans-serif;\n  font-style: normal;\n  font-weight: 600;\n  font-size: 1.75rem;\n  line-height: 110%;\n  letter-spacing: -0.02em;\n\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  user-select: none;\n  cursor: default;\n}\n\nh4, .h4 {\n  font-family: 'Inter', sans-serif;\n  font-style: normal;\n  font-weight: 600;\n  font-size: 1.375rem;\n  line-height: 110%;\n  letter-spacing: -0.02em;\n\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  user-select: none;\n  cursor: default;\n}\n\nh5, .h5 {\n  font-family: 'Inter', sans-serif;\n  font-style: normal;\n  font-weight: 500;\n  font-size: 1.125rem;\n  line-height: 110%;\n  letter-spacing: -0.02em;\n\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  user-select: none;\n  cursor: default;\n}\n\n.headline-b {\n  font-family: 'Inter', sans-serif;\n  font-size: 1.625rem;\n  font-style: normal;\n  font-weight: 600;\n  line-height: 123.077%;\n  letter-spacing: -0.26px;\n}\n\n.title-s {\n  font-family: 'Inter', sans-serif;\n  font-size: 0.9375rem;\n  font-style: normal;\n  font-weight: 500;\n  line-height: 133%;\n  letter-spacing: -0.15px;\n}\n\n.title-m {\n  font-family: 'Inter', sans-serif;\n  font-size: 1.0625rem;\n  font-style: normal;\n  font-weight: 500;\n  line-height: 129.412%;\n  letter-spacing: -0.17px;\n}\n\n.title-m-b {\n  font-family: 'Inter', sans-serif;\n  font-size: 1.0625rem;\n  font-style: normal;\n  font-weight: 600;\n  line-height: 129.412%;\n  letter-spacing: -0.17px;\n}\n\n.subhead-l {\n  font-family: 'Inter', sans-serif;\n  font-style: normal;\n  font-weight: 500;\n  font-size: 1.125rem;\n  line-height: 110%;\n  letter-spacing: -0.02em;\n}\n\n.subhead-m {\n  font-family: 'Inter', sans-serif;\n  font-style: normal;\n  font-weight: 500;\n  font-size: 1rem;\n  line-height: 110%;\n  letter-spacing: -0.02em;\n}\n\n.subhead-s {\n  font-family: 'Inter', sans-serif;\n  font-style: normal;\n  font-weight: 500;\n  font-size: 0.875rem;\n  line-height: 110%;\n  letter-spacing: -0.02em;\n}\n\n.body-l {\n  font-family: 'Inter', sans-serif;\n  font-style: normal;\n  font-weight: 400;\n  font-size: 1.125rem;\n  line-height: 160%;\n  letter-spacing: -0.02em;\n}\n\n.body-m {\n  font-family: 'Inter', sans-serif;\n  font-style: normal;\n  font-weight: 400;\n  font-size: 1rem;\n  line-height: 160%;\n  letter-spacing: -0.02em;\n}\n\n.body-m-strong {\n  font-family: 'Inter', sans-serif;\n  font-style: normal;\n  font-weight: 600;\n  font-size: 1rem;\n  line-height: 160%;\n  letter-spacing: -0.02em;\n}\n\n.body-s {\n  font-family: 'Inter', sans-serif;\n  font-style: normal;\n  font-weight: 400;\n  font-size: 0.8125rem;\n  line-height: 123.077%;\n}\n\n.body-s-b {\n  font-family: 'Inter', sans-serif;\n  font-style: normal;\n  font-weight: 500;\n  font-size: 0.8125rem;\n  line-height: 123.077%;\n}\n\n.body-mini {\n  font-family: 'Inter', sans-serif;\n  font-style: normal;\n  font-weight: 400;\n  font-size: 0.75rem;\n  line-height: 116.667%;\n}\n\n.body-mini-b {\n  font-family: 'Inter', sans-serif;\n  font-style: normal;\n  font-weight: 500;\n  font-size: 0.75rem;\n  line-height: 116.667%;\n}\n\n.body-tiny {\n  font-family: 'Inter', sans-serif;\n  font-style: normal;\n  font-weight: 400;\n  font-size: 0.625rem;\n  line-height: 120%;\n}\n\n.body-base-b {\n  font-family: 'Inter', sans-serif;\n  font-style: normal;\n  font-weight: 500;\n  font-size: 0.937rem;\n  line-height: 133.333%;\n}\n\n.body-base {\n  font-family: 'Inter', sans-serif;\n  font-style: normal;\n  font-weight: 400;\n  font-size: 0.937rem;\n  line-height: 133.333%;\n}\n\n.callout {\n  font-family: 'Inter', sans-serif;\n  font-style: normal;\n  font-weight: 500;\n  font-size: 0.875rem;\n  line-height: 100%;\n  letter-spacing: -0.02em;\n}\n\n.caption {\n  font-family: 'Inter', sans-serif;\n  font-style: normal;\n  font-weight: 400;\n  font-size: 0.75rem;\n  line-height: 140%;\n  letter-spacing: -0.01em;\n}\n\n.caption-strong {\n  font-family: 'Inter', sans-serif;\n  font-style: normal;\n  font-weight: 500;\n  font-size: 0.75rem;\n  line-height: 140%;\n  letter-spacing: -0.01em;\n}\n\n.code-s {\n  font-family: \"Fira Code\", sans-serif;\n  font-style: normal;\n  font-weight: 400;\n  font-size: 0.8125rem;\n  line-height: 161%;\n  letter-spacing: -0.13px;\n}\n\n.code-m {\n  font-family: \"Fira Code\", sans-serif;\n  font-style: normal;\n  font-weight: 400;\n  font-size: 1rem;\n  line-height: 160%;\n}\n\n.code-mini {\n  font-family: \"Fira Code\", sans-serif;\n  font-size: 0.75rem;\n  font-style: normal;\n  font-weight: 400;\n  line-height: 166.667%;\n  letter-spacing: -0.12px;\n}\n\n.code-tiny-b {\n  font-family: \"Fira Code\", sans-serif;\n  font-size: 0.625rem;\n  font-style: normal;\n  font-weight: 500;\n  line-height: 180%;\n}\n\n.ellipsis {\n  @apply overflow-ellipsis whitespace-nowrap overflow-hidden;\n}\n\n.fade-bottom {\n  mask-image: linear-gradient(to bottom, black calc(100% - 45px), transparent 100%);\n  -webkit-mask-image: linear-gradient(to bottom, black calc(100% - 45px), transparent 100%);\n}\n\n.fade-right {\n  mask-image: linear-gradient(to right, black calc(100% - 24px), transparent 100%);\n  -webkit-mask-image: linear-gradient(to right, black calc(100% - 24px), transparent 100%);\n}\n\n.auto-fade-horizontal {\n  overflow-x: scroll;\n  --fade-length: 40px;\n  --start-fade: 0px;\n  --end-fade: 0px;\n  -webkit-mask: linear-gradient(\n          to right,\n          transparent,\n          black var(--start-fade),\n          black calc(100% - var(--end-fade)),\n          transparent\n  );\n}\n.auto-fade-horizontal[data-overflow-left] {\n  --start-fade: var(--fade-length);\n}\n.auto-fade-horizontal[data-overflow-right] {\n  --end-fade: var(--fade-length);\n}\n\n.auto-fade-vertical {\n  overflow-x: scroll;\n  --fade-length: 40px;\n  --start-fade: 0px;\n  --end-fade: 0px;\n  -webkit-mask: linear-gradient(\n          to bottom,\n          transparent,\n          black var(--start-fade),\n          black calc(100% - var(--end-fade)),\n          transparent\n  );\n}\n.auto-fade-vertical[data-overflow-top] {\n  --start-fade: var(--fade-length);\n}\n.auto-fade-vertical[data-overflow-bottom] {\n  --end-fade: var(--fade-length);\n}\n\n.backdrop-blur-0 {\n  backdrop-filter: blur(0);\n  -webkit-backdrop-filter: blur(0);\n}\n\n.backdrop-blur-2 {\n  backdrop-filter: blur(2px);\n  -webkit-backdrop-filter: blur(2px);\n}\n\n.backdrop-blur-4 {\n  backdrop-filter: blur(4px);\n  -webkit-backdrop-filter: blur(4px);\n}\n\n.backdrop-blur-6 {\n  backdrop-filter: blur(6px);\n  -webkit-backdrop-filter: blur(6px);\n}\n\n.backdrop-blur-8 {\n  backdrop-filter: blur(8px);\n  -webkit-backdrop-filter: blur(8px);\n}\n\n.dir-rtl {\n  direction: rtl;\n}\n\n/*Fix react-virtualized cutting code overflow*/\n.ReactVirtualized__Grid__innerScrollContainer {\n  overflow: auto !important;\n  -ms-overflow-style: none;  /* IE and Edge */\n  scrollbar-width: none;\n}\n\n.ReactVirtualized__Grid__innerScrollContainer::-webkit-scrollbar {\n  display: none;\n}\n\n::-webkit-scrollbar {\n  display: none;\n}\n\n.show-scrollbar::-webkit-scrollbar {\n  display: initial;\n  width: 7px;\n}\n\n.show-scrollbar *::-webkit-scrollbar {\n  display: none;\n}\n\n.show-scrollbar::-webkit-scrollbar-thumb {\n  @apply bg-bg-base-hover hover:bg-bg-border-hover rounded-full\n}\n\n/* For IE, Edge and Firefox */\n.scrollbar-hide {\n  -ms-overflow-style: none;  /* IE and Edge */\n  scrollbar-width: none;  /* Firefox */\n}\n\n/* For IE, Edge and Firefox */\n.scrollbar-show {\n  -ms-overflow-style: initial;  /* IE and Edge */\n  scrollbar-width: initial;  /* Firefox */\n}\n\n.readme h1, .readme h2, .readme h3, .readme h4, .readme h5, .readme h6, .readme p, .readme span, .readme div {\n  cursor: auto;\n}\n\n.readme h1, .readme h2, .readme h3, .readme h4, .readme h5, .readme h6, .readme p, .readme span, .readme div,\n.article-response h1, .article-response h2, .article-response h3, .article-response h4, .article-response h5,\n.article-response h6, .article-response p {\n  margin: revert;\n}\n\n.code-studio-md h1:not(:first-child), .code-studio-md h2:not(:first-child), .code-studio-md h3:not(:first-child), .code-studio-md h4:not(:first-child), .code-studio-md h5:not(:first-child), .code-studio-md h6:not(:first-child), .code-studio-md p:not(:first-child),\n.doc-section h1:not(:first-child), .doc-section h2:not(:first-child), .doc-section h3:not(:first-child), .doc-section h4:not(:first-child), .doc-section h5:not(:first-child), .doc-section h6:not(:first-child), .doc-section p:not(:first-child), .doc-section pre:not(:first-child) {\n  margin-top: revert;\n}\n\n.code-studio-md h1:not(:last-child), .code-studio-md h2:not(:last-child), .code-studio-md h3:not(:last-child), .code-studio-md h4:not(:last-child), .code-studio-md h5:not(:last-child), .code-studio-md h6:not(:last-child), .code-studio-md p:not(:last-child),\n.doc-section h1:not(:last-child), .doc-section h2:not(:last-child), .doc-section h3:not(:last-child), .doc-section h4:not(:last-child), .doc-section h5:not(:last-child), .doc-section h6:not(:last-child), .doc-section p:not(:last-child), .doc-section pre:not(:last-child) {\n  margin-bottom: revert;\n}\n\n.readme h1, .readme h2, .readme h3, .readme h4, .readme h5, .readme h6, .readme p,\n.doc-section h1, .doc-section h2, .doc-section h3, .doc-section h4, .doc-section h5, .doc-section h6, .doc-section p,\n.code-studio-md h1, .code-studio-md h2, .code-studio-md h3, .code-studio-md h4, .code-studio-md h5,\n.code-studio-md h6, .code-studio-md p,\n.article-response h1, .article-response h2, .article-response h3, .article-response h4, .article-response h5,\n.article-response h6, .article-response p,\n.ipynb-markdown h1, .ipynb-markdown h2, .ipynb-markdown h3, .ipynb-markdown h4, .ipynb-markdown h5,\n.ipynb-markdown h6, .ipynb-markdown p {\n  -webkit-user-select: initial;\n  -moz-user-select: initial;\n  user-select: initial;\n}\n\n.ipynb-markdown p:not(:first-child), .ipynb-markdown h1:not(:first-child), .ipynb-markdown h2:not(:first-child), .ipynb-markdown h3:not(:first-child), .ipynb-markdown h4:not(:first-child){\n  margin-top: revert;\n}\n\n.ipynb-markdown p:not(:last-child), .ipynb-markdown h1:not(:last-child), .ipynb-markdown h2:not(:last-child), .ipynb-markdown h3:not(:last-child), .ipynb-markdown h4:not(:last-child){\n  margin-bottom: revert;\n}\n\n.ipynb-markdown blockquote {\n  @apply border-l-2 border-bg-border;\n  margin: 1em 2em;\n  padding: 0 1em;\n}\n\n.article-response h1, .article-response h2, .article-response h3, .article-response h4, .article-response h5,\n.article-response h6 {\n  @apply text-label-title;\n}\n\n.readme h1, .readme h2 {\n  border-bottom: 1px solid rgb(var(--bg-border));\n  padding-bottom: 0.6rem;\n}\n\n.readme h1, .article-response h1, .markdown h1, .code-studio-md h1, .doc-section h1 {\n  font-size: 2rem;\n}\n\n.readme h2, .article-response h2, .markdown h2, .code-studio-md h2, .doc-section h2 {\n  font-size: 1.5rem;\n}\n\n.readme h3, .article-response h3, .markdown h3, .code-studio-md h3, .doc-section h3 {\n  font-size: 1.25rem\n}\n\n.readme h4, .article-response h4, .markdown h4, .code-studio-md h4, .doc-section h4 {\n  font-size: 1rem\n}\n\n.readme h5, .article-response h5, .markdown h5, .code-studio-md h5, .doc-section h5 {\n  font-size: .875rem\n}\n\n.readme h6, .article-response h6, .markdown h6, .code-studio-md h6, .doc-section h6 {\n  font-size: .85rem\n}\n\n.readme ul, .article-response ul, .markdown ul, .code-studio-md ul, .doc-section ul {\n  list-style: revert;\n  margin: revert;\n  padding: revert;\n}\n\n.readme ol, .article-response ol, .markdown ol, .code-studio-md ol, .doc-section ol {\n  list-style: revert;\n  margin: revert;\n  padding: revert;\n}\n\n.readme a, .markdown a, .doc-section a {\n  color: rgb(var(--label-link));\n}\n\n.article-response a, .summary-card a, .code-studio-md a {\n  background-color: rgb(var(--bg-shade));\n  border: 1px solid rgb(var(--bg-border));\n  border-radius: 4px;\n  padding: 2px 4px;\n}\n\n.code-studio-md .folder-chip a {\n  background-color: transparent;\n  border: unset;\n}\n\n.article-response li>p, .code-studio-md li>p {\n  margin: 0;\n  display: unset;\n}\n\n.article-response code, .summary-card code, .code-studio-md code, .doc-section :not(pre) code, .green-code {\n  color: #14B8A6;\n  font-family: Menlo, sans-serif;\n  font-size: 0.95em;\n}\n\n.doc-section pre code {\n  color: inherit;\n}\n\n.markdown code {\n  background-color: rgb(var(--bg-shade));\n  border: 1px solid rgb(var(--bg-border));\n  padding: 1px 3px;\n  border-radius: 4px;\n}\n\n.article-response pre:has(code), .code-studio-md pre:has(code), .doc-section pre:has(code) {\n  background-color: rgb(var(--bg-shade));\n  border: 1px solid rgb(var(--bg-border));\n  border-radius: 8px;\n  padding: 12px;\n  white-space: pre-wrap;\n}\n\n.doc-section pre:has(code) {\n  overflow: auto;\n}\n\n.hljs-addition {\n  width: auto !important;\n}\n\n.search-highlight {\n  background-color: rgba(var(--yellow), 0.25);\n}\n\n.search-highlight-active {\n  background-color: rgba(var(--yellow), 0.5);\n}\n\n.onboarding-chats-img {\n  content: var(--onboarding-chats-img);\n}\n\n.bloop-head-img {\n  content: var(--bloop-head-img);\n}\n\n.notransition * {\n  -webkit-transition: none !important;\n  -moz-transition: none !important;\n  -o-transition: none !important;\n  transition: none !important;\n}\n\n.chat-head-bg {\n  background: radial-gradient(47.73% 47.73% at 50% 0%,transparent 0%, rgb(var(--chat-bg-sub)) 100%);\n}\n\n.break-word {\n  word-break: break-word;\n}\n\n.file-icon-lg span[class*=\"-icon\"]:before {\n  transform: scale(125%);\n}\n\n.padding-start > *:first-child {\n  padding-left: 32px;\n}\n\n/* prose mirror editor start */\n.ProseMirror {\n  max-height: 40vh;\n  overflow: auto;\n  white-space: pre-wrap;\n}\n\n.ProseMirror pre code {\n  white-space: pre-wrap;\n  font-family: inherit;\n  font-size: inherit;\n}\n\n.ProseMirror pre {\n  font-family: inherit;\n  font-size: inherit;\n}\n\n.ProseMirror:focus {\n  outline: none;\n}\n\n.ProseMirror[data-placeholder]::before {\n  color: rgb(var(--label-muted));\n  position: absolute;\n  content: attr(data-placeholder);\n  pointer-events: none;\n}\n\nimg.ProseMirror-separator {\n  display: inline !important;\n  border: none !important;\n  margin: 0 !important;\n}\n\n.suggestion-item-active .suggestion-item {\n  @apply bg-bg-selected\n}\n/* prose mirror editor end */\n\n.icon-stroked path {\n  stroke: rgb(var(--bg-sub));\n  stroke-width: 1px;\n  stroke-linecap: round;\n  stroke-linejoin: round;\n}\n\n/* react-mentions editor start */\n.w-full__suggestions {\n  background-color: transparent !important;\n}\n/* react-mentions editor end */"
  },
  {
    "path": "client/src/locales/en.json",
    "content": "{\n\t\"# match_one\": \"{{count}} match\",\n\t\"# match_other\": \"{{count}} matches\",\n\t\"# of #_one\": \"{{count}} of {{total}}\",\n\t\"# of #_other\": \"{{count}} of {{total}}\",\n\t\"# ranges_one\": \"{{count}} ranges\",\n\t\"# ranges_other\": \"{{count}} ranges\",\n\t\"# sections_one\": \"{{count}} section\",\n\t\"# sections_other\": \"{{count}} sections\",\n\t\"# selected section_one\": \"{{count}} selected section\",\n\t\"# selected section_other\": \"{{count}} selected sections\",\n\t\"# tokens_one\": \"token\",\n\t\"# tokens_other\": \"tokens\",\n\t\"$20 / billed monthly\": \"$20 / billed monthly\",\n\t\"<0>#</0> of # tokens\": \"<0>{{count}}</0> of {{total}} tokens\",\n\t\"<0>#</0> of # tokens_one\": \"<0>{{count}}</0> of {{total}} tokens\",\n\t\"<0>#</0> of # tokens_other\": \"<0>{{count}}</0> of {{total}} tokens\",\n\t\"<0></0><1></1> to navigate.\": \"<0></0><1></1> to navigate.\",\n\t\"<0>Create a new conversation with bloop by hitting <2></2> on your keyboard or by pressing the <4></4> in the header bar.</0>\": \"<0>Create a new conversation with bloop by hitting <2></2> on your keyboard or by pressing the <4></4> in the header bar.</0>\",\n\t\"<0>To begin, open a file from the sidebar on the left. Once you have a file open, you can ask bloop to quickly explain it by hitting <2></2> on your keyboard or by selecting \\\"Explain file\\\" from the <7></7> popup menu.</0>\": \"<0>To begin, open a file from the sidebar on the left. Once you have a file open, you can ask bloop to quickly explain it by hitting <2></2> on your keyboard or by selecting \\\"Explain file\\\" from the <7></7> popup menu.</0>\",\n\t\"<0>Use your cursor to select any piece of code within a file and ask bloop to explain it by pressing \\\"Explain\\\" in the floating toolbar.</0>\": \"<0>Use your cursor to select any piece of code within a file and ask bloop to explain it by pressing \\\"Explain\\\" in the floating toolbar.</0>\",\n\t\"<0>repoName</0> has finished indexing and was added to the context of the current project. You can also use it in your other projects now.\": \"<0>{{repoName}}</0> has finished indexing and was added to the context of the current project. You can also use it in your other projects now.\",\n\t\"<0>repoName</0> has started indexing. You’ll receive a notification as soon as this process completes.\": \"<0>{{repoName}}</0> has started indexing. You’ll receive a notification as soon as this process completes.\",\n\t\"Account settings\": \"Account settings\",\n\t\"Actions\": \"Actions\",\n\t\"Add a repository from your local machine\": \"Add a repository from your local machine\",\n\t\"Add any library documentation\": \"Add any library documentation\",\n\t\"Add at least one context file before submitting your first request.\": \"Add at least one context file before submitting your first request.\",\n\t\"Add doc to studio\": \"Add doc to studio\",\n\t\"Add docs\": \"Add docs\",\n\t\"Add file to code studio context\": \"Add file to code studio context\",\n\t\"Add file to studio\": \"Add file to studio\",\n\t\"Add file\": \"Add file\",\n\t\"Add files to studio\": \"Add files to studio\",\n\t\"Add files\": \"Add files\",\n\t\"Add local repository\": \"Add local repository\",\n\t\"Add new repository\": \"Add new repository\",\n\t\"Add private repository\": \"Add private repository\",\n\t\"Add public repository\": \"Add public repository\",\n\t\"Add repository\": \"Add repository\",\n\t\"Add to existing\": \"Add to existing\",\n\t\"Add to project\": \"Add to project\",\n\t\"Add to studio\": \"Add to studio\",\n\t\"Add\": \"Add\",\n\t\"Adding repository\": \"Adding repository\",\n\t\"All payments, invoices and billing information are managed in Stripe.\": \"All payments, invoices and billing information are managed in Stripe.\",\n\t\"All sections\": \"All sections\",\n\t\"All templates\": \"All templates\",\n\t\"All\": \"All\",\n\t\"Answer speed\": \"Answer speed\",\n\t\"Application theme\": \"Application theme\",\n\t\"Apply changes\": \"Apply changes\",\n\t\"Ask questions about your codebases in natural language, just like you’d speak to ChatGPT. Get started by syncing a repo, then open the repo and start chatting.\": \"Ask questions about your codebases in natural language, just like you’d speak to ChatGPT. Get started by syncing a repo, then open the repo and start chatting.\",\n\t\"Ask your first question\": \"Ask your first question\",\n\t\"Assistant\": \"Assistant\",\n\t\"Back to current\": \"Back to current\",\n\t\"Back\": \"Back\",\n\t\"Bad response\": \"Bad response\",\n\t\"Bad\": \"Bad\",\n\t\"Below are a few suggestions you can ask me to get started:\": \"Below are a few suggestions you can ask me to get started:\",\n\t\"Black\": \"Black\",\n\t\"Bloop needs to index your repository first. This process takes a few seconds and happens only one time per repository.\": \"Bloop needs to index your repository first. This process takes a few seconds and happens only one time per repository.\",\n\t\"Branches\": \"Branches\",\n\t\"By continuing you accept our\": \"By continuing you accept our\",\n\t\"By submitting this crash report you agree to send it to bloop for investigation.\": \"By submitting this crash report you agree to send it to bloop for investigation.\",\n\t\"Cancel diff generation\": \"Cancel diff generation\",\n\t\"Cancel\": \"Cancel\",\n\t\"Cancelled\": \"Cancelled\",\n\t\"Cancelling...\": \"Cancelling...\",\n\t\"Change application colour theme\": \"Change application colour theme\",\n\t\"Chat conversations\": \"Chat conversations\",\n\t\"Clear conversation\": \"Clear conversation\",\n\t\"Clear input\": \"Clear input\",\n\t\"Clear range\": \"Clear range\",\n\t\"Clear ranges\": \"Clear ranges\",\n\t\"Clear section\": \"Clear section\",\n\t\"Clear sections\": \"Clear sections\",\n\t\"Clear selection\": \"Clear selection\",\n\t\"Click on an identifier and jump to its references and definition in a heart beat.\": \"Click on an identifier and jump to its references and definition in a heart beat.\",\n\t\"Click to copy\": \"Click to copy\",\n\t\"Cloning\": \"Cloning\",\n\t\"Cloning...\": \"Cloning...\",\n\t\"Close all open tabs\": \"Close all open tabs\",\n\t\"Close all tabs\": \"Close all tabs\",\n\t\"Close current tab\": \"Close current tab\",\n\t\"Close currently focused tab\": \"Close currently focused tab\",\n\t\"Close search\": \"Close search\",\n\t\"Close tab\": \"Close tab\",\n\t\"Close\": \"Close\",\n\t\"Code search\": \"Code search\",\n\t\"Code studio helps you write scripts, create unit tests, debug issues or generate anything else you can think of using AI! Sync a repo, then create a code studio project.\": \"Code studio helps you write scripts, create unit tests, debug issues or generate anything else you can think of using AI! Sync a repo, then create a code studio project.\",\n\t\"Collapse\": \"Collapse\",\n\t\"Commands\": \"Commands\",\n\t\"Complete your transaction in Stripe...\": \"Complete your transaction in Stripe...\",\n\t\"Confirm\": \"Confirm\",\n\t\"Connect account\": \"Connect account\",\n\t\"Context files\": \"Context files\",\n\t\"Continue from this state\": \"Continue from this state\",\n\t\"Continue\": \"Continue\",\n\t\"Copied\": \"Copied\",\n\t\"Copy link\": \"Copy link\",\n\t\"Copy message\": \"Copy message\",\n\t\"Copy\": \"Copy\",\n\t\"Create line ranges\": \"Create line ranges\",\n\t\"Create new project\": \"Create new project\",\n\t\"Create new\": \"Create new\",\n\t\"Create project\": \"Create project\",\n\t\"Create ranges\": \"Create ranges\",\n\t\"Create template\": \"Create template\",\n\t\"Current\": \"Current\",\n\t\"Currently active\": \"Currently active\",\n\t\"Dark\": \"Dark\",\n\t\"Default project\": \"Default project\",\n\t\"Delete all conversations\": \"Delete all conversations\",\n\t\"Delete conversation\": \"Delete conversation\",\n\t\"Delete project\": \"Delete project\",\n\t\"Delete template\": \"Delete template\",\n\t\"Delete\": \"Delete\",\n\t\"Desktop app\": \"Desktop app\",\n\t\"Diff generation failed\": \"Diff generation failed\",\n\t\"Directories\": \"Directories\",\n\t\"Disconnect\": \"Disconnect\",\n\t\"Discord\": \"Discord\",\n\t\"Dismiss tutorial\": \"Dismiss tutorial\",\n\t\"Display\": \"Display\",\n\t\"Docs\": \"Docs\",\n\t\"Documentation URL...\": \"Documentation URL...\",\n\t\"Documentation in studio\": \"Documentation in studio\",\n\t\"Documentation\": \"Documentation\",\n\t\"Done\": \"Done\",\n\t\"Downgrade\": \"Downgrade\",\n\t\"Downvote\": \"Downvote\",\n\t\"Edit ranges\": \"Edit ranges\",\n\t\"Edit sections\": \"Edit sections\",\n\t\"Edit selected lines\": \"Edit selected lines\",\n\t\"Edit selected sections\": \"Edit selected sections\",\n\t\"Edit template\": \"Edit template\",\n\t\"Edit\": \"Edit\",\n\t\"Editing previously submitted questions will discard all answers and questions following it\": \"Editing previously submitted questions will discard all answers and questions following it\",\n\t\"Email address\": \"Email address\",\n\t\"Email is not valid\": \"Email is not valid\",\n\t\"Email is required\": \"Email is required\",\n\t\"Email\": \"Email\",\n\t\"Error\": \"Error\",\n\t\"Existing studio conversations\": \"Existing studio conversations\",\n\t\"Expand\": \"Expand\",\n\t\"Experimental: Faster but less accurate\": \"Experimental: Faster but less accurate\",\n\t\"Explain a file\": \"Explain a file\",\n\t\"Explain code\": \"Explain code\",\n\t\"Explain current file\": \"Explain current file\",\n\t\"Explain file\": \"Explain file\",\n\t\"Explain the purpose of the file {{filePath}}, from lines {{lineStart}} - {{lineEnd}}\": \"Explain the purpose of the file {{filePath}}, from lines {{lineStart}} - {{lineEnd}}\",\n\t\"Explain\": \"Explain\",\n\t\"Failed to apply the diff\": \"Failed to apply the diff\",\n\t\"Failed to get a response from OpenAI. Try again in a few moments.\": \"Failed to get a response from OpenAI. Try again in a few moments.\",\n\t\"Fast\": \"Fast\",\n\t\"File not indexed\": \"File not indexed\",\n\t\"File search\": \"File search\",\n\t\"File\": \"File\",\n\t\"Files\": \"Files\",\n\t\"Files, paths or folders...\": \"Files, paths or folders...\",\n\t\"Filter repositories by\": \"Filter repositories by\",\n\t\"Filters\": \"Filters\",\n\t\"First name is required\": \"First name is required\",\n\t\"First name\": \"First name\",\n\t\"Fold everything\": \"Fold everything\",\n\t\"Folder too large\": \"Folder too large\",\n\t\"Follow us on Twitter\": \"Follow us on Twitter\",\n\t\"Force index\": \"Force index\",\n\t\"Free\": \"Free\",\n\t\"General\": \"General\",\n\t\"Generate code using AI\": \"Generate code using AI\",\n\t\"Generate\": \"Generate\",\n\t\"Generated diffs to be applied\": \"Generated diffs to be applied\",\n\t\"Generating answer...\": \"Generating answer...\",\n\t\"Generating diff...\": \"Generating diff...\",\n\t\"Generating response...\": \"Generating response...\",\n\t\"GitHub Branches\": \"GitHub Branches\",\n\t\"GitHub\": \"GitHub\",\n\t\"Give your template a title\": \"Give your template a title\",\n\t\"Go back\": \"Go back\",\n\t\"Go to current state\": \"Go to current state\",\n\t\"Good\": \"Good\",\n\t\"Got it\": \"Got it\",\n\t\"Hi, I am bloop! In <2><0></0>Chat mode</2> I can answer any questions related to any of your repositories.\": \"Hi, I am bloop! In <2><0></0>Chat mode</2> I can answer any questions related to any of your repositories.\",\n\t\"Hi, I am bloop! In <2><0></0>Studio mode</2> you can choose files from your codebase, write a prompt and generate patches, scripts and tests.\": \"Hi, I am bloop! In <2><0></0>Studio mode</2> you can choose files from your codebase, write a prompt and generate patches, scripts and tests.\",\n\t\"Hide\": \"Hide\",\n\t\"History\": \"History\",\n\t\"How fast or precise bloop's answers will be.\": \"How fast or precise bloop's answers will be.\",\n\t\"In this file\": \"In this file\",\n\t\"In this project\": \"In this project\",\n\t\"Index multiple branches\": \"Index multiple branches\",\n\t\"Index repository\": \"Index repository\",\n\t\"Index\": \"Index\",\n\t\"Indexed documentation web pages\": \"Indexed documentation web pages\",\n\t\"Indexed local repositories\": \"Indexed local repositories\",\n\t\"Indexed\": \"Indexed\",\n\t\"Indexing in progress\": \"Indexing in progress\",\n\t\"Indexing repositories\": \"Indexing repositories\",\n\t\"Indexing repository\": \"Indexing repository\",\n\t\"Indexing\": \"Indexing\",\n\t\"Indexing...\": \"Indexing...\",\n\t\"Individual\": \"Individual\",\n\t\"Invert\": \"Invert\",\n\t\"Join Discord\": \"Join Discord\",\n\t\"Language\": \"Language\",\n\t\"Languages\": \"Languages\",\n\t\"Last name is required\": \"Last name is required\",\n\t\"Last name\": \"Last name\",\n\t\"Last updated \": \"Last updated \",\n\t\"Latest model updates\": \"Latest model updates\",\n\t\"Launch manually\": \"Launch manually\",\n\t\"Let’s get you started with bloop!\": \"Let’s get you started with bloop!\",\n\t\"Light\": \"Light\",\n\t\"Lines # - #\": \"Lines {{start}} - {{end}}\",\n\t\"Loading...\": \"Loading...\",\n\t\"Local repositories\": \"Local repositories\",\n\t\"Local repository\": \"Local repository\",\n\t\"Local\": \"Local\",\n\t\"Manage context\": \"Manage context\",\n\t\"Manage docs\": \"Manage docs\",\n\t\"Manage project\": \"Manage project\",\n\t\"Manage repositories\": \"Manage repositories\",\n\t\"Manage subscription\": \"Manage subscription\",\n\t\"Manage templates\": \"Manage templates\",\n\t\"Manage your general account settings\": \"Manage your general account settings\",\n\t\"Manage your general project settings\": \"Manage your general project settings\",\n\t\"Manage your preferences\": \"Manage your preferences\",\n\t\"Manage your studio settings.\": \"Manage your studio settings.\",\n\t\"Manage your subscription plan\": \"Manage your subscription plan\",\n\t\"Manage\": \"Manage\",\n\t\"Missing source\": \"Missing source\",\n\t\"More actions\": \"More actions\",\n\t\"Move\": \"Move\",\n\t\"My templates\": \"My templates\",\n\t\"Name\": \"Name\",\n\t\"Natural language search\": \"Natural language search\",\n\t\"Navigate your codebase\": \"Navigate your codebase\",\n\t\"Navigate\": \"Navigate\",\n\t\"New conversation\": \"New conversation\",\n\t\"New project\": \"New project\",\n\t\"New studio conversation\": \"New studio conversation\",\n\t\"New tab\": \"New tab\",\n\t\"New\": \"New\",\n\t\"Next page\": \"Next page\",\n\t\"Next\": \"Next\",\n\t\"No change in payment status identified.\": \"No change in payment status identified.\",\n\t\"No commands found...\": \"No commands found...\",\n\t\"No file selected\": \"No file selected\",\n\t\"No files found...\": \"No files found...\",\n\t\"No references or definitions found\": \"No references or definitions found\",\n\t\"No repositories found...\": \"No repositories found...\",\n\t\"No results\": \"No results\",\n\t\"No uses left. Uses reset at\": \"No uses left. Uses reset at\",\n\t\"Normal\": \"Normal\",\n\t\"Not a git repository\": \"Not a git repository\",\n\t\"Not synced\": \"Not synced\",\n\t\"Open Command Bar\": \"Open Command Bar\",\n\t\"Open account settings\": \"Open account settings\",\n\t\"Open in GitHub\": \"Open in GitHub\",\n\t\"Open in split view\": \"Open in split view\",\n\t\"Open in {{viewer}}\": \"Open in {{viewer}}\",\n\t\"Open subscription settings\": \"Open subscription settings\",\n\t\"Open\": \"Open\",\n\t\"Paste a link to any documentation web page\": \"Paste a link to any documentation web page\",\n\t\"Paste a link to any public repository hosted on GitHub\": \"Paste a link to any public repository hosted on GitHub\",\n\t\"Permanently delete <2>{{projectName}}</2> and remove all the data associated to it. Repositories will remain accessible in your GitHub account.\": \"Permanently delete <2>{{projectName}}</2> and remove all the data associated to it. Repositories will remain accessible in your GitHub account.\",\n\t\"Personal\": \"Personal\",\n\t\"Plans\": \"Plans\",\n\t\"Please log into your GitHub account to complete setup, this helps us combat misuse.\": \"Please log into your GitHub account to complete setup, this helps us combat misuse.\",\n\t\"Precise code navigation\": \"Precise code navigation\",\n\t\"Preferences\": \"Preferences\",\n\t\"Press <2>cmdKey</2> <4>K</4> on your keyboard to open the Command bar and add a repository.\": \"Press <2>{{cmdKey}}</2> <4>K</4> on your keyboard to open the Command bar and add a repository.\",\n\t\"Press <2>cmdKey</2> <4>K</4> on your keyboard to open the Command bar.\": \"Press <2>{{cmdKey}}</2> <4>K</4> on your keyboard to open the Command bar.\",\n\t\"Privacy policy\": \"Privacy policy\",\n\t\"Private repositories\": \"Private repositories\",\n\t\"Private repository\": \"Private repository\",\n\t\"Private\": \"Private\",\n\t\"Problem details and System configuration\": \"Problem details and System configuration\",\n\t\"Project settings\": \"Project settings\",\n\t\"Project title\": \"Project title\",\n\t\"Project\": \"Project\",\n\t\"Projects require at least one synced repository.\": \"Projects require at least one synced repository.\",\n\t\"Projects\": \"Projects\",\n\t\"Prompt guide\": \"Prompt guide\",\n\t\"Prompt templates\": \"Prompt templates\",\n\t\"Prompt\": \"Prompt\",\n\t\"Prompts\": \"Prompts\",\n\t\"Provide a short, concise title for your project\": \"Provide a short, concise title for your project\",\n\t\"Provide any steps necessary to reproduce the problem...\": \"Provide any steps necessary to reproduce the problem...\",\n\t\"Public repositories\": \"Public repositories\",\n\t\"Public repository\": \"Public repository\",\n\t\"Public\": \"Public\",\n\t\"Query suggestions\": \"Query suggestions\",\n\t\"Queued...\": \"Queued...\",\n\t\"Re-check payment status\": \"Re-check payment status\",\n\t\"Re-sync\": \"Re-sync\",\n\t\"Reading \": \"Reading \",\n\t\"Reading\": \"Reading\",\n\t\"Recent conversations\": \"Recent conversations\",\n\t\"Recent projects\": \"Recent projects\",\n\t\"Recently used\": \"Recently used\",\n\t\"Recommended: The classic response type\": \"Recommended: The classic response type\",\n\t\"References\": \"References\",\n\t\"Referencing target file\": \"Referencing target file\",\n\t\"Release to open in split view\": \"Release to open in split view\",\n\t\"Remote removed \": \"Remote removed \",\n\t\"Remove file from code studio context\": \"Remove file from code studio context\",\n\t\"Remove file\": \"Remove file\",\n\t\"Remove from project\": \"Remove from project\",\n\t\"Remove from studio\": \"Remove from studio\",\n\t\"Remove page from code studio context\": \"Remove page from code studio context\",\n\t\"Remove\": \"Remove\",\n\t\"Removed\": \"Removed\",\n\t\"Rename code studio\": \"Rename code studio\",\n\t\"Rename\": \"Rename\",\n\t\"Report a bug\": \"Report a bug\",\n\t\"Repositories\": \"Repositories\",\n\t\"Repository URL...\": \"Repository URL...\",\n\t\"Repository indexed\": \"Repository indexed\",\n\t\"Repository types\": \"Repository types\",\n\t\"Restart the app\": \"Restart the app\",\n\t\"Restore session\": \"Restore session\",\n\t\"Restore\": \"Restore\",\n\t\"Result suggestions\": \"Result suggestions\",\n\t\"Results\": \"Results\",\n\t\"Retry\": \"Retry\",\n\t\"Save changes\": \"Save changes\",\n\t\"Save context changes before answer generation\": \"Save context changes before answer generation\",\n\t\"Save\": \"Save\",\n\t\"Search branch...\": \"Search branch...\",\n\t\"Search branches...\": \"Search branches...\",\n\t\"Search code in natural language\": \"Search code in natural language\",\n\t\"Search docs\": \"Search docs\",\n\t\"Search docs...\": \"Search docs...\",\n\t\"Search file...\": \"Search file...\",\n\t\"Search files\": \"Search files\",\n\t\"Search files...\": \"Search files...\",\n\t\"Search for code using regex\": \"Search for code using regex\",\n\t\"Search local repos...\": \"Search local repos...\",\n\t\"Search pages\": \"Search pages\",\n\t\"Search private repos...\": \"Search private repos...\",\n\t\"Search projects or commands...\": \"Search projects or commands...\",\n\t\"Search public repositories...\": \"Search public repositories...\",\n\t\"Search repos or Studio projects...\": \"Search repos or Studio projects...\",\n\t\"Search studio conversations...\": \"Search studio conversations...\",\n\t\"Search templates...\": \"Search templates...\",\n\t\"Search using RegExp\": \"Search using RegExp\",\n\t\"Search your files in this project\": \"Search your files in this project\",\n\t\"Search your repositories using RegExp\": \"Search your repositories using RegExp\",\n\t\"Search\": \"Search\",\n\t\"Searching...\": \"Searching...\",\n\t\"Select a file or open a new tab to display it here.\": \"Select a file or open a new tab to display it here.\",\n\t\"Select a folder containing a git repository\": \"Select a folder containing a git repository\",\n\t\"Select all\": \"Select all\",\n\t\"Select branch\": \"Select branch\",\n\t\"Select color theme:\": \"Select color theme:\",\n\t\"Select file\": \"Select file\",\n\t\"Select folder\": \"Select folder\",\n\t\"Select less code\": \"Select less code\",\n\t\"Select library\": \"Select library\",\n\t\"Select page\": \"Select page\",\n\t\"Select repository\": \"Select repository\",\n\t\"Select section\": \"Select section\",\n\t\"Select sections\": \"Select sections\",\n\t\"Select the interface colour scheme\": \"Select the interface colour scheme\",\n\t\"Select the interface language\": \"Select the interface language\",\n\t\"Select\": \"Select\",\n\t\"Selected lines # - #.\": \"Selected lines {{start}} - {{end}}.\",\n\t\"Selected ranges will be used as context.\": \"Selected ranges will be used as context.\",\n\t\"Settings\": \"Settings\",\n\t\"Setup bloop\": \"Setup bloop\",\n\t\"Show # more match_one\": \"Show {{count}} more match\",\n\t\"Show # more match_other\": \"Show {{count}} more matches\",\n\t\"Show file\": \"Show file\",\n\t\"Show filters\": \"Show filters\",\n\t\"Show less\": \"Show less\",\n\t\"Show link\": \"Show link\",\n\t\"Show more\": \"Show more\",\n\t\"Show search steps\": \"Show search steps\",\n\t\"Show\": \"Show\",\n\t\"Sign In\": \"Sign In\",\n\t\"Sign in with GitHub\": \"Sign in with GitHub\",\n\t\"Sign out\": \"Sign out\",\n\t\"Skip (Not recommended)\": \"Skip (Not recommended)\",\n\t\"Skip\": \"Skip\",\n\t\"Something went wrong\": \"Something went wrong\",\n\t\"Start by selecting \": \"Start by selecting \",\n\t\"Start by selecting a repository and pressing Enter (↵) on your keyboard.\": \"Start by selecting a repository and pressing Enter (↵) on your keyboard.\",\n\t\"Start by selecting a type repository you would like to index.\": \"Start by selecting a type repository you would like to index.\",\n\t\"Start indexing\": \"Start indexing\",\n\t\"Start typing...\": \"Start typing...\",\n\t\"Status\": \"Status\",\n\t\"Stop generating\": \"Stop generating\",\n\t\"Stop indexing\": \"Stop indexing\",\n\t\"Streaming response...\": \"Streaming response...\",\n\t\"Studio conversation require at least one context file.\": \"Studio conversation require at least one context file.\",\n\t\"Studio conversation\": \"Studio conversation\",\n\t\"Studio conversations\": \"Studio conversations\",\n\t\"Studio mode\": \"Studio mode\",\n\t\"Studio project\": \"Studio project\",\n\t\"Studio requests\": \"Studio requests\",\n\t\"Studio\": \"Studio\",\n\t\"Submit bug report\": \"Submit bug report\",\n\t\"Submit crash report\": \"Submit crash report\",\n\t\"Submit\": \"Submit\",\n\t\"Subscription\": \"Subscription\",\n\t\"Switch branch\": \"Switch branch\",\n\t\"Switch to\": \"Switch to\",\n\t\"Sync local git repos\": \"Sync local git repos\",\n\t\"Sync\": \"Sync\",\n\t\"Synced\": \"Synced\",\n\t\"Syncing\": \"Syncing\",\n\t\"System Preference\": \"System Preference\",\n\t\"System preferences\": \"System preferences\",\n\t\"System\": \"System\",\n\t\"Take a quick look\": \"Take a quick look\",\n\t\"Template title\": \"Template title\",\n\t\"Templates\": \"Templates\",\n\t\"Terms & conditions\": \"Terms & conditions\",\n\t\"The amount of times you can generate responses in Studio conversations per day.\": \"The amount of times you can generate responses in Studio conversations per day.\",\n\t\"The diff has been applied locally.\": \"The diff has been applied locally.\",\n\t\"The folder you selected has multiple git repositories nested inside.\": \"The folder you selected has multiple git repositories nested inside.\",\n\t\"The folder you selected is not a git repository.\": \"The folder you selected is not a git repository.\",\n\t\"The following changes can be applied to your repository. Make sure the generated diffs are valid before you apply the changes.\": \"The following changes can be applied to your repository. Make sure the generated diffs are valid before you apply the changes.\",\n\t\"The following changes represent the git diff for the remote repository. Please note that these changes cannot be applied directly to a remote repository. Use the \\\"Copy\\\" button to copy the changes and apply them locally.\": \"The following changes represent the git diff for the remote repository. Please note that these changes cannot be applied directly to a remote repository. Use the \\\"Copy\\\" button to copy the changes and apply them locally.\",\n\t\"The line of code where identifier is defined\": \"The line of code where identifier is defined\",\n\t\"The line of code where the identifier is referenced\": \"The line of code where the identifier is referenced\",\n\t\"The whole file will be used as context.\": \"The whole file will be used as context.\",\n\t\"Theme\": \"Theme\",\n\t\"This is not a public repository / We couldn't find this repository\": \"This is not a public repository / We couldn't find this repository\",\n\t\"This might be because the file is too big or it has one of bloop's excluded file types.\": \"This might be because the file is too big or it has one of bloop's excluded file types.\",\n\t\"This project is empty\": \"This project is empty\",\n\t\"Tip: Select code to create ranges for context use.\": \"Tip: Select code to create ranges for context use.\",\n\t\"To update your app, please visit our releases page on GitHub and download the latest version manually. Thank you for using bloop.\": \"To update your app, please visit our releases page on GitHub and download the latest version manually. Thank you for using bloop.\",\n\t\"Today\": \"Today\",\n\t\"Toggle black theme\": \"Toggle black theme\",\n\t\"Toggle dark theme\": \"Toggle dark theme\",\n\t\"Toggle light theme\": \"Toggle light theme\",\n\t\"Toggle regex search\": \"Toggle regex search\",\n\t\"Toggle system theme\": \"Toggle system theme\",\n\t\"Toggle\": \"Toggle\",\n\t\"Token limit exceeded\": \"Token limit exceeded\",\n\t\"Token limit exceeded. Reduce the number of context files or messages to enable the ability to generate.\": \"Token limit exceeded. Reduce the number of context files or messages to enable the ability to generate.\",\n\t\"Token limit reached, this answer may be incomplete. To generate a full answer, please reduce the number of tokens used and regenerate.\": \"Token limit reached, this answer may be incomplete. To generate a full answer, please reduce the number of tokens used and regenerate.\",\n\t\"Try again\": \"Try again\",\n\t\"Unavailable\": \"Unavailable\",\n\t\"Unlimited code studio requests\": \"Unlimited code studio requests\",\n\t\"Unlimited usage and premium features are activated.\": \"Unlimited usage and premium features are activated.\",\n\t\"Unlock the value of your existing code, using AI\": \"Unlock the value of your existing code, using AI\",\n\t\"Untitled project\": \"Untitled project\",\n\t\"Update Required\": \"Update Required\",\n\t\"Upgrade now\": \"Upgrade now\",\n\t\"Upgrade plan\": \"Upgrade plan\",\n\t\"Upgrade to Personal plan\": \"Upgrade to Personal plan\",\n\t\"Upgrade\": \"Upgrade\",\n\t\"Upvote\": \"Upvote\",\n\t\"Usage exceeded\": \"Usage exceeded\",\n\t\"Usage resets at\": \"Usage resets at\",\n\t\"Usage resets in\": \"Usage resets in\",\n\t\"Usage status\": \"Usage status\",\n\t\"Use GitHub to sign in to your account\": \"Use GitHub to sign in to your account\",\n\t\"Use black theme\": \"Use black theme\",\n\t\"Use dark theme\": \"Use dark theme\",\n\t\"Use file\": \"Use file\",\n\t\"Use light theme\": \"Use light theme\",\n\t\"Use system theme\": \"Use system theme\",\n\t\"Use template\": \"Use template\",\n\t\"Use templates\": \"Use templates\",\n\t\"Use\": \"Use\",\n\t\"User\": \"User\",\n\t\"Verifying access...\": \"Verifying access...\",\n\t\"Verifying link...\": \"Verifying link...\",\n\t\"View all results\": \"View all results\",\n\t\"View all\": \"View all\",\n\t\"View bloop app documentation on our website\": \"View bloop app documentation on our website\",\n\t\"View history\": \"View history\",\n\t\"View in {{viewer}}\": \"View in {{viewer}}\",\n\t\"View\": \"View\",\n\t\"Visit the downloads page\": \"Visit the downloads page\",\n\t\"Waiting for authentication...\": \"Waiting for authentication...\",\n\t\"Watch\": \"Watch\",\n\t\"We can’t generate a response because some files have a missing source in your Context files.\": \"We can’t generate a response because some files have a missing source in your Context files.\",\n\t\"We couldn't answer your question. You can try asking again in a few moments, or rephrasing your question.\": \"We couldn't answer your question. You can try asking again in a few moments, or rephrasing your question.\",\n\t\"We couldn't find any docs at that link. Try again or make sure the link is correct!\": \"We couldn't find any docs at that link. Try again or make sure the link is correct!\",\n\t\"We want to make this the best experience for you. If you encountered a bug, please submit this bug report to us. Our team will investigate as soon as possible.\": \"We want to make this the best experience for you. If you encountered a bug, please submit this bug report to us. Our team will investigate as soon as possible.\",\n\t\"We weren't able to identify any references at the moment\": \"We weren't able to identify any references at the moment\",\n\t\"We've made some exciting enhancements to bloop! To continue enjoying the full functionality, including the natural language search feature, please update your app to the latest version.\": \"We've made some exciting enhancements to bloop! To continue enjoying the full functionality, including the natural language search feature, please update your app to the latest version.\",\n\t\"We've redirected you to Stripe to complete your transaction. <2>Launch manually</2> if it didn't work.\": \"We've redirected you to Stripe to complete your transaction. <2>Launch manually</2> if it didn't work.\",\n\t\"Welcome to bloop\": \"Welcome to bloop\",\n\t\"We’ve updated our auth service to make bloop more secure, please reauthorise your client with GitHub\": \"We’ve updated our auth service to make bloop more secure, please reauthorise your client with GitHub\",\n\t\"Whole file\": \"Whole file\",\n\t\"Whole page\": \"Whole page\",\n\t\"Write a message, @ to mention files, folders or docs...\": \"Write a message, @ to mention files, folders or docs...\",\n\t\"Write studio prompts faster with pre-written templates\": \"Write studio prompts faster with pre-written templates\",\n\t\"Write your prompt...\": \"Write your prompt...\",\n\t\"Yesterday\": \"Yesterday\",\n\t\"You can add 3 types of repositories, private, public and local.\": \"You can add 3 types of repositories, private, public and local.\",\n\t\"You\": \"You\",\n\t\"You've run out of free usage for today, please wait for your quota to reset or upgrade for unlimited usage\": \"You've run out of free usage for today, please wait for your quota to reset or upgrade for unlimited usage\",\n\t\"You've upgraded your account!\": \"You've upgraded your account!\",\n\t\"Your quota resets every 24 hours, upgrade for unlimited uses\": \"Your quota resets every 24 hours, upgrade for unlimited uses\",\n\t\"Your subscription has expired. Please update your payment details to avoid being unsubscribed.\": \"Your subscription has expired. Please update your payment details to avoid being unsubscribed.\",\n\t\"and \": \"and \",\n\t\"avatar\": \"avatar\",\n\t\"billed monthly\": \"billed monthly\",\n\t\"bloop automatically excludes certain files from indexing. This file might be too big or it might have an excluded file type.\": \"bloop automatically excludes certain files from indexing. This file might be too big or it might have an excluded file type.\",\n\t\"bloop automatically excludes certain files from the indexing. This file might be too big or it might have an excluded file type.\": \"bloop automatically excludes certain files from the indexing. This file might be too big or it might have an excluded file type.\",\n\t\"bloop crashed unexpectedly\": \"bloop crashed unexpectedly\",\n\t\"cancelled\": \"cancelled\",\n\t\"cancelling\": \"cancelling\",\n\t\"chats in bloop\": \"chats in bloop\",\n\t\"definition\": \"definition\",\n\t\"done\": \"done\",\n\t\"indexing\": \"indexing\",\n\t\"key\": \"key\",\n\t\"or \": \"or \",\n\t\"or go to the following link\": \"or go to the following link\",\n\t\"or visit: \": \"or visit: \",\n\t\"reference\": \"reference\",\n\t\"syncing\": \"syncing\",\n\t\"to seamlessly explore code across all branches in your GitHub repositories, maximizing your code discovery capabilities.\": \"to seamlessly explore code across all branches in your GitHub repositories, maximizing your code discovery capabilities.\",\n\t\"uses left_one\": \"use left\",\n\t\"uses left_other\": \"uses left\",\n\t\"{{count}} context files used\": \"{{count}} context files used\",\n\t\"{{count}} context files used_one\": \"{{count}} context file used\",\n\t\"{{count}} context files used_other\": \"{{count}} context files used\",\n\t\"{{repoName}} has finished indexing and you can use it in your projects now.\": \"{{repoName}} has finished indexing and you can use it in your projects now.\",\n\t\"{{repoName}} is currently indexing as soon as it is finished it will be added to your project.\": \"{{repoName}} is currently indexing as soon as it is finished it will be added to your project.\",\n\t\"Hide search steps\": \"Hide search steps\",\n\t\"Add documentation\": \"Add documentation\",\n\t\"<0>{{repoName}}</0> has finished indexing and can be added to your projects. Click the button to below to add it to the current project.\": \"<0>{{repoName}}</0> has finished indexing and can be added to your projects. Click the button to below to add it to the current project.\",\n\t\"Start by selecting again and pressing Enter (↵) on your keyboard.\": \"Start by selecting again and pressing Enter (↵) on your keyboard.\",\n\t\"{{repoName}} is currently indexing as soon as it is finished you will be able to add it to your project.\": \"{{repoName}} is currently indexing as soon as it is finished you will be able to add it to your project.\",\n\t\"Select the input type to use in conversations\": \"Select the input type to use in conversations\",\n\t\"Conversation input\": \"Conversation input\",\n\t\"Default\": \"Default\",\n\t\"Simplified\": \"Simplified\",\n\t\"Recommended: The classic input\": \"Recommended: The classic input\",\n\t\"Fallback: Use if experiencing problems with the default one\": \"Fallback: Use if experiencing problems with the default one\",\n\t\"Add multiple files\": \"Add multiple files\",\n\t\"Explain lines {{lineStart}} - {{lineEnd}} in {{filePath}}\": \"Explain lines {{lineStart}} - {{lineEnd}} in {{filePath}}\"\n}"
  },
  {
    "path": "client/src/locales/es.json",
    "content": "{\n\t\"# match_one\": \"{{count}} coincidencia\",\n\t\"# match_other\": \"{{count}} coincidencias\",\n\t\"# of #_one\": \"{{count}} de {{total}}\",\n\t\"# of #_other\": \"{{count}} de {{total}}\",\n\t\"# ranges_one\": \"{{count}} ranges\",\n\t\"# ranges_other\": \"{{count}} ranges\",\n\t\"# sections_one\": \"{{count}} sección\",\n\t\"# sections_other\": \"{{count}} secciones\",\n\t\"# selected section_one\": \"{{count}} sección seleccionada\",\n\t\"# selected section_other\": \"{{count}} secciónes seleccionadas\",\n\t\"# tokens_onw\": \"ficha\",\n\t\"# tokens_other\": \"fichas\",\n\t\"$20 / billed monthly\": \"$ 20 / facturado mensualmente\",\n\t\"<0>#</0> of # tokens\": \"<0>{{count}}</0> de {{total}} fichas\",\n\t\"<0>#</0> of # tokens_one\": \"<0>{{count}}</0> de {{total}} fichas\",\n\t\"<0>#</0> of # tokens_other\": \"<0>{{count}}</0> de {{total}} fichas\",\n\t\"<0></0><1></1> to navigate.\": \"<0> </0> <1> </1> para navegar.\",\n\t\"<0>Create a new conversation with bloop by hitting <2></2> on your keyboard or by pressing the <4></4> in the header bar.</0>\": \"<0> Crea una nueva conversación con Bloop presionando <2> </2> en tu teclado o presionando el <4> </4> en la barra de encabezado. </0>\",\n\t\"<0>To begin, open a file from the sidebar on the left. Once you have a file open, you can ask bloop to quickly explain it by hitting <2></2> on your keyboard or by selecting \\\"Explain file\\\" from the <7></7> popup menu.</0>\": \"<0> Para comenzar, abra un archivo desde la barra lateral a la izquierda. Una vez que tenga un archivo abierto, puede pedirle a Bloop que lo explique rápidamente presionando <2> </2> en su teclado o seleccionando \\\"Explicar archivo\\\" en el menú emergente <7> </7> emergente. </0>\",\n\t\"<0>repoName</0> has finished indexing and was added to the context of the current project. You can also use it in your other projects now.\": \"<0>{{repoName}}</0> ha terminado de indexación y se agregó al contexto del proyecto actual. También puede usarlo en sus otros proyectos ahora.\",\n\t\"Account settings\": \"Configuraciones de la cuenta\",\n\t\"Actions\": \"Comportamiento\",\n\t\"Add a repository from your local machine\": \"Añada un repositorio local\",\n\t\"Add any library documentation\": \"Agregue cualquier documentación de la biblioteca\",\n\t\"Add at least one context file before submitting your first request.\": \"Agregue al menos un archivo de contexto antes de enviar su primera solicitud.\",\n\t\"Add doc to studio\": \"Agregar documento al estudio\",\n\t\"Add docs\": \"Agregar documentos\",\n\t\"Add file to code studio context\": \"Agregar archivo al contexto de código de código\",\n\t\"Add file to studio\": \"Agregar archivo al estudio\",\n\t\"Add file\": \"Agregar archivo\",\n\t\"Add files to studio\": \"Agregar archivos a Studio\",\n\t\"Add files\": \"Agregar archivos\",\n\t\"Add local repository\": \"Agregar repositorio local\",\n\t\"Add new repository\": \"Agregar nuevo repositorio\",\n\t\"Add private repository\": \"Agregar repositorio privado\",\n\t\"Add public repository\": \"Agregar repositorio público\",\n\t\"Add repository\": \"Agregar repositorio\",\n\t\"Add to existing\": \"Agregar a la existencia\",\n\t\"Add to project\": \"Agregar al proyecto\",\n\t\"Add to studio\": \"Agregar al estudio\",\n\t\"Add\": \"Añadir\",\n\t\"Adding repository\": \"Agregar repositorio\",\n\t\"All payments, invoices and billing information are managed in Stripe.\": \"Todos los pagos, facturas e información de facturación se administran en Stripe.\",\n\t\"All sections\": \"Todas las secciones\",\n\t\"All templates\": \"Todas las plantillas\",\n\t\"All\": \"Toda\",\n\t\"Answer speed\": \"Velocidad de respuesta\",\n\t\"Application theme\": \"Tema de la aplicación\",\n\t\"Apply changes\": \"Aplicar cambios\",\n\t\"Ask questions about your codebases in natural language, just like you’d speak to ChatGPT. Get started by syncing a repo, then open the repo and start chatting.\": \"Haga preguntas sobre sus bases de código en lenguaje natural, al igual que hablaría con ChatGPT. Comience sincronizando un repositorio, luego abra el repositorio y comience a chatear.\",\n\t\"Ask your first question\": \"Haz tu primera pregunta\",\n\t\"Assistant\": \"Asistente\",\n\t\"Back to current\": \"Volver a la corriente\",\n\t\"Back\": \"Atrás\",\n\t\"Bad response\": \"Mala respuesta\",\n\t\"Bad\": \"Mala\",\n\t\"Below are a few suggestions you can ask me to get started:\": \"A continuación hay algunas sugerencias que puede pedirme que comience:\",\n\t\"Black\": \"Negra\",\n\t\"Bloop needs to index your repository first. This process takes a few seconds and happens only one time per repository.\": \"Bloop necesita indexar primero su repositorio. Este proceso toma unos segundos y ocurre solo una vez por repositorio.\",\n\t\"Branches\": \"Sucursales\",\n\t\"By continuing you accept our\": \"Al continuar, aceptas nuestros\",\n\t\"By submitting this crash report you agree to send it to bloop for investigation.\": \"Al enviar este informe de error, está aceptando que bloop pueda investigarlo.\",\n\t\"Cancel diff generation\": \"Cancelar la generación de la diferencia\",\n\t\"Cancel\": \"Cancelar\",\n\t\"Cancelled\": \"Cancelado\",\n\t\"Cancelling...\": \"Cancelando...\",\n\t\"Change application colour theme\": \"Cambiar el tema del color de la aplicación\",\n\t\"Chat conversations\": \"Conversaciones de chat\",\n\t\"Clear conversation\": \"Borrar conversación\",\n\t\"Clear input\": \"Borrar entrada\",\n\t\"Clear range\": \"Borrar rango\",\n\t\"Clear ranges\": \"Borrar rangos\",\n\t\"Clear section\": \"Borrar sección\",\n\t\"Clear sections\": \"Secciones claras\",\n\t\"Clear selection\": \"Selección clara\",\n\t\"Click to copy\": \"Haga clic para copiar\",\n\t\"Cloning\": \"Clonación\",\n\t\"Cloning...\": \"Clonando...\",\n\t\"Close all open tabs\": \"Cerrar todas las pestañas abiertas\",\n\t\"Close all tabs\": \"Cierra todas las pestañas\",\n\t\"Close current tab\": \"Cierre la pestaña actual\",\n\t\"Close search\": \"Búsqueda cerrada\",\n\t\"Close tab\": \"Cerrar pestaña\",\n\t\"Close\": \"Cerrar\",\n\t\"Code search\": \"Búsqueda de código\",\n\t\"Code studio helps you write scripts, create unit tests, debug issues or generate anything else you can think of using AI! Sync a repo, then create a code studio project.\": \"¡El estudio de código te ayuda a escribir scripts, crear pruebas unitarias, depurar problemas o generar cualquier otra cosa que puedas imaginar usando IA\",\n\t\"Collapse\": \"Colapsar\",\n\t\"Commands\": \"Comandos\",\n\t\"Complete your transaction in Stripe...\": \"Complete su transacción en Stripe ...\",\n\t\"Confirm\": \"Confirmar\",\n\t\"Connect account\": \"Conectar cuenta\",\n\t\"Context files\": \"Archivos de contexto\",\n\t\"Continue from this state\": \"Continuar desde este estado\",\n\t\"Continue\": \"Continuar\",\n\t\"Copied\": \"Copiado\",\n\t\"Copy link\": \"Copiar link\",\n\t\"Copy message\": \"Copiar mensaje\",\n\t\"Copy\": \"Copiar\",\n\t\"Create line ranges\": \"Crear rangos de línea\",\n\t\"Create new project\": \"Crear nuevo proyecto\",\n\t\"Create new\": \"Crear nuevo\",\n\t\"Create project\": \"Crear proyecto\",\n\t\"Create ranges\": \"Crear rangos\",\n\t\"Create template\": \"Crear plantilla\",\n\t\"Current\": \"Actual\",\n\t\"Currently active\": \"Actualmente activo\",\n\t\"Dark\": \"Oscura\",\n\t\"Default project\": \"Proyecto predeterminado\",\n\t\"Delete all conversations\": \"Eliminar todas las conversaciones\",\n\t\"Delete conversation\": \"Eliminar la conversación\",\n\t\"Delete project\": \"Proyecto Eliminar\",\n\t\"Delete template\": \"Plantilla eliminar\",\n\t\"Delete\": \"Borrar\",\n\t\"Desktop app\": \"Aplicación de escritorio\",\n\t\"Diff generation failed\": \"Falló la generación de la diferencia\",\n\t\"Directories\": \"Directorios\",\n\t\"Disconnect\": \"Desconectar\",\n\t\"Discord\": \"Discord\",\n\t\"Display\": \"Mostrar\",\n\t\"Docs\": \"Documento\",\n\t\"Documentation URL...\": \"URL de documentación ...\",\n\t\"Documentation in studio\": \"Documentación en estudio\",\n\t\"Documentation\": \"Documentación\",\n\t\"Done\": \"Hecho\",\n\t\"Downgrade\": \"Degradar\",\n\t\"Downvote\": \"Voto descendente\",\n\t\"Edit ranges\": \"Editar rangos\",\n\t\"Edit sections\": \"Editar secciones\",\n\t\"Edit selected lines\": \"Editar líneas seleccionadas\",\n\t\"Edit selected sections\": \"Editar secciones seleccionadas\",\n\t\"Edit template\": \"Plantilla de edición\",\n\t\"Edit\": \"Editar\",\n\t\"Editing previously submitted questions will discard all answers and questions following it\": \"La edición de preguntas enviadas previamente descartará todas las respuestas y preguntas que lo sigan\",\n\t\"Email address\": \"Dirección de correo electrónico\",\n\t\"Email is not valid\": \"El correo no es válido\",\n\t\"Email is required\": \"Se requiere correo electrónico\",\n\t\"Email\": \"Correo electrónico\",\n\t\"Error\": \"Error\",\n\t\"Existing studio conversations\": \"Conversaciones de estudio existentes\",\n\t\"Expand\": \"Expandir\",\n\t\"Experimental: Faster but less accurate\": \"Experimental: Más rápido pero menos preciso\",\n\t\"Explain a file\": \"Explicar un archivo\",\n\t\"Explain code\": \"Código de explicación\",\n\t\"Explain current file\": \"Explicar el archivo actual\",\n\t\"Explain file\": \"Explicar archivo\",\n\t\"Explain the purpose of the file {{filePath}}, from lines {{lineStart}} - {{lineEnd}}\": \"Explique el propósito del archivo {{filePath}}, de las líneas {{lineStart}} - {{lineEnd}}\",\n\t\"Explain\": \"Explicar\",\n\t\"Failed to apply the diff\": \"No se pudo aplicar la diferencia\",\n\t\"Failed to get a response from OpenAI. Try again in a few moments.\": \"No se pudo obtener una respuesta de OpenAI. Prueba otra vez en unos instantes.\",\n\t\"Fast\": \"Rápida\",\n\t\"File not indexed\": \"Archivo no indexado\",\n\t\"File search\": \"Búsqueda de archivos\",\n\t\"File\": \"Archivo\",\n\t\"Files\": \"Archivos\",\n\t\"Files, paths or folders...\": \"Archivos, rutas o carpetas ...\",\n\t\"Filter repositories by\": \"Repositorios de filtro por\",\n\t\"Filters\": \"Filtros\",\n\t\"First name is required\": \"Se requiere el primer nombre\",\n\t\"First name\": \"Nombre de pila\",\n\t\"Fold everything\": \"Colapsar todo\",\n\t\"Folder too large\": \"Carpeta demasiado grande\",\n\t\"Follow us on Twitter\": \"Síganos en Twitter\",\n\t\"Force index\": \"Índice de fuerza\",\n\t\"Free\": \"Gratis\",\n\t\"General\": \"General\",\n\t\"Generate code using AI\": \"Generar código usando IA\",\n\t\"Generate\": \"Generar\",\n\t\"Generated diffs to be applied\": \"Diferencias generadas para aplicar\",\n\t\"Generating answer...\": \"Generando respuesta ...\",\n\t\"Generating diff...\": \"Generando diferencia...\",\n\t\"Generating response...\": \"Generando respuesta ...\",\n\t\"GitHub Branches\": \"Ramas de Github\",\n\t\"GitHub\": \"Github\",\n\t\"Give your template a title\": \"Dale un título a tu plantilla\",\n\t\"Go back\": \"Volver\",\n\t\"Go to current state\": \"Ir al estado actual\",\n\t\"Good\": \"Bien\",\n\t\"Got it\": \"Entiendo\",\n\t\"Hi, I am bloop! In <2><0></0>Chat mode</2> I can answer any questions related to any of your repositories.\": \"¡Hola, soy Bloop! En <2> <0> </0> Modo de chat </2> Puedo responder cualquier pregunta relacionada con cualquiera de sus repositorios.\",\n\t\"Hi, I am bloop! In <2><0></0>Studio mode</2> you can choose files from your codebase, write a prompt and generate patches, scripts and tests.\": \"¡Hola, soy Bloop! En <2> <0> </0> Modo de estudio </2> puede elegir archivos de su base de código, escribir un mensaje y generar parches, scripts y pruebas.\",\n\t\"Hide\": \"Ocultar\",\n\t\"History\": \"Historia\",\n\t\"How fast or precise bloop's answers will be.\": \"¿Qué tan rápidas o precisas serán las respuestas de Bloop?\",\n\t\"In this file\": \"En este archivo\",\n\t\"In this project\": \"En este proyecto\",\n\t\"Index multiple branches\": \"Indexar múltiples ramas\",\n\t\"Index repository\": \"Repositorio de índices\",\n\t\"Index\": \"Índice\",\n\t\"Indexed documentation web pages\": \"Páginas web de documentación indexadas\",\n\t\"Indexed local repositories\": \"Repositorios locales indexados\",\n\t\"Indexed\": \"Indexada\",\n\t\"Indexing repositories\": \"Repositorios de indexación\",\n\t\"Indexing repository\": \"Repositorio de indexación\",\n\t\"Indexing\": \"Indexación\",\n\t\"Indexing...\": \"Indexando...\",\n\t\"Individual\": \"Individual\",\n\t\"Invert\": \"Invertir\",\n\t\"Join Discord\": \"Unirse a Discord\",\n\t\"Language\": \"Idioma\",\n\t\"Languages\": \"Lenguas\",\n\t\"Last name is required\": \"Se requiere apellido\",\n\t\"Last name\": \"Apellido\",\n\t\"Last updated \": \"Última actualización\",\n\t\"Latest model updates\": \"Últimas actualizaciones del modelo\",\n\t\"Launch manually\": \"Lanzar manualmente\",\n\t\"Let’s get you started with bloop!\": \"¡Vamos a comenzar con Bloop!\",\n\t\"Light\": \"Luz\",\n\t\"Lines # - #\": \"Líneas {{start}} - {{end}}\",\n\t\"Loading...\": \"Cargando...\",\n\t\"Local repositories\": \"Repositorios locales\",\n\t\"Local repository\": \"Repositorio local\",\n\t\"Local\": \"Local\",\n\t\"Manage context\": \"Gestionar el contexto\",\n\t\"Manage docs\": \"Administrar documentos\",\n\t\"Manage project\": \"Administrar proyecto\",\n\t\"Manage repositories\": \"Gestionar repositorios\",\n\t\"Manage subscription\": \"Administrar suscripción\",\n\t\"Manage templates\": \"Administrar plantillas\",\n\t\"Manage your general account settings\": \"Administre la configuración de su cuenta general\",\n\t\"Manage your general project settings\": \"Administre la configuración de su proyecto general\",\n\t\"Manage your preferences\": \"Administra tus preferencias\",\n\t\"Manage your studio settings.\": \"Administre la configuración de su estudio.\",\n\t\"Manage your subscription plan\": \"Administre su plan de suscripción\",\n\t\"Manage\": \"Administrar\",\n\t\"Missing source\": \"Fuente faltante\",\n\t\"More actions\": \"Mas acciones\",\n\t\"Move\": \"Mover\",\n\t\"My templates\": \"Mis plantillas\",\n\t\"Name\": \"Nombre\",\n\t\"Natural language search\": \"Búsqueda de lenguaje natural\",\n\t\"Navigate your codebase\": \"Navegue su base de código\",\n\t\"Navigate\": \"Navegar\",\n\t\"New conversation\": \"Nueva conversación\",\n\t\"New project\": \"Nuevo proyecto\",\n\t\"New studio conversation\": \"Nueva conversación de estudio\",\n\t\"New tab\": \"Nueva pestaña\",\n\t\"New\": \"Nuevo\",\n\t\"Next page\": \"Página siguiente\",\n\t\"Next\": \"Próxima\",\n\t\"No change in payment status identified.\": \"No hay cambio en el estado de pago identificado.\",\n\t\"No commands found...\": \"No se encontraron comandos ...\",\n\t\"No file selected\": \"Ningún archivo seleccionado\",\n\t\"No files found...\": \"No se encontraron archivos...\",\n\t\"No references or definitions found\": \"No se encontraron referencias ni definiciones\",\n\t\"No repositories found...\": \"No se encontraron repositorios ...\",\n\t\"No results\": \"No hay resultados\",\n\t\"No uses left. Uses reset at\": \"No quedan usos. Usa restablecer en\",\n\t\"Normal\": \"Normal\",\n\t\"Not a git repository\": \"No es un repositorio de Git\",\n\t\"Not synced\": \"No sincronizado\",\n\t\"Open Command Bar\": \"Abrir barra de comando\",\n\t\"Open account settings\": \"Abra la configuración de la cuenta\",\n\t\"Open in GitHub\": \"Abrir en Github\",\n\t\"Open in split view\": \"Abrir en vista dividida\",\n\t\"Open in {{viewer}}\": \"Abrir en {{viewer}}\",\n\t\"Open subscription settings\": \"Abrir configuración de suscripción\",\n\t\"Open\": \"Abrir\",\n\t\"Paste a link to any documentation web page\": \"Pegue un enlace a cualquier página web de documentación\",\n\t\"Paste a link to any public repository hosted on GitHub\": \"Pegar un enlace a cualquier repositorio público alojado en Github\",\n\t\"Permanently delete <2>{{projectName}}</2> and remove all the data associated to it. Repositories will remain accessible in your GitHub account.\": \"Eliminar permanentemente <2>{{projectName}}</2> y eliminar todos los datos asociados a él. Los repositorios seguirán siendo accesibles en su cuenta de GitHub.\",\n\t\"Personal\": \"Personal\",\n\t\"Plans\": \"Planes\",\n\t\"Please log into your GitHub account to complete setup, this helps us combat misuse.\": \"Inicie sesión con su cuenta de GitHub para completar la configuración\",\n\t\"Precise code navigation\": \"Navegación de código precisa\",\n\t\"Preferences\": \"Ajustes\",\n\t\"Press <2>K</2> on your keyboard to open the Command bar.\": \"Presione <2> k </2> en su teclado para abrir la barra de comando.\",\n\t\"Press <2>cmdKey</2> <4>K</4> on your keyboard to open the Command bar and add a repository.\": \"Presione <2>{{cmdKey}}</2> <4>K</4> en su teclado para abrir la barra de comando y agregar un repositorio.\",\n\t\"Press <2>cmdKey</2> <4>K</4> on your keyboard to open the Command bar.\": \"Presione <2>{{cmdKey}}</2> <4>K</4> en su teclado para abrir la barra de comando.\",\n\t\"Privacy policy\": \"Política de privacidad\",\n\t\"Private repositories\": \"Repositorios privados\",\n\t\"Private repository\": \"Repositorio privado\",\n\t\"Problem details and System configuration\": \"Detalles del problema y configuración del sistema\",\n\t\"Project settings\": \"Configuración del proyecto\",\n\t\"Project title\": \"Título del Proyecto\",\n\t\"Project\": \"Proyecto\",\n\t\"Projects require at least one synced repository.\": \"Los proyectos requieren al menos un repositorio sincronizado.\",\n\t\"Projects\": \"Proyectos\",\n\t\"Prompt guide\": \"Guía de prompts\",\n\t\"Prompt templates\": \"Plantillas de inmediato\",\n\t\"Prompt\": \"Inmediata\",\n\t\"Prompts\": \"Indicaciones\",\n\t\"Provide a short, concise title for your project\": \"Proporcione un título corto y conciso para su proyecto\",\n\t\"Provide any steps necessary to reproduce the problem...\": \"Proporcione los pasos necesarios para reproducir el problema ...\",\n\t\"Public repositories\": \"Repositorios públicos\",\n\t\"Public repository\": \"Repositorio público\",\n\t\"Public\": \"Pública\",\n\t\"Query suggestions\": \"Sugerencias de búsqueda\",\n\t\"Queued...\": \"En cola...\",\n\t\"Re-check payment status\": \"Vuelva a verificar el estado de pago\",\n\t\"Re-sync\": \"Volver a sincronizar\",\n\t\"Reading \": \"Leyendo\",\n\t\"Reading\": \"Leyendo\",\n\t\"Recent conversations\": \"Conversaciones recientes\",\n\t\"Recent projects\": \"Proyectos recientes\",\n\t\"Recently used\": \"Recientemente usado\",\n\t\"Recommended: The classic response type\": \"Recomendado: El tipo de respuesta clásico\",\n\t\"References\": \"Referencias\",\n\t\"Referencing target file\": \"Referencia al archivo objetivo\",\n\t\"Release to open in split view\": \"Lanzar para abrir en vista dividida\",\n\t\"Remote removed \": \"Remoto eliminado\",\n\t\"Remove file from code studio context\": \"Eliminar el archivo del contexto de código de código\",\n\t\"Remove file\": \"Remover archivo\",\n\t\"Remove from project\": \"Eliminar del proyecto\",\n\t\"Remove from studio\": \"Retirar del estudio\",\n\t\"Remove page from code studio context\": \"Eliminar la página del contexto del estudio de código\",\n\t\"Remove\": \"Eliminar\",\n\t\"Removed\": \"Eliminado\",\n\t\"Rename code studio\": \"Cambiar el nombre de Code Studio\",\n\t\"Rename\": \"Rebautizar\",\n\t\"Report a bug\": \"Reportar un error\",\n\t\"Repositories\": \"Repositorios\",\n\t\"Repository URL...\": \"URL de repositorio ...\",\n\t\"Repository indexed\": \"Repositorio indexado\",\n\t\"Repository types\": \"Tipos de repositorio\",\n\t\"Restart the app\": \"Reiniciar la aplicación\",\n\t\"Restore\": \"Restaurar\",\n\t\"Result suggestions\": \"Sugerencias de resultados\",\n\t\"Results\": \"Resultados\",\n\t\"Retry\": \"Rever\",\n\t\"Save changes\": \"Guardar cambios\",\n\t\"Save context changes before answer generation\": \"Guardar cambios de contexto antes de la generación de respuesta\",\n\t\"Save\": \"Guardar\",\n\t\"Search branch...\": \"Buscar rama...\",\n\t\"Search branches...\": \"Buscar ramas...\",\n\t\"Search code in natural language\": \"Buscar código en lenguaje natural\",\n\t\"Search docs\": \"Documentos de búsqueda\",\n\t\"Search docs...\": \"Documentos de búsqueda ...\",\n\t\"Search file...\": \"Buscar archivo...\",\n\t\"Search files\": \"Buscar archivos\",\n\t\"Search files...\": \"Buscar archivos...\",\n\t\"Search for code using regex\": \"Buscar código usando regex\",\n\t\"Search local repos...\": \"Buscar repos locales ...\",\n\t\"Search pages\": \"Páginas de búsqueda\",\n\t\"Search private repos...\": \"Buscar reposes privados ...\",\n\t\"Search projects or commands...\": \"Búsqueda de proyectos o comandos ...\",\n\t\"Search public repositories...\": \"Buscar repositorios públicos ...\",\n\t\"Search repos or Studio projects...\": \"Buscar repositorios o proyectos de Studio...\",\n\t\"Search repository\": \"Buscar repositorio\",\n\t\"Search studio conversations...\": \"Buscar conversaciones de estudio...\",\n\t\"Search templates...\": \"Plantillas de búsqueda ...\",\n\t\"Search using RegExp\": \"Buscar con RegExp\",\n\t\"Search your files in this project\": \"Busque sus archivos en este proyecto\",\n\t\"Search your repositories using RegExp\": \"Busque sus repositorios utilizando regexp\",\n\t\"Search\": \"Buscar\",\n\t\"Searching...\": \"Buscando...\",\n\t\"Select a file or open a new tab to display it here.\": \"Seleccione un archivo o abra una nueva pestaña para mostrarlo aquí.\",\n\t\"Select a folder containing a git repository\": \"Seleccione una carpeta que contenga un repositorio Git\",\n\t\"Select all\": \"Seleccionar todo\",\n\t\"Select branch\": \"Seleccionar rama\",\n\t\"Select color theme:\": \"Seleccione el tema:\",\n\t\"Select file\": \"Seleccionar archivo\",\n\t\"Select folder\": \"Seleccione la carpeta\",\n\t\"Select less code\": \"Seleccione menos código\",\n\t\"Select library\": \"Seleccionar biblioteca\",\n\t\"Select page\": \"Página de selección\",\n\t\"Select repository\": \"Seleccionar repositorio\",\n\t\"Select section\": \"Seleccionar sección\",\n\t\"Select sections\": \"Seleccionar secciones\",\n\t\"Select the interface colour scheme\": \"Seleccione el esquema de color de la interfaz\",\n\t\"Select the interface language\": \"Seleccione el idioma de la interfaz\",\n\t\"Select\": \"Seleccionar\",\n\t\"Selected lines # - #.\": \"Líneas seleccionadas {{start}} - {{end}}.\",\n\t\"Selected ranges will be used as context.\": \"Los rangos seleccionados se utilizarán como contexto.\",\n\t\"Settings\": \"Ajustes\",\n\t\"Setup bloop\": \"Configuración de bloop\",\n\t\"Show # more match_one\": \"Mostrar {{count}} coincidencia más\",\n\t\"Show # more match_other\": \"Mostrar {{count}} coincidencias más\",\n\t\"Show file\": \"Mostrar archivo\",\n\t\"Show filters\": \"Mostrar filtros\",\n\t\"Show less\": \"Mostrar menos\",\n\t\"Show link\": \"Mostrar enlace\",\n\t\"Show more\": \"Mostrar más\",\n\t\"Show search steps\": \"Mostrar pasos de búsqueda\",\n\t\"Show\": \"Mostrar\",\n\t\"Sign In\": \"Iniciar sesión\",\n\t\"Sign in with GitHub\": \"Iniciar sesión con Github\",\n\t\"Sign out\": \"Cerrar sesión\",\n\t\"Skip (Not recommended)\": \"Saltar (no recomendado)\",\n\t\"Skip\": \"Saltar\",\n\t\"Something went wrong\": \"Algo salió mal\",\n\t\"Start by selecting \": \"Comience por seleccionar\",\n\t\"Start by selecting a type repository you would like to index.\": \"Comience por seleccionar un repositorio de tipo que desee indexar.\",\n\t\"Start indexing\": \"Comenzar a indexación\",\n\t\"Start typing...\": \"Empiece a escribir ...\",\n\t\"Status\": \"Estado\",\n\t\"Stop generating\": \"Parar la generación\",\n\t\"Stop indexing\": \"Dejar de indexación\",\n\t\"Streaming response...\": \"Respuesta de transmisión ...\",\n\t\"Studio conversation require at least one context file.\": \"La conversación de estudio requiere al menos un archivo de contexto.\",\n\t\"Studio conversation\": \"Conversación de estudio\",\n\t\"Studio conversations\": \"Conversaciones de estudio\",\n\t\"Studio mode\": \"Modo de estudio\",\n\t\"Studio project\": \"Proyecto de estudio\",\n\t\"Studio requests\": \"Solicitudes de estudio\",\n\t\"Studio\": \"Estudio\",\n\t\"Submit bug report\": \"Enviar informe de error\",\n\t\"Submit crash report\": \"Enviar informe de bloqueo\",\n\t\"Submit\": \"Enviar\",\n\t\"Subscription\": \"Suscripción\",\n\t\"Switch branch\": \"Cambiar de rama\",\n\t\"Switch to\": \"Cambiar a\",\n\t\"Sync local git repos\": \"Sync Local Git Repos\",\n\t\"Sync\": \"Sincronizar\",\n\t\"Synced\": \"Sincronizado\",\n\t\"Syncing\": \"Sincronización\",\n\t\"System Preference\": \"Preferencia del sistema\",\n\t\"System preferences\": \"Preferencias del Sistema\",\n\t\"System\": \"Sistema\",\n\t\"Take a quick look\": \"Echar un vistazo\",\n\t\"Template title\": \"Título de plantilla\",\n\t\"Templates\": \"Plantillas\",\n\t\"Terms & conditions\": \"Términos y condiciones\",\n\t\"The amount of times you can generate responses in Studio conversations per day.\": \"La cantidad de veces que puede generar respuestas en las conversaciones de estudio por día.\",\n\t\"The diff has been applied locally.\": \"La diferencia se ha aplicado localmente.\",\n\t\"The folder you selected has multiple git repositories nested inside.\": \"La carpeta que seleccionó tiene múltiples repositorios git anidados en el interior.\",\n\t\"The folder you selected is not a git repository.\": \"La carpeta que seleccionó no es un repositorio git.\",\n\t\"The following changes can be applied to your repository. Make sure the generated diffs are valid before you apply the changes.\": \"Los siguientes cambios se pueden aplicar a tu repositorio. Asegúrate de que las diferencias generadas sean válidas antes de aplicar los cambios.\",\n\t\"The following changes represent the git diff for the remote repository. Please note that these changes cannot be applied directly to a remote repository. Use the \\\"Copy\\\" button to copy the changes and apply them locally.\": \"Los siguientes cambios representan la diferencia de git para el repositorio remoto. Ten en cuenta que estos cambios no se pueden aplicar directamente a un repositorio remoto. Usa el botón \\\"Copiar\\\" para copiar los cambios y aplicarlos localmente.\",\n\t\"The line of code where identifier is defined\": \"La línea de código donde se define el identificador\",\n\t\"The line of code where the identifier is referenced\": \"La línea de código donde se hace referencia al identificador\",\n\t\"The whole file will be used as context.\": \"Todo el archivo se utilizará como contexto.\",\n\t\"Theme\": \"Tema\",\n\t\"This is not a public repository / We couldn't find this repository\": \"No es un repositorio público / No pudimos encontrar el repositorio\",\n\t\"This might be because the file is too big or it has one of bloop's excluded file types.\": \"Esto podría deberse a que el archivo es demasiado grande o tiene uno de los tipos de archivos excluidos de Bloop.\",\n\t\"This project is empty\": \"Este proyecto está vacío\",\n\t\"Tip: Select code to create ranges for context use.\": \"Consejo: seleccione código para crear rangos para uso del contexto.\",\n\t\"To update your app, please visit our releases page on GitHub and download the latest version manually. Thank you for using bloop.\": \"Para actualizar la aplicación, visite nuestra página de lanzamientos en GitHub y descargue la última versión manualmente. Gracias por usar bloop.\",\n\t\"Today\": \"Hoy\",\n\t\"Toggle black theme\": \"Tema negro de palanca\",\n\t\"Toggle dark theme\": \"Tema oscuro de al revés\",\n\t\"Toggle light theme\": \"Tema de luz de al revés\",\n\t\"Toggle regex search\": \"Búsqueda de reglas de palanca\",\n\t\"Toggle system theme\": \"Tema del sistema de palanca\",\n\t\"Toggle\": \"Palanca\",\n\t\"Token limit exceeded\": \"Límite de token excedido\",\n\t\"Token limit exceeded. Reduce the number of context files or messages to enable the ability to generate.\": \"Límite de token excedido. Reduzca la cantidad de archivos o mensajes de contexto para habilitar la capacidad de generar.\",\n\t\"Token limit reached, this answer may be incomplete. To generate a full answer, please reduce the number of tokens used and regenerate.\": \"Límite de token alcanzado, esta respuesta puede estar incompleta. Para generar una respuesta completa, reduzca el número de tokens utilizados y regenerados.\",\n\t\"Try again\": \"Intentar otra vez\",\n\t\"Unavailable\": \"Indisponible\",\n\t\"Unlimited code studio requests\": \"Solicitudes de estudio de código ilimitadas\",\n\t\"Unlimited usage and premium features are activated.\": \"Se activan el uso ilimitado y las características premium.\",\n\t\"Unlock the value of your existing code, using AI\": \"Desbloquee el valor de su código existente, utilizando IA\",\n\t\"Untitled project\": \"Proyecto sin título\",\n\t\"Update Required\": \"Actualización necesaria\",\n\t\"Upgrade now\": \"Mejorar ahora\",\n\t\"Upgrade plan\": \"Mejorar plan\",\n\t\"Upgrade to Personal plan\": \"Actualizar al plan personal\",\n\t\"Upgrade\": \"Actualizar\",\n\t\"Upvote\": \"Votar a favor\",\n\t\"Usage exceeded\": \"El uso superado\",\n\t\"Usage resets in\": \"Restablecimiento de uso en\",\n\t\"Usage status\": \"Estado de uso\",\n\t\"Use GitHub to sign in to your account\": \"Use GitHub para iniciar sesión\",\n\t\"Use black theme\": \"Usar tema negro\",\n\t\"Use dark theme\": \"Usar tema oscuro\",\n\t\"Use file\": \"Usar archivo\",\n\t\"Use light theme\": \"Use el tema de la luz\",\n\t\"Use system theme\": \"Utilice el tema del sistema\",\n\t\"Use template\": \"Usar plantilla\",\n\t\"Use templates\": \"Usar plantillas\",\n\t\"Use\": \"Usar\",\n\t\"User\": \"Usuaria\",\n\t\"Verifying access...\": \"Verificando acceso...\",\n\t\"Verifying link...\": \"Verificación del enlace ...\",\n\t\"View all results\": \"Ver todos los resultados\",\n\t\"View all\": \"Ver todo\",\n\t\"View bloop app documentation on our website\": \"Ver documentación de la aplicación Bloop en nuestro sitio web\",\n\t\"View history\": \"Ver historial\",\n\t\"View in {{viewer}}\": \"Ver en {{viewer}}\",\n\t\"View\": \"Vista\",\n\t\"Visit the downloads page\": \"Visite la página de descarga\",\n\t\"Waiting for authentication...\": \"Esperando la autenticación ...\",\n\t\"Watch\": \"Mirar\",\n\t\"We can’t generate a response because some files have a missing source in your Context files.\": \"No podemos generar una respuesta porque algunos archivos tienen una fuente faltante en sus archivos de contexto.\",\n\t\"We couldn't answer your question. You can try asking again in a few moments, or rephrasing your question.\": \"No pudimos responder a su pregunta. Puede intentar volver a preguntar en unos momentos o reformular su pregunta.\",\n\t\"We couldn't find any docs at that link. Try again or make sure the link is correct!\": \"No pudimos encontrar ningún documento en ese enlace. ¡Inténtelo de nuevo o asegúrese de que el enlace sea correcto!\",\n\t\"We want to make this the best experience for you. If you encountered a bug, please submit this bug report to us. Our team will investigate as soon as possible.\": \"Queremos hacer de esta la mejor experiencia para usted. Si encontró un error, envíenos un informe de error. Nuestro equipo lo investigará lo antes posible.\",\n\t\"We weren't able to identify any references at the moment\": \"No pudimos identificar ninguna referencia en este momento\",\n\t\"We've made some exciting enhancements to bloop! To continue enjoying the full functionality, including the natural language search feature, please update your app to the latest version.\": \"¡Hemos añadido algunas mejoras increíbles para bloop! Para seguir disfrutando de todas las funcionalidades, incluida la función de búsqueda de lenguaje natural, actualice la aplicación a la última versión.\",\n\t\"We've redirected you to Stripe to complete your transaction. <2>Launch manually</2> if it didn't work.\": \"Te hemos redirigido a rayar para completar su transacción. <2> Iniciar manualmente </2> si no funcionó.\",\n\t\"Welcome to bloop\": \"Bienvenido a bloop\",\n\t\"We’ve updated our auth service to make bloop more secure, please reauthorise your client with GitHub\": \"Hemos actualizado nuestro servicio de autores para que el Bloop sea más seguro, por favor reanice a su cliente con GitHub\",\n\t\"Whole file\": \"Archivo completo\",\n\t\"Whole page\": \"Toda la pagina\",\n\t\"Write a message, @ to mention files, folders or docs...\": \"Escriba un mensaje, @ para mencionar archivos, carpetas o documentos ...\",\n\t\"Write studio prompts faster with pre-written templates\": \"Escriba las indicaciones de estudio más rápido con plantillas preescritas\",\n\t\"Write your prompt...\": \"Escribe tu aviso ...\",\n\t\"Yesterday\": \"Ayer\",\n\t\"You can add 3 types of repositories, private, public and local.\": \"Puede agregar 3 tipos de repositorios, privados, públicos y locales.\",\n\t\"You\": \"Tú\",\n\t\"You've run out of free usage for today, please wait for your quota to reset or upgrade for unlimited usage\": \"Se ha quedado sin uso gratuito para hoy, espere a que su cuota se reinicie o actualice para un uso ilimitado\",\n\t\"You've upgraded your account!\": \"¡Has actualizado tu cuenta!\",\n\t\"Your quota resets every 24 hours, upgrade for unlimited uses\": \"Tu cuota se restablece cada 24 horas, actualiza para solicitudes ilimitadas\",\n\t\"Your subscription has expired. Please update your payment details to avoid being unsubscribed.\": \"Su suscripción ha expirado. Actualice sus detalles de pago para evitar no suscribirse.\",\n\t\"and \": \"y\",\n\t\"avatar\": \"avatar\",\n\t\"billed monthly\": \"facturado mensualmente\",\n\t\"bloop automatically excludes certain files from indexing. This file might be too big or it might have an excluded file type.\": \"Bloop excluye automáticamente ciertos archivos de la indexación. Este archivo puede ser demasiado grande o podría tener un tipo de archivo excluido.\",\n\t\"bloop automatically excludes certain files from the indexing. This file might be too big or it might have an excluded file type.\": \"Bloop excluye automáticamente ciertos archivos de la indexación. Este archivo puede ser demasiado grande o podría tener un tipo de archivo excluido.\",\n\t\"bloop crashed unexpectedly\": \"bloop colapsó inesperadamente\",\n\t\"cancelled\": \"cancelado\",\n\t\"cancelling\": \"cancelando\",\n\t\"chats in bloop\": \"Chats en bloop\",\n\t\"definition\": \"definición\",\n\t\"done\": \"hecho\",\n\t\"indexing\": \"indexando\",\n\t\"key\": \"clave\",\n\t\"or \": \"o\",\n\t\"or go to the following link\": \"o ir al siguiente enlace\",\n\t\"or visit: \": \"o visitar:\",\n\t\"reference\": \"referencia\",\n\t\"syncing\": \"sincronizando\",\n\t\"to seamlessly explore code across all branches in your GitHub repositories, maximizing your code discovery capabilities.\": \"Para explorar sin obstáculos el código en todas las ramas en sus repositorios de GitHub, maximizando sus capacidades y posbilidades sobre código.\",\n\t\"uses left_one\": \"usos restantes\",\n\t\"uses left_other\": \"usos restante\",\n\t\"{{count}} context files used\": \"{{count}} archivos de contexto utilizados\",\n\t\"{{count}} context files used_one\": \"{{count}} archivo de contexto utilizado\",\n\t\"{{count}} context files used_other\": \"{{count}} archivos de contexto utilizados\",\n\t\"{{repoName}} has finished indexing and you can use it in your projects now.\": \"{{repoName}} ha terminado de indexación y ahora puede usarla en sus proyectos.\",\n\t\"{{repoName}} is currently indexing as soon as it is finished it will be added to your project.\": \"{{repoName}} actualmente está indexando tan pronto como finalice, se agregará a su proyecto.\",\n\t\"<0>Use your cursor to select any piece of code within a file and ask bloop to explain it by pressing \\\"Explain\\\" in the floating toolbar.</0>\": \"<0> Use su cursor para seleccionar cualquier pieza de código dentro de un archivo y solicite a Bloop que lo explique presionando \\\"explicar\\\" en la barra de herramientas flotantes. </0>\",\n\t\"Dismiss tutorial\": \"Tutorial de despedida\",\n\t\"Click on an identifier and jump to its references and definition in a heart beat.\": \"Haga clic en un identificador y salte a sus referencias y definición en un latido cardíaco.\",\n\t\"Hide search steps\": \"Ocultar pasos de búsqueda\",\n\t\"Close currently focused tab\": \"Cerrar pestaña actualmente enfocada\",\n\t\"Add documentation\": \"Agregar documentación\",\n\t\"Usage resets at\": \"Restablecimiento de uso en\",\n\t\"Restore session\": \"Restaurar sesion\",\n\t\"<0>{{repoName}}</0> has finished indexing and can be added to your projects. Click the button to below to add it to the current project.\": \"<0>{{repoName}}</0> ha terminado de indexación y se puede agregar a sus proyectos. Haga clic en el botón a continuación para agregarlo al proyecto actual.\",\n\t\"Start by selecting again and pressing Enter (↵) on your keyboard.\": \"Comience seleccionando nuevamente y presionando Enter (↵) en su teclado.\",\n\t\"{{repoName}} is currently indexing as soon as it is finished you will be able to add it to your project.\": \"{{repoName}} actualmente está indexando tan pronto como esté terminado, podrá agregarlo a su proyecto.\",\n\t\"Conversation input\": \"Aportación de conversación\",\n\t\"Simplified: Choose if the default one fails\": \"Simplificado: elija si el predeterminado falla\",\n\t\"Fallback: Use if experiencing problems with the default one\": \"Fallback: use si experimenta problemas con el predeterminado\",\n\t\"Select the input type to use in conversations\": \"Seleccione el tipo de entrada a usar en conversaciones\",\n\t\"Default\": \"Por defecto\",\n\t\"Recommended: The classic input\": \"Recomendado: la entrada clásica\",\n\t\"Add multiple files\": \"Agregar varios archivos\",\n\t\"Simplified\": \"Simplificada\",\n\t\"Explain lines {{lineStart}} - {{lineEnd}} in {{filePath}}\": \"Explique las líneas {{lineStart}} - {{lineEnd}} en {{filePath}}\"\n}"
  },
  {
    "path": "client/src/locales/it.json",
    "content": "{\n\t\"# match_one\": \"{{count}} corrispondenza\",\n\t\"# match_other\": \"{{count}} corrispondenze\",\n\t\"# of #_one\": \"{{count}} di {{total}}\",\n\t\"# of #_other\": \"{{count}} di {{total}}\",\n\t\"# ranges_one\": \"{{count}} intervalli\",\n\t\"# ranges_other\": \"{{count}} intervalli\",\n\t\"# selected section_one\": \"{{count}} sezione selezionata\",\n\t\"# selected section_other\": \"{{count}} sezioni selezionate\",\n\t\"# tokens_one\": \"token\",\n\t\"# tokens_other\": \"token\",\n\t\"$20 / billed monthly\": \"$ 20 / fatturati mensilmente\",\n\t\"<0>#</0> of # tokens\": \"<0>{{count}}</0> di {{total}} token\",\n\t\"<0>#</0> of # tokens_one\": \"<0>{{count}}</0> di {{total}} token\",\n\t\"<0>#</0> of # tokens_other\": \"<0>{{count}}</0> di {{total}} token\",\n\t\"<0></0><1></1> to navigate.\": \"<0></0><1></1> per navigare.\",\n\t\"<0>Create a new conversation with bloop by hitting <2></2> on your keyboard or by pressing the <4></4> in the header bar.</0>\": \"<0> Crea una nuova conversazione con Bloop toccando <2> </2> sulla tastiera o premendo <4> </4> nella barra di intestazione. </0>\",\n\t\"<0>To begin, open a file from the sidebar on the left. Once you have a file open, you can ask bloop to quickly explain it by hitting <2></2> on your keyboard or by selecting \\\"Explain file\\\" from the <7></7> popup menu.</0>\": \"<0> Per iniziare, aprire un file dalla barra laterale a sinistra. Una volta aperto un file, puoi chiedere a Bloop di spiegarlo rapidamente premendo <2> </2> sulla tastiera o selezionando \\\"Spiega file\\\" dal menu popup <7> </7>. </0>\",\n\t\"<0>repoName</0> has finished indexing and was added to the context of the current project. You can also use it in your other projects now.\": \"<0>{{repoName}}</0> ha terminato l'indicizzazione ed è stato aggiunto al contesto dell'attuale progetto. Ora puoi anche usarlo nei tuoi altri progetti.\",\n\t\"Account settings\": \"Impostazioni dell'account\",\n\t\"Actions\": \"Azioni\",\n\t\"Add a repository from your local machine\": \"Aggiungi un repository dal tuo computer\",\n\t\"Add at least one context file before submitting your first request.\": \"Aggiungi almeno un file di contesto prima di inviare la prima richiesta.\",\n\t\"Add doc to studio\": \"Aggiungi doc a studio\",\n\t\"Add docs\": \"Aggiungi documenti\",\n\t\"Add file to code studio context\": \"Aggiungi file al contesto di code studio\",\n\t\"Add file to studio\": \"Aggiungi file a Studio\",\n\t\"Add file\": \"Aggiungi file\",\n\t\"Add files to studio\": \"Aggiungi file a Studio\",\n\t\"Add files\": \"Aggiungere i file\",\n\t\"Add local repository\": \"Aggiungi repository locale\",\n\t\"Add new repository\": \"Aggiungi nuovo repository\",\n\t\"Add private repository\": \"Aggiungi repository privato\",\n\t\"Add public repository\": \"Aggiungi repository pubblico\",\n\t\"Add repository\": \"Aggiungi repository\",\n\t\"Add to existing\": \"Aggiungi a esistente\",\n\t\"Add to project\": \"Aggiungi al progetto\",\n\t\"Add to studio\": \"Aggiungi allo studio\",\n\t\"Add\": \"Aggiungi\",\n\t\"Adding repository\": \"Aggiunta di repository\",\n\t\"All payments, invoices and billing information are managed in Stripe.\": \"Tutti i pagamenti, le fatture e le informazioni sulla fatturazione sono gestiti in strisce.\",\n\t\"All templates\": \"Tutti i modelli\",\n\t\"All\": \"Tutti\",\n\t\"Answer speed\": \"Velocità di risposta\",\n\t\"Application theme\": \"Tema dell'applicazione\",\n\t\"Apply changes\": \"Applica i cambiamenti\",\n\t\"Ask questions about your codebases in natural language, just like you’d speak to ChatGPT. Get started by syncing a repo, then open the repo and start chatting.\": \"Fai domande sui tuoi codebase in linguaggio naturale, proprio come parleresti con ChatGPT. Inizia sincronizzando un repository, quindi apri il repository e inizia a chattare.\",\n\t\"Ask your first question\": \"Fai la tua prima domanda\",\n\t\"Assistant\": \"Assistente\",\n\t\"Back to current\": \"Torna alla corrente\",\n\t\"Back\": \"Indietro\",\n\t\"Bad response\": \"Risposta negativa\",\n\t\"Bad\": \"Negativa\",\n\t\"Below are a few suggestions you can ask me to get started:\": \"Di seguito sono riportati alcuni suggerimenti che puoi chiedermi di iniziare:\",\n\t\"Black\": \"Nera\",\n\t\"Bloop needs to index your repository first. This process takes a few seconds and happens only one time per repository.\": \"Bloop deve prima indicizzare il tuo repository. Questo processo dura alcuni secondi e avviene solo una volta per repository.\",\n\t\"Branches\": \"Rami\",\n\t\"By continuing you accept our\": \"Continuando, accetti i nostri\",\n\t\"By submitting this crash report you agree to send it to bloop for investigation.\": \"Inviando questo rapporto di arresto anomalo, accetti di inviarlo a bloop per investigare.\",\n\t\"Cancel diff generation\": \"Annulla la generazione della diff\",\n\t\"Cancel\": \"Annulla\",\n\t\"Cancelled\": \"Annullato\",\n\t\"Cancelling...\": \"Annullamento...\",\n\t\"Change application colour theme\": \"Cambia il tema del colore dell'applicazione\",\n\t\"Chat conversations\": \"Conversazioni di chat\",\n\t\"Clear conversation\": \"Pulisci conversazione\",\n\t\"Clear input\": \"Pulisci input\",\n\t\"Clear range\": \"Pulisci intervallo\",\n\t\"Clear ranges\": \"Pulisci intervalli\",\n\t\"Click to copy\": \"Clicca per copiare\",\n\t\"Cloning\": \"Clonazione\",\n\t\"Cloning...\": \"Clonazione...\",\n\t\"Close all open tabs\": \"Chiudi tutte le schede aperte\",\n\t\"Close all tabs\": \"Chiudi tutte le schede\",\n\t\"Close current tab\": \"Chiudi la finestra attuale\",\n\t\"Close search\": \"Chiudere la ricerca\",\n\t\"Close tab\": \"Chiudi scheda\",\n\t\"Close\": \"Chiudi\",\n\t\"Code search\": \"Ricerca del codice\",\n\t\"Code studio helps you write scripts, create unit tests, debug issues or generate anything else you can think of using AI! Sync a repo, then create a code studio project.\": \"\",\n\t\"Collapse\": \"Crollo\",\n\t\"Commands\": \"Comandi\",\n\t\"Complete your transaction in Stripe...\": \"Completa la transazione in Stripe...\",\n\t\"Confirm\": \"Conferma\",\n\t\"Connect account\": \"Collega account\",\n\t\"Context files\": \"File di contesto\",\n\t\"Continue from this state\": \"Continua da questo stato\",\n\t\"Continue\": \"Continua\",\n\t\"Copied\": \"Copiato\",\n\t\"Copy link\": \"Copia link\",\n\t\"Copy message\": \"Copia messaggio\",\n\t\"Copy\": \"Copia\",\n\t\"Create line ranges\": \"Crea intervalli di linea\",\n\t\"Create new project\": \"Crea nuovo progetto\",\n\t\"Create new\": \"Crea nuovo\",\n\t\"Create project\": \"Crea progetto\",\n\t\"Create ranges\": \"Crea gamme\",\n\t\"Create template\": \"Crea modello\",\n\t\"Current\": \"Attuale\",\n\t\"Currently active\": \"Attualmente attivo\",\n\t\"Dark\": \"Buia\",\n\t\"Default project\": \"Progetto predefinito\",\n\t\"Delete all conversations\": \"Elimina tutte le conversazioni\",\n\t\"Delete conversation\": \"Cancella la conversazione\",\n\t\"Delete project\": \"Elimina il progetto\",\n\t\"Delete template\": \"Elimina modello\",\n\t\"Delete\": \"Elimina\",\n\t\"Desktop app\": \"App desktop\",\n\t\"Diff generation failed\": \"Generazione diff fallita\",\n\t\"Directories\": \"Directory\",\n\t\"Disconnect\": \"Disconnetti\",\n\t\"Discord\": \"Discord\",\n\t\"Display\": \"Schermo\",\n\t\"Docs\": \"Documenti\",\n\t\"Documentation URL...\": \"Documentazione URL ...\",\n\t\"Documentation in studio\": \"Documentazione in studio\",\n\t\"Documentation\": \"Documentazione\",\n\t\"Done\": \"Fatto\",\n\t\"Downgrade\": \"Downgrade\",\n\t\"Downvote\": \"Votare in negativo\",\n\t\"Edit ranges\": \"Modifica intervalli\",\n\t\"Edit sections\": \"Modifica sezioni\",\n\t\"Edit selected lines\": \"Modifica righe selezionate\",\n\t\"Edit selected sections\": \"Modifica sezioni selezionate\",\n\t\"Edit template\": \"Modello di modifica\",\n\t\"Edit\": \"Modifica\",\n\t\"Editing previously submitted questions will discard all answers and questions following it\": \"La modifica delle domande precedentemente inviate eliminerà tutte le risposte e le domande che lo seguono\",\n\t\"Email address\": \"Indirizzo email\",\n\t\"Email is not valid\": \"Email non valida\",\n\t\"Email is required\": \"Email richiesta\",\n\t\"Email\": \"Email\",\n\t\"Error\": \"Errore\",\n\t\"Expand\": \"Espandere\",\n\t\"Experimental: Faster but less accurate\": \"Sperimentale: più veloce ma meno accurato\",\n\t\"Explain a file\": \"Spiega un file\",\n\t\"Explain code\": \"Spiega il codice\",\n\t\"Explain current file\": \"Spiega il file corrente\",\n\t\"Explain file\": \"Spiega il file\",\n\t\"Explain the purpose of the file {{filePath}}, from lines {{lineStart}} - {{lineEnd}}\": \"Spiega lo scopo del file {{filePath}}, dalle righe {{lineStart}} a {{lineEnd}}\",\n\t\"Explain\": \"Spiega\",\n\t\"Failed to apply the diff\": \"Impossibile applicare la diff\",\n\t\"Failed to get a response from OpenAI. Try again in a few moments.\": \"Impossibile ottenere una risposta da OpenAI. Riprova tra qualche istante.\",\n\t\"Fast\": \"Veloce\",\n\t\"File not indexed\": \"File non indicizzato\",\n\t\"File search\": \"Ricerca dei file\",\n\t\"File\": \"File\",\n\t\"Files\": \"File\",\n\t\"Files, paths or folders...\": \"File, percorsi o cartelle ...\",\n\t\"Filter repositories by\": \"Filtro repository di\",\n\t\"Filters\": \"Filtri\",\n\t\"First name is required\": \"Nome richiesto\",\n\t\"First name\": \"Nome\",\n\t\"Fold everything\": \"Comprimi tutto\",\n\t\"Follow us on Twitter\": \"Seguici su Twitter\",\n\t\"Force index\": \"Forza indicizzazione\",\n\t\"Free\": \"Gratuita\",\n\t\"General\": \"Generale\",\n\t\"Generate code using AI\": \"Genera codice usando l'AI\",\n\t\"Generate\": \"Genera\",\n\t\"Generated diffs to be applied\": \"Diff generate da applicare\",\n\t\"Generating answer...\": \"Risposta di generazione ...\",\n\t\"Generating diff...\": \"Generazione diff in corso...\",\n\t\"Generating response...\": \"Generazione risposta...\",\n\t\"GitHub Branches\": \"Branch GitHub\",\n\t\"GitHub\": \"Github\",\n\t\"Give your template a title\": \"Dai un titolo al tuo modello\",\n\t\"Go back\": \"Torna indietro\",\n\t\"Go to current state\": \"Vai allo stato attuale\",\n\t\"Good\": \"Positiva\",\n\t\"Got it\": \"Ho capito\",\n\t\"Hi, I am bloop! In <2><0></0>Chat mode</2> I can answer any questions related to any of your repositories.\": \"Ciao, sono Bloop! In <2> <0> </0> Modalità chat </2> Posso rispondere a qualsiasi domanda relativa a uno qualsiasi dei tuoi repository.\",\n\t\"Hi, I am bloop! In <2><0></0>Studio mode</2> you can choose files from your codebase, write a prompt and generate patches, scripts and tests.\": \"Ciao, sono Bloop! In <2> <0> </0> Modalità Studio </2> è possibile scegliere i file dalla base del codice, scrivere un prompt e generare patch, script e test.\",\n\t\"Hide\": \"Nascondi\",\n\t\"History\": \"Cronologia\",\n\t\"How fast or precise bloop's answers will be.\": \"Quanto saranno rapide o precise le risposte di Bloop.\",\n\t\"In this file\": \"In questo file\",\n\t\"Index multiple branches\": \"Indicizzare più rami\",\n\t\"Index repository\": \"Repository indice\",\n\t\"Index\": \"Indice\",\n\t\"Indexed local repositories\": \"Repository locali indicizzati\",\n\t\"Indexed\": \"Indicizzata\",\n\t\"Indexing in progress\": \"Indicizzazione in corso\",\n\t\"Indexing repositories\": \"Indicizzazione dei repository\",\n\t\"Indexing repository\": \"Repository di indicizzazione\",\n\t\"Indexing\": \"Indicizzazione\",\n\t\"Indexing...\": \"Indicizzazione...\",\n\t\"Individual\": \"Individuale\",\n\t\"Invert\": \"Inverti\",\n\t\"Join Discord\": \"Unisciti a discordia\",\n\t\"Language\": \"Lingua\",\n\t\"Languages\": \"Le lingue\",\n\t\"Last name is required\": \"Cognome richiesto\",\n\t\"Last name\": \"Cognome\",\n\t\"Last updated \": \"Ultimo aggiornamento \",\n\t\"Latest model updates\": \"Ultimi aggiornamenti del modello\",\n\t\"Launch manually\": \"Avvia manualmente\",\n\t\"Light\": \"Leggera\",\n\t\"Lines # - #\": \"Righe da {{start}} a {{end}}\",\n\t\"Loading...\": \"Caricamento...\",\n\t\"Local repositories\": \"Repository locali\",\n\t\"Local repository\": \"Repository locale\",\n\t\"Local\": \"Locale\",\n\t\"Manage context\": \"Gestire il contesto\",\n\t\"Manage docs\": \"Gestisci documenti\",\n\t\"Manage project\": \"Gestisci il progetto\",\n\t\"Manage repositories\": \"Gestire i repository\",\n\t\"Manage subscription\": \"Gestisci abbonamento\",\n\t\"Manage templates\": \"Gestisci modelli\",\n\t\"Manage your general account settings\": \"Gestisci le impostazioni del tuo account generale\",\n\t\"Manage your preferences\": \"Gestisci le tue preferenze\",\n\t\"Manage your studio settings.\": \"Gestisci le tue impostazioni in studio.\",\n\t\"Manage your subscription plan\": \"Gestisci il tuo piano di abbonamento\",\n\t\"Manage\": \"Maneggio\",\n\t\"Missing source\": \"Fonte mancante\",\n\t\"More actions\": \"Più azioni\",\n\t\"Move\": \"Mossa\",\n\t\"My templates\": \"I miei modelli\",\n\t\"Name\": \"Nome\",\n\t\"Natural language search\": \"Ricerca in lingua naturale\",\n\t\"Navigate your codebase\": \"Naviga nella base del codice\",\n\t\"Navigate\": \"Naviga\",\n\t\"New conversation\": \"Nuova conversazione\",\n\t\"New project\": \"Nuovo progetto\",\n\t\"New studio conversation\": \"Nuova conversazione in studio\",\n\t\"New tab\": \"Nuova scheda\",\n\t\"New\": \"Nuova\",\n\t\"Next page\": \"Pagina successiva\",\n\t\"Next\": \"Prossima\",\n\t\"No change in payment status identified.\": \"Nessun cambiamento nello stato del pagamento identificato.\",\n\t\"No commands found...\": \"Nessun comando trovato ...\",\n\t\"No file selected\": \"Nessun file selezionato\",\n\t\"No files found...\": \"Nessun file trovato...\",\n\t\"No references or definitions found\": \"Nessun riferimento o definizione trovati\",\n\t\"No repositories found...\": \"Nessun repository trovato ...\",\n\t\"No results\": \"Nessun risultato\",\n\t\"No uses left. Uses reset at\": \"Nessun usato a sinistra. Usa il ripristino a\",\n\t\"Normal\": \"Normale\",\n\t\"Not synced\": \"Non sincronizzato\",\n\t\"Open Command Bar\": \"Apri barra di comando\",\n\t\"Open account settings\": \"Apri le impostazioni dell'account\",\n\t\"Open in GitHub\": \"Aperto in Github\",\n\t\"Open in split view\": \"Aprire in vista divisa\",\n\t\"Open in {{viewer}}\": \"Apri in {{viewer}}\",\n\t\"Open subscription settings\": \"Apri le impostazioni di abbonamento\",\n\t\"Open\": \"Aprire\",\n\t\"Paste a link to any documentation web page\": \"Incolla un link a qualsiasi pagina Web di documentazione\",\n\t\"Paste a link to any public repository hosted on GitHub\": \"Incolla un collegamento a qualsiasi repository pubblico ospitato su GitHub\",\n\t\"Permanently delete <2>{{projectName}}</2> and remove all the data associated to it. Repositories will remain accessible in your GitHub account.\": \"Elimina permanentemente <2>{{projectName}}</2> e rimuovere tutti i dati associati ad esso. I repository rimarranno accessibili nel tuo account GitHub.\",\n\t\"Personal\": \"Personale\",\n\t\"Plans\": \"Piani\",\n\t\"Please log into your GitHub account to complete setup\": \"Accedi al tuo account GitHub per completare la configurazione\",\n\t\"Please log into your GitHub account to complete setup, this helps us combat misuse.\": \"Accedi al tuo account GitHub per completare la configurazione\",\n\t\"Precise code navigation\": \"Navigazione precisa nel codice\",\n\t\"Preferences\": \"Preferenze\",\n\t\"Press <2>cmdKey</2> <4>K</4> on your keyboard to open the Command bar and add a repository.\": \"Premere <2>{{cmdKey}}</2> <4>K</4> sulla tastiera per aprire la barra di comando e aggiungere un repository.\",\n\t\"Press <2>cmdKey</2> <4>K</4> on your keyboard to open the Command bar.\": \"Premere <2>{{cmdKey}}</2> <4> K </4> sulla tastiera per aprire la barra di comando.\",\n\t\"Privacy policy\": \"Termini sulla privacy\",\n\t\"Private repositories\": \"Repository privati\",\n\t\"Private repository\": \"Repository privato\",\n\t\"Problem details and System configuration\": \"Dettagli del problema e configurazione di sistema\",\n\t\"Project settings\": \"Impostazioni del progetto\",\n\t\"Project title\": \"Titolo del progetto\",\n\t\"Project\": \"Progetto\",\n\t\"Projects require at least one synced repository.\": \"I progetti richiedono almeno un repository sincronizzato.\",\n\t\"Projects\": \"Progetti\",\n\t\"Prompt guide\": \"Guida ai prompt\",\n\t\"Prompt templates\": \"Modelli rapidi\",\n\t\"Prompts\": \"Richiede\",\n\t\"Provide any steps necessary to reproduce the problem...\": \"Fornisci eventuali passaggi necessari per riprodurre il problema...\",\n\t\"Public repositories\": \"Repository pubblici\",\n\t\"Public repository\": \"Repository pubblico\",\n\t\"Public\": \"Pubblica\",\n\t\"Query suggestions\": \"Suggerimenti query\",\n\t\"Queued...\": \"In coda...\",\n\t\"Re-check payment status\": \"Righemigono lo stato di pagamento\",\n\t\"Re-sync\": \"Risincronizza\",\n\t\"Reading \": \"Lettura \",\n\t\"Reading\": \"Lettura\",\n\t\"Recent conversations\": \"Conversazioni recenti\",\n\t\"Recent projects\": \"Progetti recenti\",\n\t\"Recently used\": \"Usato di recente\",\n\t\"Recommended: The classic response type\": \"Raccomandato: il tipo di risposta classica\",\n\t\"References\": \"Riferimenti\",\n\t\"Relaunch GitHub auth\": \"Riavvia autenticazione GitHub\",\n\t\"Release to open in split view\": \"Rilascio per aprire in vista divisa\",\n\t\"Remote removed \": \"Repository rimosso \",\n\t\"Remove file from code studio context\": \"Rimuovi il file dal contesto Code Studio\",\n\t\"Remove file\": \"Rimuovi file\",\n\t\"Remove from project\": \"Togliere dal progetto\",\n\t\"Remove from studio\": \"Togliere dallo studio\",\n\t\"Remove page from code studio context\": \"Rimuovi la pagina dal contesto di Code Studio\",\n\t\"Remove\": \"Rimuovi\",\n\t\"Removed\": \"Rimosso\",\n\t\"Rename code studio\": \"Rinomina code studio\",\n\t\"Rename\": \"Rinomina\",\n\t\"Report a bug\": \"Segnala un bug\",\n\t\"Repositories\": \"Repository\",\n\t\"Repository URL...\": \"URL del repository ...\",\n\t\"Repository indexed\": \"Repository indicizzato\",\n\t\"Repository types\": \"Tipi di repository\",\n\t\"Restart the app\": \"Riavvia l'app\",\n\t\"Restore\": \"Ripristina\",\n\t\"Result suggestions\": \"Suggerimenti risultati\",\n\t\"Results\": \"Risultati\",\n\t\"Retry\": \"Riprova\",\n\t\"Save changes\": \"Salva modifiche\",\n\t\"Save context changes before answer generation\": \"Salva le modifiche al contesto prima di generare la risposta\",\n\t\"Save\": \"Salva\",\n\t\"Search branch...\": \"Cerca branch...\",\n\t\"Search branches...\": \"Cerca branch...\",\n\t\"Search code in natural language\": \"Cerca codice in linguaggio naturale\",\n\t\"Search docs\": \"Cerca documenti\",\n\t\"Search file...\": \"Cerca file...\",\n\t\"Search files\": \"Cerca file\",\n\t\"Search files...\": \"Cerca file ...\",\n\t\"Search for code using regex\": \"Cerca codice usando espressioni regolari\",\n\t\"Search local repos...\": \"Cerca repos locali ...\",\n\t\"Search private repos...\": \"Cerca repos privati ...\",\n\t\"Search projects or commands...\": \"Cerca progetti o comandi ...\",\n\t\"Search public repositories...\": \"Cerca repository pubblici ...\",\n\t\"Search repos or Studio projects...\": \"Cerca repository o progetti Studio...\",\n\t\"Search studio conversations...\": \"Cerca conversazioni in studio ...\",\n\t\"Search templates...\": \"Modelli di ricerca ...\",\n\t\"Search using RegExp\": \"Cerca usando espressioni regolari\",\n\t\"Search your files in this project\": \"Cerca i tuoi file in questo progetto\",\n\t\"Search your repositories using RegExp\": \"Cerca i tuoi repository usando regexp\",\n\t\"Search\": \"Ricerca\",\n\t\"Searching...\": \"Ricerca in corso...\",\n\t\"Select a file or open a new tab to display it here.\": \"Seleziona un file o apri una nuova scheda per visualizzarlo qui.\",\n\t\"Select a folder containing a git repository\": \"Seleziona una cartella contenente un repository Git\",\n\t\"Select all\": \"Seleziona tutto\",\n\t\"Select branch\": \"Seleziona branch\",\n\t\"Select color theme:\": \"Seleziona tema colori:\",\n\t\"Select file\": \"Seleziona file\",\n\t\"Select folder\": \"Seleziona cartella\",\n\t\"Select less code\": \"Seleziona meno codice\",\n\t\"Select library\": \"Seleziona libreria\",\n\t\"Select repository\": \"Seleziona repository\",\n\t\"Select sections\": \"Seleziona le sezioni\",\n\t\"Select the interface colour scheme\": \"Seleziona la combinazione di colori dell'interfaccia\",\n\t\"Select the interface language\": \"Seleziona il linguaggio dell'interfaccia\",\n\t\"Select\": \"Seleziona\",\n\t\"Selected lines # - #.\": \"Righe selezionate {{start}} - {{end}}.\",\n\t\"Selected ranges will be used as context.\": \"Gli intervalli selezionati verranno utilizzati come contesto.\",\n\t\"Settings\": \"Impostazioni\",\n\t\"Setup bloop\": \"Configura bloop\",\n\t\"Show # more match_one\": \"Mostra altra {{count}} corrispondenza\",\n\t\"Show # more match_other\": \"Mostra altre {{count}} corrispondenze\",\n\t\"Show file\": \"Mostra file\",\n\t\"Show filters\": \"Mostra filtri\",\n\t\"Show less\": \"Mostra meno\",\n\t\"Show link\": \"Mostra link\",\n\t\"Show more\": \"Mostra altro\",\n\t\"Show search steps\": \"Mostra i passaggi di ricerca\",\n\t\"Show\": \"Mostra\",\n\t\"Sign In\": \"Accedi\",\n\t\"Sign in with GitHub\": \"Accedi con GitHub\",\n\t\"Sign out\": \"Disconnetti\",\n\t\"Skip (Not recommended)\": \"Salta (non consigliato)\",\n\t\"Skip\": \"Saltare\",\n\t\"Something went wrong\": \"Qualcosa è andato storto\",\n\t\"Start by selecting \": \"Inizia selezionando\",\n\t\"Start by selecting a repository and pressing Enter (↵) on your keyboard.\": \"Inizia selezionando un repository e premendo Invio (↵) sulla tastiera.\",\n\t\"Start by selecting a type repository you would like to index.\": \"Inizia selezionando un repository di tipo che si desidera indicizzare.\",\n\t\"Start indexing\": \"Inizia l'indicizzazione\",\n\t\"Start typing...\": \"Inizia a scrivere...\",\n\t\"Status\": \"Stato\",\n\t\"Stop generating\": \"Interrompi generazione\",\n\t\"Stop indexing\": \"Smettere di indicizzare\",\n\t\"Streaming response...\": \"Risposta in streaming ...\",\n\t\"Studio conversation require at least one context file.\": \"La conversazione in studio richiede almeno un file di contesto.\",\n\t\"Studio conversation\": \"Conversazione studio\",\n\t\"Studio conversations\": \"Conversazioni in studio\",\n\t\"Studio mode\": \"Modalità studio\",\n\t\"Studio project\": \"Progetto studio\",\n\t\"Studio requests\": \"Richieste in studio\",\n\t\"Studio\": \"Studio\",\n\t\"Submit bug report\": \"Invia segnalazione bug\",\n\t\"Submit crash report\": \"Invia rapporto arresto anomalo\",\n\t\"Submit\": \"Invia\",\n\t\"Subscription\": \"Sottoscrizione\",\n\t\"Switch branch\": \"Cambia branch\",\n\t\"Switch to\": \"Passa a\",\n\t\"Sync local git repos\": \"Sincronizzare i repository git locali\",\n\t\"Sync\": \"Sincronizza\",\n\t\"Synced\": \"Sincronizzato\",\n\t\"Syncing\": \"Sincronizzazione\",\n\t\"System Preference\": \"Preferenza di sistema\",\n\t\"System preferences\": \"Preferenze di Sistema\",\n\t\"Take a quick look\": \"Dai un'occhiata veloce\",\n\t\"Template title\": \"Titolo del modello\",\n\t\"Templates\": \"Modelli\",\n\t\"Terms & conditions\": \"Termini e condizioni\",\n\t\"The amount of times you can generate responses in Studio conversations per day.\": \"La quantità di volte in cui è possibile generare risposte nelle conversazioni in studio al giorno.\",\n\t\"The diff has been applied locally.\": \"La diff è stata applicata localmente.\",\n\t\"The folder you selected has multiple git repositories nested inside.\": \"La cartella selezionata ha più repository GIT nidificati all'interno.\",\n\t\"The folder you selected is not a git repository.\": \"La cartella selezionata non è un repository Git.\",\n\t\"The following changes can be applied to your repository. Make sure the generated diffs are valid before you apply the changes.\": \"Le seguenti modifiche possono essere applicate al tuo repository. Assicurati che le diff generate siano valide prima di applicare le modifiche.\",\n\t\"The following changes represent the git diff for the remote repository. Please note that these changes cannot be applied directly to a remote repository. Use the \\\"Copy\\\" button to copy the changes and apply them locally.\": \"Le seguenti modifiche rappresentano la diff git per il repository remoto. Si prega di notare che queste modifiche non possono essere applicate direttamente a un repository remoto. Usa il pulsante \\\"Copia\\\" per copiare le modifiche e applicarle localmente.\",\n\t\"The line of code where identifier is defined\": \"La riga di codice in cui l'identificatore è definito\",\n\t\"The line of code where the identifier is referenced\": \"La riga di codice in cui l'identificatore è referenziato\",\n\t\"The whole file will be used as context.\": \"L'intero file verrà utilizzato come contesto.\",\n\t\"Theme\": \"Tema\",\n\t\"This is not a public repository / We couldn't find this repository\": \"Questo non è un repository pubblico / Non abbiamo trovato questo repository\",\n\t\"This might be because the file is too big or it has one of bloop's excluded file types.\": \"Ciò potrebbe essere dovuto al fatto che il file è troppo grande o ha uno dei tipi di file esclusi di Bloop.\",\n\t\"This project is empty\": \"Questo progetto è vuoto\",\n\t\"Tip: Select code to create ranges for context use.\": \"Suggerimento: seleziona codice per creare intervalli da usare come contesto.\",\n\t\"To update your app, please visit our releases page on GitHub and download the latest version manually. Thank you for using bloop.\": \"Per aggiornare l'app, visita la nostra pagina delle versioni su GitHub e scarica manualmente l'ultima versione. Grazie per aver usato bloop.\",\n\t\"Today\": \"Oggi\",\n\t\"Toggle black theme\": \"Attiva tema nero\",\n\t\"Toggle regex search\": \"Attiva la ricerca Regex\",\n\t\"Toggle\": \"Attivare\",\n\t\"Token limit exceeded\": \"Limite di token superato\",\n\t\"Token limit exceeded. Reduce the number of context files or messages to enable the ability to generate.\": \"Limite di token superato. Riduci il numero di file di contesto o messaggi per abilitare la generazione.\",\n\t\"Token limit reached, this answer may be incomplete. To generate a full answer, please reduce the number of tokens used and regenerate.\": \"Raggiunto il limite di token, questa risposta potrebbe essere incompleta. Per generare una risposta completa, riduci il numero di token usati e rigenera.\",\n\t\"Try again\": \"Riprova\",\n\t\"Unavailable\": \"Non disponibile\",\n\t\"Unlimited code studio requests\": \"Richieste di studio di codice illimitate\",\n\t\"Unlimited usage and premium features are activated.\": \"Uso illimitato e funzionalità premium attivate.\",\n\t\"Unlock the value of your existing code, using AI\": \"Sblocca il valore del tuo codice esistente, usando l'AI\",\n\t\"Untitled project\": \"Progetto senza titolo\",\n\t\"Update Required\": \"Aggiornamento richiesto\",\n\t\"Upgrade now\": \"Effettua upgrade ora\",\n\t\"Upgrade plan\": \"Effettua upgrade piano\",\n\t\"Upgrade\": \"Effettua upgrade\",\n\t\"Upvote\": \"Votare in positivo\",\n\t\"Usage exceeded\": \"Utilizzo superato\",\n\t\"Usage resets in\": \"L'uso si resetta tra\",\n\t\"Usage status\": \"Stato di utilizzo\",\n\t\"Use GitHub to sign in to your account\": \"Usa GitHub per accedere al tuo account\",\n\t\"Use black theme\": \"Usa il tema nero\",\n\t\"Use file\": \"Usa file\",\n\t\"Use template\": \"Usa modello\",\n\t\"Use templates\": \"Usa modelli\",\n\t\"Use\": \"Usa\",\n\t\"User\": \"Utente\",\n\t\"Verifying access...\": \"Verifica accesso...\",\n\t\"View all results\": \"Mostra tutti i risultati\",\n\t\"View all\": \"Mostra tutti\",\n\t\"View bloop app documentation on our website\": \"Visualizza la documentazione dell'app Bloop sul nostro sito Web\",\n\t\"View history\": \"Visualizza cronologia\",\n\t\"View in {{viewer}}\": \"Visualizza in {{viewer}}\",\n\t\"View\": \"Visualizza\",\n\t\"Visit the downloads page\": \"Visita la pagina dei download\",\n\t\"Waiting for authentication...\": \"In attesa di autenticazione...\",\n\t\"Watch\": \"Guarda\",\n\t\"We can’t generate a response because some files have a missing source in your Context files.\": \"Non possiamo generare una risposta perché alcuni file hanno una fonte mancante nei file di contesto.\",\n\t\"We couldn't answer your question. You can try asking again in a few moments, or rephrasing your question.\": \"Non siamo riusciti a rispondere alla tua domanda. Puoi riprovare tra qualche istante o riformulare la domanda.\",\n\t\"We want to make this the best experience for you. If you encountered a bug, please submit this bug report to us. Our team will investigate as soon as possible.\": \"Vogliamo offrire la migliore esperienza possibile. Se hai riscontrato un bug, inviaci questo rapporto. Il nostro team indagherà il prima possibile.\",\n\t\"We weren't able to identify any references at the moment\": \"Non siamo stati in grado di identificare alcun riferimento al momento\",\n\t\"We've made some exciting enhancements to bloop! To continue enjoying the full functionality, including the natural language search feature, please update your app to the latest version.\": \"Abbiamo apportato alcuni miglioramenti entusiasmanti a bloop! Per continuare a godere di tutte le funzionalità, inclusa la ricerca in linguaggio naturale, aggiorna l'app all'ultima versione.\",\n\t\"We've redirected you to Stripe to complete your transaction. <2>Launch manually</2> if it didn't work.\": \"Ti abbiamo reindirizzato a Stripe per completare la tua transazione. <2> Avvia manualmente </2> se non ha funzionato.\",\n\t\"Welcome to bloop\": \"Benvenuto in bloop\",\n\t\"We’ve updated our auth service to make bloop more secure, please reauthorise your client with GitHub\": \"Abbiamo aggiornato il nostro servizio di autenticazione per rendere bloop più sicuro, per favore riesegui l'autenticazione del tuo client con GitHub\",\n\t\"Whole file\": \"File intero\",\n\t\"Whole page\": \"Pagina intera\",\n\t\"Write a message, @ to mention files, folders or docs...\": \"Scrivi un messaggio, @ per menzionare file, cartelle o documenti ...\",\n\t\"Write studio prompts faster with pre-written templates\": \"Scrivi lo studio chiede più velocemente con modelli pre-scritti\",\n\t\"Yesterday\": \"Ieri\",\n\t\"You can add 3 types of repositories, private, public and local.\": \"Puoi aggiungere 3 tipi di repository, privati, pubblici e locali.\",\n\t\"You\": \"Voi\",\n\t\"You've run out of free usage for today, please wait for your quota to reset or upgrade for unlimited usage\": \"Hai esaurito l'uso gratuito per oggi, attendi il reset del limite o effettua l'upgrade per l'uso illimitato\",\n\t\"You've upgraded your account!\": \"Hai effettuato l'upgrade del tuo account!\",\n\t\"Your quota resets every 24 hours, upgrade for unlimited uses\": \"Il tuo limite si resetta ogni 24 ore, effettua l'upgrade per usi illimitati\",\n\t\"Your subscription has expired. Please update your payment details to avoid being unsubscribed.\": \"Il tuo abbonamento è scaduto. Aggiorna i dettagli del pagamento per evitare di essere annullati.\",\n\t\"and \": \"e \",\n\t\"avatar\": \"avatar\",\n\t\"billed monthly\": \"fatturato mensilmente\",\n\t\"bloop automatically excludes certain files from indexing. This file might be too big or it might have an excluded file type.\": \"bloop esclude automaticamente alcuni file dall'indicizzazione. Questo file potrebbe essere troppo grande o avere un tipo di file escluso.\",\n\t\"bloop automatically excludes certain files from the indexing. This file might be too big or it might have an excluded file type.\": \"bloop esclude automaticamente alcuni file dall'indicizzazione. Questo file potrebbe essere troppo grande o avere un tipo di file escluso.\",\n\t\"bloop crashed unexpectedly\": \"bloop si è chiuso inaspettatamente\",\n\t\"cancelled\": \"annullato\",\n\t\"cancelling\": \"annullamento\",\n\t\"chats in bloop\": \"chat in bloop\",\n\t\"definition\": \"definizione\",\n\t\"done\": \"fatto\",\n\t\"indexing\": \"indicizzazione\",\n\t\"key\": \"chiave\",\n\t\"or \": \"oppure\",\n\t\"or go to the following link\": \"o vai al seguente link\",\n\t\"or visit: \": \"o visita: \",\n\t\"reference\": \"riferimento\",\n\t\"syncing\": \"sincronizzazione\",\n\t\"to seamlessly explore code across all branches in your GitHub repositories, maximizing your code discovery capabilities.\": \"per esplorare il codice in tutti i branch dei tuoi repository GitHub senza interruzioni, massimizzando le tue capacità di scoperta del codice.\",\n\t\"uses left_one\": \"rimanente\",\n\t\"uses left_other\": \"rimanenti\",\n\t\"{{count}} context files used\": \"{{count}} file di contesto utilizzati\",\n\t\"{{count}} context files used_one\": \"{{count}} file di contesto utilizzato\",\n\t\"{{count}} context files used_other\": \"{{count}} file di contesto utilizzati\",\n\t\"{{repoName}} is currently indexing as soon as it is finished it will be added to your project.\": \"{{repoName}} sta attualmente indicizzando non appena sarà finito, verrà aggiunto al tuo progetto.\",\n\t\"<0>Use your cursor to select any piece of code within a file and ask bloop to explain it by pressing \\\"Explain\\\" in the floating toolbar.</0>\": \"<0> Usa il cursore per selezionare qualsiasi pezzo di codice all'interno di un file e chiedere a Bloop di spiegarlo premendo \\\"Spiega\\\" nella barra degli strumenti galleggianti. </0>\",\n\t\"Dismiss tutorial\": \"Tutorial di licenziamento\",\n\t\"Click on an identifier and jump to its references and definition in a heart beat.\": \"Fai clic su un identificatore e passa ai suoi riferimenti e definizione in un battito cardiaco.\",\n\t\"Hide search steps\": \"Nascondere i passaggi di ricerca\",\n\t\"Close currently focused tab\": \"Close Scheda attualmente focalizzata\",\n\t\"Search docs...\": \"Cerca documenti ...\",\n\t\"Indexed documentation web pages\": \"Pagine Web di documentazione indicizzata\",\n\t\"Add any library documentation\": \"Aggiungi qualsiasi documentazione di libreria\",\n\t\"Add documentation\": \"Aggiungi documentazione\",\n\t\"We couldn't find any docs at that link. Try again or make sure the link is correct!\": \"Non siamo riusciti a trovare alcun documento a quel link. Riprova o assicurati che il collegamento sia corretto!\",\n\t\"Manage your general project settings\": \"Gestisci le tue impostazioni generali del progetto\",\n\t\"Prompt\": \"Richiesta\",\n\t\"Write your prompt...\": \"Scrivi il tuo prompt ...\",\n\t\"Usage resets at\": \"L'uso si ripristina a\",\n\t\"Upgrade to Personal plan\": \"Aggiorna al piano personale\",\n\t\"Restore session\": \"Ripristina la sessione\",\n\t\"In this project\": \"In questo progetto\",\n\t\"Existing studio conversations\": \"Conversazioni in studio esistenti\",\n\t\"Select section\": \"Seleziona la sezione\",\n\t\"Clear section\": \"Pulisci sezione\",\n\t\"Let’s get you started with bloop!\": \"Ti cominciamo con Bloop!\",\n\t\"<0>{{repoName}}</0> has finished indexing and can be added to your projects. Click the button to below to add it to the current project.\": \"<0>{{repoName}}</0> ha terminato l'indicizzazione e può essere aggiunto ai tuoi progetti. Fai clic sul pulsante sotto per aggiungerlo al progetto corrente.\",\n\t\"Start by selecting again and pressing Enter (↵) on your keyboard.\": \"Inizia selezionando di nuovo e premendo Invio (↵) sulla tastiera.\",\n\t\"{{repoName}} is currently indexing as soon as it is finished you will be able to add it to your project.\": \"{{repoName}} sta attualmente indicizzando non appena sarà finito, sarai in grado di aggiungerlo al tuo progetto.\",\n\t\"Default\": \"Predefinita\",\n\t\"Fallback: Use if experiencing problems with the default one\": \"Fallback: usa se si riscontra problemi con quello predefinito\",\n\t\"Simplified\": \"Semplificata\",\n\t\"Select the input type to use in conversations\": \"Seleziona il tipo di input da utilizzare nelle conversazioni\",\n\t\"Recommended: The classic input\": \"Consigliato: l'ingresso classico\",\n\t\"Conversation input\": \"Input di conversazione\",\n\t\"Add multiple files\": \"Aggiungi più file\",\n\t\"Explain lines {{lineStart}} - {{lineEnd}} in {{filePath}}\": \"Spiega le righe {{lineStart}} - {{lineEnd}} in {{filePath}}\"\n}"
  },
  {
    "path": "client/src/locales/ja.json",
    "content": "{\n\t\"# match_one\": \"{{count}} 件\",\n\t\"# match_other\": \"{{count}} 件\",\n\t\"# of #_one\": \"{{count}}の{{total}}\",\n\t\"# of #_other\": \"{{count}}の{{total}}\",\n\t\"# ranges_one\": \"{{count}}つの範囲\",\n\t\"# ranges_other\": \"{{count}}つの範囲\",\n\t\"# sections_one\": \"{{count}}セクション\",\n\t\"# sections_other\": \"{{count}}セクション\",\n\t\"# selected section_one\": \"選択された {{count}} つのセクション\",\n\t\"# selected section_other\": \"選択された {{count}} つのセクション\",\n\t\"# tokens_one\": \"トークン\",\n\t\"# tokens_other\": \"トークン\",\n\t\"$20 / billed monthly\": \"毎月20ドル /請求されます\",\n\t\"<0>#</0> of # tokens\": \"{{total}} トークン中 <0>{{count}}</0> 個\",\n\t\"<0>#</0> of # tokens_one\": \"{{total}} トークン中 <0>{{count}}</0> 個\",\n\t\"<0>#</0> of # tokens_other\": \"{{total}} トークン中 <0>{{count}}</0> 個\",\n\t\"<0></0><1></1> to navigate.\": \"<0> </0> <1> </1>ナビゲートします。\",\n\t\"<0>Create a new conversation with bloop by hitting <2></2> on your keyboard or by pressing the <4></4> in the header bar.</0>\": \"<0>キーボードで<2> </2>を押すか、ヘッダーバーの<4> </4>を押すことにより、Bloopとの新しい会話を作成します。</0>\",\n\t\"<0>To begin, open a file from the sidebar on the left. Once you have a file open, you can ask bloop to quickly explain it by hitting <2></2> on your keyboard or by selecting \\\"Explain file\\\" from the <7></7> popup menu.</0>\": \"<0>開始するには、左側のサイドバーからファイルを開きます。 ファイルを開いたら、キーボードで<2> </2>を押すか、<7> </7>ポップアップメニューから「ファイルの説明」を選択することで、Bloopにすばやく説明するように依頼することができます。</0>\",\n\t\"<0>Use your cursor to select any piece of code within a file and ask bloop to explain it by pressing \\\"Explain\\\" in the floating toolbar.</0>\": \"<0>カーソルを使用してファイル内のコードを選択し、フローティングツールバーで「説明」を押してBloopに説明するように依頼します。</0>\",\n\t\"<0>repoName</0> has finished indexing and was added to the context of the current project. You can also use it in your other projects now.\": \"<0>{{repoName}}</0>はインデックス作成が終了し、現在のプロジェクトのコンテキストに追加されました。 今すぐ他のプロジェクトで使用することもできます。\",\n\t\"Account settings\": \"アカウント設定\",\n\t\"Actions\": \"行動\",\n\t\"Add a repository from your local machine\": \"ローカルマシンからリポジトリを追加する\",\n\t\"Add at least one context file before submitting your first request.\": \"最初のリクエストを送信する前に、少なくとも1つのコンテキストファイルを追加します。\",\n\t\"Add doc to studio\": \"スタジオにドキュメントを追加します\",\n\t\"Add docs\": \"ドキュメントを追加します\",\n\t\"Add file to code studio context\": \"コードスタジオコンテキストにファイルを追加します\",\n\t\"Add file to studio\": \"スタジオにファイルを追加します\",\n\t\"Add file\": \"ファイルを追加します\",\n\t\"Add files to studio\": \"スタジオにファイルを追加します\",\n\t\"Add files\": \"追加ファイル\",\n\t\"Add local repository\": \"ローカルリポジトリを追加します\",\n\t\"Add new repository\": \"新しいリポジトリを追加します\",\n\t\"Add private repository\": \"プライベートリポジトリを追加します\",\n\t\"Add public repository\": \"パブリックリポジトリを追加します\",\n\t\"Add repository\": \"リポジトリを追加します\",\n\t\"Add to existing\": \"既存に追加します\",\n\t\"Add to project\": \"プロジェクトに追加します\",\n\t\"Add\": \"追加\",\n\t\"Adding repository\": \"リポジトリの追加\",\n\t\"All payments, invoices and billing information are managed in Stripe.\": \"すべての支払い、請求書、請求情報は、ストライプで管理されます。\",\n\t\"All sections\": \"すべてのセクション\",\n\t\"All templates\": \"すべてのテンプレート\",\n\t\"All\": \"全て\",\n\t\"Answer speed\": \"回答速度\",\n\t\"Application theme\": \"アプリケーションテーマ\",\n\t\"Apply changes\": \"変更を適用します\",\n\t\"Ask questions about your codebases in natural language, just like you’d speak to ChatGPT. Get started by syncing a repo, then open the repo and start chatting.\": \"ChatGPTに話すように、自然言語でコードベースについて質問します。リポジトリを同期して開始し、リポジトリを開いてチャットを開始します。\",\n\t\"Ask your first question\": \"あなたの最初の質問をしてください\",\n\t\"Assistant\": \"アシスタント\",\n\t\"Back to current\": \"現在に戻ります\",\n\t\"Back\": \"戻る\",\n\t\"Bad response\": \"誤回答\",\n\t\"Bad\": \"悪い\",\n\t\"Below are a few suggestions you can ask me to get started:\": \"以下は、あなたが私に始めるように頼むことができるいくつかの提案です：\",\n\t\"Black\": \"黒\",\n\t\"Bloop needs to index your repository first. This process takes a few seconds and happens only one time per repository.\": \"Bloopは最初にリポジトリをインデックス化する必要があります。 このプロセスには数秒かかり、リポジトリごとに1回しか発生しません。\",\n\t\"Branches\": \"枝\",\n\t\"By continuing you accept our\": \"継続することで、次のことを承認します。\",\n\t\"By submitting this crash report you agree to send it to bloop for investigation.\": \"このクラッシュレポートを送信すると、調査のためにbloopに送信することに同意したことになります。\",\n\t\"Cancel diff generation\": \"差分生成をキャンセル\",\n\t\"Cancel\": \"キャンセル\",\n\t\"Cancelled\": \"キャンセル\",\n\t\"Cancelling...\": \"キャンセル中...\",\n\t\"Change application colour theme\": \"アプリケーションの色のテーマを変更します\",\n\t\"Chat conversations\": \"チャット会話\",\n\t\"Clear conversation\": \"明確な会話\",\n\t\"Clear input\": \"入力をクリアします\",\n\t\"Clear range\": \"クリア範囲\",\n\t\"Clear ranges\": \"クリア範囲\",\n\t\"Clear section\": \"クリアセクション\",\n\t\"Clear sections\": \"クリアセクション\",\n\t\"Clear selection\": \"明確な選択\",\n\t\"Click to copy\": \"クリックしてコピーします\",\n\t\"Cloning\": \"クローニング\",\n\t\"Cloning...\": \"クローン中...\",\n\t\"Close all open tabs\": \"すべての開くタブを閉じます\",\n\t\"Close all tabs\": \"すべてのタブを閉じます\",\n\t\"Close current tab\": \"現在のタブを閉じます\",\n\t\"Close search\": \"検索を閉じます\",\n\t\"Close tab\": \"タブを閉じる\",\n\t\"Close\": \"閉じる\",\n\t\"Code search\": \"コード検索\",\n\t\"Code studio helps you write scripts, create unit tests, debug issues or generate anything else you can think of using AI! Sync a repo, then create a code studio project.\": \"Code Studioは、AIを使用してスクリプトを作成したり、ユニットテストを作成したり、問題をデバッグしたり、他に思いつくものを生成するのに役立ちます！ リポジトリを同期し、Code Studioプロジェクトを作成します。\",\n\t\"Collapse\": \"崩壊\",\n\t\"Commands\": \"コマンド\",\n\t\"Complete your transaction in Stripe...\": \"ストライプでトランザクションを完了してください...\",\n\t\"Confirm\": \"確認\",\n\t\"Connect account\": \"アカウントを接続する\",\n\t\"Context files\": \"コンテキストファイル\",\n\t\"Continue from this state\": \"この状態から続けます\",\n\t\"Continue\": \"次へ\",\n\t\"Copied\": \"コピーしました\",\n\t\"Copy link\": \"リンクをコピーする\",\n\t\"Copy message\": \"メッセージをコピーします\",\n\t\"Copy\": \"コピー\",\n\t\"Create line ranges\": \"ライン範囲を作成します\",\n\t\"Create new project\": \"新しいプロジェクトを作成します\",\n\t\"Create new\": \"新しく作る\",\n\t\"Create ranges\": \"範囲を作成します\",\n\t\"Create template\": \"テンプレートを作成します\",\n\t\"Current\": \"現在\",\n\t\"Currently active\": \"現在アクティブ\",\n\t\"Dark\": \"暗い\",\n\t\"Default project\": \"デフォルトプロジェクト\",\n\t\"Delete all conversations\": \"すべての会話を削除します\",\n\t\"Delete conversation\": \"会話を削除します\",\n\t\"Delete template\": \"テンプレートを削除します\",\n\t\"Delete\": \"消去\",\n\t\"Desktop app\": \"デスクトップアプリ\",\n\t\"Diff generation failed\": \"差分の生成に失敗しました\",\n\t\"Directories\": \"ディレクトリ\",\n\t\"Disconnect\": \"切断する\",\n\t\"Discord\": \"Discord\",\n\t\"Dismiss tutorial\": \"チュートリアルを却下します\",\n\t\"Display\": \"画面\",\n\t\"Docs\": \"ドキュメント\",\n\t\"Documentation URL...\": \"ドキュメントURL ...\",\n\t\"Documentation in studio\": \"スタジオでのドキュメント\",\n\t\"Documentation\": \"ドキュメント\",\n\t\"Done\": \"終わり\",\n\t\"Downgrade\": \"ダウングレード\",\n\t\"Downvote\": \"ダウンボート\",\n\t\"Edit ranges\": \"編集範囲\",\n\t\"Edit sections\": \"編集セクション\",\n\t\"Edit selected lines\": \"選択した行を編集します\",\n\t\"Edit selected sections\": \"選択したセクションを編集します\",\n\t\"Edit template\": \"テンプレートを編集します\",\n\t\"Edit\": \"編集\",\n\t\"Editing previously submitted questions will discard all answers and questions following it\": \"以前に提出された質問を編集すると、それに続くすべての回答と質問が破棄されます\",\n\t\"Email address\": \"メールアドレス\",\n\t\"Email is not valid\": \"メールアドレスは無効です\",\n\t\"Email is required\": \"メールが必要です\",\n\t\"Email\": \"メール\",\n\t\"Error\": \"エラー\",\n\t\"Existing studio conversations\": \"既存のスタジオの会話\",\n\t\"Expand\": \"拡大する\",\n\t\"Experimental: Faster but less accurate\": \"実験的：速度は速いが精度は低い\",\n\t\"Explain a file\": \"ファイルを説明します\",\n\t\"Explain current file\": \"現在のファイルを説明します\",\n\t\"Explain file\": \"ファイルを説明します\",\n\t\"Explain the purpose of the file {{filePath}}, from lines {{lineStart}} - {{lineEnd}}\": \"ファイル {{filePath}} の目的を {{lineStart}} から {{lineEnd}} 行まで説明します。\",\n\t\"Explain\": \"説明\",\n\t\"Failed to apply the diff\": \"差分の適用に失敗しました\",\n\t\"Failed to get a response from OpenAI. Try again in a few moments.\": \"OpenAIからの回答を取得できませんでした。しばらくしてからもう一度試してください。\",\n\t\"Fast\": \"速い\",\n\t\"File not indexed\": \"ファイルはインデックス化されていません\",\n\t\"File search\": \"ファイル検索\",\n\t\"File\": \"ファイル\",\n\t\"Files\": \"ファイル\",\n\t\"Files, paths or folders...\": \"ファイル、パス、またはフォルダー...\",\n\t\"Filter repositories by\": \"レポジトリをフィルターします\",\n\t\"Filters\": \"フィルタ\",\n\t\"First name is required\": \"名が必要です\",\n\t\"First name\": \"名\",\n\t\"Fold everything\": \"すべて折りたたむ\",\n\t\"Follow us on Twitter\": \"Twitterでフォローします\",\n\t\"Force index\": \"フォースインデックス\",\n\t\"Free\": \"無料\",\n\t\"General\": \"一般\",\n\t\"Generate code using AI\": \"AIを使用してコードを生成する\",\n\t\"Generate\": \"生成する\",\n\t\"Generated diffs to be applied\": \"適用するための生成された差分\",\n\t\"Generating answer...\": \"答えを生成します...\",\n\t\"Generating diff...\": \"差分を生成中...\",\n\t\"Generating response...\": \"回答を生成中...\",\n\t\"GitHub Branches\": \"GitHubブランチ\",\n\t\"GitHub\": \"github\",\n\t\"Give your template a title\": \"テンプレートにタイトルを付けてください\",\n\t\"Go back\": \"戻る\",\n\t\"Go to current state\": \"現在の状態に移動します\",\n\t\"Good\": \"良い\",\n\t\"Got it\": \"理解しました\",\n\t\"Hi, I am bloop! In <2><0></0>Chat mode</2> I can answer any questions related to any of your repositories.\": \"こんにちは、私はブループです！ <2><0></0>チャットモード</2>リポジトリのいずれかに関連する質問に答えることができます。\",\n\t\"Hi, I am bloop! In <2><0></0>Studio mode</2> you can choose files from your codebase, write a prompt and generate patches, scripts and tests.\": \"こんにちは、私はブループです！ <2> <0> </0> スタジオモード </2>コードベースからファイルを選択し、プロンプトを書き、パッチ、スクリプト、テストを生成できます。\",\n\t\"Hide\": \"非表示\",\n\t\"History\": \"履歴\",\n\t\"How fast or precise bloop's answers will be.\": \"Bloopの答えがどれだけ速くまたは正確になるか。\",\n\t\"In this file\": \"ファイル内\",\n\t\"Index multiple branches\": \"複数のブランチをインデックスします\",\n\t\"Index repository\": \"インデックスリポジトリ\",\n\t\"Index\": \"索引\",\n\t\"Indexed documentation web pages\": \"インデックス付きドキュメントWebページ\",\n\t\"Indexed local repositories\": \"インデックス付きローカルリポジトリ\",\n\t\"Indexed\": \"インデックス付き\",\n\t\"Indexing repositories\": \"リポジトリのインデックス付け\",\n\t\"Indexing repository\": \"インデックスリポジトリ\",\n\t\"Indexing\": \"インデックス付け\",\n\t\"Indexing...\": \"インデックス中...\",\n\t\"Individual\": \"個人\",\n\t\"Invert\": \"反転\",\n\t\"Join Discord\": \"Discordに参加してください\",\n\t\"Language\": \"言語\",\n\t\"Languages\": \"言語\",\n\t\"Last name is required\": \"姓が必要です\",\n\t\"Last name\": \"姓\",\n\t\"Last updated \": \"最終更新\",\n\t\"Latest model updates\": \"最新のモデルの更新\",\n\t\"Launch manually\": \"手動で起動します\",\n\t\"Let’s get you started with bloop!\": \"Bloopを始めましょう！\",\n\t\"Light\": \"ライト\",\n\t\"Lines # - #\": \"行{{start}} - {{end}}\",\n\t\"Loading...\": \"読み込み中...\",\n\t\"Local repositories\": \"ローカルリポジトリ\",\n\t\"Local repository\": \"ローカルリポジトリ\",\n\t\"Local\": \"地元\",\n\t\"Manage context\": \"コンテキストを管理します\",\n\t\"Manage docs\": \"ドキュメントを管理します\",\n\t\"Manage project\": \"プロジェクトを管理します\",\n\t\"Manage repositories\": \"リポジトリを管理します\",\n\t\"Manage subscription\": \"サブスクリプションを管理します\",\n\t\"Manage templates\": \"テンプレートを管理します\",\n\t\"Manage your general account settings\": \"一般的なアカウント設定を管理します\",\n\t\"Manage your preferences\": \"好みを調整する\",\n\t\"Manage your studio settings.\": \"スタジオ設定を管理します。\",\n\t\"Manage your subscription plan\": \"サブスクリプションプランを管理します\",\n\t\"Manage\": \"管理\",\n\t\"Missing source\": \"不足しているソース\",\n\t\"More actions\": \"より多くのアクション\",\n\t\"Move\": \"動く\",\n\t\"My templates\": \"私のテンプレート\",\n\t\"Name\": \"名前\",\n\t\"Natural language search\": \"自然言語検索\",\n\t\"Navigate\": \"ナビゲートします\",\n\t\"New conversation\": \"新しい会話\",\n\t\"New project\": \"新しいプロジェクト\",\n\t\"New studio conversation\": \"新しいスタジオの会話\",\n\t\"New tab\": \"新しいタブ\",\n\t\"New\": \"新しい\",\n\t\"Next page\": \"次のページ\",\n\t\"Next\": \"次\",\n\t\"No change in payment status identified.\": \"支払いステータスの変更は特定されていません。\",\n\t\"No commands found...\": \"コマンドが見つかりません...\",\n\t\"No file selected\": \"ファイルが選択されていません\",\n\t\"No files found...\": \"ファイルが見つかりません...\",\n\t\"No references or definitions found\": \"参照や定義は見つかりません\",\n\t\"No repositories found...\": \"リポジトリは見つかりません...\",\n\t\"No results\": \"結果がありません\",\n\t\"No uses left. Uses reset at\": \"使用は残っていません。 でリセットを使用します\",\n\t\"Normal\": \"普通\",\n\t\"Not a git repository\": \"Gitリポジトリではありません\",\n\t\"Not synced\": \"同期していません\",\n\t\"Open Command Bar\": \"オープンコマンドバー\",\n\t\"Open account settings\": \"オープンアカウント設定\",\n\t\"Open in GitHub\": \"Githubで開きます\",\n\t\"Open in split view\": \"スプリットビューで開きます\",\n\t\"Open in {{viewer}}\": \"{{viewer}}で開く\",\n\t\"Open subscription settings\": \"サブスクリプション設定を開きます\",\n\t\"Open\": \"開ける\",\n\t\"Paste a link to any documentation web page\": \"ドキュメントWebページへのリンクを貼り付けます\",\n\t\"Paste a link to any public repository hosted on GitHub\": \"GitHubでホストされている公開リポジトリへのリンクを貼り付けます\",\n\t\"Permanently delete <2>{{projectName}}</2> and remove all the data associated to it. Repositories will remain accessible in your GitHub account.\": \"永続的に<2>{{projectName}}</2>を削除し、それに関連付けられたすべてのデータを削除します。 リポジトリは、GitHubアカウントでアクセスできます。\",\n\t\"Personal\": \"個人的\",\n\t\"Plans\": \"予定\",\n\t\"Please log into your GitHub account to complete setup, this helps us combat misuse.\": \"セットアップを完了するため、GitHubアカウントにログインしてください\",\n\t\"Precise code navigation\": \"正確なコードナビゲーション\",\n\t\"Preferences\": \"設定\",\n\t\"Press <2>cmdKey</2> <4>K</4> on your keyboard to open the Command bar and add a repository.\": \"<2>{{cmdKey}}</2> <4>K</4>をキーボードに押して、コマンドバーを開き、リポジトリを追加します。\",\n\t\"Press <2>cmdKey</2> <4>K</4> on your keyboard to open the Command bar.\": \"キーボードに<2>{{cmdKey}}</2> <4>K</4>を押して、コマンドバーを開きます。\",\n\t\"Privacy policy\": \"プライバシーポリシー\",\n\t\"Private repositories\": \"プライベートリポジトリ\",\n\t\"Private repository\": \"プライベートリポジトリ\",\n\t\"Problem details and System configuration\": \"問題の詳細とシステム構成\",\n\t\"Project settings\": \"プロジェクト設定\",\n\t\"Project title\": \"プロジェクト名\",\n\t\"Project\": \"プロジェクト\",\n\t\"Projects require at least one synced repository.\": \"プロジェクトには、少なくとも1つの同期リポジトリが必要です。\",\n\t\"Projects\": \"プロジェクト\",\n\t\"Prompt guide\": \"プロンプトガイド\",\n\t\"Prompt templates\": \"プロンプトテンプレート\",\n\t\"Prompts\": \"プロンプト\",\n\t\"Provide a short, concise title for your project\": \"プロジェクトに短い簡潔なタイトルを提供します\",\n\t\"Provide any steps necessary to reproduce the problem...\": \"問題を再現するために必要な手順を提供してください...\",\n\t\"Public repositories\": \"公開リポジトリ\",\n\t\"Public repository\": \"パブリックリポジトリ\",\n\t\"Public\": \"公共\",\n\t\"Query suggestions\": \"クエリの提案\",\n\t\"Queued...\": \"待ち...\",\n\t\"Re-check payment status\": \"支払いステータスを再確認します\",\n\t\"Re-sync\": \"再同期\",\n\t\"Reading \": \"読む\",\n\t\"Reading\": \"読む\",\n\t\"Recent conversations\": \"最近の会話\",\n\t\"Recent projects\": \"最近のプロジェクト\",\n\t\"Recently used\": \"最近使用された\",\n\t\"Recommended: The classic response type\": \"推奨：クラシックなレスポンスタイプ\",\n\t\"References\": \"参照\",\n\t\"Relaunch GitHub auth\": \"GitHub認証を再開する\",\n\t\"Release to open in split view\": \"スプリットビューで開くためにリリースします\",\n\t\"Remote removed \": \"リモートで削除されました\",\n\t\"Remove file from code studio context\": \"コードスタジオコンテキストからファイルを削除します\",\n\t\"Remove file\": \"ファイルを削除する\",\n\t\"Remove from project\": \"プロジェクトから取り外します\",\n\t\"Remove from studio\": \"スタジオから取り出します\",\n\t\"Remove page from code studio context\": \"コードスタジオコンテキストからページを削除します\",\n\t\"Remove\": \"削除\",\n\t\"Removed\": \"削除\",\n\t\"Rename code studio\": \"コードスタジオの名前を変更します\",\n\t\"Rename\": \"名前を変更します\",\n\t\"Report a bug\": \"バグを報告する\",\n\t\"Repositories\": \"リポジトリ\",\n\t\"Repository URL...\": \"リポジトリURL ...\",\n\t\"Repository indexed\": \"リポジトリインデックス\",\n\t\"Repository types\": \"リポジトリタイプ\",\n\t\"Restart the app\": \"アプリを再起動してください\",\n\t\"Restore\": \"復元する\",\n\t\"Result suggestions\": \"結果の提案\",\n\t\"Results\": \"結果\",\n\t\"Retry\": \"リトライ\",\n\t\"Save changes\": \"変更内容を保存\",\n\t\"Save context changes before answer generation\": \"回答生成の前にコンテキストの変更を保存します\",\n\t\"Save\": \"保存\",\n\t\"Search branch...\": \"ブランチを検索...\",\n\t\"Search branches...\": \"ブランチを検索...\",\n\t\"Search code in natural language\": \"自然言語でコードを検索する\",\n\t\"Search docs\": \"ドキュメントを検索します\",\n\t\"Search docs...\": \"ドキュメントを検索...\",\n\t\"Search file...\": \"ファイルを検索...\",\n\t\"Search files\": \"ファイル検索\",\n\t\"Search files...\": \"ファイル検索...\",\n\t\"Search for code using regex\": \"正規表現を使用してコードを検索する\",\n\t\"Search local repos...\": \"ローカルレポスを検索します...\",\n\t\"Search pages\": \"検索ページ\",\n\t\"Search private repos...\": \"プライベートレポを検索...\",\n\t\"Search projects or commands...\": \"プロジェクトまたはコマンドを検索...\",\n\t\"Search public repositories...\": \"パブリックリポジトリを検索...\",\n\t\"Search repos or Studio projects...\": \"リポジトリまたは Studio プロジェクトを検索...\",\n\t\"Search repository\": \"リポジトリを検索します\",\n\t\"Search studio conversations...\": \"スタジオの会話を検索...\",\n\t\"Search templates...\": \"テンプレートを検索...\",\n\t\"Search using RegExp\": \"正規表現を使用して検索します\",\n\t\"Search your files in this project\": \"このプロジェクトでファイルを検索します\",\n\t\"Search your repositories using RegExp\": \"regexpを使用してリポジトリを検索します\",\n\t\"Search\": \"検索\",\n\t\"Searching...\": \"検索中...\",\n\t\"Select a file or open a new tab to display it here.\": \"ファイルを選択するか、新しいタブを開いてここに表示します。\",\n\t\"Select a folder containing a git repository\": \"GITリポジトリを含むフォルダーを選択します\",\n\t\"Select all\": \"すべてを選択する\",\n\t\"Select branch\": \"ブランチを選択\",\n\t\"Select color theme:\": \"カラーテーマを選択する\",\n\t\"Select file\": \"ファイルを選ぶ\",\n\t\"Select folder\": \"フォルダの選択\",\n\t\"Select less code\": \"コード選択範囲を少なくしてください\",\n\t\"Select library\": \"ライブラリを選択します\",\n\t\"Select page\": \"[ページ]を選択します\",\n\t\"Select repository\": \"リポジトリを選択します\",\n\t\"Select section\": \"セクションを選択します\",\n\t\"Select sections\": \"セクションを選択します\",\n\t\"Select the interface colour scheme\": \"インターフェイスの配色を選択します\",\n\t\"Select the interface language\": \"インターフェイス言語を選択します\",\n\t\"Select\": \"選択する\",\n\t\"Selected lines # - #.\": \"選択した行{{start}} - {{end}}。\",\n\t\"Selected ranges will be used as context.\": \"選択した範囲はコンテキストとして使用されます。\",\n\t\"Settings\": \"設定\",\n\t\"Setup bloop\": \"bloopをセットアップする\",\n\t\"Show # more match_one\": \"さらに {{count}} 件を表示する\",\n\t\"Show # more match_other\": \"さらに {{count}} 件を表示する\",\n\t\"Show file\": \"ファイルを表示\",\n\t\"Show filters\": \"フィルタを表示する\",\n\t\"Show less\": \"表示を減らす\",\n\t\"Show link\": \"リンクを表示\",\n\t\"Show more\": \"もっと見る\",\n\t\"Show search steps\": \"検索手順を表示する\",\n\t\"Show\": \"表示\",\n\t\"Sign In\": \"サインイン\",\n\t\"Sign in with GitHub\": \"GitHubでサインインする\",\n\t\"Sign out\": \"サインアウト\",\n\t\"Skip (Not recommended)\": \"スキップ（非推奨）\",\n\t\"Skip\": \"スキップ\",\n\t\"Something went wrong\": \"何かがうまくいかなかった\",\n\t\"Start by selecting \": \"選択することから始めます\",\n\t\"Start by selecting a type repository you would like to index.\": \"インデックスを作成するタイプリポジトリを選択することから始めます。\",\n\t\"Start indexing\": \"インデックス作成を開始します\",\n\t\"Start typing...\": \"タイピングを開始します...\",\n\t\"Status\": \"ステータス\",\n\t\"Stop generating\": \"生成を停止する\",\n\t\"Stop indexing\": \"インデックス作成を停止します\",\n\t\"Streaming response...\": \"ストリーミング応答...\",\n\t\"Studio conversation require at least one context file.\": \"Studioの会話には、少なくとも1つのコンテキストファイルが必要です。\",\n\t\"Studio conversation\": \"スタジオの会話\",\n\t\"Studio conversations\": \"スタジオの会話\",\n\t\"Studio mode\": \"スタジオモード\",\n\t\"Studio project\": \"スタジオプロジェクト\",\n\t\"Studio requests\": \"スタジオリクエスト\",\n\t\"Studio\": \"スタジオ\",\n\t\"Submit bug report\": \"バグレポートを送信する\",\n\t\"Submit crash report\": \"クラッシュレポートを送信する\",\n\t\"Submit\": \"送信\",\n\t\"Subscription\": \"サブスクリプション\",\n\t\"Switch branch\": \"ブランチを切り替える\",\n\t\"Switch to\": \"切り替える\",\n\t\"Sync local git repos\": \"ローカルgitリポジトリを同期します\",\n\t\"Sync\": \"同期\",\n\t\"Synced\": \"同期済み\",\n\t\"Syncing\": \"同期\",\n\t\"System Preference\": \"システム優先\",\n\t\"System preferences\": \"システム環境設定\",\n\t\"System\": \"システム\",\n\t\"Take a quick look\": \"ちょっと見る\",\n\t\"Templates\": \"テンプレート\",\n\t\"Terms & conditions\": \"利用規約\",\n\t\"The amount of times you can generate responses in Studio conversations per day.\": \"スタジオでの会話で1日あたりの回答を生成できる回数。\",\n\t\"The diff has been applied locally.\": \"差分はローカルに適用されました。\",\n\t\"The folder you selected has multiple git repositories nested inside.\": \"選択したフォルダーには、内部にネストされている複数のgitリポジトリがあります。\",\n\t\"The folder you selected is not a git repository.\": \"選択したフォルダーはGitリポジトリではありません。\",\n\t\"The following changes can be applied to your repository. Make sure the generated diffs are valid before you apply the changes.\": \"以下の変更はあなたのリポジトリに適用できます。変更を適用する前に、生成された差分が有効であることを確認してください。\",\n\t\"The following changes represent the git diff for the remote repository. Please note that these changes cannot be applied directly to a remote repository. Use the \\\"Copy\\\" button to copy the changes and apply them locally.\": \"以下の変更は、リモートリポジトリのGit差分を表しています。これらの変更はリモートリポジトリに直接適用できないことに注意してください。「コピー」ボタンを使用して変更をコピーし、ローカルに適用してください。\",\n\t\"The line of code where identifier is defined\": \"識別子が定義されているコード行\",\n\t\"The line of code where the identifier is referenced\": \"識別子が参照されているコードの行\",\n\t\"The whole file will be used as context.\": \"ファイル全体がコンテキストとして使用されます。\",\n\t\"Theme\": \"テーマ\",\n\t\"This is not a public repository / We couldn't find this repository\": \"パブリックリポジトリではありません / リポジトリは見つかりませんでした\",\n\t\"This might be because the file is too big or it has one of bloop's excluded file types.\": \"これは、ファイルが大きすぎるか、bloopの除外されたファイルタイプの1つがあるためかもしれません。\",\n\t\"This project is empty\": \"このプロジェクトは空です\",\n\t\"Tip: Select code to create ranges for context use.\": \"ヒント：コードを選択して、コンテキスト使用の範囲を作成します。\",\n\t\"To update your app, please visit our releases page on GitHub and download the latest version manually. Thank you for using bloop.\": \"アプリを更新するには、GitHubのリリースページにアクセスして、最新バージョンを手動でダウンロードしてください。 bloopを使用していただきありがとうございます。\",\n\t\"Today\": \"今日\",\n\t\"Toggle black theme\": \"黒いテーマを切り替えます\",\n\t\"Toggle dark theme\": \"暗いテーマを切り替えます\",\n\t\"Toggle light theme\": \"ライトテーマを切り替えます\",\n\t\"Toggle regex search\": \"正規表現検索を切り替えます\",\n\t\"Toggle system theme\": \"システムテーマを切り替えます\",\n\t\"Toggle\": \"トグル\",\n\t\"Token limit exceeded\": \"トークンの制限が超えました\",\n\t\"Token limit exceeded. Reduce the number of context files or messages to enable the ability to generate.\": \"トークンの制限が超えました。 コンテキストファイルまたはメッセージの数を減らして、生成する機能を有効にします。\",\n\t\"Token limit reached, this answer may be incomplete. To generate a full answer, please reduce the number of tokens used and regenerate.\": \"到達したトークン制限は、この答えが不完全になる可能性があります。 完全な回答を生成するには、使用して再生するトークンの数を減らしてください。\",\n\t\"Try again\": \"もう一度やり直してください\",\n\t\"Unavailable\": \"利用できません\",\n\t\"Unlimited code studio requests\": \"無制限のコードスタジオリクエスト\",\n\t\"Unlimited usage and premium features are activated.\": \"無制限の使用機能とプレミアム機能がアクティブになります。\",\n\t\"Unlock the value of your existing code, using AI\": \"AIを使用して既存のコードの価値を引き出します\",\n\t\"Untitled project\": \"無題のプロジェクト\",\n\t\"Update Required\": \"アップデートしてください\",\n\t\"Upgrade now\": \"今すぐアップグレードする\",\n\t\"Upgrade plan\": \"プランをアップグレードする\",\n\t\"Upgrade\": \"アップグレード\",\n\t\"Upvote\": \"賛成票\",\n\t\"Usage exceeded\": \"使用量を超えました\",\n\t\"Usage resets in\": \"使用法がリセットされます\",\n\t\"Usage status\": \"使用状況\",\n\t\"Use GitHub to sign in to your account\": \"GitHubを使用してアカウントにサインインする\",\n\t\"Use file\": \"ファイルを使用します\",\n\t\"Use light theme\": \"軽いテーマを使用します\",\n\t\"Use template\": \"テンプレートを使用します\",\n\t\"Use templates\": \"テンプレートを使用します\",\n\t\"Use\": \"使用\",\n\t\"User\": \"ユーザー\",\n\t\"Verifying access...\": \"アクセスの確認...\",\n\t\"Verifying link...\": \"リンクの検証...\",\n\t\"View all results\": \"すべての結果を表示する\",\n\t\"View all\": \"すべて見る\",\n\t\"View bloop app documentation on our website\": \"WebサイトでBloopアプリのドキュメントを表示します\",\n\t\"View history\": \"履歴を表示します\",\n\t\"View in {{viewer}}\": \"{{viewer}}で表示\",\n\t\"View\": \"意見\",\n\t\"Visit the downloads page\": \"ダウンロードページにアクセスしてください\",\n\t\"Waiting for authentication...\": \"認証完了を待っています...\",\n\t\"Watch\": \"時計\",\n\t\"We can’t generate a response because some files have a missing source in your Context files.\": \"一部のファイルにはコンテキストファイルにソースが欠落しているため、応答を生成できません。\",\n\t\"We couldn't answer your question. You can try asking again in a few moments, or rephrasing your question.\": \"ご質問に回答できませんでした。しばらくしてからもう一度質問するか、質問を変えてみてください。\",\n\t\"We couldn't find any docs at that link. Try again or make sure the link is correct!\": \"そのリンクにドキュメントが見つかりませんでした。 もう一度やり直すか、リンクが正しいことを確認してください！\",\n\t\"We want to make this the best experience for you. If you encountered a bug, please submit this bug report to us. Our team will investigate as soon as possible.\": \"我々は、皆様にとって最高の体験となるよう努力しています。バグに遭遇した場合は、バグレポートをお送りください。我々のチームはできるだけ早く調査します。\",\n\t\"We weren't able to identify any references at the moment\": \"現在、参照を識別することができませんでした\",\n\t\"We've made some exciting enhancements to bloop! To continue enjoying the full functionality, including the natural language search feature, please update your app to the latest version.\": \"私たちは、bloopにいくつかのエキサイティングな機能強化を行いました！ 自然言語検索機能を含む完全な機能を継続するには、アプリを最新バージョンに更新してください。\",\n\t\"We've redirected you to Stripe to complete your transaction. <2>Launch manually</2> if it didn't work.\": \"トランザクションを完了するために、ストライプにリダイレクトしました。 <2>手動で起動</2>機能しなかった場合。\",\n\t\"Welcome to bloop\": \"bloopへようこそ\",\n\t\"We’ve updated our auth service to make bloop more secure, please reauthorise your client with GitHub\": \"bloopをより安全にするために認証サービスを更新しました。GitHubでクライアントを再承認してください\",\n\t\"Whole file\": \"ファイル全体\",\n\t\"Whole page\": \"全てのページ\",\n\t\"Write a message, @ to mention files, folders or docs...\": \"メッセージを書く @ファイル、フォルダー、またはドキュメントに言及するために...\",\n\t\"Write studio prompts faster with pre-written templates\": \"事前に書かれたテンプレートを使用すると、スタジオのプロンプトをより速く書き込みます\",\n\t\"Yesterday\": \"昨日\",\n\t\"You can add 3 types of repositories, private, public and local.\": \"3種類のリポジトリ、プライベート、パブリック、ローカルを追加できます。\",\n\t\"You\": \"あなた\",\n\t\"You've run out of free usage for today, please wait for your quota to reset or upgrade for unlimited usage\": \"今日は無料の使用法がなくなっています。クォータがリセットまたはアップグレードされるのを待ってください。\",\n\t\"You've upgraded your account!\": \"アカウントをアップグレードしました！\",\n\t\"Your quota resets every 24 hours, upgrade for unlimited uses\": \"あなたのクオータは24時間ごとにリセットされます、無制限のリクエストにアップグレードしてください\",\n\t\"Your subscription has expired. Please update your payment details to avoid being unsubscribed.\": \"あなたのサブスクリプションが期限切れになりました。 登録解除されないように、支払いの詳細を更新してください。\",\n\t\"and \": \"と\",\n\t\"avatar\": \"アバター\",\n\t\"billed monthly\": \"毎月請求されます\",\n\t\"bloop automatically excludes certain files from indexing. This file might be too big or it might have an excluded file type.\": \"Bloopは、インデックスから特定のファイルを自動的に除外します。 このファイルが大きすぎるか、除外されたファイルタイプがある場合があります。\",\n\t\"bloop automatically excludes certain files from the indexing. This file might be too big or it might have an excluded file type.\": \"Bloopは、インデックスから特定のファイルを自動的に除外します。 このファイルが大きすぎるか、除外されたファイルタイプがある場合があります。\",\n\t\"bloop crashed unexpectedly\": \"bloopが予期せずクラッシュしました\",\n\t\"cancelled\": \"キャンセル\",\n\t\"cancelling\": \"キャンセル中\",\n\t\"chats in bloop\": \"bloopでチャット\",\n\t\"definition\": \"定義\",\n\t\"done\": \"終わり\",\n\t\"indexing\": \"インデックス中です\",\n\t\"key\": \"キー\",\n\t\"or \": \"または\",\n\t\"or go to the following link\": \"または、次のリンクに移動します\",\n\t\"or visit: \": \"または訪問：\",\n\t\"reference\": \"参照\",\n\t\"syncing\": \"同期しています\",\n\t\"to seamlessly explore code across all branches in your GitHub repositories, maximizing your code discovery capabilities.\": \"GitHubリポジトリ内の全てのブランチでコードをシームレスに探索できるようにし、コード発見能力を最大化してください。\",\n\t\"uses left_one\": \"回数\",\n\t\"uses left_other\": \"回数\",\n\t\"{{count}} context files used\": \"{{count}}コンテキストファイルが使用されます\",\n\t\"{{count}} context files used_one\": \"{{count}}コンテキストファイルが使用されます\",\n\t\"{{count}} context files used_other\": \"{{count}}コンテキストファイルが使用されます\",\n\t\"{{repoName}} is currently indexing as soon as it is finished it will be added to your project.\": \"{{repoName}}は現在、完了するとすぐにインデックスを作成しています。プロジェクトに追加されます。\",\n\t\"Click on an identifier and jump to its references and definition in a heart beat.\": \"識別子をクリックして、心拍で参照と定義にジャンプします。\",\n\t\"Hide search steps\": \"検索手順を非表示にします\",\n\t\"Close currently focused tab\": \"現在焦点を合わせたタブを閉じます\",\n\t\"Add any library documentation\": \"ライブラリのドキュメントを追加します\",\n\t\"Add documentation\": \"ドキュメントを追加します\",\n\t\"Manage your general project settings\": \"一般的なプロジェクトの設定を管理します\",\n\t\"Write your prompt...\": \"あなたのプロンプトを書く...\",\n\t\"Usage resets at\": \"使用法がリセットされます\",\n\t\"Upgrade to Personal plan\": \"個人計画にアップグレードします\",\n\t\"Restore session\": \"セッションを復元します\",\n\t\"In this project\": \"このプロジェクトで\",\n\t\"<0>{{repoName}}</0> has finished indexing and can be added to your projects. Click the button to below to add it to the current project.\": \"<0>{{repoName}}</0>はインデックス作成が終了し、プロジェクトに追加できます。 以下のボタンをクリックして、現在のプロジェクトに追加します。\",\n\t\"Start by selecting again and pressing Enter (↵) on your keyboard.\": \"もう一度選択して、キーボードのEnter（‡）を押すことから始めます。\",\n\t\"{{repoName}} is currently indexing as soon as it is finished you will be able to add it to your project.\": \"{{repoName}}は現在、終了したらすぐにインデックスを作成しています。プロジェクトに追加できるようになります。\",\n\t\"Select the input type to use in conversations\": \"会話で使用する入力タイプを選択します\",\n\t\"Fallback: Use if experiencing problems with the default one\": \"フォールバック：デフォルトの問題で問題が発生している場合に使用します\",\n\t\"Simplified\": \"簡素化\",\n\t\"Default\": \"デフォルト\",\n\t\"Recommended: The classic input\": \"推奨：クラシック入力\",\n\t\"Conversation input\": \"会話の入力\",\n\t\"Add multiple files\": \"複数のファイルを追加します\",\n\t\"Explain lines {{lineStart}} - {{lineEnd}} in {{filePath}}\": \"{{filePath}} の行 {{lineStart}} - {{lineEnd}} について説明します\",\n\t\"Add to studio\": \"スタジオに追加します\"\n}"
  },
  {
    "path": "client/src/locales/zh-CN.json",
    "content": "{\n\t\"# match\": \"找到 {{count}} 个匹配\",\n\t\"# match_one\": \"找到 {{count}} 个匹配\",\n\t\"# match_other\": \"找到 {{count}} 个匹配\",\n\t\"# of #\": \"{{total}} 中的 {{count}}\",\n\t\"# of #_one\": \"{{total}} 中的 {{count}}\",\n\t\"# of #_other\": \"{{total}} 中的 {{count}}\",\n\t\"# ranges\": \"{{count}}个范围\",\n\t\"# ranges_one\": \"{{count}}个范围\",\n\t\"# ranges_other\": \"{{count}}个范围\",\n\t\"# sections\": \"{{count}}节\",\n\t\"# sections_one\": \"{{count}}节\",\n\t\"# sections_other\": \"{{count}}节\",\n\t\"# selected section\": \"{{count}} 个选定部分\",\n\t\"# selected section_one\": \"{{count}} 个选定部分\",\n\t\"# selected section_other\": \"{{count}} 个选定部分\",\n\t\"# tokens_one\": \"代币\",\n\t\"# tokens_other\": \"代币\",\n\t\"$20 / billed monthly\": \"$20 / 每月收费\",\n\t\"<0>#</0> of # tokens\": \"{{total}}个代币中的 <0>{{count}}</0> 个\",\n\t\"<0>#</0> of # tokens_one\": \"{{total}}个代币中的 <0>{{count}}</0> 个\",\n\t\"<0>#</0> of # tokens_other\": \"{{total}}个代币中的 <0>{{count}}</0> 个\",\n\t\"<0></0><1></1> to navigate.\": \"<0></0><1></1>导航。\",\n\t\"<0>Create a new conversation with bloop by hitting <2></2> on your keyboard or by pressing the <4></4> in the header bar.</0>\": \"<0>通过在键盘上按下<2></2>或按下标题栏中的<4></4>来创建一个新的对话。</0>\",\n\t\"<0>To begin, open a file from the sidebar on the left. Once you have a file open, you can ask bloop to quickly explain it by hitting <2></2> on your keyboard or by selecting \\\"Explain file\\\" from the <7></7> popup menu.</0>\": \"<0>首先，从左侧的侧边栏上打开文件。打开文件后，您可以要求 bloop 快速解释它：在键盘上按下<2></2>或从<7></7>弹出菜单中选择“说明文件”。</0>\",\n\t\"<0>repoName</0> has finished indexing and was added to the context of the current project. You can also use it in your other projects now.\": \"<0>{{repoName}}</0>已完成索引，并已添加到当前项目的上下文中。您现在也可以在其他项目中使用它。\",\n\t\"Account settings\": \"帐号设定\",\n\t\"Actions\": \"操作\",\n\t\"Add a repository from your local machine\": \"从您的本地机器添加一个仓库\",\n\t\"Add at least one context file before submitting your first request.\": \"在提交第一个请求之前，至少添加一个上下文文件。\",\n\t\"Add doc to studio\": \"将文档添加到工作室\",\n\t\"Add docs\": \"添加文档\",\n\t\"Add file to code studio context\": \"将文件添加到代码工作室上下文\",\n\t\"Add file to studio\": \"将文件添加到工作室\",\n\t\"Add file\": \"添加文件\",\n\t\"Add files to studio\": \"将文件添加到工作室\",\n\t\"Add files\": \"添加文件\",\n\t\"Add local repository\": \"添加本地存储库\",\n\t\"Add new repository\": \"添加新存储库\",\n\t\"Add private repository\": \"添加私人存储库\",\n\t\"Add public repository\": \"添加公共存储库\",\n\t\"Add repository\": \"添加存储库\",\n\t\"Add to project\": \"添加到项目\",\n\t\"Add to studio\": \"添加到工作室\",\n\t\"Add\": \"添加\",\n\t\"Adding repository\": \"添加存储库\",\n\t\"All payments, invoices and billing information are managed in Stripe.\": \"所有付款，发票和计费信息均以条纹管理。\",\n\t\"All sections\": \"所有部分\",\n\t\"All templates\": \"所有模板\",\n\t\"All\": \"全部\",\n\t\"Answer speed\": \"回答速度\",\n\t\"Application theme\": \"使用主题\",\n\t\"Apply changes\": \"应用更改\",\n\t\"Ask questions about your codebases in natural language, just like you’d speak to ChatGPT. Get started by syncing a repo, then open the repo and start chatting.\": \"用自然语言向您的代码库提问，就像您和 ChatGPT 对话一样。首先同步一个仓库，然后打开仓库开始聊天。\",\n\t\"Ask your first question\": \"问你的第一个问题\",\n\t\"Assistant\": \"助手\",\n\t\"Back to current\": \"回到当前\",\n\t\"Back\": \"返回\",\n\t\"Bad response\": \"回答不好\",\n\t\"Bad\": \"差\",\n\t\"Below are a few suggestions you can ask me to get started:\": \"以下是您可以要求我开始的一些建议：\",\n\t\"Black\": \"黑色的\",\n\t\"Branches\": \"分支\",\n\t\"By continuing you accept our\": \"继续即表示你接受我们的\",\n\t\"By submitting this crash report you agree to send it to bloop for investigation.\": \"提交此崩溃报告即表示您同意将其发送至 bloop 以进行调查。\",\n\t\"Cancel diff generation\": \"取消生成差异\",\n\t\"Cancel\": \"取消\",\n\t\"Cancelled\": \"已取消\",\n\t\"Cancelling...\": \"正在取消...\",\n\t\"Change application colour theme\": \"更改应用程序颜色主题\",\n\t\"Chat conversations\": \"聊天对话\",\n\t\"Clear conversation\": \"清除对话\",\n\t\"Clear input\": \"清除输入\",\n\t\"Clear range\": \"清除范围\",\n\t\"Clear ranges\": \"清除范围\",\n\t\"Clear section\": \"清除部分\",\n\t\"Clear sections\": \"清除部分\",\n\t\"Clear selection\": \"清除选项\",\n\t\"Click to copy\": \"单击复制\",\n\t\"Cloning\": \"克隆\",\n\t\"Cloning...\": \"克隆中...\",\n\t\"Close all open tabs\": \"关闭所有打开选项卡\",\n\t\"Close all tabs\": \"关闭所有选项卡\",\n\t\"Close current tab\": \"关闭当前选项卡\",\n\t\"Close search\": \"关闭搜索\",\n\t\"Close tab\": \"关闭选项卡\",\n\t\"Close\": \"关闭\",\n\t\"Code search\": \"代码搜索\",\n\t\"Code studio helps you write scripts, create unit tests, debug issues or generate anything else you can think of using AI! Sync a repo, then create a code studio project.\": \"代码工作室可以帮助您使用 AI 编写脚本、创建单元测试、调试问题或生成您能想到的任何其他内容！同步一个仓库，然后创建一个代码工作室项目。\",\n\t\"Collapse\": \"折叠\",\n\t\"Commands\": \"命令\",\n\t\"Complete your transaction in Stripe...\": \"完成您的交易...\",\n\t\"Confirm\": \"确认\",\n\t\"Connect account\": \"连接账户\",\n\t\"Context files\": \"上下文文件\",\n\t\"Continue\": \"继续\",\n\t\"Copied\": \"已复制\",\n\t\"Copy link\": \"复制链接\",\n\t\"Copy message\": \"复制消息\",\n\t\"Copy\": \"复制\",\n\t\"Create line ranges\": \"创建线路范围\",\n\t\"Create new project\": \"创建新项目\",\n\t\"Create new\": \"新会话\",\n\t\"Create project\": \"创建项目\",\n\t\"Create ranges\": \"创建范围\",\n\t\"Create template\": \"创建模板\",\n\t\"Current\": \"当前的\",\n\t\"Currently active\": \"当前活动\",\n\t\"Dark\": \"黑暗的\",\n\t\"Default project\": \"默认项目\",\n\t\"Delete all conversations\": \"删除所有对话\",\n\t\"Delete conversation\": \"删除对话\",\n\t\"Delete project\": \"删除项目\",\n\t\"Delete template\": \"删除模板\",\n\t\"Delete\": \"删除\",\n\t\"Desktop app\": \"桌面应用\",\n\t\"Diff generation failed\": \"生成差异失败\",\n\t\"Directories\": \"目录\",\n\t\"Disconnect\": \"断开连接\",\n\t\"Discord\": \"Discord\",\n\t\"Dismiss tutorial\": \"结束教程\",\n\t\"Display\": \"展示\",\n\t\"Docs\": \"文档\",\n\t\"Documentation in studio\": \"Studio中的文档\",\n\t\"Documentation\": \"文档\",\n\t\"Done\": \"已完成\",\n\t\"Downgrade\": \"降级\",\n\t\"Downvote\": \"低位\",\n\t\"Edit ranges\": \"编辑范围\",\n\t\"Edit sections\": \"编辑章节\",\n\t\"Edit selected lines\": \"编辑选定的行\",\n\t\"Edit selected sections\": \"编辑选定的部分\",\n\t\"Edit template\": \"编辑模板\",\n\t\"Edit\": \"编辑\",\n\t\"Editing previously submitted questions will discard all answers and questions following it\": \"编辑先前提交的问题将丢弃后续所有答案和问题\",\n\t\"Email address\": \"电子邮件地址\",\n\t\"Email is not valid\": \"电子邮件无效\",\n\t\"Email is required\": \"需要电子邮件\",\n\t\"Email\": \"电子邮件\",\n\t\"Error\": \"错误\",\n\t\"Expand\": \"展开\",\n\t\"Experimental: Faster but less accurate\": \"实验性：更快但准确性较低\",\n\t\"Explain a file\": \"解释文件\",\n\t\"Explain code\": \"解释代码\",\n\t\"Explain current file\": \"说明当前文件\",\n\t\"Explain file\": \"说明文件\",\n\t\"Explain the purpose of the file {{filePath}}, from lines {{lineStart}} - {{lineEnd}}\": \"解释文件 {{filePath}} 的用途，从第 {{lineStart}} - {{lineEnd}} 行开始\",\n\t\"Explain\": \"解释\",\n\t\"Failed to apply the diff\": \"应用差异失败\",\n\t\"Failed to get a response from OpenAI. Try again in a few moments.\": \"从 OpenAI 获取回应失败。请稍后再试。\",\n\t\"Fast\": \"快速地\",\n\t\"File not indexed\": \"文件未索引\",\n\t\"File search\": \"文件搜索\",\n\t\"File\": \"文件\",\n\t\"Files\": \"文件\",\n\t\"Files, paths or folders...\": \"文件，路径或文件夹...\",\n\t\"Filter repositories by\": \"过滤器存储库\",\n\t\"Filters\": \"筛选\",\n\t\"First name is required\": \"需要名字\",\n\t\"First name\": \"名\",\n\t\"Fold everything\": \"折叠全部\",\n\t\"Follow us on Twitter\": \"在推特上关注我们\",\n\t\"Force index\": \"强制索引\",\n\t\"Free\": \"免费\",\n\t\"General\": \"常规\",\n\t\"Generate code using AI\": \"使用 AI 生成代码\",\n\t\"Generate\": \"产生\",\n\t\"Generated diffs to be applied\": \"要应用的生成差异\",\n\t\"Generating answer...\": \"生成答案...\",\n\t\"Generating diff...\": \"正在生成差异...\",\n\t\"Generating response...\": \"正在生成回答...\",\n\t\"GitHub Branches\": \"GitHub 分支\",\n\t\"GitHub\": \"GitHub\",\n\t\"Give your template a title\": \"给您的模板标题\",\n\t\"Go back\": \"返回\",\n\t\"Go to current state\": \"进入当前状态\",\n\t\"Good\": \"好\",\n\t\"Got it\": \"了解了\",\n\t\"Hi, I am bloop! In <2><0></0>Chat mode</2> I can answer any questions related to any of your repositories.\": \"嗨，我是 bloop！ 在<2><0></0>聊天模式</2>中，我可以回答与您的任何存储库有关的任何问题。\",\n\t\"Hi, I am bloop! In <2><0></0>Studio mode</2> you can choose files from your codebase, write a prompt and generate patches, scripts and tests.\": \"嗨，我是 bloop！ 在<2><0></0>工作室模式</2>中，您可以从代码库中选择文件，编写提示并生成补丁程序，脚本和测试。\",\n\t\"Hide\": \"隐藏\",\n\t\"History\": \"历史\",\n\t\"How fast or precise bloop's answers will be.\": \"bloop 的答案将更快或是更精确。\",\n\t\"In this file\": \"在此文件中\",\n\t\"Index multiple branches\": \"索引多个分支\",\n\t\"Index repository\": \"索引存储库\",\n\t\"Index\": \"指数\",\n\t\"Indexed documentation web pages\": \"索引文档网页\",\n\t\"Indexed local repositories\": \"索引本地存储库\",\n\t\"Indexed\": \"索引\",\n\t\"Indexing repositories\": \"索引存储库\",\n\t\"Indexing repository\": \"索引存储库\",\n\t\"Indexing\": \"索引\",\n\t\"Indexing...\": \"索引中...\",\n\t\"Individual\": \"个人\",\n\t\"Invert\": \"倒置\",\n\t\"Join Discord\": \"加入Discord\",\n\t\"Language\": \"语言\",\n\t\"Languages\": \"语言\",\n\t\"Last name is required\": \"需要姓氏\",\n\t\"Last name\": \"姓\",\n\t\"Last updated \": \"最后更新 \",\n\t\"Latest model updates\": \"最新的模型更新\",\n\t\"Launch manually\": \"手动启动\",\n\t\"Let’s get you started with bloop!\": \"让我们开始使用 bloop！\",\n\t\"Light\": \"光\",\n\t\"Lines # - #\": \"行{{start}} - {{end}}\",\n\t\"Loading...\": \"加载...\",\n\t\"Local repositories\": \"本地存储库\",\n\t\"Local repository\": \"本地仓库\",\n\t\"Local\": \"当地的\",\n\t\"Manage context\": \"管理上下文\",\n\t\"Manage docs\": \"管理文档\",\n\t\"Manage project\": \"管理项目\",\n\t\"Manage repositories\": \"管理存储库\",\n\t\"Manage subscription\": \"管理订阅\",\n\t\"Manage templates\": \"管理模板\",\n\t\"Manage your general account settings\": \"管理您的一般帐户设置\",\n\t\"Manage your preferences\": \"管理您的偏好\",\n\t\"Manage your studio settings.\": \"管理您的工作室设置。\",\n\t\"Manage your subscription plan\": \"管理您的订阅计划\",\n\t\"Manage\": \"管理\",\n\t\"Missing source\": \"缺少源\",\n\t\"More actions\": \"更多操作\",\n\t\"Move\": \"移动\",\n\t\"My templates\": \"我的模板\",\n\t\"Name\": \"姓名\",\n\t\"Natural language search\": \"自然语言搜索\",\n\t\"Navigate\": \"导航\",\n\t\"New conversation\": \"新对话\",\n\t\"New project\": \"新项目\",\n\t\"New studio conversation\": \"新工作室对话\",\n\t\"New tab\": \"新标签\",\n\t\"New\": \"新的\",\n\t\"Next page\": \"下一页\",\n\t\"Next\": \"下一个\",\n\t\"No change in payment status identified.\": \"未确定付款状态的变化。\",\n\t\"No commands found...\": \"找不到命令...\",\n\t\"No file selected\": \"未选择文件\",\n\t\"No files found...\": \"找不到文件...\",\n\t\"No references or definitions found\": \"找不到任何引用或定义\",\n\t\"No repositories found...\": \"找不到存储库...\",\n\t\"No results\": \"无结果\",\n\t\"No uses left. Uses reset at\": \"没有剩下的配额，将重置于\",\n\t\"Normal\": \"普通的\",\n\t\"Not a git repository\": \"不是 git 存储库\",\n\t\"Not synced\": \"未同步\",\n\t\"Open Command Bar\": \"打开命令栏\",\n\t\"Open account settings\": \"打开帐户设置\",\n\t\"Open in GitHub\": \"在 GitHub 打开\",\n\t\"Open in split view\": \"打开拆分视图\",\n\t\"Open in {{viewer}}\": \"在{{viewer}}中打开\",\n\t\"Open subscription settings\": \"打开订阅设置\",\n\t\"Open\": \"打开\",\n\t\"Paste a link to any documentation web page\": \"将链接粘贴到任何文档网页\",\n\t\"Paste a link to any public repository hosted on GitHub\": \"将链接粘贴到 GitHub 上托管的任何公共存储库\",\n\t\"Permanently delete <2>{{projectName}}</2> and remove all the data associated to it. Repositories will remain accessible in your GitHub account.\": \"永久删除<2>{{projectName}}</2>并删除与之关联的所有数据。存储库将在您的 GitHub 帐户中保持访问。\",\n\t\"Personal\": \"个人的\",\n\t\"Plans\": \"计划\",\n\t\"Please log into your GitHub account to complete setup, this helps us combat misuse.\": \"请登录您的GitHub账户以完成设置\",\n\t\"Precise code navigation\": \"精确的代码导航\",\n\t\"Preferences\": \"偏好设置\",\n\t\"Press <2>cmdKey</2><4>K</4> on your keyboard to open the Command bar and add a repository.\": \"按<2>{{cmdKey}}</2><4>K</4>在键盘上打开命令栏并添加存储库。\",\n\t\"Press <2>cmdKey</2><4>K</4> on your keyboard to open the Command bar.\": \"按<2>{{cmdKey}}</2><4>K</4>在键盘上打开命令栏。\",\n\t\"Privacy policy\": \"隐私政策\",\n\t\"Private repositories\": \"私人存储库\",\n\t\"Private repository\": \"私有仓库\",\n\t\"Private\": \"私人的\",\n\t\"Problem details and System configuration\": \"问题详情和系统配置\",\n\t\"Project settings\": \"项目设置\",\n\t\"Project title\": \"项目名称\",\n\t\"Project\": \"项目\",\n\t\"Projects require at least one synced repository.\": \"项目至少需要一个同步存储库。\",\n\t\"Projects\": \"项目\",\n\t\"Prompt guide\": \"提示指南\",\n\t\"Prompt templates\": \"提示模板\",\n\t\"Prompts\": \"提示\",\n\t\"Provide a short, concise title for your project\": \"为您的项目提供简短，简洁的标题\",\n\t\"Provide any steps necessary to reproduce the problem...\": \"提供任何必要的步骤以复现问题...\",\n\t\"Public repositories\": \"公共存储库\",\n\t\"Public repository\": \"公开仓库\",\n\t\"Query suggestions\": \"查询建议\",\n\t\"Queued...\": \"已进入队列...\",\n\t\"Re-check payment status\": \"重新检查付款状态\",\n\t\"Re-sync\": \"重新同步\",\n\t\"Reading \": \"正在阅读 \",\n\t\"Reading\": \"阅读\",\n\t\"Recent conversations\": \"最近的对话\",\n\t\"Recent projects\": \"最近的项目\",\n\t\"Recently used\": \"最近使用的\",\n\t\"Recommended: The classic response type\": \"推荐：经典的回应类型\",\n\t\"References\": \"引用\",\n\t\"Relaunch GitHub auth\": \"重新启动 GitHub 授权\",\n\t\"Release to open in split view\": \"松开鼠标以在分裂视图中打开\",\n\t\"Remote removed \": \"已移除远程仓库\",\n\t\"Remove file from code studio context\": \"从代码工作室上下文中删除文件\",\n\t\"Remove file\": \"删除文件\",\n\t\"Remove from project\": \"从项目中删除\",\n\t\"Remove page from code studio context\": \"从代码工作室上下文中删除页面\",\n\t\"Remove\": \"移除\",\n\t\"Removed\": \"已移除\",\n\t\"Rename code studio\": \"重命名代码工作室\",\n\t\"Rename\": \"改名\",\n\t\"Report a bug\": \"报告一个错误\",\n\t\"Repositories\": \"存储库\",\n\t\"Repository URL...\": \"存储库URL ...\",\n\t\"Repository indexed\": \"存储库索引\",\n\t\"Repository types\": \"存储库类型\",\n\t\"Restart the app\": \"重新启动应用\",\n\t\"Restore session\": \"还原会话\",\n\t\"Restore\": \"恢复\",\n\t\"Result suggestions\": \"结果建议\",\n\t\"Results\": \"结果\",\n\t\"Retry\": \"重试\",\n\t\"Save changes\": \"保存更改\",\n\t\"Save context changes before answer generation\": \"在回答生成之前保存上下文更改\",\n\t\"Save\": \"保存\",\n\t\"Search branch...\": \"搜索分支...\",\n\t\"Search branches...\": \"搜索分支...\",\n\t\"Search code in natural language\": \"用自然语言搜索代码\",\n\t\"Search docs\": \"搜索文档\",\n\t\"Search docs...\": \"搜索文档...\",\n\t\"Search file...\": \"搜索文件...\",\n\t\"Search files\": \"搜索文件\",\n\t\"Search files...\": \"搜索文件...\",\n\t\"Search for code using regex\": \"使用正则表达式搜索代码\",\n\t\"Search local repos...\": \"搜索本地存储库...\",\n\t\"Search pages\": \"搜索页\",\n\t\"Search private repos...\": \"搜索私人存储库...\",\n\t\"Search projects or commands...\": \"搜索项目或命令...\",\n\t\"Search public repositories...\": \"搜索公共存储库...\",\n\t\"Search repos or Studio projects...\": \"搜索存储库或 Studio 项目...\",\n\t\"Search repository\": \"搜索存储库\",\n\t\"Search studio conversations...\": \"搜索工作室对话...\",\n\t\"Search templates...\": \"搜索模板...\",\n\t\"Search using RegExp\": \"使用RegExp搜索\",\n\t\"Search your files in this project\": \"在此项目中搜索您的文件\",\n\t\"Search your repositories using RegExp\": \"使用REGEXP搜索您的存储库\",\n\t\"Search\": \"搜索\",\n\t\"Searching...\": \"搜索中...\",\n\t\"Select a file or open a new tab to display it here.\": \"选择一个文件或打开新标签以在此处显示。\",\n\t\"Select a folder containing a git repository\": \"选择一个包含 git 存储库的文件夹\",\n\t\"Select all\": \"全选\",\n\t\"Select branch\": \"选择分行\",\n\t\"Select color theme:\": \"选择颜色主题:\",\n\t\"Select file\": \"选择文件\",\n\t\"Select folder\": \"选择文件夹\",\n\t\"Select less code\": \"选择更少的代码\",\n\t\"Select library\": \"选择库\",\n\t\"Select page\": \"选择页面\",\n\t\"Select repository\": \"选择存储库\",\n\t\"Select section\": \"选择部分\",\n\t\"Select sections\": \"选择部分\",\n\t\"Select the interface colour scheme\": \"选套用的配色方案\",\n\t\"Select the interface language\": \"选择套用的语言\",\n\t\"Select\": \"选择\",\n\t\"Selected lines # - #.\": \"选定的行{{start}} - {{end}}。\",\n\t\"Selected ranges will be used as context.\": \"选定的范围将用作上下文。\",\n\t\"Settings\": \"设置\",\n\t\"Setup bloop\": \"设置 bloop\",\n\t\"Show # more match\": \"显示 {{count}} 更多匹配\",\n\t\"Show # more match_one\": \"显示 {{count}} 更多匹配\",\n\t\"Show # more match_other\": \"显示 {{count}} 更多匹配\",\n\t\"Show file\": \"显示文件\",\n\t\"Show filters\": \"显示筛选\",\n\t\"Show less\": \"显示较少\",\n\t\"Show link\": \"显示链接\",\n\t\"Show more\": \"显示更多\",\n\t\"Show search steps\": \"显示搜索步骤\",\n\t\"Show\": \"显示\",\n\t\"Sign In\": \"登录\",\n\t\"Sign in with GitHub\": \"使用 GitHub 登录\",\n\t\"Sign out\": \"退出登录\",\n\t\"Skip (Not recommended)\": \"跳过（不建议）\",\n\t\"Skip\": \"跳过\",\n\t\"Something went wrong\": \"出错了\",\n\t\"Start by selecting \": \"通过选择开始\",\n\t\"Start by selecting a type repository you would like to index.\": \"通过选择要索引的类型存储库开始。\",\n\t\"Start indexing\": \"开始索引\",\n\t\"Start typing...\": \"开始打字...\",\n\t\"Status\": \"状态\",\n\t\"Stop generating\": \"停止生成\",\n\t\"Stop indexing\": \"停止索引\",\n\t\"Streaming response...\": \"流响应...\",\n\t\"Studio Projects\": \"工作室项目\",\n\t\"Studio conversation require at least one context file.\": \"工作室对话至少需要一个上下文文件。\",\n\t\"Studio conversation\": \"工作室对话\",\n\t\"Studio conversations\": \"工作室对话\",\n\t\"Studio mode\": \"工作室模式\",\n\t\"Studio project\": \"工作室项目\",\n\t\"Studio requests\": \"工作室请求\",\n\t\"Studio\": \"工作室\",\n\t\"Submit bug report\": \"提交错误报告\",\n\t\"Submit crash report\": \"提交崩溃报告\",\n\t\"Submit\": \"提交\",\n\t\"Subscription\": \"订阅\",\n\t\"Switch branch\": \"切换分支\",\n\t\"Switch to\": \"切换到\",\n\t\"Sync local git repos\": \"同步本地 git 存储库\",\n\t\"Sync\": \"同步\",\n\t\"Synced\": \"已同步\",\n\t\"Syncing\": \"同步\",\n\t\"System Preference\": \"系统偏好设置\",\n\t\"System preferences\": \"系统首选项\",\n\t\"Take a quick look\": \"快速查看\",\n\t\"Template title\": \"模板标题\",\n\t\"Templates\": \"模板\",\n\t\"Terms & conditions\": \"条款和条件\",\n\t\"The amount of times you can generate responses in Studio conversations per day.\": \"您每天可以在工作室对话中产生响应的次数。\",\n\t\"The diff has been applied locally.\": \"差异已在本地应用。\",\n\t\"The folder you selected has multiple git repositories nested inside.\": \"您选择的文件夹具有嵌套在其中的多个 git 存储库。\",\n\t\"The folder you selected is not a git repository.\": \"您选择的文件夹不是 git 存储库。\",\n\t\"The following changes can be applied to your repository. Make sure the generated diffs are valid before you apply the changes.\": \"以下的更改可以应用到您的仓库。在应用更改之前，请确保生成的差异是有效的。\",\n\t\"The following changes represent the git diff for the remote repository. Please note that these changes cannot be applied directly to a remote repository. Use the \\\"Copy\\\" button to copy the changes and apply them locally.\": \"以下的更改代表远程仓库的 git 差异。请注意，这些更改不能直接应用到远程仓库。使用“复制”按钮复制更改并在本地应用。\",\n\t\"The line of code where identifier is defined\": \"定义标识符的代码行\",\n\t\"The line of code where the identifier is referenced\": \"引用标识符的代码行\",\n\t\"The whole file will be used as context.\": \"整个文件将用作上下文。\",\n\t\"Theme\": \"主题\",\n\t\"This is not a public repository / We couldn't find this repository\": \"这不是公开仓库/我们找不到这个仓库\",\n\t\"This might be because the file is too big or it has one of bloop's excluded file types.\": \"这可能是因为文件太大，或属于 bloop 排除的文件类型\",\n\t\"This project is empty\": \"这个项目是空的\",\n\t\"Tip: Select code to create ranges for context use.\": \"提示：选择代码以创建范围以供上下文使用。\",\n\t\"To update your app, please visit our releases page on GitHub and download the latest version manually. Thank you for using bloop.\": \"要更新您的应用，请访问我们在 GitHub 上的发布页面并手动下载最新版本。感谢您使用 bloop。\",\n\t\"Today\": \"今天\",\n\t\"Toggle black theme\": \"切换黑色主题\",\n\t\"Toggle dark theme\": \"切换暗色主题\",\n\t\"Toggle light theme\": \"切换浅色主题\",\n\t\"Toggle regex search\": \"切换正则搜索\",\n\t\"Toggle system theme\": \"切换系统主题\",\n\t\"Toggle\": \"切换\",\n\t\"Token limit exceeded\": \"超出令牌限制\",\n\t\"Token limit exceeded. Reduce the number of context files or messages to enable the ability to generate.\": \"超出令牌限制。减少上下文文件的数量或消息，以启用生成能力。\",\n\t\"Token limit reached, this answer may be incomplete. To generate a full answer, please reduce the number of tokens used and regenerate.\": \"达到令牌限制，此答案可能不完整。要产生完整的答案，请减少使用和再生的代币数量。\",\n\t\"Try again\": \"再试一次\",\n\t\"Unavailable\": \"不可用\",\n\t\"Unlimited code studio requests\": \"无限代码工作室请求\",\n\t\"Unlimited usage and premium features are activated.\": \"无限配额和高级功能已激活。\",\n\t\"Unlock the value of your existing code, using AI\": \"利用 AI 解锁您现有代码的价值\",\n\t\"Untitled project\": \"无标题项目\",\n\t\"Update Required\": \"需要更新\",\n\t\"Upgrade now\": \"现在升级\",\n\t\"Upgrade plan\": \"升级计划\",\n\t\"Upgrade\": \"升级\",\n\t\"Upvote\": \"赞成\",\n\t\"Usage exceeded\": \"已超出配额\",\n\t\"Usage resets in\": \"配额重置于\",\n\t\"Usage status\": \"配额状态\",\n\t\"Use GitHub to sign in to your account\": \"使用 GitHub 登录您的账户\",\n\t\"Use dark theme\": \"使用深色主题\",\n\t\"Use file\": \"使用文件\",\n\t\"Use light theme\": \"使用浅色主题\",\n\t\"Use template\": \"使用模板\",\n\t\"Use templates\": \"使用模板\",\n\t\"Use\": \"使用\",\n\t\"User\": \"用户\",\n\t\"Verifying access...\": \"正在验证访问权限...\",\n\t\"Verifying link...\": \"验证链接...\",\n\t\"View all results\": \"查看所有结果\",\n\t\"View all\": \"查看全部\",\n\t\"View bloop app documentation on our website\": \"在我们的网站上查看 bloop 应用程序文档\",\n\t\"View history\": \"查看历史\",\n\t\"View in {{viewer}}\": \"在{{viewer}}中查看\",\n\t\"View\": \"查看\",\n\t\"Visit the downloads page\": \"访问下载页面\",\n\t\"Waiting for authentication...\": \"正在等待身份验证...\",\n\t\"Watch\": \"关注\",\n\t\"We can’t generate a response because some files have a missing source in your Context files.\": \"我们无法生成响应，因为某些文件在您的上下文文件中具有丢失的源。\",\n\t\"We couldn't answer your question. You can try asking again in a few moments, or rephrasing your question.\": \"我们无法回答您的问题。您可以尝试在几分钟后再次提问，或者以不同的方式提问。\",\n\t\"We couldn't find any docs at that link. Try again or make sure the link is correct!\": \"我们找不到该链接的任何文档。重试或确保链接正确！\",\n\t\"We want to make this the best experience for you. If you encountered a bug, please submit this bug report to us. Our team will investigate as soon as possible.\": \"我们希望为您提供最好的体验。如果您遇到了错误，请向我们提交错误报告。我们的团队将会尽快调查。\",\n\t\"We weren't able to identify any references at the moment\": \"我们目前无法找到任何引用\",\n\t\"We've made some exciting enhancements to bloop! To continue enjoying the full functionality, including the natural language search feature, please update your app to the latest version.\": \"我们对 bloop 进行了一些激动人心的增强功能！为了继续享受全部功能，包括自然语言搜索功能，请更新您的应用到最新版本。\",\n\t\"We've redirected you to Stripe to complete your transaction. <2>Launch manually</2> if it didn't work.\": \"我们将您重定向到条纹以完成您的交易。<2>如果它不起作用，请启动</2>。\",\n\t\"Welcome to bloop\": \"欢迎来到 bloop\",\n\t\"We’ve updated our auth service to make bloop more secure, please reauthorise your client with GitHub\": \"我们已经更新了我们的身份服务以使 bloop 更加安全，请使用 GitHub 重新授权您的程序\",\n\t\"Whole file\": \"整个文件\",\n\t\"Whole page\": \"整页\",\n\t\"Write a message, @ to mention files, folders or docs...\": \"写一条消息， @以提及文件，文件夹或文档...\",\n\t\"Write studio prompts faster with pre-written templates\": \"用预编辑的模板更快地编写工作室提示\",\n\t\"Write your prompt...\": \"写你的提示...\",\n\t\"Yesterday\": \"昨天\",\n\t\"You can add 3 types of repositories, private, public and local.\": \"您可以添加 3 种类型的存储库，即私人、公共和本地存储库。\",\n\t\"You\": \"你\",\n\t\"You've run out of free usage for today, please wait for your quota to reset or upgrade for unlimited usage\": \"您今天的免费配额已用完，请等待您的配额重置或升级到无限制配额\",\n\t\"You've upgraded your account!\": \"您已经升级了您的帐户！\",\n\t\"Your quota resets every 24 hours, upgrade for unlimited uses\": \"您的配额每 24 小时重置一次，升级以获得无限配额\",\n\t\"Your subscription has expired. Please update your payment details to avoid being unsubscribed.\": \"您的订阅已过期。请更新您的付款详细信息，以延续订阅。\",\n\t\"and \": \"和\",\n\t\"avatar\": \"头像\",\n\t\"billed monthly\": \"每月收费\",\n\t\"bloop automatically excludes certain files from indexing. This file might be too big or it might have an excluded file type.\": \"bloop 已自动将某些文件从索引中排除。该文件可能太大，或可能属于排除的文件类型。\",\n\t\"bloop automatically excludes certain files from the indexing. This file might be too big or it might have an excluded file type.\": \"bloop 已自动将某些文件从索引中排除。该文件可能太大，或可能属于排除的文件类型。\",\n\t\"bloop crashed unexpectedly\": \"bloop 意外崩溃\",\n\t\"cancelled\": \"已取消\",\n\t\"cancelling\": \"正在取消\",\n\t\"chats in bloop\": \"在 bloop 中对话\",\n\t\"definition\": \"定义\",\n\t\"done\": \"已完成\",\n\t\"indexing\": \"索引中\",\n\t\"key\": \"键\",\n\t\"or \": \"或者 \",\n\t\"or go to the following link\": \"或转到以下链接\",\n\t\"or visit: \": \"或者访问: \",\n\t\"reference\": \"引用\",\n\t\"syncing\": \"同步中\",\n\t\"to seamlessly explore code across all branches in your GitHub repositories, maximizing your code discovery capabilities.\": \"无缝地探索 GitHub 仓库中所有分支的代码，最大限度地提升您的代码发现能力。\",\n\t\"uses left\": \"配额剩余\",\n\t\"uses left_one\": \"配额剩余\",\n\t\"uses left_other\": \"配额剩余\",\n\t\"{{count}} context files used\": \"{{count}}已使用的上下文配额\",\n\t\"{{count}} context files used_one\": \"{{count}}已使用的上下文配额\",\n\t\"{{count}} context files used_other\": \"{{count}}已使用的上下文配额\",\n\t\"{{repoName}} has finished indexing and you can use it in your projects now.\": \"{{repoName}}已完成索引，您可以立即在项目中使用它。\",\n\t\"{{repoName}} is currently indexing as soon as it is finished it will be added to your project.\": \"正在索引{{repoName}}，一旦完成后，它将被添加到您的项目中。\",\n\t\"<0>Use your cursor to select any piece of code within a file and ask bloop to explain it by pressing \\\"Explain\\\" in the floating toolbar.</0>\": \"<0>使用您的光标选择文件中的任何代码，并通过按下浮动工具栏中按“解释”按钮来要求 bloop 解释。</0>\",\n\t\"Click on an identifier and jump to its references and definition in a heart beat.\": \"单击标识符，可以在瞬间查看其引用和定义。\",\n\t\"Hide search steps\": \"隐藏搜索步骤\",\n\t\"Close currently focused tab\": \"关闭当前专注的选项卡\",\n\t\"Add any library documentation\": \"添加任何库文档\",\n\t\"Add documentation\": \"添加文档\",\n\t\"Manage your general project settings\": \"管理您的一般项目设置\",\n\t\"Prompt\": \"迅速的\",\n\t\"Usage resets at\": \"配额重置于\",\n\t\"Upgrade to Personal plan\": \"升级到个人计划\",\n\t\"In this project\": \"在这个项目中\",\n\t\"Existing studio conversations\": \"现有的工作室对话\",\n\t\"<0>{{repoName}}</0> has finished indexing and can be added to your projects. Click the button to below to add it to the current project.\": \"<0>{{repoName}}</0>已完成索引，可以添加到您的项目中。单击下面的按钮将其添加到当前项目中。\",\n\t\"Start by selecting again and pressing Enter (↵) on your keyboard.\": \"再次选中并按下 Enter (↵) 键以开始。\",\n\t\"{{repoName}} is currently indexing as soon as it is finished you will be able to add it to your project.\": \"正在索引{{repoName}}，一旦完成后，您就可以将其添加到项目中。\",\n\t\"Conversation input\": \"对话输入\",\n\t\"Fallback: Use if experiencing problems with the default one\": \"备用：如果默认输入类型有问题，请使用此项\",\n\t\"Simplified\": \"简化\",\n\t\"Select the input type to use in conversations\": \"选择要在对话中使用的输入类型\",\n\t\"Default\": \"默认\",\n\t\"Recommended: The classic input\": \"推荐：经典输入\",\n\t\"Add multiple files\": \"添加多个文件\",\n\t\"Explain lines {{lineStart}} - {{lineEnd}} in {{filePath}}\": \"解释 {{filePath}} 中的 {{lineStart}} - {{lineEnd}} 行\"\n}"
  },
  {
    "path": "client/src/locales/zh-TW.json",
    "content": "{\n\t\"# match_one\": \"{{count}} 筆符合\",\n\t\"# match_other\": \"{{count}} 筆符合\",\n\t\"# of #_one\": \"{{count}} / {{total}}\",\n\t\"# of #_other\": \"{{count}} / {{total}}\",\n\t\"# ranges_one\": \"{{count}} 個範圍\",\n\t\"# ranges_other\": \"{{count}} 個範圍\",\n\t\"# sections_one\": \"{{count}} 個區段\",\n\t\"# sections_other\": \"{{count}} 個區段\",\n\t\"# selected section_one\": \"{{count}} 個已選取區段\",\n\t\"# selected section_other\": \"{{count}} 個已選取區段\",\n\t\"# tokens_one\": \"個 token\",\n\t\"# tokens_other\": \"個 token\",\n\t\"$20 / billed monthly\": \"$20 / 每月計費\",\n\t\"<0>#</0> of # tokens\": \"<0>{{count}}</0> / {{total}} 個 token\",\n\t\"<0>#</0> of # tokens_one\": \"<0>{{count}}</0> / {{total}} 個 token\",\n\t\"<0>#</0> of # tokens_other\": \"<0>{{count}}</0> / {{total}} 個 token\",\n\t\"<0></0><1></1> to navigate.\": \"使用 <0></0><1></1> 來瀏覽。\",\n\t\"<0>Create a new conversation with bloop by hitting <2></2> on your keyboard or by pressing the <4></4> in the header bar.</0>\": \"<0>按下鍵盤上的 <2></2> 或點選標題列中的 <4></4> 來與 bloop 開始新對話。</0>\",\n\t\"<0>To begin, open a file from the sidebar on the left. Once you have a file open, you can ask bloop to quickly explain it by hitting <2></2> on your keyboard or by selecting \\\"Explain file\\\" from the <7></7> popup menu.</0>\": \"<0>首先，從左側的側邊欄開啟一個檔案。開啟檔案後，您可以按下鍵盤上的 <2></2> 或從 <7></7> 彈出式選單中選擇「解釋檔案」來要求 bloop 快速解釋它。</0>\",\n\t\"<0>Use your cursor to select any piece of code within a file and ask bloop to explain it by pressing \\\"Explain\\\" in the floating toolbar.</0>\": \"<0>使用游標在檔案中選取任何程式碼片段，然後按下浮動工具列中的「解釋」來要求 bloop 解釋。</0>\",\n\t\"<0>repoName</0> has finished indexing and was added to the context of the current project. You can also use it in your other projects now.\": \"<0>{{repoName}}</0> 已完成索引，並已新增至目前專案的環境中。您現在也可以在其他專案中使用它。\",\n\t\"<0>repoName</0> has started indexing. You’ll receive a notification as soon as this process completes.\": \"<0>{{repoName}}</0> 已開始建立索引。此過程完成後，您將收到通知。\",\n\t\"Account settings\": \"帳號設定\",\n\t\"Actions\": \"動作\",\n\t\"Add a repository from your local machine\": \"從您的電腦新增儲存庫\",\n\t\"Add any library documentation\": \"新增任何函式庫文件\",\n\t\"Add at least one context file before submitting your first request.\": \"在送出第一個請求前，請至少新增一個環境檔案。\",\n\t\"Add doc to studio\": \"將文件新增至工作室\",\n\t\"Add docs\": \"新增文件\",\n\t\"Add file to code studio context\": \"將檔案新增至程式碼工作室環境\",\n\t\"Add file to studio\": \"將檔案新增至工作室\",\n\t\"Add file\": \"新增檔案\",\n\t\"Add files to studio\": \"將檔案新增至工作室\",\n\t\"Add files\": \"新增檔案\",\n\t\"Add local repository\": \"新增本機儲存庫\",\n\t\"Add new repository\": \"新增儲存庫\",\n\t\"Add private repository\": \"新增私人儲存庫\",\n\t\"Add public repository\": \"新增公開儲存庫\",\n\t\"Add repository\": \"新增儲存庫\",\n\t\"Add to existing\": \"新增至現有專案\",\n\t\"Add to project\": \"新增至專案\",\n\t\"Add to studio\": \"新增至工作室\",\n\t\"Add\": \"新增\",\n\t\"Adding repository\": \"正在新增儲存庫\",\n\t\"All payments, invoices and billing information are managed in Stripe.\": \"所有付款、發票和帳單資訊皆由 Stripe 管理。\",\n\t\"All sections\": \"所有區段\",\n\t\"All templates\": \"所有範本\",\n\t\"All\": \"全部\",\n\t\"Answer speed\": \"回答速度\",\n\t\"Application theme\": \"應用程式主題\",\n\t\"Apply changes\": \"套用變更\",\n\t\"Ask questions about your codebases in natural language, just like you’d speak to ChatGPT. Get started by syncing a repo, then open the repo and start chatting.\": \"使用自然語言詢問關於您程式碼庫的問題，就像您與 ChatGPT 對話一樣。首先同步一個儲存庫，然後開啟該儲存庫並開始聊天。\",\n\t\"Ask your first question\": \"詢問您的第一個問題\",\n\t\"Assistant\": \"助理\",\n\t\"Back to current\": \"回到目前\",\n\t\"Back\": \"返回\",\n\t\"Bad response\": \"回應不佳\",\n\t\"Bad\": \"差\",\n\t\"Below are a few suggestions you can ask me to get started:\": \"以下是一些您可以詢問我，以便開始使用的建議：\",\n\t\"Black\": \"黑色\",\n\t\"Bloop needs to index your repository first. This process takes a few seconds and happens only one time per repository.\": \"Bloop 需要先為您的儲存庫建立索引。這個過程需要幾秒鐘，並且每個儲存庫只會執行一次。\",\n\t\"Branches\": \"分支\",\n\t\"By continuing you accept our\": \"繼續即表示您接受我們的\",\n\t\"By submitting this crash report you agree to send it to bloop for investigation.\": \"送出此當機報告，即表示您同意將其傳送給 bloop 進行調查。\",\n\t\"Cancel diff generation\": \"取消差異生成\",\n\t\"Cancel\": \"取消\",\n\t\"Cancelled\": \"已取消\",\n\t\"Cancelling...\": \"正在取消...\",\n\t\"Change application colour theme\": \"變更應用程式顏色主題\",\n\t\"Chat conversations\": \"聊天對話\",\n\t\"Clear conversation\": \"清除對話\",\n\t\"Clear input\": \"清除輸入\",\n\t\"Clear range\": \"清除範圍\",\n\t\"Clear ranges\": \"清除範圍\",\n\t\"Clear section\": \"清除區段\",\n\t\"Clear sections\": \"清除區段\",\n\t\"Clear selection\": \"清除選取項目\",\n\t\"Click on an identifier and jump to its references and definition in a heart beat.\": \"點選識別碼，立即跳轉到其參考和定義。\",\n\t\"Click to copy\": \"點選以複製\",\n\t\"Cloning\": \"正在複製\",\n\t\"Cloning...\": \"正在複製...\",\n\t\"Close all open tabs\": \"關閉所有開啟的分頁\",\n\t\"Close all tabs\": \"關閉所有分頁\",\n\t\"Close current tab\": \"關閉目前分頁\",\n\t\"Close currently focused tab\": \"關閉目前焦點分頁\",\n\t\"Close search\": \"關閉搜尋\",\n\t\"Close tab\": \"關閉分頁\",\n\t\"Close\": \"關閉\",\n\t\"Code search\": \"程式碼搜尋\",\n\t\"Code studio helps you write scripts, create unit tests, debug issues or generate anything else you can think of using AI! Sync a repo, then create a code studio project.\": \"程式碼工作室可以幫助您編寫指令碼、建立單元測試、除錯問題或使用 AI 產生任何您可以想到的東西！同步一個儲存庫，然後建立一個程式碼工作室專案。\",\n\t\"Collapse\": \"摺疊\",\n\t\"Commands\": \"命令\",\n\t\"Complete your transaction in Stripe...\": \"在 Stripe 完成您的交易...\",\n\t\"Confirm\": \"確認\",\n\t\"Connect account\": \"連結帳號\",\n\t\"Context files\": \"環境檔案\",\n\t\"Continue from this state\": \"從這個狀態繼續\",\n\t\"Continue\": \"繼續\",\n\t\"Copied\": \"已複製\",\n\t\"Copy link\": \"複製連結\",\n\t\"Copy message\": \"複製訊息\",\n\t\"Copy\": \"複製\",\n\t\"Create line ranges\": \"建立行數範圍\",\n\t\"Create new project\": \"建立新專案\",\n\t\"Create new\": \"建立新的\",\n\t\"Create project\": \"建立專案\",\n\t\"Create ranges\": \"建立範圍\",\n\t\"Create template\": \"建立範本\",\n\t\"Current\": \"目前\",\n\t\"Currently active\": \"目前啟用\",\n\t\"Dark\": \"深色\",\n\t\"Default project\": \"預設專案\",\n\t\"Delete all conversations\": \"刪除所有對話\",\n\t\"Delete conversation\": \"刪除對話\",\n\t\"Delete project\": \"刪除專案\",\n\t\"Delete template\": \"刪除範本\",\n\t\"Delete\": \"刪除\",\n\t\"Desktop app\": \"桌面應用程式\",\n\t\"Diff generation failed\": \"差異生成失敗\",\n\t\"Directories\": \"目錄\",\n\t\"Disconnect\": \"斷線\",\n\t\"Discord\": \"Discord\",\n\t\"Dismiss tutorial\": \"關閉教學\",\n\t\"Display\": \"顯示\",\n\t\"Docs\": \"文件\",\n\t\"Documentation URL...\": \"文件網址...\",\n\t\"Documentation in studio\": \"工作室中的文件\",\n\t\"Documentation\": \"文件\",\n\t\"Done\": \"完成\",\n\t\"Downgrade\": \"降級\",\n\t\"Downvote\": \"倒讚\",\n\t\"Edit ranges\": \"編輯範圍\",\n\t\"Edit sections\": \"編輯區段\",\n\t\"Edit selected lines\": \"編輯選取的行數\",\n\t\"Edit selected sections\": \"編輯選取的區段\",\n\t\"Edit template\": \"編輯範本\",\n\t\"Edit\": \"編輯\",\n\t\"Editing previously submitted questions will discard all answers and questions following it\": \"編輯先前送出的問題將會捨棄之後的所有答案和問題\",\n\t\"Email address\": \"電子郵件地址\",\n\t\"Email is not valid\": \"電子郵件無效\",\n\t\"Email is required\": \"需要電子郵件\",\n\t\"Email\": \"電子郵件\",\n\t\"Error\": \"錯誤\",\n\t\"Existing studio conversations\": \"現有的工作室對話\",\n\t\"Expand\": \"展開\",\n\t\"Experimental: Faster but less accurate\": \"實驗性：更快但較不精確\",\n\t\"Explain a file\": \"解釋檔案\",\n\t\"Explain code\": \"解釋程式碼\",\n\t\"Explain current file\": \"解釋目前檔案\",\n\t\"Explain file\": \"解釋檔案\",\n\t\"Explain the purpose of the file {{filePath}}, from lines {{lineStart}} - {{lineEnd}}\": \"解釋檔案 {{filePath}} 的用途，從第 {{lineStart}} 行到第 {{lineEnd}} 行\",\n\t\"Explain\": \"解釋\",\n\t\"Failed to apply the diff\": \"套用差異失敗\",\n\t\"Failed to get a response from OpenAI. Try again in a few moments.\": \"無法從 OpenAI 取得回應。請稍後再試。\",\n\t\"Fast\": \"快速\",\n\t\"File not indexed\": \"檔案尚未建立索引\",\n\t\"File search\": \"檔案搜尋\",\n\t\"File\": \"檔案\",\n\t\"Files\": \"檔案\",\n\t\"Files, paths or folders...\": \"檔案、路徑或資料夾...\",\n\t\"Filter repositories by\": \"依...篩選儲存庫\",\n\t\"Filters\": \"篩選器\",\n\t\"First name is required\": \"需要名字\",\n\t\"First name\": \"名字\",\n\t\"Fold everything\": \"全部摺疊\",\n\t\"Folder too large\": \"資料夾太大\",\n\t\"Follow us on Twitter\": \"在 Twitter 上追蹤我們\",\n\t\"Force index\": \"強制建立索引\",\n\t\"Free\": \"免費\",\n\t\"General\": \"一般\",\n\t\"Generate code using AI\": \"使用 AI 產生程式碼\",\n\t\"Generate\": \"生成\",\n\t\"Generated diffs to be applied\": \"將套用的已生成差異\",\n\t\"Generating answer...\": \"正在產生答案...\",\n\t\"Generating diff...\": \"正在產生差異...\",\n\t\"Generating response...\": \"正在產生回應...\",\n\t\"GitHub Branches\": \"GitHub 分支\",\n\t\"GitHub\": \"GitHub\",\n\t\"Give your template a title\": \"為您的範本命名\",\n\t\"Go back\": \"返回\",\n\t\"Go to current state\": \"前往目前狀態\",\n\t\"Good\": \"好\",\n\t\"Got it\": \"知道了\",\n\t\"Hi, I am bloop! In <2><0></0>Chat mode</2> I can answer any questions related to any of your repositories.\": \"嗨，我是 bloop！在 <2><0></0>聊天模式</2> 中，我可以回答任何與您的儲存庫相關的問題。\",\n\t\"Hi, I am bloop! In <2><0></0>Studio mode</2> you can choose files from your codebase, write a prompt and generate patches, scripts and tests.\": \"嗨，我是 bloop！在 <2><0></0>工作室模式</2> 中，您可以從您的程式碼庫中選擇檔案、編寫提示並產生修補程式、指令碼和測試。\",\n\t\"Hide\": \"隱藏\",\n\t\"History\": \"歷史紀錄\",\n\t\"How fast or precise bloop’s answers will be.\": \"bloop 答案的速度或精確度。\",\n\t\"In this file\": \"在這個檔案中\",\n\t\"In this project\": \"在這個專案中\",\n\t\"Index multiple branches\": \"為多個分支建立索引\",\n\t\"Index repository\": \"為儲存庫建立索引\",\n\t\"Index\": \"索引\",\n\t\"Indexed documentation web pages\": \"已建立索引的文件網頁\",\n\t\"Indexed local repositories\": \"已建立索引的本機儲存庫\",\n\t\"Indexed\": \"已建立索引\",\n\t\"Indexing in progress\": \"正在建立索引\",\n\t\"Indexing repositories\": \"正在為儲存庫建立索引\",\n\t\"Indexing repository\": \"正在為儲存庫建立索引\",\n\t\"Indexing\": \"正在建立索引\",\n\t\"Indexing...\": \"正在建立索引...\",\n\t\"Individual\": \"個人\",\n\t\"Invert\": \"反轉\",\n\t\"Join Discord\": \"加入 Discord\",\n\t\"Language\": \"語言\",\n\t\"Languages\": \"語言\",\n\t\"Last name is required\": \"需要姓氏\",\n\t\"Last name\": \"姓氏\",\n\t\"Last updated \": \"最後更新時間 \",\n\t\"Latest model updates\": \"最新的模型更新\",\n\t\"Launch manually\": \"手動啟動\",\n\t\"Let’s get you started with bloop!\": \"讓我們開始使用 bloop 吧！\",\n\t\"Light\": \"淺色\",\n\t\"Lines # - #\": \"第 {{start}} - {{end}} 行\",\n\t\"Loading...\": \"載入中...\",\n\t\"Local repositories\": \"本機儲存庫\",\n\t\"Local repository\": \"本機儲存庫\",\n\t\"Local\": \"本機\",\n\t\"Manage context\": \"管理環境\",\n\t\"Manage docs\": \"管理文件\",\n\t\"Manage project\": \"管理專案\",\n\t\"Manage repositories\": \"管理儲存庫\",\n\t\"Manage subscription\": \"管理訂閱\",\n\t\"Manage templates\": \"管理範本\",\n\t\"Manage your general account settings\": \"管理您的一般帳號設定\",\n\t\"Manage your general project settings\": \"管理您的一般專案設定\",\n\t\"Manage your preferences\": \"管理您的偏好設定\",\n\t\"Manage your studio settings.\": \"管理您的工作室設定。\",\n\t\"Manage your subscription plan\": \"管理您的訂閱方案\",\n\t\"Manage\": \"管理\",\n\t\"Missing source\": \"缺少來源\",\n\t\"More actions\": \"更多動作\",\n\t\"Move\": \"移動\",\n\t\"My templates\": \"我的範本\",\n\t\"Name\": \"名稱\",\n\t\"Natural language search\": \"自然語言搜尋\",\n\t\"Navigate your codebase\": \"瀏覽您的程式碼庫\",\n\t\"Navigate\": \"瀏覽\",\n\t\"New conversation\": \"新的對話\",\n\t\"New project\": \"新的專案\",\n\t\"New studio conversation\": \"新的工作室對話\",\n\t\"New tab\": \"新的分頁\",\n\t\"New\": \"新增\",\n\t\"Next page\": \"下一頁\",\n\t\"Next\": \"下一個\",\n\t\"No change in payment status identified.\": \"未發現付款狀態有任何變更。\",\n\t\"No commands found...\": \"找不到命令...\",\n\t\"No file selected\": \"未選取檔案\",\n\t\"No files found...\": \"找不到檔案...\",\n\t\"No references or definitions found\": \"找不到參考或定義\",\n\t\"No repositories found...\": \"找不到儲存庫...\",\n\t\"No results\": \"沒有結果\",\n\t\"No uses left. Uses reset at\": \"沒有剩餘使用次數。使用次數將於以下時間重置：\",\n\t\"Normal\": \"正常\",\n\t\"Not a git repository\": \"不是 Git 儲存庫\",\n\t\"Not synced\": \"尚未同步\",\n\t\"Open Command Bar\": \"開啟命令列\",\n\t\"Open account settings\": \"開啟帳號設定\",\n\t\"Open in GitHub\": \"在 GitHub 中開啟\",\n\t\"Open in split view\": \"在分割檢視中開啟\",\n\t\"Open in {{viewer}}\": \"在 {{viewer}} 中開啟\",\n\t\"Open subscription settings\": \"開啟訂閱設定\",\n\t\"Open\": \"開啟\",\n\t\"Paste a link to any documentation web page\": \"貼上任何文件網頁的連結\",\n\t\"Paste a link to any public repository hosted on GitHub\": \"貼上任何託管在 GitHub 上的公開儲存庫連結\",\n\t\"Permanently delete <2>{{projectName}}</2> and remove all the data associated to it. Repositories will remain accessible in your GitHub account.\": \"永久刪除 <2>{{projectName}}</2> 並移除與其關聯的所有資料。儲存庫仍可在您的 GitHub 帳號中存取。\",\n\t\"Personal\": \"個人\",\n\t\"Plans\": \"方案\",\n\t\"Please log into your GitHub account to complete setup, this helps us combat misuse.\": \"請登入您的 GitHub 帳號以完成設定，這有助於我們打擊濫用行為。\",\n\t\"Precise code navigation\": \"精確的程式碼瀏覽\",\n\t\"Preferences\": \"偏好設定\",\n\t\"Press <2>cmdKey</2> <4>K</4> on your keyboard to open the Command bar and add a repository.\": \"按下鍵盤上的 <2>{{cmdKey}}</2> <4>K</4> 以開啟命令列並新增儲存庫。\",\n\t\"Press <2>cmdKey</2> <4>K</4> on your keyboard to open the Command bar.\": \"按下鍵盤上的 <2>{{cmdKey}}</2> <4>K</4> 以開啟命令列。\",\n\t\"Privacy policy\": \"隱私權政策\",\n\t\"Private repositories\": \"私人儲存庫\",\n\t\"Private repository\": \"私人儲存庫\",\n\t\"Private\": \"私人\",\n\t\"Problem details and System configuration\": \"問題詳細資訊和系統組態\",\n\t\"Project settings\": \"專案設定\",\n\t\"Project title\": \"專案標題\",\n\t\"Project\": \"專案\",\n\t\"Projects require at least one synced repository.\": \"專案至少需要一個已同步的儲存庫。\",\n\t\"Projects\": \"專案\",\n\t\"Prompt guide\": \"提示指南\",\n\t\"Prompt templates\": \"提示範本\",\n\t\"Prompt\": \"提示\",\n\t\"Prompts\": \"提示\",\n\t\"Provide a short, concise title for your project\": \"為您的專案提供簡短、精確的標題\",\n\t\"Provide any steps necessary to reproduce the problem...\": \"提供重現問題所需的任何步驟...\",\n\t\"Public repositories\": \"公開儲存庫\",\n\t\"Public repository\": \"公開儲存庫\",\n\t\"Public\": \"公開\",\n\t\"Query suggestions\": \"查詢建議\",\n\t\"Queued...\": \"已加入佇列...\",\n\t\"Re-check payment status\": \"重新檢查付款狀態\",\n\t\"Re-sync\": \"重新同步\",\n\t\"Reading \": \"正在讀取 \",\n\t\"Reading\": \"正在讀取\",\n\t\"Recent conversations\": \"最近的對話\",\n\t\"Recent projects\": \"最近的專案\",\n\t\"Recently used\": \"最近使用\",\n\t\"Recommended: The classic response type\": \"建議：傳統回應類型\",\n\t\"References\": \"參考\",\n\t\"Referencing target file\": \"參考目標檔案\",\n\t\"Release to open in split view\": \"放開以在分割檢視中開啟\",\n\t\"Remote removed \": \"已移除遠端 \",\n\t\"Remove file from code studio context\": \"從程式碼工作室環境中移除檔案\",\n\t\"Remove file\": \"移除檔案\",\n\t\"Remove from project\": \"從專案中移除\",\n\t\"Remove from studio\": \"從工作室中移除\",\n\t\"Remove page from code studio context\": \"從程式碼工作室環境中移除頁面\",\n\t\"Remove\": \"移除\",\n\t\"Removed\": \"已移除\",\n\t\"Rename code studio\": \"重新命名程式碼工作室\",\n\t\"Rename\": \"重新命名\",\n\t\"Report a bug\": \"回報錯誤\",\n\t\"Repositories\": \"儲存庫\",\n\t\"Repository URL...\": \"儲存庫網址...\",\n\t\"Repository indexed\": \"儲存庫已建立索引\",\n\t\"Repository types\": \"儲存庫類型\",\n\t\"Restart the app\": \"重新啟動應用程式\",\n\t\"Restore session\": \"還原工作階段\",\n\t\"Restore\": \"還原\",\n\t\"Result suggestions\": \"結果建議\",\n\t\"Results\": \"結果\",\n\t\"Retry\": \"重試\",\n\t\"Save changes\": \"儲存變更\",\n\t\"Save context changes before answer generation\": \"在產生答案之前儲存環境變更\",\n\t\"Save\": \"儲存\",\n\t\"Search branch...\": \"搜尋分支...\",\n\t\"Search branches...\": \"搜尋分支...\",\n\t\"Search code in natural language\": \"使用自然語言搜尋程式碼\",\n\t\"Search docs\": \"搜尋文件\",\n\t\"Search docs...\": \"搜尋文件...\",\n\t\"Search file...\": \"搜尋檔案...\",\n\t\"Search files\": \"搜尋檔案\",\n\t\"Search files...\": \"搜尋檔案...\",\n\t\"Search for code using regex\": \"使用正規表示式搜尋程式碼\",\n\t\"Search local repos...\": \"搜尋本機儲存庫...\",\n\t\"Search pages\": \"搜尋頁面\",\n\t\"Search private repos...\": \"搜尋私人儲存庫...\",\n\t\"Search projects or commands...\": \"搜尋專案或命令...\",\n\t\"Search public repositories...\": \"搜尋公開儲存庫...\",\n\t\"Search repos or Studio projects...\": \"搜尋儲存庫或工作室專案...\",\n\t\"Search studio conversations...\": \"搜尋工作室對話...\",\n\t\"Search templates...\": \"搜尋範本...\",\n\t\"Search using RegExp\": \"使用正規表示式搜尋\",\n\t\"Search your files in this project\": \"在這個專案中搜尋您的檔案\",\n\t\"Search your repositories using RegExp\": \"使用正規表示式搜尋您的儲存庫\",\n\t\"Search\": \"搜尋\",\n\t\"Searching...\": \"搜尋中...\",\n\t\"Select a file or open a new tab to display it here.\": \"選取一個檔案或開啟新的分頁以在此顯示。\",\n\t\"Select a folder containing a git repository\": \"選取一個包含 Git 儲存庫的資料夾\",\n\t\"Select all\": \"全選\",\n\t\"Select branch\": \"選取分支\",\n\t\"Select color theme:\": \"選取顏色主題：\",\n\t\"Select file\": \"選取檔案\",\n\t\"Select folder\": \"選取資料夾\",\n\t\"Select less code\": \"選取較少程式碼\",\n\t\"Select library\": \"選取函式庫\",\n\t\"Select page\": \"選取頁面\",\n\t\"Select repository\": \"選取儲存庫\",\n\t\"Select section\": \"選取區段\",\n\t\"Select sections\": \"選取區段\",\n\t\"Select the interface colour scheme\": \"選取介面配色方案\",\n\t\"Select the interface language\": \"選取介面語言\",\n\t\"Select\": \"選取\",\n\t\"Selected lines # - #.\": \"已選取第 {{start}} - {{end}} 行。\",\n\t\"Selected ranges will be used as context.\": \"選取的範圍將會用作環境。\",\n\t\"Settings\": \"設定\",\n\t\"Setup bloop\": \"設定 bloop\",\n\t\"Show # more match_one\": \"顯示其他 {{count}} 個相符項目\",\n\t\"Show # more match_other\": \"顯示其他 {{count}} 個相符項目\",\n\t\"Show file\": \"顯示檔案\",\n\t\"Show filters\": \"顯示篩選器\",\n\t\"Show less\": \"顯示較少\",\n\t\"Show link\": \"顯示連結\",\n\t\"Show more\": \"顯示更多\",\n\t\"Show search steps\": \"顯示搜尋步驟\",\n\t\"Show\": \"顯示\",\n\t\"Sign In\": \"登入\",\n\t\"Sign in with GitHub\": \"使用 GitHub 登入\",\n\t\"Sign out\": \"登出\",\n\t\"Skip (Not recommended)\": \"跳過（不建議）\",\n\t\"Skip\": \"跳過\",\n\t\"Something went wrong\": \"發生錯誤\",\n\t\"Start by selecting \": \"首先選取 \",\n\t\"Start by selecting a repository and pressing Enter (↵) on your keyboard.\": \"首先選取一個儲存庫，然後按下鍵盤上的 Enter (↵)。\",\n\t\"Start by selecting a type repository you would like to index.\": \"首先選取您想要建立索引的儲存庫類型。\",\n\t\"Start indexing\": \"開始建立索引\",\n\t\"Start typing...\": \"開始輸入...\",\n\t\"Status\": \"狀態\",\n\t\"Stop generating\": \"停止生成\",\n\t\"Stop indexing\": \"停止建立索引\",\n\t\"Streaming response...\": \"串流回應中...\",\n\t\"Studio conversation require at least one context file.\": \"工作室對話至少需要一個環境檔案。\",\n\t\"Studio conversation\": \"工作室對話\",\n\t\"Studio conversations\": \"工作室對話\",\n\t\"Studio mode\": \"工作室模式\",\n\t\"Studio project\": \"工作室專案\",\n\t\"Studio requests\": \"工作室請求\",\n\t\"Studio\": \"工作室\",\n\t\"Submit bug report\": \"送出錯誤報告\",\n\t\"Submit crash report\": \"送出當機報告\",\n\t\"Submit\": \"送出\",\n\t\"Subscription\": \"訂閱\",\n\t\"Switch branch\": \"切換分支\",\n\t\"Switch to\": \"切換至\",\n\t\"Sync local git repos\": \"同步本機 Git 儲存庫\",\n\t\"Sync\": \"同步\",\n\t\"Synced\": \"已同步\",\n\t\"Syncing\": \"同步中\",\n\t\"System Preference\": \"系統偏好設定\",\n\t\"System preferences\": \"系統偏好設定\",\n\t\"System\": \"系統\",\n\t\"Take a quick look\": \"快速查看\",\n\t\"Template title\": \"範本標題\",\n\t\"Templates\": \"範本\",\n\t\"Terms & conditions\": \"條款與條件\",\n\t\"The amount of times you can generate responses in Studio conversations per day.\": \"您每天可以在工作室對話中生成回應的次數。\",\n\t\"The diff has been applied locally.\": \"已在本機套用差異。\",\n\t\"The folder you selected has multiple git repositories nested inside.\": \"您選取的資料夾內有多個巢狀的 Git 儲存庫。\",\n\t\"The folder you selected is not a git repository.\": \"您選取的資料夾不是 Git 儲存庫。\",\n\t\"The following changes can be applied to your repository. Make sure the generated diffs are valid before you apply the changes.\": \"以下變更可以套用到您的儲存庫。套用變更之前，請確認生成的差異是否有效。\",\n\t\"The following changes represent the git diff for the remote repository. Please note that these changes cannot be applied directly to a remote repository. Use the \\\"Copy\\\" button to copy the changes and apply them locally.\": \"以下變更代表遠端儲存庫的 git 差異。請注意，這些變更無法直接套用到遠端儲存庫。請使用「複製」按鈕複製變更並在本機套用。\",\n\t\"The line of code where identifier is defined\": \"定義識別碼的程式碼行\",\n\t\"The line of code where the identifier is referenced\": \"參考識別碼的程式碼行\",\n\t\"The whole file will be used as context.\": \"整個檔案將作為環境資訊。\",\n\t\"Theme\": \"主題\",\n\t\"This is not a public repository / We couldn’t find this repository\": \"這不是公開儲存庫 / 我們找不到這個儲存庫\",\n\t\"This might be because the file is too big or it has one of bloop’s excluded file types.\": \"這可能是因為檔案太大或檔案類型是 bloop 排除的類型之一。\",\n\t\"This project is empty\": \"這個專案是空的\",\n\t\"Tip: Select code to create ranges for context use.\": \"提示：選取程式碼以建立用於環境的範圍。\",\n\t\"To update your app, please visit our releases page on GitHub and download the latest version manually. Thank you for using bloop.\": \"若要更新您的應用程式，請造訪我們在 GitHub 上的版本頁面並手動下載最新版本。感謝您使用 bloop。\",\n\t\"Today\": \"今天\",\n\t\"Toggle black theme\": \"切換黑色主題\",\n\t\"Toggle dark theme\": \"切換深色主題\",\n\t\"Toggle light theme\": \"切換淺色主題\",\n\t\"Toggle regex search\": \"切換正規表示式搜尋\",\n\t\"Toggle system theme\": \"切換系統主題\",\n\t\"Toggle\": \"切換\",\n\t\"Token limit exceeded\": \"已超過 token 限制\",\n\t\"Token limit exceeded. Reduce the number of context files or messages to enable the ability to generate.\": \"已超過 token 限制。請減少環境檔案或訊息的數量以啟用生成功能。\",\n\t\"Token limit reached, this answer may be incomplete. To generate a full answer, please reduce the number of tokens used and regenerate.\": \"已達到 token 限制，因此這個答案可能不完整。若要產生完整的答案，請減少使用的 token 數量並重新生成。\",\n\t\"Try again\": \"重試一次\",\n\t\"Unavailable\": \"無法使用\",\n\t\"Unlimited code studio requests\": \"無限制的程式碼工作室請求\",\n\t\"Unlimited usage and premium features are activated.\": \"已啟用無限制使用和進階功能。\",\n\t\"Unlock the value of your existing code, using AI\": \"使用 AI 解鎖您現有程式碼的價值\",\n\t\"Untitled project\": \"未命名的專案\",\n\t\"Update Required\": \"需要更新\",\n\t\"Upgrade now\": \"立即升級\",\n\t\"Upgrade plan\": \"升級方案\",\n\t\"Upgrade to Personal plan\": \"升級至個人方案\",\n\t\"Upgrade\": \"升級\",\n\t\"Upvote\": \"讚\",\n\t\"Usage exceeded\": \"使用量已超過\",\n\t\"Usage resets at\": \"使用量將於以下時間重置：\",\n\t\"Usage resets in\": \"使用量將於\",\n\t\"Usage status\": \"使用量狀態\",\n\t\"Use GitHub to sign in to your account\": \"使用 GitHub 登入您的帳號\",\n\t\"Use black theme\": \"使用黑色主題\",\n\t\"Use dark theme\": \"使用深色主題\",\n\t\"Use file\": \"使用檔案\",\n\t\"Use light theme\": \"使用淺色主題\",\n\t\"Use system theme\": \"使用系統主題\",\n\t\"Use template\": \"使用範本\",\n\t\"Use templates\": \"使用範本\",\n\t\"Use\": \"使用\",\n\t\"User\": \"使用者\",\n\t\"Verifying access...\": \"正在驗證存取權限...\",\n\t\"Verifying link...\": \"正在驗證連結...\",\n\t\"View all results\": \"檢視所有結果\",\n\t\"View all\": \"檢視全部\",\n\t\"View bloop app documentation on our website\": \"在我們的網站上檢視 bloop 應用程式文件\",\n\t\"View history\": \"檢視歷史紀錄\",\n\t\"View in {{viewer}}\": \"在 {{viewer}} 中檢視\",\n\t\"View\": \"檢視\",\n\t\"Visit the downloads page\": \"前往下載頁面\",\n\t\"Waiting for authentication...\": \"正在等待驗證...\",\n\t\"Watch\": \"關注\",\n\t\"We can’t generate a response because some files have a missing source in your Context files.\": \"我們無法產生回應，因為您的環境檔案中有些檔案缺少來源。\",\n\t\"We couldn't answer your question. You can try asking again in a few moments, or rephrasing your question.\": \"我們無法回答您的問題。您可以稍後再試一次，或換個方式詢問您的問題。\",\n\t\"We couldn't find any docs at that link. Try again or make sure the link is correct!\": \"我們在該連結找不到任何文件。請再試一次，或確認連結是否正確！\",\n\t\"We want to make this the best experience for you. If you encountered a bug, please submit this bug report to us. Our team will investigate as soon as possible.\": \"我們希望為您提供最佳體驗。如果您遇到錯誤，請向我們送出此錯誤報告。我們的團隊會盡快展開調查。\",\n\t\"We weren't able to identify any references at the moment\": \"我們目前無法識別任何參考\",\n\t\"We've made some exciting enhancements to bloop! To continue enjoying the full functionality, including the natural language search feature, please update your app to the latest version.\": \"我們對 bloop 進行了一些令人興奮的強化功能！若要繼續享用完整功能，包括自然語言搜尋功能，請將您的應用程式更新至最新版本。\",\n\t\"We've redirected you to Stripe to complete your transaction. <2>Launch manually</2> if it didn’t work.\": \"我們已將您重新導向至 Stripe 以完成交易。如果無法運作，請 <2>手動啟動</2>。\",\n\t\"Welcome to bloop\": \"歡迎使用 bloop\",\n\t\"We’ve updated our auth service to make bloop more secure, please reauthorise your client with GitHub\": \"我們已更新驗證服務，讓 bloop 更安全，請使用 GitHub 重新授權您的用戶端\",\n\t\"Whole file\": \"整個檔案\",\n\t\"Whole page\": \"整個頁面\",\n\t\"Write a message, @ to mention files, folders or docs...\": \"撰寫訊息，使用 @ 以提及檔案、資料夾或文件...\",\n\t\"Write studio prompts faster with pre-written templates\": \"使用預先編寫的範本更快地撰寫工作室提示\",\n\t\"Write your prompt...\": \"寫下您的提示...\",\n\t\"Yesterday\": \"昨天\",\n\t\"You can add 3 types of repositories, private, public and local.\": \"您可以新增 3 種類型的儲存庫：私人、公開和本機。\",\n\t\"You\": \"您\",\n\t\"You've run out of free usage for today, please wait for your quota to reset or upgrade for unlimited usage\": \"您今天的免費使用量已用完，請等待您的配額重置或升級以取得無限制使用權限\",\n\t\"You've upgraded your account!\": \"您已升級您的帳號！\",\n\t\"Your quota resets every 24 hours, upgrade for unlimited uses\": \"您的配額每 24 小時重置一次，升級以取得無限制使用次數\",\n\t\"Your subscription has expired. Please update your payment details to avoid being unsubscribed.\": \"您的訂閱已過期。請更新您的付款詳細資訊，以避免被取消訂閱。\",\n\t\"and \": \"和 \",\n\t\"avatar\": \"大頭貼\",\n\t\"billed monthly\": \"每月計費\",\n\t\"bloop automatically excludes certain files from indexing. This file might be too big or it might have an excluded file type.\": \"bloop 會自動排除某些檔案的索引。這個檔案可能太大，或者可能是被排除的檔案類型。\",\n\t\"bloop automatically excludes certain files from the indexing. This file might be too big or it might have an excluded file type.\": \"bloop 會自動排除某些檔案的索引。這個檔案可能太大，或者可能是被排除的檔案類型。\",\n\t\"bloop crashed unexpectedly\": \"bloop 意外當機\",\n\t\"cancelled\": \"已取消\",\n\t\"cancelling\": \"正在取消\",\n\t\"chats in bloop\": \"在 bloop 中的聊天\",\n\t\"definition\": \"定義\",\n\t\"done\": \"完成\",\n\t\"indexing\": \"正在建立索引\",\n\t\"key\": \"金鑰\",\n\t\"or \": \"或 \",\n\t\"or go to the following link\": \"或前往以下連結\",\n\t\"or visit: \": \"或瀏覽：\",\n\t\"reference\": \"參考\",\n\t\"syncing\": \"同步中\",\n\t\"to seamlessly explore code across all branches in your GitHub repositories, maximizing your code discovery capabilities.\": \"以在您的 GitHub 儲存庫中的所有分支之間無縫地探索程式碼，最大限度地提高您的程式碼探索能力。\",\n\t\"uses left_one\": \"剩餘使用次數\",\n\t\"uses left_other\": \"剩餘使用次數\",\n\t\"{{count}} context files used\": \"已使用 {{count}} 個環境檔案\",\n\t\"{{count}} context files used_one\": \"已使用 {{count}} 個環境檔案\",\n\t\"{{count}} context files used_other\": \"已使用 {{count}} 個環境檔案\",\n\t\"{{repoName}} has finished indexing and you can use it in your projects now.\": \"{{repoName}} 已完成索引，您現在可以在您的專案中使用它。\",\n\t\"{{repoName}} is currently indexing as soon as it is finished it will be added to your project.\": \"{{repoName}} 目前正在建立索引，完成後將會新增至您的專案。\",\n\t\"Hide search steps\": \"隱藏搜尋步驟\",\n\t\"Add documentation\": \"新增文件\",\n\t\"<0>{{repoName}}</0> has finished indexing and can be added to your projects. Click the button to below to add it to the current project.\": \"<0>{{repoName}}</0> 已完成索引，可以新增至您的專案。點選下方按鈕將其新增至目前專案。\",\n\t\"Start by selecting again and pressing Enter (↵) on your keyboard.\": \"請重新選取並按下鍵盤上的 Enter (↵) 鍵開始。\",\n\t\"{{repoName}} is currently indexing as soon as it is finished you will be able to add it to your project.\": \"{{repoName}} 目前正在建立索引，完成後您就可以將其新增至您的專案。\",\n\t\"Select the input type to use in conversations\": \"選取要在對話中使用的輸入類型\",\n\t\"Conversation input\": \"對話輸入\",\n\t\"Default\": \"預設\",\n\t\"Simplified\": \"簡化\",\n\t\"Recommended: The classic input\": \"建議：傳統輸入\",\n\t\"Fallback: Use if experiencing problems with the default one\": \"備用：如果預設輸入類型有問題，請使用此選項\",\n\t\"Add multiple files\": \"新增多個檔案\",\n\t\"Explain lines {{lineStart}} - {{lineEnd}} in {{filePath}}\": \"解釋 {{filePath}} 中的第 {{lineStart}} - {{lineEnd}} 行\"\n}\n"
  },
  {
    "path": "client/src/main.tsx",
    "content": "import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport CloudApp from './CloudApp';\n\nReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(\n  <React.StrictMode>\n    <CloudApp />\n  </React.StrictMode>,\n);\n"
  },
  {
    "path": "client/src/mappers/conversation.ts",
    "content": "import flatten from 'lodash.flatten';\nimport { ConversationExchangeType, SearchStepType } from '../types/api';\nimport {\n  ChatLoadingStep,\n  ParsedQueryType,\n  ParsedQueryTypeEnum,\n} from '../types/general';\n\nexport const mapLoadingSteps = (\n  searchSteps: SearchStepType[],\n  t: (key: string) => string,\n) => {\n  const arr: (ChatLoadingStep | ChatLoadingStep[])[] = searchSteps.map((s) => {\n    if (s.type === 'proc') {\n      return s.content.paths.map((pa) => ({\n        ...s,\n        path: pa.path || '',\n        repo: pa.repo,\n        displayText:\n          t(`Reading`) +\n          ' ' +\n          `${pa?.path?.length > 20 ? '...' : ''}${pa?.path?.slice(-20)}`,\n      }));\n    }\n    return {\n      ...s,\n      path: s.content.query,\n      displayText: s.content.query,\n    };\n  });\n  return flatten(arr);\n};\n\nconst mapQueryParts = (query: ConversationExchangeType['query']) => {\n  const array: {\n    type: ParsedQueryTypeEnum;\n    start: number;\n    end: number;\n    text: string;\n  }[] = [];\n  (\n    [\n      'paths',\n      'langs',\n      'repos',\n      'branch',\n    ] as (keyof ConversationExchangeType['query'])[]\n  ).forEach((key) => {\n    array.push(\n      // @ts-ignore\n      ...query[key].map((s) => ({\n        type: key === 'branch' ? key : key.slice(0, -1),\n        start: s.Plain.start,\n        end: s.Plain.end,\n        text: s.Plain.content,\n      })),\n    );\n  });\n  return array;\n};\n\nexport const mapUserQuery = (\n  m: ConversationExchangeType,\n): ParsedQueryType[] => {\n  const parsedQuery = [];\n  const parts = mapQueryParts(m.query).sort((a, b) => a.start - b.start);\n  let currentIndex = 0;\n  const originalString = m.query.raw_query;\n\n  for (const item of parts) {\n    if (currentIndex < item.start) {\n      const textBefore = originalString.substring(\n        currentIndex,\n        item.start - item.type.length - 1,\n      );\n      parsedQuery.push({ type: ParsedQueryTypeEnum.TEXT, text: textBefore });\n      currentIndex = item.start - item.type.length - 1;\n    }\n\n    parsedQuery.push({ type: item.type, text: item.text });\n    currentIndex = item.end;\n  }\n\n  if (currentIndex < originalString.length) {\n    const textAfter = originalString.substring(currentIndex);\n    parsedQuery.push({ type: ParsedQueryTypeEnum.TEXT, text: textAfter });\n  }\n\n  return parsedQuery;\n};\n"
  },
  {
    "path": "client/src/mappers/results.ts",
    "content": "import {\n  CodeItem,\n  File,\n  FileItem,\n  FileResItem,\n  RangeLine,\n  RefDefDataItem,\n  RepoItem,\n  SuggestionsResponse,\n  TokenInfoResponse,\n} from '../types/api';\nimport {\n  CodeResult,\n  FileResult,\n  Range,\n  RepoResult,\n  ResultItemType,\n  ResultType,\n} from '../types/results';\n\nconst mapRepoResults = (item: RepoItem, id: number): RepoResult => {\n  return {\n    type: ResultItemType.REPO,\n    id,\n    branches: 0,\n    files: 0,\n    highlights: item.data.name.highlights,\n    repoName: item.data.name.text,\n    repoRef: item.data.repo_ref,\n  };\n};\n\nconst mapCodeResults = (item: CodeItem, id: number): CodeResult => {\n  return {\n    type: ResultItemType.CODE,\n    snippets: item.data.snippets.map((snippet) => ({\n      code: snippet.data,\n      lineStart: snippet.line_range.start,\n      highlights: snippet.highlights.map((highlight) => ({\n        start: highlight.start,\n        end: highlight.end,\n      })),\n      symbols: snippet.symbols.map((symbol) => ({\n        kind: symbol.kind,\n        line: symbol.range.start.line,\n      })),\n    })),\n    language: item.data.lang,\n    relativePath: item.data.relative_path,\n    repoRef: item.data.repo_ref,\n    id,\n    repoName: item.data.repo_name,\n  };\n};\n\nconst mapFileResults = (item: FileResItem, id: number): FileResult => {\n  return {\n    relativePath: item.data.relative_path.text,\n    type: ResultItemType.FILE,\n    lines: 0,\n    repoRef: item.data.repo_ref,\n    id,\n    language: item.data.lang,\n    highlights: item.data.relative_path.highlights,\n    repoName: item.data.repo_name,\n  };\n};\n\nexport const mapResults = (data: SuggestionsResponse): ResultType[] => {\n  if (data.count === 0) {\n    return [];\n  }\n\n  return data.data\n    .map((item, id) => {\n      switch (item.kind) {\n        case 'snippets':\n          return mapCodeResults(item, id);\n        case 'file_result':\n          return mapFileResults(item, id);\n        case 'repository_result':\n          return mapRepoResults(item, id);\n        case 'flag':\n          return { type: ResultItemType.FLAG, ...item };\n        case 'lang':\n          return { type: ResultItemType.LANG, ...item };\n      }\n    })\n    .filter(Boolean) as ResultType[];\n};\n\nexport const mapRanges = (\n  data: {\n    start: RangeLine;\n    end: RangeLine;\n  }[],\n): Record<number, Range[]> => {\n  const res: Record<number, Range[]> = {};\n  data.forEach((item) => {\n    if (!res[item.start.line]) {\n      res[item.start.line] = [];\n    }\n    res[item.start.line].push({ start: item.start.byte, end: item.end.byte });\n  });\n  return res;\n};\n\nexport const mapTokenInfo = (\n  tokenInfo: TokenInfoResponse['data'],\n  path: string,\n) => {\n  const map: {\n    references: Record<string, any>;\n    definitions: Record<string, any>;\n  } = {\n    references: [],\n    definitions: [],\n  };\n  const mapItem = (td: RefDefDataItem) => {\n    const trimmed = td.snippet.data.trimStart();\n    const lengthDiff = td.snippet.data.length - trimmed.length;\n    return {\n      ...td,\n      snippet: {\n        ...td.snippet,\n        data: trimmed,\n        highlights: td.snippet.highlights.map((h) => {\n          return { start: h.start - lengthDiff, end: h.end - lengthDiff };\n        }),\n        tokenRange: td.snippet.highlights[0],\n      },\n    };\n  };\n  tokenInfo.forEach((t) => {\n    const references = t.data.filter((d) => d.kind === 'reference');\n    if (references.length) {\n      map.references[t.file] = [\n        ...(map.references[t.file] || []),\n        ...references.map(mapItem),\n      ];\n    }\n    const definitions = t.data.filter((d) => d.kind === 'definition');\n    if (definitions.length) {\n      map.definitions[t.file] = [\n        ...(map.definitions[t.file] || []),\n        ...definitions.map(mapItem),\n      ];\n    }\n  });\n\n  const arrayFromObject = (obj: Record<string, any>) =>\n    Object.entries(obj)\n      .map(([file, data]) => ({\n        file,\n        data,\n      }))\n      .sort((a, b) => (a.file === path ? -1 : b.file === path ? 1 : 0));\n\n  return {\n    references: arrayFromObject(map.references),\n    definitions: arrayFromObject(map.definitions),\n  };\n};\n"
  },
  {
    "path": "client/src/services/api.ts",
    "content": "import axios, { AxiosInstance, GenericAbortSignal } from 'axios';\nimport {\n  AllConversationsResponse,\n  CodeStudioType,\n  ConversationExchangeType,\n  ConversationType,\n  Directory,\n  DocPageType,\n  DocSectionType,\n  DocShortType,\n  FileResponse,\n  GeneratedCodeDiff,\n  HistoryConversationTurn,\n  HoverablesResponse,\n  NLSearchResponse,\n  ProjectShortType,\n  SearchResponse,\n  StudioTemplateType,\n  SuggestionsResponse,\n  TokenInfoResponse,\n  TutorialQuestionType,\n} from '../types/api';\nimport { RepoType } from '../types/general';\n\nexport const API_BASE_URL = 'http://localhost:7878/api';\nconst http: AxiosInstance = axios.create({\n  baseURL: API_BASE_URL,\n});\n\nexport const search = (\n  projectId: string,\n  q: string,\n  page: number = 0,\n  page_size: number = 5,\n  global_regex: boolean = false,\n): Promise<SearchResponse> => {\n  return http\n    .get(`/projects/${projectId}/q`, {\n      params: {\n        q: `${q} global_regex:${global_regex}`,\n        page_size,\n        page,\n        calculate_totals: page === 0,\n      },\n    })\n    .then((r) => r.data);\n};\n\nexport const searchFiles = (\n  q: string,\n  repo_ref: string,\n  page_size: number = 100,\n): Promise<SearchResponse> => {\n  return http\n    .get('/search/path', {\n      params: {\n        q,\n        repo_ref,\n        page_size,\n      },\n    })\n    .then((r) => r.data);\n};\n\nexport const getFileContent = (\n  repo_ref: string,\n  path: string,\n  branch?: string | null,\n): Promise<FileResponse> => {\n  return http\n    .get(`/file`, {\n      params: {\n        repo_ref,\n        path,\n        ...(branch ? { branch } : {}),\n      },\n    })\n    .then((r) => r.data);\n};\n\nexport const getFolderContent = (\n  repo_ref: string,\n  path?: string,\n  branch?: string | null,\n): Promise<Directory> => {\n  return http\n    .get(`/folder`, {\n      params: {\n        repo_ref,\n        path: path || '',\n        ...(branch ? { branch } : {}),\n      },\n    })\n    .then((r) => r.data);\n};\n\nexport const nlSearch = (\n  q: string,\n  user_id: string,\n): Promise<NLSearchResponse> => {\n  return http\n    .get('/answer', {\n      params: {\n        q,\n        user_id,\n      },\n    })\n    .then((r) => r.data);\n};\n\nexport const getHoverables = async (\n  relative_path: string,\n  repo_ref: string,\n  branch?: string | null,\n): Promise<HoverablesResponse> => {\n  try {\n    const { data } = await http.get('/hoverable', {\n      params: {\n        relative_path,\n        repo_ref,\n        ...(branch ? { branch } : {}),\n      },\n    });\n    return data;\n  } catch (e) {\n    return { ranges: [] };\n  }\n};\n\nexport const getTokenInfo = async (\n  path: string,\n  repoRef: string,\n  start: number,\n  end: number,\n  branch?: string,\n): Promise<TokenInfoResponse> => {\n  return http\n    .get('/token-info', {\n      params: {\n        relative_path: path,\n        repo_ref: repoRef,\n        start,\n        end,\n        branch,\n      },\n    })\n    .then((r) => r.data);\n};\n\nexport const indexRepoBranch = async (repoRef: string, branch: string) => {\n  return http.patch(\n    '/repos/indexed',\n    { branch_filter: { select: [branch] } },\n    { params: { repo: repoRef } },\n  );\n};\n\nexport const getAutocomplete = async (\n  projectId: string,\n  q: string,\n): Promise<SuggestionsResponse> => {\n  return http\n    .get(`/projects/${projectId}/autocomplete?q=${q}`)\n    .then((r) => r.data);\n};\n\nexport const getRepos = (): Promise<{ list: RepoType[] }> =>\n  http.get('/repos').then((r) => r.data);\nexport const getIndexedRepos = (): Promise<{ list: RepoType[] }> =>\n  http.get('/repos/indexed').then((r) => r.data);\n\nconst localScanCache: Record<string, any> = {};\nexport const scanLocalRepos = (path: string) => {\n  if (localScanCache[path]) {\n    return Promise.resolve(localScanCache[path]);\n  }\n  return http.get(`/repos/scan`, { params: { path } }).then((r) => {\n    localScanCache[path] = r.data;\n    setTimeout(\n      () => {\n        delete localScanCache[path];\n      },\n      1000 * 60 * 10,\n    ); // 10 minutes\n    return r.data;\n  });\n};\n\nexport const deleteRepo = (repoRef: string) =>\n  http\n    .delete(`/repos/indexed`, { params: { repo: repoRef } })\n    .then((r) => r.data);\n\nexport const cancelSync = (repoRef: string) =>\n  http.delete(`/repos/sync`, { params: { repo: repoRef } }).then((r) => r.data);\n\nexport const syncRepo = (repoRef: string) =>\n  http.get(`/repos/sync`, { params: { repo: repoRef } }).then((r) => r.data);\n\nexport const getProjectConversations = (\n  projectId: string,\n): Promise<AllConversationsResponse> =>\n  http.get(`/projects/${projectId}/conversations`).then((r) => r.data);\n\nexport const getConversation = (\n  projectId: string,\n  conversationId: string,\n): Promise<ConversationType> =>\n  http\n    .get(`/projects/${projectId}/conversations/${conversationId}`)\n    .then((r) => r.data);\n\nexport const deleteConversation = (\n  projectId: string,\n  conversationId: string,\n): Promise<void> =>\n  http\n    .delete(`/projects/${projectId}/conversations/${conversationId}`)\n    .then((r) => r.data);\n\nexport const upvoteAnswer = (\n  projectId: string,\n  thread_id: string,\n  query_id: string,\n  feedback: { type: 'positive' } | { type: 'negative'; feedback: string },\n): Promise<ConversationExchangeType> =>\n  http\n    .post(`/projects/${projectId}/answer/vote`, {\n      thread_id,\n      query_id,\n      feedback,\n    })\n    .then((r) => r.data);\n\nexport const getIndexQueue = () => http('/repos/queue').then((r) => r.data);\n\nexport const getCodeStudios = (projectId: string): Promise<CodeStudioType[]> =>\n  http(`/projects/${projectId}/studios`).then((r) => r.data);\nexport const patchCodeStudio = (\n  projectId: string,\n  id: string,\n  data: Partial<CodeStudioType>,\n) =>\n  http.patch(`/projects/${projectId}/studios/${id}`, data).then((r) => r.data);\nexport const getCodeStudio = (\n  projectId: string,\n  id: string,\n): Promise<CodeStudioType> =>\n  http(`/projects/${projectId}/studios/${id}`).then((r) => r.data);\nexport const getCodeStudioHistory = (\n  projectId: string,\n  id: string,\n): Promise<HistoryConversationTurn[]> =>\n  http(`/projects/${projectId}/studios/${id}/snapshots`).then((r) => r.data);\nexport const deleteCodeStudio = (\n  projectId: string,\n  id: string,\n): Promise<CodeStudioType> =>\n  http.delete(`/projects/${projectId}/studios/${id}`).then((r) => r.data);\nexport const postCodeStudio = (projectId: string) =>\n  http.post(`/projects/${projectId}/studios`, {}).then((r) => r.data);\nexport const importCodeStudio = (\n  projectId: string,\n  thread_id: string,\n  studio_id?: string,\n) =>\n  http\n    .post(\n      `/projects/${projectId}/studios/import`,\n      {},\n      { params: { thread_id, studio_id } },\n    )\n    .then((r) => r.data);\nexport const generateStudioDiff = (\n  projectId: string,\n  id: string,\n  abortSignal?: GenericAbortSignal,\n): Promise<GeneratedCodeDiff> =>\n  http(`/projects/${projectId}/studios/${id}/diff`, {\n    timeout: 10 * 60 * 1000,\n    signal: abortSignal,\n  }).then((r) => r.data);\nexport const confirmStudioDiff = (\n  projectId: string,\n  id: string,\n  diff: string,\n): Promise<void> =>\n  http\n    .post(`/projects/${projectId}/studios/${id}/diff/apply`, diff, {\n      headers: { 'Content-Type': 'text/plain' },\n    })\n    .then((r) => r.data);\n\nexport const getFileTokenCount = (\n  projectId: string,\n  path: string,\n  repo: string,\n  branch?: string,\n  ranges?: [number, number][],\n): Promise<number> =>\n  http\n    .post(`/projects/${projectId}/studios/file-token-count`, {\n      path,\n      repo,\n      branch,\n      ranges,\n    })\n    .then((r) => r.data);\nexport const getDocTokenCount = (\n  projectId: string,\n  doc_id: string,\n  relative_url: string,\n  ranges?: string[],\n): Promise<number> =>\n  http\n    .post(`/projects/${projectId}/studios/doc-file-token-count`, {\n      doc_id,\n      relative_url,\n      ranges,\n    })\n    .then((r) => r.data);\n\nexport const getRelatedFiles = (\n  relative_path: string,\n  repo_ref: string,\n  branch?: string,\n): Promise<{ files_importing: string[]; files_imported: string[] }> =>\n  http(`/related-files`, { params: { relative_path, repo_ref, branch } }).then(\n    (r) => r.data,\n  );\n\nexport const getRelatedFileRanges = (\n  repo_ref: string,\n  branch: string | undefined,\n  source_file_path: string,\n  related_file_path: string,\n  kind: 'Imported' | 'Importing',\n): Promise<{ ranges: { start: { line: number }; end: { line: number } }[] }> =>\n  http(`/related-files-with-ranges`, {\n    params: { source_file_path, repo_ref, branch, related_file_path, kind },\n  }).then((r) => r.data);\n\nexport const getTemplates = (): Promise<StudioTemplateType[]> =>\n  http('/template').then((r) => r.data);\nexport const patchTemplate = (\n  id: string,\n  data: {\n    name?: string;\n    content?: string;\n  },\n) => http.patch(`/template/${id}`, data).then((r) => r.data);\nexport const deleteTemplate = (id: string): Promise<StudioTemplateType> =>\n  http.delete(`/template/${id}`).then((r) => r.data);\nexport const postTemplate = (name: string, content: string) =>\n  http.post('/template', { name, content }).then((r) => r.data);\n\nexport const forceFileToBeIndexed = (repoRef: string, filePath: string) =>\n  http.patch(\n    '/repos/indexed',\n    { file_filter: { rules: [{ include_file: filePath }] } },\n    { params: { repo: repoRef } },\n  );\n\nexport const getTutorialQuestions = (\n  repo_ref: string,\n): Promise<{ questions: TutorialQuestionType[] }> =>\n  http('/tutorial-questions', { params: { repo_ref } }).then((r) => r.data);\n\nexport const getProjectDocs = (projectId: string): Promise<DocShortType[]> =>\n  http(`projects/${projectId}/docs`).then((r) => r.data);\nexport const addDocToProject = (projectId: string, doc_id: string) =>\n  http.post(`projects/${projectId}/docs`, { doc_id }).then((r) => r.data);\nexport const removeDocFromProject = (projectId: string, doc_id: string) =>\n  http.delete(`projects/${projectId}/docs/${doc_id}`).then((r) => r.data);\nexport const indexDocsUrl = (url: string): Promise<string> =>\n  http('/docs/enqueue', { params: { url } }).then((r) => r.data);\nexport const cancelDocIndexing = (docId: string) =>\n  http(`/${docId}/cancel`).then((r) => r.data);\nexport const resyncDoc = (docId: string) =>\n  http(`/${docId}/resync`).then((r) => r.data);\nexport const verifyDocsUrl = (url: string) =>\n  http('/docs/verify', { params: { url } }).then((r) => r.data);\nexport const getIndexedDocs = (): Promise<DocShortType[]> =>\n  http('/docs').then((r) => r.data);\nexport const getDocById = (id: string): Promise<DocShortType> =>\n  http(`/docs/${id}`).then((r) => r.data);\nexport const getIndexedPages = (id: number | string): Promise<DocPageType[]> =>\n  http(`/docs/${id}/list`, { params: { limit: 100 } }).then((r) => r.data);\nexport const deleteDocProvider = (\n  id: number | string,\n): Promise<DocPageType[]> => http.delete(`/docs/${id}`).then((r) => r.data);\nexport const searchDocSections = (\n  id: string,\n  q: string,\n): Promise<DocSectionType[]> =>\n  http(`/docs/${id}/search`, { params: { q, limit: 20 } }).then((r) => r.data);\nexport const getDocSections = (\n  id: number | string,\n  url: string,\n): Promise<DocSectionType[]> =>\n  http(`/docs/${id}/fetch`, {\n    params: { relative_url: url },\n  }).then((r) => r.data);\n\nexport const getAllProjects = (): Promise<ProjectShortType[]> =>\n  http('/projects').then((r) => r.data);\nexport const getProject = (id: string): Promise<ProjectShortType> =>\n  http(`/projects/${id}`).then((r) => r.data);\nexport const createProject = (name: string): Promise<string> =>\n  http.post(`/projects`, { name }).then((r) => r.data);\nexport const updateProject = (id: string, data: Partial<ProjectShortType>) =>\n  http.put(`/projects/${id}`, data).then((r) => r.data);\nexport const deleteProject = (id: string) =>\n  http.delete(`/projects/${id}`).then((r) => r.data);\nexport const getProjectRepos = (\n  id: string,\n): Promise<{ repo: RepoType; branch: string }[]> =>\n  http(`/projects/${id}/repos`).then((r) => r.data);\nexport const addRepoToProject = (\n  id: string,\n  repoRef: string,\n  branch?: string,\n) =>\n  http\n    .post(`/projects/${id}/repos`, { ref: repoRef, branch })\n    .then((r) => r.data);\nexport const removeRepoFromProject = (id: string, repoRef: string) =>\n  http\n    .delete(`/projects/${id}/repos/`, { params: { ref: repoRef } })\n    .then((r) => r.data);\nexport const changeRepoBranch = (\n  id: string,\n  repoRef: string,\n  branch?: string | null,\n) =>\n  http\n    .put(`/projects/${id}/repos/`, { ref: repoRef, branch: branch || '' })\n    .then((r) => r.data);\n"
  },
  {
    "path": "client/src/services/cache.ts",
    "content": "import { TabType } from '../types/general';\n\nexport const conversationsCache: Record<string, any> = {};\n\nexport const repositoriesSyncCache = {\n  shouldNotifyWhenDone: false,\n};\n\nexport const openTabsCache: { tabs: TabType[] } = {\n  tabs: [],\n};\n"
  },
  {
    "path": "client/src/services/storage.ts",
    "content": "export const savePlainToStorage = (key: string, value: any) => {\n  window.localStorage.setItem(key, value.toString());\n};\n\nexport const getPlainFromStorage = (key: string) => {\n  return window.localStorage.getItem(key);\n};\n\nexport const saveJsonToStorage = (key: string, value: any) =>\n  window.localStorage.setItem(key, JSON.stringify(value));\n\nexport const getJsonFromStorage = <T>(key: string): T | null => {\n  try {\n    return JSON.parse(window.localStorage.getItem(key)!);\n  } catch (e) {\n    return null;\n  }\n};\nexport const saveArrayToStorage = (\n  key: string,\n  value: string[],\n  maxSize: number,\n) => saveJsonToStorage(key, value.slice(-maxSize));\n\nexport const updateArrayInStorage = (key: string, newValue: string) => {\n  const oldValues = getJsonFromStorage<string[]>(key) || [];\n  const newValues = [...oldValues.filter((v) => v !== newValue), newValue];\n  saveArrayToStorage(key, newValues, 10);\n};\n\nexport const ONBOARDING_DONE_KEY = 'onboarding_done';\nexport const USER_DATA_FORM = 'user_data_form';\nexport const THEME = 'theme';\nexport const STUDIO_GUIDE_DONE = 'studio_guide_done';\nexport const LANGUAGE_KEY = 'language';\nexport const RIGHT_SIDEBAR_WIDTH_KEY = 'right_sidebar_width';\nexport const LEFT_SIDEBAR_WIDTH_KEY = 'left_nav_width_key';\nexport const LOADING_STEPS_SHOWN_KEY = 'loading_steps_shown';\nexport const ANSWER_SPEED_KEY = 'answer_speed_key';\nexport const ACCESS_TOKEN_KEY = 'access_token';\nexport const REFRESH_TOKEN_KEY = 'refresh_token';\nexport const USER_FONT_SIZE_KEY = 'user_font_size';\nexport const PROJECT_KEY = 'project';\nexport const RECENT_COMMANDS_KEY = 'recent_commands';\nexport const RECENT_FILES_KEY = 'recent_files';\nexport const CHAT_INPUT_TYPE_KEY = 'chat_input_type';\n"
  },
  {
    "path": "client/src/themes/default-dark.css",
    "content": "[data-theme=\"dark\"] {\n    /********************************************************* * Tokens */\n    /********************************************************* * Language Specific */\n    /********************************************************* * Line highlighting */\n}\n[data-theme=\"dark\"] pre[class*=\"language-\"], [data-theme=\"dark\"] code[class*=\"language-\"],\n[data-theme=\"black\"] pre[class*=\"language-\"], [data-theme=\"black\"] code[class*=\"language-\"] {\n    color: #d4d4d4;\n    text-shadow: none;\n    font-family: \"Fira Code\", Menlo, Monaco, Consolas, \"Andale Mono\", \"Ubuntu Mono\", \"Courier New\", monospace;\n    direction: ltr;\n    text-align: left;\n    white-space: pre;\n    word-spacing: normal;\n    word-break: normal;\n    line-height: 1.5;\n    -moz-tab-size: 4;\n    -o-tab-size: 4;\n    tab-size: 4;\n    -webkit-hyphens: none;\n    -moz-hyphens: none;\n    -ms-hyphens: none;\n    hyphens: none;\n}\n[data-theme=\"dark\"] pre[class*=\"language-\"]::selection,\n[data-theme=\"dark\"] code[class*=\"language-\"]::selection,\n[data-theme=\"dark\"] pre[class*=\"language-\"] *::selection,\n[data-theme=\"dark\"] code[class*=\"language-\"] *::selection,\n[data-theme=\"black\"] pre[class*=\"language-\"]::selection,\n[data-theme=\"black\"] code[class*=\"language-\"]::selection,\n[data-theme=\"black\"] pre[class*=\"language-\"] *::selection,\n[data-theme=\"black\"] code[class*=\"language-\"] *::selection {\n    text-shadow: none;\n    background: #264f78;\n}\n@media print {\n    [data-theme=\"dark\"] pre[class*=\"language-\"], [data-theme=\"dark\"] code[class*=\"language-\"],\n    [data-theme=\"black\"] pre[class*=\"language-\"], [data-theme=\"black\"] code[class*=\"language-\"] {\n        text-shadow: none;\n    }\n}\n[data-theme=\"dark\"] pre[class*=\"language-\"], [data-theme=\"black\"] pre[class*=\"language-\"] {\n    /*padding: 1em;\n    */\n    /*margin: .5em 0;\n    */\n    /*overflow: auto;\n    */\n    background: transparent;\n}\n[data-theme=\"dark\"] :not(pre) > code[class*=\"language-\"] , [data-theme=\"black\"] :not(pre) > code[class*=\"language-\"] {\n    padding: 0.1em 0.3em;\n    border-radius: 0.3em;\n    color: #db4c69;\n    /*background: #1e1e1e;\n    */\n}\n[data-theme=\"dark\"] .namespace , [data-theme=\"black\"] .namespace {\n    opacity: 0.7;\n}\n[data-theme=\"dark\"] .token.doctype .token.doctype-tag , [data-theme=\"black\"] .token.doctype .token.doctype-tag {\n    color: #569cd6;\n}\n[data-theme=\"dark\"] .token.doctype .token.name , [data-theme=\"black\"] .token.doctype .token.name {\n    color: #9cdcfe;\n}\n[data-theme=\"dark\"] .token.comment, [data-theme=\"black\"] .token.comment, [data-theme=\"dark\"] .token.prolog , [data-theme=\"black\"] .token.prolog {\n    color: #6a9955;\n}\n[data-theme=\"dark\"] .token.property, [data-theme=\"black\"] .token.property, [data-theme=\"dark\"] .token.tag, [data-theme=\"black\"] .token.tag, [data-theme=\"dark\"] .token.boolean, [data-theme=\"black\"] .token.boolean, [data-theme=\"dark\"] .token.number, [data-theme=\"black\"] .token.number, [data-theme=\"dark\"] .token.constant, [data-theme=\"black\"] .token.constant, [data-theme=\"dark\"] .token.symbol, [data-theme=\"black\"] .token.symbol, [data-theme=\"dark\"] .token.inserted, [data-theme=\"black\"] .token.inserted, [data-theme=\"dark\"] .token.unit , [data-theme=\"black\"] .token.unit {\n    color: #b5cea8;\n}\n[data-theme=\"dark\"] .token.tag , [data-theme=\"black\"] .token.tag {\n    color: #569cd6;\n}\n[data-theme=\"dark\"] .token.punctuation, [data-theme=\"black\"] .token.punctuation, [data-theme=\"dark\"] .language-html .language-css .token.punctuation, [data-theme=\"black\"] .language-html .language-css .token.punctuation, [data-theme=\"dark\"] .language-html .language-javascript .token.punctuation , [data-theme=\"black\"] .language-html .language-javascript .token.punctuation {\n    color: #d4d4d4;\n}\n[data-theme=\"dark\"] .token.selector, [data-theme=\"black\"] .token.selector, [data-theme=\"dark\"] .token.attr-name, [data-theme=\"black\"] .token.attr-name, [data-theme=\"dark\"] .token.string, [data-theme=\"black\"] .token.string, [data-theme=\"dark\"] .token.char, [data-theme=\"black\"] .token.char, [data-theme=\"dark\"] .token.builtin, [data-theme=\"black\"] .token.builtin, [data-theme=\"dark\"] .token.deleted , [data-theme=\"black\"] .token.deleted {\n    color: #ce9178;\n}\n[data-theme=\"dark\"] .language-css .token.string.url , [data-theme=\"black\"] .language-css .token.string.url {\n    text-decoration: underline;\n}\n[data-theme=\"dark\"] .token.operator, [data-theme=\"black\"] .token.operator, [data-theme=\"dark\"] .token.entity , [data-theme=\"black\"] .token.entity {\n    color: #d4d4d4;\n}\n[data-theme=\"dark\"] .token.operator.arrow , [data-theme=\"black\"] .token.operator.arrow {\n    color: #569cd6;\n}\n[data-theme=\"dark\"] .token.atrule , [data-theme=\"black\"] .token.atrule {\n    color: #ce9178;\n}\n[data-theme=\"dark\"] .token.atrule .token.rule , [data-theme=\"black\"] .token.atrule .token.rule {\n    color: #c586c0;\n}\n[data-theme=\"dark\"] .token.atrule .token.url , [data-theme=\"black\"] .token.atrule .token.url {\n    color: #9cdcfe;\n}\n[data-theme=\"dark\"] .token.atrule .token.url .token.function , [data-theme=\"black\"] .token.atrule .token.url .token.function {\n    color: #dcdcaa;\n}\n[data-theme=\"dark\"] .token.atrule .token.url .token.punctuation , [data-theme=\"black\"] .token.atrule .token.url .token.punctuation {\n    color: #d4d4d4;\n}\n[data-theme=\"dark\"] .token.keyword , [data-theme=\"black\"] .token.keyword {\n    color: #569cd6;\n}\n[data-theme=\"dark\"] .token.keyword.module, [data-theme=\"black\"] .token.keyword.module, [data-theme=\"dark\"] .token.keyword.control-flow , [data-theme=\"black\"] .token.keyword.control-flow {\n    color: #c586c0;\n}\n[data-theme=\"dark\"] .token.function, [data-theme=\"black\"] .token.function, [data-theme=\"dark\"] .token.function .token.maybe-class-name , [data-theme=\"black\"] .token.function .token.maybe-class-name {\n    color: #dcdcaa;\n}\n[data-theme=\"dark\"] .token.regex , [data-theme=\"black\"] .token.regex {\n    color: #d16969;\n}\n[data-theme=\"dark\"] .token.important , [data-theme=\"black\"] .token.important {\n    color: #569cd6;\n}\n[data-theme=\"dark\"] .token.italic , [data-theme=\"black\"] .token.italic {\n    font-style: italic;\n}\n[data-theme=\"dark\"] .token.constant , [data-theme=\"black\"] .token.constant {\n    color: #9cdcfe;\n}\n[data-theme=\"dark\"] .token.class-name, [data-theme=\"black\"] .token.class-name, [data-theme=\"dark\"] .token.maybe-class-name , [data-theme=\"black\"] .token.maybe-class-name {\n    color: #4ec9b0;\n}\n[data-theme=\"dark\"] .token.console , [data-theme=\"black\"] .token.console {\n    color: #9cdcfe;\n}\n[data-theme=\"dark\"] .token.parameter , [data-theme=\"black\"] .token.parameter {\n    color: #9cdcfe;\n}\n[data-theme=\"dark\"] .token.interpolation , [data-theme=\"black\"] .token.interpolation {\n    color: #9cdcfe;\n}\n[data-theme=\"dark\"] .token.punctuation.interpolation-punctuation , [data-theme=\"black\"] .token.punctuation.interpolation-punctuation {\n    color: #569cd6;\n}\n[data-theme=\"dark\"] .token.boolean , [data-theme=\"black\"] .token.boolean {\n    color: #569cd6;\n}\n[data-theme=\"dark\"] .token.property, [data-theme=\"black\"] .token.property, [data-theme=\"dark\"] .token.variable, [data-theme=\"black\"] .token.variable, [data-theme=\"dark\"] .token.imports .token.maybe-class-name, [data-theme=\"black\"] .token.imports .token.maybe-class-name, [data-theme=\"dark\"] .token.exports .token.maybe-class-name , [data-theme=\"black\"] .token.exports .token.maybe-class-name {\n    color: #9cdcfe;\n}\n[data-theme=\"dark\"] .token.selector , [data-theme=\"black\"] .token.selector {\n    color: #d7ba7d;\n}\n[data-theme=\"dark\"] .token.escape , [data-theme=\"black\"] .token.escape {\n    color: #d7ba7d;\n}\n[data-theme=\"dark\"] .token.tag .token.punctuation , [data-theme=\"black\"] .token.tag .token.punctuation {\n    color: #808080;\n}\n[data-theme=\"dark\"] .token.cdata , [data-theme=\"black\"] .token.cdata {\n    color: #808080;\n}\n[data-theme=\"dark\"] .token.attr-name , [data-theme=\"black\"] .token.attr-name {\n    color: #9cdcfe;\n}\n[data-theme=\"dark\"] .token.attr-value, [data-theme=\"black\"] .token.attr-value, [data-theme=\"dark\"] .token.attr-value .token.punctuation , [data-theme=\"black\"] .token.attr-value .token.punctuation {\n    color: #ce9178;\n}\n[data-theme=\"dark\"] .token.attr-value .token.punctuation.attr-equals , [data-theme=\"black\"] .token.attr-value .token.punctuation.attr-equals {\n    color: #d4d4d4;\n}\n[data-theme=\"dark\"] .token.entity , [data-theme=\"black\"] .token.entity {\n    color: #569cd6;\n}\n[data-theme=\"dark\"] .token.namespace , [data-theme=\"black\"] .token.namespace {\n    color: #4ec9b0;\n}\n[data-theme=\"dark\"] pre[class*=\"language-javascript\"], [data-theme=\"black\"] pre[class*=\"language-javascript\"], [data-theme=\"dark\"] code[class*=\"language-javascript\"], [data-theme=\"black\"] code[class*=\"language-javascript\"], [data-theme=\"dark\"] pre[class*=\"language-jsx\"], [data-theme=\"black\"] pre[class*=\"language-jsx\"], [data-theme=\"dark\"] code[class*=\"language-jsx\"], [data-theme=\"black\"] code[class*=\"language-jsx\"], [data-theme=\"dark\"] pre[class*=\"language-typescript\"], [data-theme=\"black\"] pre[class*=\"language-typescript\"], [data-theme=\"dark\"] code[class*=\"language-typescript\"], [data-theme=\"black\"] code[class*=\"language-typescript\"], [data-theme=\"dark\"] pre[class*=\"language-tsx\"], [data-theme=\"black\"] pre[class*=\"language-tsx\"], [data-theme=\"dark\"] code[class*=\"language-tsx\"] , [data-theme=\"black\"] code[class*=\"language-tsx\"] {\n    color: #9cdcfe;\n}\n[data-theme=\"dark\"] pre[class*=\"language-css\"], [data-theme=\"black\"] pre[class*=\"language-css\"], [data-theme=\"dark\"] code[class*=\"language-css\"] , [data-theme=\"black\"] code[class*=\"language-css\"] {\n    color: #ce9178;\n}\n[data-theme=\"dark\"] pre[class*=\"language-html\"], [data-theme=\"black\"] pre[class*=\"language-html\"], [data-theme=\"dark\"] code[class*=\"language-html\"] , [data-theme=\"black\"] code[class*=\"language-html\"] {\n    color: #d4d4d4;\n}\n[data-theme=\"dark\"] .language-regex .token.anchor , [data-theme=\"black\"] .language-regex .token.anchor {\n    color: #dcdcaa;\n}\n[data-theme=\"dark\"] .language-html .token.punctuation , [data-theme=\"black\"] .language-html .token.punctuation {\n    color: #808080;\n}\n[data-theme=\"dark\"] pre[class*=\"language-\"] > code[class*=\"language-\"] , [data-theme=\"black\"] pre[class*=\"language-\"] > code[class*=\"language-\"] {\n    position: relative;\n    z-index: 1;\n}\n[data-theme=\"dark\"] .line-highlight.line-highlight , [data-theme=\"black\"] .line-highlight.line-highlight {\n    background: #f7ebc6;\n    box-shadow: inset 5px 0 0 #f7d87c;\n    z-index: 0;\n}\n\n@media (prefers-color-scheme: dark) {\n    [data-theme=\"system\"] {\n        /********************************************************* * Tokens */\n        /********************************************************* * Language Specific */\n        /********************************************************* * Line highlighting */\n    }\n    [data-theme=\"system\"] pre[class*=\"language-\"], [data-theme=\"system\"] code[class*=\"language-\"] {\n        color: #d4d4d4;\n        text-shadow: none;\n        font-family: \"Fira Code\", Menlo, Monaco, Consolas, \"Andale Mono\", \"Ubuntu Mono\", \"Courier New\", monospace;\n        direction: ltr;\n        text-align: left;\n        white-space: pre;\n        word-spacing: normal;\n        word-break: normal;\n        line-height: 1.5;\n        -moz-tab-size: 4;\n        -o-tab-size: 4;\n        tab-size: 4;\n        -webkit-hyphens: none;\n        -moz-hyphens: none;\n        -ms-hyphens: none;\n        hyphens: none;\n    }\n    [data-theme=\"system\"] pre[class*=\"language-\"]::selection, [data-theme=\"system\"] code[class*=\"language-\"]::selection, [data-theme=\"system\"] pre[class*=\"language-\"] *::selection, [data-theme=\"system\"] code[class*=\"language-\"] *::selection {\n        text-shadow: none;\n        background: #264f78;\n    }\n    @media print {\n        [data-theme=\"system\"] pre[class*=\"language-\"], [data-theme=\"system\"] code[class*=\"language-\"] {\n            text-shadow: none;\n        }\n    }\n    [data-theme=\"system\"] pre[class*=\"language-\"] {\n        /*padding: 1em;\n        */\n        /*margin: .5em 0;\n        */\n        /*overflow: auto;\n        */\n        background: transparent;\n    }\n    [data-theme=\"system\"] :not(pre) > code[class*=\"language-\"] {\n        padding: 0.1em 0.3em;\n        border-radius: 0.3em;\n        color: #db4c69;\n        /*background: #1e1e1e;\n        */\n    }\n    [data-theme=\"system\"] .namespace {\n        opacity: 0.7;\n    }\n    [data-theme=\"system\"] .token.doctype .token.doctype-tag {\n        color: #569cd6;\n    }\n    [data-theme=\"system\"] .token.doctype .token.name {\n        color: #9cdcfe;\n    }\n    [data-theme=\"system\"] .token.comment, [data-theme=\"system\"] .token.prolog {\n        color: #6a9955;\n    }\n    [data-theme=\"system\"] .token.property, [data-theme=\"system\"] .token.tag, [data-theme=\"system\"] .token.boolean, [data-theme=\"system\"] .token.number, [data-theme=\"system\"] .token.constant, [data-theme=\"system\"] .token.symbol, [data-theme=\"system\"] .token.inserted, [data-theme=\"system\"] .token.unit {\n        color: #b5cea8;\n    }\n    [data-theme=\"system\"] .token.tag {\n        color: #569cd6;\n    }\n    [data-theme=\"system\"] .token.punctuation, [data-theme=\"system\"] .language-html .language-css .token.punctuation, [data-theme=\"system\"] .language-html .language-javascript .token.punctuation {\n        color: #d4d4d4;\n    }\n    [data-theme=\"system\"] .token.selector, [data-theme=\"system\"] .token.attr-name, [data-theme=\"system\"] .token.string, [data-theme=\"system\"] .token.char, [data-theme=\"system\"] .token.builtin, [data-theme=\"system\"] .token.deleted {\n        color: #ce9178;\n    }\n    [data-theme=\"system\"] .language-css .token.string.url {\n        text-decoration: underline;\n    }\n    [data-theme=\"system\"] .token.operator, [data-theme=\"system\"] .token.entity {\n        color: #d4d4d4;\n    }\n    [data-theme=\"system\"] .token.operator.arrow {\n        color: #569cd6;\n    }\n    [data-theme=\"system\"] .token.atrule {\n        color: #ce9178;\n    }\n    [data-theme=\"system\"] .token.atrule .token.rule {\n        color: #c586c0;\n    }\n    [data-theme=\"system\"] .token.atrule .token.url {\n        color: #9cdcfe;\n    }\n    [data-theme=\"system\"] .token.atrule .token.url .token.function {\n        color: #dcdcaa;\n    }\n    [data-theme=\"system\"] .token.atrule .token.url .token.punctuation {\n        color: #d4d4d4;\n    }\n    [data-theme=\"system\"] .token.keyword {\n        color: #569cd6;\n    }\n    [data-theme=\"system\"] .token.keyword.module, [data-theme=\"system\"] .token.keyword.control-flow {\n        color: #c586c0;\n    }\n    [data-theme=\"system\"] .token.function, [data-theme=\"system\"] .token.function .token.maybe-class-name {\n        color: #dcdcaa;\n    }\n    [data-theme=\"system\"] .token.regex {\n        color: #d16969;\n    }\n    [data-theme=\"system\"] .token.important {\n        color: #569cd6;\n    }\n    [data-theme=\"system\"] .token.italic {\n        font-style: italic;\n    }\n    [data-theme=\"system\"] .token.constant {\n        color: #9cdcfe;\n    }\n    [data-theme=\"system\"] .token.class-name, [data-theme=\"system\"] .token.maybe-class-name {\n        color: #4ec9b0;\n    }\n    [data-theme=\"system\"] .token.console {\n        color: #9cdcfe;\n    }\n    [data-theme=\"system\"] .token.parameter {\n        color: #9cdcfe;\n    }\n    [data-theme=\"system\"] .token.interpolation {\n        color: #9cdcfe;\n    }\n    [data-theme=\"system\"] .token.punctuation.interpolation-punctuation {\n        color: #569cd6;\n    }\n    [data-theme=\"system\"] .token.boolean {\n        color: #569cd6;\n    }\n    [data-theme=\"system\"] .token.property, [data-theme=\"system\"] .token.variable, [data-theme=\"system\"] .token.imports .token.maybe-class-name, [data-theme=\"system\"] .token.exports .token.maybe-class-name {\n        color: #9cdcfe;\n    }\n    [data-theme=\"system\"] .token.selector {\n        color: #d7ba7d;\n    }\n    [data-theme=\"system\"] .token.escape {\n        color: #d7ba7d;\n    }\n    [data-theme=\"system\"] .token.tag .token.punctuation {\n        color: #808080;\n    }\n    [data-theme=\"system\"] .token.cdata {\n        color: #808080;\n    }\n    [data-theme=\"system\"] .token.attr-name {\n        color: #9cdcfe;\n    }\n    [data-theme=\"system\"] .token.attr-value, [data-theme=\"system\"] .token.attr-value .token.punctuation {\n        color: #ce9178;\n    }\n    [data-theme=\"system\"] .token.attr-value .token.punctuation.attr-equals {\n        color: #d4d4d4;\n    }\n    [data-theme=\"system\"] .token.entity {\n        color: #569cd6;\n    }\n    [data-theme=\"system\"] .token.namespace {\n        color: #4ec9b0;\n    }\n    [data-theme=\"system\"] pre[class*=\"language-javascript\"], [data-theme=\"system\"] code[class*=\"language-javascript\"], [data-theme=\"system\"] pre[class*=\"language-jsx\"], [data-theme=\"system\"] code[class*=\"language-jsx\"], [data-theme=\"system\"] pre[class*=\"language-typescript\"], [data-theme=\"system\"] code[class*=\"language-typescript\"], [data-theme=\"system\"] pre[class*=\"language-tsx\"], [data-theme=\"system\"] code[class*=\"language-tsx\"] {\n        color: #9cdcfe;\n    }\n    [data-theme=\"system\"] pre[class*=\"language-css\"], [data-theme=\"system\"] code[class*=\"language-css\"] {\n        color: #ce9178;\n    }\n    [data-theme=\"system\"] pre[class*=\"language-html\"], [data-theme=\"system\"] code[class*=\"language-html\"] {\n        color: #d4d4d4;\n    }\n    [data-theme=\"system\"] .language-regex .token.anchor {\n        color: #dcdcaa;\n    }\n    [data-theme=\"system\"] .language-html .token.punctuation {\n        color: #808080;\n    }\n    [data-theme=\"system\"] pre[class*=\"language-\"] > code[class*=\"language-\"] {\n        position: relative;\n        z-index: 1;\n    }\n    [data-theme=\"system\"] .line-highlight.line-highlight {\n        background: #f7ebc6;\n        box-shadow: inset 5px 0 0 #f7d87c;\n        z-index: 0;\n    }\n}\n"
  },
  {
    "path": "client/src/themes/default-light.css",
    "content": "/** * VS theme by Andrew Lock (https://andrewlock.net) * Inspired by Visual Studio syntax coloring */\n[data-theme=\"light\"] {\n    /* Code blocks */\n    /* Inline code */\n    /* overrides color-values for the Line Numbers plugin * http://prismjs.com/plugins/line-numbers/ */\n    /* overrides color-values for the Line Highlight plugin * http://prismjs.com/plugins/line-highlight/ */\n}\n[data-theme=\"light\"] code[class*=\"language-\"], [data-theme=\"light\"] pre[class*=\"language-\"] {\n    color: #393a34;\n    font-family: \"Fira Code\", Menlo, Monaco, Consolas, \"Andale Mono\", \"Ubuntu Mono\", \"Courier New\", monospace;\n    direction: ltr;\n    text-align: left;\n    white-space: pre;\n    word-spacing: normal;\n    word-break: normal;\n    -moz-tab-size: 4;\n    -o-tab-size: 4;\n    tab-size: 4;\n    -webkit-hyphens: none;\n    -moz-hyphens: none;\n    -ms-hyphens: none;\n    hyphens: none;\n}\n[data-theme=\"light\"] pre > code[class*=\"language-\"] {\n    font-size: 1em;\n}\n[data-theme=\"light\"] pre[class*=\"language-\"]::-moz-selection, [data-theme=\"light\"] pre[class*=\"language-\"] ::-moz-selection, [data-theme=\"light\"] code[class*=\"language-\"]::-moz-selection, [data-theme=\"light\"] code[class*=\"language-\"] ::-moz-selection {\n    background: #c1def1;\n}\n[data-theme=\"light\"] pre[class*=\"language-\"]::selection, [data-theme=\"light\"] pre[class*=\"language-\"] ::selection, [data-theme=\"light\"] code[class*=\"language-\"]::selection, [data-theme=\"light\"] code[class*=\"language-\"] ::selection {\n    background: #c1def1;\n}\n[data-theme=\"light\"] pre[class*=\"language-\"] {\n    /*padding: 1em;*/\n    /*margin: 0.5em 0;*/\n    background: transparent;\n}\n[data-theme=\"light\"] :not(pre) > code[class*=\"language-\"] {\n    padding: 0.2em;\n    padding-top: 1px;\n    padding-bottom: 1px;\n    background: #f8f8f8;\n    border: 1px solid #ddd;\n}\n[data-theme=\"light\"] .token.comment, [data-theme=\"light\"] .token.prolog, [data-theme=\"light\"] .token.doctype, [data-theme=\"light\"] .token.cdata {\n    color: #008000;\n    font-style: italic;\n}\n[data-theme=\"light\"] .token.namespace {\n    opacity: 0.7;\n}\n[data-theme=\"light\"] .token.string {\n    color: #a31515;\n}\n[data-theme=\"light\"] .token.punctuation, [data-theme=\"light\"] .token.operator {\n    color: #393a34;\n    /* no highlight */\n}\n[data-theme=\"light\"] .token.url, [data-theme=\"light\"] .token.symbol, [data-theme=\"light\"] .token.number, [data-theme=\"light\"] .token.boolean, [data-theme=\"light\"] .token.variable, [data-theme=\"light\"] .token.constant, [data-theme=\"light\"] .token.inserted {\n    color: #36acaa;\n}\n[data-theme=\"light\"] .token.atrule, [data-theme=\"light\"] .token.keyword, [data-theme=\"light\"] .token.attr-value, [data-theme=\"light\"] .language-autohotkey .token.selector, [data-theme=\"light\"] .language-json .token.boolean, [data-theme=\"light\"] .language-json .token.number, [data-theme=\"light\"] code[class*=\"language-css\"] {\n    color: #00f;\n}\n[data-theme=\"light\"] .token.function {\n    color: #393a34;\n}\n[data-theme=\"light\"] .token.deleted, [data-theme=\"light\"] .language-autohotkey .token.tag {\n    color: #9a050f;\n}\n[data-theme=\"light\"] .token.selector, [data-theme=\"light\"] .language-autohotkey .token.keyword {\n    color: #00009f;\n}\n[data-theme=\"light\"] .token.important {\n    color: #e90;\n}\n[data-theme=\"light\"] .token.important, [data-theme=\"light\"] .token.bold {\n    font-weight: bold;\n}\n[data-theme=\"light\"] .token.italic {\n    font-style: italic;\n}\n[data-theme=\"light\"] .token.class-name, [data-theme=\"light\"] .language-json .token.property {\n    color: #2b91af;\n}\n[data-theme=\"light\"] .token.tag, [data-theme=\"light\"] .token.selector {\n    color: #800000;\n}\n[data-theme=\"light\"] .token.attr-name, [data-theme=\"light\"] .token.property, [data-theme=\"light\"] .token.regex, [data-theme=\"light\"] .token.entity {\n    color: #f00;\n}\n[data-theme=\"light\"] .token.directive.tag .tag {\n    background: #ff0;\n    color: #393a34;\n}\n[data-theme=\"light\"] .line-numbers.line-numbers .line-numbers-rows {\n    border-right-color: #a5a5a5;\n}\n[data-theme=\"light\"] .line-numbers .line-numbers-rows > span:before {\n    color: #2b91af;\n}\n[data-theme=\"light\"] .line-highlight.line-highlight {\n    background: rgba(193, 222, 241, 0.2);\n    background: -webkit-linear-gradient(left, rgba(193, 222, 241, 0.2) 70%, rgba(221, 222, 241, 0));\n    background: linear-gradient(to right, rgba(193, 222, 241, 0.2) 70%, rgba(221, 222, 241, 0));\n}\n\n@media (prefers-color-scheme: light) {\n    [data-theme=\"system\"] code[class*=\"language-\"], [data-theme=\"system\"] pre[class*=\"language-\"] {\n        color: #393a34;\n        font-family: \"Fira Code\", Menlo, Monaco, Consolas, \"Andale Mono\", \"Ubuntu Mono\", \"Courier New\", monospace;\n        direction: ltr;\n        text-align: left;\n        white-space: pre;\n        word-spacing: normal;\n        word-break: normal;\n        -moz-tab-size: 4;\n        -o-tab-size: 4;\n        tab-size: 4;\n        -webkit-hyphens: none;\n        -moz-hyphens: none;\n        -ms-hyphens: none;\n        hyphens: none;\n    }\n    [data-theme=\"system\"] pre > code[class*=\"language-\"] {\n        font-size: 1em;\n    }\n    [data-theme=\"system\"] pre[class*=\"language-\"]::-moz-selection, [data-theme=\"system\"] pre[class*=\"language-\"] ::-moz-selection, [data-theme=\"system\"] code[class*=\"language-\"]::-moz-selection, [data-theme=\"system\"] code[class*=\"language-\"] ::-moz-selection {\n        background: #c1def1;\n    }\n    [data-theme=\"system\"] pre[class*=\"language-\"]::selection, [data-theme=\"system\"] pre[class*=\"language-\"] ::selection, [data-theme=\"system\"] code[class*=\"language-\"]::selection, [data-theme=\"system\"] code[class*=\"language-\"] ::selection {\n        background: #c1def1;\n    }\n    [data-theme=\"system\"] pre[class*=\"language-\"] {\n        /*padding: 1em;*/\n        /*margin: 0.5em 0;*/\n        background: transparent;\n    }\n    [data-theme=\"system\"] :not(pre) > code[class*=\"language-\"] {\n        padding: 0.2em;\n        padding-top: 1px;\n        padding-bottom: 1px;\n        background: #f8f8f8;\n        border: 1px solid #ddd;\n    }\n    [data-theme=\"system\"] .token.comment, [data-theme=\"system\"] .token.prolog, [data-theme=\"system\"] .token.doctype, [data-theme=\"system\"] .token.cdata {\n        color: #008000;\n        font-style: italic;\n    }\n    [data-theme=\"system\"] .token.namespace {\n        opacity: 0.7;\n    }\n    [data-theme=\"system\"] .token.string {\n        color: #a31515;\n    }\n    [data-theme=\"system\"] .token.punctuation, [data-theme=\"system\"] .token.operator {\n        color: #393a34;\n        /* no highlight */\n    }\n    [data-theme=\"system\"] .token.url, [data-theme=\"system\"] .token.symbol, [data-theme=\"system\"] .token.number, [data-theme=\"system\"] .token.boolean, [data-theme=\"system\"] .token.variable, [data-theme=\"system\"] .token.constant, [data-theme=\"system\"] .token.inserted {\n        color: #36acaa;\n    }\n    [data-theme=\"system\"] .token.atrule, [data-theme=\"system\"] .token.keyword, [data-theme=\"system\"] .token.attr-value, [data-theme=\"system\"] .language-autohotkey .token.selector, [data-theme=\"system\"] .language-json .token.boolean, [data-theme=\"system\"] .language-json .token.number, [data-theme=\"system\"] code[class*=\"language-css\"] {\n        color: #00f;\n    }\n    [data-theme=\"system\"] .token.function {\n        color: #393a34;\n    }\n    [data-theme=\"system\"] .token.deleted, [data-theme=\"system\"] .language-autohotkey .token.tag {\n        color: #9a050f;\n    }\n    [data-theme=\"system\"] .token.selector, [data-theme=\"system\"] .language-autohotkey .token.keyword {\n        color: #00009f;\n    }\n    [data-theme=\"system\"] .token.important {\n        color: #e90;\n    }\n    [data-theme=\"system\"] .token.important, [data-theme=\"system\"] .token.bold {\n        font-weight: bold;\n    }\n    [data-theme=\"system\"] .token.italic {\n        font-style: italic;\n    }\n    [data-theme=\"system\"] .token.class-name, [data-theme=\"system\"] .language-json .token.property {\n        color: #2b91af;\n    }\n    [data-theme=\"system\"] .token.tag, [data-theme=\"system\"] .token.selector {\n        color: #800000;\n    }\n    [data-theme=\"system\"] .token.attr-name, [data-theme=\"system\"] .token.property, [data-theme=\"system\"] .token.regex, [data-theme=\"system\"] .token.entity {\n        color: #f00;\n    }\n    [data-theme=\"system\"] .token.directive.tag .tag {\n        background: #ff0;\n        color: #393a34;\n    }\n    [data-theme=\"system\"] .line-numbers.line-numbers .line-numbers-rows {\n        border-right-color: #a5a5a5;\n    }\n    [data-theme=\"system\"] .line-numbers .line-numbers-rows > span:before {\n        color: #2b91af;\n    }\n    [data-theme=\"system\"] .line-highlight.line-highlight {\n        background: rgba(193, 222, 241, 0.2);\n        background: -webkit-linear-gradient(left, rgba(193, 222, 241, 0.2) 70%, rgba(221, 222, 241, 0));\n        background: linear-gradient(to right, rgba(193, 222, 241, 0.2) 70%, rgba(221, 222, 241, 0));\n    }\n}\n"
  },
  {
    "path": "client/src/types/api.ts",
    "content": "import { SymbolType, Range, TokenInfoType } from './results';\nimport {\n  DiffChunkType,\n  DiffHunkType,\n  RepoType,\n  StudioContextDoc,\n  StudioContextFile,\n} from './general';\n\nexport interface RangeLine {\n  byte: number;\n  column: number;\n  line: number;\n}\n\nexport interface SearchResponseStats {\n  lang?: Record<string, number>;\n  repo?: Record<string, number>;\n  org?: Record<string, number>;\n}\n\nexport interface SearchResponse {\n  count?: number;\n  data: (CodeItem | RepoItem | FileResItem | DirectoryItem | FileItem)[];\n  stats: SearchResponseStats;\n  metadata: {\n    page?: number;\n    page_size?: number;\n    page_count?: number;\n    total_count?: number;\n  };\n}\nexport interface FileSearchResponse extends SearchResponse {\n  data: FileItem[];\n}\n\nexport interface GeneralSearchResponse extends SearchResponse {\n  data: (CodeItem | RepoItem | FileResItem)[];\n}\n\nexport interface DirectorySearchResponse extends SearchResponse {\n  data: DirectoryItem[];\n}\n\nexport interface SymbolSnippetItem {\n  kind: SymbolType;\n  range: {\n    start: RangeLine;\n    end: RangeLine;\n  };\n}\n\nexport interface SnippetItem {\n  data: string;\n  highlights: Range[];\n  symbols: SymbolSnippetItem[];\n  line_range: Range;\n}\n\nexport interface Snippet {\n  relative_path: string;\n  repo_name: string;\n  repo_ref: string;\n  lang: string;\n  snippets: SnippetItem[];\n}\n\nexport interface RepoFileNameItem {\n  text: string;\n  highlights: Range[];\n}\n\nexport interface Repository {\n  name: RepoFileNameItem;\n  repo_ref: string;\n}\n\nexport interface SearchResponseFile {\n  relative_path: RepoFileNameItem;\n  repo_name: string;\n  repo_ref: string;\n  lang: string;\n  branches: string;\n  is_dir: boolean;\n}\n\nexport interface FlagItem {\n  kind: 'flag';\n  data: string;\n}\n\nexport interface LangItem {\n  kind: 'lang';\n  data: string;\n}\n\nexport interface CodeItem {\n  kind: 'snippets';\n  data: Snippet;\n}\n\nexport interface RepoItem {\n  kind: 'repository_result';\n  data: Repository;\n}\n\nexport interface FileResItem {\n  kind: 'file_result';\n  data: SearchResponseFile;\n}\n\nexport interface DirectoryItem {\n  kind: 'dir';\n  data: Directory;\n}\n\nexport interface FileItem {\n  kind: 'file';\n  data: File;\n}\n\nexport interface Directory {\n  repo_name: string;\n  relative_path: string;\n  repo_ref: string;\n  entries: DirectoryEntry[];\n}\n\nexport interface DirectoryFileEntryData {\n  File: {\n    lang: string;\n    indexed: boolean;\n  };\n}\n\nexport interface DirectoryEntry {\n  name: string;\n  entry_data: 'Directory' | DirectoryFileEntryData;\n}\n\nexport interface File {\n  repo_name: string;\n  relative_path: string;\n  lang: string;\n  contents: string;\n  repo_ref: string;\n  siblings: DirectoryEntry[];\n  size: number;\n  loc: number;\n  sloc: number;\n  indexed: boolean;\n}\n\nexport interface FileResponse {\n  contents: string;\n  lang: string;\n}\n\nexport interface FiltersItem {\n  name: string;\n  count: number;\n}\n\nexport interface FiltersResponse {\n  repos: FiltersItem[];\n  paths: FiltersItem[];\n  commits: FiltersItem[];\n  languages: FiltersItem[];\n}\n\nexport interface HoverablesResponse {\n  ranges: {\n    start: { byte: number; line: number; column: number };\n    end: { byte: number; line: number; column: number };\n  }[];\n}\n\ninterface RangeWithLine extends Range {\n  line: number;\n}\nexport interface TokenInfoSnippet {\n  data: string;\n  highlights: Range[];\n  symbols: [];\n  line_range: Range;\n}\n\nexport interface TokenInfoDataItem {\n  start: RangeLine;\n  end: RangeLine;\n  snippet: TokenInfoSnippet;\n}\n\nexport interface TokenInfoItem {\n  file: string;\n  data: TokenInfoDataItem[];\n}\n\nexport type RefDefDataItem = {\n  kind: TokenInfoType;\n  range: {\n    start: {\n      byte: number;\n      line: number;\n      column: number;\n    };\n    end: {\n      byte: number;\n      line: number;\n      column: number;\n    };\n  };\n  snippet: {\n    data: string;\n    highlights: Range[];\n    tokenRange?: Range;\n    symbols: never[];\n    line_range: Range;\n  };\n};\n\nexport interface TokenInfoResponse {\n  data: {\n    file: string;\n    data: RefDefDataItem[];\n  }[];\n}\n\nexport type ConversationShortType = {\n  created_at: number;\n  id: string;\n  title: string;\n  thread_id: string;\n};\n\nexport type AllConversationsResponse = ConversationShortType[];\n\ntype ProcStep = {\n  type: 'proc';\n  content: { query: string; paths: { repo: string; path: string }[] };\n};\n\ntype CodeStep = {\n  type: 'code';\n  content: { query: string };\n};\n\ntype PathStep = {\n  type: 'path';\n  content: { query: string };\n};\n\nexport type SearchStepType = ProcStep | CodeStep | PathStep;\n\nexport type ConversationType = {\n  thread_id: string;\n  exchanges: ConversationExchangeType[];\n};\n\nexport type ConversationExchangeType = {\n  id: string;\n  search_steps: SearchStepType[];\n  query: {\n    raw_query: string;\n    repos: {\n      Plain: { start: number; end: number; content: string };\n    }[];\n    paths: {\n      Plain: { start: number; end: number; content: string };\n    }[];\n    langs: {\n      Plain: {\n        start: number;\n        end: number;\n        content: string;\n      };\n    }[];\n    branch: {\n      Plain: {\n        start: number;\n        end: number;\n        content: string;\n      };\n    }[];\n    target: {\n      Plain: {\n        start: number;\n        end: number;\n        content: string;\n      };\n    };\n  };\n  conclusion: string;\n  answer: string;\n  paths: string[];\n  response_timestamp: string;\n  focused_chunk: {\n    repo_path: { repo: string; path: string };\n    start_line: number;\n    end_line: number;\n  } | null;\n};\n\nexport type CodeStudioMessageType =\n  | {\n      User: string;\n    }\n  | { Assistant: string };\n\nexport type CodeStudioTokenCountType = {\n  total: number;\n  per_file: (number | null)[];\n  per_doc_file: (number | null)[];\n  messages: number;\n};\n\nexport type CodeStudioType = {\n  id: string;\n  name: string;\n  modified_at: string;\n  messages: CodeStudioMessageType[];\n  context: StudioContextFile[];\n  doc_context: StudioContextDoc[];\n  token_counts: CodeStudioTokenCountType;\n};\n\nexport interface SuggestionsResponse {\n  count: number;\n  data: (\n    | CodeItem\n    | FlagItem\n    | FileResItem\n    | RepoItem\n    | DirectoryItem\n    | FileItem\n    | LangItem\n  )[];\n}\n\nexport interface NLSnippet {\n  repo_name: string;\n  relative_path: string;\n  text: string;\n  lang: string;\n  start_line: number;\n}\n\nexport interface NLSearchResponse {\n  query_id: string;\n  answer_path: string;\n  snippets: NLSnippet[];\n  user_id: string;\n}\n\nexport type StudioTemplateType = {\n  id: string;\n  name: string;\n  content: string;\n  modified_at: string;\n  is_default: boolean;\n};\n\nexport type HistoryConversationTurn = Omit<CodeStudioType, 'name'> & {\n  id: number;\n  modified_at: string;\n};\n\nexport type TutorialQuestionType = {\n  tag: string;\n  question: string;\n};\n\nexport type DocShortType = {\n  id: string;\n  name: string;\n  url: string;\n  favicon: string;\n  index_status: string;\n};\n\nexport type DocPageType = {\n  doc_id: string;\n  doc_source: string;\n  relative_url: string;\n  absolute_url: string;\n  doc_title: string;\n};\n\nexport type DocSectionType = {\n  ancestry: string[];\n  doc_id: string;\n  doc_source: string;\n  doc_title: string;\n  header: string;\n  point_id: string;\n  relative_url: string;\n  absolute_url: string;\n  section_range: { start: number; end: number };\n  text: string;\n};\n\nexport type GeneratedCodeDiff = {\n  chunks: DiffChunkType[];\n};\n\nexport type ProjectShortType = {\n  id: string;\n  name: string;\n  modified_at: null | string;\n  most_common_langs: string[];\n};\n\nexport type ProjectFullType = ProjectShortType & {\n  repos: { repo: RepoType; branch: string }[];\n  studios: CodeStudioType[];\n  conversations: ConversationShortType[];\n  docs: DocShortType[];\n};\n"
  },
  {
    "path": "client/src/types/file-icons-js/index.d.ts",
    "content": "declare module 'file-icons-js' {\n  export function getClass(name): string | null;\n  export function getClassWithColor(name): string | null;\n}\n"
  },
  {
    "path": "client/src/types/general.ts",
    "content": "import React, { MemoExoticComponent, ReactElement } from 'react';\nimport { DocShortType, HistoryConversationTurn, SearchStepType } from './api';\n\nexport enum MenuItemType {\n  DEFAULT = 'default',\n  LINK = 'link',\n  SELECTABLE = 'selectable',\n  REMOVABLE = 'removable',\n  DANGER = 'danger',\n}\n\nexport enum ExtendedMenuItemType {\n  SHARED = 'shared',\n  DIVIDER = 'divider',\n  DIVIDER_WITH_TEXT = 'divider_with_text',\n}\n\nexport type SearchHistoryType = {\n  text: string;\n  type: MenuItemType | ExtendedMenuItemType;\n  icon?: React.ReactElement;\n};\n\nexport enum FilterName {\n  LANGUAGE = 'lang',\n  ORGANISATION = 'org',\n  REPOSITORY = 'repo',\n  PATH = 'path',\n}\n\nexport type FilterType = {\n  title: string;\n  items: {\n    label: string;\n    description: string;\n    checked: boolean;\n    icon?: ReactElement;\n  }[];\n  type: 'checkbox' | 'button';\n  name: FilterName | string;\n  singleSelect?: boolean;\n  disabled?: boolean;\n};\n\nexport enum SyncStatus {\n  Cancelled = 'cancelled',\n  Cancelling = 'cancelling',\n  Uninitialized = 'uninitialized',\n  Queued = 'queued',\n  Done = 'done',\n  Error = 'error',\n  Removed = 'removed',\n  Indexing = 'indexing',\n  Syncing = 'syncing',\n  RemoteRemoved = 'remote_removed',\n}\n\nexport enum RepoProvider {\n  GitHub = 'github',\n  Local = 'local',\n}\n\nexport type RepoType = {\n  ref: string;\n  name: string;\n  provider: RepoProvider;\n  local_duplicates: string[];\n  last_update: string;\n  last_index: string;\n  sync_status: SyncStatus;\n  most_common_lang: string;\n  branches: { name: string; last_commit_unix_secs: number }[];\n  branch_filter: { select: string[] } | null;\n};\n\nexport type RepoUi = RepoType & {\n  shortName: string;\n  folderName: string;\n  alreadySynced?: boolean;\n  isSyncing?: boolean;\n};\n\nexport type CodeStudioShortType = {\n  id: string;\n  name: string;\n  modified_at: string;\n  repos: string[];\n  most_common_ext: string;\n};\n\nexport enum TabTypesEnum {\n  FILE = 'file',\n  CHAT = 'chat',\n  STUDIO = 'studio',\n  DOC = 'doc',\n}\n\nexport type FileTabType = {\n  type: TabTypesEnum.FILE;\n  key: string;\n  isTemp?: boolean;\n  path: string;\n  repoRef: string;\n  branch?: string | null;\n  scrollToLine?: string;\n  tokenRange?: string;\n  studioId?: string;\n  initialRanges?: [number, number][];\n  isFileInContext?: boolean;\n};\n\nexport type ChatTabType = {\n  type: TabTypesEnum.CHAT;\n  key: string;\n  conversationId?: string;\n  title?: string;\n  initialQuery?: {\n    path: string;\n    lines: [number, number];\n    repoRef: string;\n    branch?: string | null;\n  };\n};\n\nexport type StudioTabType = {\n  type: TabTypesEnum.STUDIO;\n  key: string;\n  studioId: string;\n  title?: string;\n  snapshot?: HistoryConversationTurn;\n};\n\nexport type DocTabType = {\n  type: TabTypesEnum.DOC;\n  key: string;\n  docId: string;\n  title?: string;\n  favicon?: string;\n  relativeUrl: string;\n  studioId?: string;\n  initialSections?: string[];\n  isDocInContext?: boolean;\n};\n\nexport type TabType = FileTabType | ChatTabType | StudioTabType | DocTabType;\n\nexport type DraggableTabItem = {\n  id: string;\n  index: number;\n  t: TabType;\n  side: 'left' | 'right';\n};\n\nexport type ConversationMessage = {\n  author: 'user' | 'server';\n  text?: string;\n  isLoading: boolean;\n  snippets?: {\n    path: string;\n    code: string;\n    repoName: string;\n    lang: string;\n    line: number;\n  }[];\n  error?: string;\n};\n\nexport enum ChatMessageType {\n  Answer = 'answer',\n  Prompt = 'prompt',\n}\n\nexport enum ChatMessageAuthor {\n  User = 'user',\n  Server = 'server',\n}\n\nexport enum ParsedQueryTypeEnum {\n  TEXT = 'text',\n  REPO = 'repo',\n  PATH = 'path',\n  LANG = 'lang',\n  BRANCH = 'branch',\n}\nexport type ParsedQueryType = { type: ParsedQueryTypeEnum; text: string };\n\nexport type ChatMessageUser = {\n  author: ChatMessageAuthor.User;\n  text: string;\n  parsedQuery?: ParsedQueryType[];\n  isFromHistory?: boolean;\n};\n\nexport type MessageResultCite = {\n  Cite: {\n    path_alias?: number;\n    path: string;\n    comment: string;\n    start_line: number;\n    end_line: number;\n  };\n};\n\nexport type MessageResultDirectory = {\n  Directory: {\n    path: string | null;\n    comment: string | null;\n  };\n};\n\nexport type MessageResultNew = {\n  New: {\n    language: string;\n    code: string;\n  };\n};\n\nexport type MessageResultModify = {\n  Modify: {\n    path: string;\n    language: string;\n    diff: {\n      header: {\n        old_start: number;\n        new_start: number;\n        old_lines: number;\n        new_lines: number;\n      };\n      lines: string[];\n    };\n  };\n};\n\nexport type ChatLoadingStep = SearchStepType & {\n  path: string;\n  displayText: string;\n};\n\nexport type FileSystemResult = {\n  Filesystem?: (\n    | MessageResultCite\n    | MessageResultNew\n    | MessageResultModify\n    | MessageResultDirectory\n  )[];\n};\n\nexport type ArticleResult = {\n  Article?: string;\n};\n\nexport type ChatMessageServer = {\n  author: ChatMessageAuthor.Server;\n  text?: string;\n  isLoading: boolean;\n  loadingSteps: ChatLoadingStep[];\n  error?: string;\n  isFromHistory?: boolean;\n  conclusion?: string;\n  queryId: string;\n  responseTimestamp: string;\n  explainedFile?: string;\n};\n\nexport type ChatMessage = ChatMessageUser | ChatMessageServer;\n\nexport type IpynbOutputType = {\n  name?: string;\n  stream?: string;\n  ename?: string;\n  evalue?: string;\n  traceback?: string[];\n  data?: {\n    'text/plain'?: string[];\n    'text/html'?: string[];\n    'text/latex'?: string[];\n    'image/png'?: string;\n    'image/jpeg'?: string;\n    'image/gif'?: string;\n    'image/svg+xml'?: string;\n    'application/javascript'?: string[];\n  };\n  output_type?: string;\n  png?: string;\n  jpeg?: string;\n  gif?: string;\n  svg?: string;\n  html?: string;\n  latex?: string;\n  text?: string[];\n  execution_count?: number;\n  prompt_number?: number;\n  metadata?: {\n    scrolled?: boolean;\n  };\n};\n\nexport type IpynbCellType = {\n  attachments?: {\n    [s: string]: {\n      [s: string]: string;\n    };\n  };\n  cell_type?: string;\n  language?: string;\n  execution_count?: number | null;\n  prompt_number?: number;\n  auto_number?: number;\n  level?: number;\n  source?: string[];\n  outputs?: IpynbOutputType[];\n  input?: string[];\n};\n\nexport type FileHighlightsType = Record<\n  string,\n  ({ lines: [number, number]; color: string; index: number } | undefined)[]\n>;\n\nexport type LocaleType = 'en' | 'ja' | 'zhCN' | 'zhTW' | 'es' | 'it';\n\nexport enum StudioConversationMessageAuthor {\n  USER = 'User',\n  ASSISTANT = 'Assistant',\n}\n\nexport type StudioConversationMessage = {\n  author: StudioConversationMessageAuthor;\n  message: string;\n  isLoading?: boolean;\n  error?: string;\n};\n\nexport type DiffChunkType = {\n  file: string;\n  lang: string;\n  repo: string;\n  branch: string | null;\n  hunks: DiffHunkType[];\n  raw_patch: string;\n};\n\nexport type DiffHunkType = {\n  line_start: number;\n  patch: string;\n};\n\nexport enum StudioLeftPanelType {\n  CONTEXT = 'context',\n  TEMPLATES = 'templates',\n  FILE = 'file',\n  DIFF = 'diff',\n  DOCS = 'docs',\n}\n\nexport enum StudioRightPanelType {\n  CONVERSATION = 'conversation',\n}\n\nexport type FileStudioPanelType = {\n  type: StudioLeftPanelType.FILE;\n  data: {\n    repo: RepoType;\n    branch: string | null;\n    filePath: string;\n    isFileInContext: boolean;\n    initialRanges?: [number, number][];\n  };\n};\n\nexport type DocsStudioPanelType = {\n  type: StudioLeftPanelType.DOCS;\n  data: {\n    docProvider: DocShortType;\n    url: string;\n    absoluteUrl: string;\n    title: string;\n    selectedSection?: string;\n    isDocInContext: boolean;\n    initialSections?: string[];\n  };\n};\n\nexport type DiffPanelType = {\n  type: StudioLeftPanelType.DIFF;\n  data: {\n    repo: RepoType;\n    branch: string | null;\n    filePath: string;\n    hunks: DiffHunkType[];\n  };\n};\n\nexport type StudioLeftPanelDataType =\n  | {\n      type: StudioLeftPanelType.CONTEXT | StudioLeftPanelType.TEMPLATES;\n      data?: null;\n    }\n  | FileStudioPanelType\n  | DocsStudioPanelType\n  | DiffPanelType;\n\nexport type StudioRightPanelDataType = {\n  type: StudioRightPanelType.CONVERSATION;\n  data?: null;\n};\n\nexport type StudioContextFile = {\n  path: string;\n  ranges: { start: number; end: number }[];\n  repo: string;\n  branch: string | null;\n  hidden: boolean;\n};\n\nexport type StudioContextDoc = {\n  doc_id: string;\n  doc_source: string;\n  doc_icon: string | null;\n  doc_title: string | null;\n  relative_url: string;\n  absolute_url: string;\n  ranges: string[];\n  hidden: boolean;\n};\n\nexport type CommandBarItemCustomType = {\n  key: string;\n  Component: MemoExoticComponent<any>;\n  componentProps: Record<string, any>;\n  focusedItemProps?: Record<string, any>;\n};\n\nexport type CommandBarItemGeneralType = {\n  Icon: (props: {\n    raw?: boolean | undefined;\n    sizeClassName?: string | undefined;\n    className?: string | undefined;\n  }) => JSX.Element;\n  label: string;\n  shortcut?: string[];\n  id: string;\n  key: string;\n  parent?: CommandBarStepType;\n  footerHint: string | ReactElement;\n  closeOnClick?: boolean;\n  footerBtns: {\n    label: string;\n    shortcut?: string[];\n    action?: () => void | Promise<void>;\n  }[];\n  iconContainerClassName?: string;\n  onClick?: (e: React.MouseEvent | KeyboardEvent) => void | Promise<void>;\n};\n\nexport type CommandBarItemInvisibleType = {\n  footerHint: string | ReactElement;\n  footerBtns: {\n    label: string;\n    shortcut?: string[];\n    action?: () => void | Promise<void>;\n  }[];\n  focusedItemProps?: Record<string, any>;\n};\n\nexport type CommandBarItemType =\n  | CommandBarItemCustomType\n  | CommandBarItemGeneralType\n  | CommandBarItemInvisibleType;\n\nexport type CommandBarSectionType = {\n  label?: string;\n  key: string;\n  items: (CommandBarItemGeneralType | CommandBarItemCustomType)[];\n};\n\nexport type CommandBarStepType = {\n  id: string;\n  label: string;\n  parent?: CommandBarStepType;\n};\n\nexport type CommandBarActiveStepType =\n  | AddToStudioStepType\n  | SearchFilesStepType\n  | SearchDocsStepType\n  | {\n      id: Exclude<\n        CommandBarStepEnum,\n        | CommandBarStepEnum.ADD_TO_STUDIO\n        | CommandBarStepEnum.SEARCH_FILES\n        | CommandBarStepEnum.SEARCH_DOCS\n      >;\n      data?: Record<string, any>;\n    };\n\nexport type AddFileToStudioDataType = {\n  path: string;\n  repoRef: string;\n  branch?: string | null;\n};\n\nexport type AddDocToStudioDataType = {\n  docId: string;\n  relativeUrl: string;\n  title?: string;\n  favicon?: string;\n};\n\nexport type AddToStudioStepType = {\n  id: CommandBarStepEnum.ADD_TO_STUDIO;\n  data: AddFileToStudioDataType | AddDocToStudioDataType;\n};\n\nexport type SearchFilesStepType = {\n  id: CommandBarStepEnum.SEARCH_FILES;\n  data?: { studioId: string };\n};\n\nexport type SearchDocsStepType = {\n  id: CommandBarStepEnum.SEARCH_DOCS;\n  data: { studioId?: string; docId: string };\n};\n\nexport enum CommandBarStepEnum {\n  INITIAL = 'initial',\n  MANAGE_REPOS = 'manage_repos',\n  ADD_NEW_REPO = 'add_new_repo',\n  PRIVATE_REPOS = 'private_repos',\n  PUBLIC_REPOS = 'public_repos',\n  LOCAL_REPOS = 'local_repos',\n  DOCS = 'docs',\n  REPO_SETTINGS = 'repo_settings',\n  CREATE_PROJECT = 'create_project',\n  TOGGLE_THEME = 'toggle_theme',\n  SEARCH_FILES = 'search_files',\n  SEARCH_DOCS = 'search_docs',\n  ADD_TO_STUDIO = 'add_to_studio',\n}\n\nexport enum SettingSections {\n  GENERAL,\n  PREFERENCES,\n}\n\nexport enum ProjectSettingSections {\n  GENERAL,\n  TEMPLATES,\n}\n\nexport type SettingsTypesSections = SettingSections | ProjectSettingSections;\n\ntype InputEditorTextContent = {\n  type: 'text';\n  text: string;\n};\n\ntype InputEditorMentionContent = {\n  type: 'mention';\n  attrs: {\n    type: 'lang' | 'dir' | 'file' | 'repo';\n    id: string;\n    display: string;\n  };\n};\n\nexport type InputEditorContent =\n  | InputEditorTextContent\n  | InputEditorMentionContent;\n\nexport type InputValueType = {\n  parsed: ParsedQueryType[];\n  plain: string;\n};\n\nexport type ToastType = {\n  id: string;\n  type: 'default' | 'error';\n  title: string;\n  text: string | ReactElement;\n  Icon?: (props: {\n    raw?: boolean | undefined;\n    sizeClassName?: string | undefined;\n    className?: string | undefined;\n  }) => JSX.Element;\n};\n\nexport type RepoIndexingStatusType = {\n  status: SyncStatus;\n  percentage?: string;\n  branch?: string;\n};\n\nexport type IndexingStatusType = Record<string, RepoIndexingStatusType>;\n\nexport type OnboardingStateType = {\n  isCommandBarTutorialFinished?: boolean;\n  isChatOpened?: boolean;\n  isFileExplained?: boolean;\n  isCodeExplained?: boolean;\n  isCodeNavigated?: boolean;\n};\n\nexport type ChatInputType = 'default' | 'simplified';\n"
  },
  {
    "path": "client/src/types/index.ts",
    "content": "declare global {\n  // eslint-disable-next-line no-unused-vars\n  interface Window {\n    __APP_SESSION__: string | number;\n  }\n}\n\nexport interface Commit {\n  message: string;\n  author: string;\n  image?: string;\n  datetime: number;\n  hash: string;\n}\nexport enum FileTreeFileType {\n  FILE,\n  DIR,\n}\nexport interface RepositoryFile {\n  name: string;\n  path: string;\n  type: FileTreeFileType;\n  // children: RepositoryFile[];\n  // commit: Commit;\n  lang?: string;\n  indexed: boolean;\n}\n\nexport interface RepositoryBranch {\n  name: string;\n  commit: Commit;\n  files: number;\n  active?: boolean;\n  main?: boolean;\n}\n\nexport enum RepoSource {\n  GH,\n  LOCAL,\n}\n\nexport interface Repository {\n  url: string;\n  name: string;\n  description: string;\n  fileCount: number;\n  commits: Commit[];\n  branches: RepositoryBranch[];\n  followers: number;\n  files: RepositoryFile[];\n  currentPath: string;\n  source: RepoSource;\n}\n\nexport type Theme = 'system' | 'dark' | 'light' | 'black';\n"
  },
  {
    "path": "client/src/types/prism.ts",
    "content": "import { Range } from './results';\n\nexport type PrismToken = {\n  type: string | string[];\n  alias: string | string[];\n  content: Array<PrismToken | string> | string;\n};\n\nexport type Token = {\n  types: string[];\n  content: string;\n  empty?: boolean;\n  byteRange: Range;\n};\n"
  },
  {
    "path": "client/src/types/results.ts",
    "content": "import { Token } from './prism';\nimport { RefDefDataItem } from './api';\nimport { FileTreeFileType, RepositoryFile } from './index';\n\nexport type BaseSymbolType =\n  | 'method'\n  | 'variable'\n  | 'field'\n  | 'typeParameter'\n  | 'constant'\n  | 'class'\n  | 'interface'\n  | 'struct'\n  | 'event'\n  | 'operator'\n  | 'module'\n  | 'property'\n  | 'enum'\n  | 'reference'\n  | 'keyword'\n  | 'file'\n  | 'folder'\n  | 'color'\n  | 'unit'\n  | 'snippet'\n  | 'text';\n\nexport type ExtendedSymbolType =\n  | 'function'\n  | 'constructor'\n  | 'value'\n  | 'parameter'\n  | 'generator'\n  | 'const'\n  | 'var'\n  | 'type'\n  | 'enumerator'\n  | 'union'\n  | 'typedef'\n  | 'alias'\n  | 'label'\n  | 'member'\n  | 'func';\n\nexport type SymbolType = BaseSymbolType | ExtendedSymbolType | 'multiple';\n\nexport enum ResultItemType {\n  CODE,\n  REPO,\n  FILE,\n  FLAG,\n  LANG,\n}\n\nexport type BaseResultType = {\n  type: ResultItemType;\n  id: number | string;\n  repoName: string;\n};\n\nexport interface CodeResult extends BaseResultType {\n  relativePath: string;\n  repoRef: string;\n  snippets: Snippet[];\n  type: ResultItemType.CODE;\n  language: string;\n}\n\nexport interface FlagResult {\n  type: ResultItemType.FLAG;\n  data: string;\n}\n\nexport interface LangResult {\n  type: ResultItemType.LANG;\n  data: string;\n}\n\nexport interface RepoResult extends BaseResultType {\n  repoRef: string;\n  branches: number;\n  files: number;\n  type: ResultItemType.REPO;\n  highlights: Range[];\n}\n\nexport interface FileResult extends BaseResultType {\n  relativePath: string;\n  repoRef: string;\n  lines: number;\n  highlights: Range[];\n  type: ResultItemType.FILE;\n  language: string;\n}\n\nexport interface SnippetSymbol {\n  line: number;\n  kind: SymbolType;\n}\n\nexport interface Snippet {\n  code: string;\n  lineStart?: number;\n  highlights?: Range[];\n  symbols?: SnippetSymbol[];\n}\n\nexport type HighlightMap = {\n  highlight: boolean;\n  token: Token;\n  startHl?: boolean;\n  endHl?: boolean;\n};\n\nexport type TokensLine = {\n  tokens: HighlightMap[];\n  lineNumber: number | null;\n};\n\nexport type Range = { start: number; end: number };\n\nexport type ResultType = CodeResult | RepoResult | FileResult;\nexport type SuggestionType = ResultType | FlagResult | LangResult;\n\nexport type FullResult = {\n  relativePath: string;\n  repoPath: string;\n  code: string;\n  language: string;\n  hoverableRanges: Record<number, Range[]>;\n  repoName: string;\n  size: number;\n  loc: number;\n  indexed: boolean;\n};\n\nexport type DirectoryResult = {\n  name: string;\n  entries: {\n    name: string;\n    path: string;\n    type: FileTreeFileType;\n    lang?: string;\n  }[];\n  relativePath: string;\n};\n\nexport type TokenInfoItem = {\n  code: string;\n  line: number;\n  highlights: Range[];\n};\n\nexport type TokenInfoFile = {\n  path: string;\n  items: TokenInfoItem[];\n};\n\nexport type TokenInfoType = 'reference' | 'definition';\n\nexport type TokenInfo = {\n  references?: TokenInfoFile[];\n  definitions?: TokenInfoFile[];\n};\n\nexport type TokenInfoWrapped = {\n  hoverableRange: Range | null;\n  tokenRange: Range | null;\n  lineNumber?: number;\n  isLoading: boolean;\n  data: {\n    references: { file: string; data: RefDefDataItem[] }[];\n    definitions: { file: string; data: RefDefDataItem[] }[];\n  };\n};\n\nexport type ResultClick = (\n  repo: string,\n  path?: string,\n  lineNumbers?: [number, number],\n) => void;\n\nexport type FileTreeItem = RepositoryFile & {\n  children: FileTreeItem[];\n  selected?: boolean;\n};\n\nexport type MentionOptionType = {\n  id: string;\n  display: string;\n  type: 'file' | 'dir' | 'lang' | 'repo';\n  isFirst: boolean;\n  hint?: string;\n};\n"
  },
  {
    "path": "client/src/utils/commandBarUtils.test.ts",
    "content": "import { RepositoryIcon } from '../icons';\nimport {\n  CommandBarStepEnum,\n  CommandBarItemGeneralType,\n  CommandBarItemCustomType,\n} from '../types/general';\nimport { newProjectShortcut } from '../consts/shortcuts';\nimport { bubbleUpRecentItems } from './commandBarUtils';\n\nconst items1: CommandBarItemGeneralType[] = [\n  {\n    label: 'Manage repositories',\n    Icon: RepositoryIcon,\n    id: CommandBarStepEnum.MANAGE_REPOS,\n    key: CommandBarStepEnum.MANAGE_REPOS,\n    shortcut: [],\n    footerHint: '',\n    footerBtns: [],\n  },\n  {\n    label: 'Add new repository',\n    Icon: RepositoryIcon,\n    id: CommandBarStepEnum.ADD_NEW_REPO,\n    key: CommandBarStepEnum.ADD_NEW_REPO,\n    shortcut: ['cmd', 'A'],\n    footerHint: '',\n    footerBtns: [],\n  },\n  {\n    label: 'New project',\n    Icon: RepositoryIcon,\n    id: CommandBarStepEnum.CREATE_PROJECT,\n    key: CommandBarStepEnum.CREATE_PROJECT,\n    shortcut: newProjectShortcut,\n    footerHint: '',\n    footerBtns: [],\n  },\n];\n\nconst items2: CommandBarItemGeneralType[] = [\n  {\n    label: `Account settings`,\n    Icon: RepositoryIcon,\n    id: `account-settings`,\n    key: `account-settings`,\n    shortcut: ['option', 'A'],\n    footerHint: `Open account settings`,\n    footerBtns: [],\n  },\n  {\n    label: `Subscription`,\n    Icon: RepositoryIcon,\n    id: `subscription-settings`,\n    key: `subscription-settings`,\n    shortcut: ['option', 'S'],\n    footerHint: `Open subscription settings`,\n    footerBtns: [],\n  },\n];\n\nconst items3: CommandBarItemGeneralType[] = [\n  {\n    label: `Theme`,\n    Icon: RepositoryIcon,\n    id: CommandBarStepEnum.TOGGLE_THEME,\n    key: CommandBarStepEnum.TOGGLE_THEME,\n    shortcut: ['alt', '1'],\n    footerHint: `Change application colour theme`,\n    footerBtns: [],\n  },\n];\n\nconst sections = [\n  {\n    items: items1,\n    itemsOffset: 0,\n    label: 'Context',\n    key: 'context-items',\n  },\n  {\n    items: items2,\n    itemsOffset: items1.length,\n    label: 'Recent projects',\n    key: 'recent-projects',\n  },\n  {\n    items: items3,\n    itemsOffset: items1.length + items2.length,\n    label: 'Commands',\n    key: 'general-commands',\n  },\n];\n\nconst recentLabel = 'Recent';\n\nconst testArrayIncludes = (\n  arr: (CommandBarItemGeneralType | CommandBarItemCustomType)[],\n  itemKey: string,\n) => !!arr.find((a) => a.key === itemKey);\n\ndescribe('commandBarUtils', () => {\n  describe('bubbleUpRecentItems', () => {\n    test('no recent items', () => {\n      const result = bubbleUpRecentItems(sections, [], recentLabel);\n      expect(result.length).toEqual(3);\n      expect(result[0].items.length).toEqual(items1.length);\n      expect(result[1].items.length).toEqual(items2.length);\n      expect(result[2].items.length).toEqual(items3.length);\n    });\n    test('irrelevant recent items', () => {\n      const result = bubbleUpRecentItems(\n        sections,\n        ['foo', 'bar', 'baz'],\n        recentLabel,\n      );\n      expect(result.length).toEqual(3);\n      expect(result[0].items.length).toEqual(items1.length);\n      expect(result[1].items.length).toEqual(items2.length);\n      expect(result[2].items.length).toEqual(items3.length);\n    });\n    test('1 recent item in section 1', () => {\n      const key = items1[1].key;\n      const result = bubbleUpRecentItems(sections, [key], recentLabel);\n      expect(result.length).toEqual(4);\n      expect(result[0].items.length).toEqual(1);\n      expect(result[0].label).toEqual(recentLabel);\n      expect(result[0].items[0].key).toEqual(key);\n      expect(result[1].items.length).toEqual(items1.length - 1);\n      expect(testArrayIncludes(result[1].items, key)).toBe(false);\n      expect(result[2].items.length).toEqual(items2.length);\n      expect(result[3].items.length).toEqual(items3.length);\n    });\n    test('1 recent item in section 1 + irrelevant', () => {\n      const key = items1[1].key;\n      const result = bubbleUpRecentItems(sections, [key, 'foo'], recentLabel);\n      expect(result.length).toEqual(4);\n      expect(result[0].items.length).toEqual(1);\n      expect(result[0].label).toEqual(recentLabel);\n      expect(result[0].items[0].key).toEqual(key);\n      expect(result[1].items.length).toEqual(items1.length - 1);\n      expect(testArrayIncludes(result[1].items, key)).toBe(false);\n      expect(result[2].items.length).toEqual(items2.length);\n      expect(result[3].items.length).toEqual(items3.length);\n    });\n    test('1 recent item in section 3', () => {\n      const key = items3[0].key;\n      const result = bubbleUpRecentItems(sections, [key], recentLabel);\n      expect(result.length).toEqual(3);\n      expect(result[0].items.length).toEqual(1);\n      expect(result[0].label).toEqual(recentLabel);\n      expect(result[0].items[0].key).toEqual(key);\n      expect(result[1].items.length).toEqual(items1.length);\n      expect(result[2].items.length).toEqual(items2.length);\n    });\n    test('recent items in different sections', () => {\n      const key1 = items1[2].key;\n      const key2 = items2[1].key;\n      const key3 = items3[0].key;\n      const recentKeysArr = [key3, key2, key1];\n      const result = bubbleUpRecentItems(sections, recentKeysArr, recentLabel);\n      expect(result.length).toEqual(3);\n      expect(result[0].items.length).toEqual(recentKeysArr.length);\n      expect(result[0].label).toEqual(recentLabel);\n      expect(result[0].items[0].key).toEqual(key1);\n      expect(result[0].items[1].key).toEqual(key2);\n      expect(result[0].items[2].key).toEqual(key3);\n      expect(result[1].items.length).toEqual(items1.length - 1);\n      expect(testArrayIncludes(result[1].items, key1)).toBe(false);\n      expect(result[2].items.length).toEqual(items2.length - 1);\n      expect(testArrayIncludes(result[2].items, key2)).toBe(false);\n    });\n    test('all keys are recent', () => {\n      const recentKeysArr = [\n        ...items1.map((i) => i.key),\n        ...items2.map((i) => i.key),\n        ...items3.map((i) => i.key),\n      ].reverse();\n      const result = bubbleUpRecentItems(sections, recentKeysArr, recentLabel);\n      expect(result.length).toEqual(1);\n      expect(result[0].items.length).toEqual(recentKeysArr.length);\n      expect(result[0].label).toEqual(recentLabel);\n      expect(result[0].items[0].key).toEqual(items1[0].key);\n    });\n  });\n});\n"
  },
  {
    "path": "client/src/utils/commandBarUtils.ts",
    "content": "import {\n  CommandBarItemCustomType,\n  CommandBarItemGeneralType,\n  CommandBarSectionType,\n} from '../types/general';\n\nexport const bubbleUpRecentItems = (\n  sections: CommandBarSectionType[],\n  recentKeys: string[],\n  recentLabel: string,\n): CommandBarSectionType[] => {\n  const newSections: CommandBarSectionType[] = [];\n  const recentItems: (CommandBarItemGeneralType | CommandBarItemCustomType)[] =\n    [];\n  sections.forEach((s) => {\n    recentItems.push(\n      ...s.items.filter((item) => recentKeys.includes(item.key)),\n    );\n  });\n  if (recentItems.length) {\n    newSections.push({\n      label: recentLabel,\n      items: recentItems.sort(\n        (a, b) => recentKeys.indexOf(b.key) - recentKeys.indexOf(a.key),\n      ),\n      key: 'recent-items',\n    });\n  }\n  sections.forEach((s) => {\n    const newItems = s.items.filter((item) => !recentKeys.includes(item.key));\n    if (newItems.length) {\n      newSections.push({\n        ...s,\n        items: newItems,\n      });\n    }\n  });\n  return newSections;\n};\n"
  },
  {
    "path": "client/src/utils/domUtils.ts",
    "content": "export const findElementInCurrentTab = (\n  selector: string,\n): HTMLElement | null => {\n  return document.querySelector(selector);\n};\n\nexport const findAllElementsInCurrentTab = <\n  T extends HTMLElement = HTMLElement,\n>(\n  selector: string,\n  // eslint-disable-next-line no-undef\n): NodeListOf<T> | null => {\n  return document.querySelectorAll(selector);\n};\n\nexport const isFocusInInput = (ignoreCommandInput?: boolean) => {\n  const isInInput =\n    ['INPUT', 'TEXTAREA'].includes(document.activeElement?.tagName || '') ||\n    (document.activeElement as HTMLElement)?.isContentEditable;\n  const isInCommandInput = document.activeElement?.id === 'command-input';\n  return ignoreCommandInput ? isInInput && !isInCommandInput : isInInput;\n};\n\nexport const focusInput = () => {\n  findElementInCurrentTab('.ProseMirror')?.focus();\n  findElementInCurrentTab('.ReactMention textarea')?.focus();\n};\n\nexport const blurInput = () => {\n  findElementInCurrentTab('.ProseMirror')?.blur();\n  findElementInCurrentTab('.ReactMention textarea')?.blur();\n};\n"
  },
  {
    "path": "client/src/utils/file.ts",
    "content": "import { FileTreeFileType, RepositoryFile, RepoSource } from '../types';\n\nexport const sortFiles = (a: RepositoryFile, b: RepositoryFile) => {\n  if (a.type != b.type) {\n    return a.type === FileTreeFileType.DIR ? -1 : 1;\n  }\n  return a.name.toString().localeCompare(b.name);\n};\n\nexport const getFileName = (path: string) => path.split('/').reverse()[0];\n\nexport const getRepoSource = (repoRef: string): RepoSource => {\n  const p = repoRef.split('/')[0];\n  if (p === 'github.com') {\n    return RepoSource.GH;\n  } else {\n    return RepoSource.LOCAL;\n  }\n};\n\nexport const cleanRepoName = (repoName: string) =>\n  repoName.replace('github.com/', '');\n"
  },
  {
    "path": "client/src/utils/index.test.ts",
    "content": "import { ParsedQueryTypeEnum } from '../types/general';\nimport {\n  concatenateParsedQuery,\n  getCommonFolder,\n  getFileExtensionForLang,\n  humanNumber,\n  mergeRanges,\n  splitUserInputAfterAutocomplete,\n} from './index';\n\ndescribe('Utils', () => {\n  describe('splitUserInputAfterAutocomplete', () => {\n    test('simple string', () => {\n      expect(\n        JSON.stringify(splitUserInputAfterAutocomplete('my simple string')),\n      ).toEqual(\n        JSON.stringify([\n          { type: ParsedQueryTypeEnum.TEXT, text: 'my simple string' },\n        ]),\n      );\n    });\n    test('filter at start', () => {\n      expect(\n        JSON.stringify(\n          splitUserInputAfterAutocomplete('|lang:TypeScript| my simple string'),\n        ),\n      ).toEqual(\n        JSON.stringify([\n          { type: ParsedQueryTypeEnum.LANG, text: 'TypeScript' },\n          { type: ParsedQueryTypeEnum.TEXT, text: ' my simple string' },\n        ]),\n      );\n    });\n    test('filter at the end', () => {\n      expect(\n        JSON.stringify(\n          splitUserInputAfterAutocomplete('my simple string |lang:TypeScript|'),\n        ),\n      ).toEqual(\n        JSON.stringify([\n          { type: ParsedQueryTypeEnum.TEXT, text: 'my simple string ' },\n          { type: ParsedQueryTypeEnum.LANG, text: 'TypeScript' },\n        ]),\n      );\n    });\n    test('lang filter in the middle', () => {\n      expect(\n        JSON.stringify(\n          splitUserInputAfterAutocomplete('my simple |lang:TypeScript| string'),\n        ),\n      ).toEqual(\n        JSON.stringify([\n          { type: ParsedQueryTypeEnum.TEXT, text: 'my simple ' },\n          { type: ParsedQueryTypeEnum.LANG, text: 'TypeScript' },\n          { type: ParsedQueryTypeEnum.TEXT, text: ' string' },\n        ]),\n      );\n    });\n    test('path filter in the middle', () => {\n      expect(\n        JSON.stringify(\n          splitUserInputAfterAutocomplete(\n            'my |path:src/index.js| simple string',\n          ),\n        ),\n      ).toEqual(\n        JSON.stringify([\n          { type: ParsedQueryTypeEnum.TEXT, text: 'my ' },\n          { type: ParsedQueryTypeEnum.PATH, text: 'src/index.js' },\n          { type: ParsedQueryTypeEnum.TEXT, text: ' simple string' },\n        ]),\n      );\n    });\n    test('lang filter after path filter in the middle', () => {\n      expect(\n        JSON.stringify(\n          splitUserInputAfterAutocomplete(\n            'my |path:src/index.js| simple |lang:TypeScript| string',\n          ),\n        ),\n      ).toEqual(\n        JSON.stringify([\n          { type: ParsedQueryTypeEnum.TEXT, text: 'my ' },\n          { type: ParsedQueryTypeEnum.PATH, text: 'src/index.js' },\n          { type: ParsedQueryTypeEnum.TEXT, text: ' simple ' },\n          { type: ParsedQueryTypeEnum.LANG, text: 'TypeScript' },\n          { type: ParsedQueryTypeEnum.TEXT, text: ' string' },\n        ]),\n      );\n    });\n    test('path filter after lang filter in the middle', () => {\n      expect(\n        JSON.stringify(\n          splitUserInputAfterAutocomplete(\n            'my |lang:TypeScript| simple |path:src/index.js| string',\n          ),\n        ),\n      ).toEqual(\n        JSON.stringify([\n          { type: ParsedQueryTypeEnum.TEXT, text: 'my ' },\n          { type: ParsedQueryTypeEnum.LANG, text: 'TypeScript' },\n          { type: ParsedQueryTypeEnum.TEXT, text: ' simple ' },\n          { type: ParsedQueryTypeEnum.PATH, text: 'src/index.js' },\n          { type: ParsedQueryTypeEnum.TEXT, text: ' string' },\n        ]),\n      );\n    });\n    test('repo filter after lang filter in the middle', () => {\n      expect(\n        JSON.stringify(\n          splitUserInputAfterAutocomplete(\n            'my |lang:TypeScript| simple |repo:BloopAI/bloop| string',\n          ),\n        ),\n      ).toEqual(\n        JSON.stringify([\n          { type: ParsedQueryTypeEnum.TEXT, text: 'my ' },\n          { type: ParsedQueryTypeEnum.LANG, text: 'TypeScript' },\n          { type: ParsedQueryTypeEnum.TEXT, text: ' simple ' },\n          { type: ParsedQueryTypeEnum.REPO, text: 'BloopAI/bloop' },\n          { type: ParsedQueryTypeEnum.TEXT, text: ' string' },\n        ]),\n      );\n    });\n  });\n  describe('getFileExtensionForLang', () => {\n    test('main languages', () => {\n      expect(getFileExtensionForLang('JavaScript')).toEqual('index.js');\n      expect(getFileExtensionForLang('TypeScript')).toEqual('index.ts');\n      expect(getFileExtensionForLang('JSX')).toEqual('index.jsx');\n      expect(getFileExtensionForLang('Rust')).toEqual('index.rs');\n    });\n    test('lowercased', () => {\n      expect(getFileExtensionForLang('javascript', true)).toEqual('index.js');\n      expect(getFileExtensionForLang('typescript', true)).toEqual('index.ts');\n      expect(getFileExtensionForLang('jsx', true)).toEqual('index.jsx');\n      expect(getFileExtensionForLang('rust', true)).toEqual('index.rs');\n    });\n    test('unknown languages', () => {\n      expect(getFileExtensionForLang('asd', true)).toEqual('index.asd');\n      expect(getFileExtensionForLang('ASD')).toEqual('index.ASD');\n      expect(getFileExtensionForLang('Asd')).toEqual('index.Asd');\n    });\n    test('empty input', () => {\n      expect(getFileExtensionForLang('', true)).toEqual('default');\n      expect(getFileExtensionForLang('')).toEqual('default');\n    });\n  });\n  describe('getCommonFolder', () => {\n    test('no folder', () => {\n      expect(getCommonFolder(['index.js', '.gitignore'])).toEqual('');\n    });\n    test('no common folder', () => {\n      expect(getCommonFolder(['src/index.js', 'public/.gitignore'])).toEqual(\n        '',\n      );\n      expect(getCommonFolder(['/src/index.js', '/public/.gitignore'])).toEqual(\n        '',\n      );\n      expect(\n        getCommonFolder(['src/index.js', 'public/src/.gitignore']),\n      ).toEqual('');\n    });\n    test('one common folder', () => {\n      expect(\n        getCommonFolder(['src/components/index.js', 'src/utils.js']),\n      ).toEqual('src');\n      expect(\n        getCommonFolder(['src/components/index.js', 'src/utils/utils.js']),\n      ).toEqual('src');\n    });\n    test('two common folders', () => {\n      expect(\n        getCommonFolder(['src/components/index.js', 'src/components/utils.js']),\n      ).toEqual('src/components');\n    });\n    test('windows path', () => {\n      expect(\n        getCommonFolder([\n          '\\\\src\\\\components\\\\index.js',\n          '\\\\src\\\\components\\\\utils.js',\n        ]),\n      ).toEqual('\\\\src\\\\components');\n    });\n    test('empty input', () => {\n      expect(getCommonFolder([])).toEqual('/');\n    });\n  });\n  describe('concatenateParsedQuery', () => {\n    test('no filters used', () => {\n      expect(\n        concatenateParsedQuery([\n          { type: ParsedQueryTypeEnum.TEXT, text: 'Hello world!' },\n        ]),\n      ).toEqual('Hello world!');\n      expect(\n        concatenateParsedQuery([\n          { type: ParsedQueryTypeEnum.TEXT, text: 'Hello' },\n          { type: ParsedQueryTypeEnum.TEXT, text: ' world!' },\n        ]),\n      ).toEqual('Hello world!');\n    });\n    test('filters used', () => {\n      expect(\n        concatenateParsedQuery([\n          { type: ParsedQueryTypeEnum.TEXT, text: 'Hello ' },\n          { type: ParsedQueryTypeEnum.LANG, text: 'js' },\n          { type: ParsedQueryTypeEnum.TEXT, text: ' world ' },\n          { type: ParsedQueryTypeEnum.REPO, text: 'BloopAI/bloop' },\n          { type: ParsedQueryTypeEnum.TEXT, text: ' ' },\n          { type: ParsedQueryTypeEnum.PATH, text: 'src/index.js' },\n          { type: ParsedQueryTypeEnum.TEXT, text: ' ? ' },\n          { type: ParsedQueryTypeEnum.BRANCH, text: 'origin/main' },\n          { type: ParsedQueryTypeEnum.PATH, text: 'src/components/index.js' },\n        ]),\n      ).toEqual(\n        'Hello |lang:js| world |repo:BloopAI/bloop| |path:src/index.js| ? |path:src/components/index.js|',\n      );\n    });\n  });\n  describe('mergeRanges', () => {\n    test('empty ranges', () => {\n      expect(mergeRanges([])).toEqual([]);\n    });\n    test('no overlap ranges', () => {\n      expect(\n        mergeRanges([\n          [1, 5],\n          [7, 10],\n        ]),\n      ).toEqual([\n        [1, 5],\n        [7, 10],\n      ]);\n      expect(\n        mergeRanges([\n          [7, 10],\n          [1, 5],\n        ]),\n      ).toEqual([\n        [1, 5],\n        [7, 10],\n      ]);\n    });\n    test('no overlap ranges next to each other', () => {\n      expect(\n        mergeRanges([\n          [1, 5],\n          [6, 10],\n        ]),\n      ).toEqual([[1, 10]]);\n      expect(\n        mergeRanges([\n          [7, 10],\n          [1, 5],\n          [11, 15],\n        ]),\n      ).toEqual([\n        [1, 5],\n        [7, 15],\n      ]);\n    });\n    test('overlap ranges', () => {\n      expect(\n        mergeRanges([\n          [1, 5],\n          [3, 10],\n        ]),\n      ).toEqual([[1, 10]]);\n      expect(\n        mergeRanges([\n          [7, 10],\n          [1, 5],\n          [9, 15],\n        ]),\n      ).toEqual([\n        [1, 5],\n        [7, 15],\n      ]);\n    });\n  });\n  describe('humanNumber', () => {\n    test('< 1000', () => {\n      expect(humanNumber(123)).toEqual('123');\n      expect(humanNumber(999)).toEqual('999');\n      expect(humanNumber(5)).toEqual('5');\n    });\n    test('> 1000', () => {\n      expect(humanNumber(1000)).toEqual('1k');\n      expect(humanNumber(1001)).toEqual('1k');\n      expect(humanNumber(5432)).toEqual('5.4k');\n      expect(humanNumber(5999)).toEqual('6k');\n      expect(humanNumber(10009)).toEqual('10k');\n      expect(humanNumber(100009)).toEqual('100k');\n      expect(humanNumber(1000009)).toEqual('1000k');\n    });\n    test('none', () => {\n      expect(humanNumber(0)).toEqual(0);\n    });\n  });\n});\n"
  },
  {
    "path": "client/src/utils/index.ts",
    "content": "import { MouseEvent } from 'react';\nimport { v4 as uuidv4 } from 'uuid';\nimport { es, ja, zhCN, zhTW } from 'date-fns/locale';\nimport {\n  LocaleType,\n  ParsedQueryType,\n  ParsedQueryTypeEnum,\n  RepoType,\n  RepoUi,\n} from '../types/general';\nimport { PathParts } from '../components/Breadcrumbs';\nimport langs from './langs.json';\n\nexport const copyToClipboard = (value: string) => {\n  // navigator clipboard api needs a secure context (https)\n  if (\n    navigator.clipboard &&\n    typeof navigator.clipboard.writeText === 'function' &&\n    window.isSecureContext\n  ) {\n    return navigator.clipboard.writeText(value).then();\n  } else {\n    let textArea = document.createElement('textarea');\n    textArea.value = value;\n    // make the textarea out of viewport\n    textArea.style.position = 'fixed';\n    textArea.style.left = '-999999px';\n    textArea.style.top = '-999999px';\n    document.body.appendChild(textArea);\n    textArea.focus();\n    textArea.select();\n    return new Promise((res, rej) => {\n      document.execCommand('copy') ? res(true) : rej();\n      textArea.remove();\n    });\n  }\n};\n\n/**\n * Returns a hash code from a string, only use for comparison as not secure\n * @param  {String} str The string to hash.\n * @return {Number}    A 32bit integer\n * @see http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/\n */\nexport const hashCode = (str: string) => {\n  let hash = 0;\n  for (let i = 0, len = str.length; i < len; i++) {\n    let chr = str.charCodeAt(i);\n    hash = (hash << 5) - hash + chr;\n    hash |= 0; // Convert to 32bit integer\n  }\n  return hash;\n};\n\nexport const parseFilters = (input: string) => {\n  const regex = /((repo|lang):[^\\s)]+)/gim;\n\n  let m;\n  const filters: Record<string, string[]> = {\n    lang: [],\n    repo: [],\n  };\n\n  while ((m = regex.exec(input)) !== null) {\n    if (m.index === regex.lastIndex) {\n      regex.lastIndex++;\n    }\n    if (m[2]) {\n      filters[m[2]].push(m[1].split(':')[1]);\n    }\n  }\n  return filters;\n};\n\nexport const getFileExtensionForLang = (lang: string, lowercased?: boolean) => {\n  if (!lang) {\n    return 'default';\n  }\n  // @ts-ignore\n  let ext = langs[lang]?.[0];\n  if (lowercased) {\n    const key = Object.keys(langs).find((key) => key?.toLowerCase() === lang);\n    if (key) {\n      // @ts-ignore\n      ext = langs[key]?.[0];\n    }\n  }\n  return 'index' + (ext || `.${lang}`);\n};\n\nexport const getPrettyLangName = (lang: string) => {\n  switch (lang) {\n    case 'js':\n    case 'jsx':\n      return 'JavaScript';\n    case 'ts':\n    case 'tsx':\n      return 'TypeScript';\n    default:\n      return Object.keys(langs).find((key) => key?.toLowerCase() === lang);\n  }\n};\n\nexport const isWindowsPath = (path: string) => path.includes('\\\\');\n\nexport const breadcrumbsItemPath = (\n  array: string[],\n  index: number,\n  isWindows: boolean,\n  isFile?: boolean,\n) => {\n  const separator = isWindows ? '\\\\' : '/';\n  const path = array.slice(0, index + 1).join(separator);\n\n  const pathEnding = isFile ? '' : separator;\n  return `${path}${pathEnding}`;\n};\n\nexport const splitPath = (path: string) =>\n  path?.split(isWindowsPath(path) ? '\\\\' : '/') || [];\n\nexport const splitPathForBreadcrumbs = (\n  path: string,\n  onClick?: (\n    e: MouseEvent | null,\n    item: string,\n    index: number,\n    arr: string[],\n  ) => void,\n): PathParts[] => {\n  return splitPath(path)\n    .filter((p) => p !== '/')\n    .map(\n      (item, index, arr): PathParts => ({\n        label: item,\n        onClick: (e?: MouseEvent) => {\n          e?.preventDefault();\n          onClick?.(e || null, item, index, arr);\n        },\n      }),\n    );\n};\n\nexport const buildRepoQuery = (\n  repo?: string,\n  path?: string,\n  selectedBranch?: string | null,\n) => {\n  return `open:true ${repo ? `repo:${repo}` : ''} ${\n    path ? `path:${path.includes(' ') ? `\"${path}\"` : path}` : ''\n  }${selectedBranch ? ` branch:${selectedBranch}` : ''}`;\n};\n\nexport const getFileManagerName = (os: string) => {\n  switch (os) {\n    case 'Darwin':\n      return 'Finder';\n    case 'Windows_NT':\n      return 'File Explorer';\n    default:\n      return 'File manager';\n  }\n};\n\nexport function groupReposByParentFolder(repos: RepoType[]): RepoUi[] {\n  const isWindows = repos?.[0]?.ref ? isWindowsPath(repos[0].ref) : false;\n  // Extract unique parent folders\n  const parentFolders = Array.from(\n    new Set(\n      repos.map((obj) =>\n        obj.ref\n          .split(isWindows ? '\\\\' : '/')\n          .slice(0, -1)\n          .join(isWindows ? '\\\\' : '/'),\n      ),\n    ),\n  );\n\n  // Group repos by parent folder\n  const groupedObjects: {\n    [parentFolder: string]: string[];\n  } = {};\n  for (const parentFolder of parentFolders) {\n    groupedObjects[parentFolder] = repos\n      .filter((obj) => obj.ref.startsWith(parentFolder + '/'))\n      .map((r) => r.ref);\n  }\n\n  // Add folderName property to each repo\n  const objectsWithFolderName: RepoUi[] = repos.map((r) => {\n    const folderName =\n      Object.entries(groupedObjects)\n        .filter(([folder, repos]) => repos.includes(r.ref))\n        .sort((a, b) => (a[0].length < b[0].length ? -1 : 1))\n        .pop()?.[0] || isWindows\n        ? '\\\\'\n        : '/';\n    return {\n      ...r,\n      folderName,\n      selected: true,\n      shortName: r.name,\n    };\n  });\n\n  const commonFolder = getCommonFolder(\n    objectsWithFolderName.map((lr) => lr.folderName),\n  )\n    .split(isWindows ? '\\\\' : '/')\n    .slice(0, -1)\n    .join(isWindows ? '\\\\' : '/');\n\n  return objectsWithFolderName.map((r) => ({\n    ...r,\n    folderName: r.folderName.replace(commonFolder, ''),\n  }));\n}\n\nexport const getCommonFolder = (paths: string[]) => {\n  if (!paths?.length) {\n    return '/';\n  }\n  const pathParts = paths\n    .map((p) => splitPath(p))\n    .sort((a, b) => a.length - b.length);\n  let commonFolder = [];\n\n  for (let i = 0; i < pathParts[0].length; i++) {\n    if (pathParts.every((pp) => pp[i] === pathParts[0][i])) {\n      commonFolder.push(pathParts[0][i]);\n    } else {\n      break;\n    }\n  }\n  return commonFolder.join(isWindowsPath(paths[0]) ? '\\\\' : '/');\n};\n\nexport const arrayUnique = (array: any[], property: string) => {\n  const unique: any = {};\n  const distinct = [];\n  for (const i in array) {\n    if (typeof unique[array[i][property]] == 'undefined') {\n      distinct.push(array[i]);\n    }\n    unique[array[i][property]] = 0;\n  }\n  return distinct;\n};\n\nexport const generateUniqueId = (): string => {\n  return uuidv4();\n};\n\nexport const deleteAuthCookie = () => {\n  document.cookie =\n    'X-Bleep-Cognito=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';\n};\n\nexport const previewTheme = (key: string) => {\n  document.body.classList.add('notransition'); // to avoid flashing UI with color changes\n  document.body.dataset.theme = key;\n  setTimeout(\n    () => document.body.classList.remove('notransition'),\n    300, // longest color transition\n  );\n};\n\nexport const calculatePopupPositionInsideContainer = (\n  top: number,\n  left: number,\n  containerRect: DOMRect,\n) => {\n  const viewportWidth =\n    window.innerWidth || document.documentElement.clientWidth;\n  const viewportHeight =\n    window.innerHeight || document.documentElement.clientHeight;\n\n  const popupWidth = 170; // Adjust as needed\n  const popupHeight = 34; // Adjust as needed\n\n  top -= popupHeight + 15;\n\n  // Adjust top position to ensure the popup stays within the container\n  if (top < containerRect.top) {\n    top = containerRect.top;\n  } else if (top > containerRect.bottom) {\n    top = containerRect.bottom - popupHeight;\n  }\n\n  // Adjust left position to ensure the popup stays within the container\n  if (left < containerRect.left) {\n    left = containerRect.left;\n  } else if (left > containerRect.right) {\n    left = containerRect.right - popupWidth;\n  }\n\n  // Adjust top position to ensure the popup stays within the viewport\n  if (top < 0) {\n    top = 0;\n  } else if (top + popupHeight > viewportHeight) {\n    top = viewportHeight - popupHeight;\n  }\n\n  // Adjust left position to ensure the popup stays within the viewport\n  if (left < 0) {\n    left = 0;\n  } else if (left + popupWidth > viewportWidth) {\n    left = viewportWidth - popupWidth;\n  }\n\n  return { top, left };\n};\n\nfunction getLineNumber(element: HTMLElement | null) {\n  while (element) {\n    if (element?.dataset?.['line-number'] || element?.dataset?.lineNumber) {\n      return element.dataset['line-number'] || element.dataset.lineNumber;\n    }\n    element = element.parentElement;\n  }\n  return null;\n}\n\nexport function getSelectionLines(element: HTMLElement): null | number {\n  if (!element) {\n    return null;\n  }\n\n  const lineNumber = getLineNumber(element);\n\n  return lineNumber ? Number(lineNumber) : null;\n}\n\nexport const escapeHtml = (unsafe: string) => {\n  return unsafe\n    .replaceAll('&', '&amp;')\n    .replaceAll('<', '&lt;')\n    .replaceAll('>', '&gt;')\n    .replaceAll('\"', '&quot;')\n    .replaceAll(\"'\", '&#039;');\n};\n\nexport function humanFileSize(\n  bytes: number,\n  si: boolean = true,\n  dp: number = 1,\n) {\n  const thresh = si ? 1000 : 1024;\n\n  if (Math.abs(bytes) < thresh) {\n    return bytes + ' B';\n  }\n\n  const units = si\n    ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']\n    : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];\n  let u = -1;\n  const r = 10 ** dp;\n\n  do {\n    bytes /= thresh;\n    ++u;\n  } while (\n    Math.round(Math.abs(bytes) * r) / r >= thresh &&\n    u < units.length - 1\n  );\n\n  return bytes.toFixed(dp) + ' ' + units[u];\n}\n\nexport function humanNumber(num: number) {\n  if (!num) {\n    return num;\n  }\n  if (num < 1000) {\n    return num.toString();\n  }\n  return `${parseFloat((num / 1000).toFixed(1))}k`;\n}\n\nexport const getDateFnsLocale = (locale: LocaleType) => {\n  switch (locale) {\n    case 'ja':\n      return { locale: ja };\n    case 'zhCN':\n      return { locale: zhCN };\n    case 'zhTW':\n      return { locale: zhTW };\n    case 'es':\n      return { locale: es };\n    default:\n      return undefined;\n  }\n};\n\nexport function mergeRanges(ranges: [number, number][]): [number, number][] {\n  ranges.sort((a, b) => a[0] - b[0]);\n\n  const mergedRanges: [number, number][] = [];\n\n  if (ranges.length === 0) {\n    return mergedRanges;\n  }\n\n  let currentRange = ranges[0];\n\n  for (let i = 1; i < ranges.length; i++) {\n    const nextRange = ranges[i];\n\n    if (nextRange[0] <= currentRange[1] + 1) {\n      currentRange[1] = Math.max(currentRange[1], nextRange[1]);\n    } else {\n      mergedRanges.push(currentRange);\n      currentRange = nextRange;\n    }\n  }\n\n  mergedRanges.push(currentRange);\n\n  return mergedRanges;\n}\n\nexport function splitUserInputAfterAutocomplete(\n  input: string,\n): ParsedQueryType[] {\n  const pathRegex = /\\|path:(.*?)\\|/g;\n  const langRegex = /\\|lang:(.*?)\\|/g;\n  const combinedRegex = /\\|(path|lang|repo):(.*?)\\|/g;\n  const result: ParsedQueryType[] = [];\n\n  let lastIndex = 0;\n\n  const addTextContent = (text: string) => {\n    if (text.length > 0) {\n      result.push({ type: ParsedQueryTypeEnum.TEXT, text });\n    }\n  };\n\n  input.replace(combinedRegex, (_, type, text, index) => {\n    addTextContent(input.substring(lastIndex, index));\n    result.push({\n      type:\n        type === 'lang'\n          ? ParsedQueryTypeEnum.LANG\n          : type === 'repo'\n          ? ParsedQueryTypeEnum.REPO\n          : ParsedQueryTypeEnum.PATH,\n      text,\n    });\n    lastIndex = index + text.length + type.length + 3; // 3 is the length of \"(type:\"\n    return '';\n  });\n\n  addTextContent(input.substring(lastIndex));\n\n  return result;\n}\n\nexport function concatenateParsedQuery(query: ParsedQueryType[]) {\n  let result = '';\n  query.forEach((q) => {\n    if (q.type === ParsedQueryTypeEnum.TEXT) {\n      result += q.text;\n    } else if (q.type === ParsedQueryTypeEnum.PATH) {\n      result += `|path:${q.text}|`;\n    } else if (q.type === ParsedQueryTypeEnum.LANG) {\n      result += `|lang:${q.text}|`;\n    } else if (q.type === ParsedQueryTypeEnum.REPO) {\n      result += `|repo:${q.text}|`;\n    }\n  });\n  return result;\n}\n\nexport const noOp = () => {};\n"
  },
  {
    "path": "client/src/utils/keyboardUtils.ts",
    "content": "export const mapShortcuts = (shortcut?: string[], osType?: string) => {\n  return shortcut?.map((k) => {\n    switch (k) {\n      case 'option':\n        return osType === 'Darwin' ? '⌥' : 'Alt';\n      case 'entr':\n        return '↵';\n      case 'cmd':\n        return osType === 'Darwin' ? '⌘' : 'Ctrl';\n      case 'bksp':\n        return '⌫';\n      case 'shift':\n        return '⇧';\n      default:\n        return k;\n    }\n  });\n};\n\nexport const checkEventKeys = (e: KeyboardEvent, shortcut?: string[]) => {\n  if (!shortcut) {\n    return false;\n  }\n  const keys = shortcut.map((k) => k?.toLowerCase());\n  if (\n    (keys.includes('cmd') && !(e.metaKey || e.ctrlKey)) ||\n    (!keys.includes('cmd') && (e.metaKey || e.ctrlKey))\n  ) {\n    return false;\n  }\n  if (\n    (keys.includes('shift') && !e.shiftKey) ||\n    (!keys.includes('shift') && e.shiftKey)\n  ) {\n    return false;\n  }\n  if (\n    (keys.includes('option') && !e.altKey) ||\n    (!keys.includes('option') && e.altKey)\n  ) {\n    return false;\n  }\n  if (e.key === 'Enter' && keys.includes('entr')) {\n    return true;\n  }\n  if (e.key === 'Backspace' && keys.includes('bksp')) {\n    return true;\n  }\n  if (e.key === 'Escape' && keys.includes('esc')) {\n    return true;\n  }\n  if (!keys.includes(e.key)) {\n    if (\n      (e.altKey &&\n        keys.includes(\n          e.code.replace('Key', '').replace('Digit', '')?.toLowerCase(),\n        )) ||\n      (e.shiftKey && keys.includes(e.code.replace('Digit', '')?.toLowerCase()))\n    ) {\n      return true;\n    }\n    if (e.key === '=' && keys.includes('+')) {\n      return true;\n    }\n    return false;\n  }\n  return true;\n};\n"
  },
  {
    "path": "client/src/utils/langs.json",
    "content": "{\n  \"ABAP\": [\n    \".abap\"\n  ],\n  \"AGS Script\": [\n    \".asc\",\n    \".ash\"\n  ],\n  \"AMPL\": [\n    \".ampl\",\n    \".mod\"\n  ],\n  \"ANTLR\": [\n    \".g4\"\n  ],\n  \"API Blueprint\": [\n    \".apib\"\n  ],\n  \"APL\": [\n    \".apl\",\n    \".dyalog\"\n  ],\n  \"ASP\": [\n    \".asp\",\n    \".asax\",\n    \".ascx\",\n    \".ashx\",\n    \".asmx\",\n    \".aspx\",\n    \".axd\"\n  ],\n  \"ATS\": [\n    \".dats\",\n    \".hats\",\n    \".sats\"\n  ],\n  \"ActionScript\": [\n    \".as\"\n  ],\n  \"Ada\": [\n    \".adb\",\n    \".ada\",\n    \".ads\"\n  ],\n  \"Agda\": [\n    \".agda\"\n  ],\n  \"Alloy\": [\n    \".als\"\n  ],\n  \"ApacheConf\": [\n    \".apacheconf\",\n    \".vhost\"\n  ],\n  \"Apex\": [\n    \".cls\"\n  ],\n  \"AppleScript\": [\n    \".applescript\",\n    \".scpt\"\n  ],\n  \"Arc\": [\n    \".arc\"\n  ],\n  \"Arduino\": [\n    \".ino\"\n  ],\n  \"AsciiDoc\": [\n    \".asciidoc\",\n    \".adoc\",\n    \".asc\"\n  ],\n  \"AspectJ\": [\n    \".aj\"\n  ],\n  \"Assembly\": [\n    \".asm\",\n    \".a51\",\n    \".inc\",\n    \".nasm\"\n  ],\n  \"Augeas\": [\n    \".aug\"\n  ],\n  \"AutoHotkey\": [\n    \".ahk\",\n    \".ahkl\"\n  ],\n  \"AutoIt\": [\n    \".au3\"\n  ],\n  \"Awk\": [\n    \".awk\",\n    \".auk\",\n    \".gawk\",\n    \".mawk\",\n    \".nawk\"\n  ],\n  \"Batchfile\": [\n    \".bat\",\n    \".cmd\"\n  ],\n  \"Befunge\": [\n    \".befunge\"\n  ],\n  \"Bison\": [\n    \".bison\"\n  ],\n  \"BitBake\": [\n    \".bb\"\n  ],\n  \"BlitzBasic\": [\n    \".bb\",\n    \".decls\"\n  ],\n  \"BlitzMax\": [\n    \".bmx\"\n  ],\n  \"Bluespec\": [\n    \".bsv\"\n  ],\n  \"Boo\": [\n    \".boo\"\n  ],\n  \"Brainfuck\": [\n    \".b\",\n    \".bf\"\n  ],\n  \"Brightscript\": [\n    \".brs\"\n  ],\n  \"Bro\": [\n    \".bro\"\n  ],\n  \"C\": [\n    \".c\",\n    \".cats\",\n    \".h\",\n    \".idc\",\n    \".w\"\n  ],\n  \"C#\": [\n    \".cs\",\n    \".cake\",\n    \".cshtml\",\n    \".csx\"\n  ],\n  \"C++\": [\n    \".cpp\",\n    \".c++\",\n    \".cc\",\n    \".cp\",\n    \".cxx\",\n    \".h\",\n    \".h++\",\n    \".hh\",\n    \".hpp\",\n    \".hxx\",\n    \".inc\",\n    \".inl\",\n    \".ipp\",\n    \".tcc\",\n    \".tpp\"\n  ],\n  \"C-ObjDump\": [\n    \".c-objdump\"\n  ],\n  \"C2hs Haskell\": [\n    \".chs\"\n  ],\n  \"CLIPS\": [\n    \".clp\"\n  ],\n  \"CMake\": [\n    \".cmake\",\n    \".cmake.in\"\n  ],\n  \"COBOL\": [\n    \".cob\",\n    \".cbl\",\n    \".ccp\",\n    \".cobol\",\n    \".cpy\"\n  ],\n  \"CSS\": [\n    \".css\"\n  ],\n  \"CSV\": [\n    \".csv\"\n  ],\n  \"Cap'n Proto\": [\n    \".capnp\"\n  ],\n  \"CartoCSS\": [\n    \".mss\"\n  ],\n  \"Ceylon\": [\n    \".ceylon\"\n  ],\n  \"Chapel\": [\n    \".chpl\"\n  ],\n  \"Charity\": [\n    \".ch\"\n  ],\n  \"ChucK\": [\n    \".ck\"\n  ],\n  \"Cirru\": [\n    \".cirru\"\n  ],\n  \"Clarion\": [\n    \".clw\"\n  ],\n  \"Clean\": [\n    \".icl\",\n    \".dcl\"\n  ],\n  \"Click\": [\n    \".click\"\n  ],\n  \"Clojure\": [\n    \".clj\",\n    \".boot\",\n    \".cl2\",\n    \".cljc\",\n    \".cljs\",\n    \".cljs.hl\",\n    \".cljscm\",\n    \".cljx\",\n    \".hic\"\n  ],\n  \"CoffeeScript\": [\n    \".coffee\",\n    \"._coffee\",\n    \".cake\",\n    \".cjsx\",\n    \".cson\",\n    \".iced\"\n  ],\n  \"ColdFusion\": [\n    \".cfm\",\n    \".cfml\"\n  ],\n  \"ColdFusion CFC\": [\n    \".cfc\"\n  ],\n  \"Common Lisp\": [\n    \".lisp\",\n    \".asd\",\n    \".cl\",\n    \".l\",\n    \".lsp\",\n    \".ny\",\n    \".podsl\",\n    \".sexp\"\n  ],\n  \"Component Pascal\": [\n    \".cp\",\n    \".cps\"\n  ],\n  \"Cool\": [\n    \".cl\"\n  ],\n  \"Coq\": [\n    \".coq\",\n    \".v\"\n  ],\n  \"Cpp-ObjDump\": [\n    \".cppobjdump\",\n    \".c++-objdump\",\n    \".c++objdump\",\n    \".cpp-objdump\",\n    \".cxx-objdump\"\n  ],\n  \"Creole\": [\n    \".creole\"\n  ],\n  \"Crystal\": [\n    \".cr\"\n  ],\n  \"Cucumber\": [\n    \".feature\"\n  ],\n  \"Cuda\": [\n    \".cu\",\n    \".cuh\"\n  ],\n  \"Cycript\": [\n    \".cy\"\n  ],\n  \"Cython\": [\n    \".pyx\",\n    \".pxd\",\n    \".pxi\"\n  ],\n  \"D\": [\n    \".d\",\n    \".di\"\n  ],\n  \"D-ObjDump\": [\n    \".d-objdump\"\n  ],\n  \"DIGITAL Command Language\": [\n    \".com\"\n  ],\n  \"DM\": [\n    \".dm\"\n  ],\n  \"DNS Zone\": [\n    \".zone\",\n    \".arpa\"\n  ],\n  \"DTrace\": [\n    \".d\"\n  ],\n  \"Darcs Patch\": [\n    \".darcspatch\",\n    \".dpatch\"\n  ],\n  \"Dart\": [\n    \".dart\"\n  ],\n  \"Diff\": [\n    \".diff\",\n    \".patch\"\n  ],\n  \"Dockerfile\": [\n    \".dockerfile\"\n  ],\n  \"Dogescript\": [\n    \".djs\"\n  ],\n  \"Dylan\": [\n    \".dylan\",\n    \".dyl\",\n    \".intr\",\n    \".lid\"\n  ],\n  \"E\": [\n    \".E\"\n  ],\n  \"ECL\": [\n    \".ecl\",\n    \".eclxml\"\n  ],\n  \"ECLiPSe\": [\n    \".ecl\"\n  ],\n  \"Eagle\": [\n    \".sch\",\n    \".brd\"\n  ],\n  \"Ecere Projects\": [\n    \".epj\"\n  ],\n  \"Eiffel\": [\n    \".e\"\n  ],\n  \"Elixir\": [\n    \".ex\",\n    \".exs\"\n  ],\n  \"Elm\": [\n    \".elm\"\n  ],\n  \"Emacs Lisp\": [\n    \".el\",\n    \".emacs\",\n    \".emacs.desktop\"\n  ],\n  \"EmberScript\": [\n    \".em\",\n    \".emberscript\"\n  ],\n  \"Erlang\": [\n    \".erl\",\n    \".es\",\n    \".escript\",\n    \".hrl\",\n    \".xrl\",\n    \".yrl\"\n  ],\n  \"F#\": [\n    \".fs\",\n    \".fsi\",\n    \".fsx\"\n  ],\n  \"FLUX\": [\n    \".fx\",\n    \".flux\"\n  ],\n  \"FORTRAN\": [\n    \".f90\",\n    \".f\",\n    \".f03\",\n    \".f08\",\n    \".f77\",\n    \".f95\",\n    \".for\",\n    \".fpp\"\n  ],\n  \"Factor\": [\n    \".factor\"\n  ],\n  \"Fancy\": [\n    \".fy\",\n    \".fancypack\"\n  ],\n  \"Fantom\": [\n    \".fan\"\n  ],\n  \"Filterscript\": [\n    \".fs\"\n  ],\n  \"Formatted\": [\n    \".for\",\n    \".eam.fs\"\n  ],\n  \"Forth\": [\n    \".fth\",\n    \".4th\",\n    \".f\",\n    \".for\",\n    \".forth\",\n    \".fr\",\n    \".frt\",\n    \".fs\"\n  ],\n  \"FreeMarker\": [\n    \".ftl\"\n  ],\n  \"Frege\": [\n    \".fr\"\n  ],\n  \"G-code\": [\n    \".g\",\n    \".gco\",\n    \".gcode\"\n  ],\n  \"GAMS\": [\n    \".gms\"\n  ],\n  \"GAP\": [\n    \".g\",\n    \".gap\",\n    \".gd\",\n    \".gi\",\n    \".tst\"\n  ],\n  \"GAS\": [\n    \".s\",\n    \".ms\"\n  ],\n  \"GDScript\": [\n    \".gd\"\n  ],\n  \"GLSL\": [\n    \".glsl\",\n    \".fp\",\n    \".frag\",\n    \".frg\",\n    \".fs\",\n    \".fsh\",\n    \".fshader\",\n    \".geo\",\n    \".geom\",\n    \".glslv\",\n    \".gshader\",\n    \".shader\",\n    \".vert\",\n    \".vrx\",\n    \".vsh\",\n    \".vshader\"\n  ],\n  \"Game Maker Language\": [\n    \".gml\"\n  ],\n  \"Genshi\": [\n    \".kid\"\n  ],\n  \"Gentoo Ebuild\": [\n    \".ebuild\"\n  ],\n  \"Gentoo Eclass\": [\n    \".eclass\"\n  ],\n  \"Gettext Catalog\": [\n    \".po\",\n    \".pot\"\n  ],\n  \"Glyph\": [\n    \".glf\"\n  ],\n  \"Gnuplot\": [\n    \".gp\",\n    \".gnu\",\n    \".gnuplot\",\n    \".plot\",\n    \".plt\"\n  ],\n  \"Go\": [\n    \".go\"\n  ],\n  \"Golo\": [\n    \".golo\"\n  ],\n  \"Gosu\": [\n    \".gs\",\n    \".gst\",\n    \".gsx\",\n    \".vark\"\n  ],\n  \"Grace\": [\n    \".grace\"\n  ],\n  \"Gradle\": [\n    \".gradle\"\n  ],\n  \"Grammatical Framework\": [\n    \".gf\"\n  ],\n  \"Graph Modeling Language\": [\n    \".gml\"\n  ],\n  \"GraphQL\": [\n    \".graphql\"\n  ],\n  \"Graphviz (DOT)\": [\n    \".dot\",\n    \".gv\"\n  ],\n  \"Groff\": [\n    \".man\",\n    \".1\",\n    \".1in\",\n    \".1m\",\n    \".1x\",\n    \".2\",\n    \".3\",\n    \".3in\",\n    \".3m\",\n    \".3qt\",\n    \".3x\",\n    \".4\",\n    \".5\",\n    \".6\",\n    \".7\",\n    \".8\",\n    \".9\",\n    \".l\",\n    \".me\",\n    \".ms\",\n    \".n\",\n    \".rno\",\n    \".roff\"\n  ],\n  \"Groovy\": [\n    \".groovy\",\n    \".grt\",\n    \".gtpl\",\n    \".gvy\"\n  ],\n  \"Groovy Server Pages\": [\n    \".gsp\"\n  ],\n  \"HCL\": [\n    \".hcl\",\n    \".tf\"\n  ],\n  \"HLSL\": [\n    \".hlsl\",\n    \".fx\",\n    \".fxh\",\n    \".hlsli\"\n  ],\n  \"HTML\": [\n    \".html\",\n    \".htm\",\n    \".html.hl\",\n    \".inc\",\n    \".st\",\n    \".xht\",\n    \".xhtml\"\n  ],\n  \"HTML+Django\": [\n    \".mustache\",\n    \".jinja\"\n  ],\n  \"HTML+EEX\": [\n    \".eex\"\n  ],\n  \"HTML+ERB\": [\n    \".erb\",\n    \".erb.deface\"\n  ],\n  \"HTML+PHP\": [\n    \".phtml\"\n  ],\n  \"HTTP\": [\n    \".http\"\n  ],\n  \"Hack\": [\n    \".hh\",\n    \".php\"\n  ],\n  \"Haml\": [\n    \".haml\",\n    \".haml.deface\"\n  ],\n  \"Handlebars\": [\n    \".handlebars\",\n    \".hbs\"\n  ],\n  \"Harbour\": [\n    \".hb\"\n  ],\n  \"Haskell\": [\n    \".hs\",\n    \".hsc\"\n  ],\n  \"Haxe\": [\n    \".hx\",\n    \".hxsl\"\n  ],\n  \"Hy\": [\n    \".hy\"\n  ],\n  \"HyPhy\": [\n    \".bf\"\n  ],\n  \"IDL\": [\n    \".pro\",\n    \".dlm\"\n  ],\n  \"IGOR Pro\": [\n    \".ipf\"\n  ],\n  \"INI\": [\n    \".ini\",\n    \".cfg\",\n    \".prefs\",\n    \".pro\",\n    \".properties\"\n  ],\n  \"IRC log\": [\n    \".irclog\",\n    \".weechatlog\"\n  ],\n  \"Idris\": [\n    \".idr\",\n    \".lidr\"\n  ],\n  \"Inform 7\": [\n    \".ni\",\n    \".i7x\"\n  ],\n  \"Inno Setup\": [\n    \".iss\"\n  ],\n  \"Io\": [\n    \".io\"\n  ],\n  \"Ioke\": [\n    \".ik\"\n  ],\n  \"Isabelle\": [\n    \".thy\"\n  ],\n  \"J\": [\n    \".ijs\"\n  ],\n  \"JFlex\": [\n    \".flex\",\n    \".jflex\"\n  ],\n  \"JSON\": [\n    \".json\",\n    \".geojson\",\n    \".lock\",\n    \".topojson\"\n  ],\n  \"JSON5\": [\n    \".json5\"\n  ],\n  \"JSONLD\": [\n    \".jsonld\"\n  ],\n  \"JSONiq\": [\n    \".jq\"\n  ],\n  \"JSX\": [\n    \".jsx\"\n  ],\n  \"Jade\": [\n    \".jade\"\n  ],\n  \"Jasmin\": [\n    \".j\"\n  ],\n  \"Java\": [\n    \".java\"\n  ],\n  \"Java Server Pages\": [\n    \".jsp\"\n  ],\n  \"JavaScript\": [\n    \".js\",\n    \"._js\",\n    \".bones\",\n    \".es\",\n    \".es6\",\n    \".frag\",\n    \".gs\",\n    \".jake\",\n    \".jsb\",\n    \".jscad\",\n    \".jsfl\",\n    \".jsm\",\n    \".jss\",\n    \".njs\",\n    \".pac\",\n    \".sjs\",\n    \".ssjs\",\n    \".sublime-build\",\n    \".sublime-commands\",\n    \".sublime-completions\",\n    \".sublime-keymap\",\n    \".sublime-macro\",\n    \".sublime-menu\",\n    \".sublime-mousemap\",\n    \".sublime-project\",\n    \".sublime-settings\",\n    \".sublime-theme\",\n    \".sublime-workspace\",\n    \".sublime_metrics\",\n    \".sublime_session\",\n    \".xsjs\",\n    \".xsjslib\"\n  ],\n  \"Julia\": [\n    \".jl\"\n  ],\n  \"Jupyter Notebook\": [\n    \".ipynb\"\n  ],\n  \"KRL\": [\n    \".krl\"\n  ],\n  \"KiCad\": [\n    \".sch\",\n    \".brd\",\n    \".kicad_pcb\"\n  ],\n  \"Kit\": [\n    \".kit\"\n  ],\n  \"Kotlin\": [\n    \".kt\",\n    \".ktm\",\n    \".kts\"\n  ],\n  \"LFE\": [\n    \".lfe\"\n  ],\n  \"LLVM\": [\n    \".ll\"\n  ],\n  \"LOLCODE\": [\n    \".lol\"\n  ],\n  \"LSL\": [\n    \".lsl\",\n    \".lslp\"\n  ],\n  \"LabVIEW\": [\n    \".lvproj\"\n  ],\n  \"Lasso\": [\n    \".lasso\",\n    \".las\",\n    \".lasso8\",\n    \".lasso9\",\n    \".ldml\"\n  ],\n  \"Latte\": [\n    \".latte\"\n  ],\n  \"Lean\": [\n    \".lean\",\n    \".hlean\"\n  ],\n  \"Less\": [\n    \".less\"\n  ],\n  \"Lex\": [\n    \".l\",\n    \".lex\"\n  ],\n  \"LilyPond\": [\n    \".ly\",\n    \".ily\"\n  ],\n  \"Limbo\": [\n    \".b\",\n    \".m\"\n  ],\n  \"Linker Script\": [\n    \".ld\",\n    \".lds\"\n  ],\n  \"Linux Kernel Module\": [\n    \".mod\"\n  ],\n  \"Liquid\": [\n    \".liquid\"\n  ],\n  \"Literate Agda\": [\n    \".lagda\"\n  ],\n  \"Literate CoffeeScript\": [\n    \".litcoffee\"\n  ],\n  \"Literate Haskell\": [\n    \".lhs\"\n  ],\n  \"LiveScript\": [\n    \".ls\",\n    \"._ls\"\n  ],\n  \"Logos\": [\n    \".xm\",\n    \".x\",\n    \".xi\"\n  ],\n  \"Logtalk\": [\n    \".lgt\",\n    \".logtalk\"\n  ],\n  \"LookML\": [\n    \".lookml\"\n  ],\n  \"LoomScript\": [\n    \".ls\"\n  ],\n  \"Lua\": [\n    \".lua\",\n    \".fcgi\",\n    \".nse\",\n    \".pd_lua\",\n    \".rbxs\",\n    \".wlua\"\n  ],\n  \"M\": [\n    \".mumps\",\n    \".m\"\n  ],\n  \"M4\": [\n    \".m4\"\n  ],\n  \"M4Sugar\": [\n    \".m4\"\n  ],\n  \"MAXScript\": [\n    \".ms\",\n    \".mcr\"\n  ],\n  \"MTML\": [\n    \".mtml\"\n  ],\n  \"MUF\": [\n    \".muf\",\n    \".m\"\n  ],\n  \"Makefile\": [\n    \".mak\",\n    \".d\",\n    \".mk\",\n    \".mkfile\"\n  ],\n  \"Mako\": [\n    \".mako\",\n    \".mao\"\n  ],\n  \"Markdown\": [\n    \".md\",\n    \".markdown\",\n    \".mkd\",\n    \".mkdn\",\n    \".mkdown\",\n    \".ron\"\n  ],\n  \"Mask\": [\n    \".mask\"\n  ],\n  \"Mathematica\": [\n    \".mathematica\",\n    \".cdf\",\n    \".m\",\n    \".ma\",\n    \".mt\",\n    \".nb\",\n    \".nbp\",\n    \".wl\",\n    \".wlt\"\n  ],\n  \"Matlab\": [\n    \".matlab\",\n    \".m\"\n  ],\n  \"Max\": [\n    \".maxpat\",\n    \".maxhelp\",\n    \".maxproj\",\n    \".mxt\",\n    \".pat\"\n  ],\n  \"MediaWiki\": [\n    \".mediawiki\",\n    \".wiki\"\n  ],\n  \"Mercury\": [\n    \".m\",\n    \".moo\"\n  ],\n  \"Metal\": [\n    \".metal\"\n  ],\n  \"MiniD\": [\n    \".minid\"\n  ],\n  \"Mirah\": [\n    \".druby\",\n    \".duby\",\n    \".mir\",\n    \".mirah\"\n  ],\n  \"Modelica\": [\n    \".mo\"\n  ],\n  \"Modula-2\": [\n    \".mod\"\n  ],\n  \"Module Management System\": [\n    \".mms\",\n    \".mmk\"\n  ],\n  \"Monkey\": [\n    \".monkey\"\n  ],\n  \"Moocode\": [\n    \".moo\"\n  ],\n  \"MoonScript\": [\n    \".moon\"\n  ],\n  \"Myghty\": [\n    \".myt\"\n  ],\n  \"NCL\": [\n    \".ncl\"\n  ],\n  \"NL\": [\n    \".nl\"\n  ],\n  \"NSIS\": [\n    \".nsi\",\n    \".nsh\"\n  ],\n  \"Nemerle\": [\n    \".n\"\n  ],\n  \"NetLinx\": [\n    \".axs\",\n    \".axi\"\n  ],\n  \"NetLinx+ERB\": [\n    \".axs.erb\",\n    \".axi.erb\"\n  ],\n  \"NetLogo\": [\n    \".nlogo\"\n  ],\n  \"NewLisp\": [\n    \".nl\",\n    \".lisp\",\n    \".lsp\"\n  ],\n  \"Nginx\": [\n    \".nginxconf\",\n    \".vhost\"\n  ],\n  \"Nimrod\": [\n    \".nim\",\n    \".nimrod\"\n  ],\n  \"Ninja\": [\n    \".ninja\"\n  ],\n  \"Nit\": [\n    \".nit\"\n  ],\n  \"Nix\": [\n    \".nix\"\n  ],\n  \"Nu\": [\n    \".nu\"\n  ],\n  \"NumPy\": [\n    \".numpy\",\n    \".numpyw\",\n    \".numsc\"\n  ],\n  \"OCaml\": [\n    \".ml\",\n    \".eliom\",\n    \".eliomi\",\n    \".ml4\",\n    \".mli\",\n    \".mll\",\n    \".mly\"\n  ],\n  \"ObjDump\": [\n    \".objdump\"\n  ],\n  \"Objective-C\": [\n    \".m\",\n    \".h\"\n  ],\n  \"Objective-C++\": [\n    \".mm\"\n  ],\n  \"Objective-J\": [\n    \".j\",\n    \".sj\"\n  ],\n  \"Omgrofl\": [\n    \".omgrofl\"\n  ],\n  \"Opa\": [\n    \".opa\"\n  ],\n  \"Opal\": [\n    \".opal\"\n  ],\n  \"OpenCL\": [\n    \".cl\",\n    \".opencl\"\n  ],\n  \"OpenEdge ABL\": [\n    \".p\",\n    \".cls\"\n  ],\n  \"OpenSCAD\": [\n    \".scad\"\n  ],\n  \"Org\": [\n    \".org\"\n  ],\n  \"Ox\": [\n    \".ox\",\n    \".oxh\",\n    \".oxo\"\n  ],\n  \"Oxygene\": [\n    \".oxygene\"\n  ],\n  \"Oz\": [\n    \".oz\"\n  ],\n  \"PAWN\": [\n    \".pwn\",\n    \".inc\"\n  ],\n  \"PHP\": [\n    \".php\",\n    \".aw\",\n    \".ctp\",\n    \".fcgi\",\n    \".inc\",\n    \".php3\",\n    \".php4\",\n    \".php5\",\n    \".phps\",\n    \".phpt\"\n  ],\n  \"PLSQL\": [\n    \".pls\",\n    \".pck\",\n    \".pkb\",\n    \".pks\",\n    \".plb\",\n    \".plsql\",\n    \".sql\"\n  ],\n  \"PLpgSQL\": [\n    \".sql\"\n  ],\n  \"POV-Ray SDL\": [\n    \".pov\",\n    \".inc\"\n  ],\n  \"Pan\": [\n    \".pan\"\n  ],\n  \"Papyrus\": [\n    \".psc\"\n  ],\n  \"Parrot\": [\n    \".parrot\"\n  ],\n  \"Parrot Assembly\": [\n    \".pasm\"\n  ],\n  \"Parrot Internal Representation\": [\n    \".pir\"\n  ],\n  \"Pascal\": [\n    \".pas\",\n    \".dfm\",\n    \".dpr\",\n    \".inc\",\n    \".lpr\",\n    \".pp\"\n  ],\n  \"Perl\": [\n    \".pl\",\n    \".al\",\n    \".cgi\",\n    \".fcgi\",\n    \".perl\",\n    \".ph\",\n    \".plx\",\n    \".pm\",\n    \".pod\",\n    \".psgi\",\n    \".t\"\n  ],\n  \"Perl6\": [\n    \".6pl\",\n    \".6pm\",\n    \".nqp\",\n    \".p6\",\n    \".p6l\",\n    \".p6m\",\n    \".pl\",\n    \".pl6\",\n    \".pm\",\n    \".pm6\",\n    \".t\"\n  ],\n  \"Pickle\": [\n    \".pkl\"\n  ],\n  \"PicoLisp\": [\n    \".l\"\n  ],\n  \"PigLatin\": [\n    \".pig\"\n  ],\n  \"Pike\": [\n    \".pike\",\n    \".pmod\"\n  ],\n  \"Pod\": [\n    \".pod\"\n  ],\n  \"PogoScript\": [\n    \".pogo\"\n  ],\n  \"Pony\": [\n    \".pony\"\n  ],\n  \"PostScript\": [\n    \".ps\",\n    \".eps\"\n  ],\n  \"PowerShell\": [\n    \".ps1\",\n    \".psd1\",\n    \".psm1\"\n  ],\n  \"Processing\": [\n    \".pde\"\n  ],\n  \"Prolog\": [\n    \".pl\",\n    \".pro\",\n    \".prolog\",\n    \".yap\"\n  ],\n  \"Propeller Spin\": [\n    \".spin\"\n  ],\n  \"Protocol Buffer\": [\n    \".proto\"\n  ],\n  \"Public Key\": [\n    \".asc\",\n    \".pub\"\n  ],\n  \"Puppet\": [\n    \".pp\"\n  ],\n  \"Pure Data\": [\n    \".pd\"\n  ],\n  \"PureBasic\": [\n    \".pb\",\n    \".pbi\"\n  ],\n  \"PureScript\": [\n    \".purs\"\n  ],\n  \"Python\": [\n    \".py\",\n    \".bzl\",\n    \".cgi\",\n    \".fcgi\",\n    \".gyp\",\n    \".lmi\",\n    \".pyde\",\n    \".pyp\",\n    \".pyt\",\n    \".pyw\",\n    \".rpy\",\n    \".tac\",\n    \".wsgi\",\n    \".xpy\"\n  ],\n  \"Python traceback\": [\n    \".pytb\"\n  ],\n  \"PRQL\": [\n    \".prql\"\n  ],\n  \"QML\": [\n    \".qml\",\n    \".qbs\"\n  ],\n  \"QMake\": [\n    \".pro\",\n    \".pri\"\n  ],\n  \"R\": [\n    \".r\",\n    \".rd\",\n    \".rsx\"\n  ],\n  \"RAML\": [\n    \".raml\"\n  ],\n  \"RDoc\": [\n    \".rdoc\"\n  ],\n  \"REALbasic\": [\n    \".rbbas\",\n    \".rbfrm\",\n    \".rbmnu\",\n    \".rbres\",\n    \".rbtbar\",\n    \".rbuistate\"\n  ],\n  \"RHTML\": [\n    \".rhtml\"\n  ],\n  \"RMarkdown\": [\n    \".rmd\"\n  ],\n  \"Racket\": [\n    \".rkt\",\n    \".rktd\",\n    \".rktl\",\n    \".scrbl\"\n  ],\n  \"Ragel in Ruby Host\": [\n    \".rl\"\n  ],\n  \"Raw token data\": [\n    \".raw\"\n  ],\n  \"Rebol\": [\n    \".reb\",\n    \".r\",\n    \".r2\",\n    \".r3\",\n    \".rebol\"\n  ],\n  \"Red\": [\n    \".red\",\n    \".reds\"\n  ],\n  \"Redcode\": [\n    \".cw\"\n  ],\n  \"Ren'Py\": [\n    \".rpy\"\n  ],\n  \"RenderScript\": [\n    \".rs\",\n    \".rsh\"\n  ],\n  \"RobotFramework\": [\n    \".robot\"\n  ],\n  \"Rouge\": [\n    \".rg\"\n  ],\n  \"Ruby\": [\n    \".rb\",\n    \".builder\",\n    \".fcgi\",\n    \".gemspec\",\n    \".god\",\n    \".irbrc\",\n    \".jbuilder\",\n    \".mspec\",\n    \".pluginspec\",\n    \".podspec\",\n    \".rabl\",\n    \".rake\",\n    \".rbuild\",\n    \".rbw\",\n    \".rbx\",\n    \".ru\",\n    \".ruby\",\n    \".thor\",\n    \".watchr\"\n  ],\n  \"Rust\": [\n    \".rs\",\n    \".rs.in\"\n  ],\n  \"SAS\": [\n    \".sas\"\n  ],\n  \"SCSS\": [\n    \".scss\"\n  ],\n  \"SMT\": [\n    \".smt2\",\n    \".smt\"\n  ],\n  \"SPARQL\": [\n    \".sparql\",\n    \".rq\"\n  ],\n  \"SQF\": [\n    \".sqf\",\n    \".hqf\"\n  ],\n  \"SQL\": [\n    \".sql\",\n    \".cql\",\n    \".ddl\",\n    \".inc\",\n    \".prc\",\n    \".tab\",\n    \".udf\",\n    \".viw\"\n  ],\n  \"SQLPL\": [\n    \".sql\",\n    \".db2\"\n  ],\n  \"STON\": [\n    \".ston\"\n  ],\n  \"SVG\": [\n    \".svg\"\n  ],\n  \"Sage\": [\n    \".sage\",\n    \".sagews\"\n  ],\n  \"SaltStack\": [\n    \".sls\"\n  ],\n  \"Sass\": [\n    \".sass\"\n  ],\n  \"Scala\": [\n    \".scala\",\n    \".sbt\",\n    \".sc\"\n  ],\n  \"Scaml\": [\n    \".scaml\"\n  ],\n  \"Scheme\": [\n    \".scm\",\n    \".sld\",\n    \".sls\",\n    \".sps\",\n    \".ss\"\n  ],\n  \"Scilab\": [\n    \".sci\",\n    \".sce\",\n    \".tst\"\n  ],\n  \"Self\": [\n    \".self\"\n  ],\n  \"Shell\": [\n    \".sh\",\n    \".bash\",\n    \".bats\",\n    \".cgi\",\n    \".command\",\n    \".fcgi\",\n    \".ksh\",\n    \".sh.in\",\n    \".tmux\",\n    \".tool\",\n    \".zsh\"\n  ],\n  \"ShellSession\": [\n    \".sh-session\"\n  ],\n  \"Shen\": [\n    \".shen\"\n  ],\n  \"Slash\": [\n    \".sl\"\n  ],\n  \"Slim\": [\n    \".slim\"\n  ],\n  \"Smali\": [\n    \".smali\"\n  ],\n  \"Smalltalk\": [\n    \".st\",\n    \".cs\"\n  ],\n  \"Smarty\": [\n    \".tpl\"\n  ],\n  \"SourcePawn\": [\n    \".sp\",\n    \".inc\",\n    \".sma\"\n  ],\n  \"Squirrel\": [\n    \".nut\"\n  ],\n  \"Stan\": [\n    \".stan\"\n  ],\n  \"Standard ML\": [\n    \".ML\",\n    \".fun\",\n    \".sig\",\n    \".sml\"\n  ],\n  \"Stata\": [\n    \".do\",\n    \".ado\",\n    \".doh\",\n    \".ihlp\",\n    \".mata\",\n    \".matah\",\n    \".sthlp\"\n  ],\n  \"Stylus\": [\n    \".styl\"\n  ],\n  \"SuperCollider\": [\n    \".sc\",\n    \".scd\"\n  ],\n  \"Swift\": [\n    \".swift\"\n  ],\n  \"SystemVerilog\": [\n    \".sv\",\n    \".svh\",\n    \".vh\"\n  ],\n  \"TOML\": [\n    \".toml\"\n  ],\n  \"TXL\": [\n    \".txl\"\n  ],\n  \"Tcl\": [\n    \".tcl\",\n    \".adp\",\n    \".tm\"\n  ],\n  \"Tcsh\": [\n    \".tcsh\",\n    \".csh\"\n  ],\n  \"TeX\": [\n    \".tex\",\n    \".aux\",\n    \".bbx\",\n    \".bib\",\n    \".cbx\",\n    \".cls\",\n    \".dtx\",\n    \".ins\",\n    \".lbx\",\n    \".ltx\",\n    \".mkii\",\n    \".mkiv\",\n    \".mkvi\",\n    \".sty\",\n    \".toc\"\n  ],\n  \"Tea\": [\n    \".tea\"\n  ],\n  \"Terra\": [\n    \".t\"\n  ],\n  \"Text\": [\n    \".txt\",\n    \".fr\",\n    \".nb\",\n    \".ncl\",\n    \".no\"\n  ],\n  \"Textile\": [\n    \".textile\"\n  ],\n  \"Thrift\": [\n    \".thrift\"\n  ],\n  \"TSX\": [\".tsx\"],\n  \"Turing\": [\n    \".t\",\n    \".tu\"\n  ],\n  \"Turtle\": [\n    \".ttl\"\n  ],\n  \"Twig\": [\n    \".twig\"\n  ],\n  \"TypeScript\": [\n    \".ts\",\n    \".tsx\"\n  ],\n  \"Unified Parallel C\": [\n    \".upc\"\n  ],\n  \"Unity3D Asset\": [\n    \".anim\",\n    \".asset\",\n    \".mat\",\n    \".meta\",\n    \".prefab\",\n    \".unity\"\n  ],\n  \"Uno\": [\n    \".uno\"\n  ],\n  \"UnrealScript\": [\n    \".uc\"\n  ],\n  \"UrWeb\": [\n    \".ur\",\n    \".urs\"\n  ],\n  \"VCL\": [\n    \".vcl\"\n  ],\n  \"VHDL\": [\n    \".vhdl\",\n    \".vhd\",\n    \".vhf\",\n    \".vhi\",\n    \".vho\",\n    \".vhs\",\n    \".vht\",\n    \".vhw\"\n  ],\n  \"Vala\": [\n    \".vala\",\n    \".vapi\"\n  ],\n  \"Verilog\": [\n    \".v\",\n    \".veo\"\n  ],\n  \"VimL\": [\n    \".vim\"\n  ],\n  \"Visual Basic\": [\n    \".vb\",\n    \".bas\",\n    \".cls\",\n    \".frm\",\n    \".frx\",\n    \".vba\",\n    \".vbhtml\",\n    \".vbs\"\n  ],\n  \"Volt\": [\n    \".volt\"\n  ],\n  \"Vue\": [\n    \".vue\"\n  ],\n  \"Web Ontology Language\": [\n    \".owl\"\n  ],\n  \"WebIDL\": [\n    \".webidl\"\n  ],\n  \"X10\": [\n    \".x10\"\n  ],\n  \"XC\": [\n    \".xc\"\n  ],\n  \"XML\": [\n    \".xml\",\n    \".ant\",\n    \".axml\",\n    \".ccxml\",\n    \".clixml\",\n    \".cproject\",\n    \".csl\",\n    \".csproj\",\n    \".ct\",\n    \".dita\",\n    \".ditamap\",\n    \".ditaval\",\n    \".dll.config\",\n    \".dotsettings\",\n    \".filters\",\n    \".fsproj\",\n    \".fxml\",\n    \".glade\",\n    \".gml\",\n    \".grxml\",\n    \".iml\",\n    \".ivy\",\n    \".jelly\",\n    \".jsproj\",\n    \".kml\",\n    \".launch\",\n    \".mdpolicy\",\n    \".mm\",\n    \".mod\",\n    \".mxml\",\n    \".nproj\",\n    \".nuspec\",\n    \".odd\",\n    \".osm\",\n    \".plist\",\n    \".pluginspec\",\n    \".props\",\n    \".ps1xml\",\n    \".psc1\",\n    \".pt\",\n    \".rdf\",\n    \".rss\",\n    \".scxml\",\n    \".srdf\",\n    \".storyboard\",\n    \".stTheme\",\n    \".sublime-snippet\",\n    \".targets\",\n    \".tmCommand\",\n    \".tml\",\n    \".tmLanguage\",\n    \".tmPreferences\",\n    \".tmSnippet\",\n    \".tmTheme\",\n    \".ts\",\n    \".tsx\",\n    \".ui\",\n    \".urdf\",\n    \".ux\",\n    \".vbproj\",\n    \".vcxproj\",\n    \".vssettings\",\n    \".vxml\",\n    \".wsdl\",\n    \".wsf\",\n    \".wxi\",\n    \".wxl\",\n    \".wxs\",\n    \".x3d\",\n    \".xacro\",\n    \".xaml\",\n    \".xib\",\n    \".xlf\",\n    \".xliff\",\n    \".xmi\",\n    \".xml.dist\",\n    \".xproj\",\n    \".xsd\",\n    \".xul\",\n    \".zcml\"\n  ],\n  \"XPages\": [\n    \".xsp-config\",\n    \".xsp.metadata\"\n  ],\n  \"XProc\": [\n    \".xpl\",\n    \".xproc\"\n  ],\n  \"XQuery\": [\n    \".xquery\",\n    \".xq\",\n    \".xql\",\n    \".xqm\",\n    \".xqy\"\n  ],\n  \"XS\": [\n    \".xs\"\n  ],\n  \"XSLT\": [\n    \".xslt\",\n    \".xsl\"\n  ],\n  \"Xojo\": [\n    \".xojo_code\",\n    \".xojo_menu\",\n    \".xojo_report\",\n    \".xojo_script\",\n    \".xojo_toolbar\",\n    \".xojo_window\"\n  ],\n  \"Xtend\": [\n    \".xtend\"\n  ],\n  \"YAML\": [\n    \".yml\",\n    \".reek\",\n    \".rviz\",\n    \".sublime-syntax\",\n    \".syntax\",\n    \".yaml\",\n    \".yaml-tmlanguage\"\n  ],\n  \"YANG\": [\n    \".yang\"\n  ],\n  \"Yacc\": [\n    \".y\",\n    \".yacc\",\n    \".yy\"\n  ],\n  \"Zephir\": [\n    \".zep\"\n  ],\n  \"Zimpl\": [\n    \".zimpl\",\n    \".zmpl\",\n    \".zpl\"\n  ],\n  \"desktop\": [\n    \".desktop\",\n    \".desktop.in\"\n  ],\n  \"eC\": [\n    \".ec\",\n    \".eh\"\n  ],\n  \"edn\": [\n    \".edn\"\n  ],\n  \"fish\": [\n    \".fish\"\n  ],\n  \"mupad\": [\n    \".mu\"\n  ],\n  \"nesC\": [\n    \".nc\"\n  ],\n  \"ooc\": [\n    \".ooc\"\n  ],\n  \"reStructuredText\": [\n    \".rst\",\n    \".rest\",\n    \".rest.txt\",\n    \".rst.txt\"\n  ],\n  \"wisp\": [\n    \".wisp\"\n  ],\n  \"xBase\": [\n    \".prg\",\n    \".ch\",\n    \".prw\"\n  ]\n}\n"
  },
  {
    "path": "client/src/utils/mappers.ts",
    "content": "import {\n  RepoProvider,\n  RepoType,\n  RepoUi,\n  StudioConversationMessage,\n  StudioConversationMessageAuthor,\n  SyncStatus,\n} from '../types/general';\nimport { CodeStudioMessageType } from '../types/api';\nimport { splitPath } from './index';\n\nexport const mapReposBySections = (githubRepos: RepoType[]) => {\n  const byOrg: Record<string, RepoUi[]> = {};\n  githubRepos.forEach((r) => {\n    const pathParts = splitPath(r.name);\n    const repoUI = {\n      ...r,\n      shortName: pathParts[pathParts.length - 1],\n      folderName: pathParts[0],\n      alreadySynced: ![\n        SyncStatus.Uninitialized,\n        SyncStatus.Removed,\n        SyncStatus.Syncing,\n        SyncStatus.Indexing,\n        SyncStatus.Queued,\n      ].includes(r.sync_status),\n      isSyncing: [\n        SyncStatus.Syncing,\n        SyncStatus.Indexing,\n        SyncStatus.Queued,\n      ].includes(r.sync_status),\n    };\n    const sectionName =\n      r.provider === RepoProvider.Local ? 'Local' : pathParts[0];\n    if (!byOrg[sectionName]) {\n      byOrg[sectionName] = [];\n    }\n    byOrg[sectionName].push(repoUI);\n  });\n  const result: { org: string; items: RepoUi[]; offset: number }[] = [];\n  Object.keys(byOrg)\n    .sort((a, b) =>\n      a === 'Local'\n        ? 1\n        : b === 'Local'\n        ? -1\n        : a?.toLowerCase() < b?.toLowerCase()\n        ? -1\n        : 1,\n    )\n    .forEach((k) => {\n      result.push({\n        org: k,\n        items: byOrg[k].sort((a, b) =>\n          a.folderName?.toLowerCase() < b.folderName?.toLowerCase()\n            ? -1\n            : a.folderName?.toLowerCase() > b.folderName?.toLowerCase()\n            ? 1\n            : a.shortName?.toLowerCase() < b.shortName?.toLowerCase()\n            ? -1\n            : 1,\n        ),\n        offset: result[result.length - 1]\n          ? result[result.length - 1].offset +\n            result[result.length - 1].items.length\n          : 0,\n      });\n    });\n  return result;\n};\n\nexport function mapConversation(\n  messages: CodeStudioMessageType[],\n): StudioConversationMessage[] {\n  return messages.map((m) => {\n    const author = Object.keys(m)[0] as StudioConversationMessageAuthor;\n    return { author, message: Object.values(m)[0] };\n  });\n}\n\nexport function filterOutDuplicates<T>(arr: T[], key: keyof T): T[] {\n  return arr.filter(\n    (value, index, self) =>\n      self.findIndex((v) => v[key] === value[key]) === index,\n  );\n}\n"
  },
  {
    "path": "client/src/utils/navigationUtils.ts",
    "content": "import { Location } from 'react-router-dom';\n\nexport const buildURLPart = (navItem: any) => {\n  switch (navItem?.type) {\n    case 'search':\n      return `search${\n        navItem.pathParams\n          ? '?' + new URLSearchParams(navItem.pathParams).toString()\n          : ''\n      }#${navItem.query}`;\n    case 'repo':\n      return `repo${\n        navItem.path\n          ? '?' +\n            new URLSearchParams({\n              path: navItem.path,\n              ...navItem.pathParams,\n            }).toString()\n          : ''\n      }`;\n    case 'full-result':\n      return `full-result?${new URLSearchParams({\n        path: navItem.path || '',\n        threadId: navItem.threadId?.toString() || '',\n        recordId: navItem.recordId?.toString() || '',\n        ...navItem.pathParams,\n      }).toString()}`;\n    case 'conversation-result':\n      return `conversation-result?${new URLSearchParams({\n        threadId: navItem.threadId?.toString() || '',\n        recordId: navItem.recordId?.toString() || '',\n        ...navItem.pathParams,\n      }).toString()}`;\n    case 'article-response':\n      return `article-response?${new URLSearchParams({\n        threadId: navItem.threadId?.toString() || '',\n        recordId: navItem.recordId?.toString() || '',\n        ...navItem.pathParams,\n      }).toString()}`;\n    default:\n      return '';\n  }\n};\n\nexport const getNavItemFromURL = (location: Location, repoName: string) => {\n  const type = location.pathname.split('/')[3];\n  const possibleTypes = [\n    'search',\n    'repo',\n    'full-result',\n    'home',\n    'conversation-result',\n    'article-response',\n  ];\n  if (!possibleTypes.includes(type)) {\n    return undefined;\n  }\n  const navItem: any = {\n    isInitial: true,\n    type: type,\n    searchType: 0,\n    repo: repoName,\n  };\n  navItem.query = decodeURIComponent(location.hash.slice(1));\n  navItem.path = new URLSearchParams(location.search).get('path') || undefined;\n  navItem.pathParams = {};\n  const modalPath = new URLSearchParams(location.search).get('modalPath');\n  if (modalPath) {\n    navItem.pathParams.modalPath = modalPath;\n  }\n  const modalScrollToLine = new URLSearchParams(location.search).get(\n    'modalScrollToLine',\n  );\n  if (modalScrollToLine) {\n    navItem.pathParams.modalScrollToLine = modalScrollToLine;\n  }\n  const modalHighlightColor = new URLSearchParams(location.search).get(\n    'modalHighlightColor',\n  );\n  if (modalHighlightColor) {\n    navItem.pathParams.highlightColor = modalHighlightColor;\n  }\n  const highlightColor = new URLSearchParams(location.search).get(\n    'highlightColor',\n  );\n  if (highlightColor) {\n    navItem.pathParams.highlightColor = highlightColor;\n  }\n  const scrollToLine = new URLSearchParams(location.search).get('scrollToLine');\n  if (scrollToLine) {\n    navItem.pathParams.scrollToLine = scrollToLine;\n  }\n  const threadId = new URLSearchParams(location.search).get('threadId');\n  if (threadId) {\n    navItem.threadId = threadId;\n  }\n  const recordId = new URLSearchParams(location.search).get('recordId');\n  if (recordId) {\n    navItem.recordId = Number(recordId);\n  }\n  return navItem.type === 'repo' && !navItem.path\n    ? [navItem]\n    : [\n        {\n          type: 'repo',\n          repo: repoName,\n          isInitial: true,\n        },\n        navItem,\n      ];\n};\n"
  },
  {
    "path": "client/src/utils/prism.ts",
    "content": "/*\n * https://github.com/FormidableLabs/prism-react-renderer/blob/master/src/utils/normalizeTokens.js\n * */\n\nimport Prism, { TokenStream } from 'prismjs';\nimport 'prismjs/components/prism-typescript.min';\nimport 'prismjs/components/prism-jsx.min';\nimport 'prismjs/components/prism-tsx.min';\nimport 'prismjs/components/prism-python.min';\nimport 'prismjs/components/prism-rust.min';\nimport 'prismjs/components/prism-scheme.min';\nimport 'prismjs/components/prism-json.min';\nimport 'prismjs/components/prism-markdown.min';\nimport 'prismjs/components/prism-toml.min';\nimport 'prismjs/components/prism-yaml.min';\nimport 'prismjs/components/prism-bash.min';\nimport 'prismjs/components/prism-graphql.min';\nimport 'prismjs/components/prism-kotlin.min';\nimport 'prismjs/components/prism-r.min';\nimport 'prismjs/components/prism-batch.min';\nimport 'prismjs/components/prism-ruby.min';\nimport 'prismjs/components/prism-go.min';\nimport 'prismjs/components/prism-ini.min';\nimport 'prismjs/components/prism-gradle.min';\nimport 'prismjs/components/prism-perl.min';\nimport 'prismjs/components/prism-java.min';\nimport 'prismjs/components/prism-csharp.min';\nimport 'prismjs/components/prism-erlang.min';\nimport 'prismjs/components/prism-c.min';\nimport 'prismjs/components/prism-swift.min';\nimport 'prismjs/components/prism-markup-templating.min';\nimport 'prismjs/components/prism-php.min';\nimport 'prismjs/components/prism-cpp.min';\nimport 'prismjs/components/prism-scss.min';\nimport 'prismjs/components/prism-less.min';\nimport 'prismjs/components/prism-scala.min';\nimport 'prismjs/components/prism-julia.min';\nimport 'prismjs/components/prism-docker.min';\nimport 'prismjs/components/prism-diff.min';\nimport 'prismjs/components/prism-cobol.min';\nimport type { Token } from '../types/prism';\n\nconst newlineRe = /\\r\\n|\\r|\\n/;\n\ntype LineEndings = 'CRLF' | 'LF';\n\n// Empty lines need to contain a single empty token, denoted with { empty: true }\nconst normalizeEmptyLines = (line: Token[]) => {\n  if (line.length === 0) {\n    line.push({\n      types: ['plain'],\n      content: '\\n',\n      empty: true,\n      byteRange: { start: 0, end: 0 },\n    });\n  } else if (line.length === 1 && line[0].content === '') {\n    line[0].content = '\\n';\n    line[0].empty = true;\n  }\n};\n\nconst appendTypes = (types: string[], add: string[] | string): string[] => {\n  const typesSize = types.length;\n  if (typesSize > 0 && types[typesSize - 1] === add) {\n    return types;\n  }\n\n  return types.concat(add);\n};\n\nconst normalizeWhitespaces = (tokens: Token[][]) => {\n  const normalized: Token[][] = [];\n  tokens.forEach((line) => {\n    const lineTokens: Token[] = [];\n    line.forEach((token) => {\n      const data = token.content.match(/^(\\s*)([^\\s]+)(\\s*)$/);\n\n      if (!data || (data[1] == null && data[3] === null)) {\n        lineTokens.push(token);\n        return;\n      }\n      if (data[1]) {\n        lineTokens.push({\n          types: ['plain'],\n          content: data[1],\n          empty: false,\n          byteRange: { start: 0, end: 0 },\n        });\n      }\n      lineTokens.push({\n        ...token,\n        content: data[2],\n      });\n      if (data[3]) {\n        lineTokens.push({\n          types: ['plain'],\n          content: data[3],\n          empty: false,\n          byteRange: { start: 0, end: 0 },\n        });\n      }\n    });\n    normalized.push(lineTokens);\n  });\n  return normalized;\n};\n\nconst appendRanges = (tokens: Token[][], lineEndings: 'CRLF' | 'LF') => {\n  let b = 0;\n  return tokens.map((line) => {\n    const lt = line.map((token) => {\n      const tokenLengthInBytes = new TextEncoder().encode(token.content).length;\n      const currRange = { start: b, end: b + tokenLengthInBytes };\n      if (!token.empty && tokenLengthInBytes) {\n        b += tokenLengthInBytes;\n      }\n      return { ...token, byteRange: currRange };\n    });\n    b += lineEndings === 'CRLF' ? 2 : 1;\n    return lt;\n  });\n};\n\n// Takes an array of Prism's tokens and groups them by line, turning plain\n// strings into tokens as well. Tokens can become recursive in some cases,\n// which means that their types are concatenated. Plain-string tokens however\n// are always of type \"plain\".\n// This is not recursive to avoid exceeding the call-stack limit, since it's unclear\n// how nested Prism's tokens can become\nconst normalizeTokens = (\n  tokens: Array<Prism.Token | string>,\n  lineEndings: 'CRLF' | 'LF',\n): Token[][] => {\n  const typeArrStack: string[][] = [[]];\n  const tokenArrStack = [tokens];\n  const tokenArrIndexStack = [0];\n  const tokenArrSizeStack = [tokens.length];\n\n  let i = 0;\n  let stackIndex = 0;\n  let currentLine: Token[] = [];\n\n  let bytes = 0;\n  const acc = [currentLine];\n\n  while (stackIndex > -1) {\n    while (\n      (i = tokenArrIndexStack[stackIndex]++) < tokenArrSizeStack[stackIndex]\n    ) {\n      let content: string | TokenStream = '';\n      let types = typeArrStack[stackIndex];\n\n      const tokenArr = tokenArrStack[stackIndex];\n      const token = tokenArr[i];\n\n      // Determine content and append type to types if necessary\n      let currRange = null;\n\n      if (typeof token === 'string') {\n        types = stackIndex > 0 ? types : ['plain'];\n        content = token;\n      } else if (!!token) {\n        types = appendTypes(types, token.type);\n        if (token.alias) {\n          types = appendTypes(types, token.alias);\n        }\n\n        content = token.content;\n      }\n\n      // If token.content is an array, increase the stack depth and repeat this while-loop\n      if (typeof content !== 'string') {\n        stackIndex++;\n        typeArrStack.push(types);\n        tokenArrStack.push(content as any);\n        tokenArrIndexStack.push(0);\n        tokenArrSizeStack.push(content.length);\n        continue;\n      }\n\n      // Split by newlines\n      const splitByNewlines = content.split(newlineRe);\n      const newlineCount = splitByNewlines.length;\n\n      currentLine.push({\n        types,\n        content: splitByNewlines[0],\n        byteRange: { start: 0, end: 0 },\n      });\n\n      // Create a new line for each string on a new line\n      for (let i = 1; i < newlineCount; i++) {\n        normalizeEmptyLines(currentLine);\n        acc.push((currentLine = []));\n        currentLine.push({\n          types,\n          content: splitByNewlines[i],\n          byteRange: { start: 0, end: 0 },\n        });\n      }\n    }\n\n    // Decrease the stack depth\n    stackIndex--;\n    typeArrStack.pop();\n    tokenArrStack.pop();\n    tokenArrIndexStack.pop();\n    tokenArrSizeStack.pop();\n  }\n\n  normalizeEmptyLines(currentLine);\n\n  return appendRanges(normalizeWhitespaces(acc), lineEndings);\n};\n\nexport const getPrismLanguage = (lang: string) => {\n  const langMap = {\n    JavaScript: 'jsx',\n    TypeScript: 'tsx',\n    'C#': 'csharp',\n    'C++': 'cpp',\n    'c++': 'cpp',\n  };\n  // @ts-ignore\n  return langMap[lang] || lang?.toLowerCase() || 'plain';\n};\n\nfunction getLineEnding(content: string): LineEndings | undefined {\n  const matched = content?.match(/\\r\\n|\\r|\\n/);\n  if (matched) {\n    const returned = {\n      '\\r': 'CR',\n      '\\n': 'LF',\n      '\\r\\n': 'CRLF',\n    }[matched[0]];\n\n    return returned as LineEndings;\n  }\n}\n\nexport const tokenizeCode = (code: string, lang?: string) => {\n  const lineEndings = getLineEnding(code);\n  const tokens = Prism.tokenize(\n    code,\n    lang && Prism.languages[lang]\n      ? Prism.languages[lang]\n      : Prism.languages.plaintext,\n  );\n  return normalizeTokens(tokens, lineEndings!);\n};\n\nexport const highlightCode = (code: string, lang: string) => {\n  if (!code) {\n    return code;\n  }\n  return Prism.highlight(\n    code,\n    Prism.languages[lang] || Prism.languages.plaintext,\n    lang,\n  );\n};\n\nexport default normalizeTokens;\n"
  },
  {
    "path": "client/src/utils/requestUtils.ts",
    "content": "export function polling(func: () => Promise<any>, interval: number): number {\n  let intervalId: number | null = null;\n  let isRequestInProgress = false;\n\n  const poll = async () => {\n    if (!isRequestInProgress) {\n      isRequestInProgress = true;\n      try {\n        await func();\n      } catch (error) {\n        console.error(error);\n      } finally {\n        isRequestInProgress = false;\n      }\n    }\n  };\n\n  poll();\n  intervalId = window.setInterval(poll, interval);\n\n  return intervalId;\n}\n"
  },
  {
    "path": "client/src/utils/scrollUtils.ts",
    "content": "import { Align } from 'react-window';\n\ntype ReactWindowProps = {\n  height: number;\n  itemCount: number;\n  itemSize: number;\n};\n\nexport const getOffsetForIndexAndAlignment = (\n  { height, itemCount, itemSize }: ReactWindowProps,\n  index: number,\n  align: Align,\n  scrollOffset: number,\n): number => {\n  const size = height;\n  const rowsOnPage = size / itemSize;\n  const lastItemOffset = Math.max(0, itemCount * itemSize - size);\n  const maxOffset = Math.min(lastItemOffset, index * itemSize);\n  const minOffset = Math.max(0, index * itemSize - size + itemSize);\n\n  if (index < rowsOnPage / 2) {\n    // if in the first half of the first 'page', don't scroll\n    return 0;\n  }\n  if (index < rowsOnPage || index > itemCount - rowsOnPage) {\n    // if on the first or last page, minOffset/maxOffset will be incorrect, scroll to center the item\n    return index * itemSize - size / 2;\n  }\n  if (index > itemCount - rowsOnPage / 2) {\n    // if in the last half of the last page, scroll to bottom\n    return lastItemOffset;\n  }\n\n  if (align === 'smart') {\n    if (scrollOffset >= minOffset - size && scrollOffset <= maxOffset + size) {\n      align = 'auto';\n    } else {\n      align = 'center';\n    }\n  }\n\n  switch (align) {\n    case 'start':\n      return maxOffset;\n    case 'end':\n      return minOffset;\n    case 'center': {\n      const middleOffset = Math.round(minOffset + (maxOffset - minOffset) / 2);\n      return middleOffset;\n    }\n    case 'auto':\n    default:\n      if (scrollOffset >= minOffset && scrollOffset <= maxOffset) {\n        return scrollOffset;\n      } else if (scrollOffset < minOffset) {\n        return minOffset;\n      } else {\n        return maxOffset;\n      }\n  }\n};\n"
  },
  {
    "path": "client/src/utils/textSearch.ts",
    "content": "export const HIGHLIGHT_CLASSNAME = 'search-highlight';\nexport const ACTIVE_HIGHLIGHT_CLASSNAME = 'search-highlight-active';\n\nexport function unmark(parentNode?: HTMLElement): void {\n  const markedElements = (parentNode || document).querySelectorAll(\n    `.${HIGHLIGHT_CLASSNAME}`,\n  );\n  for (let i = 0; i < markedElements.length; i++) {\n    const element = markedElements[i] as HTMLElement;\n    const parentNode = element.parentNode as HTMLElement;\n    const text = element.innerText;\n    const textNode = document.createTextNode(text);\n    parentNode.replaceChild(textNode, element);\n    joinTextNodes(parentNode);\n  }\n}\n\nexport function joinTextNodes(parentNode: HTMLElement): void {\n  const childNodes = parentNode.childNodes;\n  const newChildNodes = [];\n  let currentText = '';\n  for (let i = 0; i < childNodes.length; i++) {\n    const childNode = childNodes[i];\n    if (childNode.nodeType === Node.TEXT_NODE) {\n      currentText += childNode.textContent;\n    } else {\n      if (currentText !== '') {\n        newChildNodes.push(document.createTextNode(currentText));\n        currentText = '';\n      }\n      newChildNodes.push(childNode);\n    }\n  }\n  if (currentText !== '') {\n    newChildNodes.push(document.createTextNode(currentText));\n  }\n  while (parentNode.firstChild) {\n    parentNode.removeChild(parentNode.firstChild);\n  }\n  for (let j = 0; j < newChildNodes.length; j++) {\n    parentNode.appendChild(newChildNodes[j]);\n  }\n}\n\nexport function markNode(node: HTMLElement, regex: RegExp): void {\n  for (let i = 0; i < node.childNodes.length; i++) {\n    const childNode = node.childNodes[i];\n    if (childNode.nodeType === Node.TEXT_NODE) {\n      const nodeValue = childNode.nodeValue;\n      if (nodeValue) {\n        const newValue = nodeValue.replace(regex, function (match) {\n          return `<span class=\"${HIGHLIGHT_CLASSNAME}\">${match}</span>`;\n        });\n        if (newValue !== nodeValue) {\n          const newElement = document.createElement('span');\n          newElement.innerHTML = newValue;\n          childNode.parentNode?.replaceChild(newElement, childNode);\n        }\n      }\n    } else if (childNode.nodeType === Node.ELEMENT_NODE) {\n      const computedNodeStyle = window.getComputedStyle(\n        childNode as Element,\n        null,\n      );\n      if (\n        computedNodeStyle.visibility === 'hidden' ||\n        computedNodeStyle.display === 'none' ||\n        computedNodeStyle.opacity === '0'\n      ) {\n        continue;\n      }\n      markNode(childNode as HTMLElement, regex);\n    }\n  }\n}\n"
  },
  {
    "path": "client/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "client/tailwind.config.cjs",
    "content": "/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n  content: ['./src/**/*.{js,jsx,ts,tsx}'],\n  theme: {\n    extend: {\n      colors: {\n        \"bg-sub\": \"rgba(var(--bg-sub), 1)\",\n        \"bg-sub-hover\": \"rgba(var(--bg-sub-hover), 1)\",\n        \"bg-base\": \"rgba(var(--bg-base), 1)\",\n        \"bg-base-hover\": \"rgba(var(--bg-base-hover), 1)\",\n        \"bg-selected\": \"rgba(var(--bg-selected), 0.12)\",\n        \"bg-shade\": \"rgba(var(--bg-shade), 1)\",\n        \"bg-shade-hover\": \"rgba(var(--bg-shade-hover), 1)\",\n        \"bg-border\": \"rgba(var(--bg-border), 1)\",\n        \"bg-border-hover\": \"rgba(var(--bg-border-hover), 1)\",\n        \"bg-border-selected\": \"rgba(var(--bg-border-selected), 1)\",\n        \"bg-divider\": \"rgba(var(--bg-divider), 1)\",\n        \"bg-contrast\": \"rgba(var(--bg-contrast), 1)\",\n        \"bg-contrast-hover\": \"rgba(var(--bg-contrast-hover), 1)\",\n        \"label-title\": \"rgba(var(--label-title), 1)\",\n        \"label-base\": \"rgba(var(--label-base), 1)\",\n        \"label-muted\": \"rgba(var(--label-muted), 1)\",\n        \"label-faint\": \"rgba(var(--label-faint), 1)\",\n        \"label-link\": \"rgba(var(--label-link), 1)\",\n        \"label-contrast\": \"rgba(var(--label-contrast), 1)\",\n        \"label-control\": \"rgba(var(--label-control), 1)\",\n        \"sub-surface\": \"rgba(var(--sub-surface), 0.75)\",\n        \"shade-surface\": \"rgba(var(--shade-surface), 0.75)\",\n        \"base-surface\": \"rgba(var(--base-surface), 0.75)\",\n        \"brand-default\": \"rgba(var(--brand-default), 1)\",\n        \"brand-default-hover\": \"rgba(var(--brand-default-hover), 1)\",\n        \"brand-default-subtitle\": \"rgba(var(--brand-default-subtitle), 1)\",\n        \"brand-studio\": \"rgba(var(--brand-studio), 1)\",\n        \"brand-studio-hover\": \"rgba(var(--brand-studio-hover), 1)\",\n        \"brand-studio-subtle\": \"rgba(var(--brand-studio-subtle), 1)\",\n        \"green\": \"rgba(var(--green), 1)\",\n        \"green-subtle\": \"rgba(var(--green-subtle), 1)\",\n        \"green-subtle-hover\": \"rgba(var(--green-subtle-hover), 1)\",\n        \"red\": \"rgba(var(--red), 1)\",\n        \"red-subtle\": \"rgba(var(--red-subtle), 1)\",\n        \"red-subtle-hover\": \"rgba(var(--red-subtle-hover), 1)\",\n        \"yellow\": \"rgba(var(--yellow), 1)\",\n        \"yellow/16\": \"rgba(var(--yellow), 0.16)\",\n        \"yellow-subtle\": \"rgba(var(--yellow-subtle), 1)\",\n        \"yellow-subtle-hover\": \"rgba(var(--yellow-subtle-hover), 1)\",\n        \"blue\": \"rgba(var(--blue), 1)\",\n        \"blue-subtle\": \"rgba(var(--blue-subtle), 1)\",\n        \"blue-subtle-hover\": \"rgba(var(--blue-subtle-hover), 1)\",\n        \"line-select\": \"rgba(var(--line-select), 1)\",\n        \"danger-300\": \"#FB7185\",\n        \"warning-100\": \"#F0A892\",\n        \"warning-300\": \"#ED6E47\",\n        \"warning-300/12\": \"rgba(237,110,71, 0.12)\",\n        sky: '#0EA4E9',\n        violet: '#8B5CF6',\n        pink: '#EC4899',\n        orange: '#F78166',\n        'orange-600': '#E9694C',\n        // yellow: \"#EAB408\",\n        purple: \"#652D90\",\n      },\n      boxShadow: {\n        float: \"var(--shadow-float)\",\n        high: \"var(--shadow-high)\",\n        medium: 'var(--shadow-medium)',\n        low: 'var(--shadow-low)',\n        \"rings-gray\": \"var(--shadow-rings-gray\",\n        \"rings-blue\": \"var(--shadow-rings-blue)\",\n      },\n      dropShadow: {\n        float: \"0px 16px 34px rgba(0, 0, 0, 0.75)\",\n      },\n      spacing: {\n        \"4.5\": \"1.125rem\",\n        \"10.5\": \"2.625rem\",\n        \"11.5\": \"2.875rem\",\n        '13': '3.25rem',\n        '15': '3.75rem',\n        \"30\": \"7.5rem\",\n        \"67\": \"17.7rem\",\n        \"68\": \"17.75rem\",\n        \"90\": \"20.25rem\",\n        \"95\": \"23.75rem\",\n        \"97\": \"25.75rem\",\n        \"98\": \"26.5rem\",\n        '99': '29.625rem',\n        '100': '32rem',\n        85: '21.25rem',\n      },\n      borderRadius: {\n        'px': '1px',\n        1: \"0.075rem\",\n        4: \"0.25rem\",\n        6: \"6px\",\n        14: \"0.875rem\"\n      },\n      padding: {\n        \"2.5\": \"0.625rem\"\n      },\n      margin: {\n        13: '3.375rem'\n      },\n      maxWidth:{\n        '5': '5rem',\n        '12': '12rem',\n        'md2': '29.625rem',\n        'md3': '30.25rem',\n        '5.5xl': '70rem',\n        '6.5xl': '77.5rem',\n        96: '24rem'\n      },\n      minWidth: {\n        \"6\": \"1.5rem\"\n      },\n      borderWidth: {\n        6:\"6px\"\n      },\n      scale: {\n        25: \"0.25\"\n      },\n      transitionTimingFunction: {\n        'in-bounce': \"cubic-bezier(0.17, 0.67, 0.83, 0.67)\",\n        'in-slow': \"cubic-bezier(0.4, 0, 0, 1)\",\n        'out-slow': \"cubic-bezier(0.6, 0.6, 0, 1)\"\n      },\n      transitionProperty: {\n        visibility: 'visibility'\n      },\n      gridTemplateColumns: {\n        \"4-fit\": \"repeat(4, auto)\"\n      },\n      display: ['hover', 'focus', 'group-hover'],\n      backdropBlur: {\n        'md': \"6px\"\n      },\n      backgroundImage: {\n        \"studio\": \"linear-gradient(135deg, #C7363E 0%, #C7369E 100%)\",\n        skeleton: 'linear-gradient(90deg, rgba(var(--bg-base-hover), .1) 0%, rgba(var(--bg-base-hover), .1) 33%, rgb(var(--bg-base-hover)) 60%, rgba(var(--bg-base-hover), .1) 100%)',\n      },\n      backgroundSize :{\n        '50%': '50%',\n      },\n      zIndex: {\n        60: 60,\n        70: 70,\n        80: 80,\n        90: 90,\n        100: 100,\n      },\n      animation: {\n        'pulse-slow': 'pulse 5s cubic-bezier(0.4, 0, 0.6, 1) infinite',\n        'opacity-slow': 'opacity 5s cubic-bezier(0.4, 0, 0.6, 1) infinite',\n        'pulse-shadow-slow': 'shadow 5s cubic-bezier(0.4, 0, 0.6, 1) infinite',\n        'spin-slow': 'spin 1.5s linear infinite',\n        'spin-extra-slow': 'spin 2s linear infinite',\n        'move-x': 'move-x 2.5s linear infinite',\n        'move-x-fast': 'move-bg 1.5s linear infinite',\n        'flash-highlight': 'flash-highlight 1.5s linear',\n        'loader-state-zero': 'loader-state-zero 0.55s cubic-bezier(.2,.5,.5,.8)',\n        'loader-state-one': 'loader-state-one 2s cubic-bezier(.5,.5,.5,1)',\n        'loader-state-two': 'loader-state-two 0.55s cubic-bezier(.5,.2,.8,.5)',\n        'loader-state-three': 'loader-state-three 0.55s cubic-bezier(.5,.0,1,.5)',\n      },\n      keyframes: {\n        shadow: {\n          '0%': {\n            boxShadow: 'var(--shadow-float)',\n          },\n          '50%': {\n            boxShadow: 'var(--shadow-low)',\n          },\n          '100%': {\n            boxShadow: 'var(--shadow-float)',\n          },\n        },\n        opacity: {\n          '0%': {\n            opacity: 0,\n          },\n          '50%': {\n            opacity: 1,\n          },\n          '100%': {\n            opacity: 0,\n          },\n        },\n        'move-x': {\n          '0%': {\n            transform: 'translateX(-75%)',\n          },\n          '25%': {\n            transform: 'translateX(-25%)',\n          },\n          '50%': {\n            transform: 'translateX(25%)',\n          },\n          '100%': {\n            transform: 'translateX(75%)',\n          },\n        },\n        'move-bg' :{\n          '0%': {\n            backgroundPosition: '110% 0',\n          },\n          '100%':{\n            backgroundPosition: '0 0%'\n          }\n        },\n        'flash-highlight': {\n          '0%': { backgroundColor: 'transparent' },\n          '10%': { backgroundColor: 'rgba(var(--bg-highlight, 0.75))' },\n          '25%': { backgroundColor: 'rgba(var(--bg-highlight), 0.25)' },\n          '80%': { backgroundColor: 'rgba(var(--bg-highlight), 0.25)' },\n          '100%': { backgroundColor: 'transparent' },\n        },\n        'loader-state-zero': {\n          '0%': {width: '0%'},\n          '100%': {width: '50%'},\n        },\n        'loader-state-one': {\n          '0%': {width: '50%'},\n          '100%': {width: '90%'},\n        },\n        'loader-state-two': {\n          '0%': {width: '50%'},\n          '100%': {width: '100%'},\n        },\n        'loader-state-three': {\n          '0%': {width: '90%'},\n          '100%': {width: '100%'},\n        }\n      }\n    },\n    fontFamily: {\n      default: ['Inter', 'sans-serif'],\n      code: ['Menlo', 'sans-serif'],\n    },\n    namedGroups: [\"tooltip\",\"summary\"],\n  },\n  plugins: [\n    require('tailwindcss-labeled-groups')(['custom', 'summary' , 'code' ,'row'])\n  ],\n};\n"
  },
  {
    "path": "client/tests/setupTests.ts",
    "content": "import '@testing-library/jest-dom/extend-expect';\n"
  },
  {
    "path": "client/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ESNext\"],\n    \"allowJs\": false,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n  },\n  \"include\": [\"src\"],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "client/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Node\",\n    \"allowSyntheticDefaultImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "client/vite.config.ts",
    "content": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\nimport EnvironmentPlugin from 'vite-plugin-environment';\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  build: {\n    sourcemap: true, // Source map generation must be turned on\n  },\n  envDir: '../.',\n  plugins: [\n    react(),\n    EnvironmentPlugin(\n      {\n        ONBOARDING: '',\n        API_URL: '',\n      },\n      {\n        defineOn: 'import.meta.env',\n      },\n    ),\n  ],\n  define: {\n    __APP_SESSION__: (Math.random() * 100000).toString(),\n  },\n});\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "version: '3'\nservices:\n  qdrant:\n    image: \"qdrant/qdrant\"\n    restart: always\n    container_name: qdrant\n    ports:\n      - \"6333:6333\"\n      - \"6334:6334\"\n    expose:\n      - 6333\n      - 6334\n      - 6335\n    volumes:\n      - ./qdrant_data:/qdrant_data\n  bloop-app:\n    image: bloop-app\n    restart: on-failure\n    command: --qdrant-url=http://qdrant:6334\n    depends_on:\n      qdrant:\n        condition: service_started\n    ports:\n      - \"7878:7878\"\n    expose:\n      - 7878\n    build:\n      dockerfile: \"./Dockerfile\"\n      args:\n        - OPENAI_API_KEY\n        - GITHUB_ACCESS_TOKEN"
  },
  {
    "path": "flake.nix",
    "content": "{\n  description = \"bloop\";\n\n  # nixConfig = {\n  #   extra-substituters = \"https://bloopai.cachix.org\";\n  #   extra-trusted-public-keys =\n  #     \"bloopai.cachix.org-1:uSHFor+Jd3znikUnLc58xnHBXTcuIBSjdJxV5rLIMJU=\";\n  # };\n\n  inputs = {\n    nixpkgs.url = \"github:nixos/nixpkgs/nixos-unstable\";\n    nixpkgs2305.url = \"github:nixos/nixpkgs/nixos-23.05\";\n    flake-utils.url = \"github:numtide/flake-utils\";\n  };\n\n  outputs = { self, nixpkgs, nixpkgs2305, flake-utils }:\n    flake-utils.lib.eachDefaultSystem (system:\n      let\n        pkgs = import nixpkgs { inherit system; };\n        pkgsStable = import nixpkgs2305 { inherit system; };\n        pkgsStatic = pkgs.pkgsStatic;\n        lib = pkgs.lib;\n\n        llvm = pkgs.llvmPackages_14;\n        clang = llvm.clang;\n        libclang = llvm.libclang;\n        stdenv =\n          if pkgs.stdenv.isLinux then\n            pkgs.stdenvAdapters.useMoldLinker llvm.stdenv\n          else\n            llvm.stdenv;\n\n        mkShell =\n          if stdenv.isLinux then\n            pkgs.mkShell.override { inherit stdenv; }\n          else\n            pkgs.mkShell;\n\n        rustPlatform = pkgs.makeRustPlatform {\n          cargo = pkgs.cargo;\n          rustc = pkgs.rustc;\n        };\n\n        runtimeDeps = with pkgs;\n          ([ openssl.out rocksdb git zlib nsync ]\n            ++ lib.optionals stdenv.isDarwin [\n            darwin.apple_sdk.frameworks.Foundation\n            darwin.apple_sdk.frameworks.CoreFoundation\n            darwin.apple_sdk.frameworks.Security\n          ]);\n\n        buildDeps = with pkgs;\n          ([\n            stdenv.cc.cc.lib\n            glib.dev\n            pkg-config\n            openssl.out\n            openssl.dev\n            llvm.bintools\n\n            protobuf\n          ] ++ lib.optionals stdenv.isDarwin [\n            darwin.apple_sdk.frameworks.Foundation\n            darwin.apple_sdk.frameworks.CoreFoundation\n            darwin.apple_sdk.frameworks.Security\n          ]);\n\n        guiDeps = with pkgs;\n          [ nodePackages.npm nodejs ] ++ (lib.optionals stdenv.isLinux [\n            gdk-pixbuf\n            gdk-pixbuf.dev\n            zlib.dev\n            dbus.dev\n            libsoup.dev\n            gtk3.dev\n            webkitgtk\n            dmidecode\n            appimage-run\n            appimagekit\n            gdk-pixbuf\n          ] ++ lib.optionals stdenv.isDarwin [\n            darwin.apple_sdk.frameworks.MetalKit\n            darwin.apple_sdk.frameworks.MetalPerformanceShaders\n            darwin.apple_sdk.frameworks.Carbon\n            darwin.apple_sdk.frameworks.WebKit\n            darwin.apple_sdk.frameworks.AppKit\n          ]);\n\n        envVars = {\n          LIBCLANG_PATH = \"${libclang.lib}/lib\";\n          ROCKSDB_LIB_DIR = \"${pkgs.rocksdb}/lib\";\n          ROCKSDB_INCLUDE_DIR = \"${pkgs.rocksdb}/include\";\n          OPENSSL_LIB_DIR = \"${pkgs.openssl.out}/lib\";\n          OPENSSL_INCLUDE_DIR = \"${pkgs.openssl.dev}/include\";\n          OPENSSL_NO_VENDOR = \"1\";\n        } // lib.optionalAttrs stdenv.isLinux {\n          RUSTFLAGS = \"-C link-arg=-fuse-ld=mold\";\n        };\n\n        bleep =\n          (rustPlatform.buildRustPackage.override { inherit stdenv; } rec {\n            meta = with pkgs.lib; {\n              description = \"Search code. Fast.\";\n              homepage = \"https://bloop.ai\";\n              license = licenses.asl20;\n              platforms = platforms.all;\n            };\n\n            name = \"bleep\";\n            pname = name;\n            src = pkgs.lib.sources.cleanSource ./.;\n\n            cargoLock = {\n              lockFile = ./Cargo.lock;\n              outputHashes = {\n                \"hyperpolyglot-0.1.7\" =\n                  \"sha256-JY75NB6sPxN0p/hksnBbat4S2EYFi2nExYoVHpYoib8=\";\n                \"tree-sitter-cpp-0.20.0\" =\n                  \"sha256-h6mJdmQzJlxYIcY+d5IiaFghraUgBGZwqFPKwB3E4pQ=\";\n                \"tree-sitter-go-0.19.1\" =\n                  \"sha256-f885YTswEDH/QfRPUxcLp/1E2zXLKl25R9IyTGKb1eM=\";\n                \"tree-sitter-java-0.20.0\" =\n                  \"sha256-gQzoWGV9wYiLibMFkLoY2sdEJg+ae9NnHt/GFfFzP8U=\";\n                \"ort-1.14.8\" =\n                  \"sha256-6YAhbrgI95WwRV0ngS0yaYlxfDGUFXYU0/oGf6vs68M=\";\n                \"comrak-0.18.0\" =\n                  \"sha256-UWY00jF2aKAG3Oz0P1UWF/7TiTIrCUGHwfjW+O1ok7Q=\";\n                \"tree-sitter-php-0.19.1\" =\n                  \"sha256-oHUfcuqtFFl+70/uJjE74J1JVV93G9++UaEIntOH5tM=\";\n                \"esaxx-rs-0.1.8\" =\n                  \"sha256-rPNNSn829eOo/glgmHPqnoylZmDLlaI5vKMRtfTikGs=\";\n              };\n            };\n\n            buildNoDefaultFeatures = true;\n            checkNoDefaultFeatures = true;\n            cargoTestFlags = \"-p ${name}\";\n            cargoBuildFlags = \"-p ${name}\";\n\n            nativeCheckInputs = buildDeps;\n            nativeBuildInputs = buildDeps;\n            checkInputs = runtimeDeps;\n            buildInputs = runtimeDeps;\n          }).overrideAttrs (old: envVars);\n\n        frontend = (pkgs.buildNpmPackage rec {\n          meta = with pkgs.lib; {\n            description = \"Search code. Fast.\";\n            homepage = \"https://bloop.ai\";\n            license = licenses.asl20;\n            platforms = platforms.all;\n          };\n\n          name = \"bleep-frontend\";\n          pname = name;\n          src = pkgs.lib.sources.cleanSource ./.;\n\n          # The prepack script runs the build script, which we'd rather do in the build phase.\n          npmPackFlags = [ \"--ignore-scripts\" ];\n          npmDepsHash = \"sha256-YvmdThbqlmQ9MXL+a7eyXJ33sQNStQah9MUW2zhc/Uc=\";\n          makeCacheWritable = true;\n          npmBuildScript = \"build-web\";\n          installPhase = ''\n            mkdir -p $out\n            cp -r client/dist $out/dist\n          '';\n        });\n\n      in\n      {\n        packages = {\n          default = bleep;\n\n          frontend = frontend;\n          bleep = bleep;\n          docker = pkgs.dockerTools.buildImage {\n            name = \"bleep\";\n            config = { Cmd = [ \"${bleep}/bin/bleep\" ]; };\n            extraCommands = ''\n              ln -s ${bleep}/bin/bleep /bleep\n            '';\n\n          };\n        };\n\n        devShells = {\n          default = (mkShell {\n            buildInputs = buildDeps ++ runtimeDeps ++ guiDeps ++ (with pkgs; [\n              git-lfs\n              rustfmt\n              clippy\n              rust-analyzer\n              cargo\n              rustc\n              cargo-watch\n            ]);\n\n            src = pkgs.lib.sources.cleanSource ./.;\n\n            BLOOP_LOG = \"bleep=debug\";\n          }).overrideAttrs (old: envVars);\n        };\n\n        formatter = pkgs.nixfmt;\n      });\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@bloop/root\",\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"run-s lfs:install lfs:pull start:tauri\",\n    \"watch\": \"run-s lfs:install lfs:pull watch:tauri\",\n    \"start:tauri\": \"npm --prefix apps/desktop run tauri dev -- --no-watch -- -- --config-file=../../../local_config.json\",\n    \"watch:tauri\": \"npm --prefix apps/desktop run tauri dev --            -- -- --config-file=../../../local_config.json\",\n    \"start-app\": \"npm start\",\n    \"start-web\": \"npm --prefix client run dev\",\n    \"lfs:install\": \"git lfs install\",\n    \"lfs:pull\": \"git lfs pull\",\n    \"build-app\": \"npm --prefix apps/desktop run tauri build -- --verbose\",\n    \"build-web\": \"npm --prefix client run build\",\n    \"tauri\": \"npm --prefix apps/desktop run tauri\",\n    \"lint\": \"eslint client/src apps/**/src --ext ts --ext tsx --ext js --ext jsx --ext html\",\n    \"client-type-check\": \"npm --prefix client run type-check\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.22.9\",\n    \"@playwright/test\": \"^1.36.2\",\n    \"@sentry/vite-plugin\": \"^2.5.0\",\n    \"@tauri-apps/cli\": \"^1.5.2\",\n    \"@testing-library/jest-dom\": \"^5.17.0\",\n    \"@testing-library/react\": \"^14.0.0\",\n    \"@types/jest\": \"^29.5.3\",\n    \"@types/lodash.debounce\": \"^4.0.7\",\n    \"@types/lodash.findlastindex\": \"^4.6.7\",\n    \"@types/lodash.flatten\": \"^4.4.7\",\n    \"@types/lodash.isequal\": \"^4.5.6\",\n    \"@types/lodash.throttle\": \"^4.1.7\",\n    \"@types/node\": \"^18.11.18\",\n    \"@types/prismjs\": \"^1.26.0\",\n    \"@types/react\": \"^18.0.17\",\n    \"@types/react-dom\": \"^18.0.6\",\n    \"@types/react-mentions\": \"^4.1.12\",\n    \"@types/react-virtualized-auto-sizer\": \"^1.0.1\",\n    \"@types/react-window\": \"^1.8.5\",\n    \"@types/remarkable\": \"^2.0.3\",\n    \"@types/sanitize-html\": \"^2.9.0\",\n    \"@types/uuid\": \"^9.0.2\",\n    \"@typescript-eslint/parser\": \"^6.2.0\",\n    \"@vitejs/plugin-react\": \"^3.1.0\",\n    \"autoprefixer\": \"^10.4.14\",\n    \"babel-loader\": \"^9.1.3\",\n    \"dotenv\": \"^16.3.1\",\n    \"eslint\": \"~8.22.0\",\n    \"eslint-config-prettier\": \"^8.9.0\",\n    \"eslint-config-react-app\": \"^7.0.1\",\n    \"eslint-import-resolver-typescript\": \"^3.4.2\",\n    \"eslint-plugin-import\": \"^2.26.0\",\n    \"eslint-plugin-jest\": \"^26.8.3\",\n    \"eslint-plugin-node\": \"^11.1.0\",\n    \"eslint-plugin-prettier\": \"^5.0.0\",\n    \"eslint-plugin-promise\": \"^6.0.0\",\n    \"eslint-plugin-react\": \"^7.30.1\",\n    \"jest\": \"^29.6.2\",\n    \"jest-environment-jsdom\": \"^29.6.2\",\n    \"postcss\": \"^8.4.27\",\n    \"prettier\": \"^3.0.0\",\n    \"react-test-renderer\": \"^18.2.0\",\n    \"tailwindcss\": \"^3.3.3\",\n    \"tailwindcss-labeled-groups\": \"^0.0.2\",\n    \"ts-jest\": \"^29.1.1\",\n    \"typescript\": \"^5.1.6\",\n    \"vite\": \"^4.4.7\",\n    \"vite-plugin-environment\": \"^1.1.3\"\n  },\n  \"dependencies\": {\n    \"@nytimes/react-prosemirror\": \"^0.4.2\",\n    \"@rive-app/react-canvas\": \"^4.0.0\",\n    \"@sentry/integrations\": \"^7.60.1\",\n    \"@sentry/react\": \"^7.60.1\",\n    \"@sentry/tracing\": \"^7.60.1\",\n    \"@tauri-apps/api\": \"^1.5.0\",\n    \"@tippyjs/react\": \"^4.2.6\",\n    \"ansi-to-html\": \"^0.7.2\",\n    \"axios\": \"^1.4.0\",\n    \"date-fns\": \"^2.30.0\",\n    \"downshift\": \"^8.2.3\",\n    \"file-icons-js\": \"^1.1.0\",\n    \"framer-motion\": \"^10.15.0\",\n    \"i18next\": \"^23.3.0\",\n    \"i18next-http-backend\": \"^2.2.1\",\n    \"lodash.debounce\": \"^4.0.8\",\n    \"lodash.findlastindex\": \"^4.6.0\",\n    \"lodash.flatten\": \"^4.4.0\",\n    \"lodash.isequal\": \"^4.5.0\",\n    \"lodash.throttle\": \"^4.1.1\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"prismjs\": \"^1.29.0\",\n    \"prosemirror-commands\": \"^1.5.2\",\n    \"prosemirror-keymap\": \"^1.2.2\",\n    \"prosemirror-model\": \"^1.19.3\",\n    \"prosemirror-schema-basic\": \"^1.2.2\",\n    \"prosemirror-state\": \"^1.4.3\",\n    \"prosemirror-view\": \"^1.32.4\",\n    \"react\": \"^18.2.0\",\n    \"react-dnd\": \"^16.0.1\",\n    \"react-dnd-html5-backend\": \"^16.0.1\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-i18next\": \"^13.0.2\",\n    \"react-markdown\": \"^8.0.7\",\n    \"react-mentions\": \"^4.4.10\",\n    \"react-router-dom\": \"^6.14.2\",\n    \"react-virtualized-auto-sizer\": \"^1.0.20\",\n    \"react-window\": \"^1.8.9\",\n    \"rehype-mathjax\": \"^4.0.3\",\n    \"remark-math\": \"^5.1.1\",\n    \"remarkable\": \"^2.0.1\",\n    \"rudder-sdk-js\": \"2.28.0\",\n    \"sanitize-html\": \"^2.11.0\",\n    \"sonner\": \"^1.2.4\",\n    \"uuid\": \"^9.0.0\"\n  },\n  \"volta\": {\n    \"node\": \"16.17.0\",\n    \"npm\": \"8.19.0\"\n  }\n}\n"
  },
  {
    "path": "playwright.config.js",
    "content": "// @ts-check\nconst { devices } = require('@playwright/test');\nconst dotenv = require('dotenv');\ndotenv.config({ path: './tests/.env' });\n/**\n * Read environment variables from file.\n * https://github.com/motdotla/dotenv\n */\n\n/**\n * @see https://playwright.dev/docs/test-configuration\n * @type {import('@playwright/test').PlaywrightTestConfig}\n */\nconst config = {\n  testDir: './tests',\n  /* Maximum time one test can run for. */\n  timeout: 120 * 1000,\n  expect: {\n    /**\n     * Maximum time expect() should wait for the condition to be met.\n     * For example in `await expect(locator).toHaveText();`\n     */\n    timeout: 5000,\n  },\n  /* Run tests in files in parallel */\n  fullyParallel: true,\n  /* Fail the build on CI if you accidentally left test.only in the source code. */\n  forbidOnly: !!process.env.CI,\n  /* Retry on CI only */\n  retries: process.env.CI ? 2 : 0,\n  /* Opt out of parallel tests on CI. */\n  workers: process.env.CI ? 1 : undefined,\n  /* Reporter to use. See https://playwright.dev/docs/test-reporters */\n  reporter: [\n    ['html', { open: 'never' }],\n    ['json', { outputFile: 'playwright-report/results.json' }],\n  ],\n  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */\n  use: {\n    /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */\n    actionTimeout: 0,\n    /* Base URL to use in actions like `await page.goto('/')`. */\n    // baseURL: 'http://localhost:3000',\n\n    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */\n    trace: 'on-first-retry',\n    headless: false,\n  },\n\n  /* Configure projects for major browsers */\n  projects: [\n    {\n      name: 'msedge',\n      use: { ...devices['Desktop Edge'] },\n    },\n    {\n      name: 'webkit',\n      use: { ...devices['Desktop Safari'] },\n    },\n    {\n      name: 'firefox',\n      use: { ...devices['Desktop Firefox'] },\n    },\n  ],\n\n  /* Folder for test artifacts such as screenshots, videos, traces, etc. */\n  // outputDir: 'test-results/',\n\n  /* Run your local dev server before starting the tests */\n  // webServer: {\n  //   command: '',\n  //   port: 3000,\n  // },\n};\n\nmodule.exports = config;\n"
  },
  {
    "path": "release_description_template.txt",
    "content": "macOS 11+ (Apple Silicon) - [bloop_VERSION_aarch64.dmg](https://github.com/BloopAI/bloop/releases/download/vVERSION/bloop_VERSION_aarch64.dmg)\nmacOS 11+ (Intel) - [bloop_VERSION_x64.dmg](https://github.com/BloopAI/bloop/releases/download/vVERSION/bloop_VERSION_x64.dmg)\nUbuntu Linux - [bloop_VERSION_amd64.deb](https://github.com/BloopAI/bloop/releases/download/vVERSION/bloop_VERSION_amd64.deb)\nLinux AppImage (Works on most distributions) - [bloop_VERSION_amd64.AppImage](https://github.com/BloopAI/bloop/releases/download/vVERSION/bloop_VERSION_amd64.AppImage)\nWindows - [bloop_VERSION_x64_en-US.msi](https://github.com/BloopAI/bloop/releases/download/vVERSION/bloop_VERSION_x64_en-US.msi)"
  },
  {
    "path": "server/README.md",
    "content": "# Server\n\nA cargo workspace which contains `bleep`, the Rust package which powers bloop's search and code navigation.\n\n## Setup\n\n### Install\n\nDependencies:\n - [`rust`](https://rustup.rs/)\n - `onnxruntime`\n\n### Build\n\n```bash\ncargo build -p bleep --release\n```\n\n## Usage\n\nTo index and search all the repos in a directory (say, `/path/to/source`) run (from this repo's root dir):\n\n```bash\n$ cargo run -p bleep --release -- \\\n  --source-dir /path/to/dir\n```\n\n`bleep` will recursively scan `/path/to/source` for repositories and start indexing them. It will also start a webserver. The location of the search index can be specified with the `--index-dir` parameter. By default, it is stored in the system cache.\n\n`bleep` periodically checks for changes to local and remote repos and automatically reindexes if a change is detected. Indexing and polling can be disabled by passing the `--disable-background` and `--disable-fsevents` flags.\n\nThe log level can be customized by setting the `BLOOP_LOG` env var.\n\n### Sync GitHub\n\nTo sync GitHub repos, first create a [GitHub Client ID](https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app). Then call `bleep` with the `--github-client-id <token>` parameter.\n\n### Query\n\nWith the server running you can start searching your code with regex search:\n\n```\n$ curl -v \"localhost:7878/api/q?q=anyhow%20path:webserver%20repo:bloop\" | jq\n```\n\nYou can check which repos are indexed and their status:\n```\n$ curl -v \"localhost:7878/api/repos/indexed\" | jq\n```\n\n### Arguments\n\nRun this to see the full list of arguments that `bleep` accepts:\n\n```\ncargo run -p bleep -- --help\n```"
  },
  {
    "path": "server/bleep/Cargo.toml",
    "content": "[package]\nname = \"bleep\"\nversion = \"0.6.4\"\nedition = \"2021\"\ndefault-run = \"bleep\"\nbuild = \"build.rs\"\n\n[features]\ndefault = [\"color-eyre\"]\ndebug = [\"console-subscriber\", \"histogram\"]\nonnx = []\nmetal = []\n\n[target.'cfg(not(all(target_os = \"macos\", target_arch = \"aarch64\")))'.dependencies]\nndarray = { version = \"0.15\" }\nort = { git = \"https://github.com/bloopai/ort\", branch = \"env-builder-telemetry\", features = [\"load-dynamic\"] }\n\n[target.aarch64-apple-darwin.dependencies]\nllm = { git = \"https://github.com/bloopai/llm\", branch = \"batch-metal-kernels\", default-features = false, features = [\"bert\", \"metal\"] }\n\n[[bin]]\nname = \"bleep\"\nrequired-features = [\"color-eyre\"]\n\n[dependencies]\n\n# core\ntantivy = { version = \"0.21.0\", features = [\"mmap\"] }\ntantivy-columnar = \"0.2.0\"\ntokio = { version = \"1.32.0\", features = [\"macros\", \"process\", \"rt\", \"rt-multi-thread\", \"io-std\", \"io-util\", \"sync\", \"fs\"] }\ntokio-stream = { version = \"0.1.14\", features = [\"sync\"]}\nasync-trait = \"0.1.73\"\nasync-stream = \"0.3.5\"\nflume = \"0.10.14\"\nfutures = \"0.3.30\"\nfutures-util = \"0.3.30\"\nrayon = \"1.8.0\"\nclap = { version = \"4.4.4\", features = [\"derive\"] }\ntracing = \"0.1.37\"\ntracing-subscriber = { version = \"0.3.17\", features = [\"env-filter\", \"registry\"] }\ntracing-appender = \"0.2.2\"\ncolor-eyre = { version = \"0.6.2\", optional = true }\nregex = \"1.9.5\"\nregex-syntax = \"0.6.29\"\nfuzzy-matcher = \"0.3.7\"\nuuid = { version = \"1.4.1\", features = [\"v4\", \"fast-rng\", \"serde\"] }\nsqlx = { version = \"0.6.3\", features = [\"sqlite\", \"migrate\", \"offline\", \"runtime-tokio-rustls\", \"chrono\", \"uuid\"] }\n\n# for debugging\nconsole-subscriber = { version = \"0.1.10\", optional = true }\nhistogram = { version = \"0.7.4\", optional = true }\n\n# error handling\nanyhow = \"1.0.75\"\nthiserror = \"1.0.48\"\n\n# query parsing\npest = \"2.7.3\"\npest_derive = \"2.7.3\"\n\n# code-nav\ntree-sitter = \"0.20.10\"\ntree-sitter-c = \"0.20.6\"\ntree-sitter-go = { git = \"https://github.com/tree-sitter/tree-sitter-go\", rev = \"05900fa\" }\ntree-sitter-javascript = \"0.20.1\"\ntree-sitter-python = \"=0.20.2\"\ntree-sitter-rust = \"0.20.4\"\ntree-sitter-typescript = \"0.20.2\"\ntree-sitter-c-sharp = \"0.20.0\"\ntree-sitter-java = { git = \"https://github.com/tree-sitter/tree-sitter-java\", tag = \"v0.20.0\" }\ntree-sitter-cpp = { git = \"https://github.com/tree-sitter/tree-sitter-cpp\", rev = \"5ead1e2\" }\ntree-sitter-ruby = \"0.20.0\"\ntree-sitter-r = \"0.19.5\"\ntree-sitter-php = { git = \"https://github.com/tree-sitter/tree-sitter-php\" }\ntree-sitter-COBOL = { git = \"https://github.com/BloopAI/tree-sitter-cobol\" }\npetgraph = { version = \"0.6.4\", default-features = false, features = [\"serde-1\"] }\n\n# webserver\nserde_json = \"1.0.107\"\naxum = { version = \"0.6.20\", features = [\"http2\", \"headers\", \"macros\"] }\ntower = \"0.4.13\"\ntower-http = { version = \"0.4.4\", features = [\"auth\", \"cors\", \"catch-panic\", \"fs\"] }\n\n# api integrations\noctocrab = { version = \"0.25.1\", features = [\"rustls\", \"rustls-webpki-tokio\"] }\nreqwest = { version = \"0.11.20\", features = [\"rustls-tls-webpki-roots\", \"cookies\", \"gzip\"], default-features = false }\nreqwest-eventsource = \"0.5.0\"\nsecrecy = { version = \"0.8.0\", features = [\"serde\"] }\n\n# file processing\nignore = \"=0.4.20\"\nhyperpolyglot = { git = \"https://github.com/bloopai/hyperpolyglot\" }\nblake3 = \"1.5.0\"\nnotify-debouncer-mini = { version = \"0.3.0\", default-features = false }\n\n# git\ngit-version = \"0.3.5\"\ngix = { git = \"https://github.com/BloopAI/gitoxide\", version=\"0.55.2\", features = [\"blocking-http-transport-reqwest-rust-tls-no-trust-dns\", \"pack-cache-lru-static\"] }\n\n# semantic\nqdrant-client = { version = \"1.5.0\", default-features = false }\ntiktoken-rs = \"0.5.7\"\ntokenizers = { version = \"0.14.0\", default-features = false, features = [\"progressbar\", \"cli\", \"onig\", \"esaxx_fast\"] }\nitertools = \"0.10.0\"\n\n\n# answer parsing\n# We use the git version here, so that we can pull in recent changes that make footnotes work. The\n# latest crates.io version at the time of writing does not include necessary patches.\n#\n# 25.09.23: we now maintain a fork for stability\ncomrak = { default-features = false, git = \"https://github.com/BloopAI/comrak\" }\nlazy-regex = \"3.0.2\"\nquick-xml = { version = \"0.29.0\", features = [\"serialize\"] }\n\n# doc scraper\nselect = \"0.6\"\ntree-sitter-md = \"0.1.5\"\nurl = \"2.4.1\"\n\n# misc\nserde = \"1.0.188\"\nerased-serde = \"0.3.31\"\nsmallvec = { version = \"1.11.1\", features = [\"serde\"]}\neither = \"1.9.0\"\ncompact_str = \"0.7.1\"\nbincode = \"1.3.3\"\ndirectories = \"5.0.1\"\nchrono = { version = \"0.4.31\", features = [\"serde\"], default-features = false }\nphf = \"0.11.2\"\nrand = \"0.8.5\"\nonce_cell = \"1.18.0\"\nrelative-path = \"1.9.0\"\nscc = { version= \"1.9.1\", features = [\"serde\"] }\nthread-priority = \"0.13.1\"\ndiffy = \"0.3.0\"\n\n[dev-dependencies]\npretty_assertions = \"1.4.0\"\ntempdir = \"0.3.7\"\nexpect-test = \"1.4.1\"\n\n[build-dependencies]\nphf_codegen = \"0.11.2\"\nserde = {version = \"1.0.188\", features = [\"derive\"]}\nserde_yaml = \"0.9.25\"\nblake3 = \"1.5.0\"\n"
  },
  {
    "path": "server/bleep/build.rs",
    "content": "use std::{\n    collections::HashMap,\n    env,\n    ffi::OsStr,\n    fs::File,\n    io::{BufWriter, Write},\n    path::Path,\n};\n\n#[derive(serde::Deserialize)]\nstruct Language {\n    r#type: String,\n    aliases: Option<Vec<String>>,\n}\nfn main() {\n    set_index_version();\n    process_languages();\n    determine_embedder_backend();\n    println!(\"cargo:rerun-if-changed=migrations\");\n}\n\nfn set_index_version() {\n    use std::fs::{read_dir, read_to_string};\n\n    let model_directories = &[\"src/intelligence/scope_resolution\", \"migrations\"];\n    let model_files = &[\n        \"sqlx-data.json\",\n        \"src/indexes/file.rs\",\n        \"src/semantic.rs\",\n        \"src/semantic/schema.rs\",\n        \"src/semantic/chunk.rs\",\n        \"src/indexes/schema.rs\",\n        \"src/intelligence/scope_resolution.rs\",\n        \"../languages.yml\",\n    ];\n\n    let mut hasher = blake3::Hasher::new();\n    for path in model_files {\n        hasher.update(read_to_string(path).unwrap().as_bytes());\n        println!(\"cargo:rerun-if-changed={path}\");\n    }\n\n    for path in model_directories\n        .iter()\n        .flat_map(|dir| read_dir(dir).unwrap())\n        .filter_map(Result::ok)\n        .filter_map(|entry| {\n            let path = entry.path();\n            if Some(OsStr::new(\"rs\")) == path.extension() {\n                Some(path)\n            } else {\n                None\n            }\n        })\n    {\n        hasher.update(read_to_string(&path).unwrap().as_bytes());\n        println!(\"cargo:rerun-if-changed={}\", path.to_string_lossy());\n    }\n\n    let version_file = Path::new(&env::var(\"OUT_DIR\").unwrap()).join(\"schema_version.rs\");\n    write!(\n        File::create(version_file).unwrap(),\n        r#\"pub const SCHEMA_VERSION: &str = \"{}\";\"#,\n        hasher.finalize()\n    )\n    .unwrap();\n}\n\nfn process_languages() {\n    let langs_file = File::open(\"../languages.yml\").unwrap();\n    let langs: HashMap<String, Language> = serde_yaml::from_reader(langs_file).unwrap();\n\n    let languages_path = Path::new(&env::var(\"OUT_DIR\").unwrap()).join(\"languages.rs\");\n    let mut ext_map = phf_codegen::Map::new();\n    let mut case_map = phf_codegen::Map::new();\n\n    for (name, data) in langs\n        .into_iter()\n        .filter(|(_, d)| d.r#type == \"programming\" || d.r#type == \"prose\")\n    {\n        let name_lower = name.to_ascii_lowercase();\n\n        for alias in data.aliases.unwrap_or_default() {\n            ext_map.entry(alias, &format!(\"\\\"{name_lower}\\\"\"));\n        }\n\n        case_map.entry(name_lower, &format!(\"\\\"{name}\\\"\"));\n    }\n\n    write!(\n        BufWriter::new(File::create(languages_path).unwrap()),\n        \"pub static EXT_MAP: phf::Map<&str, &str> = \\n{};\\n\\\n         pub static PROPER_CASE_MAP: phf::Map<&str, &str> = \\n{};\\n\",\n        ext_map.build(),\n        case_map.build(),\n    )\n    .unwrap();\n\n    println!(\"cargo:rerun-if-changed=../languages.yml\");\n}\n\nfn determine_embedder_backend() {\n    if is_apple_silicon() {\n        println!(\"cargo:rustc-cfg=feature=\\\"metal\\\"\")\n    } else {\n        println!(\"cargo:rustc-cfg=feature=\\\"onnx\\\"\")\n    }\n}\n\nfn is_apple_silicon() -> bool {\n    let target = env::var(\"TARGET\").unwrap();\n    let components: Vec<_> = target.split('-').map(|s| s.to_string()).collect();\n    components[0] == \"aarch64\" && components[2] == \"darwin\"\n}\n"
  },
  {
    "path": "server/bleep/migrations/20230424095042_conversations.sql",
    "content": "CREATE TABLE conversations (\n    id INTEGER PRIMARY KEY AUTOINCREMENT,\n    created_at INTEGER NOT NULL,\n    user_id TEXT NOT NULL,\n    thread_id TEXT NOT NULL,\n    repo_ref TEXT NOT NULL,\n    title TEXT NOT NULL,\n\n    -- JSON serialized fields\n    exchanges TEXT NOT NULL,\n    llm_history TEXT NOT NULL,\n    path_aliases TEXT NOT NULL,\n    code_chunks TEXT NOT NULL\n);\n"
  },
  {
    "path": "server/bleep/migrations/20230613143506_file-cache.sql",
    "content": "-- Add migration script here\nCREATE TABLE file_cache (\n    cache_hash TEXT PRIMARY KEY NOT NULL,\n    repo_ref TEXT NOT NULL\n);\n\nCREATE TABLE chunk_cache (\n    chunk_hash TEXT NOT NULL,\n    file_hash TEXT NOT NULL,\n    branches TEXT NOT NULL,\n    repo_ref TEXT NOT NULL\n);\n"
  },
  {
    "path": "server/bleep/migrations/20230616140930_query-log.sql",
    "content": "CREATE TABLE query_log (\n    id INTEGER PRIMARY KEY AUTOINCREMENT,\n    created_at DATETIME NOT NULL default (datetime('now')),\n    raw_query TEXT NOT NULL\n);\n"
  },
  {
    "path": "server/bleep/migrations/20230725183450_remove_conversation_llm_history_code_chunks_path.sql",
    "content": "-- Add migration script here\nALTER TABLE conversations DROP COLUMN llm_history;\nALTER TABLE conversations DROP COLUMN code_chunks;\nALTER TABLE conversations DROP COLUMN path_aliases;"
  },
  {
    "path": "server/bleep/migrations/20230821131141_code_studio.sql",
    "content": "CREATE TABLE studios (\n    -- UUID\n    id TEXT NOT NULL,\n\n    name TEXT NOT NULL,\n    modified_at DATETIME NOT NULL DEFAULT (datetime('now')),\n\n    -- JSON serialized fields\n    context TEXT NOT NULL,\n    messages TEXT NOT NULL\n);\n"
  },
  {
    "path": "server/bleep/migrations/20230831165918_templates.sql",
    "content": "CREATE TABLE templates (\n    id INTEGER PRIMARY KEY,\n    name TEXT NOT NULL,\n    modified_at DATETIME NOT NULL DEFAULT (datetime('now')),\n    content TEXT NOT NULL\n);\n\nINSERT INTO templates (\n  name,\n  content\n) VALUES (\n  'Write a test',\n  'Perform the following steps:\n\nStep 1: Describe the purpose of the code\nStep 2: Identify which functions would benefit from unit testing\nStep 3: Write a unit test for each of those functions'\n), (\n  'Code review',\n  'You''re a senior software engineer responsible for making sure no code makes it into production that doesn''t pass these rules:\n- All identifiers in the code have been named semantically so that their purpose is understood\n- All logic has been written in the most efficient way possible, optimising for the lowest compute consumption\n- Comments have been written where necessary to help future developers understand this code\n\nYour job is to 1) produce a list of issues related to the provided code and 2) provide example fixes for each issue'\n), (\n  'Explanation',\n  'I''m a new software engineer looking to learn about this codebase. As you''re a senior software engineer, could you please explain:\n1. The business purpose of this code\n2. The user journey\n3. How the code works, in the order of the user journey'\n);"
  },
  {
    "path": "server/bleep/migrations/20230831170927_code_studio_uuid_blob.sql",
    "content": "-- We re-create `studios`, with the `id` type as `BLOB` this time.\n\nDROP TABLE studios;\nCREATE TABLE studios (\n    -- UUID\n    id BLOB NOT NULL,\n\n    name TEXT NOT NULL,\n    modified_at DATETIME NOT NULL DEFAULT (datetime('now')),\n\n    -- JSON serialized fields\n    context TEXT NOT NULL,\n    messages TEXT NOT NULL\n);\n"
  },
  {
    "path": "server/bleep/migrations/20230831184906_code_studio_history.sql",
    "content": "ALTER TABLE studios RENAME TO studios_old;\n\nCREATE TABLE studios (\n    id BLOB NOT NULL,\n    name TEXT NOT NULL,\n\n    PRIMARY KEY (id)\n);\n\nCREATE TABLE studio_snapshots (\n    id INTEGER PRIMARY KEY,\n    studio_id BLOB NOT NULL,\n\n    modified_at DATETIME NOT NULL DEFAULT (datetime('now')),\n\n    -- JSON serialized fields\n    context TEXT NOT NULL,\n    messages TEXT NOT NULL,\n\n    FOREIGN KEY(studio_id) REFERENCES studios(id) ON DELETE CASCADE\n);\n\nINSERT INTO studios(id, name)\nSELECT id, name\nFROM studios_old;\n\nINSERT INTO studio_snapshots(studio_id, modified_at, context, messages)\nSELECT id, modified_at, context, messages\nFROM studios_old; \n\nDROP TABLE studios_old;\n"
  },
  {
    "path": "server/bleep/migrations/20230901200307_code_studio_user_id.sql",
    "content": "DELETE FROM studios;\nALTER TABLE studios ADD COLUMN user_id TEXT NOT NULL;\n"
  },
  {
    "path": "server/bleep/migrations/20230907154037_studio_int_id.sql",
    "content": "DROP TABLE studios;\nDROP TABLE studio_snapshots;\n\n-- Identical to before, except we change the primary key to be of type `INTEGER`.\nCREATE TABLE studios (\n    id INTEGER PRIMARY KEY,\n    name TEXT NOT NULL,\n    user_id TEXT NOT NULL\n);\n\n-- Identical to before, except we change the `studio_id` column to be of type `INTEGER`.\nCREATE TABLE studio_snapshots (\n    id INTEGER PRIMARY KEY,\n    studio_id INTEGER NOT NULL REFERENCES studios(id) ON DELETE CASCADE,\n\n    modified_at DATETIME NOT NULL DEFAULT (datetime('now')),\n\n    -- JSON serialized fields\n    context TEXT NOT NULL,\n    messages TEXT NOT NULL\n);\n"
  },
  {
    "path": "server/bleep/migrations/20230911161509_template_user_id.sql",
    "content": "-- This migration codifies the idea of \"default\" templates into the schema. These are simply\n-- templates which have a `NULL` `user_id` value. We also re-create default templates here, in case\n-- they have been edited. Now, we want to *maintain* \"default\" templates, copying them when modified\n-- instead of overwriting them.\n\nALTER TABLE templates ADD COLUMN user_id TEXT;\nDELETE FROM templates;\nINSERT INTO templates (\n  name,\n  content\n) VALUES (\n  'Write a test',\n  'Perform the following steps:\n\nStep 1: Describe the purpose of the code\nStep 2: Identify which functions would benefit from unit testing\nStep 3: Write a unit test for each of those functions'\n), (\n  'Code review',\n  'You''re a senior software engineer responsible for making sure no code makes it into production that doesn''t pass these rules:\n- All identifiers in the code have been named semantically so that their purpose is understood\n- All logic has been written in the most efficient way possible, optimising for the lowest compute consumption\n- Comments have been written where necessary to help future developers understand this code\n\nYour job is to 1) produce a list of issues related to the provided code and 2) provide example fixes for each issue'\n), (\n  'Explanation',\n  'I''m a new software engineer looking to learn about this codebase. As you''re a senior software engineer, could you please explain:\n1. The business purpose of this code\n2. The user journey\n3. How the code works, in the order of the user journey'\n);\n"
  },
  {
    "path": "server/bleep/migrations/20230912084309_nullable_studio_name.sql",
    "content": "ALTER TABLE studios RENAME TO studios_old;\nALTER TABLE studio_snapshots RENAME TO studio_snapshots_old;\n\n-- Identical to before, except we allow `name` to be null.\nCREATE TABLE studios (\n    id INTEGER PRIMARY KEY,\n    name TEXT,\n    user_id TEXT NOT NULL\n);\n\nCREATE TABLE studio_snapshots (\n    id INTEGER PRIMARY KEY,\n    studio_id INTEGER NOT NULL REFERENCES studios(id) ON DELETE CASCADE,\n\n    modified_at DATETIME NOT NULL DEFAULT (datetime('now')),\n\n    -- JSON serialized fields\n    context TEXT NOT NULL,\n    messages TEXT NOT NULL\n);\n\nINSERT INTO studios(id, name, user_id)\nSELECT id, name, user_id\nFROM studios_old;\n\nINSERT INTO studio_snapshots(id, studio_id, modified_at, context, messages)\nSELECT id, studio_id, modified_at, context, messages\nFROM studio_snapshots_old;\n\nDROP TABLE studio_snapshots_old;\nDROP TABLE studios_old;\n"
  },
  {
    "path": "server/bleep/migrations/20230915091923_tutorial-questions.sql",
    "content": "CREATE TABLE tutorial_questions (\n    repo_ref TEXT NOT NULL,\n    question TEXT NOT NULL,\n    tag TEXT NOT NULL\n);\n"
  },
  {
    "path": "server/bleep/migrations/20230919100529_code_studio_docs.sql",
    "content": "CREATE TABLE docs (\n    id INTEGER PRIMARY KEY,\n\n    url TEXT NOT NULL,\n    index_status TEXT NOT NULL,\n\n    name TEXT,\n    favicon TEXT,\n    description TEXT,\n\n    modified_at DATETIME NOT NULL DEFAULT (datetime('now'))\n);\nALTER TABLE studio_snapshots ADD COLUMN doc_context TEXT NOT NULL DEFAULT '[]';\n"
  },
  {
    "path": "server/bleep/migrations/20231004101827_refactor_template.sql",
    "content": "-- This migration adds an additional `refactor` template for Code Studio.\nDELETE FROM templates;\nINSERT INTO templates (\n  name,\n  content\n) VALUES (\n  'Write a test',\n  'Perform the following steps:\n\nStep 1: Describe the purpose of the code\nStep 2: Identify which functions would benefit from unit testing\nStep 3: Write a unit test for each of those functions'\n), (\n  'Code review',\n  'You''re a senior software engineer responsible for making sure no code makes it into production that doesn''t pass these rules:\n- All identifiers in the code have been named semantically so that their purpose is understood\n- All logic has been written in the most efficient way possible, optimising for the lowest compute consumption\n- Comments have been written where necessary to help future developers understand this code\n\nYour job is to 1) produce a list of issues related to the provided code and 2) provide example fixes for each issue'\n), (\n  'Explanation',\n  'I''m a new software engineer looking to learn about this codebase. As you''re a senior software engineer, could you please explain:\n1. The business purpose of this code\n2. The user journey\n3. How the code works, in the order of the user journey'\n), (\n    'Refactor',\n    'You are a senior software engineer reviewing a junior colleague''s code. Your job is to re-write it so that it is cleaner and more readable. \n\nYou should:\n- Only respond with code\n- Write code that is identical in functionality to your colleague''s\n- Write code that is cleaner and more readable than your colleague''s\n- If possible, write code that is more performant than your colleague''s\n- If possible, write code that is more secure than your colleague''s\n- If necessary, introduce new abstractions (classes, functions etc.)'\n);\n"
  },
  {
    "path": "server/bleep/migrations/20231122012638_projects.sql",
    "content": "-- This is a partial migration to migrate to the new `projects` structure. Here, we try to migrate\n-- as much of the data as possible. However, we cannot migrate everything as some JSON data has to\n-- be directly manipulated, in a way that is difficult with plain SQLite. As a compromise, we create\n-- a `rust_migrations` table, and keep track of logical changes there, intended to be applied\n-- immediately after all other pending database migrations.\n\nCREATE TABLE projects (\n    id INTEGER PRIMARY KEY,\n    user_id INTEGER NOT NULL,\n    name TEXT\n);\n\n-- First, we have to modify studios & snapshots. This involves recreating the `studios` table, and\n-- consequently also the `studio_snapshots` table, as SQLite doesn't allow us to dynamically modify\n-- foreign key constraints. So, we instead recreate both tables and copy data as required.\n\nALTER TABLE studios RENAME TO studios_old;\nALTER TABLE studio_snapshots RENAME TO studio_snapshots_old;\n\nALTER TABLE studios_old ADD COLUMN project_id INTEGER;\nALTER TABLE projects ADD COLUMN studio_id_tmp INTEGER;\n\nINSERT INTO projects (studio_id_tmp, user_id, name)\nSELECT id, user_id, name FROM studios_old;\n\nUPDATE studios_old\nSET project_id = (SELECT id FROM projects WHERE projects.studio_id_tmp = studios_old.id);\n\nCREATE TABLE studios (\n    id INTEGER PRIMARY KEY,\n    name TEXT,\n    project_id INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE\n);\n\nCREATE TABLE studio_snapshots (\n    id INTEGER PRIMARY KEY,\n    studio_id INTEGER NOT NULL REFERENCES studios (id) ON DELETE CASCADE,\n\n    modified_at DATETIME NOT NULL DEFAULT (datetime('now')),\n\n    -- JSON serialized fields\n    context TEXT NOT NULL,\n    messages TEXT NOT NULL,\n    doc_context TEXT NOT NULL DEFAULT '[]'\n);\n\nINSERT INTO studios (id, name, project_id)\nSELECT id, name, project_id FROM studios_old;\n\nINSERT INTO studio_snapshots (id, studio_id, modified_at, context, messages, doc_context)\nSELECT id, studio_id, modified_at, context, messages, doc_context FROM studio_snapshots_old;\n\nDROP TABLE studios_old;\nDROP TABLE studio_snapshots_old;\n\nALTER TABLE projects DROP COLUMN studio_id_tmp;\n\n-- Next, we can update the conversations table, following a similar process.\n\nALTER TABLE conversations RENAME TO conversations_old;\nALTER TABLE conversations_old ADD COLUMN project_id INTEGER;\nALTER TABLE projects ADD COLUMN conversation_id_tmp;\n\nINSERT INTO projects (conversation_id_tmp, user_id, name)\nSELECT id, user_id, title FROM conversations_old;\n\nUPDATE conversations_old\nSET project_id = (SELECT id FROM projects WHERE projects.conversation_id_tmp = conversations_old.id);\n\nCREATE TABLE conversations (\n    id INTEGER PRIMARY KEY AUTOINCREMENT,\n    project_id INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,\n    created_at INTEGER NOT NULL,\n    thread_id TEXT NOT NULL,\n    title TEXT NOT NULL,\n\n    -- JSON serialized fields\n    exchanges TEXT NOT NULL\n);\n\nINSERT INTO conversations (id, project_id, created_at, thread_id, title, exchanges)\nSELECT id, project_id, created_at, thread_id, title, exchanges FROM conversations_old;\n\n-- NB: We keep the `conversations_old` table around briefly, so that we can also create entries in `project_repos`:\n\nCREATE TABLE project_repos (\n    id INTEGER PRIMARY KEY,\n    project_id INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,\n    repo_ref TEXT NOT NULL,\n    branch TEXT,\n    UNIQUE (project_id, repo_ref)\n);\n\nINSERT INTO project_repos (project_id, repo_ref)\nSELECT project_id, repo_ref FROM conversations_old;\n\nDROP TABLE conversations_old;\n\n-- Finally, we create a new table to keep track of whether our manually-written Rust logic to\n-- migrate JSON data has been applied.\n\nCREATE TABLE rust_migrations (\n    id INTEGER PRIMARY KEY,\n\n    -- A textual reference we use in the Rust code to check the status of this logical migration.\n    ref TEXT NOT NULL,\n\n    applied BOOL NOT NULL\n);\n\nINSERT INTO rust_migrations (ref, applied) VALUES (\"project_migration\", false);\n"
  },
  {
    "path": "server/bleep/migrations/20231201200442_project_docs.sql",
    "content": "CREATE TABLE project_docs (\n    id INTEGER PRIMARY KEY,\n    project_id INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,\n    doc_id INTEGER NOT NULL,\n    UNIQUE (project_id, doc_id)\n);\n"
  },
  {
    "path": "server/bleep/sqlx-data.json",
    "content": "{\n  \"db\": \"SQLite\",\n  \"00ed95c6ae97970d4412af1d3f724dc9bd84c5f1fdb0ce8f4b8abefa9d86eded\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"name\",\n          \"ordinal\": 0,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"modified_at\",\n          \"ordinal\": 1,\n          \"type_info\": \"Datetime\"\n        }\n      ],\n      \"nullable\": [\n        true,\n        true\n      ],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"SELECT name, (\\n            SELECT ss.modified_at\\n            FROM studio_snapshots ss\\n            JOIN studios s ON s.project_id = $1 AND ss.studio_id = s.id\\n            ORDER BY ss.modified_at DESC\\n            LIMIT 1\\n        ) AS modified_at\\n        FROM projects\\n        WHERE id = $1 AND user_id = $2\\n        LIMIT 1\"\n  },\n  \"0411fe6b12497b63d08f6fb4d0dffba5d74895105b80a76c8300c8054ccabb2b\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 3\n      }\n    },\n    \"query\": \"INSERT INTO project_repos (project_id, repo_ref, branch) VALUES ($1, $2, $3)\"\n  },\n  \"069c6404909c217e0b27e974480cce3f592a0d43ece6dec17fbcee37ce7a6ffa\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"user_id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        true\n      ],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"SELECT user_id FROM templates WHERE id = ? AND (user_id = ? OR user_id IS NULL)\"\n  },\n  \"0814a29c70503ad8abb4894621394e2ce45f1244772ce30345279dbc104ea01f\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"messages\",\n          \"ordinal\": 0,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"context\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"doc_context\",\n          \"ordinal\": 2,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        false,\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"SELECT messages, context, doc_context FROM studio_snapshots WHERE id = ?\"\n  },\n  \"081aa337d2c658e09c21c3f7ec2f5164296c66e78fad34d2e65b47a0de508aeb\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"context\",\n          \"ordinal\": 0,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"messages\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"SELECT context, messages FROM studio_snapshots WHERE id = ?\"\n  },\n  \"0905057375a1d628fd57d3cc8ac5f9664711df0fcc886beb7ae14f661d121dfe\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 5\n      }\n    },\n    \"query\": \"INSERT INTO conversations (\\n                    id, thread_id, title, exchanges, project_id, created_at\\n                )\\n                VALUES (?, ?, ?, ?, ?, strftime('%s', 'now'))\"\n  },\n  \"0b7aa6a8c243ac8ca79f52a5ef370ccce954b3635ce5e70a6e80732472a916f2\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"exchanges\",\n          \"ordinal\": 0,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"thread_id\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 3\n      }\n    },\n    \"query\": \"SELECT c.exchanges, c.thread_id\\n            FROM conversations c\\n            JOIN projects p ON p.id = c.project_id AND p.user_id = ?\\n            WHERE c.project_id = ? AND c.id = ?\"\n  },\n  \"0fda94d4963a3991ff65079f63ba876030ecaeb1bb8fee3d6e729939a73ad4ea\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        }\n      ],\n      \"nullable\": [\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"INSERT INTO studios(name, project_id) VALUES (?, ?) RETURNING id\"\n  },\n  \"11f5e7122d047f87c398cf56470c284e2037203bc4d1506efc85e7431e2e2f5f\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"context\",\n          \"ordinal\": 0,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"SELECT context FROM studio_snapshots WHERE id = ?\"\n  },\n  \"147e29626a2c6adc98c563344b9ce31656bbaa7b024ee1ce64d8ad8e40494d8f\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"repo_ref\",\n          \"ordinal\": 0,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"branch\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        true\n      ],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"SELECT repo_ref, branch\\n        FROM project_repos\\n        WHERE project_id = $1 AND EXISTS (\\n            SELECT p.id\\n            FROM projects p\\n            WHERE p.id = $1 AND p.user_id = $2\\n        )\"\n  },\n  \"16c994183e3bb07ba8d6029c16d712222ce107cd1976889059c01780cb351dcd\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"repo_ref\",\n          \"ordinal\": 0,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"branch\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        true\n      ],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"SELECT pr.repo_ref, pr.branch\\n            FROM project_repos pr\\n            INNER JOIN projects p ON p.id = pr.project_id AND p.user_id = ?\"\n  },\n  \"17d2e9685f222726da591a004925941a8ef1a2d280a7e8e132005c879db20cee\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"context\",\n          \"ordinal\": 0,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"doc_context\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"project_id\",\n          \"ordinal\": 2,\n          \"type_info\": \"Int64\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        false,\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 0\n      }\n    },\n    \"query\": \"SELECT\\n            context,\\n            doc_context,\\n            (SELECT project_id FROM studios WHERE studios.id = studio_snapshots.studio_id) AS project_id\\n        FROM studio_snapshots\"\n  },\n  \"26065ed9dd0dfa42b8b943726d85425d0b45b2cafcceb9887ea626040bef9264\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        },\n        {\n          \"name\": \"index_status\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"name\",\n          \"ordinal\": 2,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"url\",\n          \"ordinal\": 3,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"favicon\",\n          \"ordinal\": 4,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"description\",\n          \"ordinal\": 5,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"modified_at\",\n          \"ordinal\": 6,\n          \"type_info\": \"Datetime\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        false,\n        true,\n        false,\n        true,\n        true,\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"SELECT id, index_status, name, url, favicon, description, modified_at FROM docs WHERE id = ?\"\n  },\n  \"291848ee7ef54ea247a2f83e89d2dd8e96024ba2fe65d0e443ecc94942aa6fa9\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"repo_ref\",\n          \"ordinal\": 0,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"branch\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        true\n      ],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"SELECT repo_ref, branch\\n            FROM project_repos\\n            WHERE project_id = ?\"\n  },\n  \"2d33f9119b3b56c55378080c5c95aa91fcb495ceb39caaa4f2541d8b2aa408ae\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"UPDATE docs SET favicon = ? WHERE id = ?\"\n  },\n  \"359b4d0fa1fcb081767303103b23f0650568cf4e79787c7ddcd21af5bad6761b\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 3\n      }\n    },\n    \"query\": \"INSERT INTO studio_snapshots(studio_id, context, messages) VALUES (?, ?, ?)\"\n  },\n  \"379eebe0708c4eaacf217368200c618e55e25f405b81e999dafb77a7579e2af4\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        },\n        {\n          \"name\": \"name\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"modified_at\",\n          \"ordinal\": 2,\n          \"type_info\": \"Datetime\"\n        },\n        {\n          \"name\": \"content\",\n          \"ordinal\": 3,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"is_default: bool\",\n          \"ordinal\": 4,\n          \"type_info\": \"Int\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        false,\n        false,\n        false,\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"SELECT id, name, modified_at, content, user_id IS NULL as \\\"is_default: bool\\\"\\n        FROM templates\\n        WHERE user_id = ? OR user_id IS NULL\"\n  },\n  \"38d0daccf0db90300be1f13ee5f4626af929d83e91d2df0f73ec9354fd7b16ff\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        }\n      ],\n      \"nullable\": [\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"DELETE FROM docs WHERE id = ? RETURNING id\"\n  },\n  \"3a8e50b31077e4aa516fc2492aa65b57e23a68fb23a53aabb9134a768cbb26f7\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"INSERT INTO project_repos (project_id, repo_ref)\\n                SELECT $1, $2\\n                WHERE NOT EXISTS (\\n                    SELECT 1 FROM project_repos WHERE project_id = $1 AND repo_ref = $2\\n                )\"\n  },\n  \"400b01ce2735d2606363727d3ad2b2e829775ea081d4cc2dc83b19e226061c1e\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"repo_ref\",\n          \"ordinal\": 0,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"SELECT repo_ref\\n        FROM project_repos\\n        WHERE project_id = ?\"\n  },\n  \"4118c5a7a20ea12c56aefd31ce0cb4c81f4bd969380e8bdf6db1cc894fd8be4f\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"project_id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        },\n        {\n          \"name\": \"repo_ref\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"SELECT pr.project_id, pr.repo_ref\\n        FROM project_repos pr\\n        JOIN projects p ON p.id = pr.project_id AND p.user_id = ?\"\n  },\n  \"454d7dfb50480aae5ad9c8372262d55a302e214e1c7ceb8d62b53832f75bd85b\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"UPDATE docs SET modified_at = datetime('now') WHERE id = ?\"\n  },\n  \"4573aa5ae3c4778b61a41e8984bb07b49d93e431c3f8434b5302df1a7a81997c\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"UPDATE studio_snapshots SET doc_context = ? WHERE id = ?\"\n  },\n  \"49f204678451d2c045fc1569707957e41bc170ea2ede754e2a5e660c14347bba\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"chunk_hash\",\n          \"ordinal\": 0,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"branches\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"SELECT chunk_hash, branches FROM chunk_cache WHERE file_hash = ?\"\n  },\n  \"4a279b8dbb55668f4073a19e7269ae280051183079d994faa8b8d9d8ebac424f\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"cache_hash\",\n          \"ordinal\": 0,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"SELECT cache_hash FROM file_cache WHERE repo_ref = ?\"\n  },\n  \"4a7ef503bb663992c6bf6567324e9bacbca61e9c03e23ba24a2773d7efa68043\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id!\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        },\n        {\n          \"name\": \"thread_id\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"created_at\",\n          \"ordinal\": 2,\n          \"type_info\": \"Int64\"\n        },\n        {\n          \"name\": \"title\",\n          \"ordinal\": 3,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        true,\n        false,\n        false,\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"SELECT c.id as 'id!', c.thread_id, c.created_at, c.title FROM conversations c JOIN projects p ON p.id = c.project_id AND p.user_id = ? WHERE p.id = ?\\n        ORDER BY c.created_at DESC\"\n  },\n  \"4aacc9795c1a466afdc7c5cedcdf1a6b67652a7ddcdee0399e67024e087d75a9\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"UPDATE studios SET name = ? WHERE id = ?\"\n  },\n  \"4b9781f9d09ff9468d533a162da45c4a0e13d7a58ae0c2afdc590fe6b8fc431e\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"INSERT INTO project_docs (project_id, doc_id)\\n                SELECT $1, $2\\n                WHERE NOT EXISTS (\\n                    SELECT 1 FROM project_docs WHERE project_id = $1 AND doc_id = $2\\n                )\"\n  },\n  \"4bf8d04acb2c99669237578467e50ac6822cb46053bced5d7d7a9dc374353e0d\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"DELETE FROM query_log WHERE created_at < ?\"\n  },\n  \"4d56665709831e4733eacc0b36fdd947d757c1b1bb1e7cf23c8eb6bbb79df7cc\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"INSERT INTO query_log (raw_query) VALUES (?)\"\n  },\n  \"4f8ac5ae1005cb9f30a211b05dbb803586cb3cc8991687b9e1f8fcbed308ff29\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        }\n      ],\n      \"nullable\": [\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 4\n      }\n    },\n    \"query\": \"INSERT INTO conversations (\\n                    thread_id, title, exchanges, project_id, created_at\\n                )\\n                VALUES (?, ?, ?, ?, strftime('%s', 'now'))\\n                RETURNING id\"\n  },\n  \"5128142bf657cfde043a1b53834d40980caa3e9ae5fd6f4d7f30d89be512f105\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"DELETE FROM chunk_cache WHERE chunk_hash = ? AND file_hash = ?\"\n  },\n  \"51613ed54060deefe909ecdf9f88a27a88f2a5b246589cebf67599953b6b13a0\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        }\n      ],\n      \"nullable\": [\n        true\n      ],\n      \"parameters\": {\n        \"Right\": 3\n      }\n    },\n    \"query\": \"DELETE FROM project_repos\\n        WHERE project_id = $1 AND repo_ref = $2 AND EXISTS (\\n            SELECT id\\n            FROM projects\\n            WHERE id = $1 AND user_id = $3\\n        )\\n        RETURNING id\"\n  },\n  \"523e2fd0c4f2c3318e894af3537b1c2e503e0865fcaabc4bdda43960ca0ef45c\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 3\n      }\n    },\n    \"query\": \"INSERT INTO templates (name, content, user_id) VALUES (?, ?, ?)\"\n  },\n  \"5261d3dfd64c97c87d86c9bae61bf51775174086518a4a0493b4b77da3b8cd6a\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"title\",\n          \"ordinal\": 0,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"exchanges\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"SELECT c.title, c.exchanges\\n        FROM conversations c\\n        JOIN projects p ON p.id = c.project_id AND p.user_id = ?\\n        WHERE thread_id = ?\"\n  },\n  \"548957b8a631f8a6388907ca13690348ae429ffe8528dd413f03ed236b4f7fb9\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 3\n      }\n    },\n    \"query\": \"DELETE FROM conversations\\n        WHERE id = $1 AND project_id = $2 AND EXISTS (\\n            SELECT p.id\\n            FROM projects p\\n            WHERE p.id = $2 AND p.user_id = $3\\n        )\"\n  },\n  \"5776008bf71ba2a90bad43c66a6e622ad71a81e1751c00b62aafa70840997999\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"UPDATE templates SET content = ? WHERE id = ?\"\n  },\n  \"596c58708e0f456557cc30581f5d646d1f5618d7d4c1dd8b6f6172f259943271\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        },\n        {\n          \"name\": \"index_status\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"name\",\n          \"ordinal\": 2,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"url\",\n          \"ordinal\": 3,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"favicon\",\n          \"ordinal\": 4,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"description\",\n          \"ordinal\": 5,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"modified_at\",\n          \"ordinal\": 6,\n          \"type_info\": \"Datetime\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        false,\n        true,\n        false,\n        true,\n        true,\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 0\n      }\n    },\n    \"query\": \"SELECT id, index_status, name, url, favicon, description, modified_at FROM docs\"\n  },\n  \"596f303a72529bbc9009d39dd5965fd3b5818595b57a3288cc33063bea5e0eed\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        },\n        {\n          \"name\": \"name\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"context\",\n          \"ordinal\": 2,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"doc_context\",\n          \"ordinal\": 3,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"messages\",\n          \"ordinal\": 4,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"modified_at\",\n          \"ordinal\": 5,\n          \"type_info\": \"Datetime\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        true,\n        false,\n        false,\n        false,\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 3\n      }\n    },\n    \"query\": \"SELECT s.id, s.name, ss.context, ss.doc_context, ss.messages, ss.modified_at\\n        FROM studios s\\n        INNER JOIN studio_snapshots ss ON ss.id = ?\\n        WHERE s.id = ? AND s.project_id = ?\"\n  },\n  \"5a12c77f8ee2a83b87cb5d9a79015fcc81d39d1466f853f613cb0fd789ace552\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"UPDATE docs SET description = ? WHERE id = ?\"\n  },\n  \"5bdd10bd3029a70911749c200c1680224e2fb9f4ce1bf463a6ebfcdf3de10ad6\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"UPDATE templates SET modified_at = datetime('now') WHERE id = ?\"\n  },\n  \"671df14b7c9077b95e586690f8c6d3f2eeb0a3942d0b800f272b010fcd2ca97b\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"messages\",\n          \"ordinal\": 0,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"context\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"SELECT messages, context FROM studio_snapshots WHERE id = ?\"\n  },\n  \"6767e546a8e1db775647021a74ebcb7868ce6b1e3c5b354cb5ee58464a1c3514\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        }\n      ],\n      \"nullable\": [\n        true\n      ],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"DELETE FROM projects WHERE id = ? AND user_id = ? RETURNING id\"\n  },\n  \"69c8b59ce4be3fc6edb58563bf69f55ea5dca4646b0ba05820e5d1b2b07c3c82\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"repo_ref\",\n          \"ordinal\": 0,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"question\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"tag\",\n          \"ordinal\": 2,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        false,\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"SELECT * FROM tutorial_questions WHERE repo_ref = ?\"\n  },\n  \"6a4def7f50a90ba02442566f45a8a0752eac147f00372c0b7cb3971aa8824333\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        }\n      ],\n      \"nullable\": [\n        true\n      ],\n      \"parameters\": {\n        \"Right\": 4\n      }\n    },\n    \"query\": \"DELETE FROM studio_snapshots\\n        WHERE id IN (\\n            SELECT ss.id\\n            FROM studio_snapshots ss\\n            JOIN studios s ON s.id = ss.studio_id\\n            JOIN projects p ON p.id = s.project_id\\n            WHERE ss.id = ? AND ss.studio_id = ? AND s.project_id = ? AND p.user_id = ?\\n        )\\n        RETURNING id\"\n  },\n  \"6bba4ade0d1e5cc62dc28a64ad5d2aa9cfdef463725d1cbac9af2cd5086b7a48\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"INSERT INTO project_docs (project_id, doc_id) VALUES ($1, $2)\"\n  },\n  \"6e842ac5eb4b5be53dff501a24b6b91c0557d6a7119480441a60c8e99de7daf1\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        },\n        {\n          \"name\": \"name\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"modified_at\",\n          \"ordinal\": 2,\n          \"type_info\": \"Datetime\"\n        },\n        {\n          \"name\": \"content\",\n          \"ordinal\": 3,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"is_default: bool\",\n          \"ordinal\": 4,\n          \"type_info\": \"Int\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        false,\n        false,\n        false,\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"SELECT id, name, modified_at, content, user_id IS NULL as \\\"is_default: bool\\\"\\n        FROM templates\\n        WHERE id = ? AND (user_id = ? OR user_id IS NULL)\"\n  },\n  \"719da56687c5febedfc051a6848e64e28c22120471edc06f0111b140ff0fd95c\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id!\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        },\n        {\n          \"name\": \"modified_at\",\n          \"ordinal\": 1,\n          \"type_info\": \"Datetime\"\n        },\n        {\n          \"name\": \"context\",\n          \"ordinal\": 2,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"doc_context\",\n          \"ordinal\": 3,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"messages\",\n          \"ordinal\": 4,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        true,\n        false,\n        false,\n        false,\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 3\n      }\n    },\n    \"query\": \"SELECT ss.id as 'id!', ss.modified_at, ss.context, ss.doc_context, ss.messages\\n        FROM studio_snapshots ss\\n        JOIN studios s ON s.id = ss.studio_id AND s.project_id = ?\\n        JOIN projects p ON p.id = s.project_id\\n        WHERE ss.studio_id = ? AND p.user_id = ?\\n        ORDER BY modified_at DESC\"\n  },\n  \"755ae8f05f5a0ae7c0942d5982abdc523a79cc3675f58bcc170a16e6999683b8\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"UPDATE conversations SET exchanges = ? WHERE id = ?\"\n  },\n  \"76464f75732fee5c742a23d7a0b95de1def360bae7943a2389944b0177106033\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        }\n      ],\n      \"nullable\": [\n        true\n      ],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"DELETE FROM templates WHERE id = ? AND user_id = ? RETURNING id\"\n  },\n  \"77cb1637b38b9a0988d8e07c08d8086c9047b50a7c2a664171a851271b877f9b\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        }\n      ],\n      \"nullable\": [\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"SELECT id FROM studios WHERE id = ? AND name IS NULL\"\n  },\n  \"783a880def0de864c1a6725fa645a2051dc67ccbd718937f9e93d2b1196e0cf0\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"UPDATE docs SET index_status = ? WHERE id = ?\"\n  },\n  \"7ce3d2c4733a59855501218145216b2367ccf46c8d76c11e9fccf6eb123623ef\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        }\n      ],\n      \"nullable\": [\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"INSERT INTO studios (project_id, name) VALUES (?, ?) RETURNING id\"\n  },\n  \"804524ce5653a0df70fb32bb7cba5dd4593ac0f3236db279ba0829b76526ea4a\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        }\n      ],\n      \"nullable\": [\n        true\n      ],\n      \"parameters\": {\n        \"Right\": 3\n      }\n    },\n    \"query\": \"DELETE FROM project_docs\\n        WHERE project_id = $1 AND doc_id = $2 AND EXISTS (\\n            SELECT id\\n            FROM projects\\n            WHERE id = $1 AND user_id = $3\\n        )\\n        RETURNING id\"\n  },\n  \"881aa78dfa3cd1bc3aa7a6edb8281aec5a972c1f53607d25c4e1f6d03cd3faef\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 3\n      }\n    },\n    \"query\": \"INSERT INTO tutorial_questions (question, tag, repo_ref) VALUES (?, ?, ?)\"\n  },\n  \"8efc3961c0182990afa0ffb553ecb77b3ad14a777970facb34f74d7e8e7bc1ab\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        }\n      ],\n      \"nullable\": [\n        true\n      ],\n      \"parameters\": {\n        \"Right\": 3\n      }\n    },\n    \"query\": \"UPDATE projects SET name = ? WHERE id = ? AND user_id = ? RETURNING id\"\n  },\n  \"8f99eede8e6c1fb27acc2524c00cebbc2d4e73db8af05599521e3b00c621347f\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"UPDATE studio_snapshots SET modified_at = ? WHERE id = ?\"\n  },\n  \"9146d9c8a7f17cc65c017cb364d1a853a9163b5ece336c0a6ef4e28e8df56a6b\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"UPDATE chunk_cache SET branches = ? WHERE chunk_hash = ?\"\n  },\n  \"940f2221bcffd98ced716442c4360353a6e2366c134c8d72283620db288e701c\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 4\n      }\n    },\n    \"query\": \"INSERT INTO studio_snapshots (studio_id, context, doc_context, messages)\\n         VALUES (?, ?, ?, ?)\"\n  },\n  \"95eaff0006df7f12604154c46113197e9440f06520672e2d7409cd0b831d83c2\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"repo_ref\",\n          \"ordinal\": 0,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"SELECT repo_ref\\n            FROM project_repos\\n            WHERE project_id = ?\"\n  },\n  \"9db35f3045790fbd63f1efc4a96e5a7234f09cc513323320fd145146b03cce2b\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        }\n      ],\n      \"nullable\": [\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"INSERT INTO projects (user_id, name) VALUES (?, ?) RETURNING id\"\n  },\n  \"9f862a56e79cc9ae6e9b896064a0057335b40225be0a8c8d29d9227de12ae364\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"DELETE FROM file_cache WHERE repo_ref = ?\"\n  },\n  \"a0c83095b8fd654d41970e3fbd65985ba5b1a40eb51d20dd00ebe223a743757d\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"INSERT INTO docs (url, index_status) VALUES (?, ?)\"\n  },\n  \"a2783e84301bf5a6639a03097ce52f91ec1240ea7c1df3e51c4d26098206729f\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 0\n      }\n    },\n    \"query\": \"UPDATE rust_migrations SET applied = true WHERE ref = 'project_migration'\"\n  },\n  \"a333e6cb457d03c6e54002e37908ecd56efe7862eed7a186db8eb2dc559c7f13\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        }\n      ],\n      \"nullable\": [\n        true\n      ],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"SELECT ss.id\\n        FROM studio_snapshots ss\\n        JOIN studios s ON s.id = ss.studio_id\\n        JOIN projects p ON s.project_id = p.id\\n        WHERE ss.studio_id = ? AND p.user_id = ?\\n        ORDER BY ss.modified_at DESC\\n        LIMIT 1\"\n  },\n  \"a4278b11c21e533d662043810e7cc8a3fca86cf03989766fffb84302d84394e5\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"UPDATE studio_snapshots SET context = ? WHERE id = ?\"\n  },\n  \"a7d8d84f5c97f5223d048bc3cdef92580195491edb3fc4c1757886a47be880bf\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"repo_ref\",\n          \"ordinal\": 0,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"SELECT repo_ref\\n            FROM project_repos\\n            WHERE project_id = $1 AND EXISTS (\\n                SELECT id\\n                FROM projects\\n                WHERE id = $1 AND user_id = $2\\n            )\"\n  },\n  \"a80c38e828ea4c3657ead89145e263af0ca709039ad9d57f45ddbde88af1acf0\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        },\n        {\n          \"name\": \"url\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"index_status\",\n          \"ordinal\": 2,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"name\",\n          \"ordinal\": 3,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"favicon\",\n          \"ordinal\": 4,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"description\",\n          \"ordinal\": 5,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"modified_at\",\n          \"ordinal\": 6,\n          \"type_info\": \"Datetime\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        false,\n        false,\n        true,\n        true,\n        true,\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"SELECT d.id, d.url, d.index_status, d.name, d.favicon, d.description, d.modified_at\\n        FROM project_docs pd\\n        INNER JOIN docs d ON d.id = pd.doc_id\\n        WHERE project_id = $1 AND EXISTS (\\n            SELECT p.id\\n            FROM projects p\\n            WHERE p.id = $1 AND p.user_id = $2\\n        )\"\n  },\n  \"a8baec57552045778eb4609e83452c668583bf5a6ff8fec1898523058a061bcb\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        },\n        {\n          \"name\": \"name\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"modified_at\",\n          \"ordinal\": 2,\n          \"type_info\": \"Datetime\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        true,\n        true\n      ],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"SELECT p.id, p.name, (\\n            SELECT ss.modified_at\\n            FROM studio_snapshots ss\\n            JOIN studios s ON s.project_id = p.id AND ss.studio_id = s.id\\n            ORDER BY ss.modified_at DESC\\n            LIMIT 1\\n        ) AS modified_at\\n        FROM projects p\\n        WHERE user_id = ?\"\n  },\n  \"abf57821a0ac6f855a9dc677de87beac319610add247dbff2f4ce9a2eec3ce2a\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"question\",\n          \"ordinal\": 0,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"tag\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"SELECT question, tag FROM tutorial_questions WHERE repo_ref = ?\"\n  },\n  \"ac1299cb16ae8ff77ded6a11241b84414352c12e55ce40b89e5b85109c7dc523\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"raw_query\",\n          \"ordinal\": 0,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"SELECT raw_query FROM query_log WHERE created_at > ?\"\n  },\n  \"adcf8cfb776a4a3954bf2fe4bbb9e562ae4a0a388cd26de91e8b51753357030e\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"UPDATE templates SET name = ? WHERE id = ?\"\n  },\n  \"b3ebaeec21c90aa9ebc59a808e03c661839d0a0eaa86ad2bf4251e895f8e0a03\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 4\n      }\n    },\n    \"query\": \"INSERT INTO chunk_cache (chunk_hash, file_hash, branches, repo_ref) VALUES (?, ?, ?, ?)\"\n  },\n  \"bdc5bead3461cb22ac8882b2e01fca9c3aa15db524a9e8bd897cd2b5d5769c55\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        }\n      ],\n      \"nullable\": [\n        true\n      ],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"DELETE FROM conversations\\n            WHERE thread_id = ? AND EXISTS (\\n                SELECT p.id\\n                FROM projects p\\n                WHERE p.id = project_id AND p.user_id = ?\\n            )\\n            RETURNING id\"\n  },\n  \"c1b0276926023df7e91a219e7720b0f5ade8d1b13de577dce4d78d31075a497b\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        }\n      ],\n      \"nullable\": [\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"SELECT id FROM projects WHERE id = ? AND user_id = ?\"\n  },\n  \"c60995349620b88d6c1fce3fbd4988a16715bcb8c4a579312b11f26d86f061a3\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"repo_ref\",\n          \"ordinal\": 0,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"SELECT repo_ref FROM project_repos WHERE project_id = ?\"\n  },\n  \"d2b52987aaa4bdc39c04254834c941cad2165eefd02eef46fda413822be91fd0\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"messages\",\n          \"ordinal\": 0,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"SELECT messages FROM studio_snapshots WHERE id = ?\"\n  },\n  \"d616a930841d3828f8cc151852bd2cfda4750e713857caedfbe43b3502a0bb45\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"INSERT INTO file_cache (repo_ref, cache_hash) VALUES (?, ?)\"\n  },\n  \"db4077fd7603079ffc8c237ec49a640a6061a06d12499bdb7b39ed3c23c1b38e\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"UPDATE studio_snapshots SET messages = ? WHERE id = ?\"\n  },\n  \"deae1c1c2619ec6e76e0b5fcc526bbabbc1d66642efc6158a793068221ebd019\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        },\n        {\n          \"name\": \"name\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"url\",\n          \"ordinal\": 2,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"description\",\n          \"ordinal\": 3,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"favicon\",\n          \"ordinal\": 4,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"modified_at\",\n          \"ordinal\": 5,\n          \"type_info\": \"Datetime\"\n        },\n        {\n          \"name\": \"index_status\",\n          \"ordinal\": 6,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        true,\n        false,\n        true,\n        true,\n        false,\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"\\n            SELECT id, name, url, description, favicon, modified_at, index_status\\n            FROM docs \\n            WHERE name LIKE $1 OR description LIKE $1 OR url LIKE $1\\n            LIMIT ?\\n            \"\n  },\n  \"dfcac17e60fb1250bf08d6f53d7f0c7ca45c0c8a22d17c4640f2e8c04050a767\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        },\n        {\n          \"name\": \"project_id\",\n          \"ordinal\": 1,\n          \"type_info\": \"Int64\"\n        },\n        {\n          \"name\": \"exchanges\",\n          \"ordinal\": 2,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        false,\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 0\n      }\n    },\n    \"query\": \"SELECT id, project_id, exchanges FROM conversations\"\n  },\n  \"e352cd10053f43e1e586da8a933868b5329fb1468292769587f130d7bc8f6ca3\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        }\n      ],\n      \"nullable\": [\n        true\n      ],\n      \"parameters\": {\n        \"Right\": 3\n      }\n    },\n    \"query\": \"UPDATE project_repos\\n        SET branch = ?\\n        WHERE project_id = ? AND repo_ref = ?\\n        RETURNING id\"\n  },\n  \"e72d027c61c96b02889751f6817129ea73d23383be3dae5fc1bffdc6e7b2c46d\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        },\n        {\n          \"name\": \"name\",\n          \"ordinal\": 1,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"modified_at!\",\n          \"ordinal\": 2,\n          \"type_info\": \"Datetime\"\n        },\n        {\n          \"name\": \"context\",\n          \"ordinal\": 3,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"doc_context\",\n          \"ordinal\": 4,\n          \"type_info\": \"Text\"\n        },\n        {\n          \"name\": \"messages\",\n          \"ordinal\": 5,\n          \"type_info\": \"Text\"\n        }\n      ],\n      \"nullable\": [\n        false,\n        true,\n        false,\n        false,\n        false,\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"SELECT\\n            s.id,\\n            s.name,\\n            ss.modified_at as \\\"modified_at!\\\",\\n            ss.context,\\n            ss.doc_context,\\n            ss.messages\\n        FROM studios s\\n        INNER JOIN studio_snapshots ss ON s.id = ss.studio_id\\n        INNER JOIN projects p ON p.id = s.project_id\\n        WHERE s.project_id = ? AND p.user_id = ? AND (ss.studio_id, ss.modified_at) IN (\\n            SELECT studio_id, MAX(modified_at)\\n            FROM studio_snapshots\\n            GROUP BY studio_id\\n        )\"\n  },\n  \"e95b48b7690809a8a16b642414139a00e975b1a08a6dda02521ca9d43a485017\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"applied\",\n          \"ordinal\": 0,\n          \"type_info\": \"Bool\"\n        }\n      ],\n      \"nullable\": [\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 0\n      }\n    },\n    \"query\": \"SELECT applied FROM rust_migrations WHERE ref = 'project_migration'\"\n  },\n  \"ec193a038eb7fc3aaca3c3adebcc4dbde01b47ae34ac2df2227c5e5459617182\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"UPDATE studio_snapshots SET modified_at = datetime('now') WHERE id = ?\"\n  },\n  \"ed6379e37c16064198f48dbfb91899d74eb346533e3c9ab3814ba67b68d71f51\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 1\n      }\n    },\n    \"query\": \"DELETE FROM chunk_cache WHERE repo_ref = ?\"\n  },\n  \"ed687be90dac04aae56f72cf7c6cceb8bac19ce7d88d4b7c7e3bc8e9f9a06473\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"INSERT INTO studio_snapshots(studio_id, context, doc_context, messages)\\n            SELECT studio_id, context, doc_context, ?\\n            FROM studio_snapshots\\n            WHERE id = ?\"\n  },\n  \"ee41c9f473344709b6e7a4bdf29948d236870d54d33938d200d015b174249291\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        }\n      ],\n      \"nullable\": [\n        true\n      ],\n      \"parameters\": {\n        \"Right\": 3\n      }\n    },\n    \"query\": \"DELETE FROM studios\\n        WHERE id = $1 AND project_id = $2 AND EXISTS (\\n            SELECT p.id FROM projects p WHERE p.id = $2 AND p.user_id = $3\\n        )\\n        RETURNING id\"\n  },\n  \"f91f80f8d1a82a5d79ce50131618877a50c0753a1ccb1f4cee714e274f022907\": {\n    \"describe\": {\n      \"columns\": [],\n      \"nullable\": [],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"UPDATE docs SET name = ? WHERE id = ?\"\n  },\n  \"fc9f0b31bbf0316abd2e24cadabfd2e5ea42cb729b69a2f8caa1a3c52f167f63\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        }\n      ],\n      \"nullable\": [\n        false\n      ],\n      \"parameters\": {\n        \"Right\": 3\n      }\n    },\n    \"query\": \"SELECT s.id FROM studios s\\n        JOIN projects p ON p.id = s.project_id\\n        WHERE s.id = ? AND p.id = ? AND p.user_id = ?\"\n  },\n  \"fd74b491f6b06bb58c7d62b461094e5463e397bb649ae338c2b1a0e67e6155c3\": {\n    \"describe\": {\n      \"columns\": [\n        {\n          \"name\": \"id\",\n          \"ordinal\": 0,\n          \"type_info\": \"Int64\"\n        }\n      ],\n      \"nullable\": [\n        true\n      ],\n      \"parameters\": {\n        \"Right\": 2\n      }\n    },\n    \"query\": \"INSERT INTO templates(name, content, user_id)\\n            SELECT name, content, ?\\n            FROM templates\\n            WHERE id = ?\\n            RETURNING id\"\n  }\n}"
  },
  {
    "path": "server/bleep/src/agent/exchange.rs",
    "content": "use crate::{query::parser::SemanticQuery, repo::RepoRef};\nuse std::fmt;\n\nuse chrono::prelude::{DateTime, Utc};\n\n#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Hash, PartialEq, Eq)]\npub struct RepoPath {\n    pub repo: RepoRef,\n    pub path: String,\n}\n\nimpl fmt::Display for RepoPath {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}:{}\", self.repo, self.path)\n    }\n}\n\n/// A continually updated conversation exchange.\n///\n/// This contains the query from the user, the intermediate steps the model takes, and the final\n/// conclusion from the model alongside the answer, if any.\n#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default)]\npub struct Exchange {\n    pub id: uuid::Uuid,\n    pub query: SemanticQuery<'static>,\n    pub answer: Option<String>,\n    pub search_steps: Vec<SearchStep>,\n    pub paths: Vec<RepoPath>,\n    pub code_chunks: Vec<CodeChunk>,\n\n    /// A specifically chosen \"focused\" code chunk.\n    ///\n    /// This is different from the `code_chunks` list, as focused code chunks also contain the full\n    /// surrounding context from the source file, not just the relevant snippet.\n    ///\n    /// In the context of the app, this can be used to show code side-by-side with an outcome, such\n    /// as when displaying an article.\n    pub focused_chunk: Option<FocusedChunk>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    query_timestamp: Option<DateTime<Utc>>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    response_timestamp: Option<DateTime<Utc>>,\n\n    conclusion: Option<String>,\n}\n\nimpl Exchange {\n    pub fn new(id: uuid::Uuid, query: SemanticQuery<'static>) -> Self {\n        Self {\n            id,\n            query,\n            query_timestamp: Some(Utc::now()),\n            ..Default::default()\n        }\n    }\n\n    /// Advance this exchange.\n    ///\n    /// An update should not result in fewer search results or fewer search steps.\n    pub fn apply_update(&mut self, update: Update) {\n        match update {\n            Update::StartStep(search_step) => self.search_steps.push(search_step),\n            Update::ReplaceStep(search_step) => match (self.search_steps.last_mut(), search_step) {\n                (Some(l @ SearchStep::Path { .. }), r @ SearchStep::Path { .. }) => *l = r,\n                (Some(l @ SearchStep::Code { .. }), r @ SearchStep::Code { .. }) => *l = r,\n                (Some(l @ SearchStep::Proc { .. }), r @ SearchStep::Proc { .. }) => *l = r,\n                _ => panic!(\"Tried to replace a step that was not found\"),\n            },\n            Update::Article(full_text) => {\n                *self.answer.get_or_insert_with(String::new) = full_text;\n            }\n            Update::Focus(chunk) => {\n                self.focused_chunk = Some(chunk);\n            }\n            Update::SetTimestamp => {\n                self.response_timestamp = Some(Utc::now());\n            }\n        }\n    }\n\n    /// Get the query associated with this exchange, if it has been made.\n    pub fn query(&self) -> Option<String> {\n        self.query.target().map(|q| q.to_string())\n    }\n\n    /// Get the answer associated with this exchange.\n    ///\n    /// This returns a tuple of `full_text`.\n    pub fn answer(&self) -> Option<&str> {\n        return self.answer.as_deref();\n    }\n\n    /// Return a copy of this exchange, with all function call responses redacted.\n    ///\n    /// This is used to reduce the size of an exchange when we send it over the wire, by removing\n    /// data that the front-end does not use.\n    pub fn compressed(mut self) -> Self {\n        self.code_chunks.clear();\n        self.paths.clear();\n        self.search_steps = self\n            .search_steps\n            .into_iter()\n            .map(|step| step.compressed())\n            .collect();\n\n        self\n    }\n}\n\n#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]\n#[serde(rename_all = \"lowercase\", tag = \"type\", content = \"content\")]\n#[non_exhaustive]\npub enum SearchStep {\n    Path {\n        query: String,\n        response: String,\n    },\n    Code {\n        query: String,\n        response: String,\n    },\n    Proc {\n        query: String,\n        paths: Vec<RepoPath>,\n        response: String,\n    },\n}\n\nimpl SearchStep {\n    /// Create a \"compressed\" clone of this step, by redacting all response data.\n    ///\n    /// Used in `Exchange::compressed`.\n    fn compressed(&self) -> Self {\n        match self {\n            Self::Path { query, .. } => Self::Path {\n                query: query.clone(),\n                response: \"[hidden, compressed]\".into(),\n            },\n            Self::Code { query, .. } => Self::Code {\n                query: query.clone(),\n                response: \"[hidden, compressed]\".into(),\n            },\n            Self::Proc { query, paths, .. } => Self::Proc {\n                query: query.clone(),\n                paths: paths.clone(),\n                response: \"[hidden, compressed]\".into(),\n            },\n        }\n    }\n\n    pub fn get_response(&self) -> String {\n        match self {\n            Self::Path { response, .. } => response.clone(),\n            Self::Code { response, .. } => response.clone(),\n            Self::Proc { response, .. } => response.clone(),\n        }\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]\npub struct CodeChunk {\n    pub repo_path: RepoPath,\n    pub alias: usize,\n    pub snippet: String,\n    #[serde(rename = \"start\")]\n    pub start_line: usize,\n    #[serde(rename = \"end\")]\n    pub end_line: usize,\n    pub start_byte: Option<usize>,\n    pub end_byte: Option<usize>,\n}\n\nimpl CodeChunk {\n    /// Returns true if a code-chunk contains an empty snippet or a snippet with only whitespace\n    pub fn is_empty(&self) -> bool {\n        self.snippet.trim().is_empty()\n    }\n}\n\nimpl fmt::Display for CodeChunk {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(\n            f,\n            \"{}: {}\\t{}\\n{}\",\n            self.alias, self.repo_path.repo, self.repo_path.path, self.snippet\n        )\n    }\n}\n\n#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]\npub struct FocusedChunk {\n    pub repo_path: RepoPath,\n    pub start_line: usize,\n    pub end_line: usize,\n}\n\n#[derive(Debug)]\npub enum Update {\n    StartStep(SearchStep),\n    ReplaceStep(SearchStep),\n    Article(String),\n    Focus(FocusedChunk),\n    SetTimestamp,\n}\n"
  },
  {
    "path": "server/bleep/src/agent/model.rs",
    "content": "use crate::agent::prompts;\nuse std::str::FromStr;\n\n#[derive(Debug, Copy, Clone)]\npub struct LLMModel {\n    /// The name of this model according to tiktoken\n    pub tokenizer: &'static str,\n\n    /// The name of this model for use in the llm gateway\n    pub model_name: &'static str,\n\n    /// The number of tokens reserved for the answer\n    pub answer_headroom: usize,\n\n    /// The number of tokens reserved for the prompt\n    pub prompt_headroom: usize,\n\n    /// The number of tokens reserved for history\n    pub history_headroom: usize,\n\n    /// The system prompt to be used\n    pub system_prompt: fn(&str) -> String,\n}\n\npub const GPT_3_5_TURBO_FINETUNED: LLMModel = LLMModel {\n    tokenizer: \"gpt-3.5-turbo-0613\",\n    model_name: \"gpt-3.5-turbo-finetuned\",\n    answer_headroom: 512,\n    prompt_headroom: 1600,\n    history_headroom: 1024,\n    system_prompt: prompts::answer_article_prompt_finetuned,\n};\n\n// GPT-4 turbo has a context window of 128k tokens\nconst GPT_4_TURBO_MAX_TOKENS: usize = 128_000;\n// We want to use only 24k tokens\nconst ACTUAL_MAX_TOKENS: usize = 24_000;\n// 104k tokens should be left unused. This is done by adding 104k to the headrooms\n// (tokens left unused for other purposes answer, prompt...)\nconst HEADROOM_CORRECTION: usize = GPT_4_TURBO_MAX_TOKENS - ACTUAL_MAX_TOKENS;\n// PS: when we want to fully utilize the model max context window, the correction is 0\npub const GPT_4_TURBO_24K: LLMModel = LLMModel {\n    tokenizer: \"gpt-4-1106-preview\",\n    model_name: \"gpt-4-turbo\",\n    answer_headroom: 1024 + HEADROOM_CORRECTION,\n    prompt_headroom: 2500 + HEADROOM_CORRECTION,\n    history_headroom: 2048 + HEADROOM_CORRECTION,\n    system_prompt: prompts::answer_article_prompt,\n};\n\npub const GPT_4: LLMModel = LLMModel {\n    tokenizer: \"gpt-4-0613\",\n    model_name: \"gpt-4-0613\",\n    answer_headroom: 1024,\n    prompt_headroom: 2500,\n    history_headroom: 2048,\n    system_prompt: prompts::answer_article_prompt,\n};\n\nimpl FromStr for LLMModel {\n    type Err = ();\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        #[allow(clippy::wildcard_in_or_patterns)]\n        match s {\n            \"gpt-4\" => Ok(GPT_4),\n            \"gpt-4-turbo-24k\" => Ok(GPT_4_TURBO_24K),\n            \"gpt-3.5-turbo-finetuned\" | _ => Ok(GPT_3_5_TURBO_FINETUNED),\n        }\n    }\n}\n\nimpl<'de> serde::Deserialize<'de> for LLMModel {\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: serde::Deserializer<'de>,\n    {\n        let s = String::deserialize(deserializer)?;\n        s.parse::<LLMModel>()\n            .map_err(|_| serde::de::Error::custom(\"failed to deserialize\"))\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/agent/prompts.rs",
    "content": "use std::collections::HashSet;\n\nuse crate::agent::exchange::RepoPath;\n\npub fn functions(add_proc: bool) -> serde_json::Value {\n    let mut funcs = serde_json::json!(\n        [\n            {\n                \"name\": \"code\",\n                \"description\":  \"Search the contents of files in a codebase semantically. Results will not necessarily match search terms exactly, but should be related.\",\n                \"parameters\": {\n                    \"type\": \"object\",\n                    \"properties\": {\n                        \"query\": {\n                            \"type\": \"string\",\n                            \"description\": \"A search query consisting of keywords. For example: 'react functional components', 'contextmanager', 'bearer token'\"\n                        }\n                    },\n                    \"required\": [\"query\"]\n                }\n            },\n            {\n                \"name\": \"path\",\n                \"description\": \"Search the pathnames in a codebase. Use when you want to find a specific file or directory. Results may not be exact matches, but will be similar by some edit-distance.\",\n                \"parameters\": {\n                    \"type\": \"object\",\n                    \"properties\": {\n                        \"query\": {\n                            \"type\": \"string\",\n                            \"description\": \"A search query. This should not contain whitespace. For example: 'server/src', 'test', 'index.js'\"\n                        }\n                    },\n                    \"required\": [\"query\"]\n                }\n            },\n            {\n                \"name\": \"none\",\n                \"description\": \"Call this to answer the user. Call this only when you have enough information to answer the user's query.\",\n                \"parameters\": {\n                    \"type\": \"object\",\n                    \"properties\": {\n                        \"paths\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"type\": \"integer\",\n                                \"description\": \"The indices of the paths to answer with respect to. Can be empty if the answer is not related to a specific path.\"\n                            }\n                        }\n                    },\n                    \"required\": [\"paths\"]\n                }\n            },\n        ]\n    );\n\n    if add_proc {\n        funcs.as_array_mut().unwrap().push(\n            serde_json::json!(\n            {\n                \"name\": \"proc\",\n                \"description\": \"Read one or more files and extract the snippets of text that are relevant to the search terms.\",\n                \"parameters\": {\n                    \"type\": \"object\",\n                    \"properties\": {\n                        \"query\": {\n                            \"type\": \"string\",\n                            \"description\": \"A search query consisting of keywords.\"\n                        },\n                        \"paths\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"type\": \"integer\",\n                                \"description\": \"The indices of the paths to search.\"\n                            }\n                        }\n                    },\n                    \"required\": [\"query\", \"paths\"]\n                }\n            }\n            )\n        );\n    }\n    funcs\n}\n\npub fn system<'a>(paths: impl IntoIterator<Item = &'a RepoPath>) -> String {\n    let paths = paths.into_iter().collect::<Vec<_>>();\n\n    let mut s = \"\".to_string();\n\n    let repos = paths.iter().map(|rp| &rp.repo).collect::<HashSet<_>>();\n\n    s.push_str(\"## REPOS ##\\n\");\n    for repo in repos {\n        s.push_str(&format!(\"{repo}\\n\"));\n    }\n\n    let mut iter = paths.into_iter().peekable();\n\n    if iter.peek().is_some() {\n        s.push_str(\"\\n## PATHS ##\\nindex, repo, path\\n\");\n        for (i, path) in iter.enumerate() {\n            let repo = &path.repo;\n            let repo = &format!(\"{repo}\");\n            let path = &path.path;\n            s.push_str(&format!(\"{}, {}, {}\\n\", i, repo, path));\n        }\n        s.push('\\n');\n    }\n\n    s.push_str(\n        r#\"Your job is to choose the best action. Call functions to find information that will help answer the user's query. Call functions.none when you have enough information to answer. Follow these rules at all times:\n\n- ALWAYS call a function, DO NOT answer the question directly, even if the query is not in English\n- DO NOT call a function that you've used before with the same arguments\n- DO NOT assume the structure of the indexed repos (listed above), or the existence of files or folders\n- Your queries to functions.code or functions.path should be significantly different to previous queries\n- Call functions.none with paths that you are confident will help answer the user's query, include paths containing the information needed for a complete answer including definitions and references\n- If the user query is general (e.g. 'What does this do?', 'What is this repo?') look for READMEs, documentation and entry points in the code (main files, index files, api files etc.)\n- If the user is referring to, or asking for, information that is in your history, call functions.none\n- If after attempting to gather information you are still unsure how to answer the query, call functions.none\n- If the query is a greeting, or neither a question nor an instruction, call functions.none\n- When calling functions.code your query should consist of keywords. E.g. if the user says 'What does contextmanager do?', your query should be 'contextmanager'. If the user says 'How is contextmanager used in app', your query should be 'contextmanager app'. If the user says 'What is in the src directory', your query should be 'src'\n- When calling functions.path your query should be a single term (no whitespace). E.g. if the user says 'Where is the query parser?', your query should be 'parser'. If the users says 'What's in the auth dir?', your query should be 'auth'\n- If the output of a function is empty, try calling the function again with DIFFERENT arguments OR try calling a different function\n- Only call functions.proc with path indices that are under the PATHS heading above\n- Call functions.proc with paths that might contain relevant information. Either because of the path name or to expand on a chunk returned by functions.code. For example, if a chunk contains a reference to a term in the query, you might want to call functions.proc with the path of the chunk\n- ALWAYS call a function. DO NOT answer the question directly\"#);\n    s\n}\n\npub fn answer_article_prompt(context: &str) -> String {\n    format!(\n        r#\"{context}####\n\nYou are an expert programmer called 'bloop' and you are helping a junior colleague answer questions about some repos using the information above. If their query refers to 'this' or 'it' and there is no other context, assume that it refers to the information above.\n\nProvide only as much information and code as is necessary to answer the query, but be concise. Keep number of quoted lines to a minimum when possible. If you do not have enough information needed to answer the query, do not make up an answer. Infer as much as possible from the information above.\nWhen referring to code, you must provide an example in a code block.\n\nRespect these rules at all times:\n- Link ALL paths AND code symbols (functions, methods, fields, classes, structs, types, variables, values, definitions, directories, etc) by embedding them in a markdown link, with the URL corresponding to the full path, and the anchor following the form `LX` or `LX-LY`, where X represents the starting line number, and Y represents the ending line number, if the reference is more than one line.\n  - For example, to refer to lines 50 to 78 in a sentence, respond with something like: The compiler is initialized in [`src/foo.rs`](//local/path/to/repo:src/foo.rs#L50-L78)\n  - For example, to refer to the `new` function on a struct, respond with something like: The [`new`](github.com/org/repo:src/bar.rs#L26-53) function initializes the struct\n  - For example, to refer to the `foo` field on a struct and link a single line, respond with something like: The [`foo`](github.com/org/repo:src/foo.rs#L138) field contains foos. Do not respond with something like [`foo`](src/foo.rs#L138-L138)\n  - For example, to refer to a folder `foo`, respond with something like: The files can be found in [`foo`](//local/path/to/repo:path/to/foo/) folder\n- Do not print out line numbers directly, only in a link\n- Do not refer to more lines than necessary when creating a line range, be precise\n- Do NOT output bare symbols. ALL symbols must include a link\n  - E.g. Do not simply write `Bar`, write [`Bar`](github.com/org/repo:src/bar.rs#L100-L105).\n  - E.g. Do not simply write \"Foos are functions that create `Foo` values out of thin air.\" Instead, write: \"Foos are functions that create [`Foo`](github.com/org/repo:src/foo.rs#L80-L120) values out of thin air.\"\n- Link all fields\n  - E.g. Do not simply write: \"It has one main field: `foo`.\" Instead, write: \"It has one main field: [`foo`](//local/path/to/repo:src/foo.rs#L193).\"\n- Do NOT link external urls not present in the context, do NOT link urls from the internet\n- Link all symbols, even when there are multiple in one sentence\n  - E.g. Do not simply write: \"Bars are [`Foo`]( that return a list filled with `Bar` variants.\" Instead, write: \"Bars are functions that return a list filled with [`Bar`](//local/path/to/repo:src/bar.rs#L38-L57) variants.\"\n  - If you do not have enough information needed to answer the query, do not make up an answer. Instead respond only with a footnote that asks the user for more information, e.g. `assistant: I'm sorry, I couldn't find what you were looking for, could you provide more information?`\n- Code blocks MUST be displayed to the user using XML in the following formats:\n  - Do NOT output plain markdown blocks, the user CANNOT see them\n  - To create new code, you MUST mimic the following structure (example given):\n###\nThe following demonstrates logging in JavaScript:\n<GeneratedCode>\n<Code>\nconsole.log(\"hello world\")\n</Code>\n<Language>JavaScript</Language>\n</GeneratedCode>\n###\n  - To quote existing code, use the following structure (example given):\n###\nThis is referred to in the Rust code:\n<QuotedCode>\n<Code>\nprintln!(\"hello world!\");\nprintln!(\"hello world!\");\n</Code>\n<Language>Rust</Language>\n<Path>//local/path/to/repo:src/main.rs</Path>\n<StartLine>4</StartLine>\n<EndLine>5</EndLine>\n</QuotedCode>\n###\n  - `<GeneratedCode>` and `<QuotedCode>` elements MUST contain a `<Language>` value, and `<QuotedCode>` MUST additionally contain `<Path>`, `<StartLine>`, and `<EndLine>`.\n  - Note: the line range is inclusive\n- When writing example code blocks, use `<GeneratedCode>`, and when quoting existing code, use `<QuotedCode>`.\n- You MUST use XML code blocks instead of markdown.\"#\n    )\n}\n\n// Do not change this prompt. A model needs to be retrained before doing it (the non finetune prompt can be modified instead)\npub fn answer_article_prompt_finetuned(context: &str) -> String {\n    format!(\n        r#\"{context}####\n\nYou are an expert programmer called 'bloop' and you are helping a junior colleagure answer questions about a codebase using the information above. If their query refers to 'this' or 'it' and there is no other context, assume that it refers to the information above.\n\nProvide only as much information and code as is necessary to answer the query, but be concise. Keep number of quoted lines to a minimum when possible. If you do not have enough information needed to answer the query, do not make up an answer. Infer as much as possible from the information above.\nWhen referring to code, you must provide an example in a code block.\n\nRespect these rules at all times:\n- Link ALL paths AND code symbols (functions, methods, fields, classes, structs, types, variables, values, definitions, directories, etc) by embedding them in a markdown link, with the URL corresponding to the full path, and the anchor following the form `LX` or `LX-LY`, where X represents the starting line number, and Y represents the ending line number, if the reference is more than one line.\n  - For example, to refer to lines 50 to 78 in a sentence, respond with something like: The compiler is initialized in [`src/foo.rs`](src/foo.rs#L50-L78)\n  - For example, to refer to the `new` function on a struct, respond with something like: The [`new`](src/bar.rs#L26-53) function initializes the struct\n  - For example, to refer to the `foo` field on a struct and link a single line, respond with something like: The [`foo`](src/foo.rs#L138) field contains foos. Do not respond with something like [`foo`](src/foo.rs#L138-L138)\n  - For example, to refer to a folder `foo`, respond with something like: The files can be found in [`foo`](path/to/foo/) folder\n- Do not print out line numbers directly, only in a link\n- Do not refer to more lines than necessary when creating a line range, be precise\n- Do NOT output bare symbols. ALL symbols must include a link\n  - E.g. Do not simply write `Bar`, write [`Bar`](src/bar.rs#L100-L105).\n  - E.g. Do not simply write \"Foos are functions that create `Foo` values out of thin air.\" Instead, write: \"Foos are functions that create [`Foo`](src/foo.rs#L80-L120) values out of thin air.\"\n- Link all fields\n  - E.g. Do not simply write: \"It has one main field: `foo`.\" Instead, write: \"It has one main field: [`foo`](src/foo.rs#L193).\"\n- Do NOT link external urls not present in the context, do NOT link urls from the internet\n- Link all symbols, even when there are multiple in one sentence\n  - E.g. Do not simply write: \"Bars are [`Foo`]( that return a list filled with `Bar` variants.\" Instead, write: \"Bars are functions that return a list filled with [`Bar`](src/bar.rs#L38-L57) variants.\"\n  - If you do not have enough information needed to answer the query, do not make up an answer. Instead respond only with a footnote that asks the user for more information, e.g. `assistant: I'm sorry, I couldn't find what you were looking for, could you provide more information?`\n- Code blocks MUST be displayed to the user using XML in the following formats:\n  - Do NOT output plain markdown blocks, the user CANNOT see them\n  - To create new code, you MUST mimic the following structure (example given):\n###\nThe following demonstrates logging in JavaScript:\n<GeneratedCode>\n<Code>\nconsole.log(\"hello world\")\n</Code>\n<Language>JavaScript</Language>\n</GeneratedCode>\n###\n  - To quote existing code, use the following structure (example given):\n###\nThis is referred to in the Rust code:\n<QuotedCode>\n<Code>\nprintln!(\"hello world!\");\nprintln!(\"hello world!\");\n</Code>\n<Language>Rust</Language>\n<Path>src/main.rs</Path>\n<StartLine>4</StartLine>\n<EndLine>5</EndLine>\n</QuotedCode>\n###\n  - `<GeneratedCode>` and `<QuotedCode>` elements MUST contain a `<Language>` value, and `<QuotedCode>` MUST additionally contain `<Path>`, `<StartLine>`, and `<EndLine>`.\n  - Note: the line range is inclusive\n- When writing example code blocks, use `<GeneratedCode>`, and when quoting existing code, use `<QuotedCode>`.\n- You MUST use XML code blocks instead of markdown.\"#\n    )\n}\n\npub fn studio_article_prompt(context: &str) -> String {\n    format!(\n        r#\"{context}Your job is to answer a query about a codebase using the information above.\n\nYou must use the following formatting rules at all times:\n- Provide only as much information and code as is necessary to answer the query and be concise\n- If you do not have enough information needed to answer the query, do not make up an answer\n- When referring to code, you must provide an example in a code block\n- Keep number of quoted lines of code to a minimum when possible\n- When outputting code blocks, you MUST use four backticks for the outer block!\n  - For example, to generate code which includes doc comments:\n    ````rust\n    /** Foos the bar\n\n    ```\n    assert_eq!(foo(123), 124);\n    ```\n    **/\n    fn foo(bar: i32) -> i32 {{\n        bar + 1\n    }}\n    ````\n- When quoting code in a code block, use the following info string format: language:LANG,path:PATH\n  - For example, to quote `github.com/org/repo:src/main.c`:\n    ````language:c,path:github.com/org/repo:src/main.c\n    int main() {{\n      printf(\"hello world!\");\n    }}\n    ````\n  - For example, to quote `local//path/to/repo:index.js`:\n  ````language:javascript,path:local//path/to/repo:index.js\n  console.log(\"hello world!\")\n  ````\n- Basic markdown is otherwise allowed\"#\n    )\n}\n\npub fn studio_name_prompt(context_json: &str, messages_json: &str) -> String {\n    format!(\n        r#\"Your job is to generate a name for a conversation about software source code, given source code context and conversation history.\n\nFollow these rules strictly:\n    - You MUST only return the new title, and NO additional text\n    - Be brief, only return a few words, 3-5 is ideal\n    - Do NOT include quotation marks in your title\n    - Do NOT use gerunds (e.g. \"Searching for...\")\n\nHere are some example titles demonstrating the correct style:\n    - Rust PyO3 Function Reference\n    - Update HelmRelease Chart Version\n    - Readable Code and Tests\n\n######\n\nHere is the source code context:\n=====\n{context_json}\n=====\n\nAnd here is the serialized conversation:\n=====\n{messages_json}\n=====\"#\n    )\n}\n\npub fn studio_diff_prompt(context_formatted: &str) -> String {\n    format!(\n        r#\"Below are files from a codebase. Your job is to write a Unified Format patch to complete a provided task. To write a unified format patch, surround it in a code block: ```diff\n\nFollow these rules strictly:\n- Diff paths follow the format `github.com/org/repo:path/to/file.js` for remote repositories, or `local//path/to/repo:path/to/file.js` for local repositories. Make sure to include these in your diff.\n- You MUST only return a single diff block, no additional commentary.\n- Keep hunks concise only include a short context for each hunk. \n- ALWAYS respect input whitespace, to ensure diffs can be applied cleanly!\n- Only generate diffs that can be applied by `patch`! NO extra information like `git` commands\n- To add a new file, set the input file as /dev/null\n- To remove an existing file, set the output file to /dev/null\n\n# Example outputs\n\n```diff\n--- github.com/BloopAI/tutorial:src/index.js\n+++ github.com/BloopAI/tutorial:src/index.js\n@@ -10,5 +10,5 @@\n const maybeHello = () => {{\n     if (Math.random() > 0.5) {{\n-        console.log(\"hello world!\")\n+        console.log(\"hello?\")\n     }}\n }}\n```\n\n```diff\n--- local//Users/blooper/dev/bloop:README.md\n+++ local//Users/blooper/dev/bloop:README.md\n@@ -1,3 +1,3 @@\n # Bloop AI\n \n-bloop is ChatGPT for your code. Ask questions in natural language, search for code and generate patches using your existing codebase as context.\n+bloop is ChatGPT for your code. Ask questions in natural language, search for code and generate patches using your existing code base as context.\n```\n\n```diff\n--- github.com/BloopAI/bloop:client/src/locales/en.json\n+++ github.com/BloopAI/bloop:client/src/locales/en.json\n@@ -21,5 +21,5 @@\n \t\"Report a bug\": \"Report a bug\",\n \t\"Sign In\": \"Sign In\",\n-\t\"Sign in with GitHub\": \"Sign in with GitHub\",\n+\t\"Sign in via GitHub\": \"Sign in via GitHub\",\n \t\"Status\": \"Status\",\n \t\"Submit bug report\": \"Submit bug report\",\n```\n\nAdding a new file:\n\n```diff\n--- /dev/null\n+++ local//tmp/test-project:src/sum.rs\n@@ -0,0 +1,3 @@\n+fn sum(a: f32, b: f32) -> f32 {{\n+    a + b\n+}}\n```\n\nRemoving an existing file:\n\n```diff\n--- local//tmp/another-project:src/div.rs\n+++ /dev/null\n@@ -1,3 +0,0 @@\n-fn div(a: f32, b: f32) -> f32 {{\n-    a / b\n-}}\n```\n\n#####\n\n{context_formatted}\"#\n    )\n}\n\npub fn studio_diff_regen_hunk_prompt(context_formatted: &str) -> String {\n    format!(\n        r#\"The provided diff contains no context lines. Output a new hunk with the correct 3 context lines.\n\nHere is the full context for reference:\n\n#####\n\n{context_formatted}\"#\n    )\n}\n\npub fn symbol_classification_prompt(snippets: &str) -> String {\n    format!(\n        r#\"{snippets}\n\nAbove are code chunks and non-local symbols that have been extracted from the chunks. Each chunk is followed by an enumerated list of symbols that it contains. Given a user query, select the symbol which is most relevant to it, e.g. the references or definition of this symbol would help somebody answer the query. Symbols which are language builtins or which come from third party libraries are unlikely to be helpful.\n\nDo not answer with the symbol name, use the symbol index. If none of the symbols are relevant, answer with 0.\n\n### Examples ###\nQ: how does ranking work?\n23\n\nQ: which function makes an api call \n3\"#\n    )\n}\n\npub fn hypothetical_document_prompt(query: &str) -> String {\n    format!(\n        r#\"Write a code snippet that could hypothetically be returned by a code search engine as the answer to the query: {query}\n\n- Write the snippets in a programming or markup language that is likely given the query\n- The snippet should be between 5 and 10 lines long\n- Surround the snippet in triple backticks\n\nFor example:\n\nWhat's the Qdrant threshold?\n\n```rust\nSearchPoints {{\n    limit,\n    vector: vectors.get(idx).unwrap().clone(),\n    collection_name: COLLECTION_NAME.to_string(),\n    offset: Some(offset),\n    score_threshold: Some(0.3),\n    with_payload: Some(WithPayloadSelector {{\n        selector_options: Some(with_payload_selector::SelectorOptions::Enable(true)),\n    }}),\n```\"#\n    )\n}\n\npub fn try_parse_hypothetical_documents(document: &str) -> Vec<String> {\n    let pattern = r\"```([\\s\\S]*?)```\";\n    let re = regex::Regex::new(pattern).unwrap();\n\n    re.captures_iter(document)\n        .map(|m| m[1].trim().to_string())\n        .collect()\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_parse_hypothetical_document() {\n        let document = r#\"Here is some pointless text\n\n        ```rust\npub fn final_explanation_prompt(context: &str, query: &str, query_history: &str) -> String {\n    struct Rule<'a> {\n        title: &'a str,\n        description: &'a str,\n        note: &'a str,\n        schema: &'a str,```\n\nHere is some more pointless text\n\n```\npub fn functions() -> serde_json::Value {\n    serde_json::json!(\n```\"#;\n        let expected = vec![\n            r#\"rust\npub fn final_explanation_prompt(context: &str, query: &str, query_history: &str) -> String {\n    struct Rule<'a> {\n        title: &'a str,\n        description: &'a str,\n        note: &'a str,\n        schema: &'a str,\"#,\n            r#\"pub fn functions() -> serde_json::Value {\n    serde_json::json!(\"#,\n        ];\n\n        assert_eq!(try_parse_hypothetical_documents(document), expected);\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/agent/symbol.rs",
    "content": "use crate::agent::{exchange::CodeChunk, Agent};\nuse crate::intelligence::{code_navigation::FileSymbols, Language, TSLanguage};\nuse crate::llm;\nuse crate::webserver::intelligence::{get_token_info, TokenInfoRequest};\nuse anyhow::{Context, Result};\nuse tracing::log::{debug, info, warn};\n\nuse super::exchange::RepoPath;\nuse super::prompts::symbol_classification_prompt;\n\npub struct ChunkWithHoverableSymbols {\n    pub chunk: CodeChunk,\n    pub symbols: Vec<HoverableSymbol>,\n}\n\n/// This helps the code and proc tool return related chunks based on references and definitions.\n/// `get_related_chunks` receives a list of chunks from code or proc search and returns `MAX_CHUNKS` related chunks\n/// For each input chunk, we extract all symbols (variables, function names, structs...).\n/// Then we disconsider symbols that are defined in the same file using the scope graph.\n/// We then pick ONE symbol using a classifier (`filter_symbols`), where the classifier has access to user query, original chunks and filtered list of symbols.\n/// This selected symbol may be present in many files one or more times.\n/// We extract the surrounding code (up to `NUMBER_CHUNK_LINES` lines) for each occurence and pick `MAX_CHUNKS` occurrences/chunks.\n\nimpl Agent {\n    pub async fn extract_hoverable_symbols(\n        &self,\n        chunk: CodeChunk,\n    ) -> Result<ChunkWithHoverableSymbols> {\n        // get hoverable elements\n        let document = self\n            .app\n            .indexes\n            .file\n            .by_path(&chunk.repo_path.repo, &chunk.repo_path.path, None)\n            .await?\n            .with_context(|| format!(\"failed to read path: {}\", &chunk.repo_path))?;\n\n        let graph = document\n            .symbol_locations\n            .scope_graph()\n            .with_context(|| format!(\"no scope graph for file: {}\", &chunk.repo_path))?;\n\n        let hoverable_ranges = document\n            .hoverable_ranges()\n            .ok_or_else(|| anyhow::anyhow!(\"no hoverable ranges\"))?;\n\n        let mut symbols = hoverable_ranges\n            .into_iter()\n            .filter(|range| {\n                (range.start.byte >= chunk.start_byte.unwrap_or_default())\n                    && (range.start.byte < chunk.end_byte.unwrap_or_default())\n            })\n            .filter(|range| {\n                // if this node can be resolved locally in the scope-graph, omit it\n                if let Some(node_by_range) = graph.node_by_range(range.start.byte, range.end.byte) {\n                    if graph.is_reference(node_by_range) || graph.is_definition(node_by_range) {\n                        return false;\n                    }\n                }\n                true\n            })\n            .map(|range| HoverableSymbol {\n                name: chunk.snippet[(range.start.byte - chunk.start_byte.unwrap_or_default())\n                    ..(range.end.byte - chunk.start_byte.unwrap_or_default())]\n                    .to_string(),\n                token_info_request: TokenInfoRequest {\n                    relative_path: chunk.repo_path.path.clone(),\n                    repo_ref: chunk.repo_path.repo.indexed_name(),\n                    branch: None,\n                    start: range.start.byte,\n                    end: range.end.byte,\n                },\n                repo_path: chunk.repo_path.clone(),\n            })\n            .collect::<Vec<_>>();\n\n        symbols.sort_by(|a, b| a.name.cmp(&b.name));\n        symbols.dedup_by(|a, b| a.name == b.name);\n\n        debug!(\n            \"Attached {} symbols: {:?}\",\n            symbols.len(),\n            symbols.iter().map(|s| s.name.as_str()).collect::<Vec<_>>()\n        );\n\n        Ok(ChunkWithHoverableSymbols {\n            chunk: chunk.clone(),\n            symbols,\n        })\n    }\n\n    pub async fn expand_symbol_into_chunks(&self, symbol: Symbol) -> Vec<CodeChunk> {\n        // each symbol may be in multiple files and have multiple occurences in each file\n        symbol\n            .related_symbols\n            .iter()\n            .flat_map(|file_symbols| {\n                let filename = file_symbols.file.clone();\n\n                file_symbols\n                    .data\n                    .iter()\n                    .map(|occurrence| CodeChunk {\n                        repo_path: RepoPath {\n                            repo: file_symbols.repo.clone(),\n                            path: filename.clone(),\n                        },\n                        alias: 0,\n                        snippet: occurrence.snippet.data.clone(),\n                        start_line: occurrence.snippet.line_range.start,\n                        end_line: occurrence.snippet.line_range.end,\n                        start_byte: None,\n                        end_byte: None,\n                    })\n                    .collect::<Vec<_>>()\n            })\n            .collect::<Vec<_>>()\n    }\n\n    pub async fn filter_symbols(\n        &self,\n        query: &str,\n        chunks_with_symbols: Vec<ChunkWithHoverableSymbols>,\n    ) -> Result<Symbol, SymbolError> {\n        if chunks_with_symbols.is_empty() {\n            return Err(SymbolError::ListEmpty);\n        }\n\n        const NUMBER_CHUNK_LINES: usize = 10;\n\n        // we have multiples chunks and each chunk may have multiple symbols\n        // unique alias (i) per symbol\n        let mut i: i32 = -1;\n        let symbols = chunks_with_symbols\n            .into_iter()\n            .map(|chunk_with_symbol| {\n                (\n                    chunk_with_symbol.chunk,\n                    chunk_with_symbol\n                        .symbols\n                        .into_iter()\n                        .map(|symbol| {\n                            i += 1;\n                            (i, symbol)\n                        })\n                        .collect::<Vec<_>>(),\n                )\n            })\n            .collect::<Vec<_>>();\n\n        // Classifier\n\n        // context\n        let chunks_string = symbols\n            .iter()\n            .filter(|(_, s)| !s.is_empty())\n            .map(|(c, s)| {\n                let symbols_string = s\n                    .iter()\n                    .map(|(i, refdef)| format!(\"{}: {}\", i, refdef.name))\n                    .collect::<Vec<_>>()\n                    .join(\"\\n\");\n\n                format!(\n                    \"```{}\\n{}```\\n\\n{}\",\n                    c.repo_path,\n                    c.snippet.clone(),\n                    symbols_string\n                )\n            })\n            .collect::<Vec<_>>()\n            .join(\"\\n\\n\");\n\n        // instruction\n        let messages = vec![\n            llm::client::api::Message::system(&symbol_classification_prompt(&chunks_string)),\n            llm::client::api::Message::user(query),\n        ];\n\n        let response = match self\n            .llm_gateway\n            .clone()\n            .model(\"gpt-4-0613\")\n            .temperature(0.0)\n            .max_tokens(5)\n            .chat(&messages, None)\n            .await\n        {\n            Ok(response) => response,\n            Err(e) => {\n                warn!(\n                    \"Symbol classifier llm call failed, picking the first symbol: {}\",\n                    e\n                );\n                \"0\".into()\n            }\n        };\n\n        let selected_symbol = match response.as_str().parse::<i32>() {\n            Ok(symbol) => symbol,\n            Err(e) => {\n                warn!(\"Parsing to integer failed, picking the first symbol: {}\", e);\n                0\n            }\n        };\n\n        // finding symbol metadata\n        match symbols\n            .into_iter()\n            .flat_map(|(_, symbol_with_alias)| symbol_with_alias)\n            .find(|(alias, _)| *alias == selected_symbol)\n        {\n            Some((_alias, symbol_metadata)) => Ok(Symbol {\n                name: symbol_metadata.name,\n                related_symbols: {\n                    let document = self\n                        .app\n                        .indexes\n                        .file\n                        .by_path(\n                            &symbol_metadata.repo_path.repo,\n                            &symbol_metadata.repo_path.path,\n                            None,\n                        )\n                        .await?\n                        .context(\"Document is None\")?;\n\n                    let all_docs = {\n                        let associated_langs =\n                            match document.lang.as_deref().map(TSLanguage::from_id) {\n                                Some(Language::Supported(config)) => config.language_ids,\n                                _ => &[],\n                            };\n                        self.app\n                            .indexes\n                            .file\n                            .by_repo(\n                                &symbol_metadata.repo_path.repo,\n                                associated_langs.iter(),\n                                None,\n                            )\n                            .await\n                    };\n\n                    get_token_info(\n                        symbol_metadata.token_info_request,\n                        &symbol_metadata.repo_path.repo,\n                        self.app.indexes.clone(),\n                        &document,\n                        &all_docs,\n                        Some(0),\n                        Some(NUMBER_CHUNK_LINES),\n                    )\n                    .await?\n                    .into_iter()\n                    .filter(|file_symbol| {\n                        file_symbol.file != symbol_metadata.repo_path.path\n                            || file_symbol.repo != symbol_metadata.repo_path.repo\n                    })\n                    .collect::<Vec<_>>()\n                },\n            }),\n            _ => Err(SymbolError::OutOfBounds),\n        }\n    }\n\n    pub async fn get_related_chunks(&mut self, chunks: Vec<CodeChunk>) -> Result<Vec<CodeChunk>> {\n        const MAX_CHUNKS: usize = 3;\n\n        // get symbols with ref/defs for each chunk\n        let chunks_with_symbols = futures::future::join_all(\n            chunks\n                .iter()\n                .filter(|c| !c.is_empty())\n                .map(|c| self.extract_hoverable_symbols(c.clone())), // TODO: Log failure\n        )\n        .await\n        .into_iter()\n        .filter_map(Result::ok)\n        .collect();\n\n        // get original user query\n        let user_query = self\n            .last_exchange()\n            .query\n            .target()\n            .context(\"Query has no target\")?;\n\n        // select one symbol\n        let selected_symbol = match self.filter_symbols(&user_query, chunks_with_symbols).await {\n            Ok(selected_symbol) => {\n                info!(\"Selected symbol: {}\", selected_symbol.name);\n                selected_symbol\n            }\n            Err(e) => {\n                info!(\"Returning no extra chunks: {}\", e);\n                return Ok(Vec::new());\n            }\n        };\n\n        // take 3 chunks, update path aliases, update enchange chunks\n        let extra_chunks = self\n            .expand_symbol_into_chunks(selected_symbol)\n            .await\n            .iter()\n            .take(MAX_CHUNKS)\n            .map(|c| {\n                let chunk = CodeChunk {\n                    alias: self.get_path_alias(&c.repo_path),\n                    ..c.clone()\n                };\n                self.conversation\n                    .exchanges\n                    .last_mut()\n                    .unwrap()\n                    .code_chunks\n                    .push(chunk.clone());\n                chunk\n            })\n            .collect::<Vec<_>>();\n\n        Ok(extra_chunks)\n    }\n}\n\npub struct HoverableSymbol {\n    pub name: String,\n    pub token_info_request: TokenInfoRequest,\n    pub repo_path: RepoPath,\n}\npub struct Symbol {\n    pub name: String,\n    pub related_symbols: Vec<FileSymbols>,\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum SymbolError {\n    #[error(\"No symbol retrieved in the provided chunks\")]\n    ListEmpty,\n    #[error(\"Selected symbol out of bounds\")]\n    OutOfBounds,\n    #[error(\"anyhow: {0:?}\")]\n    Anyhow(#[from] anyhow::Error),\n}\n"
  },
  {
    "path": "server/bleep/src/agent/tools/answer.rs",
    "content": "use std::{collections::HashMap, mem, ops::Range, pin::pin};\n\nuse anyhow::{anyhow, Context, Result};\nuse futures::StreamExt;\nuse tracing::{debug, info, instrument, trace};\n\nuse crate::{\n    agent::{\n        exchange::{CodeChunk, FocusedChunk, Update},\n        model, transcoder, Agent,\n    },\n    llm,\n};\n\nconst CHUNK_MERGE_DISTANCE: usize = 20;\n\nimpl Agent {\n    #[instrument(skip(self))]\n    pub async fn answer(&mut self, aliases: &[usize]) -> Result<()> {\n        debug!(\"creating article response\");\n\n        if aliases.len() == 1 {\n            let repo_path = self\n                .paths()\n                .nth(aliases[0])\n                .context(\"invalid path alias passed\")?;\n\n            let doc = self\n                .get_file_content(repo_path)\n                .await?\n                .context(\"path did not exist\")?;\n\n            self.update(Update::Focus(FocusedChunk {\n                repo_path: repo_path.clone(),\n                start_line: 0,\n                end_line: doc.content.lines().count(),\n            }))\n            .await?;\n        }\n\n        let context = self.answer_context(aliases).await?;\n        let system_prompt = (self.answer_model.system_prompt)(&context);\n        let system_message = llm::client::api::Message::system(&system_prompt);\n        let history = {\n            let h = self.utter_history().collect::<Vec<_>>();\n            let system_headroom = tiktoken_rs::num_tokens_from_messages(\n                self.answer_model.tokenizer,\n                &[(&system_message).into()],\n            )?;\n            let headroom = self.answer_model.answer_headroom + system_headroom;\n            trim_utter_history(h, headroom, self.answer_model)?\n        };\n        let messages = Some(system_message)\n            .into_iter()\n            .chain(history.iter().cloned())\n            .collect::<Vec<_>>();\n\n        let mut stream = pin!(\n            self.llm_gateway\n                .clone()\n                .model(self.answer_model.model_name)\n                .frequency_penalty(\n                    if self.answer_model.model_name == \"gpt-3.5-turbo-finetuned\" {\n                        Some(0.2)\n                    } else {\n                        Some(0.0)\n                    }\n                )\n                .chat_stream(&messages, None)\n                .await?\n        );\n\n        let mut response = String::new();\n        while let Some(fragment) = stream.next().await {\n            let fragment = fragment?;\n            response += &fragment;\n\n            let article = transcoder::decode(&response);\n            self.update(Update::Article(article)).await?;\n        }\n\n        if let Some(article) = self.last_exchange().answer() {\n            trace!(%article, \"generated answer\");\n        }\n\n        self.update(Update::SetTimestamp).await?;\n\n        Ok(())\n    }\n\n    #[instrument(skip(self))]\n    async fn answer_context(&mut self, aliases: &[usize]) -> Result<String> {\n        let paths = self.paths().collect::<Vec<_>>();\n\n        let mut s = \"\".to_owned();\n\n        let mut aliases = aliases\n            .iter()\n            .copied()\n            .filter(|alias| *alias < paths.len())\n            .collect::<Vec<_>>();\n\n        aliases.sort();\n        aliases.dedup();\n\n        debug!(?paths, ?aliases, \"created filtered path alias list\");\n\n        s += \"##### REPOS #####\\n\";\n        for repo in self.relevant_repos() {\n            s += &format!(\"{repo}\\n\");\n        }\n\n        if !aliases.is_empty() {\n            s += \"\\n##### PATHS #####\\n\";\n\n            for alias in &aliases {\n                let path = &paths[*alias];\n                s += &format!(\"{path}\\n\");\n            }\n        }\n\n        let code_chunks = self.canonicalize_code_chunks(&aliases).await;\n\n        // Sometimes, there are just too many code chunks in the context, and deduplication still\n        // doesn't trim enough chunks. So, we enforce a hard limit here that stops adding tokens\n        // early if we reach a heuristic limit.\n        let bpe = tiktoken_rs::get_bpe_from_model(self.answer_model.tokenizer)?;\n        let mut remaining_prompt_tokens =\n            tiktoken_rs::get_completion_max_tokens(self.answer_model.tokenizer, &s)?;\n\n        // Select as many recent chunks as possible\n        let mut recent_chunks = Vec::new();\n        for chunk in code_chunks.iter().rev() {\n            let snippet =\n                chunk\n                    .snippet\n                    .lines()\n                    .enumerate()\n                    .fold(String::new(), |mut acc, (i, line)| {\n                        acc += &format!(\"{} {line}\\n\", i + chunk.start_line + 1, line = line);\n                        acc\n                    });\n\n            let formatted_snippet = format!(\"### {} ###\\n{snippet}\\n\\n\", chunk.repo_path);\n\n            let snippet_tokens = bpe.encode_ordinary(&formatted_snippet).len();\n\n            if snippet_tokens >= remaining_prompt_tokens - self.answer_model.prompt_headroom {\n                info!(\"breaking at {} tokens\", remaining_prompt_tokens);\n                break;\n            }\n\n            recent_chunks.push((chunk.clone(), formatted_snippet));\n\n            remaining_prompt_tokens -= snippet_tokens;\n            debug!(\"{}\", remaining_prompt_tokens);\n        }\n\n        // group recent chunks by path alias\n        let mut recent_chunks_by_alias: HashMap<_, _> =\n            recent_chunks\n                .into_iter()\n                .fold(HashMap::<_, Vec<_>>::new(), |mut map, item| {\n                    map.entry(item.0.alias).or_default().push(item);\n                    map\n                });\n\n        // write the header if we have atleast one chunk\n        if !recent_chunks_by_alias.values().all(Vec::is_empty) {\n            s += \"\\n##### CODE CHUNKS #####\\n\\n\";\n        }\n\n        // sort by alias, then sort by lines\n        let mut aliases = recent_chunks_by_alias.keys().copied().collect::<Vec<_>>();\n        aliases.sort();\n\n        for alias in aliases {\n            let chunks = recent_chunks_by_alias.get_mut(&alias).unwrap();\n            chunks.sort_by(|a, b| a.0.start_line.cmp(&b.0.start_line));\n            for (_, formatted_snippet) in chunks {\n                s += formatted_snippet;\n            }\n        }\n\n        Ok(s)\n    }\n\n    /// History of `user`, `assistant` messages. These are the messages that are shown to the user.\n    fn utter_history(&self) -> impl Iterator<Item = llm::client::api::Message> + '_ {\n        const ANSWER_MAX_HISTORY_SIZE: usize = 5;\n\n        self.conversation\n            .exchanges\n            .iter()\n            .rev()\n            .take(ANSWER_MAX_HISTORY_SIZE)\n            .rev()\n            .flat_map(|e| {\n                let query = e.query().map(|q| llm::client::api::Message::PlainText {\n                    role: \"user\".to_owned(),\n                    content: q,\n                });\n\n                let conclusion = e.answer().map(|answer| {\n                    let encoded = transcoder::encode_summarized(answer, \"gpt-4-0613\").unwrap();\n\n                    llm::client::api::Message::PlainText {\n                        role: \"assistant\".to_owned(),\n                        content: encoded,\n                    }\n                });\n\n                query.into_iter().chain(conclusion).collect::<Vec<_>>()\n            })\n    }\n\n    fn code_chunks(&self) -> impl Iterator<Item = CodeChunk> + '_ {\n        self.conversation\n            .exchanges\n            .iter()\n            .flat_map(|e| e.code_chunks.iter().cloned())\n    }\n\n    /// Merge overlapping and nearby code chunks\n    async fn canonicalize_code_chunks(&mut self, aliases: &[usize]) -> Vec<CodeChunk> {\n        debug!(?aliases, \"canonicalizing code chunks\");\n\n        /// The ratio of code tokens to context size.\n        ///\n        /// Making this closure to 1 means that more of the context is taken up by source code.\n        const CONTEXT_CODE_RATIO: f32 = 0.5;\n\n        let bpe = tiktoken_rs::get_bpe_from_model(self.answer_model.tokenizer).unwrap();\n        let context_size = tiktoken_rs::model::get_context_size(self.answer_model.tokenizer);\n        let max_tokens = (context_size as f32 * CONTEXT_CODE_RATIO) as usize;\n\n        // Note: The end line number here is *not* inclusive.\n        let mut spans_by_path = HashMap::<_, Vec<_>>::new();\n        for c in self.code_chunks().filter(|c| aliases.contains(&c.alias)) {\n            spans_by_path\n                .entry(c.repo_path.clone())\n                .or_default()\n                .push(c.start_line..c.end_line);\n        }\n\n        // If there are no relevant code chunks, but there is a focused chunk, we use that.\n        if spans_by_path.is_empty() {\n            if let Some(ref chunk) = self.last_exchange().focused_chunk {\n                spans_by_path\n                    .entry(chunk.repo_path.clone())\n                    .or_default()\n                    .push(chunk.start_line..chunk.end_line);\n            }\n        }\n\n        debug!(?spans_by_path, \"expanding spans\");\n\n        let self_ = &*self;\n        // Map of path -> line list\n        let lines_by_file = futures::stream::iter(&mut spans_by_path)\n            .then(|(path, spans)| async move {\n                spans.sort_by_key(|c| c.start);\n                let lines = self_\n                    .get_file_content(path)\n                    .await\n                    .unwrap()\n                    .unwrap_or_else(|| panic!(\"path did not exist in the index: {path}\"))\n                    .content\n                    .lines()\n                    .map(str::to_owned)\n                    .collect::<Vec<_>>();\n\n                (path.clone(), lines)\n            })\n            .collect::<HashMap<_, _>>()\n            .await;\n\n        // Total number of lines to try and expand by, per loop iteration.\n        const TOTAL_LINE_INC: usize = 100;\n\n        // We keep track of whether any spans were changed below, so that we know when to break\n        // out of this loop.\n        let mut changed = true;\n\n        while !spans_by_path.is_empty() && changed {\n            changed = false;\n\n            let tokens = spans_by_path\n                .iter()\n                .flat_map(|(path, spans)| spans.iter().map(move |s| (path, s)))\n                .map(|(path, span)| {\n                    let snippet = lines_by_file.get(path).unwrap()[span.clone()].join(\"\\n\");\n                    bpe.encode_ordinary(&snippet).len()\n                })\n                .sum::<usize>();\n\n            // First, we grow the spans if possible.\n            if tokens < max_tokens {\n                // NB: We divide TOTAL_LINE_INC by 2, because we expand in 2 directions.\n                let range_step = (TOTAL_LINE_INC / 2)\n                    / spans_by_path\n                        .values()\n                        .map(|spans| spans.len())\n                        .sum::<usize>()\n                        .max(1);\n\n                let range_step = range_step.max(1);\n\n                for (path, span) in spans_by_path\n                    .iter_mut()\n                    .flat_map(|(path, spans)| spans.iter_mut().map(move |s| (path, s)))\n                {\n                    let file_lines = lines_by_file.get(path).unwrap().len();\n\n                    let old_span = span.clone();\n\n                    span.start = span.start.saturating_sub(range_step);\n\n                    // Expand the end line forwards, capping at the total number of lines.\n                    span.end += range_step;\n                    span.end = span.end.min(file_lines);\n\n                    if *span != old_span {\n                        trace!(?path, \"growing span\");\n                        changed = true;\n                    }\n                }\n            }\n\n            // Next, we merge any overlapping spans.\n            for spans in spans_by_path.values_mut() {\n                *spans = mem::take(spans)\n                    .into_iter()\n                    .fold(Vec::new(), |mut a, next| {\n                        // There is some rightward drift here, which could be fixed once if-let\n                        // chains are stabilized.\n                        if let Some(prev) = a.last_mut() {\n                            if let Some(next) = merge_overlapping(prev, next) {\n                                a.push(next);\n                            } else {\n                                changed = true;\n                            }\n                        } else {\n                            a.push(next);\n                        }\n\n                        a\n                    });\n            }\n        }\n\n        debug!(?spans_by_path, \"expanded spans\");\n\n        let code_chunks = spans_by_path\n            .into_iter()\n            .flat_map(|(path, spans)| spans.into_iter().map(move |s| (path.clone(), s)))\n            .map(|(repo_path, span)| {\n                let snippet = lines_by_file.get(&repo_path).unwrap()[span.clone()].join(\"\\n\");\n\n                CodeChunk {\n                    alias: self.get_path_alias(&repo_path),\n                    start_line: span.start,\n                    end_line: span.end,\n                    snippet,\n                    repo_path,\n                    start_byte: None,\n                    end_byte: None,\n                }\n            })\n            .collect::<Vec<CodeChunk>>();\n\n        // Handle case where there is only one code chunk and it exceeds `max_tokens`.\n        // In this instance we trim the chunk to fit within the limit.\n        if code_chunks.len() == 1 {\n            let chunk = code_chunks.first().unwrap();\n            let trimmed_snippet = transcoder::limit_tokens(&chunk.snippet, bpe, max_tokens);\n            let num_trimmed_lines = trimmed_snippet.lines().count();\n            vec![CodeChunk {\n                alias: chunk.alias,\n                repo_path: chunk.repo_path.clone(),\n                snippet: trimmed_snippet.to_string(),\n                start_line: chunk.start_line,\n                end_line: (chunk.start_line + num_trimmed_lines).saturating_sub(1),\n                start_byte: chunk.start_byte,\n                end_byte: chunk.end_byte,\n            }]\n        } else {\n            code_chunks\n        }\n    }\n}\n\n// headroom refers to the amount of space reserved for the rest of the prompt\nfn trim_utter_history(\n    mut history: Vec<llm::client::api::Message>,\n    headroom: usize,\n    model: model::LLMModel,\n) -> Result<Vec<llm::client::api::Message>> {\n    let mut tiktoken_msgs: Vec<tiktoken_rs::ChatCompletionRequestMessage> =\n        history.iter().map(|m| m.into()).collect::<Vec<_>>();\n\n    // remove the earliest messages, one by one, until we can accommodate into prompt\n    while tiktoken_rs::get_chat_completion_max_tokens(model.tokenizer, &tiktoken_msgs)? < headroom {\n        if !tiktoken_msgs.is_empty() {\n            tiktoken_msgs.remove(0);\n            history.remove(0);\n        } else {\n            return Err(anyhow!(\"could not find message to trim\"));\n        }\n    }\n\n    Ok(history)\n}\n\n/// Merge line ranges if they overlap or are nearby.\n///\n/// This function assumes that the first parameter is a line range which starts *before* the line\n/// range given by the second parameter.\nfn merge_overlapping(a: &mut Range<usize>, b: Range<usize>) -> Option<Range<usize>> {\n    if a.end + CHUNK_MERGE_DISTANCE >= b.start {\n        // `b` might be contained in `a`, which allows us to discard it.\n        if a.end < b.end {\n            a.end = b.end;\n        }\n\n        None\n    } else {\n        Some(b)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_trimming_utter_history() {\n        let long_string = \"long string \".repeat(2000);\n        let history = vec![\n            llm::client::api::Message::user(\"bar\"),\n            llm::client::api::Message::assistant(\"baz\"),\n            llm::client::api::Message::user(&long_string),\n            llm::client::api::Message::assistant(\"quux\"),\n            llm::client::api::Message::user(\"fred\"),\n            llm::client::api::Message::assistant(\"thud\"),\n            llm::client::api::Message::user(&long_string),\n            llm::client::api::Message::user(\"corge\"),\n        ];\n\n        // the answer needs 8100 tokens of 8192, the utter history can admit just one message\n        assert_eq!(\n            trim_utter_history(history.clone(), 8100, model::GPT_4).unwrap(),\n            vec![llm::client::api::Message::user(\"corge\"),]\n        );\n\n        // the answer needs just 4000 tokens of 8192, the utter history can accomodate\n        // one long_string, but no more long_strings\n        assert_eq!(\n            trim_utter_history(history, 4000, model::GPT_4).unwrap(),\n            vec![\n                llm::client::api::Message::assistant(\"quux\"),\n                llm::client::api::Message::user(\"fred\"),\n                llm::client::api::Message::assistant(\"thud\"),\n                llm::client::api::Message::user(&long_string),\n                llm::client::api::Message::user(\"corge\"),\n            ]\n        );\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/agent/tools/code.rs",
    "content": "use anyhow::Result;\nuse tracing::{debug, info, trace};\n\nuse crate::{\n    agent::{\n        exchange::{CodeChunk, RepoPath, SearchStep, Update},\n        prompts, Agent, AgentSemanticSearchParams,\n    },\n    llm,\n    query::parser::Literal,\n    semantic::SemanticSearchParams,\n};\n\nimpl Agent {\n    pub async fn code_search(&mut self, query: &str) -> Result<String> {\n        const CODE_SEARCH_LIMIT: u64 = 10;\n        const MINIMUM_RESULTS: usize = CODE_SEARCH_LIMIT as usize / 2;\n\n        self.update(Update::StartStep(SearchStep::Code {\n            query: query.to_owned(),\n            response: String::new(),\n        }))\n        .await?;\n\n        let relevant_repos = self.relevant_repos();\n\n        let mut results = self\n            .semantic_search(AgentSemanticSearchParams {\n                query: Literal::from(&query.to_string()),\n                paths: vec![],\n                repos: relevant_repos.clone(),\n                semantic_params: SemanticSearchParams {\n                    limit: CODE_SEARCH_LIMIT,\n                    offset: 0,\n                    threshold: 0.3,\n                    exact_match: false,\n                },\n            })\n            .await?;\n\n        debug!(\"returned {} results\", results.len());\n\n        if results.len() < MINIMUM_RESULTS {\n            info!(\"too few results returned, running HyDE\");\n\n            let hyde_docs = self.hyde(query).await?;\n            if !hyde_docs.is_empty() {\n                let hyde_doc = hyde_docs.first().unwrap().into();\n                let hyde_results = self\n                    .semantic_search(AgentSemanticSearchParams {\n                        query: hyde_doc,\n                        paths: vec![],\n                        repos: relevant_repos,\n                        semantic_params: SemanticSearchParams {\n                            limit: CODE_SEARCH_LIMIT,\n                            offset: 0,\n                            threshold: 0.3,\n                            exact_match: false,\n                        },\n                    })\n                    .await?;\n\n                debug!(\"returned {} HyDE results\", results.len());\n                results.extend(hyde_results);\n            }\n        }\n\n        let mut chunks = results\n            .into_iter()\n            .map(|chunk| {\n                let repo = chunk.repo_ref;\n                let path = chunk.relative_path;\n\n                let repo_path = RepoPath {\n                    repo,\n                    path: path.clone(),\n                };\n\n                CodeChunk {\n                    alias: self.get_path_alias(&repo_path),\n                    snippet: chunk.text,\n                    start_line: chunk.start_line as usize,\n                    end_line: chunk.end_line as usize,\n                    start_byte: Some(chunk.start_byte as usize),\n                    end_byte: Some(chunk.end_byte as usize),\n                    repo_path,\n                }\n            })\n            .collect::<Vec<_>>();\n\n        chunks.sort_by(|a, b| a.alias.cmp(&b.alias).then(a.start_line.cmp(&b.start_line)));\n\n        for chunk in chunks.iter().filter(|c| !c.is_empty()) {\n            self.conversation\n                .exchanges\n                .last_mut()\n                .unwrap()\n                .code_chunks\n                .push(chunk.clone())\n        }\n\n        let extra_chunks = match self.get_related_chunks(chunks.clone()).await {\n            Ok(chunks) => chunks,\n            Err(e) => {\n                info!(?e, \"failed to get related chunks\");\n                vec![]\n            }\n        };\n\n        chunks.extend(extra_chunks);\n\n        let response = chunks\n            .clone()\n            .into_iter()\n            .filter(|c| !c.is_empty())\n            .map(|c| c.to_string())\n            .collect::<Vec<_>>()\n            .join(\"\\n\\n\");\n\n        self.update(Update::ReplaceStep(SearchStep::Code {\n            query: query.to_string(),\n            response: response.clone(),\n        }))\n        .await?;\n\n        Ok(response)\n    }\n\n    /// Hypothetical Document Embedding (HyDE): https://arxiv.org/abs/2212.10496\n    ///\n    /// This method generates synthetic documents based on the query. These are then\n    /// parsed and code is extracted. This has been shown to improve semantic search recall.\n    async fn hyde(&self, query: &str) -> Result<Vec<String>> {\n        let prompt = vec![llm::client::api::Message::system(\n            &prompts::hypothetical_document_prompt(query),\n        )];\n\n        trace!(?query, \"generating hyde docs\");\n\n        let response = self\n            .llm_gateway\n            .clone()\n            .model(\"gpt-3.5-turbo-0613\")\n            .temperature(0.0)\n            .chat(&prompt, None)\n            .await?;\n\n        trace!(\"parsing hyde response\");\n\n        let documents = prompts::try_parse_hypothetical_documents(&response);\n\n        for doc in documents.iter() {\n            info!(?doc, \"got hyde doc\");\n        }\n\n        Ok(documents)\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/agent/tools/path.rs",
    "content": "use std::collections::HashSet;\n\nuse anyhow::Result;\nuse tracing::instrument;\n\nuse crate::{\n    agent::{\n        exchange::{RepoPath, SearchStep, Update},\n        Agent, AgentSemanticSearchParams,\n    },\n    semantic::SemanticSearchParams,\n};\n\nimpl Agent {\n    #[instrument(skip(self))]\n    pub async fn path_search(&mut self, query: &String) -> Result<String> {\n        self.update(Update::StartStep(SearchStep::Path {\n            query: query.clone(),\n            response: String::new(),\n        }))\n        .await?;\n\n        // First, perform a lexical search for the path\n        let mut paths = self\n            .fuzzy_path_search(query)\n            .await\n            .map(|c| RepoPath {\n                repo: c.repo_ref,\n                path: c.relative_path,\n            })\n            .collect::<HashSet<_>>() // TODO: This shouldn't be necessary. Path search should return unique results.\n            .into_iter()\n            .collect::<Vec<_>>();\n\n        // If there are no lexical results, perform a semantic search.\n        if paths.is_empty() {\n            let semantic_paths = self\n                .semantic_search(AgentSemanticSearchParams {\n                    query: query.into(),\n                    paths: vec![],\n                    repos: self.relevant_repos(),\n                    semantic_params: SemanticSearchParams {\n                        limit: 30,\n                        offset: 0,\n                        threshold: 0.0,\n                        exact_match: false,\n                    },\n                })\n                .await?\n                .into_iter()\n                .map(|chunk| RepoPath {\n                    repo: chunk.repo_ref,\n                    path: chunk.relative_path,\n                })\n                .collect::<HashSet<_>>()\n                .into_iter()\n                .collect();\n\n            paths = semantic_paths;\n        }\n\n        let mut paths = paths\n            .iter()\n            .map(|repo_path| (self.get_path_alias(repo_path), repo_path.to_string()))\n            .collect::<Vec<_>>();\n        paths.sort_by(|a: &(usize, String), b| a.0.cmp(&b.0)); // Sort by alias\n\n        let response = paths\n            .iter()\n            .map(|(alias, path)| format!(\"{}: {}\", alias, path))\n            .collect::<Vec<_>>()\n            .join(\"\\n\");\n\n        self.update(Update::ReplaceStep(SearchStep::Path {\n            query: query.clone(),\n            response: response.clone(),\n        }))\n        .await?;\n\n        Ok(response)\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/agent/tools/proc.rs",
    "content": "use anyhow::{anyhow, Result};\nuse tracing::instrument;\n\nuse crate::{\n    agent::{\n        exchange::{CodeChunk, RepoPath, SearchStep, Update},\n        Agent, AgentSemanticSearchParams,\n    },\n    query::parser::Literal,\n    semantic::SemanticSearchParams,\n};\n\nimpl Agent {\n    #[instrument(skip(self))]\n    pub async fn process_files(&mut self, query: &str, aliases: &[usize]) -> Result<String> {\n        let paths = aliases\n            .iter()\n            .copied()\n            .map(|i| self.paths().nth(i).ok_or(i).map(Clone::clone))\n            .collect::<Result<Vec<_>, _>>()\n            .map_err(|i| anyhow!(\"invalid path alias {i}\"))?;\n\n        self.update(Update::StartStep(SearchStep::Proc {\n            query: query.to_string(),\n            paths: paths.clone(),\n            response: String::new(),\n        }))\n        .await?;\n\n        let repos = paths.iter().map(|p| p.repo.clone()).collect::<Vec<_>>();\n\n        let results = self\n            .semantic_search(AgentSemanticSearchParams {\n                query: Literal::from(&query.to_string()),\n                paths: paths.clone(),\n                repos,\n                semantic_params: SemanticSearchParams {\n                    limit: 10,\n                    offset: 0,\n                    threshold: 0.0,\n                    exact_match: true,\n                },\n            })\n            .await?;\n\n        let mut chunks = results\n            .into_iter()\n            .map(|chunk| {\n                let repo_path = RepoPath {\n                    repo: chunk.repo_ref.clone(),\n                    path: chunk.relative_path,\n                };\n\n                CodeChunk {\n                    alias: self.get_path_alias(&repo_path),\n                    snippet: chunk.text,\n                    start_line: chunk.start_line as usize,\n                    end_line: chunk.end_line as usize,\n                    start_byte: Some(chunk.start_byte as usize),\n                    end_byte: Some(chunk.end_byte as usize),\n                    repo_path,\n                }\n            })\n            .collect::<Vec<_>>();\n\n        chunks.sort_by(|a, b| a.alias.cmp(&b.alias).then(a.start_line.cmp(&b.start_line)));\n\n        for chunk in chunks.iter().filter(|c| !c.is_empty()) {\n            self.conversation\n                .exchanges\n                .last_mut()\n                .unwrap()\n                .code_chunks\n                .push(chunk.clone())\n        }\n\n        let extra_chunks = self.get_related_chunks(chunks.clone()).await?;\n\n        chunks.extend(extra_chunks);\n\n        let response = chunks\n            .iter()\n            .filter(|c| !c.is_empty())\n            .map(|c| c.to_string())\n            .collect::<Vec<_>>()\n            .join(\"\\n\\n\");\n\n        self.update(Update::ReplaceStep(SearchStep::Proc {\n            query: query.to_string(),\n            paths,\n            response: response.clone(),\n        }))\n        .await?;\n\n        Ok(response)\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/agent/transcoder.rs",
    "content": "//! Transcoder for articles generated by the LLM.\n//!\n//! The LLM generates articles with a special format, for example it uses XML to denote code blocks\n//! instead of regular Markdown code blocks. This module both decodes this format into markdown\n//! components, and encodes them back.\n\nuse std::{borrow::Cow, collections::HashMap, mem};\n\nuse anyhow::{Context, Result};\nuse comrak::nodes::{NodeHtmlBlock, NodeValue};\nuse lazy_regex::regex;\nuse regex::Regex;\nuse serde::Deserialize;\nuse tiktoken_rs::CoreBPE;\n\n/// Decode an article.\n///\n/// If successful, this returns `body`.\npub fn decode(llm_message: &str) -> String {\n    let sanitized = sanitize(llm_message);\n    let markdown = xml_for_each(&sanitized, |code| xml_to_markdown(code).ok());\n\n    // The `comrak` crate has a very unusual API which makes this logic difficult to follow. It\n    // favours arena allocation instead of a tree-based AST, and requires `Write`rs to regenerate\n    // markdown output.\n    //\n    // There are quirks to the parsing logic, comments have been added for clarity.\n\n    let arena = comrak::Arena::new();\n    let mut options = comrak::ComrakOptions::default();\n    options.extension.footnotes = true;\n\n    // We don't have an easy built-in way to generate a string with `comrak`, so we encapsulate\n    // that logic here.\n    let comrak_to_string = |node| {\n        let mut out = Vec::<u8>::new();\n        comrak::format_commonmark(node, &options, &mut out).unwrap();\n        String::from_utf8_lossy(&out)\n            .trim()\n            .replace(\"\\n\\n<!-- end list -->\", \"\")\n    };\n\n    let root = comrak::parse_document(&arena, &markdown, &options);\n\n    for block in root.children() {\n        offset_embedded_link_ranges(block, -1);\n    }\n\n    comrak_to_string(root)\n}\n\n/// Offset line ranges in embedded links by a specific value.\n///\n/// This can be used to offset lines by one, either positively or negatively.\nfn offset_embedded_link_ranges<'a>(element: &'a comrak::nodes::AstNode<'a>, offset: i32) -> bool {\n    // We have to convert links to use 0-based indexes as the model works with 1-based indexes.\n    //\n    // TODO: We can update the model so that it works with 0-based indexes, and remove this\n    // altogether.\n\n    match &mut element.data.borrow_mut().value {\n        NodeValue::Link(link) => {\n            let url = mem::take(&mut link.url);\n            link.url = url\n                .split_once('#')\n                .and_then(|(url, anchor)| {\n                    if let Some((start, end)) = anchor.split_once('-') {\n                        if !start.starts_with('L') || !end.starts_with('L') {\n                            return None;\n                        }\n\n                        let start = start.get(1..)?.parse::<usize>().ok()?;\n                        let end = end.get(1..)?.parse::<usize>().ok()?;\n\n                        Some(format!(\n                            \"{url}#L{}-L{}\",\n                            start as i32 + offset,\n                            end as i32 + offset,\n                        ))\n                    } else {\n                        if !anchor.starts_with('L') {\n                            return None;\n                        }\n\n                        let line = anchor.get(1..)?.parse::<usize>().ok()?;\n                        Some(format!(\"{url}#L{}\", line as i32 + offset))\n                    }\n                })\n                .unwrap_or(url);\n\n            true\n        }\n\n        // False positive lint, we want the side effects:\n        // https://github.com/rust-lang/rust-clippy/issues/3351\n        #[allow(clippy::unnecessary_fold)]\n        _ => element\n            .children()\n            .map(|child| offset_embedded_link_ranges(child, offset))\n            .fold(false, |a, e| a || e),\n    }\n}\n\npub fn encode(markdown: &str) -> String {\n    let arena = comrak::Arena::new();\n    let mut options = comrak::ComrakOptions::default();\n    options.extension.footnotes = true;\n\n    let root = comrak::parse_document(&arena, markdown, &options);\n\n    for block in root.children() {\n        offset_embedded_link_ranges(block, 1);\n\n        let (info, literal) = match &mut block.data.borrow_mut().value {\n            NodeValue::CodeBlock(block) => (block.info.clone(), block.literal.clone()),\n            _ => continue,\n        };\n\n        let attributes = info\n            .split(',')\n            .filter_map(|param| {\n                let mut iter = param.trim().split(':');\n\n                let key = iter.next()?;\n                let value = iter.next()?;\n\n                Some((key.to_owned(), value.to_owned()))\n            })\n            .collect::<HashMap<String, String>>();\n\n        let xml = attributes.get(\"type\").and_then(|ty| match ty.as_str() {\n            \"Quoted\" => {\n                let path = attributes.get(\"path\")?;\n                let lang = attributes.get(\"lang\")?;\n                let mut lines = attributes.get(\"lines\")?.split('-');\n\n                let start_line = lines.next()?.parse::<usize>().ok()? + 1;\n                let end_line = lines.next()?.parse::<usize>().ok()? + 1;\n\n                Some(format!(\n                    \"<QuotedCode>\\n\\\n                    <Code>\\n\\\n                    {literal}\\\n                    </Code>\\n\\\n                    <Language>{lang}</Language>\\n\\\n                    <Path>{path}</Path>\\n\\\n                    <StartLine>{start_line}</StartLine>\\n\\\n                    <EndLine>{end_line}</EndLine>\\n\\\n                    </QuotedCode>\"\n                ))\n            }\n\n            \"Generated\" => {\n                let lang = attributes.get(\"lang\")?;\n\n                Some(format!(\n                    \"<GeneratedCode>\\n\\\n                    <Code>\\n\\\n                    {literal}\\\n                    </Code>\\n\\\n                    <Language>{lang}</Language>\\n\\\n                    </GeneratedCode>\"\n                ))\n            }\n\n            _ => None,\n        });\n\n        if let Some(xml) = xml {\n            block.data.borrow_mut().value = NodeValue::HtmlBlock(NodeHtmlBlock {\n                literal: xml,\n                // The block type here is not used.\n                block_type: 0,\n            });\n        }\n    }\n\n    let mut out = Vec::<u8>::new();\n    comrak::format_commonmark(root, &options, &mut out).unwrap();\n    String::from_utf8_lossy(&out).trim().to_owned()\n}\n\npub fn encode_summarized(markdown: &str, model: &str) -> Result<String> {\n    let article = xml_for_each(&encode(markdown), |xml| try_trim_code_xml(xml).ok());\n    let bpe = tiktoken_rs::get_bpe_from_model(model)?;\n    Ok(limit_tokens(&article, bpe, 500).to_owned())\n}\n\nfn sanitize(article: &str) -> String {\n    let sanitized = xml_for_each(article, |code| Some(fixup_xml_code(code).into_owned()));\n    regex!(\"<!--.*?-->\").replace_all(&sanitized, \"\").into()\n}\n\nfn fixup_xml_code(xml: &str) -> Cow<str> {\n    if !xml.trim().starts_with('<') {\n        return Cow::Borrowed(xml);\n    }\n\n    if let Some(match_) = regex!(\"<(Generated|Quoted)Code>\\\\s*<Code>(.*)\"sm)\n        .captures(xml)\n        .and_then(|cap| cap.get(2))\n    {\n        let mut buf = String::new();\n\n        buf += &xml[..match_.start()];\n\n        // First, we clean up incorrectly escaped symbols in the code block.\n        {\n            let s = &xml[match_.range()];\n\n            let code_len = regex!(\"</Code>\")\n                .find(s)\n                .map(|m| m.start())\n                .unwrap_or(s.len());\n            let (s, tail) = s.split_at(code_len);\n\n            // The `regex` crate does not support negative lookahead, so we cannot write a regex\n            // like `&(?!amp;)`. So, we just perform naive substitutions to first obtain an\n            // unescaped copy of the string, and then re-escape it in order to fix up the result.\n            //\n            // This matters if the input string is something like `&amp;foo < &bar&lt;i32&gt;()`:\n            //\n            // - First, we convert that to `&foo < &bar<i32>()`\n            // - Second, we convert it to `&amp;foo < &amp;bar&lt;i32&gt;`, our desired result.\n\n            let s = regex!(\"&lt;\"m).replace_all(s, \"<\");\n            let s = regex!(\"&gt;\"m).replace_all(&s, \">\");\n            let s = regex!(\"&amp;\"m).replace_all(&s, \"&\");\n\n            let s = regex!(\"&\"m).replace_all(&s, \"&amp;\");\n            let s = regex!(\"<\"m).replace_all(&s, \"&lt;\");\n            let s = regex!(\">\"m).replace_all(&s, \"&gt;\");\n\n            buf += &s;\n            buf += tail;\n        }\n\n        {\n            // Next, we clean up the tags.\n            //\n            // Because the LLM is generating XML output token-by-token, we may end up in a\n            // situation where closing tags are missing, or tags are half written. To fix this,\n            // first we remove all half-complete opening or closing tags (e.g. `<foo` or `</`).\n            // Then, we add missing closing tags, *in the order we expect them to appear in the\n            // final XML output.* This is not perfect, but it should work well enough to allow us\n            // to parse the XML.\n\n            buf = regex!(\"<[^>]*$\").replace_all(&buf, \"\").into_owned();\n\n            for tag in [\n                \"Code\",\n                \"Language\",\n                \"Path\",\n                \"StartLine\",\n                \"EndLine\",\n                \"QuotedCode\",\n                \"GeneratedCode\",\n            ] {\n                let opening_tag = format!(\"<{tag}>\");\n                let closing_tag = format!(\"</{tag}>\");\n\n                if buf.contains(&opening_tag) && !buf.contains(&closing_tag) {\n                    buf += &closing_tag;\n                }\n            }\n        }\n\n        Cow::Owned(buf)\n    } else {\n        Cow::Borrowed(xml)\n    }\n}\n\nfn xml_to_markdown(xml: &str) -> Result<String> {\n    let code_chunk =\n        quick_xml::de::from_str::<CodeChunk>(xml).context(\"failed to deserialize code chunk\")?;\n\n    Ok(code_chunk.to_markdown())\n}\n\n/// An XML code chunk that is generated by the LLM.\n#[derive(serde::Deserialize, Debug)]\nenum CodeChunk {\n    QuotedCode {\n        #[serde(default, rename = \"Code\")]\n        code: String,\n        #[serde(default, rename = \"Language\")]\n        language: String,\n        #[serde(default, rename = \"Path\")]\n        path: String,\n        #[serde(default, rename = \"StartLine\", deserialize_with = \"deserialize_lineno\")]\n        start_line: Option<u32>,\n        #[serde(default, rename = \"EndLine\", deserialize_with = \"deserialize_lineno\")]\n        end_line: Option<u32>,\n    },\n    GeneratedCode {\n        #[serde(default, rename = \"Code\")]\n        code: String,\n        #[serde(default, rename = \"Language\")]\n        language: String,\n    },\n}\n\nfn deserialize_lineno<'a, D: serde::Deserializer<'a>>(de: D) -> Result<Option<u32>, D::Error> {\n    let opt = Option::deserialize(de)?;\n    let opt = opt.and_then(|s: String| {\n        if s.is_empty() {\n            Some(0)\n        } else {\n            s.parse().ok()\n        }\n    });\n\n    Ok(opt)\n}\n\nimpl CodeChunk {\n    fn to_markdown(&self) -> String {\n        let (ty, code, lang, path, start, end) = match self {\n            CodeChunk::QuotedCode {\n                code,\n                language,\n                path,\n                start_line,\n                end_line,\n            } => (\n                \"Quoted\",\n                code,\n                language,\n                path.as_str(),\n                start_line.map(|n| n.saturating_sub(1)),\n                end_line.map(|n| n.saturating_sub(1)),\n            ),\n            CodeChunk::GeneratedCode { code, language } => {\n                (\"Generated\", code, language, \"\", None, None)\n            }\n        };\n\n        // If we find ticks in the code content, we make sure we have at least N+1 ticks for our\n        // braces, to ensure a proper block is formed.\n        let ticks = if let Some(captures) = regex!(\"^(````*)\"m).captures(code) {\n            let num_ticks = captures.get(1).unwrap().len();\n            \"`\".repeat(num_ticks + 1)\n        } else {\n            \"```\".to_owned()\n        };\n\n        format!(\n            \"{ticks}type:{ty},lang:{lang},path:{path},lines:{}-{}\\n{code}\\n{ticks}\",\n            start.unwrap_or(0),\n            end.unwrap_or(0)\n        )\n    }\n}\n\n/// Modify every XML section of a markdown document.\n///\n/// The provided closure returns an option, which returns `Some(..)` with a replacement for the\n/// input string, or `None` if the input string does not need to be replaced.\n///\n/// This function operates heuristically, in order to allow malformed XML and XML that contains\n/// multiple serial newlines. This means we accept invalid markdown, and are more forgiving with\n/// the input, at the expense of creating parsing edge cases that can cause trouble due to input\n/// ambiguity.\n///\n/// One such case is this:\n///\n/// ```xml\n/// This is a sample markdown document. **Hello** world.\n///\n/// <Code>\n///     println!(\"code ends with </Code>\");\n/// </Code>\n/// ```\n///\n/// The above markdown document contains an XML block enclosed in `<Code>...</Code>`, but it is\n/// not valid as the code snippet contains unescaped characters. Of note, the `println!` call\n/// contains literal `<` and `>` characters, which in valid XML *must* be escaped as `&lt;` and\n/// `&gt;`, respectively. Because of this, the xml block will be incorrectly parsed to terminate\n/// halfway through the string literal provided in the code sample.\n///\n/// In general, there is no great way around this. We tolerate *most* ambiguity, but this edge case\n/// remains as a consequence of ambiguous input.\n///\n/// For further context, we must accept ambiguous unescaped (invalid) input, as the LLM may\n/// generate such documents.\nfn xml_for_each(article: &str, f: impl Fn(&str) -> Option<String>) -> String {\n    let mut out = String::new();\n    let mut rest = article;\n\n    while let Some(captures) = regex!(r\"\\n(```.*\\n)?\\s*(<(\\w+)>)\").captures(rest) {\n        let md_block_start = captures.get(1);\n        let tag = captures.get(2).unwrap();\n        let name = &rest[captures.get(3).unwrap().range()];\n\n        let start = md_block_start.map(|c| c.start()).unwrap_or(tag.start());\n\n        out += &rest[..start];\n\n        let xml = if let Some(captures) = Regex::new(&format!(r\"(</{name}>)(?:\\n?```)?\"))\n            .unwrap()\n            .captures(rest)\n        {\n            let m = captures.get(0).unwrap();\n            let end_tag = captures.get(1).unwrap();\n\n            let xml = &rest[tag.start()..end_tag.end()];\n            rest = &rest[m.end()..];\n\n            xml\n        } else {\n            let xml = &rest[tag.start()..];\n            rest = \"\";\n            xml\n        };\n\n        if let Some(update) = f(xml) {\n            out += &update;\n        } else {\n            out += xml;\n        }\n    }\n\n    out += rest;\n    out\n}\n\nfn try_trim_code_xml(xml: &str) -> Result<String> {\n    // We just remove code chunks completely.\n\n    let xml = fixup_xml_code(xml);\n    let _code_chunk: CodeChunk =\n        quick_xml::de::from_str(&xml).context(\"couldn't parse as XML code block\")?;\n    Ok(String::new())\n}\n\npub fn limit_tokens(text: &str, bpe: CoreBPE, max_tokens: usize) -> &str {\n    let mut tokens = bpe.encode_ordinary(text);\n    tokens.truncate(max_tokens);\n\n    while !tokens.is_empty() {\n        if let Ok(s) = bpe.decode(tokens.clone()) {\n            return &text[..s.len()];\n        }\n\n        let _ = tokens.pop();\n    }\n\n    \"\"\n}\n\n#[cfg(test)]\nmod tests {\n    use pretty_assertions::assert_eq;\n\n    use super::*;\n\n    #[test]\n    fn test_trim_code() {\n        let input = \"Sample Markdown test.\n\n<QuotedCode>\n<Code>\nfn foo() -> i32 {\n    42\n}\n</Code>\n<Language>Rust</Language>\n<Path>src/main.rs</Path>\n<StartLine>10</StartLine>\n<EndLine>12</EndLine>\n</QuotedCode>\n\n<GeneratedCode>\n<Code>\nfn foo() -> i32 {\n    42\n}\n</Code>\n<Language>Rust</Language>\n</GeneratedCode>\n\ntest\ntest\ntest\";\n\n        let expected = \"Sample Markdown test.\n\n\n\n\n\ntest\ntest\ntest\";\n\n        let out = xml_for_each(input, |code| try_trim_code_xml(code).ok());\n\n        assert_eq!(expected, out);\n    }\n\n    #[test]\n    fn test_trim_code_with_regular_xml() {\n        let input = \"Sample Markdown test.\n\n<p>hello</p>\n\n<QuotedCode>\n<Code>\nfn foo() -> i32 {\n    42\n}\n</Code>\n<Language>Rust</Language>\n<Path>src/main.rs</Path>\n<StartLine>10</StartLine>\n<EndLine>12</EndLine>\n</QuotedCode>\n\n<GeneratedCode>\n<Code>\nfn foo() -> i32 {\n    42\n}\n</Code>\n<Language>Rust</Language>\n</GeneratedCode>\n\ntest\ntest\ntest\";\n\n        let expected = \"Sample Markdown test.\n\n<p>hello</p>\n\n\n\n\n\ntest\ntest\ntest\";\n\n        let out = xml_for_each(input, |code| try_trim_code_xml(code).ok());\n\n        assert_eq!(expected, out);\n    }\n\n    #[test]\n    fn test_fixup_quoted_code() {\n        let input = \"<QuotedCode>\n<Code>\nfn foo<T>(t: T) -> bool {\n    &amp;foo < &bar&lt;i32&gt;(t)\n}\n</Code>\n<Language>Rust</Language>\n<Path>src/main.rs</Path>\n<StartLine>10</StartLine>\n<EndLine>12</EndLine>\n</QuotedCode>\";\n\n        let expected = \"<QuotedCode>\n<Code>\nfn foo&lt;T&gt;(t: T) -&gt; bool {\n    &amp;foo &lt; &amp;bar&lt;i32&gt;(t)\n}\n</Code>\n<Language>Rust</Language>\n<Path>src/main.rs</Path>\n<StartLine>10</StartLine>\n<EndLine>12</EndLine>\n</QuotedCode>\";\n\n        assert_eq!(expected, &fixup_xml_code(input));\n    }\n\n    #[test]\n    fn test_fixup_generated_code() {\n        let input = \"<GeneratedCode>\n<Code>\nfn foo<T>(t: T) -> bool {\n    &amp;foo < &bar&lt;i32&gt;(t)\n}\n</Code>\n<Language>Rust</Language>\n</GeneratedCode>\";\n\n        let expected = \"<GeneratedCode>\n<Code>\nfn foo&lt;T&gt;(t: T) -&gt; bool {\n    &amp;foo &lt; &amp;bar&lt;i32&gt;(t)\n}\n</Code>\n<Language>Rust</Language>\n</GeneratedCode>\";\n\n        assert_eq!(expected, &fixup_xml_code(input));\n    }\n\n    #[test]\n    fn test_sanitize_article() {\n        let input = \"First, we test some *generated code* below:\n\n<GeneratedCode>\n<Code>\nfn foo<T>(t: T) -> bool {\n    &amp;foo < &bar&lt;i32&gt;(t)\n}\n</Code>\n<Language>Rust</Language>\n</GeneratedCode>\n\nThen, we test some quoted code:\n\n<QuotedCode>\n<Code>\nfn foo<T>(t: T) -> bool {\n    &amp;foo < &bar&lt;i32&gt;(t)\n}\n</Code>\n<Language>Rust</Language>\n<Path>src/main.rs</Path>\n<StartLine>10</StartLine>\n<EndLine>12</EndLine>\n</QuotedCode>\n\n# Foo\n\nThese should result in sanitized XML output, while maintaining the rest of the markdown article.\n\";\n\n        let expected = \"First, we test some *generated code* below:\n\n<GeneratedCode>\n<Code>\nfn foo&lt;T&gt;(t: T) -&gt; bool {\n    &amp;foo &lt; &amp;bar&lt;i32&gt;(t)\n}\n</Code>\n<Language>Rust</Language>\n</GeneratedCode>\n\nThen, we test some quoted code:\n\n<QuotedCode>\n<Code>\nfn foo&lt;T&gt;(t: T) -&gt; bool {\n    &amp;foo &lt; &amp;bar&lt;i32&gt;(t)\n}\n</Code>\n<Language>Rust</Language>\n<Path>src/main.rs</Path>\n<StartLine>10</StartLine>\n<EndLine>12</EndLine>\n</QuotedCode>\n\n# Foo\n\nThese should result in sanitized XML output, while maintaining the rest of the markdown article.\n\";\n\n        assert_eq!(expected, sanitize(input));\n    }\n\n    #[test]\n    fn test_sanitize_article_partial_generation() {\n        let input = \"First, we test some **partially** *generated code* below:\n\n<GeneratedCode>\n<Code>\nfn foo<T>(t: T) -> bool {\n    &amp;foo <\n\";\n\n        let expected = \"First, we test some **partially** *generated code* below:\n\n<GeneratedCode>\n<Code>\nfn foo&lt;T&gt;(t: T) -&gt; bool {\n    &amp;foo &lt;\n</Code></GeneratedCode>\";\n\n        assert_eq!(expected, sanitize(input));\n    }\n\n    #[test]\n    fn test_decode_2() {\n        let input = \"First, we test some *generated code* below:\n\n<GeneratedCode>\n<Code>\nfn foo&lt;T&gt;(t: T) -&gt; bool {\n    &amp;foo &lt; &amp;bar&lt;i32&gt;(t)\n}\n</Code>\n<Language>Rust</Language>\n</GeneratedCode>\n\nThen, we test some quoted code:\n\n<QuotedCode>\n<Code>\nfn foo&lt;T&gt;(t: T) -&gt; bool {\n    &amp;foo &lt; &amp;bar&lt;i32&gt;(t)\n}\n</Code>\n<Language>Rust</Language>\n<Path>src/main.rs</Path>\n<StartLine>10</StartLine>\n<EndLine>12</EndLine>\n</QuotedCode>\n\n# Foo\n\nThese should result in base64-encoded XML output, while maintaining the rest of the markdown article.\";\n\n        let expected = \"First, we test some *generated code* below:\n\n``` type:Generated,lang:Rust,path:,lines:0-0\nfn foo<T>(t: T) -> bool {\n    &foo < &bar<i32>(t)\n}\n```\n\nThen, we test some quoted code:\n\n``` type:Quoted,lang:Rust,path:src/main.rs,lines:9-11\nfn foo<T>(t: T) -> bool {\n    &foo < &bar<i32>(t)\n}\n```\n\n# Foo\n\nThese should result in base64-encoded XML output, while maintaining the rest of the markdown article.\";\n\n        let body = decode(input);\n        assert_eq!(expected, body);\n    }\n\n    #[test]\n    fn test_decode_partial_xml() {\n        let input = \"The `Compiler` struct in [`server/bleep/src/query/compiler.rs`](server/bleep/src/query/compiler.rs) is used to compile a list of queries into a single Tantivy query that matches any of them. Here is an example of its usage:\n\n<QuotedCode>\n<Code>\nlet mut compiler = Compiler::new();\ncompiler.literal(schema.name, |q| q.repo.clone());\nlet compiled_query = compiler.compile(queries, tantivy_index);\n</Code>\n<Language>Rust</Language>\n<Path>server/bleep/s\n\";\n\n        let expected = \"The `Compiler` struct in [`server/bleep/src/query/compiler.rs`](server/bleep/src/query/compiler.rs) is used to compile a list of queries into a single Tantivy query that matches any of them. Here is an example of its usage:\n\n``` type:Quoted,lang:Rust,path:server/bleep/s,lines:0-0\nlet mut compiler = Compiler::new();\ncompiler.literal(schema.name, |q| q.repo.clone());\nlet compiled_query = compiler.compile(queries, tantivy_index);\n```\";\n\n        let body = decode(input);\n        assert_eq!(expected, body);\n    }\n\n    #[test]\n    fn test_decode_partial_xml_no_path() {\n        let input = \"## Example of Using the Query Compiler\n\nThe `Compiler` struct in [`server/bleep/src/query/compiler.rs`](server/bleep/src/query/compiler.rs) is used to compile a list of queries into a single Tantivy query that matches any of them. Here is an example of its usage:\n\n<QuotedCode>\n<Code>\nlet mut compiler = Compiler::new();\ncompiler.literal(schema.name, |q| q.repo.clone());\nlet compiled_query = compiler.compile(queries, tantivy_index);\n</Code>\n<Language>Rust</Language>\n</QuotedCode>\n\";\n\n        let expected = \"## Example of Using the Query Compiler\n\nThe `Compiler` struct in [`server/bleep/src/query/compiler.rs`](server/bleep/src/query/compiler.rs) is used to compile a list of queries into a single Tantivy query that matches any of them. Here is an example of its usage:\n\n``` type:Quoted,lang:Rust,path:,lines:0-0\nlet mut compiler = Compiler::new();\ncompiler.literal(schema.name, |q| q.repo.clone());\nlet compiled_query = compiler.compile(queries, tantivy_index);\n```\";\n\n        let body = decode(input);\n        assert_eq!(expected, body);\n    }\n\n    #[test]\n    fn test_sanitize_multi_blocks() {\n        let input = \"## Example of Using the Query Compiler\n\nThe `Compiler` struct in [`server/bleep/src/query/compiler.rs`](server/bleep/src/query/compiler.rs) is used to compile a list of queries into a single Tantivy query that matches any of them. Here is an example of its usage:\n\n<QuotedCode>\n<Code>\nlet mut compiler = Compiler::new();\n\ncompiler.literal(schema.name, |q| q.repo.clone());\nlet compiled_query =\n\";\n\n        let expected = \"## Example of Using the Query Compiler\n\nThe `Compiler` struct in [`server/bleep/src/query/compiler.rs`](server/bleep/src/query/compiler.rs) is used to compile a list of queries into a single Tantivy query that matches any of them. Here is an example of its usage:\n\n``` type:Quoted,lang:,path:,lines:0-0\nlet mut compiler = Compiler::new();\n\ncompiler.literal(schema.name, |q| q.repo.clone());\nlet compiled_query =\n```\";\n\n        let body = decode(input);\n        assert_eq!(expected, body);\n    }\n\n    #[test]\n    fn test_decode_partial_xml_empty_line_number() {\n        let input = \"## Example of Using the Query Compiler\n\nThe `Compiler` struct in [`server/bleep/src/query/compiler.rs`](server/bleep/src/query/compiler.rs) is used to compile a list of queries into a single Tantivy query that matches any of them. Here is an example of its usage:\n\n<QuotedCode>\n<Code>\nlet mut compiler = Compiler::new();\ncompiler.literal(schema.name, |q| q.repo.clone());\nlet compiled_query = compiler.compile(queries, tantivy_index);\n</Code>\n<Language>Rust</Language>\n<StartLine>\";\n\n        let expected = \"## Example of Using the Query Compiler\n\nThe `Compiler` struct in [`server/bleep/src/query/compiler.rs`](server/bleep/src/query/compiler.rs) is used to compile a list of queries into a single Tantivy query that matches any of them. Here is an example of its usage:\n\n``` type:Quoted,lang:Rust,path:,lines:0-0\nlet mut compiler = Compiler::new();\ncompiler.literal(schema.name, |q| q.repo.clone());\nlet compiled_query = compiler.compile(queries, tantivy_index);\n```\";\n\n        let body = decode(input);\n        assert_eq!(expected, body);\n    }\n\n    #[test]\n    fn test_encode() {\n        let input = \"Foo\n\n``` type:Quoted,lang:Rust,path:src/main.rs,lines:0-2\nfn main() {\n    println!(\\\"hello world\\\");\n}\n```\n\nBar.\n\n``` type:Generated,lang:Rust,path:,lines:0-0\nfn main() {\n    println!(\\\"hello world\\\");\n}\n```\n\n\";\n\n        let expected = \"Foo\n\n<QuotedCode>\n<Code>\nfn main() {\n    println!(\\\"hello world\\\");\n}\n</Code>\n<Language>Rust</Language>\n<Path>src/main.rs</Path>\n<StartLine>1</StartLine>\n<EndLine>3</EndLine>\n</QuotedCode>\n\nBar.\n\n<GeneratedCode>\n<Code>\nfn main() {\n    println!(\\\"hello world\\\");\n}\n</Code>\n<Language>Rust</Language>\n</GeneratedCode>\";\n\n        let encoded = encode(input);\n\n        assert_eq!(expected, encoded);\n    }\n\n    #[test]\n    fn test_encode_summarized() {\n        let input = \"Foo\n\n``` type:Quoted,lang:Rust,path:src/main.rs,lines:0-2\nfn main() {\n    println!(\\\"hello world\\\");\n}\n```\n\nBar.\n\n``` type:Generated,lang:Rust,path:,lines:0-0\nfn main() {\n    println!(\\\"hello world\\\");\n}\n```\n\n\";\n\n        let expected = \"Foo\n\n\n\nBar.\n\n\";\n\n        let encoded = encode_summarized(input, \"gpt-4-0613\").unwrap();\n\n        assert_eq!(expected, encoded);\n    }\n\n    #[test]\n    fn test_xml_empty_lines() {\n        let input = \"\nFoo\n\n\n\nbar\n\n<GeneratedCode>\n<Code>\nfn main() {\n    let x = 1;\n\n    dbg!(x);\n}\n</Code>\n<Language>Rust</Language>\n</GeneratedCode>\n\nquux\";\n\n        let expected = \"Foo\n\nbar\n\n``` type:Generated,lang:Rust,path:,lines:0-0\nfn main() {\n    let x = 1;\n\n    dbg!(x);\n}\n```\n\nquux\";\n\n        let body = decode(&sanitize(input));\n        assert_eq!(expected, body);\n    }\n\n    #[test]\n    fn test_limit_tokens() {\n        let bpe = tiktoken_rs::get_bpe_from_model(\"gpt-3.5-turbo\").unwrap();\n        assert_eq!(limit_tokens(\"fn 🚨() {}\", bpe.clone(), 1), \"fn\");\n\n        // Note: the following calls return a string that does not split the emoji, despite the\n        // tokenizer interpreting the tokens like that.\n        assert_eq!(limit_tokens(\"fn 🚨() {}\", bpe.clone(), 2), \"fn\");\n        assert_eq!(limit_tokens(\"fn 🚨() {}\", bpe.clone(), 3), \"fn\");\n\n        // Now we have a sufficient number of input tokens to overcome the emoji.\n        assert_eq!(limit_tokens(\"fn 🚨() {}\", bpe.clone(), 4), \"fn 🚨\");\n        assert_eq!(limit_tokens(\"fn 🚨() {}\", bpe.clone(), 5), \"fn 🚨()\");\n        assert_eq!(limit_tokens(\"fn 🚨() {}\", bpe, 6), \"fn 🚨() {}\");\n    }\n\n    #[test]\n    fn test_decode_erroneous_endlist() {\n        let input = r#\"The code in [`cmd/worker/slack.go`](cmd/worker/slack.go#L1-L42) is a Go program that sends a message to a Slack channel using a webhook URL.\n\nHere's a breakdown of the code:\n\n- Lines 1-8: The package declaration and import statements. The program imports packages for handling bytes, formatting, HTTP requests, and environment variables.\n\n<QuotedCode>\n<Code>\npackage main\n\nimport (\n    \"bytes\"\n    \"fmt\"\n    \"net/http\"\n    \"os\"\n)\n</Code>\n<Language>Go</Language>\n<Path>cmd/worker/slack.go</Path>\n<StartLine>1</StartLine>\n<EndLine>8</EndLine>\n</QuotedCode>\n\n- Lines 10-12: A constant `SLACK_WEBHOOK_URL` is declared. This constant is used to get the Slack webhook URL from the environment variables.\n\n<QuotedCode>\n<Code>\nconst (\n    SLACK_WEBHOOK_URL = \"SLACK_WEBHOOK_URL\"\n)\n</Code>\n<Language>Go</Language>\n<Path>cmd/worker/slack.go</Path>\n<StartLine>10</StartLine>\n<EndLine>12</EndLine>\n</QuotedCode>\n\n- Lines 14-41: The `sendSlackMessage` function is defined. This function takes an organization name as an argument and sends a message to a Slack channel.\n\n<QuotedCode>\n<Code>\nfunc sendSlackMessage(org string) error {\n\n    endpoint := os.Getenv(SLACK_WEBHOOK_URL)\n    if endpoint == \"\" {\n        return fmt.Errorf(\"sendSlackMessage: environment variables %s must not be empty\",\n            SLACK_WEBHOOK_URL)\n    }\n\n    orgRelease := fmt.Sprintf(\"https://github.com/%s/%s/blob/main/%s/release.yaml\",\n        REPO_OWNER, REPO_ENVS, org)\n    message := fmt.Sprintf(\"New organization %#q added.\\nHelmRelease: %s\",\n        org, orgRelease)\n\n    requestBody := []byte(`{\"text\": \"` + message + `\"}`)\n    req, err := http.NewRequest(\"POST\", endpoint, bytes.NewBuffer(requestBody))\n    if err != nil {\n        return fmt.Errorf(\"sendSlackMessage: failed to create request: %v\", err)\n    }\n    req.Header.Set(\"Content-Type\", \"application/json\")\n\n    client := &http.Client{}\n    resp, err := client.Do(req)\n    if err != nil {\n        return fmt.Errorf(\"sendSlackMessage: failed to send Slack message: %v\", err)\n    }\n    defer resp.Body.Close()\n\n    return nil\n}\n</Code>\n<Language>Go</Language>\n<Path>cmd/worker/slack.go</Path>\n<StartLine>14</StartLine>\n<EndLine>41</EndLine>\n</QuotedCode>\"#;\n\n        let expected_body = r#\"The code in [`cmd/worker/slack.go`](cmd/worker/slack.go#L0-L41) is a Go program that sends a message to a Slack channel using a webhook URL.\n\nHere's a breakdown of the code:\n\n- Lines 1-8: The package declaration and import statements. The program imports packages for handling bytes, formatting, HTTP requests, and environment variables.\n\n``` type:Quoted,lang:Go,path:cmd/worker/slack.go,lines:0-7\npackage main\n\nimport (\n    \"bytes\"\n    \"fmt\"\n    \"net/http\"\n    \"os\"\n)\n```\n\n- Lines 10-12: A constant `SLACK_WEBHOOK_URL` is declared. This constant is used to get the Slack webhook URL from the environment variables.\n\n``` type:Quoted,lang:Go,path:cmd/worker/slack.go,lines:9-11\nconst (\n    SLACK_WEBHOOK_URL = \"SLACK_WEBHOOK_URL\"\n)\n```\n\n- Lines 14-41: The `sendSlackMessage` function is defined. This function takes an organization name as an argument and sends a message to a Slack channel.\n\n``` type:Quoted,lang:Go,path:cmd/worker/slack.go,lines:13-40\nfunc sendSlackMessage(org string) error {\n\n    endpoint := os.Getenv(SLACK_WEBHOOK_URL)\n    if endpoint == \"\" {\n        return fmt.Errorf(\"sendSlackMessage: environment variables %s must not be empty\",\n            SLACK_WEBHOOK_URL)\n    }\n\n    orgRelease := fmt.Sprintf(\"https://github.com/%s/%s/blob/main/%s/release.yaml\",\n        REPO_OWNER, REPO_ENVS, org)\n    message := fmt.Sprintf(\"New organization %#q added.\\nHelmRelease: %s\",\n        org, orgRelease)\n\n    requestBody := []byte(`{\"text\": \"` + message + `\"}`)\n    req, err := http.NewRequest(\"POST\", endpoint, bytes.NewBuffer(requestBody))\n    if err != nil {\n        return fmt.Errorf(\"sendSlackMessage: failed to create request: %v\", err)\n    }\n    req.Header.Set(\"Content-Type\", \"application/json\")\n\n    client := &http.Client{}\n    resp, err := client.Do(req)\n    if err != nil {\n        return fmt.Errorf(\"sendSlackMessage: failed to send Slack message: %v\", err)\n    }\n    defer resp.Body.Close()\n\n    return nil\n}\n```\"#;\n\n        let body = decode(input);\n        assert_eq!(expected_body, body);\n    }\n\n    #[test]\n    fn test_decode_indexing_base() {\n        let input = \"Foo [bar](bar.rs#L1-L10) [quux](quux.rs#L5).\n\n- Fred [thud](thud.rs#L1-L10) corge.\n- Grault [garply](waldo.rs#L1-L10) plugh.\";\n\n        let expected = \"Foo [bar](bar.rs#L0-L9) [quux](quux.rs#L4).\n\n- Fred [thud](thud.rs#L0-L9) corge.\n- Grault [garply](waldo.rs#L0-L9) plugh.\";\n\n        let body = decode(input);\n\n        assert_eq!(expected, body);\n    }\n\n    #[test]\n    fn test_encode_indexing_base() {\n        // We test a list with *two* items to check that short circuiting logic doesn't case\n        // issues.\n\n        let input = \"Foo [bar](bar.rs#L0-L9) [quux](quux.rs#L4).\n\n- Fred [thud](thud.rs#L0-L9) corge.\n- Grault [garply](waldo.rs#L0-L9) plugh.\";\n\n        let expected = \"Foo [bar](bar.rs#L1-L10) [quux](quux.rs#L5).\n\n- Fred [thud](thud.rs#L1-L10) corge.\n- Grault [garply](waldo.rs#L1-L10) plugh.\";\n\n        assert_eq!(expected, encode(input));\n    }\n\n    #[test]\n    fn test_bug_short_circuit_link_offset() {\n        let input = \"Yes, this project is deployable on Kubernetes. The project contains a Helm chart located in the [`helm/bloop/`](helm/bloop/) directory. This chart includes various Kubernetes resource definitions such as:\n\n- A [`Deployment`](helm/bloop/templates/bloop-deployment.yaml#L1-L21) for the main application\n- A [`Service`](helm/bloop/templates/bloop-service.yaml#L1-L18) to expose the application within the cluster\n- A [`PersistentVolumeClaim`](helm/bloop/templates/bloop-pvc.yaml#L1-L15) for persistent storage\n- A [`StatefulSet`](helm/bloop/templates/qdrant-statefulset.yaml#L1-L145) for the Qdrant service\n- A [`Job`](helm/bloop/templates/notification-job.yaml#L1-L25) for sending notifications\n\nThe Helm chart's configurable values are defined in the [`values.yaml`](helm/bloop/values.yaml#L1-L201) file.\";\n\n        let expected_body = \"Yes, this project is deployable on Kubernetes. The project contains a Helm chart located in the [`helm/bloop/`](helm/bloop/) directory. This chart includes various Kubernetes resource definitions such as:\n\n- A [`Deployment`](helm/bloop/templates/bloop-deployment.yaml#L0-L20) for the main application\n- A [`Service`](helm/bloop/templates/bloop-service.yaml#L0-L17) to expose the application within the cluster\n- A [`PersistentVolumeClaim`](helm/bloop/templates/bloop-pvc.yaml#L0-L14) for persistent storage\n- A [`StatefulSet`](helm/bloop/templates/qdrant-statefulset.yaml#L0-L144) for the Qdrant service\n- A [`Job`](helm/bloop/templates/notification-job.yaml#L0-L24) for sending notifications\n\nThe Helm chart's configurable values are defined in the [`values.yaml`](helm/bloop/values.yaml#L0-L200) file.\";\n\n        let body = decode(input);\n        assert_eq!(expected_body, body);\n    }\n\n    #[test]\n    fn test_markdown_wrapped_generated_xml_block_info_string() {\n        let input = \"Generated code, accidentally wrapped in a markdown block:\n\n```jsx\n<GeneratedCode>\n<Code>\n<Link to=\\\"/home\\\">Home</Link>\n</Code>\n<Language>JSX</Language>\n</GeneratedCode>\n```\n\nAnother paragraph.\";\n\n        let expected = \"Generated code, accidentally wrapped in a markdown block:\n\n``` type:Generated,lang:JSX,path:,lines:0-0\n<Link to=\\\"/home\\\">Home</Link>\n```\n\nAnother paragraph.\";\n\n        let body = decode(input);\n        assert_eq!(expected, body);\n    }\n\n    #[test]\n    fn test_markdown_wrapped_quoted_xml_block() {\n        let input = \"Quoted code this time, also accidentally wrapped in a markdown block, but this time without an info string:\n\n```\n<QuotedCode>\n<Code>\nfn main() {\n    println!(\\\"hello world\\\");\n}\n</Code>\n<Language>Rust</Language>\n<Path>src/main.rs</Path>\n<StartLine>1</StartLine>\n<EndLine>3</EndLine>\n</QuotedCode>\n```\n\nAnother paragraph.\";\n\n        let expected = \"Quoted code this time, also accidentally wrapped in a markdown block, but this time without an info string:\n\n``` type:Quoted,lang:Rust,path:src/main.rs,lines:0-2\nfn main() {\n    println!(\\\"hello world\\\");\n}\n```\n\nAnother paragraph.\";\n\n        let body = decode(input);\n        assert_eq!(expected, body);\n    }\n\n    #[test]\n    fn test_xml_code_with_markdown_doc_comment() {\n        let input = \"Generated code, with markdown doc comments:\n\n<GeneratedCode>\n<Code>\n/**\n```\nassert_eq!(foo(), 123);\n```\n*/\nfn foo() -> i32 {\n    123\n}\n</Code>\n<Language>Rust</Language>\n</GeneratedCode>\n\nAnother paragraph.\";\n\n        let expected = \"Generated code, with markdown doc comments:\n\n```` type:Generated,lang:Rust,path:,lines:0-0\n/**\n```\nassert_eq!(foo(), 123);\n```\n*/\nfn foo() -> i32 {\n    123\n}\n````\n\nAnother paragraph.\";\n\n        let body = decode(input);\n        assert_eq!(expected, body);\n    }\n\n    #[test]\n    fn test_xml_code_with_markdown_doc_comment_meta() {\n        let input =\n            \"Generated code, with markdown doc comments that have multiple block nesting levels:\n\n<GeneratedCode>\n<Code>\n/**\n````\n```\nbar\n```\nassert_eq!(foo(), 123);\n````\n*/\nfn foo() -> i32 {\n    123\n}\n</Code>\n<Language>Rust</Language>\n</GeneratedCode>\n\nAnother paragraph.\";\n\n        let expected =\n            \"Generated code, with markdown doc comments that have multiple block nesting levels:\n\n````` type:Generated,lang:Rust,path:,lines:0-0\n/**\n````\n```\nbar\n```\nassert_eq!(foo(), 123);\n````\n*/\nfn foo() -> i32 {\n    123\n}\n`````\n\nAnother paragraph.\";\n\n        let body = decode(input);\n        assert_eq!(expected, body);\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/agent.rs",
    "content": "use std::{ops::Deref, str::FromStr, sync::Arc, time::Duration};\n\nuse anyhow::{anyhow, Context, Result};\nuse futures::{Future, TryStreamExt};\nuse tokio::sync::mpsc::Sender;\nuse tracing::{debug, error, info, instrument};\n\nuse crate::{\n    agent::exchange::RepoPath,\n    indexes::reader::{ContentDocument, FileDocument},\n    llm::client::{api, Client},\n    query::{parser, stopwords::remove_stopwords},\n    repo::RepoRef,\n    semantic::{self, SemanticSearchParams},\n    webserver::{conversation::Conversation, middleware::User},\n    Application,\n};\n\nuse self::exchange::{Exchange, SearchStep, Update};\n\n/// The maximum number of steps the agent will take before forcing an answer.\nconst MAX_STEPS: usize = 10;\n\npub mod exchange;\npub mod model;\npub mod prompts;\npub mod symbol;\npub mod transcoder;\n\n/// A collection of modules that each add methods to `Agent`.\n///\n/// These methods correspond to `Action` handlers, and often have supporting methods and supporting\n/// functions, that are local to their own implementation. These modules also have independent\n/// tests.\nmod tools {\n    pub mod answer;\n    pub mod code;\n    pub mod path;\n    pub mod proc;\n}\n\npub enum Error {\n    Timeout(Duration),\n    Processing(anyhow::Error),\n}\n\npub struct Agent {\n    pub app: Application,\n    pub conversation: Conversation,\n    pub exchange_tx: Sender<Exchange>,\n\n    pub llm_gateway: Client,\n    pub user: User,\n    pub query_id: uuid::Uuid,\n    pub repo_refs: Vec<RepoRef>,\n\n    pub answer_model: model::LLMModel,\n    pub agent_model: model::LLMModel,\n\n    /// Indicate whether the request was answered.\n    ///\n    /// This is used in the `Drop` handler, in order to track cancelled answer queries.\n    pub exchange_state: ExchangeState,\n}\n\npub enum ExchangeState {\n    Pending,\n    Complete,\n    Failed,\n}\n\npub struct AgentSemanticSearchParams<'a> {\n    pub query: parser::Literal<'a>,\n    pub paths: Vec<RepoPath>,\n    pub repos: Vec<RepoRef>,\n    pub semantic_params: SemanticSearchParams,\n}\n\n/// We use a `Drop` implementation to track agent query cancellation.\n///\n/// Query control flow can be complex, as there are several points where an error may be returned\n/// via `?`. Rather than dealing with this in a complex way, we can simply use `Drop` destructors\n/// to send cancellation messages to our analytics provider.\n///\n/// By default, dropping an agent struct will send a cancellation message. However, calling\n/// `.complete()` will \"diffuse\" tracking, and disable the cancellation message from sending on drop.\nimpl Drop for Agent {\n    fn drop(&mut self) {\n        match self.exchange_state {\n            ExchangeState::Failed => {}\n            ExchangeState::Pending => {\n                if std::thread::panicking() {\n                } else {\n                    self.last_exchange_mut().apply_update(Update::SetTimestamp);\n                    tokio::spawn(self.store());\n                }\n            }\n\n            ExchangeState::Complete => {\n                tokio::spawn(self.store());\n            }\n        }\n    }\n}\n\nimpl Agent {\n    /// Complete this agent, preventing an analytics message from sending on drop.\n    pub fn complete(&mut self, success: bool) {\n        // Checked in `Drop::drop`\n        self.exchange_state = if success {\n            ExchangeState::Complete\n        } else {\n            ExchangeState::Failed\n        };\n    }\n\n    /// Update the last exchange\n    #[instrument(skip(self), level = \"debug\")]\n    async fn update(&mut self, update: Update) -> Result<()> {\n        self.last_exchange_mut().apply_update(update);\n\n        // Immutable reborrow of `self`\n        let self_ = &*self;\n        self_\n            .exchange_tx\n            .send(self.last_exchange().clone())\n            .await\n            .map_err(|_| anyhow!(\"exchange_tx was closed\"))\n    }\n\n    fn last_exchange(&self) -> &Exchange {\n        self.conversation\n            .exchanges\n            .last()\n            .expect(\"exchange list was empty\")\n    }\n\n    fn last_exchange_mut(&mut self) -> &mut Exchange {\n        self.conversation\n            .exchanges\n            .last_mut()\n            .expect(\"exchange list was empty\")\n    }\n\n    fn paths(&self) -> impl Iterator<Item = &RepoPath> {\n        self.conversation\n            .exchanges\n            .iter()\n            .flat_map(|e| e.paths.iter())\n    }\n\n    fn get_path_alias(&mut self, repo_path: &RepoPath) -> usize {\n        // This has to be stored a variable due to a Rust NLL bug:\n        // https://github.com/rust-lang/rust/issues/51826\n        let pos = self.paths().position(|p| p == repo_path);\n        if let Some(i) = pos {\n            i\n        } else {\n            let i = self.paths().count();\n            self.last_exchange_mut().paths.push(repo_path.clone());\n            i\n        }\n    }\n\n    fn relevant_repos(&self) -> Vec<RepoRef> {\n        let query_repos = self.last_exchange().query.repos().collect::<Vec<_>>();\n\n        if query_repos.is_empty() {\n            self.repo_refs.clone()\n        } else {\n            self.repo_refs\n                .iter()\n                .filter(|r| query_repos.contains(&r.indexed_name().into()))\n                .cloned()\n                .collect()\n        }\n    }\n\n    pub async fn step(&mut self, action: Action) -> Result<Option<Action>> {\n        info!(?action, %self.conversation.thread_id, \"executing next action\");\n\n        match &action {\n            Action::Query(s) => {\n                // Always make a code search for the user query on the first exchange\n                if self.conversation.exchanges.len() == 1 {\n                    let keywords = {\n                        let keys = remove_stopwords(s);\n                        if keys.is_empty() {\n                            s.clone()\n                        } else {\n                            keys\n                        }\n                    };\n                    self.code_search(&keywords).await?;\n                }\n                s.clone()\n            }\n\n            Action::Answer { paths } => {\n                self.answer(paths).await.context(\"answer action failed\")?;\n                return Ok(None);\n            }\n\n            Action::Path { query } => self.path_search(query).await?,\n            Action::Code { query } => self.code_search(query).await?,\n            Action::Proc { query, paths } => self.process_files(query, paths).await?,\n        };\n\n        if self.last_exchange().search_steps.len() >= MAX_STEPS {\n            return Ok(Some(Action::Answer {\n                paths: self.paths().enumerate().map(|(i, _)| i).collect(),\n            }));\n        }\n\n        let functions = serde_json::from_value::<Vec<api::Function>>(\n            prompts::functions(self.paths().next().is_some()), // Only add proc if there are paths in context\n        )\n        .unwrap();\n\n        let mut history = vec![api::Message::system(&prompts::system(self.paths()))];\n        history.extend(self.history()?);\n\n        let trimmed_history = trim_history(history.clone(), self.agent_model)?;\n\n        let raw_response = self\n            .llm_gateway\n            .chat_stream(&trimmed_history, Some(&functions))\n            .await?\n            .try_fold(api::FunctionCall::default(), |acc, e| async move {\n                let e: api::FunctionCall = serde_json::from_str(&e).map_err(|err| {\n                    tracing::error!(\n                        \"Failed to deserialize to FunctionCall: {:?}. Error: {:?}\",\n                        e,\n                        err\n                    );\n                    err\n                })?;\n                Ok(api::FunctionCall {\n                    name: acc.name.or(e.name),\n                    arguments: acc.arguments + &e.arguments,\n                })\n            })\n            .await\n            .context(\"failed to fold LLM function call output\")?;\n\n        let action =\n            Action::deserialize_gpt(&raw_response).context(\"failed to deserialize LLM output\")?;\n\n        Ok(Some(action))\n    }\n\n    /// The full history of messages, including intermediate function calls\n    fn history(&self) -> Result<Vec<api::Message>> {\n        const ANSWER_MAX_HISTORY_SIZE: usize = 3;\n        const FUNCTION_CALL_INSTRUCTION: &str = \"Call a function. Do not answer\";\n\n        let history = self\n            .conversation\n            .exchanges\n            .iter()\n            .rev()\n            .take(ANSWER_MAX_HISTORY_SIZE)\n            .rev()\n            .try_fold(Vec::new(), |mut acc, e| -> Result<_> {\n                let query = e\n                    .query()\n                    .map(|q| api::Message::user(&q))\n                    .ok_or_else(|| anyhow!(\"query does not have target\"))?;\n\n                let steps = e.search_steps.iter().flat_map(|s| {\n                    let (name, arguments) = match s {\n                        SearchStep::Path { query, .. } => (\n                            \"path\".to_owned(),\n                            format!(\"{{\\n \\\"query\\\": \\\"{query}\\\"\\n}}\"),\n                        ),\n                        SearchStep::Code { query, .. } => (\n                            \"code\".to_owned(),\n                            format!(\"{{\\n \\\"query\\\": \\\"{query}\\\"\\n}}\"),\n                        ),\n                        SearchStep::Proc { query, paths, .. } => (\n                            \"proc\".to_owned(),\n                            format!(\n                                \"{{\\n \\\"paths\\\": [{}],\\n \\\"query\\\": \\\"{query}\\\"\\n}}\",\n                                paths\n                                    .iter()\n                                    .map(|path| self\n                                        .paths()\n                                        .position(|p| p == path)\n                                        .unwrap()\n                                        .to_string())\n                                    .collect::<Vec<_>>()\n                                    .join(\", \")\n                            ),\n                        ),\n                    };\n\n                    vec![\n                        api::Message::function_call(&api::FunctionCall {\n                            name: Some(name.clone()),\n                            arguments,\n                        }),\n                        api::Message::function_return(&name, &s.get_response()),\n                        api::Message::user(FUNCTION_CALL_INSTRUCTION),\n                    ]\n                });\n\n                let answer = match e.answer() {\n                    // NB: We intentionally discard the summary as it is redundant.\n                    Some(answer) => {\n                        let encoded = transcoder::encode_summarized(answer, \"gpt-3.5-turbo\")?;\n                        Some(api::Message::function_return(\"none\", &encoded))\n                    }\n\n                    None => None,\n                };\n\n                acc.extend(\n                    std::iter::once(query)\n                        .chain(vec![api::Message::user(FUNCTION_CALL_INSTRUCTION)])\n                        .chain(steps)\n                        .chain(answer.into_iter()),\n                );\n                Ok(acc)\n            })?;\n        Ok(history)\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    async fn semantic_search(\n        &self,\n        AgentSemanticSearchParams {\n            query,\n            paths,\n            repos,\n            semantic_params,\n        }: AgentSemanticSearchParams<'_>,\n    ) -> Result<Vec<semantic::Payload>> {\n        let paths_set = paths\n            .into_iter()\n            .map(|p| parser::Literal::Plain(p.path.into()))\n            .collect::<Vec<_>>();\n\n        let paths = if paths_set.is_empty() {\n            self.last_exchange().query.paths.clone()\n        } else if self.last_exchange().query.paths.is_empty() {\n            paths_set\n        } else {\n            paths_set\n                .into_iter()\n                .zip(self.last_exchange().query.paths.clone())\n                .flat_map(|(llm, user)| {\n                    if llm\n                        .as_plain()\n                        .unwrap()\n                        .starts_with(user.as_plain().unwrap().as_ref())\n                    {\n                        // llm-defined is more specific than user request\n                        vec![llm]\n                    } else if user\n                        .as_plain()\n                        .unwrap()\n                        .starts_with(llm.as_plain().unwrap().as_ref())\n                    {\n                        // user-defined is more specific than llm request\n                        vec![user]\n                    } else {\n                        vec![llm, user]\n                    }\n                })\n                .collect()\n        };\n\n        let query = parser::SemanticQuery {\n            target: Some(query),\n            repos: repos\n                .iter()\n                .map(RepoRef::indexed_name)\n                .map(|r| parser::Literal::Plain(r.into()))\n                .collect(),\n            paths,\n            ..self.last_exchange().query.clone()\n        };\n\n        debug!(?query, %self.conversation.thread_id, \"executing semantic query\");\n        self.app.semantic.search(&query, semantic_params).await\n    }\n\n    async fn get_file_content(\n        &self,\n        RepoPath { repo, path }: &RepoPath,\n    ) -> Result<Option<ContentDocument>> {\n        let branch = self.last_exchange().query.first_branch();\n\n        debug!(%repo, path, ?branch, %self.conversation.thread_id, \"executing file search\");\n        self.app\n            .indexes\n            .file\n            .by_path(repo, path, branch.as_deref())\n            .await\n            .with_context(|| format!(\"failed to read path: {}\", path))\n    }\n\n    async fn fuzzy_path_search<'a>(\n        &'a self,\n        query: &str,\n    ) -> impl Iterator<Item = FileDocument> + 'a {\n        let langs = self.last_exchange().query.langs.iter().map(Deref::deref);\n        let user_id = self.user.username().expect(\"didn't have user ID\");\n\n        let (repos, branches): (Vec<_>, Vec<_>) = sqlx::query! {\n            \"SELECT pr.repo_ref, pr.branch\n            FROM project_repos pr\n            INNER JOIN projects p ON p.id = pr.project_id AND p.user_id = ?\",\n            user_id,\n        }\n        .fetch_all(&*self.app.sql)\n        .await\n        .expect(\"failed to fetch repo associations\")\n        .into_iter()\n        .filter_map(|row| {\n            let repo_ref = RepoRef::from_str(&row.repo_ref).ok()?;\n            Some((repo_ref, row.branch))\n        })\n        .filter(|(repo_ref, _)| self.repo_refs.contains(repo_ref))\n        .unzip();\n\n        let branch = branches.first().cloned().flatten();\n\n        debug!(?query, ?branch, %self.conversation.thread_id, \"executing fuzzy search\");\n        self.app\n            .indexes\n            .file\n            .skim_fuzzy_path_match(repos.into_iter(), query, branch.as_deref(), langs, 50)\n            .await\n    }\n\n    /// Store the conversation in the DB.\n    ///\n    /// This allows us to make subsequent requests.\n    // NB: This isn't an `async fn` so as to not capture a lifetime.\n    fn store(&mut self) -> impl Future<Output = ()> {\n        let sql = Arc::clone(&self.app.sql);\n\n        let user_id = self\n            .user\n            .username()\n            .context(\"didn't have user ID\")\n            .map(str::to_owned);\n\n        let conversation = self.conversation.clone();\n\n        async move {\n            let result = match user_id {\n                Ok(user_id) => conversation.store(&sql, &user_id).await,\n                Err(e) => Err(e),\n            };\n\n            if let Err(e) = result {\n                error!(\"failed to store conversation: {e}\");\n            }\n        }\n    }\n}\n\nfn trim_history(\n    mut history: Vec<api::Message>,\n    model: model::LLMModel,\n) -> Result<Vec<api::Message>> {\n    const HIDDEN: &str = \"[HIDDEN]\";\n\n    let mut tiktoken_msgs = history.iter().map(|m| m.into()).collect::<Vec<_>>();\n\n    while tiktoken_rs::get_chat_completion_max_tokens(model.tokenizer, &tiktoken_msgs)?\n        < model.history_headroom\n    {\n        let _ = history\n            .iter_mut()\n            .zip(tiktoken_msgs.iter_mut())\n            .position(|(m, tm)| match m {\n                api::Message::PlainText {\n                    role,\n                    ref mut content,\n                } => {\n                    if role == \"assistant\" && content != HIDDEN {\n                        *content = HIDDEN.into();\n                        tm.content = Some(HIDDEN.into());\n                        true\n                    } else {\n                        false\n                    }\n                }\n                api::Message::FunctionReturn {\n                    role: _,\n                    name: _,\n                    ref mut content,\n                } if content != HIDDEN => {\n                    *content = HIDDEN.into();\n                    tm.content = Some(HIDDEN.into());\n                    true\n                }\n                _ => false,\n            })\n            .ok_or_else(|| anyhow!(\"could not find message to trim\"))?;\n    }\n\n    Ok(history)\n}\n\n#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum Action {\n    /// A user-provided query.\n    Query(String),\n\n    Path {\n        query: String,\n    },\n    #[serde(rename = \"none\")]\n    Answer {\n        paths: Vec<usize>,\n    },\n    Code {\n        query: String,\n    },\n    Proc {\n        query: String,\n        paths: Vec<usize>,\n    },\n}\n\nimpl Action {\n    /// Deserialize this action from the GPT-tagged enum variant format.\n    ///\n    /// We convert (2 examples):\n    ///\n    /// ```text\n    /// {\"name\": \"Variant1\", \"args\": {}}\n    /// {\"name\": \"Variant2\", \"args\": {\"a\":123}}\n    /// ```\n    ///\n    /// To:\n    ///\n    /// ```text\n    /// {\"Variant1\": {}}\n    /// {\"Variant2\": {\"a\":123}}\n    /// ```\n    ///\n    /// So that we can deserialize using the serde-provided \"tagged\" enum representation.\n    fn deserialize_gpt(call: &api::FunctionCall) -> Result<Self> {\n        let mut map = serde_json::Map::new();\n        map.insert(\n            call.name.clone().unwrap(),\n            serde_json::from_str(&call.arguments)?,\n        );\n\n        Ok(serde_json::from_value(serde_json::Value::Object(map))?)\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/background/control.rs",
    "content": "use std::{\n    sync::{\n        atomic::{AtomicBool, AtomicUsize, Ordering},\n        Arc, RwLock,\n    },\n    time::{Duration, Instant},\n};\n\nuse tracing::debug;\n\nuse crate::repo::{FilterUpdate, RepoRef, SyncStatus};\n\nuse super::{Progress, ProgressEvent};\n\nconst GIT_REPORT_DELAY: Duration = Duration::from_secs(3);\n\nenum ControlEvent {\n    /// Cancel whatever's happening, and return\n    Cancel,\n\n    /// Cancel and immediately remove the repo\n    Remove,\n}\n\npub struct SyncPipes {\n    /// Together with `filter_updates`, it uniquely identifies the sync process\n    reporef: RepoRef,\n\n    /// Is this a re-sync?\n    resync: bool,\n\n    /// Together with `reporef`, it uniquely identifies the sync process\n    filter_updates: FilterUpdate,\n\n    /// Channel to stream updates to the frontend\n    progress: super::ProgressStream,\n\n    /// Control event received from frontend\n    event: RwLock<Option<ControlEvent>>,\n\n    /// Interrupt signal channel for `gix`\n    git_interrupt: Arc<AtomicBool>,\n}\n\nimpl SyncPipes {\n    pub(super) fn new(\n        reporef: RepoRef,\n        resync: bool,\n        filter_updates: FilterUpdate,\n        progress: super::ProgressStream,\n    ) -> Self {\n        Self {\n            reporef,\n            resync,\n            progress,\n            filter_updates,\n            git_interrupt: Default::default(),\n            event: Default::default(),\n        }\n    }\n\n    pub(crate) fn git_sync_progress(&self) -> GitSync {\n        // clear any state stored on the frontend\n        _ = self.progress.send(Progress {\n            reporef: self.reporef.clone(),\n            resync: self.resync,\n            branch_filter: self.filter_updates.branch_filter.clone(),\n            event: ProgressEvent::IndexPercent(None),\n        });\n\n        GitSync {\n            created: Instant::now(),\n            max: Arc::new(usize::MAX.into()),\n            cnt: Arc::new(0.into()),\n            id: Default::default(),\n            name: Default::default(),\n            progress: self.progress.clone(),\n            reporef: self.reporef.clone(),\n            resync: self.resync,\n            filter_updates: self.filter_updates.clone(),\n        }\n    }\n\n    pub(crate) fn index_percent(&self, current: u8) {\n        _ = self.progress.send(Progress {\n            reporef: self.reporef.clone(),\n            resync: self.resync,\n            branch_filter: self.filter_updates.branch_filter.clone(),\n            event: ProgressEvent::IndexPercent(Some(current)),\n        });\n    }\n\n    pub(crate) fn status(&self, new: SyncStatus) {\n        _ = self.progress.send(Progress {\n            reporef: self.reporef.clone(),\n            resync: self.resync,\n            branch_filter: self.filter_updates.branch_filter.clone(),\n            event: ProgressEvent::StatusChange(new),\n        });\n    }\n\n    pub(crate) fn is_interrupted(&self) -> Arc<AtomicBool> {\n        Arc::clone(&self.git_interrupt)\n    }\n\n    pub(crate) fn is_cancelled(&self) -> bool {\n        use ControlEvent::*;\n        matches!(self.event.read().unwrap().as_ref(), Some(Cancel | Remove))\n    }\n\n    pub(crate) fn is_removed(&self) -> bool {\n        use ControlEvent::*;\n        matches!(self.event.read().unwrap().as_ref(), Some(Remove))\n    }\n\n    pub(crate) fn cancel(&self) {\n        *self.event.write().unwrap() = Some(ControlEvent::Cancel);\n        self.git_interrupt.store(true, Ordering::Relaxed);\n    }\n\n    pub(crate) fn remove(&self) {\n        *self.event.write().unwrap() = Some(ControlEvent::Remove);\n        self.git_interrupt.store(true, Ordering::Relaxed);\n    }\n}\n\n#[derive(Clone)]\npub(crate) struct GitSync {\n    /// Record creation time so we can delay sending events\n    created: Instant,\n\n    /// Maximum value\n    max: Arc<AtomicUsize>,\n\n    /// Current value\n    cnt: Arc<AtomicUsize>,\n\n    /// This is where we report status\n    progress: super::ProgressStream,\n\n    /// Copy from `SyncPipes`, because we can't make this a referential type\n    reporef: RepoRef,\n\n    /// Copy from `SyncPipes`, because we can't make this a referential type\n    resync: bool,\n\n    /// Copy from `SyncPipes`, because we can't make this a referential type\n    filter_updates: FilterUpdate,\n\n    /// Used by `gix`\n    id: gix::progress::Id,\n\n    /// Used by `gix`\n    name: String,\n}\n\nimpl gix::progress::Progress for GitSync {\n    fn init(\n        &mut self,\n        max: Option<gix::progress::prodash::progress::Step>,\n        _unit: Option<gix::progress::Unit>,\n    ) {\n        let Some(max) = max else {\n            return;\n        };\n\n        self.max.store(max, Ordering::SeqCst);\n        self.cnt.store(0, Ordering::SeqCst);\n    }\n\n    fn set_name(&mut self, name: String) {\n        self.name = name;\n    }\n\n    fn name(&self) -> Option<String> {\n        Some(self.name.clone())\n    }\n\n    fn id(&self) -> gix::progress::Id {\n        self.id\n    }\n\n    fn message(&self, level: gix::progress::MessageLevel, message: String) {\n        debug!(name = self.name, message, ?level, \"git status message\");\n    }\n}\n\nimpl gix::progress::Count for GitSync {\n    fn set(&self, step: gix::progress::prodash::progress::Step) {\n        self.cnt.store(step, Ordering::SeqCst);\n\n        if self.created.elapsed() > GIT_REPORT_DELAY {\n            let current = ((step as f32 / self.max.load(Ordering::SeqCst) as f32) * 100f32) as u8;\n            _ = self.progress.send(Progress {\n                reporef: self.reporef.clone(),\n                resync: self.resync,\n                branch_filter: self.filter_updates.branch_filter.clone(),\n                event: ProgressEvent::IndexPercent(Some(current.min(100))),\n            });\n        }\n    }\n\n    fn step(&self) -> gix::progress::prodash::progress::Step {\n        1\n    }\n\n    fn inc_by(&self, step: gix::progress::prodash::progress::Step) {\n        self.cnt.fetch_add(step, Ordering::SeqCst);\n\n        if self.created.elapsed() > GIT_REPORT_DELAY {\n            let max = self.max.load(Ordering::SeqCst);\n            let current = self.cnt.load(Ordering::SeqCst);\n\n            // This logic is super arbitrary. If the `max` is larger\n            // than 10000, we assume that it's a meaningful value. At\n            // the beginning of the syncing process `gix` sometimes\n            // reports some smaller values that cause a false `0%`\n            // progress to make it to the frontend.\n            //\n            // The magic constant 4 * 1024 was experientially\n            // determined to roughly align with the scale of data git\n            // ends up receiving from the server.\n            //\n            // Feel free to change anything as long as it looks good\n            // on the frontend.\n            let current = if max > 10000 {\n                ((current as f32 / (max * 4 * 1024) as f32) * 100f32) as u8\n            } else {\n                0\n            };\n\n            _ = self.progress.send(Progress {\n                reporef: self.reporef.clone(),\n                resync: self.resync,\n                branch_filter: self.filter_updates.branch_filter.clone(),\n                event: ProgressEvent::IndexPercent(Some(current.min(100))),\n            });\n        }\n    }\n\n    fn counter(&self) -> gix::progress::StepShared {\n        self.cnt.clone()\n    }\n}\n\n/// These implementations just create clones of the original one, we\n/// don't treat children as separate tasks apart from id/naming to\n/// preserve some illusion of being good citizens.\nimpl gix::progress::NestedProgress for GitSync {\n    type SubProgress = Self;\n\n    fn add_child(&mut self, name: impl Into<String>) -> Self::SubProgress {\n        self.add_child_with_id(name, self.id)\n    }\n\n    fn add_child_with_id(\n        &mut self,\n        name: impl Into<String>,\n        id: gix::progress::Id,\n    ) -> Self::SubProgress {\n        let name = name.into();\n        let progress = if name == \"read pack\" {\n            self.progress.clone()\n        } else {\n            let (sender, _) = tokio::sync::broadcast::channel(1000);\n            sender\n        };\n\n        GitSync {\n            created: self.created,\n            max: self.max.clone(),\n            cnt: self.cnt.clone(),\n            filter_updates: self.filter_updates.clone(),\n            reporef: self.reporef.clone(),\n            resync: self.resync,\n            progress,\n            name,\n            id,\n        }\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/background/notifyqueue.rs",
    "content": "use std::{collections::VecDeque, sync::Arc};\n\nuse tokio::sync::{RwLock, Semaphore};\n\nuse crate::repo::RepoRef;\n\nuse super::sync::SyncHandle;\n\n/// Asynchronous queue with await semantics for popping the front\n/// element.\npub(crate) struct NotifyQueue {\n    queue: RwLock<VecDeque<Arc<SyncHandle>>>,\n    available: Semaphore,\n}\n\nimpl Default for NotifyQueue {\n    fn default() -> Self {\n        Self {\n            queue: Default::default(),\n            available: Semaphore::new(0),\n        }\n    }\n}\n\nimpl NotifyQueue {\n    pub(crate) async fn push_front(&self, item: Arc<SyncHandle>) {\n        let mut q = self.queue.write().await;\n\n        self.available.add_permits(1);\n\n        q.push_front(item);\n    }\n\n    pub(crate) async fn push(&self, item: Arc<SyncHandle>) {\n        let mut q = self.queue.write().await;\n\n        self.available.add_permits(1);\n\n        q.push_back(item);\n    }\n\n    pub(super) async fn pop_if(&self, pred: impl Fn(&SyncHandle) -> bool) -> Arc<SyncHandle> {\n        loop {\n            let permit = self.available.acquire().await.expect(\"fatal\");\n            let mut q = self.queue.write().await;\n\n            let first = q.iter().position(|h| (pred)(h));\n\n            if let Some(pos) = first {\n                permit.forget();\n                return q.remove(pos).expect(\"locked\");\n            }\n        }\n    }\n\n    #[allow(unused)]\n    pub(super) async fn get_list(&self) -> Vec<Arc<SyncHandle>> {\n        self.queue.read().await.iter().cloned().collect()\n    }\n\n    pub(super) async fn contains(&self, reporef: &RepoRef) -> bool {\n        self.queue\n            .read()\n            .await\n            .iter()\n            .any(|h| &h.reporef == reporef)\n    }\n\n    pub(super) async fn remove(&self, reporef: RepoRef) {\n        let mut q = self.queue.write().await;\n        if let Ok(ticket) = self.available.try_acquire() {\n            ticket.forget();\n        }\n        q.retain(|item| item.reporef != reporef);\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/background/sync.rs",
    "content": "use either::Either;\nuse tokio::sync::OwnedSemaphorePermit;\nuse tracing::{debug, error, info, warn};\n\nuse crate::{\n    cache::FileCache,\n    indexes,\n    remotes::RemoteError,\n    repo::{\n        iterator::FileFilterRule, Backend, FileFilterConfig, FilterUpdate, RepoError, RepoMetadata,\n        RepoRef, Repository, SyncStatus,\n    },\n    Application,\n};\n\nuse std::{borrow::Borrow, num::NonZeroU32, path::PathBuf, sync::Arc};\n\nuse super::control::SyncPipes;\n\npub struct SyncHandle {\n    pub(crate) reporef: RepoRef,\n    pub(crate) filter_updates: FilterUpdate,\n    pub(crate) pipes: SyncPipes,\n    pub(crate) file_cache: FileCache,\n    pub(crate) app: Application,\n    pub(crate) shallow_config: gix::remote::fetch::Shallow,\n    shallow: bool,\n    exited: flume::Sender<SyncStatus>,\n    exit_signal: flume::Receiver<SyncStatus>,\n}\n\ntype Result<T> = std::result::Result<T, SyncError>;\n#[derive(thiserror::Error, Debug)]\npub(super) enum SyncError {\n    #[error(\"no keys for backend: {0:?}\")]\n    NoKeysForBackend(Backend),\n\n    #[error(\"path not allowed: {0:?}\")]\n    PathNotAllowed(PathBuf),\n\n    #[error(\"indexing failed: {0:?}\")]\n    Indexing(RepoError),\n\n    #[error(\"sync failed: {0:?}\")]\n    Sync(RemoteError),\n\n    #[error(\"file cache cleanup failed: {0:?}\")]\n    State(RepoError),\n\n    #[error(\"folder cleanup failed: path: {0:?}, error: {1}\")]\n    RemoveLocal(PathBuf, std::io::Error),\n\n    #[error(\"tantivy: {0:?}\")]\n    Tantivy(anyhow::Error),\n\n    #[error(\"sql: {0:?}\")]\n    Sql(anyhow::Error),\n\n    #[error(\"syncing in progress\")]\n    SyncInProgress,\n\n    #[error(\"cancelled by user\")]\n    Cancelled,\n\n    #[error(\"repo removed by the user\")]\n    Removed,\n}\n\nimpl PartialEq for SyncHandle {\n    fn eq(&self, other: &Self) -> bool {\n        self.reporef == other.reporef\n    }\n}\n\nimpl Drop for SyncHandle {\n    fn drop(&mut self) {\n        let status = self.set_status(|v| {\n            use SyncStatus::*;\n            match &v.sync_status {\n                Indexing | Syncing => Error {\n                    message: \"unknown\".into(),\n                },\n                Cancelling => Cancelled,\n                other => other.clone(),\n            }\n        });\n\n        _ = self.app.config.source.save_pool(self.app.repo_pool.clone());\n\n        debug!(?status, %self.reporef, \"normalized status after sync\");\n        if self\n            .exited\n            .send(status.unwrap_or(SyncStatus::Removed))\n            .is_err()\n        {\n            debug!(\"notification failed, repo probably deleted\");\n        }\n    }\n}\n\npub struct SyncConfig {\n    app: Application,\n    reporef: RepoRef,\n    filter_updates: Option<FilterUpdate>,\n    shallow: bool,\n}\n\nimpl SyncConfig {\n    pub fn new(app: impl Borrow<Application>, reporef: RepoRef) -> SyncConfig {\n        SyncConfig {\n            app: app.borrow().clone(),\n            reporef,\n            filter_updates: None,\n            shallow: false,\n        }\n    }\n\n    pub fn shallow(mut self, shallow: bool) -> Self {\n        self.shallow = shallow;\n        self\n    }\n\n    pub async fn into_handle(self) -> Arc<SyncHandle> {\n        SyncHandle::new(self).await\n    }\n}\n\nimpl SyncHandle {\n    async fn new(config: SyncConfig) -> Arc<Self> {\n        let SyncConfig {\n            app,\n            reporef,\n            filter_updates,\n            shallow,\n            ..\n        } = config;\n        let status = app.sync_queue.broadcast();\n\n        let mut shallow_config = if shallow {\n            gix::remote::fetch::Shallow::DepthAtRemote(NonZeroU32::new(1).unwrap())\n        } else {\n            gix::remote::fetch::Shallow::DepthAtRemote(NonZeroU32::new(1000).unwrap())\n        };\n\n        // Going through an extra hoop here to ensure the outward\n        // facing interface communicates intent.\n        //\n        // How filter updates work specifically should not have to\n        // trickle down to all callers.\n        let filter_updates = if shallow {\n            FilterUpdate {\n                file_filter: Some(FileFilterConfig {\n                    rules: vec![FileFilterRule::ExcludeRegex(\".*\".into())],\n                }),\n                ..Default::default()\n            }\n        } else {\n            filter_updates.unwrap_or_default()\n        };\n\n        let (exited, exit_signal) = flume::bounded(1);\n        let current = app\n            .repo_pool\n            .entry_async(reporef.clone())\n            .await\n            .or_insert_with(|| {\n                if reporef.is_local() {\n                    Repository::local_from(&reporef)\n                } else {\n                    let name = reporef.to_string();\n                    let remote = reporef.as_ref().into();\n                    let disk_path = app\n                        .config\n                        .source\n                        .repo_path_for_name(&name.replace('/', \"_\"));\n\n                    Repository {\n                        disk_path,\n                        remote,\n                        shallow,\n                        sync_status: SyncStatus::Queued,\n                        pub_sync_status: SyncStatus::Queued,\n                        last_index_unix_secs: 0,\n                        last_commit_unix_secs: 0,\n                        most_common_lang: None,\n                        branch_filter: None,\n                        file_filter: Default::default(),\n                        locked: false,\n                    }\n                }\n            });\n\n        let pipes = SyncPipes::new(\n            reporef.clone(),\n            current.get().last_index_unix_secs != 0,\n            filter_updates.clone(),\n            status,\n        );\n\n        // if we're not upgrading from shallow to full checkout\n        // this seems to be a speed optimization for git operations\n        if !shallow && !current.get().shallow {\n            shallow_config = gix::remote::fetch::Shallow::NoChange;\n        }\n\n        let sh = Self {\n            app: app.clone(),\n            reporef: reporef.clone(),\n            file_cache: FileCache::new(app.sql.clone(), app.semantic.clone()),\n            shallow_config,\n            shallow,\n            pipes,\n            filter_updates,\n            exited,\n            exit_signal,\n        };\n\n        sh.pipes.status(current.get().sync_status.clone());\n        sh.into()\n    }\n\n    pub(super) fn notify_done(&self) -> flume::Receiver<SyncStatus> {\n        self.exit_signal.clone()\n    }\n\n    /// The permit that's taken here is exclusively for parallelism control.\n    pub(super) async fn run(&self, _permit: OwnedSemaphorePermit) -> Result<SyncStatus> {\n        debug!(?self.reporef, \"syncing repo\");\n        let Application { ref repo_pool, .. } = self.app;\n\n        // skip git operations if the repo has been marked as removed\n        // if the ref is non-existent, sync it and add it to the pool\n        let removed = repo_pool\n            .read_async(&self.reporef, |_k, v| v.sync_status == SyncStatus::Removed)\n            .await\n            .unwrap_or(false);\n\n        if !removed {\n            match self.git_sync().await {\n                Ok(status) => {\n                    if let SyncStatus::Done = self.set_status(|_| status).unwrap() {\n                        return Ok(SyncStatus::Done);\n                    }\n                }\n                Err(err) => {\n                    self.set_status(|_| SyncStatus::Error {\n                        message: err.to_string(),\n                    });\n                    return Err(err);\n                }\n            }\n        }\n\n        if self.pipes.is_cancelled() && !self.pipes.is_removed() {\n            self.set_status(|_| SyncStatus::Cancelled);\n            debug!(?self.reporef, \"cancelled while cloning\");\n            return Err(SyncError::Cancelled);\n        }\n\n        // Can we unwrap here?\n        let repository = repo_pool\n            .read_async(&self.reporef, |_k, v| v.clone())\n            .await\n            .unwrap();\n\n        let tutorial_questions = if !repository.shallow {\n            let db = self.app.sql.clone();\n            let llm_gateway = self.app.user().await.llm_gateway(&self.app).await;\n            let repo_pool = self.app.repo_pool.clone();\n            let reporef = self.reporef.clone();\n\n            Some(tokio::task::spawn(\n                crate::commits::generate_tutorial_questions(db, llm_gateway, repo_pool, reporef),\n            ))\n        } else {\n            None\n        };\n\n        let indexed = self.index().await;\n        let status = match indexed {\n            Ok(Either::Left(status)) => Some(status),\n            Ok(Either::Right(state)) => {\n                info!(\"commit complete; indexing done\");\n                self.app.repo_pool.update(&self.reporef, |_k, repo| {\n                    repo.sync_done_with(self.shallow, &self.filter_updates, state)\n                });\n\n                if let Some(tutorial_questions) = tutorial_questions {\n                    if let Err(err) = tutorial_questions.await {\n                        error!(?err, \"failed to generate tutorial questions\");\n                    }\n                }\n                self.set_status(|_| SyncStatus::Done)\n            }\n            Err(SyncError::Cancelled) => self.set_status(|_| SyncStatus::Cancelled),\n            Err(err) => self.set_status(|_| SyncStatus::Error {\n                message: err.to_string(),\n            }),\n        };\n\n        status.ok_or(SyncError::Removed)\n    }\n\n    async fn index(&self) -> Result<Either<SyncStatus, Arc<RepoMetadata>>> {\n        use SyncStatus::*;\n        let Application {\n            ref indexes,\n            ref repo_pool,\n            ..\n        } = self.app;\n\n        let writers = indexes.writers().await.map_err(SyncError::Tantivy)?;\n        let repo = {\n            let mut orig = repo_pool\n                .read_async(&self.reporef, |_k, v| v.clone())\n                .await\n                .unwrap();\n\n            if let Some(ref bf) = self.filter_updates.branch_filter {\n                orig.branch_filter = bf.patch_into(orig.branch_filter.as_ref());\n            }\n\n            if let Some(ref ff) = self.filter_updates.file_filter {\n                orig.file_filter = ff.patch_into(&orig.file_filter);\n            }\n            orig\n        };\n\n        let indexed = match repo.sync_status {\n            current @ (Uninitialized | Syncing | Indexing) => return Ok(Either::Left(current)),\n            Removed => return self.delete_repo(&repo, writers).await,\n            RemoteRemoved => {\n                // Note we don't clean up here, leave the\n                // barebones behind.\n                //\n                // This is to be able to report to the user that\n                // something happened, and let them clean up in a\n                // subsequent action.\n                return Ok(Either::Left(RemoteRemoved));\n            }\n            _ => {\n                self.set_status(|_| Indexing).unwrap();\n                writers.index(self, &repo).await.map(Either::Right)\n            }\n        };\n\n        match indexed {\n            Ok(_) => {\n                writers.commit().map_err(SyncError::Tantivy)?;\n                indexed.map_err(SyncError::Indexing)\n            }\n            Err(_) if self.pipes.is_removed() => self.delete_repo(&repo, writers).await,\n            Err(_) if self.pipes.is_cancelled() => {\n                writers.rollback().map_err(SyncError::Tantivy)?;\n                debug!(?self.reporef, \"index cancelled by user\");\n                Err(SyncError::Cancelled)\n            }\n            Err(err) => {\n                writers.rollback().map_err(SyncError::Tantivy)?;\n                Err(SyncError::Indexing(err))\n            }\n        }\n    }\n\n    async fn delete_repo(\n        &self,\n        repo: &Repository,\n        writers: indexes::GlobalWriteHandle<'_>,\n    ) -> Result<Either<SyncStatus, Arc<RepoMetadata>>> {\n        self.app.repo_pool.remove(&self.reporef);\n\n        let deleted = self.delete_repo_indexes(repo, &writers).await;\n        if deleted.is_ok() {\n            writers.commit().map_err(SyncError::Tantivy)?;\n            self.app\n                .config\n                .source\n                .save_pool(self.app.repo_pool.clone())\n                .map_err(SyncError::State)?;\n        }\n\n        deleted.map(|_| Either::Left(SyncStatus::Removed))\n    }\n\n    async fn git_sync(&self) -> Result<SyncStatus> {\n        let repo = self.reporef.clone();\n        let backend = repo.backend();\n        let creds = match self.app.credentials.for_repo(&repo) {\n            Some(creds) => creds,\n            None => {\n                let Some(path) = repo.local_path() else {\n                    return Err(SyncError::NoKeysForBackend(backend));\n                };\n\n                if !self.app.allow_path(&path) {\n                    return Err(SyncError::PathNotAllowed(path));\n                }\n\n                // we _never_ touch the git repositories of local repos\n                return Ok(SyncStatus::Queued);\n            }\n        };\n\n        let repo = self.sync_lock().await?;\n\n        // This reads really badly, but essentially we need a way to\n        // retry after cleaning things up, and duplicating _too much_\n        // code.\n        let mut loop_counter = 0;\n        let loop_max = 1;\n        let git_err = loop {\n            match creds.clone_or_pull(self, repo.clone()).await {\n                Err(\n                    err @ RemoteError::GitCloneFetch(gix::clone::fetch::Error::PrepareFetch(\n                        gix::remote::fetch::prepare::Error::RefMap(\n                            gix::remote::ref_map::Error::Handshake(\n                                gix::protocol::handshake::Error::InvalidCredentials { .. },\n                            ),\n                        ),\n                    )),\n                ) => {\n                    error!(?err, ?self.reporef, \"invalid credentials for accessing git repo\");\n                    return Err(SyncError::Sync(err));\n                }\n                Err(\n                    err @ RemoteError::GitOpen(_)\n                    | err @ RemoteError::GitFetch(_)\n                    | err @ RemoteError::GitPrepareFetch(_)\n                    | err @ RemoteError::GitClone(_)\n                    | err @ RemoteError::GitCloneFetch(_)\n                    | err @ RemoteError::GitConnect(_)\n                    | err @ RemoteError::GitFindRemote(_),\n                ) => {\n                    _ = repo.remove_all().await;\n\n                    if loop_counter == loop_max {\n                        break err;\n                    }\n\n                    loop_counter += 1;\n                }\n                Err(RemoteError::RemoteNotFound) => {\n                    error!(?repo, \"remote repository removed; disabling local syncing\");\n\n                    // we want indexing to pick this up later and handle the new state\n                    // all local cleanups are done, so everything should be consistent\n                    return Ok(SyncStatus::RemoteRemoved);\n                }\n                Err(RemoteError::GitHub(\n                    octocrab::Error::Service { .. }\n                    | octocrab::Error::Hyper { .. }\n                    | octocrab::Error::Http { .. },\n                )) => {\n                    warn!(\"likely network error, skipping further syncing\");\n                    return Ok(SyncStatus::Done);\n                }\n                Err(RemoteError::Interrupted) if self.pipes.is_removed() => {\n                    _ = repo.remove_all().await;\n                    return Ok(SyncStatus::Removed);\n                }\n                Err(RemoteError::Interrupted) if self.pipes.is_cancelled() => {\n                    return Ok(SyncStatus::Cancelled);\n                }\n                Err(err) => {\n                    error!(?err, ?self.reporef, \"failed to sync repository\");\n                    return Err(SyncError::Sync(err));\n                }\n                Ok(status) => {\n                    self.app\n                        .config\n                        .source\n                        .save_pool(self.app.repo_pool.clone())\n                        .expect(\"filesystem error\");\n\n                    return Ok(status);\n                }\n            }\n        };\n\n        Err(SyncError::Sync(git_err))\n    }\n\n    async fn delete_repo_indexes(\n        &self,\n        repo: &Repository,\n        writers: &indexes::GlobalWriteHandleRef<'_>,\n    ) -> Result<()> {\n        let Application { ref semantic, .. } = self.app;\n\n        semantic\n            .delete_points_for_hash(&self.reporef.to_string(), std::iter::empty())\n            .await;\n\n        self.file_cache\n            .delete(&self.reporef)\n            .await\n            .map_err(SyncError::Sql)?;\n\n        if !self.reporef.is_local() {\n            repo.remove_all()\n                .await\n                .map_err(|e| SyncError::RemoveLocal(repo.disk_path.clone(), e))?;\n        }\n\n        for handle in writers {\n            handle.delete(repo);\n        }\n\n        Ok(())\n    }\n\n    pub(crate) fn set_status(\n        &self,\n        updater: impl FnOnce(&Repository) -> SyncStatus,\n    ) -> Option<SyncStatus> {\n        let (new_status, old_status) =\n            self.app.repo_pool.update(&self.reporef, move |_k, repo| {\n                let new_status = (updater)(repo);\n                let old_status = std::mem::replace(&mut repo.sync_status, new_status);\n\n                if !matches!(repo.sync_status, SyncStatus::Queued)\n                    || matches!(old_status, SyncStatus::Syncing)\n                {\n                    repo.pub_sync_status = repo.sync_status.clone();\n                }\n\n                if matches!(\n                    repo.sync_status,\n                    SyncStatus::Error { .. } | SyncStatus::Done\n                ) {\n                    repo.locked = false;\n                }\n\n                (repo.sync_status.clone(), old_status)\n            })?;\n\n        if let SyncStatus::Error { ref message } = new_status {\n            error!(?self.reporef, err=?message, \"indexing failed\");\n        } else {\n            debug!(?self.reporef, ?new_status, \"new status\");\n        }\n\n        if !matches!(new_status, SyncStatus::Queued) && new_status != old_status {\n            self.pipes.status(new_status.clone());\n        }\n        Some(new_status)\n    }\n\n    async fn sync_lock(&self) -> std::result::Result<Repository, SyncError> {\n        let repo = self\n            .app\n            .repo_pool\n            .update_async(&self.reporef, |_k, repo| {\n                if repo.lock().is_err() {\n                    Err(SyncError::SyncInProgress)\n                } else {\n                    Ok(repo.clone())\n                }\n            })\n            .await;\n\n        match repo {\n            Some(Ok(repo)) => {\n                let new_status = repo.sync_status.clone();\n                debug!(?self.reporef, ?new_status, \"new status\");\n                Ok(repo)\n            }\n            Some(err) => err,\n            None => Err(SyncError::Removed),\n        }\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/background.rs",
    "content": "use once_cell::sync::OnceCell;\nuse rayon::ThreadPool;\nuse thread_priority::ThreadBuilderExt;\nuse tokio::sync::Semaphore;\nuse tracing::{debug, error, info};\n\nuse crate::{\n    repo::{BranchFilterConfig, RepoRef, SyncStatus},\n    Application, Configuration,\n};\n\nuse std::{future::Future, pin::Pin, sync::Arc, thread};\n\nmod sync;\npub(crate) use sync::{SyncConfig, SyncHandle};\n\nmod control;\npub(crate) use control::SyncPipes;\n\nmod notifyqueue;\nuse notifyqueue::NotifyQueue;\n\ntype ProgressStream = tokio::sync::broadcast::Sender<Progress>;\n\nstatic RAYON_POOL: OnceCell<ThreadPool> = OnceCell::new();\n\n/// Get a handle to a `tokio`-enabled `rayon` thread pool.\npub fn rayon_pool() -> &'static ThreadPool {\n    RAYON_POOL\n        .get()\n        .expect(\"rayon thread pool was not yet initialized!\")\n}\n\n#[derive(serde::Serialize, Clone)]\npub struct Progress {\n    #[serde(rename = \"ref\")]\n    reporef: RepoRef,\n    #[serde(rename = \"rsync\")]\n    resync: bool,\n    #[serde(rename = \"b\")]\n    branch_filter: Option<BranchFilterConfig>,\n    #[serde(rename = \"ev\")]\n    event: ProgressEvent,\n}\n\n#[derive(serde::Serialize, Clone)]\n#[serde(rename_all = \"snake_case\")]\npub enum ProgressEvent {\n    IndexPercent(Option<u8>),\n    StatusChange(SyncStatus),\n}\n\ntype Task = Pin<Box<dyn Future<Output = ()> + Send + Sync>>;\n#[derive(Clone)]\npub struct SyncQueue {\n    runner: BackgroundExecutor,\n    active: Arc<scc::HashMap<RepoRef, Arc<SyncHandle>>>,\n    tickets: Arc<Semaphore>,\n    pub(crate) queue: Arc<NotifyQueue>,\n\n    /// Report progress from indexing runs\n    pub(crate) progress: ProgressStream,\n}\n\n#[derive(Clone)]\npub struct BackgroundExecutor {\n    sender: flume::Sender<Task>,\n}\n\nimpl BackgroundExecutor {\n    fn start(config: Arc<Configuration>) -> Self {\n        let (sender, receiver) = flume::unbounded();\n\n        let tokio: Arc<_> = tokio::runtime::Builder::new_multi_thread()\n            .thread_name(\"background-jobs\")\n            .worker_threads(config.max_threads)\n            .max_blocking_threads(config.max_threads)\n            .enable_time()\n            .enable_io()\n            .build()\n            .unwrap()\n            .into();\n\n        let tokio_ref = tokio.clone();\n\n        // test can re-initialize the app, and we shouldn't fail\n        let rayon_pool = rayon::ThreadPoolBuilder::new()\n            .spawn_handler(move |thread| {\n                let tokio_ref = tokio_ref.clone();\n                let thread_priority = thread_priority::ThreadPriority::Max;\n\n                thread::Builder::new()\n                    .name(\"index-worker\".to_owned())\n                    .spawn_with_priority(thread_priority, move |_| {\n                        let _tokio = tokio_ref.enter();\n                        thread.run()\n                    })\n                    .map(|_| ())\n            })\n            .num_threads(config.max_threads)\n            .build()\n            .unwrap();\n\n        if RAYON_POOL.set(rayon_pool).is_err() {\n            panic!(\"rayon pool was already initialized!\");\n        }\n\n        thread::spawn(move || {\n            while let Ok(task) = receiver.recv() {\n                tokio.spawn(task);\n            }\n        });\n\n        Self { sender }\n    }\n\n    fn spawn<T>(&self, job: impl Future<Output = T> + Send + Sync + 'static) {\n        self.sender\n            .send(Box::pin(async move {\n                job.await;\n            }))\n            .unwrap();\n    }\n\n    #[allow(unused)]\n    pub async fn wait_for<T: Send + Sync + 'static>(\n        &self,\n        job: impl Future<Output = T> + Send + Sync + 'static,\n    ) -> T {\n        let (s, r) = flume::bounded(1);\n        self.spawn(async move { s.send_async(job.await).await.unwrap() });\n        r.recv_async().await.unwrap()\n    }\n}\n\nimpl SyncQueue {\n    pub fn start(config: Arc<Configuration>) -> Self {\n        let (progress, _) = tokio::sync::broadcast::channel(config.max_threads * 2);\n\n        let instance = Self {\n            tickets: Arc::new(Semaphore::new(config.max_threads)),\n            runner: BackgroundExecutor::start(config.clone()),\n            active: Default::default(),\n            queue: Default::default(),\n            progress,\n        };\n\n        {\n            let instance = instance.clone();\n\n            // We spawn the queue handler on the background executor\n            instance.runner.clone().spawn(async move {\n                while let (Ok(permit), next) = tokio::join!(\n                    instance.tickets.clone().acquire_owned(),\n                    instance\n                        .queue\n                        .pop_if(|h| !instance.active.contains(&h.reporef))\n                ) {\n                    let active = Arc::clone(&instance.active);\n                    match active\n                        .insert_async(next.reporef.clone(), next.clone())\n                        .await\n                    {\n                        Ok(_) => {\n                            tokio::task::spawn(async move {\n                                info!(?next.reporef, \"indexing\");\n\n                                let result = next.run(permit).await;\n                                _ = active.remove(&next.reporef);\n\n                                if result.is_ok() {\n                                    debug!(?result, \"sync finished\");\n                                } else {\n                                    error!(?result, \"sync failed\");\n                                }\n                            });\n                        }\n                        Err((_, next)) => {\n                            // this shouldn't happen, but we can handle it gracefully\n                            instance.queue.push(next).await\n                        }\n                    };\n                }\n            });\n        }\n\n        instance\n    }\n\n    pub fn broadcast(&self) -> tokio::sync::broadcast::Sender<Progress> {\n        self.progress.clone()\n    }\n\n    pub fn subscribe(&self) -> tokio::sync::broadcast::Receiver<Progress> {\n        self.progress.subscribe()\n    }\n\n    pub(crate) async fn read_queue(&self) -> Vec<QueuedRepoStatus> {\n        let mut output = vec![];\n        self.active\n            .scan_async(|_, handle| {\n                output.push(QueuedRepoStatus {\n                    reporef: handle.reporef.clone(),\n                    branch_filter: handle.filter_updates.branch_filter.clone(),\n                    state: QueueState::Active,\n                });\n            })\n            .await;\n\n        for handle in self.queue.get_list().await {\n            output.push(QueuedRepoStatus {\n                reporef: handle.reporef.clone(),\n                branch_filter: handle.filter_updates.branch_filter.clone(),\n                state: QueueState::Queued,\n            });\n        }\n\n        output\n    }\n}\n\n#[derive(serde::Serialize, Debug)]\npub(crate) struct QueuedRepoStatus {\n    reporef: RepoRef,\n    branch_filter: Option<BranchFilterConfig>,\n    state: QueueState,\n}\n\n#[derive(serde::Serialize, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub(crate) enum QueueState {\n    Active,\n    Queued,\n}\n\npub struct BoundSyncQueue(pub(crate) Application);\nimpl BoundSyncQueue {\n    /// Enqueue repo for syncing\n    pub(crate) async fn enqueue(self, config: SyncConfig) {\n        self.0\n            .sync_queue\n            .queue\n            .push(config.into_handle().await)\n            .await;\n    }\n\n    /// Enqueue repos for syncing with the current configuration.\n    ///\n    /// Skips any repositories in the list which are already queued or being synced.\n    /// Returns the number of new repositories queued for syncing.\n    pub(crate) async fn enqueue_all(self, repositories: Vec<RepoRef>) -> usize {\n        let Self(app) = &self;\n        let jobs = &app.sync_queue;\n\n        let mut num_queued = 0;\n        for reporef in repositories {\n            if jobs.queue.contains(&reporef).await || jobs.active.contains(&reporef) {\n                continue;\n            }\n\n            info!(%reporef, \"queueing for sync\");\n            jobs.queue\n                .push(SyncConfig::new(app, reporef).into_handle().await)\n                .await;\n            num_queued += 1;\n        }\n\n        num_queued\n    }\n\n    /// Block until the repository sync & index process is complete.\n    ///\n    /// Returns the new status.\n    pub(crate) async fn block_until_synced(self, reporef: RepoRef) -> anyhow::Result<SyncStatus> {\n        let Self(app) = &self;\n        let jobs = &app.sync_queue;\n\n        let handle = SyncConfig::new(app, reporef).into_handle().await;\n        let finished = handle.notify_done();\n\n        jobs.queue.push(handle).await;\n        Ok(finished.recv_async().await?)\n    }\n\n    pub(crate) async fn remove(self, reporef: RepoRef) -> Option<()> {\n        let Self(app) = &self;\n        let jobs = &app.sync_queue;\n\n        let active = jobs\n            .active\n            .update_async(&reporef, |_, v| {\n                v.pipes.remove();\n                v.set_status(|_| SyncStatus::Removed);\n            })\n            .await;\n\n        if active.is_none() {\n            // Re-queue to the front, so clean any currently queued refs\n            jobs.queue.remove(reporef.clone()).await;\n            app.repo_pool\n                .update_async(&reporef, |_k, v| v.mark_removed())\n                .await?;\n\n            jobs.queue\n                .push_front(SyncConfig::new(app, reporef).into_handle().await)\n                .await;\n        }\n\n        Some(())\n    }\n\n    pub(crate) async fn cancel(&self, reporef: RepoRef) {\n        self.0\n            .sync_queue\n            .active\n            .update_async(&reporef, |_, v| {\n                v.set_status(|_| SyncStatus::Cancelling);\n                v.pipes.cancel();\n            })\n            .await;\n    }\n\n    pub(crate) async fn startup_scan(self) -> anyhow::Result<()> {\n        let Self(Application { ref repo_pool, .. }) = self;\n\n        let mut repos = vec![];\n        repo_pool.scan_async(|k, _| repos.push(k.clone())).await;\n\n        self.enqueue_all(repos).await;\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/bin/bleep.rs",
    "content": "use anyhow::Result;\nuse bleep::{Application, Configuration, Environment};\n\n#[tokio::main]\nasync fn main() -> Result<()> {\n    let config = Configuration::cli_overriding_config_file()?;\n\n    _ = color_eyre::install();\n\n    Application::install_logging(&config);\n    let app = Application::initialize(Environment::server(), config).await?;\n\n    app.run().await\n}\n"
  },
  {
    "path": "server/bleep/src/cache.rs",
    "content": "use std::{\n    collections::HashSet,\n    ops::Deref,\n    sync::{Arc, RwLock},\n    time::Instant,\n};\n\nuse qdrant_client::qdrant::{PointId, PointStruct};\nuse rayon::prelude::ParallelIterator;\nuse scc::hash_map::Entry;\nuse sqlx::Sqlite;\nuse tracing::{error, info, trace, warn};\nuse uuid::Uuid;\n\nuse crate::{\n    repo::RepoRef,\n    semantic::{\n        embedder::{EmbedChunk, EmbedQueue},\n        Payload, Semantic,\n    },\n    state::RepositoryPool,\n};\n\nuse super::db::SqlDb;\n\n#[derive(serde::Serialize, serde::Deserialize, Eq)]\npub struct FreshValue<T> {\n    // default value is `false` on deserialize\n    pub(crate) fresh: bool,\n    pub(crate) value: T,\n}\n\nimpl<T: Default> FreshValue<T> {\n    fn fresh_default() -> Self {\n        Self {\n            fresh: true,\n            value: Default::default(),\n        }\n    }\n}\n\nimpl<T> PartialEq for FreshValue<T>\nwhere\n    T: PartialEq,\n{\n    fn eq(&self, other: &Self) -> bool {\n        self.value.eq(&other.value)\n    }\n}\n\nimpl<T> FreshValue<T> {\n    fn stale(value: T) -> Self {\n        Self {\n            fresh: false,\n            value,\n        }\n    }\n}\n\nimpl<T> From<T> for FreshValue<T> {\n    fn from(value: T) -> Self {\n        Self { fresh: true, value }\n    }\n}\n\n/// Snapshot of the current state of a FileCache\n/// Since it's atomically (as in ACID) read from SQLite, this will be\n/// representative at a single point in time\npub struct FileCacheSnapshot<'a> {\n    snapshot: Arc<scc::HashMap<CacheKeys, FreshValue<()>>>,\n    parent: &'a FileCache,\n    reporef: &'a RepoRef,\n}\n\n/// CacheKeys unifies the different keys to different databases.\n///\n/// Different layers of cache use different keys.\n///\n/// Tantivy keys are more specific. Since in Tantivy we can't update\n/// an existing record, all cache keys identify a record in the\n/// database universally (as in, in space & time).\n///\n/// In QDrant, however, it is possible to update existing records,\n/// therefore the cache key is less strong. We use the weaker key to\n/// identify existing, similar records, and update them with a\n/// refreshed property set.\n///\n/// For the specific calculation of what goes into these keys, take a\n/// look at\n/// [`Workload::cache_keys`][crate::indexes::file::Workload::cache_keys]\n#[derive(Clone, Debug, Hash, PartialEq, Eq)]\npub struct CacheKeys(String, String);\n\nimpl CacheKeys {\n    pub fn new(semantic: impl Into<String>, tantivy: impl Into<String>) -> Self {\n        Self(semantic.into(), tantivy.into())\n    }\n\n    pub fn tantivy(&self) -> &str {\n        &self.1\n    }\n\n    pub fn semantic(&self) -> &str {\n        &self.0\n    }\n}\n\nimpl<'a> FileCacheSnapshot<'a> {\n    pub(crate) fn parent(&'a self) -> &'a FileCache {\n        self.parent\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub(crate) fn is_fresh(&self, keys: &CacheKeys) -> bool {\n        match self.snapshot.entry(keys.clone()) {\n            Entry::Occupied(mut val) => {\n                val.get_mut().fresh = true;\n\n                trace!(\"cache hit\");\n                true\n            }\n            Entry::Vacant(val) => {\n                _ = val.insert_entry(FreshValue::fresh_default());\n\n                trace!(\"cache miss\");\n                false\n            }\n        }\n    }\n}\n\nimpl<'a> Deref for FileCacheSnapshot<'a> {\n    type Target = scc::HashMap<CacheKeys, FreshValue<()>>;\n\n    fn deref(&self) -> &Self::Target {\n        &self.snapshot\n    }\n}\n\n/// Manage the SQL cache for a repository, establishing a\n/// content-addressed space for files in it.\n///\n/// The cache keys are should be directly mirrored in Tantivy for each\n/// file entry, as Tantivy can't upsert content.\n///\n/// NB: consistency with Tantivy state is NOT ensured here.\npub struct FileCache {\n    db: SqlDb,\n    semantic: Semantic,\n    embed_queue: EmbedQueue,\n}\n\n#[derive(Default)]\npub struct InsertStats {\n    pub new: usize,\n    pub updated: usize,\n    pub deleted: usize,\n}\n\nimpl InsertStats {\n    fn empty() -> Self {\n        Self::default()\n    }\n}\n\nimpl<'a> FileCache {\n    pub(crate) fn new(db: SqlDb, semantic: Semantic) -> Self {\n        Self {\n            db,\n            semantic,\n            embed_queue: Default::default(),\n        }\n    }\n\n    pub(crate) async fn reset(&'a self, repo_pool: &RepositoryPool) -> anyhow::Result<()> {\n        let mut refs = vec![];\n        // knocking out our current file caches will force re-indexing qdrant\n        repo_pool.for_each(|reporef, repo| {\n            refs.push(reporef.to_owned());\n            repo.last_index_unix_secs = 0;\n        });\n\n        for reporef in refs {\n            self.delete(&reporef).await?;\n        }\n\n        Ok(())\n    }\n\n    /// Retrieve a file-level snapshot of the cache for the repository in scope.\n    pub(crate) async fn retrieve(&'a self, reporef: &'a RepoRef) -> FileCacheSnapshot<'a> {\n        let repo_str = reporef.to_string();\n        let rows = sqlx::query! {\n            \"SELECT cache_hash FROM file_cache \\\n             WHERE repo_ref = ?\",\n            repo_str,\n        }\n        .fetch_all(self.db.as_ref())\n        .await;\n\n        let output = scc::HashMap::default();\n        for row in rows.into_iter().flatten() {\n            let (semantic_hash, tantivy_hash) = row.cache_hash.split_at(64);\n            _ = output.insert(\n                CacheKeys::new(semantic_hash, tantivy_hash),\n                FreshValue::stale(()),\n            );\n        }\n\n        FileCacheSnapshot {\n            reporef,\n            parent: self,\n            snapshot: output.into(),\n        }\n    }\n\n    /// Synchronize the cache and DBs.\n    ///\n    /// `delete_tantivy` is a callback that takes a single key and\n    /// records the delete operation in a Tantivy writer.\n    ///\n    /// Semantic deletions are handled internally.\n    pub(crate) async fn synchronize(\n        &'a self,\n        cache: FileCacheSnapshot<'a>,\n        delete_tantivy: impl Fn(&str),\n    ) -> anyhow::Result<()> {\n        let mut tx = self.db.begin().await?;\n        self.delete_files(cache.reporef, &mut tx).await?;\n\n        let repo_str = cache.reporef.to_string();\n\n        // files that are no longer tracked by the git index are to be removed\n        // from the tantivy & qdrant indices\n        let qdrant_stale = {\n            let mut semantic_fresh = HashSet::new();\n            let mut semantic_all = HashSet::new();\n\n            cache.retain(|k, v| {\n                // check if it's already in to avoid unnecessary copies\n                if v.fresh && !semantic_fresh.contains(k.semantic()) {\n                    semantic_fresh.insert(k.semantic().to_string());\n                }\n\n                if !semantic_all.contains(k.semantic()) {\n                    semantic_all.insert(k.semantic().to_string());\n                }\n\n                // just call the passed closure for tantivy\n                if !v.fresh {\n                    delete_tantivy(k.tantivy())\n                }\n\n                v.fresh\n            });\n\n            semantic_all\n                .difference(&semantic_fresh)\n                .cloned()\n                .collect::<Vec<_>>()\n        };\n\n        // generate a transaction to push the remaining entries\n        // into the sql cache\n        {\n            let mut next = cache.first_occupied_entry_async().await;\n            while let Some(entry) = next {\n                let key = entry.key();\n                let hash = format!(\"{}{}\", key.0, key.1);\n                sqlx::query!(\n                    \"INSERT INTO file_cache \\\n                    (repo_ref, cache_hash) \\\n                    VALUES (?, ?)\",\n                    repo_str,\n                    hash,\n                )\n                .execute(&mut tx)\n                .await?;\n\n                next = entry.next();\n            }\n\n            tx.commit().await?;\n        }\n\n        // batch-delete points from qdrant index\n        if !qdrant_stale.is_empty() {\n            let semantic = self.semantic.clone();\n            tokio::spawn(async move {\n                semantic\n                    .delete_points_for_hash(&repo_str, qdrant_stale.into_iter())\n                    .await;\n            });\n        }\n\n        // make sure we generate & commit all remaining embeddings\n        self.batched_embed_or_flush_queue(true).await?;\n\n        Ok(())\n    }\n\n    /// Delete all caches for the repository in scope.\n    pub(crate) async fn delete(&self, reporef: &RepoRef) -> anyhow::Result<()> {\n        let mut tx = self.db.begin().await?;\n        self.delete_files(reporef, &mut tx).await?;\n        self.delete_chunks(reporef, &mut tx).await?;\n        tx.commit().await?;\n\n        Ok(())\n    }\n\n    /// Process the next chunk from the embedding queue if the batch size is met.\n    pub fn process_embedding_queue(&self) -> anyhow::Result<()> {\n        tokio::task::block_in_place(|| {\n            tokio::runtime::Handle::current()\n                .block_on(async { self.batched_embed_or_flush_queue(false).await })\n        })\n    }\n\n    /// Commit the embed log, invoking the embedder if batch size is met.\n    ///\n    /// If `flush == true`, drain the log, send the entire batch to\n    /// the embedder, and commit the results, disregarding the internal\n    /// batch sizing.\n    async fn batched_embed_or_flush_queue(&self, flush: bool) -> anyhow::Result<()> {\n        let new_points = self.embed_queued_points(flush).await?;\n\n        if !new_points.is_empty() {\n            if let Err(err) = self\n                .semantic\n                .qdrant_client()\n                .upsert_points(self.semantic.collection_name(), new_points, None)\n                .await\n            {\n                error!(?err, \"failed to write new points into qdrant\");\n            }\n        }\n        Ok(())\n    }\n\n    /// Empty the queue in batches, and generate embeddings using the\n    /// configured embedder\n    async fn embed_queued_points(&self, flush: bool) -> Result<Vec<PointStruct>, anyhow::Error> {\n        let batch_size = self.semantic.config.embedding_batch_size.get();\n        let log = &self.embed_queue;\n        let mut output = vec![];\n\n        loop {\n            // if we're not currently flushing the log, only process full batches\n            if log.is_empty() || (log.len() < batch_size && !flush) {\n                return Ok(output);\n            }\n\n            let mut batch = vec![];\n\n            // fill this batch with embeddings\n            while let Some(embedding) = log.pop() {\n                batch.push(embedding);\n\n                if batch.len() == batch_size {\n                    break;\n                }\n            }\n\n            let (elapsed, res) = {\n                let time = Instant::now();\n                let res = self\n                    .semantic\n                    .embedder()\n                    .batch_embed(batch.iter().map(|c| c.data.as_ref()).collect::<Vec<_>>())\n                    .await;\n\n                (time.elapsed(), res)\n            };\n\n            match res {\n                Ok(res) => {\n                    trace!(?elapsed, size = batch.len(), \"batch embedding successful\");\n                    output.extend(\n                        res.into_iter()\n                            .zip(batch)\n                            .map(|(embedding, src)| PointStruct {\n                                id: Some(PointId::from(src.id)),\n                                vectors: Some(embedding.into()),\n                                payload: src.payload,\n                            }),\n                    )\n                }\n                Err(err) => {\n                    error!(\n                        ?err,\n                        ?elapsed,\n                        size = batch.len(),\n                        \"remote batch embeddings failed\"\n                    )\n                }\n            }\n        }\n    }\n\n    /// Chunks and inserts the buffer content into the semantic db.\n    #[allow(clippy::too_many_arguments)]\n    pub(crate) async fn process_semantic(\n        &self,\n        cache_keys: &CacheKeys,\n        repo_name: &str,\n        repo_ref: &RepoRef,\n        relative_path: &str,\n        buffer: &str,\n        lang_str: &str,\n        branches: &[String],\n    ) -> InsertStats {\n        let chunk_cache = self.chunks_for_file(repo_ref, cache_keys).await;\n        self.semantic\n            .chunks_for_buffer(\n                cache_keys.semantic().into(),\n                repo_name,\n                &repo_ref.to_string(),\n                relative_path,\n                buffer,\n                lang_str,\n                branches,\n            )\n            .for_each(|(data, payload)| {\n                let cached = chunk_cache.update_or_embed(&data, payload);\n                if let Err(err) = cached {\n                    warn!(?err, %repo_name, %relative_path, \"embedding failed\");\n                }\n            });\n\n        match chunk_cache.commit().await {\n            Ok(stats) => {\n                info!(\n                    repo_name,\n                    relative_path, stats.new, stats.updated, stats.deleted, \"Successful commit\"\n                );\n                stats\n            }\n            Err(err) => {\n                warn!(repo_name, relative_path, ?err, \"Failed to upsert vectors\");\n                InsertStats::empty()\n            }\n        }\n    }\n\n    /// Delete all files in the `file_cache` table for the repository in scope.\n    async fn delete_files(\n        &self,\n        reporef: &RepoRef,\n        tx: &mut sqlx::Transaction<'_, Sqlite>,\n    ) -> anyhow::Result<()> {\n        let repo_str = reporef.to_string();\n        sqlx::query! {\n            \"DELETE FROM file_cache \\\n                 WHERE repo_ref = ?\",\n            repo_str\n        }\n        .execute(&mut *tx)\n        .await?;\n\n        Ok(())\n    }\n\n    /// Delete all chunks in the `chunk_cache` table for the repository in scope.\n    async fn delete_chunks(\n        &self,\n        reporef: &RepoRef,\n        tx: &mut sqlx::Transaction<'_, Sqlite>,\n    ) -> anyhow::Result<()> {\n        let repo_str = reporef.to_string();\n        sqlx::query! {\n            \"DELETE FROM chunk_cache \\\n                 WHERE repo_ref = ?\",\n            repo_str\n        }\n        .execute(&mut *tx)\n        .await?;\n\n        Ok(())\n    }\n\n    async fn chunks_for_file(&'a self, reporef: &'a RepoRef, key: &'a CacheKeys) -> ChunkCache<'a> {\n        ChunkCache::for_file(\n            &self.db,\n            &self.semantic,\n            reporef,\n            &self.embed_queue,\n            key.semantic(),\n        )\n        .await\n    }\n}\n\n/// Manage both the SQL cache and the underlying qdrant database to\n/// ensure consistency.\n///\n/// Operates on a single file's level.\npub struct ChunkCache<'a> {\n    sql: &'a SqlDb,\n    semantic: &'a Semantic,\n    reporef: &'a RepoRef,\n    file_cache_key: &'a str,\n    cache: scc::HashMap<String, FreshValue<String>>,\n    update: scc::HashMap<(Vec<String>, String), Vec<String>>,\n    new_sql: RwLock<Vec<(String, String)>>,\n    embed_queue: &'a EmbedQueue,\n}\n\nimpl<'a> ChunkCache<'a> {\n    async fn for_file(\n        sql: &'a SqlDb,\n        semantic: &'a Semantic,\n        reporef: &'a RepoRef,\n        embed_log: &'a EmbedQueue,\n        file_cache_key: &'a str,\n    ) -> ChunkCache<'a> {\n        let rows = sqlx::query! {\n            \"SELECT chunk_hash, branches FROM chunk_cache \\\n             WHERE file_hash = ?\",\n            file_cache_key,\n        }\n        .fetch_all(sql.as_ref())\n        .await;\n\n        let cache = scc::HashMap::<String, FreshValue<_>>::default();\n        for row in rows.into_iter().flatten() {\n            _ = cache.insert(row.chunk_hash, FreshValue::stale(row.branches));\n        }\n\n        Self {\n            sql,\n            semantic,\n            reporef,\n            file_cache_key,\n            cache,\n            embed_queue: embed_log,\n            update: Default::default(),\n            new_sql: Default::default(),\n        }\n    }\n\n    /// Update a cache entry with the details from `payload`, or create a new embedding.\n    ///\n    /// New insertions are queued, and stored on the repository-level\n    /// `FileCache` instance that created this.\n    fn update_or_embed(&self, data: &'a str, payload: Payload) -> anyhow::Result<()> {\n        let id = self.derive_chunk_uuid(data, &payload);\n        let branches_hash = blake3::hash(payload.branches.join(\"\\n\").as_ref()).to_string();\n\n        match self.cache.entry(id) {\n            Entry::Occupied(mut existing) => {\n                let key = existing.key();\n                trace!(?key, \"found; not upserting new\");\n                if existing.get().value != branches_hash {\n                    self.update\n                        .entry((payload.branches, branches_hash.clone()))\n                        .or_default()\n                        .get_mut()\n                        .push(existing.key().to_owned());\n                }\n                *existing.get_mut() = branches_hash.into();\n            }\n            Entry::Vacant(vacant) => {\n                let key = vacant.key();\n                trace!(?key, \"inserting new\");\n                self.new_sql\n                    .write()\n                    .unwrap()\n                    .push((vacant.key().to_owned(), branches_hash.clone()));\n\n                self.embed_queue.push(EmbedChunk {\n                    id: vacant.key().clone(),\n                    data: data.into(),\n                    payload: payload.into_qdrant(),\n                });\n\n                vacant.insert_entry(branches_hash.into());\n            }\n        }\n\n        Ok(())\n    }\n\n    /// Commit both qdrant and cache changes to the respective databases.\n    ///\n    /// The SQLite operations mirror qdrant changes 1:1, so any\n    /// discrepancy between the 2 should be minimized.\n    ///\n    /// In addition, the SQLite cache is committed only AFTER all\n    /// qdrant writes have successfully completed, meaning they're in\n    /// qdrant's pipelines.\n    ///\n    /// Since qdrant changes are pipelined on their end, data written\n    /// here is not necessarily available for querying when the\n    /// commit's completed.\n    pub async fn commit(self) -> anyhow::Result<InsertStats> {\n        let mut tx = self.sql.begin().await?;\n\n        let updated = self.commit_branch_updates(&mut tx).await?;\n        let deleted = self.commit_deletes(&mut tx).await?;\n        let new = self.commit_inserts(&mut tx).await?;\n\n        tx.commit().await?;\n\n        Ok(InsertStats {\n            new,\n            updated,\n            deleted,\n        })\n    }\n\n    /// Insert new additions to sqlite\n    ///\n    /// Note this step will update the cache before changes are\n    /// actually written to qdrant in batches.\n    ///\n    /// All qdrant operations are executed in batches through a call\n    /// to [`FileCache::commit_embed_log`].\n    async fn commit_inserts(\n        &self,\n        tx: &mut sqlx::Transaction<'_, Sqlite>,\n    ) -> Result<usize, anyhow::Error> {\n        let new_sql = std::mem::take(&mut *self.new_sql.write().unwrap());\n        let new_size = new_sql.len();\n\n        let repo_str = self.reporef.to_string();\n        for (p, branches) in new_sql {\n            sqlx::query! {\n                \"INSERT INTO chunk_cache (chunk_hash, file_hash, branches, repo_ref) \\\n                 VALUES (?, ?, ?, ?)\",\n                 p, self.file_cache_key, branches, repo_str\n            }\n            .execute(&mut *tx)\n            .await?;\n        }\n\n        Ok(new_size)\n    }\n\n    /// Delete points that have expired in the latest index.\n    async fn commit_deletes(\n        &self,\n        tx: &mut sqlx::Transaction<'_, Sqlite>,\n    ) -> Result<usize, anyhow::Error> {\n        let mut to_delete = vec![];\n        self.cache\n            .scan_async(|id, p| {\n                if !p.fresh {\n                    to_delete.push(id.to_owned());\n                }\n            })\n            .await;\n\n        let delete_size = to_delete.len();\n        for p in to_delete.iter() {\n            sqlx::query! {\n                \"DELETE FROM chunk_cache \\\n                 WHERE chunk_hash = ? AND file_hash = ?\",\n                p,\n                self.file_cache_key\n            }\n            .execute(&mut *tx)\n            .await?;\n        }\n\n        if !to_delete.is_empty() {\n            self.semantic\n                .qdrant_client()\n                .delete_points(\n                    self.semantic.collection_name(),\n                    &to_delete\n                        .into_iter()\n                        .map(PointId::from)\n                        .collect::<Vec<_>>()\n                        .into(),\n                    None,\n                )\n                .await?;\n        }\n        Ok(delete_size)\n    }\n\n    /// Update points where the list of branches in which they're\n    /// searchable has changed.\n    async fn commit_branch_updates(\n        &self,\n        tx: &mut sqlx::Transaction<'_, Sqlite>,\n    ) -> Result<usize, anyhow::Error> {\n        let mut update_size = 0;\n        let mut qdrant_updates = tokio::task::JoinSet::new();\n\n        let mut next = self.update.first_occupied_entry();\n        while let Some(entry) = next {\n            let (branches_list, branches_hash) = entry.key();\n            let points = entry.get();\n            update_size += points.len();\n\n            for p in entry.get() {\n                sqlx::query! {\n                    \"UPDATE chunk_cache SET branches = ? \\\n                     WHERE chunk_hash = ?\",\n                     branches_hash,\n                     p\n                }\n                .execute(&mut *tx)\n                .await?;\n            }\n\n            let id = points\n                .iter()\n                .cloned()\n                .map(PointId::from)\n                .collect::<Vec<_>>()\n                .into();\n\n            let payload = qdrant_client::client::Payload::new_from_hashmap(\n                [(\"branches\".to_string(), branches_list.to_owned().into())].into(),\n            );\n\n            let semantic = self.semantic.clone();\n            qdrant_updates.spawn(async move {\n                semantic\n                    .qdrant_client()\n                    .set_payload(semantic.collection_name(), &id, payload, None)\n                    .await\n            });\n            next = entry.next();\n        }\n\n        while let Some(success) = qdrant_updates.join_next().await {\n            _ = success?;\n        }\n\n        Ok(update_size)\n    }\n\n    /// Generate a content hash from the embedding data, and pin it to\n    /// the containing file's content id.\n    fn derive_chunk_uuid(&self, data: &str, payload: &Payload) -> String {\n        let id = {\n            let mut bytes = [0; 16];\n            let mut hasher = blake3::Hasher::new();\n            hasher.update(&payload.start_line.to_le_bytes());\n            hasher.update(&payload.end_line.to_le_bytes());\n            hasher.update(self.file_cache_key.as_bytes());\n            hasher.update(data.as_ref());\n            bytes.copy_from_slice(&hasher.finalize().as_bytes()[16..32]);\n            Uuid::from_bytes(bytes).to_string()\n        };\n        id\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/collector/bytes_filter.rs",
    "content": "// a version of tantivy::collector::FilterCollector that works on byte fast fields\n\nuse tantivy::collector::{Collector, SegmentCollector};\nuse tantivy::schema::Field;\nuse tantivy::{Score, SegmentReader, TantivyError};\n\npub struct BytesFilterCollector<TCollector, TPredicate>\nwhere\n    TPredicate: 'static + Clone,\n{\n    field: Field,\n    collector: TCollector,\n    predicate: TPredicate,\n}\n\nimpl<TCollector, TPredicate> BytesFilterCollector<TCollector, TPredicate>\nwhere\n    TCollector: Collector + Send + Sync,\n    TPredicate: Fn(&[u8]) -> bool + Send + Sync + Clone,\n{\n    /// Create a new BytesFilterCollector.\n    pub fn new(\n        field: Field,\n        predicate: TPredicate,\n        collector: TCollector,\n    ) -> BytesFilterCollector<TCollector, TPredicate> {\n        BytesFilterCollector {\n            field,\n            predicate,\n            collector,\n        }\n    }\n}\n\nimpl<TCollector, TPredicate> Collector for BytesFilterCollector<TCollector, TPredicate>\nwhere\n    TCollector: Collector + Send + Sync,\n    TPredicate: Fn(&[u8]) -> bool + Send + Sync + Clone,\n{\n    // That's the type of our result.\n    // Our standard deviation will be a float.\n    type Fruit = TCollector::Fruit;\n\n    type Child = BytesFilterSegmentCollector<TCollector::Child, TPredicate>;\n\n    fn for_segment(\n        &self,\n        segment_local_id: u32,\n        segment_reader: &SegmentReader,\n    ) -> tantivy::Result<BytesFilterSegmentCollector<TCollector::Child, TPredicate>> {\n        let schema = segment_reader.schema();\n        let field_entry = schema.get_field_entry(self.field);\n        if !field_entry.is_fast() {\n            return Err(TantivyError::SchemaError(format!(\n                \"Field {:?} is not a fast field.\",\n                field_entry.name()\n            )));\n        }\n\n        let field_name = schema.get_field_name(self.field);\n        let fast_field_reader = segment_reader.fast_fields().bytes(field_name)?.unwrap();\n\n        let segment_collector = self\n            .collector\n            .for_segment(segment_local_id, segment_reader)?;\n\n        Ok(BytesFilterSegmentCollector {\n            fast_field_reader,\n            segment_collector,\n            predicate: self.predicate.clone(),\n        })\n    }\n\n    fn requires_scoring(&self) -> bool {\n        self.collector.requires_scoring()\n    }\n\n    fn merge_fruits(\n        &self,\n        segment_fruits: Vec<<TCollector::Child as SegmentCollector>::Fruit>,\n    ) -> tantivy::Result<TCollector::Fruit> {\n        self.collector.merge_fruits(segment_fruits)\n    }\n}\n\npub struct BytesFilterSegmentCollector<TSegmentCollector, TPredicate>\nwhere\n    TPredicate: 'static,\n{\n    fast_field_reader: tantivy_columnar::BytesColumn,\n    segment_collector: TSegmentCollector,\n    predicate: TPredicate,\n}\n\nimpl<TSegmentCollector, TPredicate> SegmentCollector\n    for BytesFilterSegmentCollector<TSegmentCollector, TPredicate>\nwhere\n    TSegmentCollector: SegmentCollector,\n    TPredicate: 'static + Fn(&[u8]) -> bool + Send + Sync,\n{\n    type Fruit = TSegmentCollector::Fruit;\n\n    fn collect(&mut self, doc: u32, score: Score) {\n        let mut value = Vec::new();\n        self.fast_field_reader\n            .ords()\n            .values_for_doc(doc)\n            .for_each(|ord| {\n                self.fast_field_reader\n                    .ord_to_bytes(ord, &mut value)\n                    .unwrap();\n            });\n        if (self.predicate)(&value) {\n            self.segment_collector.collect(doc, score)\n        }\n    }\n\n    fn harvest(self) -> <TSegmentCollector as SegmentCollector>::Fruit {\n        self.segment_collector.harvest()\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/collector/frequency.rs",
    "content": "use std::collections::HashMap;\n\nuse tantivy::{\n    collector::{Collector, SegmentCollector},\n    schema::Field,\n    Score, SegmentReader,\n};\nuse tantivy_columnar::BytesColumn;\n\npub struct FrequencyCollector(pub Field);\n\nimpl Collector for FrequencyCollector {\n    type Fruit = HashMap<Vec<u8>, usize>;\n\n    type Child = FrequencySegmentCollector;\n\n    fn for_segment(\n        &self,\n        _segment_local_id: u32,\n        segment_reader: &SegmentReader,\n    ) -> tantivy::Result<FrequencySegmentCollector> {\n        let field_name = segment_reader.schema().get_field_name(self.0);\n        let reader = segment_reader.fast_fields().bytes(field_name)?.unwrap();\n        Ok(FrequencySegmentCollector {\n            reader,\n            freqs: HashMap::new(),\n        })\n    }\n\n    fn requires_scoring(&self) -> bool {\n        // this collector does not care about score.\n        false\n    }\n\n    fn merge_fruits(&self, segments: Vec<Self::Fruit>) -> tantivy::Result<Self::Fruit> {\n        let mut freqs = HashMap::new();\n        for segment in segments.into_iter() {\n            for (k, v) in segment {\n                freqs.entry(k).and_modify(|old| *old += v).or_insert(v);\n            }\n        }\n        Ok(freqs)\n    }\n}\n\npub struct FrequencySegmentCollector {\n    reader: BytesColumn,\n    freqs: HashMap<Vec<u8>, usize>,\n}\n\nimpl SegmentCollector for FrequencySegmentCollector {\n    type Fruit = HashMap<Vec<u8>, usize>;\n\n    fn collect(&mut self, doc: u32, _score: Score) {\n        let mut k = Vec::new();\n        self.reader.ords().values_for_doc(doc).for_each(|ord| {\n            self.reader.ord_to_bytes(ord, &mut k).unwrap();\n        });\n        self.freqs.entry(k).and_modify(|v| *v += 1).or_insert(1);\n    }\n\n    fn harvest(self) -> <Self as SegmentCollector>::Fruit {\n        self.freqs\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/collector/group.rs",
    "content": "use tantivy::{\n    collector::{Collector, SegmentCollector},\n    schema::Field,\n    DocAddress, Score, SegmentReader,\n};\n\nuse std::collections::HashMap;\n\n#[derive(Debug, Default)]\npub struct Group {\n    pub items: Vec<DocAddress>,\n}\n\n#[derive(Debug)]\npub struct Groups {\n    pub items: HashMap<blake3::Hash, Group>,\n}\n\nimpl Groups {\n    fn non_zero_count(self) -> Option<Self> {\n        if self.items.is_empty() {\n            None\n        } else {\n            Some(self)\n        }\n    }\n}\n\n/// Tantivy collector that groups search results by fastfield\npub struct GroupCollector {\n    // fast field to group these search results by\n    //\n    // currently, this fast field must be a bytes fast field,\n    // but this could be generic over any hashable fast field\n    field: Field,\n    // maximum number of items in each group\n    group_size: usize,\n    // maximum number of groups to return\n    limit: usize,\n}\n\nimpl GroupCollector {\n    pub fn with_field(field: Field) -> Self {\n        Self {\n            field,\n            group_size: 1,\n            limit: 100,\n        }\n    }\n\n    pub fn with_group_size(self, group_size: usize) -> Self {\n        Self { group_size, ..self }\n    }\n\n    pub fn with_limit(self, limit: usize) -> Self {\n        Self { limit, ..self }\n    }\n}\n\nimpl Collector for GroupCollector {\n    type Fruit = Option<Groups>;\n    type Child = GroupSegmentCollector;\n\n    fn for_segment(\n        &self,\n        segment_local_id: u32,\n        segment_reader: &SegmentReader,\n    ) -> tantivy::Result<GroupSegmentCollector> {\n        let field_name = segment_reader.schema().get_field_name(self.field);\n        let fast_field_reader = segment_reader.fast_fields().bytes(field_name)?.unwrap();\n        Ok(GroupSegmentCollector {\n            fast_field_reader,\n            segment_local_id,\n            group_size: self.group_size,\n            groups: Groups {\n                items: HashMap::new(),\n            },\n        })\n    }\n\n    fn requires_scoring(&self) -> bool {\n        // this collector does not care about score.\n        false\n    }\n\n    fn merge_fruits(&self, segment_groups: Vec<Option<Groups>>) -> tantivy::Result<Option<Groups>> {\n        let mut groups = Groups {\n            items: HashMap::new(),\n        };\n\n        for segment_group in segment_groups.into_iter().flatten() {\n            // merge segment_group into groups\n            let permitted_groups = self.limit.saturating_sub(groups.items.len());\n            for (k, v) in segment_group.items.into_iter().take(permitted_groups) {\n                groups\n                    .items\n                    .entry(k)\n                    .and_modify(|entries| {\n                        let permitted_items = self.group_size.saturating_sub(entries.items.len());\n                        entries.items.extend(v.items.iter().take(permitted_items));\n                    })\n                    .or_insert_with(|| v);\n            }\n        }\n        Ok(groups.non_zero_count())\n    }\n}\n\npub struct GroupSegmentCollector {\n    fast_field_reader: tantivy_columnar::BytesColumn,\n    segment_local_id: u32,\n    groups: Groups,\n    group_size: usize,\n}\n\nimpl SegmentCollector for GroupSegmentCollector {\n    type Fruit = Option<Groups>;\n\n    fn collect(&mut self, doc: u32, _score: Score) {\n        let mut value = Vec::new();\n        self.fast_field_reader\n            .ords()\n            .values_for_doc(doc)\n            .for_each(|ord| {\n                self.fast_field_reader\n                    .ord_to_bytes(ord, &mut value)\n                    .unwrap();\n            });\n        let hash = blake3::hash(&value);\n        let entry = self.groups.items.entry(hash).or_default();\n        if entry.items.len() < self.group_size {\n            entry\n                .items\n                .push(DocAddress::new(self.segment_local_id, doc))\n        }\n    }\n\n    fn harvest(self) -> <Self as SegmentCollector>::Fruit {\n        self.groups.non_zero_count()\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/collector.rs",
    "content": "mod bytes_filter;\nmod frequency;\nmod group;\n\npub use bytes_filter::BytesFilterCollector;\npub use frequency::FrequencyCollector;\npub use group::GroupCollector;\n"
  },
  {
    "path": "server/bleep/src/commits.rs",
    "content": "use std::collections::HashSet;\n\nuse anyhow::{bail, Context, Result};\nuse gix::{\n    bstr::ByteSlice,\n    diff::blob::{sink::Counter, Algorithm, UnifiedDiffBuilder},\n    object::{blob::diff::Platform, tree::diff::Action},\n    objs::tree::EntryMode,\n    Commit, Id,\n};\nuse serde::Serialize;\nuse tracing::{debug, error, trace};\n\nuse crate::{\n    llm::{self, client::api::Message},\n    repo::iterator::EXT_BLACKLIST,\n    repo::RepoRef,\n    state::RepositoryPool,\n};\n\nconst COMMIT_EXCLUDE_EXTENSIONS: [&str; 7] = [\"md\", \"txt\", \"json\", \"toml\", \"yml\", \"yaml\", \"rst\"];\n\n#[derive(Default, Debug)]\npub struct DiffStat {\n    modified_file_exts: HashSet<String>,\n    modified_file_paths: HashSet<String>,\n    num_file_insertions: usize,\n    num_file_deletions: usize,\n    num_line_insertions: u32,\n    num_line_deletions: u32,\n    commit_message: String,\n    diff: String,\n}\n\n#[derive(Serialize, Debug)]\npub struct Question {\n    pub question: String,\n    pub tag: String,\n}\n\n#[derive(Debug)]\nstruct NoneError;\n\nimpl std::fmt::Display for NoneError {\n    fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        unreachable!()\n    }\n}\n\nimpl std::error::Error for NoneError {\n    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {\n        None\n    }\n}\n\nstruct CommitIterator<'a> {\n    commit: Commit<'a>,\n    parent: Option<Id<'a>>,\n}\n\nimpl<'a> Iterator for CommitIterator<'a> {\n    type Item = DiffStat;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        let Some(parent_id) = self.parent else {\n            return None;\n        };\n\n        let parent_commit = parent_id.object().ok()?.into_commit();\n        let mut stats = DiffStat {\n            commit_message: self\n                .commit\n                .message_raw()\n                .unwrap()\n                .to_str_lossy()\n                .to_string(),\n            ..Default::default()\n        };\n\n        _ = self\n            .commit\n            .tree()\n            .unwrap()\n            .changes()\n            .unwrap()\n            .track_path()\n            .for_each_to_obtain_tree(&parent_commit.tree().unwrap(), |change| {\n                let ext = change\n                    .location\n                    .to_path_lossy()\n                    .extension()\n                    .map(|ext| ext.to_string_lossy().to_string());\n\n                if let Some(ext) = ext.clone() {\n                    if EXT_BLACKLIST.contains(&ext.as_str()) {\n                        debug!(\"Ignoring file with excluded extension: {}\", ext);\n                        return Ok::<Action, NoneError>(Action::Continue);\n                    }\n                }\n\n                if let Some(ext) = ext.clone() {\n                    stats.modified_file_exts.insert(ext);\n                }\n\n                let location = change.location.to_str_lossy();\n                stats.modified_file_paths.insert(location.to_string());\n\n                match &change.event {\n                    gix::object::tree::diff::change::Event::Addition {\n                        entry_mode: EntryMode::Blob,\n                        id,\n                    } => {\n                        stats.num_file_insertions += 1;\n                        add_diff(\n                            &location,\n                            &ext.as_deref(),\n                            \"\".into(),\n                            id.object().unwrap().data.as_bstr().to_str_lossy(),\n                            &mut stats,\n                        );\n                    }\n                    gix::object::tree::diff::change::Event::Deletion {\n                        entry_mode: EntryMode::Blob,\n                        id,\n                    } => {\n                        stats.num_file_deletions += 1;\n                        add_diff(\n                            &location,\n                            &ext.as_deref(),\n                            id.object().unwrap().data.as_bstr().to_str_lossy(),\n                            \"\".into(),\n                            &mut stats,\n                        );\n                    }\n                    gix::object::tree::diff::change::Event::Rewrite {\n                        source_id,\n                        id,\n                        entry_mode: EntryMode::Blob,\n                        ..\n                    } => {\n                        let platform = Platform::from_ids(source_id, id).unwrap();\n                        let old = platform.old.data.as_bstr().to_str_lossy();\n                        let new = platform.new.data.as_bstr().to_str_lossy();\n                        add_diff(&location, &ext.as_deref(), old, new, &mut stats);\n                    }\n                    gix::object::tree::diff::change::Event::Modification {\n                        previous_entry_mode,\n                        previous_id,\n                        entry_mode,\n                        id,\n                    } if matches!(previous_entry_mode, EntryMode::Blob)\n                        && matches!(entry_mode, EntryMode::Blob) =>\n                    {\n                        let platform = Platform::from_ids(previous_id, id).unwrap();\n                        let old = platform.old.data.as_bstr().to_str_lossy();\n                        let new = platform.new.data.as_bstr().to_str_lossy();\n                        add_diff(&location, &ext.as_deref(), old, new, &mut stats);\n                    }\n                    _ => {}\n                }\n\n                Ok::<Action, NoneError>(Action::Continue)\n            })\n            .unwrap();\n\n        self.commit = parent_commit;\n        self.parent = self.commit.parent_ids().next();\n\n        Some(stats)\n    }\n}\n\nfn add_diff(\n    location: &str,\n    extension: &Option<&str>,\n    old: std::borrow::Cow<'_, str>,\n    new: std::borrow::Cow<'_, str>,\n    stats: &mut DiffStat,\n) {\n    let input = gix::diff::blob::intern::InternedInput::new(old.as_ref(), new.as_ref());\n    stats.diff += &format!(\n        r#\"diff --git a/{location} b/{location}\n--- a/{location}\n+++ b/{location}\n\"#\n    );\n\n    let diff = gix::diff::blob::diff(\n        Algorithm::Histogram,\n        &input,\n        Counter::new(UnifiedDiffBuilder::new(&input)),\n    );\n\n    if let Some(ext) = extension {\n        if !COMMIT_EXCLUDE_EXTENSIONS.contains(ext) {\n            // Confusingly these are inverted\n            stats.num_line_insertions += &diff.removals;\n            stats.num_line_deletions += &diff.insertions;\n        }\n    }\n\n    stats.diff += diff.wrapped.as_str();\n    stats.diff += \"\\n\";\n}\n\npub async fn expand_commits_to_questions(\n    src_commits: Vec<DiffStat>,\n    llm_gateway: &llm::client::Client,\n) -> Result<Vec<Question>> {\n    const NUM_TUTORIAL_QUESTIONS: usize = 5;\n    const COMMIT_EXCLUDE_KEYWORDS: [&str; 7] = [\n        \"merge\", \"revert\", \"bump\", \"chore\", \"fix\", \"refactor\", \"docs\",\n    ];\n\n    let min_mod_files = 1usize;\n    let max_mod_files = 20usize;\n    let min_diff_lines = 100usize;\n\n    let mut questions = vec![];\n    let mut filtered_commits = src_commits\n        .into_iter()\n        .filter(|commit| {\n            // Skip commits where:\n            //  - the message contains one of the COMMIT_EXCLUDE_KEYWORDS\n            //  - all of the modified files are not in COMMIT_EXCLUDE_EXTENSIONS\n            //  - the number of modified files is less than min_mod_files\n            //  - the number of modified files is greater than max_mod_files\n            //  - the number of diff lines is less than min_diff_lines\n\n            let contains_exclude_keyword = COMMIT_EXCLUDE_KEYWORDS\n                .iter()\n                .any(|keyword| commit.commit_message.to_lowercase().contains(keyword));\n\n            let all_files_excluded = commit\n                .modified_file_exts\n                .iter()\n                .all(|ext| COMMIT_EXCLUDE_EXTENSIONS.contains(&ext.as_str()));\n\n            !all_files_excluded\n                && !contains_exclude_keyword\n                && commit.modified_file_paths.len() > min_mod_files\n                && commit.modified_file_paths.len() < max_mod_files\n                && commit.diff.lines().collect::<Vec<_>>().len() > min_diff_lines\n                && commit.num_line_insertions > commit.num_line_deletions * 2\n        })\n        .collect::<Vec<_>>();\n\n    // sort commits by max difference between insertions and deletions\n    filtered_commits.sort_by(|a, b| {\n        (a.num_line_insertions - a.num_line_deletions)\n            .cmp(&(b.num_line_insertions - b.num_line_deletions))\n    });\n\n    debug!(\"processing {:?} commits\", filtered_commits.len());\n    for commit in filtered_commits {\n        trace!(?commit.commit_message, \"generating questions\");\n        let result = generate_question(llm_gateway, commit).await;\n\n        match result {\n            Ok(Some(sug)) => questions.push(sug),\n            Err(err) => error!(?err, \"llm failure\"),\n            _ => {}\n        }\n\n        if questions.len() >= NUM_TUTORIAL_QUESTIONS {\n            return Ok(questions);\n        }\n    }\n\n    Ok(questions)\n}\n\npub fn latest_commits(\n    repo_pool: RepositoryPool,\n    repo_ref: RepoRef,\n    branch: Option<String>,\n) -> Result<Vec<DiffStat>> {\n    let repo = gix::open(\n        repo_pool\n            .read(&repo_ref, |_k, v| v.disk_path.clone())\n            .context(\"invalid git repo\")?,\n    )\n    .context(\"can't open git repo\")?;\n    let head = if let Some(branchref) = branch {\n        repo.find_reference(&branchref)\n            .context(\"invalid branch name\")?\n            .into_fully_peeled_id()\n            .context(\"git error\")?\n            .object()\n            .context(\"git error\")?\n            .into_commit()\n    } else {\n        repo.head()\n            .context(\"invalid branch name\")?\n            .into_peeled_id()\n            .context(\"git error\")?\n            .object()\n            .context(\"git error\")?\n            .into_commit()\n    };\n    let parent = head.parent_ids().next();\n    Ok(CommitIterator {\n        parent,\n        commit: head,\n    }\n    .take(100)\n    .collect::<Vec<_>>())\n}\n\nasync fn generate_question(\n    llm_gateway: &llm::client::Client,\n    commit: DiffStat,\n) -> Result<Option<Question>> {\n    let classification = classify_commit(llm_gateway, &commit)\n        .await\n        .context(\"classification failed\")\n        .unwrap_or_default();\n\n    if !classification {\n        return Ok(None);\n    }\n\n    let question = get_question(llm_gateway, &commit)\n        .await\n        .context(\"question failed\")?;\n\n    if question == \"0\" {\n        return Ok(None);\n    }\n\n    let tag = get_tag(llm_gateway, &question)\n        .await\n        .context(\"tag failed\")?;\n\n    Ok(Some(Question { question, tag }))\n}\n\nasync fn classify_commit(llm_gateway: &llm::client::Client, commit: &DiffStat) -> Result<bool> {\n    let bpe = tiktoken_rs::get_bpe_from_model(\"gpt-3.5-turbo-0613\").unwrap();\n    let raw_commit = format!(\"{}\\n\\n{}\", commit.commit_message, commit.diff);\n    let commit_msg = crate::agent::transcoder::limit_tokens(&raw_commit, bpe, 3000);\n\n    let response = llm_gateway\n        .clone()\n        .model(\"gpt-3.5-turbo-0613\")\n        .max_tokens(1)\n        .chat(\n            &[\n                Message::system(\n                    \"Your job is to classify whether a commit introduces new functionality or not.\n1. New functionality\n2. No new functionality\n\nOutput your classification as a number between 1 and 2. Output only a number.   \nExample output: 2\",\n                ),\n                Message::user(commit_msg),\n            ],\n            None,\n        )\n        .await\n        .context(\"llm error\")?;\n\n    let result: u8 = response.parse()?;\n    Ok(result == 1)\n}\n\nasync fn get_question(llm_gateway: &llm::client::Client, commit: &DiffStat) -> Result<String> {\n    let bpe = tiktoken_rs::get_bpe_from_model(\"gpt-4-0613\").unwrap();\n    let raw_commit = format!(\"{}\\n\\n{}\", commit.commit_message, commit.diff);\n    let commit = crate::agent::transcoder::limit_tokens(&raw_commit, bpe, 7000);\n\n    llm_gateway\n        .clone()\n        .model(\"gpt-4-0613\")\n        .max_tokens(64)\n        .chat(\n            &[\n                Message::system(\n                    r#\"While writing technical documentation for a project, you need to identify useful parts of the codebase that new developers should learn about.\nOutput a question that a new developer could answer from the information.\n\nFollow these rules at all times:\n- Start with How. For example: How are analytics events for mouseclicks tracked?\n- Phrase your question in the present tense\n- Do not refer to changes, improvements or fixes\n- Do not refer to classes, functions, files or variables\n- Refer only to core concepts\n- Examples of good questions:\n  - How does analytics work?\n  - How does the deployment script work?\n  - How does the authentication work?\n  \nIf you cannot write a good question, simply reply: 0\"#,\n                ),\n                Message::user(commit),\n            ],\n            None,\n        )\n        .await\n        .context(\"llm error\")\n}\n\nasync fn get_tag(llm_gateway: &llm::client::Client, question: &str) -> Result<String> {\n    let bpe = tiktoken_rs::get_bpe_from_model(\"gpt-3.5-turbo-0613\").unwrap();\n    let question = crate::agent::transcoder::limit_tokens(question, bpe, 7168);\n    llm_gateway\n        .clone()\n        .model(\"gpt-3.5-turbo-0613\")\n        .max_tokens(6)\n        .chat(\n            &[\n                Message::system(\n                    r#\"Your job is to create a one or two unhyphenated word tag for this question. The tag should easily identify the question and consist only of the noun.\n\nDO NOT include the words: feature, question, user, how to, implementation, functionality, support, mechanism.\nDO NOT hyphenate words.\n\nSome examples:\n\nUser: How does analytics work?\nAssistant: analytics\n\nUser: What time does the job run each day?\nAssistant: job time\n\nUser: How do we initialise the backend, before the frontend?\nAssistant: initialisation\"#,\n                ),\n                Message::user(question),\n            ],\n            None,\n        )\n        .await\n        .context(\"llm error\")\n}\n\npub async fn generate_tutorial_questions(\n    db: crate::db::SqlDb,\n    llm_gateway: Result<llm::client::Client>,\n    repo_pool: RepositoryPool,\n    reporef: RepoRef,\n) -> Result<()> {\n    let repo_str = reporef.to_string();\n    let rows = sqlx::query! {\n        \"SELECT * FROM tutorial_questions \\\n         WHERE repo_ref = ?\",\n        repo_str,\n    }\n    .fetch_all(db.as_ref())\n    .await?;\n\n    if !rows.is_empty() {\n        debug!(%reporef, \"skipping tutorial questions, already have some\");\n        return Ok(());\n    }\n\n    debug!(%reporef, \"generating tutorial questions\");\n    let Ok(llm_gateway) = llm_gateway else {\n        bail!(\"badly configured llm gw\");\n    };\n\n    // Due to `Send` issues on the gix side, we need to split this off quite brutally.\n    let latest_commits = {\n        let reporef = reporef.clone();\n        tokio::task::spawn_blocking(|| latest_commits(repo_pool, reporef, None))\n            .await\n            .context(\"threads error\")??\n    };\n\n    let questions = expand_commits_to_questions(latest_commits, &llm_gateway).await?;\n\n    debug!(%reporef, count=questions.len(), \"found questions\");\n    tracing::info!(\"{:?}\", &questions);\n\n    let mut tx = db.begin().await?;\n    for q in questions {\n        _ = sqlx::query!(\n            \"INSERT INTO tutorial_questions (question, tag, repo_ref) \\\n             VALUES (?, ?, ?)\",\n            q.question,\n            q.tag,\n            repo_str,\n        )\n        .execute(&mut tx)\n        .await?;\n    }\n\n    tx.commit().await?;\n\n    debug!(%reporef, \"questions committed\");\n    Ok(())\n}\n"
  },
  {
    "path": "server/bleep/src/config.rs",
    "content": "use crate::state::StateSource;\nuse anyhow::{Context, Result};\nuse clap::Parser;\n\nuse secrecy::{ExposeSecret, SecretString};\nuse serde::{Deserialize, Serialize, Serializer};\nuse std::{\n    num::NonZeroUsize,\n    path::{Path, PathBuf},\n};\n\n#[derive(Serialize, Deserialize, Parser, Debug, Clone)]\n#[clap(author, version, about, long_about = None)]\npub struct Configuration {\n    //\n    // Core configuration options\n    //\n    #[clap(short, long)]\n    #[serde(skip)]\n    /// If a config file is given, it will override _all_ command line parameters!\n    pub config_file: Option<PathBuf>,\n\n    #[clap(flatten)]\n    #[serde(default)]\n    pub source: StateSource,\n\n    #[clap(short, long, default_value_os_t = default_index_dir())]\n    #[serde(default = \"default_index_dir\")]\n    /// Directory to store all persistent state\n    pub index_dir: PathBuf,\n\n    #[clap(long, default_value_t = false)]\n    #[serde(skip)]\n    /// Quit after indexing the specified repos\n    pub index_only: bool,\n\n    #[clap(long, default_value_t = false)]\n    #[serde(default)]\n    /// Disable periodic reindexing, and `git pull` on remote repositories.\n    pub disable_background: bool,\n\n    #[clap(long, default_value_t = false)]\n    #[serde(default)]\n    /// Disable system-native notification backends to detect new git commits immediately.\n    pub disable_fsevents: bool,\n\n    #[clap(long, default_value_t = false)]\n    #[serde(default)]\n    /// Avoid writing logs to files.\n    ///\n    /// If this flag is not set to `true`, logs are written to <index_dir>/logs/bloop.log.YYYY-MM-DD-HH\n    pub disable_log_write: bool,\n\n    #[clap(short, long, default_value_t = default_buffer_size())]\n    #[serde(default = \"default_buffer_size\")]\n    /// Size of memory to use for file indexes\n    pub buffer_size: usize,\n\n    #[clap(short, long, default_value_t = default_repo_buffer_size())]\n    #[serde(default = \"default_repo_buffer_size\")]\n    /// Size of memory to use for repo indexes\n    pub repo_buffer_size: usize,\n\n    #[clap(short, long, default_value_t = default_parallelism())]\n    #[serde(default = \"default_parallelism\")]\n    /// Maximum number of parallel background threads\n    pub max_threads: usize,\n\n    #[clap(long, default_value_t = default_host())]\n    #[serde(default = \"default_host\")]\n    /// Bind the webserver to `<port>`\n    pub host: String,\n\n    #[clap(long, default_value_t = default_port())]\n    #[serde(default = \"default_port\")]\n    /// Bind the webserver to `<host>`\n    pub port: u16,\n\n    #[clap(long)]\n    #[serde(serialize_with = \"serialize_secret_opt_str\", default)]\n    /// OpenAI API key\n    pub openai_api_key: Option<SecretString>,\n\n    #[clap(long)]\n    #[serde(serialize_with = \"serialize_secret_opt_str\", default)]\n    /// Github Access Token\n    pub github_access_token: Option<SecretString>,\n\n    //\n    // External dependencies\n    //\n    #[clap(long)]\n    /// Path to dynamic libraries used in the app.\n    pub dylib_dir: Option<PathBuf>,\n\n    //\n    // Semantic values\n    //\n    #[clap(long, default_value_t = default_qdrant_url())]\n    #[serde(default = \"default_qdrant_url\")]\n    /// URL for the qdrant server\n    pub qdrant_url: String,\n\n    #[clap(long, default_value_os_t = default_model_dir())]\n    #[serde(default = \"default_model_dir\")]\n    /// Path to the embedding model directory\n    pub model_dir: PathBuf,\n\n    #[clap(long, default_value_t = default_max_chunk_tokens())]\n    #[serde(default = \"default_max_chunk_tokens\")]\n    /// Maximum number of tokens in a chunk (should be the model's input size)\n    pub max_chunk_tokens: usize,\n\n    #[clap(long, default_value_t = default_collection_name())]\n    #[serde(default = \"default_collection_name\")]\n    /// Qdrant collection name. Defaults to `documents`\n    pub collection_name: String,\n\n    #[clap(long, default_value_t = interactive_batch_size())]\n    #[serde(default = \"interactive_batch_size\")]\n    /// Batch size for batched embeddings\n    pub embedding_batch_size: NonZeroUsize,\n\n    /// Path to built front-end folder\n    #[clap(long)]\n    pub frontend_dist: Option<PathBuf>,\n}\n\nmacro_rules! right_if_default {\n    ($left:expr, $right:expr, $default:expr) => {\n        if $left == $default {\n            $right\n        } else {\n            $left\n        }\n    };\n}\n\nimpl Configuration {\n    pub fn read(file: impl AsRef<Path>) -> Result<Self> {\n        let file = std::fs::File::open(file)?;\n        Ok(serde_json::from_reader::<_, Self>(file)?)\n    }\n\n    pub fn from_cli() -> Result<Self> {\n        Ok(Self::try_parse()?)\n    }\n\n    pub fn index_path(&self, name: impl AsRef<Path>) -> impl AsRef<Path> {\n        self.index_dir.join(name)\n    }\n\n    pub fn cli_overriding_config_file() -> Result<Self> {\n        let cli = Self::from_cli()?;\n        let Ok(file) = cli\n            .config_file\n            .as_ref()\n            .context(\"no config file specified\")\n            .and_then(Self::read)\n        else {\n            return Ok(cli);\n        };\n\n        Ok(Self::merge(file, cli))\n    }\n\n    /// Merge 2 configurations with values from `b` taking precedence\n    ///\n    /// In case a default value is recognized in *either* sides,\n    /// always the non-default value will be used for the resulting\n    /// configuration.\n    pub fn merge(a: Self, b: Self) -> Self {\n        // the values here are in the order they're listed in the\n        // original `Configuration` declaration\n        Self {\n            config_file: b.config_file.or(a.config_file),\n\n            source: right_if_default!(b.source, a.source, Default::default()),\n\n            index_dir: right_if_default!(b.index_dir, a.index_dir, default_index_dir()),\n\n            index_only: b.index_only | a.index_only,\n\n            disable_background: b.disable_background | a.disable_background,\n\n            disable_fsevents: b.disable_fsevents | a.disable_fsevents,\n\n            disable_log_write: b.disable_log_write | a.disable_log_write,\n\n            buffer_size: right_if_default!(b.buffer_size, a.buffer_size, default_buffer_size()),\n\n            repo_buffer_size: right_if_default!(\n                b.repo_buffer_size,\n                a.repo_buffer_size,\n                default_repo_buffer_size()\n            ),\n\n            max_threads: right_if_default!(b.max_threads, a.max_threads, default_parallelism()),\n\n            host: right_if_default!(b.host, a.host, default_host()),\n\n            port: right_if_default!(b.port, a.port, default_port()),\n\n            openai_api_key: b.openai_api_key.or(a.openai_api_key),\n\n            github_access_token: b.github_access_token.or(a.github_access_token),\n\n            model_dir: right_if_default!(b.model_dir, a.model_dir, default_model_dir()),\n\n            max_chunk_tokens: right_if_default!(\n                b.max_chunk_tokens,\n                a.max_chunk_tokens,\n                default_max_chunk_tokens()\n            ),\n\n            collection_name: right_if_default!(\n                b.collection_name,\n                a.collection_name,\n                default_collection_name()\n            ),\n\n            embedding_batch_size: right_if_default!(\n                b.embedding_batch_size,\n                a.embedding_batch_size,\n                interactive_batch_size()\n            ),\n\n            frontend_dist: b.frontend_dist.or(a.frontend_dist),\n\n            qdrant_url: right_if_default!(b.qdrant_url, a.qdrant_url, String::new()),\n\n            dylib_dir: b.dylib_dir.or(a.dylib_dir),\n        }\n    }\n\n    /// Directory where logs are written to\n    pub fn log_dir(&self) -> PathBuf {\n        self.index_dir.join(\"logs\")\n    }\n}\n\npub fn serialize_secret_opt_str<S>(\n    opt_secstr: &Option<SecretString>,\n    ser: S,\n) -> Result<S::Ok, S::Error>\nwhere\n    S: Serializer,\n{\n    match opt_secstr {\n        Some(secstr) => ser.serialize_some(secstr.expose_secret()),\n        None => ser.serialize_none(),\n    }\n}\n\npub fn serialize_secret_str<S>(secstr: &SecretString, ser: S) -> Result<S::Ok, S::Error>\nwhere\n    S: Serializer,\n{\n    ser.serialize_str(secstr.expose_secret())\n}\n\n//\n// Configuration defaults\n//\nfn default_index_dir() -> PathBuf {\n    match directories::ProjectDirs::from(\"ai\", \"bloop\", \"bleep\") {\n        Some(dirs) => dirs.data_dir().to_owned(),\n        None => \"bloop_index\".into(),\n    }\n}\n\nfn default_model_dir() -> PathBuf {\n    \"model\".into()\n}\n\nfn default_collection_name() -> String {\n    \"documents\".into()\n}\n\npub fn default_parallelism() -> usize {\n    std::thread::available_parallelism().unwrap().get()\n}\n\npub const fn minimum_parallelism() -> usize {\n    1\n}\n\npub const fn default_buffer_size() -> usize {\n    500_000_000\n}\n\nconst fn default_repo_buffer_size() -> usize {\n    200_000_000\n}\n\nconst fn default_port() -> u16 {\n    7878\n}\n\nfn default_host() -> String {\n    String::from(\"127.0.0.1\")\n}\n\nfn default_qdrant_url() -> String {\n    String::from(\"http://127.0.0.1:6334\")\n}\n\nfn default_max_chunk_tokens() -> usize {\n    256\n}\n\nfn interactive_batch_size() -> NonZeroUsize {\n    let batch_size = if cfg!(feature = \"metal\") { 5 } else { 1 };\n    NonZeroUsize::new(batch_size).unwrap()\n}\n"
  },
  {
    "path": "server/bleep/src/db/query_log.rs",
    "content": "use chrono::{DateTime, Utc};\n\npub struct QueryLog<'a> {\n    db: &'a super::SqlitePool,\n}\n\nimpl<'a> QueryLog<'a> {\n    pub fn new(db: &'a super::SqlitePool) -> Self {\n        Self { db }\n    }\n\n    pub async fn insert(&self, raw: &str) -> anyhow::Result<()> {\n        sqlx::query!(\"INSERT INTO query_log (raw_query) VALUES (?)\", raw)\n            .execute(self.db)\n            .await?;\n\n        Ok(())\n    }\n\n    pub async fn since(&self, cutoff: DateTime<Utc>) -> anyhow::Result<Vec<String>> {\n        let recs = sqlx::query!(\n            \"SELECT raw_query FROM query_log \\\n\t\t      WHERE created_at > ?\",\n            cutoff\n        )\n        .fetch_all(self.db)\n        .await?;\n\n        Ok(recs.into_iter().map(|r| r.raw_query).collect())\n    }\n\n    pub async fn prune(&self, cutoff: DateTime<Utc>) -> anyhow::Result<()> {\n        sqlx::query!(\"DELETE FROM query_log WHERE created_at < ?\", cutoff)\n            .execute(self.db)\n            .await?;\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/db.rs",
    "content": "use std::{path::Path, sync::Arc};\n\nuse anyhow::{Context, Result};\nuse futures::TryFutureExt;\nuse sqlx::SqlitePool;\nuse tracing::{debug, error};\n\nuse crate::Configuration;\n\nmod query_log;\npub use query_log::QueryLog;\n\npub type SqlDb = Arc<SqlitePool>;\n\n#[tracing::instrument(skip_all)]\npub async fn initialize(config: &Configuration) -> Result<SqlitePool> {\n    let data_dir = config.index_dir.to_string_lossy();\n    let url = format!(\"sqlite://{data_dir}/bleep.db?mode=rwc\");\n\n    match connect(&url).await {\n        Ok(pool) => {\n            debug!(\"connected\");\n            Ok(pool)\n        }\n        Err(e) => {\n            error!(?e, \"error while migrating, recreating database...\");\n\n            reset(&data_dir)?;\n            debug!(\"reset complete\");\n\n            Ok(connect(&data_dir)\n                .await\n                .context(\"failed to recreate database\")?)\n        }\n    }\n}\n\n#[tracing::instrument()]\nasync fn connect(url: &str) -> Result<SqlitePool> {\n    let pool = SqlitePool::connect(url).await?;\n\n    if let Err(e) = sqlx::migrate!()\n        .run(&pool)\n        .map_err(anyhow::Error::from)\n        .and_then(|_| logical_migrations(&pool))\n        .await\n    {\n        // We manually close the pool here to ensure file handles are properly cleaned up on\n        // Windows.\n        pool.close().await;\n        Err(e)?\n    } else {\n        logical_migrations(&pool).await?;\n        Ok(pool)\n    }\n}\n\n#[tracing::instrument()]\nfn reset(data_dir: &str) -> Result<()> {\n    let db_path = Path::new(data_dir).join(\"bleep.db\");\n    let bk_path = db_path.with_extension(\"db.bk\");\n    std::fs::rename(db_path, bk_path).context(\"failed to backup old database\")\n}\n\nasync fn logical_migrations(db: &SqlitePool) -> Result<()> {\n    // A series of logically applied migrations.\n    project_migration(db).await\n}\n\nasync fn project_migration(db: &SqlitePool) -> Result<()> {\n    let applied =\n        sqlx::query! { \"SELECT applied FROM rust_migrations WHERE ref = 'project_migration'\" }\n            .fetch_one(db)\n            .await?\n            .applied;\n\n    if applied {\n        return Ok(());\n    }\n\n    let conversations = sqlx::query! { \"SELECT id, project_id, exchanges FROM conversations\" }\n        .fetch_all(db)\n        .await?;\n\n    for row in conversations {\n        // As part of this migration, we assume each conversation project only ever had 1 repo.\n        // It's not possible for there to be more than one after the accompanying SQL migration.\n        let project_repo = sqlx::query! {\n            \"SELECT repo_ref FROM project_repos WHERE project_id = ?\",\n            row.project_id,\n        }\n        .fetch_one(db)\n        .await?;\n\n        let mut exchanges = serde_json::from_str::<serde_json::Value>(&row.exchanges)\n            .context(\"did not find valid JSON in `exchanges`\")?;\n\n        fixup_exchange(&mut exchanges, &project_repo.repo_ref)\n            .context(\"`exchanges` was malformed\")?;\n\n        let exchanges_json = serde_json::to_string(&exchanges)?;\n\n        sqlx::query! {\n            \"UPDATE conversations SET exchanges = ? WHERE id = ?\",\n            exchanges_json,\n            row.id,\n        }\n        .execute(db)\n        .await?;\n    }\n\n    let studio_snapshots = sqlx::query! {\n        \"SELECT\n            context,\n            doc_context,\n            (SELECT project_id FROM studios WHERE studios.id = studio_snapshots.studio_id) AS project_id\n        FROM studio_snapshots\"\n    }\n    .fetch_all(db)\n    .await?;\n\n    for ss in studio_snapshots {\n        let context =\n            serde_json::from_str(&ss.context).context(\"did not find valid JSON in `context`\")?;\n\n        let doc_context = serde_json::from_str(&ss.doc_context)\n            .context(\"did not find valid JSON in `doc_context`\")?;\n\n        for repo_ref in studio_context_repos(&context).context(\"invalid studio `context` JSON\")? {\n            sqlx::query! {\n                \"INSERT INTO project_repos (project_id, repo_ref)\n                SELECT $1, $2\n                WHERE NOT EXISTS (\n                    SELECT 1 FROM project_repos WHERE project_id = $1 AND repo_ref = $2\n                )\",\n                ss.project_id,\n                repo_ref,\n            }\n            .execute(db)\n            .await?;\n        }\n\n        for doc_id in\n            studio_doc_context_doc_ids(&doc_context).context(\"invalid studio `doc_context` JSON\")?\n        {\n            sqlx::query! {\n                \"INSERT INTO project_docs (project_id, doc_id)\n                SELECT $1, $2\n                WHERE NOT EXISTS (\n                    SELECT 1 FROM project_docs WHERE project_id = $1 AND doc_id = $2\n                )\",\n                ss.project_id,\n                doc_id,\n            }\n            .execute(db)\n            .await?;\n        }\n    }\n\n    sqlx::query! { \"UPDATE rust_migrations SET applied = true WHERE ref = 'project_migration'\" }\n        .execute(db)\n        .await?;\n\n    Ok(())\n}\n\nfn fixup_exchange(exchanges: &mut serde_json::Value, repo_ref: &str) -> Option<()> {\n    for exchange in exchanges.as_array_mut()? {\n        let exchange = exchange.as_object_mut()?;\n\n        // First we fixup the top-level `paths` field.\n        for p in exchange.get_mut(\"paths\")?.as_array_mut()? {\n            let path = p.as_str()?.to_owned();\n            *p = serde_json::json!({\n                \"repo\": repo_ref,\n                \"path\": path,\n            });\n        }\n\n        // Then, we replace `CodeChunk::path` with `CodeChunk::repo_path`.\n        for cc in exchange.get_mut(\"code_chunks\")?.as_array_mut()? {\n            let cc = cc.as_object_mut()?;\n            let path = cc.remove(\"path\")?.as_str()?.to_owned();\n            cc.insert(\n                \"repo_path\".to_owned(),\n                serde_json::json!({\n                    \"repo\": repo_ref,\n                    \"path\": path,\n                }),\n            );\n        }\n\n        // Similarly, update `focused_chunk`, if it exists.\n        match exchange.get_mut(\"focused_chunk\") {\n            Some(serde_json::Value::Null) | None => {}\n            Some(serde_json::Value::Object(fc)) => {\n                let file_path = fc.remove(\"file_path\");\n\n                fc.insert(\n                    \"repo_path\".to_owned(),\n                    serde_json::json!({\n                        \"repo\": repo_ref,\n                        \"path\": file_path,\n                    }),\n                );\n            }\n            Some(_) => return None,\n        }\n\n        // Finally, we can update the search steps.\n        for step in exchange.get_mut(\"search_steps\")?.as_array_mut()? {\n            let step = step.as_object_mut()?;\n\n            if step.get(\"type\").and_then(|v| v.as_str()) == Some(\"proc\") {\n                let content = step.get_mut(\"content\")?.as_object_mut()?;\n\n                for p in content.get_mut(\"paths\")?.as_array_mut()? {\n                    let path = p.as_str()?.to_owned();\n                    *p = serde_json::json!({\n                        \"repo\": repo_ref,\n                        \"path\": path,\n                    });\n                }\n            }\n        }\n    }\n\n    Some(())\n}\n\nfn studio_context_repos(context: &serde_json::Value) -> Option<Vec<&str>> {\n    let mut repos = Vec::new();\n    for context_file in context.as_array()? {\n        repos.push(context_file.as_object()?.get(\"repo\")?.as_str()?);\n    }\n    Some(repos)\n}\n\nfn studio_doc_context_doc_ids(doc_context: &serde_json::Value) -> Option<Vec<i64>> {\n    let mut ids = Vec::new();\n    for file in doc_context.as_array()? {\n        ids.push(file.as_object()?.get(\"doc_id\")?.as_i64()?);\n    }\n    Some(ids)\n}\n"
  },
  {
    "path": "server/bleep/src/env.rs",
    "content": "use Feature::*;\n\n#[repr(u64)]\npub(crate) enum Feature {\n    /// Allow scanning any path on the system. This is dangerous!\n    AnyPathScan = 1 << 0,\n\n    /// Only allow scanning inside the directories in\n    /// `app.config.source_dir`\n    SafePathScan = 1 << 1,\n\n    /// Require authorization to access API endpoints\n    AuthorizationRequired = 1 << 2,\n\n    /// Allow GitHub Device flow. This is useful for typically local\n    /// installations\n    DesktopUserAuth = 1 << 3,\n\n    /// Use GitHub App permission system scoped to a single\n    /// installation. Cloud instances use this.\n    CloudUserAuth = 1 << 4,\n}\n\n#[rustfmt::skip]\n#[derive(Debug, Clone, Copy)]\n#[repr(u64)]\n/// Select the environment the service will run in.\n///\n/// The different variants represent distinct capability sets that are\n/// suited for different deployment model, and will enable or disable\n/// certain features.\nenum EnvironmentInner {\n    /// Safe API that's suitable for public use\n    Server =\n\tDesktopUserAuth as u64\n\t| SafePathScan as u64,\n\n    /// Use a GitHub App installation to manage repositories and user access.\n    ///\n    /// Running the server in this environment makes use of a GitHub App in order to list and fetch\n    /// repositories. Note that GitHub App installs for a user profile are not valid in this mode.\n    ///\n    /// Connecting properly to a GitHub App installation requires the following flags:\n    ///\n    /// - `--github-client-id`\n    /// - `--github-client-secret`\n    /// - `--github-app-id`\n    /// - `--github-app-private-key`\n    /// - `--github-app-install-id`\n    /// - `--instance-domain`\n    ///\n    /// Users are authenticated by checking whether they belong to the organization which installed\n    /// the GitHub App. All users belonging to the organization are able to see all repos that the\n    /// installation was allowed to access.\n    PrivateServer =\n\tCloudUserAuth as u64\n\t| AuthorizationRequired as u64,\n\n    /// Enables scanning arbitrary user-specified locations through a Web-endpoint.\n    InsecureLocal =\n\tAnyPathScan as u64\n\t| DesktopUserAuth as u64,\n}\n\n#[derive(Debug, Clone)]\npub struct Environment(EnvironmentInner);\n\nimpl Environment {\n    pub fn server() -> Self {\n        Self(EnvironmentInner::Server)\n    }\n\n    pub fn private_server() -> Self {\n        Self(EnvironmentInner::PrivateServer)\n    }\n\n    pub fn insecure_local() -> Self {\n        Self(EnvironmentInner::InsecureLocal)\n    }\n\n    pub(crate) fn allow(&self, f: Feature) -> bool {\n        0 < self.0 as u64 & f as u64\n    }\n\n    pub fn is_cloud_instance(&self) -> bool {\n        self.allow(CloudUserAuth)\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/indexes/analytics.rs",
    "content": "use tokio::sync::mpsc;\n\n#[derive(Default)]\npub struct WorkerStats {\n    // size in bytes\n    pub size: usize,\n    // number of qdrant chunkc\n    pub chunks: usize,\n    // number of dir-entries reindexed by this worker\n    pub reindex_count: usize,\n}\n\nimpl std::ops::AddAssign for WorkerStats {\n    fn add_assign(&mut self, rhs: Self) {\n        self.size += rhs.size;\n        self.chunks += rhs.chunks;\n        self.reindex_count += rhs.reindex_count;\n    }\n}\n\n// the main entrypoint into gathering analytics for an index job\npub struct StatsGatherer {\n    // reciever of stats from worker threads\n    stats_rx: mpsc::UnboundedReceiver<WorkerStats>,\n    // pass this along to each worker thread\n    stats_tx: mpsc::UnboundedSender<WorkerStats>,\n    // set to true if this is the first index of this reporef\n    pub is_first_index: bool,\n    // set to true if the index was reset on startup\n    pub was_index_reset: bool,\n    // combine stats from each worker thread into `repo_stats`\n    pub repo_stats: WorkerStats,\n}\n\nimpl StatsGatherer {\n    pub fn for_repo() -> Self {\n        let (stats_tx, stats_rx) = mpsc::unbounded_channel();\n        Self {\n            stats_rx,\n            stats_tx,\n            is_first_index: false,\n            was_index_reset: false,\n            repo_stats: WorkerStats::default(),\n        }\n    }\n\n    pub fn sender(&self) -> mpsc::UnboundedSender<WorkerStats> {\n        self.stats_tx.clone()\n    }\n\n    pub async fn finish(&mut self) {\n        // aggregate stats\n        self.stats_rx.close();\n        while let Some(stats) = self.stats_rx.recv().await {\n            self.repo_stats += stats;\n        }\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/indexes/doc.rs",
    "content": "use async_stream::{stream, try_stream};\nuse futures::stream::{Stream, StreamExt};\nuse rayon::prelude::*;\nuse tantivy::{\n    collector::TopDocs,\n    query::{BooleanQuery, Query, TermQuery},\n    schema::{Field, IndexRecordOption, Term},\n    tokenizer::NgramTokenizer,\n};\nuse thiserror::Error;\nuse tokio::sync::{Mutex, RwLock};\nuse tracing::{error, info, trace};\n\nuse crate::{\n    db::SqlDb,\n    indexes::schema,\n    query::compiler::{case_permutations, trigrams},\n    scraper::{self, Config, Scraper},\n};\n\nuse std::{collections::HashSet, sync::Arc};\n\n#[derive(Clone)]\npub struct Doc {\n    sql: SqlDb,\n    #[allow(unused)]\n    section_index: tantivy::Index,\n    section_schema: schema::Section,\n    index_writer: Arc<Mutex<tantivy::IndexWriter>>,\n    index_reader: Arc<tantivy::IndexReader>,\n    index_queue: Arc<RwLock<Vec<SyncHandle>>>,\n}\n\n// Wrapper around a tokio::task::JoinHandle containing metadata about the task. All task\n// progress is collected by a reciever in the SyncHandle.\nstruct SyncHandle {\n    id: i64,\n    url: String,\n    name: Option<String>,\n    favicon: Option<String>,\n    description: Option<String>,\n    status: &'static str,\n    join_handle: Option<tokio::task::JoinHandle<Result<(), Error>>>,\n    progress_stream: tokio::sync::watch::Receiver<Progress>,\n}\n\nstatic STATUS_DONE: &str = \"done\";\nstatic STATUS_INDEXING: &str = \"indexing\";\nstatic STATUS_ERROR: &str = \"error\";\n\n#[derive(serde::Serialize)]\npub struct SqlRecord {\n    pub id: i64,\n    pub url: String,\n    pub index_status: String,\n    pub name: Option<String>,\n    pub favicon: Option<String>,\n    pub description: Option<String>,\n    pub modified_at: chrono::NaiveDateTime,\n}\n\n#[derive(serde::Serialize, Clone)]\npub enum Progress {\n    Init(i64),\n    SetMetadata,\n    Err(String),\n    Update(Update),\n    Done(i64),\n}\n\n#[derive(serde::Serialize, Clone)]\npub struct Update {\n    url: url::Url,\n    discovered_count: usize,\n\n    #[serde(skip)]\n    metadata: scraper::Meta,\n}\n\n#[derive(Error, Debug)]\npub enum Error {\n    #[error(\"failed to initialize doc index: {0}\")]\n    Initialize(String),\n\n    #[error(\"no document with id: {0}\")]\n    InvalidDocId(i64),\n\n    #[error(\"not a valid docs url: `{0}`\")]\n    InvalidUrl(url::Url),\n\n    #[error(\"failed to parse `{0}` as a url: {1}\")]\n    UrlParse(String, url::ParseError),\n\n    #[error(\"failed to perform sql transaction: {0}\")]\n    Sql(#[from] sqlx::Error),\n\n    #[error(\"io error: {0}\")]\n    Io(#[from] std::io::Error),\n\n    #[error(\"tantivy error: {0}\")]\n    Tantivy(#[from] tantivy::error::TantivyError),\n\n    #[error(\"duplicate url: {0}\")]\n    DuplicateUrl(url::Url),\n\n    #[error(\"network error: {0}\")]\n    Network(#[from] reqwest::Error),\n\n    #[error(\"no docs found at url: {0}\")]\n    EmptyDocs(url::Url),\n}\n\nimpl Doc {\n    /// Initialize docs index\n    pub fn create(\n        sql: SqlDb,\n        path: &std::path::Path,\n        buffer_size: usize,\n        max_threads: usize,\n    ) -> Result<Self, Error> {\n        std::fs::create_dir_all(path)?;\n\n        let section_schema = schema::Section::new();\n        let mut section_index = tantivy::Index::open_or_create(\n            tantivy::directory::MmapDirectory::open(path)\n                .map_err(|e| Error::Initialize(e.to_string()))?, // todo: handle tantivy index upgrades here\n            section_schema.schema.clone(),\n        )\n        .map_err(|e| Error::Initialize(e.to_string()))?;\n        let index_writer = Arc::new(Mutex::new(\n            section_index.writer(buffer_size).map_err(Error::Tantivy)?,\n        ));\n        let index_reader = Arc::new(section_index.reader().map_err(Error::Tantivy)?);\n\n        section_index\n            .set_multithread_executor(max_threads)\n            .map_err(|e| Error::Initialize(e.to_string()))?;\n\n        section_index\n            .tokenizers()\n            .register(\"trigram\", NgramTokenizer::new(1, 3, false).unwrap());\n\n        Ok(Self {\n            sql,\n            section_index,\n            section_schema,\n            index_reader,\n            index_writer,\n            index_queue: Arc::new(RwLock::new(Vec::new())),\n        })\n    }\n\n    async fn set_title<'a, E>(&self, title: &str, id: i64, executor: &'a mut E) -> Result<(), Error>\n    where\n        &'a mut E: sqlx::Executor<'a, Database = sqlx::Sqlite>,\n    {\n        let mut index_queue = self.index_queue.write().await;\n        let sync_handle = index_queue\n            .iter_mut()\n            .find(|job| job.id == id)\n            .ok_or(Error::InvalidDocId(id))?;\n        sync_handle.name = Some(title.to_string());\n        sqlx::query! {\n            \"UPDATE docs SET name = ? WHERE id = ?\",\n            title,\n            id,\n        }\n        .execute(executor)\n        .await\n        .map(|_| ())\n        .map_err(Error::Sql)\n    }\n\n    async fn set_favicon<'a, E>(\n        &self,\n        favicon: &str,\n        id: i64,\n        executor: &'a mut E,\n    ) -> Result<(), Error>\n    where\n        &'a mut E: sqlx::Executor<'a, Database = sqlx::Sqlite>,\n    {\n        let mut index_queue = self.index_queue.write().await;\n        let sync_handle = index_queue\n            .iter_mut()\n            .find(|job| job.id == id)\n            .ok_or(Error::InvalidDocId(id))?;\n        sync_handle.favicon = Some(favicon.to_string());\n        sqlx::query! {\n            \"UPDATE docs SET favicon = ? WHERE id = ?\",\n            favicon,\n            id,\n        }\n        .execute(executor)\n        .await\n        .map(|_| ())\n        .map_err(Error::Sql)\n    }\n\n    async fn set_description<'a, E>(\n        &self,\n        description: &str,\n        id: i64,\n        executor: &'a mut E,\n    ) -> Result<(), Error>\n    where\n        &'a mut E: sqlx::Executor<'a, Database = sqlx::Sqlite>,\n    {\n        let mut index_queue = self.index_queue.write().await;\n        let sync_handle = index_queue\n            .iter_mut()\n            .find(|job| job.id == id)\n            .ok_or(Error::InvalidDocId(id))?;\n        sync_handle.description = Some(description.to_string());\n        sqlx::query! {\n            \"UPDATE docs SET description = ? WHERE id = ?\",\n            description,\n            id,\n        }\n        .execute(executor)\n        .await\n        .map(|_| ())\n        .map_err(Error::Sql)\n    }\n\n    async fn set_metadata<'a, E>(\n        &self,\n        metadata: &scraper::Meta,\n        id: i64,\n        doc_source: &url::Url,\n        executor: &'a mut E,\n    ) where\n        for<'t> &'t mut E: sqlx::Executor<'t, Database = sqlx::Sqlite>,\n    {\n        // set title\n        if let Some(title) = &metadata.title {\n            if let Err(e) = self.set_title(title, id, executor).await {\n                error!(%e, %title, %id, \"failed to set doc title\");\n            } else {\n                info!(%id, %title, \"doc title set\");\n            };\n        }\n\n        // set favicon\n        if let Some(favicon) = &metadata.icon {\n            let resolved_url = url::Url::parse(favicon)\n                .unwrap_or_else(|_| normalize_absolute_url(doc_source, favicon));\n            if let Err(e) = self.set_favicon(resolved_url.as_str(), id, executor).await {\n                error!(%e, %favicon, %id, \"failed to set doc icon\");\n            } else {\n                info!(%id, %favicon, \"doc icon set\");\n            };\n        }\n\n        // set description\n        if let Some(description) = &metadata.description {\n            if let Err(e) = self.set_description(description, id, executor).await {\n                error!(%e, %description, %id, \"failed to set doc description\");\n            } else {\n                info!(%id, %description, \"doc description set\");\n            };\n        }\n    }\n\n    async fn set_index_status<'a, E>(&self, status: &str, id: i64, executor: E) -> Result<(), Error>\n    where\n        E: sqlx::Executor<'a, Database = sqlx::Sqlite>,\n    {\n        let status = status.to_string();\n        sqlx::query! {\n            \"UPDATE docs SET index_status = ? WHERE id = ?\",\n            status,\n            id,\n        }\n        .execute(executor)\n        .await\n        .map(|_| ())\n        .map_err(Error::Sql)\n    }\n\n    /// Add a doc source to the index\n    ///\n    /// The sqlite DB stores metadata about the doc-provider, and the tantivy index stores\n    /// searchable page content.\n    pub async fn enqueue(self, url: url::Url) -> Result<i64, Error> {\n        // check if index writer is available\n\n        let mut transaction = self.sql.begin().await?;\n        let url_string = url.to_string();\n        let id = sqlx::query! {\n            \"INSERT INTO docs (url, index_status) VALUES (?, ?)\",\n            url_string,\n            STATUS_INDEXING,\n        }\n        .execute(&mut transaction)\n        .await?\n        .last_insert_rowid();\n\n        let (tx, rx) = tokio::sync::watch::channel(Progress::Init(id));\n        let self_ = self.clone();\n        let url_ = url.clone();\n        let mut lock = self.index_queue.write().await;\n        let sync_handle = SyncHandle {\n            id,\n            url: url.to_string(),\n            name: None,\n            favicon: None,\n            description: None,\n            status: STATUS_INDEXING,\n            join_handle: None,\n            progress_stream: rx,\n        };\n        lock.push(sync_handle);\n        lock.last_mut().unwrap().join_handle = Some(tokio::task::spawn(async move {\n            self_.begin_sync_job(id, url_, tx, transaction).await\n        }));\n\n        Ok(id)\n    }\n\n    async fn begin_sync_job(\n        self,\n        id: i64,\n        url: url::Url,\n        tx: tokio::sync::watch::Sender<Progress>,\n        mut transaction: sqlx::Transaction<'_, sqlx::Sqlite>,\n    ) -> Result<(), Error> {\n        let mut is_meta_set = false;\n        let mut stream = Box::pin(self.clone().insert_into_tantivy(id, url.clone()));\n        let mut discovered_count = 0;\n\n        while let Some(progress) = stream.next().await {\n            // populate metadata in sqlite\n            //\n            // the first scraped doc that contains metadata is used to populate\n            // metadata in sqlite - this is typically the base_url entered by the user,\n            // if the base_url does not contain any metadata, we move on the the second\n            // scraped url\n            if let Progress::Update(update) = progress.clone() {\n                discovered_count = update.discovered_count;\n                if update.url == url || (!update.metadata.is_empty() && !is_meta_set) {\n                    // do not set meta for this doc provider in subsequent turns\n                    is_meta_set = true;\n                    self.set_metadata(&update.metadata, id, &url, &mut transaction)\n                        .await;\n                    let _ = tx.send(Progress::SetMetadata);\n                };\n            }\n            let _ = tx.send(progress); // TODO: log err here\n        }\n\n        // scraped doc, but no pages\n        if discovered_count == 0 {\n            // delete sql entry\n            sqlx::query!(\"DELETE FROM docs WHERE id = ? RETURNING id\", id)\n                .fetch_optional(&mut transaction)\n                .await?\n                .ok_or(Error::InvalidDocId(id))?;\n            error!(doc_source = url.as_str(), %id, \"no docs found at url\");\n\n            let error = Error::EmptyDocs(url);\n\n            // send error down the stream\n            let _ = tx.send(Progress::Err(error.to_string()));\n\n            // send job status to error\n            if let Some(job) = self\n                .index_queue\n                .write()\n                .await\n                .iter_mut()\n                .find(|job| job.id == id)\n            {\n                job.status = STATUS_ERROR;\n            }\n\n            // return error\n            Err(error)?;\n        }\n\n        self.set_index_status(STATUS_DONE, id, &mut transaction)\n            .await?;\n        transaction.commit().await?;\n\n        // remove handle from queue\n        let mut lock = self.index_queue.write().await;\n        lock.retain(|handle| handle.id != id);\n        Ok(())\n    }\n\n    pub async fn status(self, id: i64) -> impl Stream<Item = Result<Progress, Error>> {\n        try_stream! {\n            let lock = self.index_queue.read().await;\n            let handle = lock.iter().find(|handle| handle.id == id);\n            match handle {\n                Some(h) => {\n                    let s = tokio_stream::wrappers::WatchStream::from_changes(h.progress_stream.clone());\n                    drop(lock);\n                    for await progress in s {\n                        yield progress;\n                    }\n                }\n                None => {\n                    let _ = self.list_one(id).await?;\n                    yield Progress::Done(id);\n                }\n            }\n        }\n    }\n\n    /// Cancel a running index job\n    pub async fn cancel(self, id: i64) -> Result<i64, Error> {\n        let lock = self.index_queue.read().await;\n        let handle = lock\n            .iter()\n            .find(|handle| handle.id == id)\n            .ok_or(Error::InvalidDocId(id))?;\n        handle\n            .join_handle\n            .as_ref()\n            .ok_or(Error::InvalidDocId(id))?\n            .abort();\n        drop(lock);\n\n        // remove handle from queue\n        let mut lock = self.index_queue.write().await;\n        lock.retain(|handle| handle.id != id);\n\n        // bring tantivy back to last saved state\n        //\n        // - if this is a sync job: this rolls back to before the sync started\n        // - if this is a resync job: this rolls back to before the old copy was deleted\n        match self.index_writer.lock().await.rollback() {\n            Ok(_) => info!(%id, \"successfully cancelled sync job, rolled back to old copy\"),\n            Err(e) => error!(%id, %e, \"tantivy rollback failed\"),\n        };\n\n        Ok(id)\n    }\n\n    /// Update documentation in the index - this will rescrape the entire website\n    ///\n    /// We may only resync ids that are persisted to the sql db, this ensures that resync cannot\n    /// be called on a currently syncing job\n    pub async fn resync(self, id: i64) -> Result<i64, Error> {\n        let record = sqlx::query!(\n            \"SELECT id, index_status, name, url, favicon, description, modified_at FROM docs WHERE id = ?\",\n            id\n        )\n        .fetch_one(&*self.sql)\n        .await\n        .map_err(|_| Error::InvalidDocId(id))?;\n\n        let url =\n            url::Url::parse(&record.url).map_err(|e| Error::UrlParse(record.url.clone(), e))?;\n\n        // delete old docs from tantivy\n        //\n        // create a checkpoint before deletion, so we can revert to here if the job is cancelled\n        let _ = self.index_writer.lock().await.commit();\n        self.index_writer\n            .lock()\n            .await\n            .delete_term(Term::from_field_i64(self.section_schema.doc_id, id));\n\n        // update modified time\n        sqlx::query! {\n            \"UPDATE docs SET modified_at = datetime('now') WHERE id = ?\",\n            id,\n        }\n        .execute(&*self.sql)\n        .await?;\n\n        // create a new sync job for this doc-id\n        let (tx, rx) = tokio::sync::watch::channel(Progress::Init(id));\n        let transaction = self.sql.begin().await?;\n        let self_ = self.clone();\n        let url_ = url.clone();\n\n        let mut lock = self.index_queue.write().await;\n        lock.push(SyncHandle {\n            id,\n            url: url.to_string(),\n            name: record.name.clone(),\n            favicon: record.favicon.clone(),\n            status: STATUS_INDEXING,\n            description: record.description.clone(),\n            join_handle: None,\n            progress_stream: rx,\n        });\n        lock.last_mut().unwrap().join_handle = Some(tokio::task::spawn(async move {\n            self_.begin_sync_job(id, url_, tx, transaction).await\n        }));\n\n        Ok(id)\n    }\n\n    /// Remove this doc source from tantivy and sqlite\n    pub async fn delete(&self, id: i64) -> Result<i64, Error> {\n        // delete entry from sql\n        let id = sqlx::query!(\"DELETE FROM docs WHERE id = ? RETURNING id\", id)\n            .fetch_optional(&*self.sql)\n            .await?\n            .ok_or(Error::InvalidDocId(id))?\n            .id;\n\n        // delete entry from tantivy\n        let mut index_writer = self.index_writer.lock().await;\n        index_writer.delete_term(Term::from_field_i64(self.section_schema.doc_id, id));\n        index_writer.commit()?;\n\n        Ok(id)\n    }\n\n    /// List all synced doc sources\n    pub async fn list(&self) -> Result<Vec<SqlRecord>, Error> {\n        // the sqlite db contains indexed and resyncing doc-ids\n        let mut indexed = sqlx::query!(\n            \"SELECT id, index_status, name, url, favicon, description, modified_at FROM docs\"\n        )\n        .fetch_all(&*self.sql)\n        .await?\n        .into_iter()\n        .map(|record| SqlRecord {\n            id: record.id,\n            name: record.name,\n            index_status: record.index_status,\n            description: record.description,\n            favicon: record.favicon,\n            url: record.url,\n            modified_at: record.modified_at,\n        })\n        .collect::<Vec<_>>();\n\n        // the index queue contains both sync and resync doc-ids\n        let index_queue = self.index_queue.read().await;\n        let syncing = index_queue\n            .iter()\n            .map(|job| SqlRecord {\n                id: job.id,\n                url: job.url.clone(),\n                name: job.name.clone(),\n                favicon: job.favicon.clone(),\n                description: job.description.clone(),\n                index_status: job.status.to_string(),\n                modified_at: chrono::Utc::now().naive_local(),\n            })\n            .collect::<Vec<_>>();\n\n        // if there is a resync job already, remove from list of indexed docs\n        indexed.retain(|x| !syncing.iter().any(|y| y.id == x.id));\n        indexed.extend(syncing);\n        indexed.sort_by_key(|record| record.id);\n\n        Ok(indexed)\n    }\n\n    /// List a doc source by id.\n    ///\n    /// This doc-source could be in the sync-queue, if it is syncing/resyncing,\n    /// or in the sqlite db if it has been indexed completely\n    pub async fn list_one(&self, id: i64) -> Result<SqlRecord, Error> {\n        let queued_item = self\n            .index_queue\n            .read()\n            .await\n            .iter()\n            .find(|job| job.id == id)\n            .map(|job| SqlRecord {\n                id: job.id,\n                url: job.url.clone(),\n                name: job.name.clone(),\n                favicon: job.favicon.clone(),\n                description: job.description.clone(),\n                index_status: job.status.to_string(),\n                modified_at: chrono::Utc::now().naive_local(),\n            });\n\n        let indexed_item = sqlx::query!(\n            \"SELECT id, index_status, name, url, favicon, description, modified_at FROM docs WHERE id = ?\",\n            id\n        )\n        .fetch_one(&*self.sql)\n        .await\n        .ok()\n        .map(|record| SqlRecord {\n            id: record.id,\n            name: record.name,\n            index_status: record.index_status,\n            description: record.description,\n            favicon: record.favicon,\n            url: record.url,\n            modified_at: record.modified_at,\n        });\n\n        queued_item.or(indexed_item).ok_or(Error::InvalidDocId(id))\n    }\n\n    /// Search for doc source by title\n    pub async fn search(&self, q: String, limit: usize) -> Result<Vec<SqlRecord>, Error> {\n        let limit = limit.to_string();\n        let q = format!(\"%{q}%\");\n        let records = sqlx::query!(\n            \"\n            SELECT id, name, url, description, favicon, modified_at, index_status\n            FROM docs \n            WHERE name LIKE $1 OR description LIKE $1 OR url LIKE $1\n            LIMIT ?\n            \",\n            q,\n            limit,\n        )\n        .fetch_all(&*self.sql)\n        .await?;\n\n        Ok(records\n            .into_iter()\n            .map(|r| SqlRecord {\n                id: r.id,\n                index_status: r.index_status,\n                name: r.name,\n                favicon: r.favicon,\n                description: r.description,\n                url: r.url,\n                modified_at: r.modified_at,\n            })\n            .collect())\n    }\n\n    /// Search for pages in a given doc source\n    pub async fn search_sections(\n        &self,\n        q: String,\n        limit: usize,\n        id: i64,\n    ) -> Result<Vec<Section>, Error> {\n        // use the tantivy index for section search\n        let reader = Arc::clone(&self.index_reader);\n        let searcher = reader.searcher();\n\n        let doc_id_query = Box::new(TermQuery::new(\n            Term::from_field_i64(self.section_schema.doc_id, id),\n            IndexRecordOption::Basic,\n        ));\n\n        let terms = q\n            .split(|c: char| c.is_whitespace() || \"./-{}[]()?-_\".contains(c))\n            .filter(|s| !s.is_empty())\n            .collect::<Vec<_>>();\n\n        // for each term, build up a trigram query\n        let trigram_queries = Box::new(BooleanQuery::union(\n            terms\n                .iter()\n                .map(|term| build_trigram_query(term, self.section_schema.text))\n                .collect::<Vec<_>>(),\n        )) as Box<dyn Query>;\n\n        let header_trigram_queries = Box::new(BooleanQuery::union(\n            terms\n                .iter()\n                .map(|term| build_trigram_query(term, self.section_schema.header))\n                .collect::<Vec<_>>(),\n        )) as Box<dyn Query>;\n\n        let matcher = fuzzy_matcher::skim::SkimMatcherV2::default();\n\n        let tantivy_results = searcher\n            .search(\n                &BooleanQuery::intersection(vec![\n                    // trigram_queries,\n                    Box::new(BooleanQuery::union(vec![\n                        header_trigram_queries,\n                        trigram_queries,\n                        // ancestry_trigram_queries,\n                        // rel_url_trigram_queries,\n                    ])) as Box<dyn Query>,\n                    doc_id_query as Box<dyn Query>,\n                ]),\n                &TopDocs::with_limit(1000),\n            )\n            .expect(\"failed to search index\");\n\n        let mut results = tantivy_results\n            .into_par_iter()\n            .filter_map(move |(_, addr)| {\n                let retrieved_doc = searcher\n                    .doc(addr)\n                    .expect(\"failed to get document by address\");\n                Section::from_tantivy_document(retrieved_doc, &self.section_schema)\n            })\n            .filter_map(|search_result| {\n                let term_distances =\n                    terms\n                        .iter()\n                        .fold(Vec::new(), |mut acc: Vec<(&&str, usize)>, x| {\n                            let next = if let Some((term, dist)) = acc.last() {\n                                (x, dist + term.len())\n                            } else {\n                                (x, 0)\n                            };\n                            acc.push(next);\n                            acc\n                        });\n\n                let dq = term_distances\n                    .windows(2)\n                    .map(|window| {\n                        let d1 = window[0].1;\n                        let d2 = window[1].1;\n                        d2.abs_diff(d1).pow(2)\n                    })\n                    .sum::<usize>();\n\n                let dt = |positions: &[Option<usize>], max: usize| {\n                    positions\n                        .windows(2)\n                        .map(|window| {\n                            let d1 = window[0].unwrap_or(max);\n                            let d2 = window[1].unwrap_or(max);\n                            d2.abs_diff(d1).pow(2)\n                        })\n                        .sum::<usize>()\n                };\n\n                // utility closure to calculate proximity penalty\n                let distance_penalty = |dq, dt, terms: &[&str]| {\n                    let total_len = terms.iter().map(|s| s.len()).sum::<usize>();\n                    if terms.is_empty() || dt == 0 {\n                        1.\n                    } else {\n                        (dt as f32 / dq as f32) * (total_len as f32)\n                    }\n                };\n\n                let (mut header_score, mut header_positions) = terms\n                    .iter()\n                    .map(|t| matcher.fuzzy(&search_result.header, t, true))\n                    .map(|t| {\n                        if let Some((s, pos)) = t {\n                            (s, pos.first().cloned())\n                        } else {\n                            (0, None)\n                        }\n                    })\n                    .fold((0, Vec::new()), |mut acc, (s, pos)| {\n                        acc.1.push(pos);\n                        (acc.0 + s, acc.1)\n                    });\n\n                header_positions.sort();\n                header_positions.reverse();\n                let header_hit_distance = dt(header_positions.as_slice(), 9999);\n\n                let header_distance_penalty = distance_penalty(dq, header_hit_distance, &terms);\n\n                // boost header score based on the \"level\" of this header, i.e., h1 gets a boost of\n                // 60, h2 gets a boost of 50, etc.\n                header_score += 10 * (6 - search_result.ancestry.len()) as i64;\n                header_score -= header_distance_penalty as i64;\n\n                let (mut text_score, mut text_positions) = terms\n                    .iter()\n                    .map(|t| matcher.fuzzy(&search_result.header, t, true))\n                    .map(|t| {\n                        if let Some((s, pos)) = t {\n                            (s, pos.first().cloned())\n                        } else {\n                            (0, None)\n                        }\n                    })\n                    .fold((0, Vec::new()), |mut acc, (s, pos)| {\n                        acc.1.push(pos);\n                        (acc.0 + s, acc.1)\n                    });\n\n                text_positions.sort();\n                text_positions.reverse();\n\n                let text_hit_distance = dt(text_positions.as_slice(), search_result.text.len());\n\n                let text_distance_penalty = distance_penalty(dq, text_hit_distance, &terms);\n\n                text_score -= text_distance_penalty as i64;\n\n                Some((\n                    search_result,\n                    text_score as f32 + (header_score as f32) * 2.0,\n                ))\n            })\n            .collect::<Vec<_>>();\n\n        results.par_sort_by(|(_, a_score), (_, b_score)| {\n            b_score\n                .partial_cmp(a_score)\n                .unwrap_or(std::cmp::Ordering::Less)\n        });\n\n        Ok(results\n            .into_iter()\n            .map(|(doc, _)| doc)\n            .take(limit)\n            .collect::<Vec<_>>())\n    }\n\n    pub async fn list_sections(&self, limit: usize, id: i64) -> Result<Vec<Section>, Error> {\n        let reader = Arc::clone(&self.index_reader);\n        let searcher = reader.searcher();\n        let doc_id_query = Box::new(TermQuery::new(\n            Term::from_field_i64(self.section_schema.doc_id, id),\n            IndexRecordOption::Basic,\n        )) as Box<dyn Query>;\n        let collector = TopDocs::with_limit(limit);\n        Ok(searcher\n            .search(&doc_id_query, &collector)?\n            .into_iter()\n            .map(|(_score, addr)| {\n                let retrieved_doc = searcher.doc(addr).expect(\"failed to fetch doc\");\n                Section::from_tantivy_document(retrieved_doc, &self.section_schema).unwrap()\n            })\n            .collect())\n    }\n\n    /// Scroll pages in a doc\n    pub async fn list_pages(&self, limit: usize, id: i64) -> Result<Vec<Page>, Error> {\n        let reader = Arc::clone(&self.index_reader);\n        let searcher = reader.searcher();\n        let doc_id_query = Box::new(TermQuery::new(\n            Term::from_field_i64(self.section_schema.doc_id, id),\n            IndexRecordOption::Basic,\n        )) as Box<dyn Query>;\n        let collector =\n            crate::collector::GroupCollector::with_field(self.section_schema.raw_relative_url)\n                .with_group_size(1)\n                .with_limit(limit);\n        Ok(searcher\n            .search(&doc_id_query, &collector)?\n            .into_iter()\n            .flat_map(|groups| groups.items.into_values())\n            .flat_map(|group| group.items.into_iter())\n            .filter_map(|addr| {\n                let retrieved_doc = searcher.doc(addr).expect(\"failed to fetch doc\");\n                Page::from_tantivy_document(retrieved_doc, &self.section_schema)\n            })\n            .collect())\n    }\n\n    /// Fetch all sections of a page, in order\n    pub async fn fetch<S: AsRef<str>>(\n        &self,\n        id: i64,\n        relative_url: S,\n    ) -> Result<Vec<Section>, Error> {\n        let reader = Arc::clone(&self.index_reader);\n        let searcher = reader.searcher();\n        let doc_id_query = Box::new(TermQuery::new(\n            Term::from_field_i64(self.section_schema.doc_id, id),\n            IndexRecordOption::Basic,\n        )) as Box<dyn Query>;\n        let relative_url_query = Box::new(TermQuery::new(\n            Term::from_field_text(self.section_schema.relative_url, relative_url.as_ref()),\n            IndexRecordOption::Basic,\n        )) as Box<dyn Query>;\n        let query = Box::new(BooleanQuery::intersection(vec![\n            doc_id_query,\n            relative_url_query,\n        ])) as Box<dyn Query>;\n        let collector = TopDocs::with_limit(9999);\n        let results = searcher.search(&query, &collector)?;\n\n        if results.is_empty() {\n            return Err(Error::InvalidDocId(id));\n        }\n\n        let mut results = results\n            .into_iter()\n            .filter_map(|(_score, addr)| {\n                let retrieved_doc = searcher.doc(addr).expect(\"failed to fetch doc\");\n                Section::from_tantivy_document(retrieved_doc, &self.section_schema)\n            })\n            .collect::<Vec<_>>();\n\n        results.sort_by_key(|s| s.section_range.start);\n\n        Ok(results)\n    }\n\n    pub fn contains_url(&self, url: &url::Url) -> bool {\n        let reader = Arc::clone(&self.index_reader);\n        let searcher = reader.searcher();\n        let query = Box::new(TermQuery::new(\n            Term::from_field_text(self.section_schema.absolute_url, url.as_str()),\n            IndexRecordOption::Basic,\n        )) as Box<dyn Query>;\n        let collector = TopDocs::with_limit(1);\n\n        let Ok(results) = searcher.search(&query, &collector) else {\n            return false;\n        };\n\n        !results.is_empty()\n    }\n\n    /// Scrape & insert a doc source into tantivy and return doc metadata if available\n    fn insert_into_tantivy(self, id: i64, doc_source: url::Url) -> impl Stream<Item = Progress> {\n        stream! {\n            let mut scraper = Scraper::with_config(Config::new(doc_source.clone()));\n            let mut stream = Box::pin(scraper.complete());\n            let mut handles = Vec::new();\n            let mut discovered_count = 0;\n            let point_ids = Arc::new(Mutex::new(HashSet::<uuid::Uuid>::new()));\n            while let Some(doc) = stream.next().await {\n                discovered_count += 1;\n                let progress = Progress::Update(Update {\n                    url: doc.url.clone(),\n                    discovered_count,\n                    metadata: doc.meta.clone(),\n                });\n                yield progress;\n                if doc.is_empty() {\n                    continue;\n                }\n                let doc_source = doc_source.clone();\n                let section_schema = self.section_schema.clone();\n                let index_writer = Arc::clone(&self.index_writer);\n                let cache = Arc::clone(&point_ids);\n                handles.push(tokio::task::spawn(async move {\n                    let (section_ids, tantivy_docs_to_insert) = doc.sections(id, &doc_source, &section_schema);\n                    let mut cache_lock = cache.lock().await;\n                    if !section_ids.iter().any(|u| cache_lock.contains(u)) {\n                        cache_lock.extend(section_ids.iter());\n                        let lock = index_writer.lock().await;\n                        for d in tantivy_docs_to_insert {\n                            let _ = lock.add_document(d);\n                        }\n                    }\n                }));\n            }\n            futures::future::join_all(handles).await;\n\n            trace!(%id, url = doc_source.as_str(), \"commiting doc-provider to index\");\n            match self.index_writer.lock().await.commit() {\n                Ok(_) => info!(%id, url = doc_source.as_str(), \"index complete\"),\n                Err(e) => error!(%id, url = doc_source.as_str(), %e, \"tantivy commit failed\"),\n            }\n\n            yield Progress::Done(id);\n        }\n    }\n\n    pub async fn verify(&self, url: url::Url) -> Result<reqwest::StatusCode, Error> {\n        if self.contains_url(&url) {\n            return Err(Error::DuplicateUrl(url));\n        }\n        reqwest::get(url)\n            .await\n            .map(|r| r.status())\n            .map_err(Error::Network)\n    }\n}\n\nimpl scraper::Document {\n    // break down this `Document` into sections and build `tantivy::Document` of out of each section\n    fn sections(\n        &self,\n        id: i64,\n        doc_source: &url::Url,\n        schema: &schema::Section,\n    ) -> (Vec<uuid::Uuid>, Vec<tantivy::Document>) {\n        info!(\n            url = %(self.url.as_str()),\n            doc_id = %id,\n            \"indexing doc\",\n        );\n        scraper::chunk::by_section(self.content.as_deref().unwrap_or_default()) // this is an infallible unwrap however\n            .into_par_iter()\n            .filter_map(|section| {\n                let point_id = {\n                    let mut bytes = [0; 16];\n                    let mut hasher = blake3::Hasher::new();\n                    hasher.update(&id.to_le_bytes()); // doc id\n                    hasher.update(self.meta.title.as_deref().unwrap_or(\"\").as_bytes()); // title of this page\n                    hasher.update(section.data.as_bytes()); // section data\n                    hasher.update(&section.section_range.start.byte.to_le_bytes()); // section start location\n                    hasher.update(&section.section_range.end.byte.to_le_bytes()); // section end location\n                    bytes.copy_from_slice(&hasher.finalize().as_bytes()[16..32]);\n                    uuid::Uuid::from_bytes(bytes)\n                };\n\n                let Some(relative_url) = self.relative_url(doc_source) else {\n                    error!(\n                        \"`{}` is not relative to `{}`\",\n                        self.url.as_str(),\n                        doc_source.as_str()\n                    );\n                    return None;\n                };\n\n                use tantivy::doc;\n                Some((point_id, doc!(\n                            schema.doc_id => id,\n                            schema.point_id => point_id.to_string().as_str(),\n                            schema.doc_source => doc_source.as_str(),\n                            schema.doc_title => self.meta.title.as_deref().unwrap_or_default(),\n                            schema.doc_description => self.meta.description.as_deref().unwrap_or_default(),\n                            schema.absolute_url => self.url.as_str(),\n                            schema.relative_url => relative_url.as_str(),\n                            schema.raw_relative_url => relative_url.as_bytes(),\n                            schema.header => section.header.unwrap_or_default(),\n                            schema.ancestry => section.ancestry_str().as_str(),\n                            schema.text => section.data,\n                            schema.start_byte => section.section_range.start.byte as u64,\n                            schema.end_byte => section.section_range.end.byte as u64,\n                            schema.section_depth => section.ancestry.len() as u64,\n                )))\n            })\n            .unzip()\n    }\n}\n\n#[derive(serde::Serialize)]\npub struct Section {\n    pub doc_id: i64,\n    pub doc_title: Option<String>,\n    pub point_id: uuid::Uuid,\n    pub doc_source: url::Url,\n    pub relative_url: String,\n    pub absolute_url: url::Url,\n    pub header: String,\n    pub ancestry: Vec<String>,\n    pub text: String,\n    pub section_range: std::ops::Range<usize>,\n}\n\nimpl Section {\n    pub fn from_tantivy_document(\n        doc: tantivy::schema::Document,\n        schema: &schema::Section,\n    ) -> Option<Self> {\n        Some(Section {\n            doc_id: doc.get_first(schema.doc_id)?.as_i64()?,\n            doc_title: doc\n                .get_first(schema.doc_title)?\n                .as_text()\n                .map(ToOwned::to_owned),\n            doc_source: doc\n                .get_first(schema.doc_source)?\n                .as_text()?\n                .parse::<url::Url>()\n                .unwrap(),\n            point_id: doc\n                .get_first(schema.point_id)?\n                .as_text()?\n                .parse::<uuid::Uuid>()\n                .unwrap(),\n            ancestry: doc.get_first(schema.ancestry)?.as_text().map(|t| {\n                scraper::chunk::Section::ancestry_from_str(t)\n                    .into_iter()\n                    .map(ToOwned::to_owned)\n                    .collect()\n            })?,\n            header: doc.get_first(schema.header)?.as_text()?.to_owned(),\n            relative_url: doc.get_first(schema.relative_url)?.as_text()?.to_owned(),\n            absolute_url: doc\n                .get_first(schema.absolute_url)?\n                .as_text()?\n                .parse::<url::Url>()\n                .unwrap(),\n            section_range: doc.get_first(schema.start_byte)?.as_u64()? as usize\n                ..doc.get_first(schema.end_byte)?.as_u64()? as usize,\n            text: doc.get_first(schema.text)?.as_text()?.to_owned(),\n        })\n    }\n}\n\n#[derive(serde::Serialize)]\npub struct Page {\n    pub doc_id: i64,\n    pub doc_source: url::Url,\n    pub doc_title: Option<String>,\n    pub doc_description: Option<String>,\n    pub doc_favicon: Option<String>,\n    pub relative_url: String,\n    pub absolute_url: url::Url,\n}\n\nimpl Page {\n    pub fn from_tantivy_document(\n        doc: tantivy::schema::Document,\n        schema: &schema::Section,\n    ) -> Option<Self> {\n        Some(Page {\n            doc_id: doc.get_first(schema.doc_id)?.as_i64()?,\n            doc_title: doc\n                .get_first(schema.doc_title)?\n                .as_text()\n                .map(ToOwned::to_owned),\n            doc_description: doc\n                .get_first(schema.doc_title)?\n                .as_text()\n                .map(ToOwned::to_owned),\n            doc_favicon: doc\n                .get_first(schema.doc_title)?\n                .as_text()\n                .map(ToOwned::to_owned),\n            doc_source: doc\n                .get_first(schema.doc_source)?\n                .as_text()?\n                .parse::<url::Url>()\n                .unwrap(),\n            relative_url: doc.get_first(schema.relative_url)?.as_text()?.to_owned(),\n            absolute_url: doc\n                .get_first(schema.absolute_url)?\n                .as_text()?\n                .parse::<url::Url>()\n                .unwrap(),\n        })\n    }\n}\n\nfn normalize_absolute_url(base_url: &url::Url, absolute_url: &str) -> url::Url {\n    let mut root = base_url.clone();\n    root.set_path(absolute_url);\n    root\n}\n\nfn build_trigram_query(query: &str, field: Field) -> Box<dyn Query> {\n    Box::new(BooleanQuery::union(\n        case_permutations(query)\n            .map(|q| {\n                BooleanQuery::intersection(\n                    trigrams(q.as_str())\n                        .map(|s| Term::from_field_text(field, s.as_str()))\n                        .map(|q| TermQuery::new(q, IndexRecordOption::Basic))\n                        .map(Box::new)\n                        .map(|q| q as Box<dyn Query>)\n                        .collect::<Vec<_>>(),\n                )\n            })\n            .map(Box::new)\n            .map(|q| q as Box<dyn Query>)\n            .collect::<Vec<_>>(),\n    ))\n}\n"
  },
  {
    "path": "server/bleep/src/indexes/file.rs",
    "content": "use std::{\n    collections::HashSet,\n    panic::AssertUnwindSafe,\n    path::{Path, PathBuf},\n    sync::atomic::{AtomicU64, Ordering},\n};\n\nuse anyhow::{bail, Result};\nuse async_trait::async_trait;\nuse rayon::prelude::*;\nuse tantivy::{\n    collector::TopDocs,\n    doc,\n    query::{BooleanQuery, Query, QueryParser, TermQuery},\n    schema::{IndexRecordOption, Schema, Term},\n    IndexWriter,\n};\nuse tokenizers as _;\nuse tokio::runtime::Handle;\nuse tracing::{error, info, trace, warn};\n\npub use super::{\n    analytics::{StatsGatherer, WorkerStats},\n    schema::File,\n};\n\n#[cfg(feature = \"debug\")]\nuse std::time::Instant;\n\nuse super::{\n    reader::{ContentDocument, ContentReader, FileDocument, FileReader},\n    DocumentRead, Indexable, Indexer,\n};\nuse crate::{\n    background::SyncHandle,\n    cache::{CacheKeys, FileCache, FileCacheSnapshot},\n    intelligence::TreeSitterFile,\n    query::compiler::{case_permutations, trigrams},\n    repo::{iterator::*, RepoMetadata, RepoRef, Repository},\n    symbol::SymbolLocations,\n};\n\nstruct Workload<'a> {\n    cache: &'a FileCacheSnapshot<'a>,\n    file_filter: &'a FileFilter,\n    repo_ref: &'a RepoRef,\n    repo_disk_path: &'a Path,\n    repo_name: &'a str,\n    repo_metadata: &'a RepoMetadata,\n    relative_path: PathBuf,\n    normalized_path: PathBuf,\n    stats_tx: tokio::sync::mpsc::UnboundedSender<WorkerStats>,\n}\n\nimpl<'a> Workload<'a> {\n    fn cache_keys(&self, dir_entry: &RepoDirEntry) -> CacheKeys {\n        let semantic_hash = {\n            let mut hash = blake3::Hasher::new();\n            hash.update(crate::state::SCHEMA_VERSION.as_bytes());\n            hash.update(self.relative_path.to_string_lossy().as_ref().as_ref());\n            hash.update(self.repo_ref.to_string().as_bytes());\n            hash.update(dir_entry.buffer().unwrap_or_default().as_bytes());\n            hash.update(\n                self.file_filter\n                    .is_allowed(&self.relative_path)\n                    .map(|include| {\n                        if include {\n                            &b\"__filter_override_include\"[..]\n                        } else {\n                            &b\"__filter_override_exclude\"[..]\n                        }\n                    })\n                    .unwrap_or(&b\"__no_filter_override\"[..]),\n            );\n            hash.finalize().to_hex().to_string()\n        };\n\n        let tantivy_hash = {\n            let branch_list = dir_entry.branches();\n            let mut hash = blake3::Hasher::new();\n            hash.update(semantic_hash.as_ref());\n            hash.update(branch_list.join(\"\\n\").as_bytes());\n            hash.finalize().to_hex().to_string()\n        };\n\n        CacheKeys::new(semantic_hash, tantivy_hash)\n    }\n\n    fn transmit_stats(&self, stats: WorkerStats) {\n        if let Err(e) = self.stats_tx.send(stats) {\n            warn!(\"failed to transmit worker stats: {e}\");\n        }\n    }\n}\n\n#[async_trait]\nimpl Indexable for File {\n    async fn index_repository(\n        &self,\n        SyncHandle {\n            ref reporef,\n            ref file_cache,\n            ref pipes,\n            ref app,\n            ..\n        }: &SyncHandle,\n        repo: &Repository,\n        repo_metadata: &RepoMetadata,\n        writer: &IndexWriter,\n    ) -> Result<()> {\n        let file_filter = FileFilter::compile(&repo.file_filter)?;\n        let cache = file_cache.retrieve(reporef).await;\n        let repo_name = reporef.indexed_name();\n        let processed = &AtomicU64::new(0);\n        let mut stats_gatherer = StatsGatherer::for_repo();\n        stats_gatherer.is_first_index = cache.is_empty();\n        stats_gatherer.was_index_reset = app.indexes.was_index_reset;\n\n        let worker_stats_tx = stats_gatherer.sender();\n        let file_worker = |count: usize| {\n            let cache = &cache;\n            let callback = move |dir_entry: RepoDirEntry| {\n                let completed = processed.fetch_add(1, Ordering::Relaxed);\n                pipes.index_percent(((completed as f32 / count as f32) * 100f32) as u8);\n\n                let worker_stats_tx = worker_stats_tx.clone();\n                let entry_disk_path = dir_entry.path().to_owned();\n                let relative_path = {\n                    let entry_srcpath = PathBuf::from(&entry_disk_path);\n                    entry_srcpath\n                        .strip_prefix(&repo.disk_path)\n                        .map(ToOwned::to_owned)\n                        .unwrap_or(entry_srcpath)\n                };\n                let normalized_path = repo.disk_path.join(&relative_path);\n\n                let workload = Workload {\n                    repo_disk_path: &repo.disk_path,\n                    repo_name: &repo_name,\n                    file_filter: &file_filter,\n                    repo_ref: reporef,\n                    relative_path,\n                    normalized_path,\n                    repo_metadata,\n                    cache,\n                    stats_tx: worker_stats_tx,\n                };\n\n                trace!(entry_disk_path, \"queueing entry\");\n\n                if let Err(err) = self.worker(dir_entry, workload, writer) {\n                    warn!(%err, entry_disk_path, \"indexing failed; skipping\");\n                }\n\n                if let Err(err) = cache.parent().process_embedding_queue() {\n                    warn!(?err, \"failed to commit embeddings\");\n                }\n            };\n\n            move |dir_entry: RepoDirEntry| {\n                let result = std::panic::catch_unwind(AssertUnwindSafe(|| (callback)(dir_entry)));\n                if let Err(err) = result {\n                    error!(\n                        ?err,\n                        \"Indexing crashed. This is bad. Please send these logs to support!\"\n                    );\n                }\n            }\n        };\n\n        let start = std::time::Instant::now();\n\n        if reporef.is_remote() {\n            let walker = GitWalker::open_repository(\n                reporef,\n                &repo.disk_path,\n                repo.branch_filter.as_ref().map(Into::into),\n            )?;\n            let count = walker.len();\n            walker.for_each(pipes, file_worker(count));\n        } else {\n            let branch = gix::open::Options::isolated()\n                .filter_config_section(|_| false)\n                .open(&repo.disk_path)\n                .ok()\n                .and_then(|r| {\n                    r.to_thread_local()\n                        .head()\n                        .ok()?\n                        .try_into_referent()\n                        .map(|r| {\n                            use gix::bstr::ByteSlice;\n                            r.name().shorten().to_str_lossy().into_owned()\n                        })\n                })\n                .unwrap_or_else(|| \"HEAD\".to_owned());\n\n            let walker = FileWalker::index_directory(&repo.disk_path, branch);\n            let count = walker.len();\n            walker.for_each(pipes, file_worker(count));\n        };\n\n        if pipes.is_cancelled() {\n            bail!(\"cancelled\");\n        }\n\n        info!(?repo.disk_path, \"repo file indexing finished, took {:?}\", start.elapsed());\n\n        stats_gatherer.finish().await;\n\n        file_cache\n            .synchronize(cache, |key| {\n                writer.delete_term(Term::from_field_text(self.unique_hash, key));\n            })\n            .await?;\n\n        pipes.index_percent(100);\n        Ok(())\n    }\n\n    fn delete_by_repo(&self, writer: &IndexWriter, repo: &Repository) {\n        writer.delete_term(Term::from_field_text(\n            self.repo_disk_path,\n            &repo.disk_path.to_string_lossy(),\n        ));\n    }\n\n    fn schema(&self) -> Schema {\n        self.schema.clone()\n    }\n}\n\nimpl Indexer<File> {\n    pub async fn skim_fuzzy_path_match(\n        &self,\n        repo_refs: impl IntoIterator<Item = RepoRef>,\n        query_str: &str,\n        branch: Option<&str>,\n        langs: impl Iterator<Item = &str>,\n        limit: usize,\n    ) -> impl Iterator<Item = FileDocument> + '_ {\n        let searcher = self.reader.searcher();\n        let file_source = &self.source;\n\n        let repo_ref_terms = {\n            let term_queries = repo_refs\n                .into_iter()\n                .map(|repo_ref| {\n                    TermQuery::new(\n                        Term::from_field_text(self.source.repo_ref, &repo_ref.to_string()),\n                        IndexRecordOption::Basic,\n                    )\n                })\n                .map(|q| Box::new(q) as Box<dyn Query>)\n                .collect::<Vec<_>>();\n\n            Box::new(BooleanQuery::union(term_queries))\n        };\n\n        let branch_term = branch\n            .map(|b| {\n                trigrams(b)\n                    .map(|token| Term::from_field_text(self.source.branches, token.as_str()))\n                    .map(|term| TermQuery::new(term, IndexRecordOption::Basic))\n                    .map(Box::new)\n                    .map(|q| q as Box<dyn Query>)\n                    .collect::<Vec<_>>()\n            })\n            .map(BooleanQuery::intersection)\n            .map(Box::new);\n\n        let langs_term = langs\n            .map(|l| Term::from_field_bytes(self.source.lang, l.as_bytes()))\n            .map(|t| TermQuery::new(t, IndexRecordOption::Basic))\n            .map(Box::new)\n            .map(|q| q as Box<dyn Query>)\n            .collect::<Vec<_>>();\n\n        let langs_term = match langs_term.len() {\n            0 => None,\n            _ => Some(Box::new(BooleanQuery::union(langs_term))),\n        };\n\n        let search_terms = trigrams(query_str)\n            .flat_map(|s| case_permutations(s.as_str()))\n            .map(|token| Term::from_field_text(self.source.relative_path, token.as_str()))\n            .map(|term| {\n                BooleanQuery::intersection(\n                    [\n                        Some(Box::new(TermQuery::new(term, IndexRecordOption::Basic))\n                            as Box<dyn Query>),\n                        Some(Box::clone(&repo_ref_terms) as Box<dyn Query>),\n                        branch_term\n                            .as_ref()\n                            .map(Box::clone)\n                            .map(|t| t as Box<dyn Query>),\n                        langs_term\n                            .as_ref()\n                            .map(Box::clone)\n                            .map(|t| t as Box<dyn Query>),\n                    ]\n                    .into_iter()\n                    .flatten()\n                    .collect(),\n                )\n            })\n            .map(|t| Box::new(t) as Box<dyn Query>)\n            .collect::<Vec<_>>();\n\n        let matcher = fuzzy_matcher::skim::SkimMatcherV2::default();\n\n        let mut results = searcher\n            .search(\n                &BooleanQuery::union(search_terms),\n                &TopDocs::with_limit(50_000),\n            )\n            .expect(\"failed to search index\")\n            .into_iter()\n            .map(move |(_, addr)| {\n                let retrieved_doc = searcher\n                    .doc(addr)\n                    .expect(\"failed to get document by address\");\n                FileReader.read_document(file_source, retrieved_doc)\n            })\n            .filter(|doc| !doc.relative_path.ends_with('/'))\n            .filter_map(|doc| {\n                let (score, positions) = matcher.fuzzy(&doc.relative_path, query_str, true)?;\n\n                // the closer the position is to the end, the higher its score is\n                let position_bonus = positions\n                    .iter()\n                    .map(|p| *p as f32 / doc.relative_path.len() as f32)\n                    .sum::<f32>();\n\n                // add bonus if hits occur in the file-name\n                let file_name_bonus = {\n                    let file_name_start = doc.relative_path.rfind('/').unwrap_or(0);\n                    positions.iter().filter(|&p| p > &file_name_start).count() as f32\n                };\n\n                Some((doc, score as f32 + position_bonus + file_name_bonus))\n            })\n            .collect::<Vec<_>>();\n\n        results.sort_by(|(_, a_score), (_, b_score)| {\n            b_score\n                .partial_cmp(a_score)\n                .unwrap_or(std::cmp::Ordering::Less)\n        });\n        results.into_iter().map(|(doc, _)| doc).take(limit)\n    }\n\n    pub async fn by_path(\n        &self,\n        repo_ref: &RepoRef,\n        relative_path: &str,\n        branch: Option<&str>,\n    ) -> Result<Option<ContentDocument>> {\n        let searcher = self.reader.searcher();\n\n        let file_index = searcher.index();\n\n        // query the `relative_path` field of the `File` index, using tantivy's query language\n        //\n        // XXX: can we use the bloop query language here instead?\n        let query_parser = QueryParser::for_index(\n            file_index,\n            vec![self.source.repo_ref, self.source.relative_path],\n        );\n\n        let mut query_string =\n            format!(r#\"repo_ref:\"{repo_ref}\" AND relative_path:\"{relative_path}\"\"#);\n\n        if let Some(b) = branch {\n            query_string += &format!(r#\" AND branches:\"{b}\"\"#);\n        }\n\n        let query = query_parser\n            .parse_query(&query_string)\n            .expect(\"failed to parse tantivy query\");\n\n        self.top_hit(query, searcher).await\n    }\n\n    async fn top_hit(\n        &self,\n        query: Box<dyn Query>,\n        searcher: tantivy::Searcher,\n    ) -> Result<Option<ContentDocument>> {\n        let file_source = &self.source;\n\n        let collector = TopDocs::with_limit(1);\n        let search_results = searcher\n            .search(&query, &collector)\n            .expect(\"failed to search index\");\n\n        match search_results.as_slice() {\n            // no paths matched, the input path was not well formed\n            [] => Ok(None),\n\n            // exactly one path, good\n            [(_, doc_addr)] => {\n                let retrieved_doc = searcher\n                    .doc(*doc_addr)\n                    .expect(\"failed to get document by address\");\n                Ok(Some(\n                    ContentReader.read_document(file_source, retrieved_doc),\n                ))\n            }\n\n            // more than one path matched, this can occur when top docs is no\n            // longer limited to 1 and the index contains dupes\n            _ => {\n                warn!(\"TopDocs is not limited to 1 and index contains duplicates\");\n                Err(anyhow::Error::msg(\"multiple paths returned\"))\n            }\n        }\n    }\n\n    // Produce all files in a repo\n    //\n    // TODO: Look at this again when:\n    //  - directory retrieval is ready\n    //  - unified referencing is ready\n    pub async fn by_repo<S: AsRef<str>>(\n        &self,\n        repo_ref: &RepoRef,\n        langs: impl Iterator<Item = S>,\n        branch: Option<&str>,\n    ) -> Vec<ContentDocument> {\n        let searcher = self.reader.searcher();\n\n        let mut query = vec![];\n\n        // repo query\n        query.push(Box::new(TermQuery::new(\n            Term::from_field_text(self.source.repo_ref, &repo_ref.to_string()),\n            IndexRecordOption::Basic,\n        )) as Box<dyn Query>);\n\n        let branch_term = branch\n            .map(|b| {\n                trigrams(b)\n                    .map(|token| Term::from_field_text(self.source.branches, token.as_str()))\n                    .map(|term| TermQuery::new(term, IndexRecordOption::Basic))\n                    .map(Box::new)\n                    .map(|q| q as Box<dyn Query>)\n                    .collect::<Vec<_>>()\n            })\n            .map(BooleanQuery::intersection);\n        if let Some(b) = branch_term {\n            query.push(Box::new(b) as Box<dyn Query>);\n        };\n\n        query.push({\n            let queries = langs\n                .map(|lang| {\n                    Box::new(TermQuery::new(\n                        Term::from_field_bytes(\n                            self.source.lang,\n                            lang.as_ref().to_ascii_lowercase().as_bytes(),\n                        ),\n                        IndexRecordOption::Basic,\n                    )) as Box<dyn Query>\n                })\n                .collect::<Vec<_>>();\n            Box::new(BooleanQuery::union(queries))\n        });\n\n        let query = BooleanQuery::intersection(query);\n        let collector = TopDocs::with_limit(500);\n        searcher\n            .search(&query, &collector)\n            .expect(\"failed to search index\")\n            .into_par_iter()\n            .map(|(_, doc_addr)| {\n                let retrieved_doc = searcher\n                    .doc(doc_addr)\n                    .expect(\"failed to get document by address\");\n                ContentReader.read_document(&self.source, retrieved_doc)\n            })\n            .collect()\n    }\n}\n\nimpl File {\n    #[tracing::instrument(fields(repo=%workload.repo_ref, entry_disk_path=?dir_entry.path()), skip_all)]\n    fn worker(\n        &self,\n        dir_entry: RepoDirEntry,\n        workload: Workload<'_>,\n        writer: &IndexWriter,\n    ) -> Result<()> {\n        #[cfg(feature = \"debug\")]\n        let start = Instant::now();\n        trace!(\"processing file\");\n\n        let cache_keys = workload.cache_keys(&dir_entry);\n        let last_commit = workload.repo_metadata.last_commit_unix_secs.unwrap_or(0);\n\n        match dir_entry {\n            _ if workload.cache.is_fresh(&cache_keys) => {\n                info!(\"fresh; skipping\");\n            }\n            RepoDirEntry::Dir(dir) => {\n                trace!(\"writing dir document\");\n                let doc = dir.build_document(self, &workload, last_commit as u64, &cache_keys);\n                writer.add_document(doc)?;\n                trace!(\"dir document written\");\n            }\n            RepoDirEntry::File(file) => {\n                trace!(\"writing file document\");\n                let doc = file\n                    .build_document(\n                        self,\n                        &workload,\n                        &cache_keys,\n                        last_commit as u64,\n                        workload.cache.parent(),\n                    )\n                    .ok_or(anyhow::anyhow!(\"failed to build document\"))?;\n                writer.add_document(doc)?;\n                trace!(\"file document written\");\n            }\n        }\n\n        #[cfg(feature = \"debug\")]\n        {\n            let elapsed = start.elapsed();\n            let time: u64 = elapsed\n                .as_millis()\n                .try_into()\n                .expect(\"nobody waits this long\");\n            self.histogram.write().unwrap().increment(time, 1).unwrap();\n\n            if time\n                > self\n                    .histogram\n                    .read()\n                    .unwrap()\n                    .percentile(99.9)\n                    .unwrap()\n                    .low()\n            {\n                // default console formatter is different when we're debugging. need to print more info here.\n                warn!(?self.relative_path, ?elapsed, \"file took too long to process\")\n            }\n        }\n\n        Ok(())\n    }\n}\n\nimpl RepoDir {\n    #[allow(clippy::too_many_arguments)]\n    fn build_document(\n        self,\n        schema: &File,\n        workload: &Workload<'_>,\n        last_commit: u64,\n        cache_keys: &CacheKeys,\n    ) -> tantivy::schema::Document {\n        let Workload {\n            relative_path,\n            repo_name,\n            repo_disk_path,\n            repo_ref,\n            ..\n        } = workload;\n\n        let relative_path_str = format!(\"{}/\", relative_path.to_string_lossy());\n        #[cfg(windows)]\n        let relative_path_str = relative_path_str.replace('\\\\', \"/\");\n\n        let branches = self.branches.join(\"\\n\");\n        let stats = WorkerStats {\n            size: self.size(),\n            chunks: 0,\n            reindex_count: 1,\n        };\n        workload.transmit_stats(stats);\n\n        doc!(\n            schema.raw_repo_name => repo_name.as_bytes(),\n            schema.raw_relative_path => relative_path_str.as_bytes(),\n            schema.repo_disk_path => repo_disk_path.to_string_lossy().as_ref(),\n            schema.relative_path => relative_path_str,\n            schema.repo_ref => repo_ref.to_string(),\n            schema.repo_name => *repo_name,\n            schema.last_commit_unix_seconds => last_commit,\n            schema.branches => branches,\n            schema.is_directory => true,\n            schema.unique_hash => cache_keys.tantivy(),\n\n            // always indicate dirs as indexed\n            schema.indexed => true,\n\n            // nulls\n            schema.raw_content => Vec::<u8>::default(),\n            schema.content => String::default(),\n            schema.line_end_indices => Vec::<u8>::default(),\n            schema.lang => Vec::<u8>::default(),\n            schema.avg_line_length => f64::default(),\n            schema.symbol_locations => bincode::serialize(&SymbolLocations::default()).unwrap(),\n            schema.symbols => String::default(),\n        )\n    }\n}\n\nimpl RepoFile {\n    #[allow(clippy::too_many_arguments)]\n    fn build_document(\n        self,\n        schema: &File,\n        workload: &Workload<'_>,\n        cache_keys: &CacheKeys,\n        last_commit: u64,\n        file_cache: &FileCache,\n    ) -> Option<tantivy::schema::Document> {\n        let Workload {\n            relative_path,\n            repo_name,\n            repo_disk_path,\n            repo_ref,\n            repo_metadata,\n            normalized_path,\n            file_filter,\n            ..\n        } = workload;\n\n        let relative_path_str = relative_path.to_string_lossy().to_string();\n        #[cfg(windows)]\n        let relative_path_str = relative_path_str.replace('\\\\', \"/\");\n\n        let branches = self.branches.join(\"\\n\");\n        let explicitly_allowed = file_filter.is_allowed(relative_path);\n        let indexed = explicitly_allowed.unwrap_or_else(|| self.should_index());\n        let mut stats = WorkerStats {\n            size: self.size(),\n            reindex_count: 1,\n            ..Default::default()\n        };\n\n        if !indexed {\n            let lang_str = repo_metadata\n                .langs\n                .get(normalized_path, b\"\")\n                .unwrap_or_else(|| {\n                    warn!(?normalized_path, \"Path not found in language map\");\n                    \"\"\n                });\n\n            return Some(doc!(\n                schema.raw_content => vec![],\n                schema.content => \"\",\n                schema.line_end_indices => vec![],\n                schema.avg_line_length => 0f64,\n                schema.symbol_locations => vec![],\n                schema.symbols => vec![],\n                schema.raw_repo_name => repo_name.as_bytes(),\n                schema.raw_relative_path => relative_path_str.as_bytes(),\n                schema.unique_hash => cache_keys.tantivy(),\n                schema.repo_disk_path => repo_disk_path.to_string_lossy().as_ref(),\n                schema.relative_path => relative_path_str,\n                schema.repo_ref => repo_ref.to_string(),\n                schema.repo_name => *repo_name,\n                schema.lang => lang_str.to_ascii_lowercase().as_bytes(),\n                schema.last_commit_unix_seconds => last_commit,\n                schema.branches => branches,\n                schema.is_directory => false,\n                schema.indexed => false,\n            ));\n        }\n\n        let mut buffer = match self.buffer() {\n            Ok(b) => b,\n            Err(err) => {\n                warn!(?err, \"failed to open file buffer; skipping file\");\n                return None;\n            }\n        };\n        let lang_str = repo_metadata\n            .langs\n            .get(normalized_path, buffer.as_ref())\n            .unwrap_or_else(|| {\n                warn!(?normalized_path, \"Path not found in language map\");\n                \"\"\n            });\n\n        let symbol_locations = {\n            // build a syntax aware representation of the file\n            let scope_graph = TreeSitterFile::try_build(buffer.as_bytes(), lang_str)\n                .and_then(TreeSitterFile::scope_graph);\n\n            match scope_graph {\n                // we have a graph, use that\n                Ok(graph) => SymbolLocations::TreeSitter(graph),\n                // no graph, it's empty\n                Err(_) => SymbolLocations::Empty,\n            }\n        };\n\n        // flatten the list of symbols into a string with just text\n        let symbols = symbol_locations\n            .list()\n            .iter()\n            .map(|sym| buffer[sym.range.start.byte..sym.range.end.byte].to_owned())\n            .collect::<HashSet<_>>()\n            .into_iter()\n            .collect::<Vec<_>>()\n            .join(\"\\n\");\n\n        // add an NL if this file is not NL-terminated\n        if !buffer.ends_with('\\n') {\n            buffer += \"\\n\";\n        }\n\n        let line_end_indices = buffer\n            .match_indices('\\n')\n            .flat_map(|(i, _)| u32::to_le_bytes(i as u32))\n            .collect::<Vec<_>>();\n\n        // Skip files that are too long. This is not necessarily caught in the filesize check, e.g.\n        // for a file like `vocab.txt` which has thousands of very short lines.\n        if !matches!(explicitly_allowed, Some(true))\n            && line_end_indices.len() > MAX_LINE_COUNT as usize\n        {\n            return None;\n        }\n\n        let lines_avg = buffer.len() as f64 / buffer.lines().count() as f64;\n\n        let insert_stats = tokio::task::block_in_place(|| {\n            Handle::current().block_on(async {\n                file_cache\n                    .process_semantic(\n                        cache_keys,\n                        repo_name,\n                        repo_ref,\n                        &relative_path_str,\n                        &buffer,\n                        lang_str,\n                        &self.branches,\n                    )\n                    .await\n            })\n        });\n\n        stats.chunks += insert_stats.new;\n        workload.transmit_stats(stats);\n\n        Some(doc!(\n            schema.raw_content => buffer.as_bytes(),\n            schema.raw_repo_name => repo_name.as_bytes(),\n            schema.raw_relative_path => relative_path_str.as_bytes(),\n            schema.unique_hash => cache_keys.tantivy(),\n            schema.repo_disk_path => repo_disk_path.to_string_lossy().as_ref(),\n            schema.relative_path => relative_path_str,\n            schema.repo_ref => repo_ref.to_string(),\n            schema.repo_name => *repo_name,\n            schema.content => buffer,\n            schema.line_end_indices => line_end_indices,\n            schema.lang => lang_str.to_ascii_lowercase().as_bytes(),\n            schema.avg_line_length => lines_avg,\n            schema.last_commit_unix_seconds => last_commit,\n            schema.symbol_locations => bincode::serialize(&symbol_locations).unwrap(),\n            schema.symbols => symbols,\n            schema.branches => branches,\n            schema.is_directory => false,\n            schema.indexed => true,\n        ))\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/indexes/reader.rs",
    "content": "use anyhow::Result;\nuse async_trait::async_trait;\nuse tantivy::{\n    schema::{Field, Value},\n    Index,\n};\n\nuse super::{file::File, repo::Repo, DocumentRead};\nuse crate::{\n    intelligence::TreeSitterFile,\n    query::{\n        compiler::Compiler,\n        parser::{self, Query, Target},\n    },\n    repo::RepoRef,\n    symbol::SymbolLocations,\n    text_range::TextRange,\n};\n\n#[derive(Default, Debug, Clone)]\npub struct ContentDocument {\n    pub content: String,\n    pub lang: Option<String>,\n    pub relative_path: String,\n    pub repo_name: String,\n    pub repo_ref: String,\n    pub line_end_indices: Vec<u32>,\n    pub symbol_locations: SymbolLocations,\n    pub branches: Option<String>,\n    pub indexed: bool,\n}\n\nimpl std::hash::Hash for ContentDocument {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.repo_ref.hash(state);\n        self.branches.hash(state);\n        self.relative_path.hash(state);\n        self.content.hash(state);\n    }\n}\n\nimpl PartialEq for ContentDocument {\n    fn eq(&self, other: &Self) -> bool {\n        self.repo_ref == other.repo_ref\n            && self.branches == other.branches\n            && self.relative_path == other.relative_path\n            && self.content == other.content\n    }\n}\nimpl Eq for ContentDocument {}\n\nimpl ContentDocument {\n    pub fn hoverable_ranges(&self) -> Option<Vec<TextRange>> {\n        TreeSitterFile::try_build(self.content.as_bytes(), self.lang.as_ref()?)\n            .and_then(TreeSitterFile::hoverable_ranges)\n            .ok()\n    }\n}\n\n#[derive(Debug)]\npub struct FileDocument {\n    pub relative_path: String,\n    pub repo_name: String,\n    pub repo_ref: RepoRef,\n    pub lang: Option<String>,\n    pub branches: String,\n    pub indexed: bool,\n    pub is_dir: bool,\n}\n\npub struct RepoDocument {\n    pub org: String,\n    pub name: String,\n    pub repo_ref: String,\n}\n\npub struct ContentReader;\n\n#[async_trait]\nimpl DocumentRead for ContentReader {\n    type Schema = File;\n    type Document = ContentDocument;\n\n    fn query_matches(&self, query: &Query<'_>) -> bool {\n        matches!(\n            query,\n            Query {\n                open: Some(false) | None,\n                target: Some(Target::Content(..) | Target::Symbol(..)),\n                ..\n            }\n        )\n    }\n\n    fn compile<'a, I>(\n        &self,\n        schema: &File,\n        queries: I,\n        tantivy_index: &Index,\n    ) -> Result<Box<dyn tantivy::query::Query>>\n    where\n        I: Iterator<Item = &'a Query<'a>>,\n    {\n        Compiler::new()\n            .priority(&[schema.relative_path])\n            .literal(schema.relative_path, |q| q.path.clone())\n            .literal(schema.repo_name, |q| q.repo.clone())\n            .literal(schema.branches, |q| q.branch.clone())\n            .byte_string(schema.lang, |q| q.lang.as_ref().map(AsRef::as_ref))\n            .literal(schema.symbols, |q| {\n                q.target.as_ref().and_then(Target::symbol).cloned()\n            })\n            .literal(schema.content, |q| {\n                q.target.as_ref().and_then(Target::content).cloned()\n            })\n            .compile(queries, tantivy_index)\n    }\n\n    fn read_document(&self, schema: &File, doc: tantivy::Document) -> Self::Document {\n        let relative_path = read_text_field(&doc, schema.relative_path);\n        let repo_ref = read_text_field(&doc, schema.repo_ref);\n        let repo_name = read_text_field(&doc, schema.repo_name);\n        let content = read_text_field(&doc, schema.content);\n        let lang = read_lang_field(&doc, schema.lang);\n        let branches = read_lang_field(&doc, schema.branches);\n        let indexed = read_bool_field(&doc, schema.indexed);\n\n        let line_end_indices = doc\n            .get_first(schema.line_end_indices)\n            .unwrap()\n            .as_bytes()\n            .unwrap()\n            .chunks_exact(4)\n            .map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))\n            .collect();\n\n        let symbol_locations = bincode::deserialize(\n            doc.get_first(schema.symbol_locations)\n                .unwrap()\n                .as_bytes()\n                .unwrap(),\n        )\n        .unwrap_or_default();\n\n        ContentDocument {\n            relative_path,\n            repo_name,\n            repo_ref,\n            content,\n            symbol_locations,\n            line_end_indices,\n            lang,\n            branches,\n            indexed,\n        }\n    }\n}\n\npub struct FileReader;\n\n#[async_trait]\nimpl DocumentRead for FileReader {\n    type Document = FileDocument;\n    type Schema = File;\n\n    fn query_matches(&self, query: &Query<'_>) -> bool {\n        matches!(\n            query,\n            // Match both language or filename searches. Handles searches like:\n            //   lang:Rust\n            //   path:server\n            //   lang:Rust path:server\n            Query {\n                open: Some(false) | None,\n                target: None,\n                lang: Some(..),\n                ..\n            } | Query {\n                open: Some(false) | None,\n                target: None,\n                path: Some(..),\n                ..\n            }\n        )\n    }\n\n    fn compile<'a, I>(\n        &self,\n        schema: &Self::Schema,\n        queries: I,\n        tantivy_index: &Index,\n    ) -> Result<Box<dyn tantivy::query::Query>>\n    where\n        I: Iterator<Item = &'a Query<'a>>,\n    {\n        Compiler::new()\n            .literal(schema.relative_path, |q| q.path.clone())\n            .literal(schema.repo_name, |q| q.repo.clone())\n            .literal(schema.branches, |q| q.branch.clone())\n            .byte_string(schema.lang, |q| q.lang.as_ref().map(AsRef::as_ref))\n            .compile(queries, tantivy_index)\n    }\n\n    fn read_document(&self, schema: &Self::Schema, doc: tantivy::Document) -> Self::Document {\n        let relative_path = read_text_field(&doc, schema.relative_path);\n        let repo_ref = read_text_field(&doc, schema.repo_ref).parse().unwrap();\n        let repo_name = read_text_field(&doc, schema.repo_name);\n        let lang = read_lang_field(&doc, schema.lang);\n        let branches = read_text_field(&doc, schema.branches);\n        let indexed = read_bool_field(&doc, schema.indexed);\n        let is_dir = read_bool_field(&doc, schema.is_directory);\n\n        FileDocument {\n            relative_path,\n            repo_name,\n            repo_ref,\n            lang,\n            branches,\n            indexed,\n            is_dir,\n        }\n    }\n}\n\npub struct RepoReader;\n\n#[async_trait]\nimpl DocumentRead for RepoReader {\n    type Document = RepoDocument;\n    type Schema = Repo;\n\n    fn query_matches(&self, query: &Query<'_>) -> bool {\n        matches!(\n            query,\n            Query {\n                open: Some(false) | None,\n                repo: Some(..),\n                path: None,\n                target: None,\n                ..\n            }\n        )\n    }\n\n    fn compile<'a, I>(\n        &self,\n        schema: &Repo,\n        queries: I,\n        tantivy_index: &Index,\n    ) -> Result<Box<dyn tantivy::query::Query>>\n    where\n        I: Iterator<Item = &'a Query<'a>>,\n    {\n        Compiler::new()\n            .literal(schema.name, |q| q.repo.clone())\n            .compile(queries, tantivy_index)\n    }\n\n    fn read_document(&self, schema: &Repo, doc: tantivy::Document) -> Self::Document {\n        let org = read_text_field(&doc, schema.org);\n        let name = read_text_field(&doc, schema.name);\n        let repo_ref = read_text_field(&doc, schema.repo_ref);\n\n        RepoDocument {\n            org,\n            name,\n            repo_ref,\n        }\n    }\n}\n\npub struct OpenReader;\n\n#[derive(Debug)]\npub struct OpenDocument {\n    pub relative_path: String,\n    pub repo_name: String,\n    pub repo_ref: String,\n    pub lang: Option<String>,\n    pub content: String,\n    pub line_end_indices: Vec<u32>,\n    pub indexed: bool,\n}\n\n#[async_trait]\nimpl DocumentRead for OpenReader {\n    type Document = OpenDocument;\n    type Schema = File;\n\n    fn query_matches(&self, query: &Query<'_>) -> bool {\n        matches!(\n            query,\n            Query {\n                open: Some(true),\n\n                // All open queries must specify at least the repository name. We don't accept regex\n                // inputs for this type of query.\n                repo: Some(parser::Literal::Plain(..)),\n                path: None | Some(parser::Literal::Plain(..)),\n\n                // We want to make sure this query isn't a symbol or content search, which doesn't\n                // make sense for a file open.\n                target: None,\n                ..\n            }\n        )\n    }\n\n    fn compile<'a, I>(\n        &self,\n        schema: &File,\n        queries: I,\n        tantivy_index: &Index,\n    ) -> Result<Box<dyn tantivy::query::Query>>\n    where\n        I: Iterator<Item = &'a Query<'a>>,\n    {\n        Compiler::new()\n            .literal(schema.repo_name, |q| q.repo.clone())\n            .literal(schema.branches, |q| q.branch.clone())\n            .literal(schema.relative_path, |q| match &q.path {\n                // We coerce path searches to always return sibling files. These are sorted later\n                // by users of this reader.\n                Some(parser::Literal::Plain(s)) => {\n                    Some(parser::Literal::Plain(match base_name(s) {\n                        \"\" => return None,\n                        s => s.into(),\n                    }))\n                }\n                _ => None,\n            })\n            .byte_string(schema.lang, |q| q.lang.as_ref().map(AsRef::as_ref))\n            .compile(queries, tantivy_index)\n    }\n\n    fn read_document(&self, schema: &File, doc: tantivy::Document) -> Self::Document {\n        let relative_path = read_text_field(&doc, schema.relative_path);\n        let repo_name = read_text_field(&doc, schema.repo_name);\n        let repo_ref = read_text_field(&doc, schema.repo_ref);\n        let lang = read_lang_field(&doc, schema.lang);\n        let content = read_text_field(&doc, schema.content);\n        let line_end_indices = doc\n            .get_first(schema.line_end_indices)\n            .unwrap()\n            .as_bytes()\n            .unwrap()\n            .chunks_exact(4)\n            .map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))\n            .collect();\n        let indexed = read_bool_field(&doc, schema.indexed);\n\n        Self::Document {\n            relative_path,\n            repo_name,\n            repo_ref,\n            lang,\n            content,\n            line_end_indices,\n            indexed,\n        }\n    }\n}\n\n/// Get the basename of a path, returning an empty string if the path contains no separators.\n///\n/// ## Examples\n///\n/// - `\"bar/foo.txt\" -> \"bar/\"`\n/// - `\"bar/\" -> \"bar/\"`\n/// - `\"foo.txt\" -> \"\"`\npub fn base_name(path: &str) -> &str {\n    path.rfind('/').map(|i| &path[..i + 1]).unwrap_or(\"\")\n}\n\nfn read_bool_field(doc: &tantivy::Document, field: Field) -> bool {\n    doc.get_first(field).unwrap().as_bool().unwrap()\n}\n\nfn read_text_field(doc: &tantivy::Document, field: Field) -> String {\n    let Some(field) = doc.get_first(field) else {\n        return Default::default();\n    };\n\n    field.as_text().unwrap().into()\n}\n\nfn read_lang_field(doc: &tantivy::Document, lang: Field) -> Option<String> {\n    let lang_str = crate::query::languages::proper_case(\n        doc.get_first(lang)\n            .and_then(Value::as_bytes)\n            .map(String::from_utf8_lossy)\n            .unwrap_or_default(),\n    )\n    .into_owned();\n\n    // None if \"\", Some(l) otherwise\n    if lang_str.is_empty() {\n        None\n    } else {\n        Some(lang_str)\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_base_name() {\n        assert_eq!(base_name(\"bar/foo.txt\"), format!(\"bar/\"));\n        assert_eq!(base_name(\"bar/\"), format!(\"bar/\"));\n        assert_eq!(base_name(\"foo.txt\"), \"\");\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/indexes/repo.rs",
    "content": "use anyhow::Result;\nuse async_trait::async_trait;\nuse tantivy::{doc, schema::Schema, IndexWriter, Term};\nuse tracing::info;\n\npub use super::schema::Repo;\nuse super::Indexable;\nuse crate::{\n    background::SyncHandle,\n    repo::{RepoMetadata, Repository},\n};\n\nimpl Default for Repo {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\n#[async_trait]\nimpl Indexable for Repo {\n    async fn index_repository(\n        &self,\n        SyncHandle { ref reporef, .. }: &SyncHandle,\n        repo: &Repository,\n        _metadata: &RepoMetadata,\n        writer: &IndexWriter,\n    ) -> Result<()> {\n        // Make sure we delete any stale references to this repository when indexing.\n        self.delete_by_repo(writer, repo);\n\n        writer.add_document(doc!(\n            // We don't have organization support for now.\n            self.org => \"\",\n            self.disk_path => repo.disk_path.to_string_lossy().into_owned(),\n            self.name => reporef.indexed_name(),\n            self.raw_name => reporef.indexed_name().as_bytes(),\n            self.repo_ref => reporef.to_string(),\n        ))?;\n\n        info!(\n            ?repo.disk_path,\n            \"finished indexing repo metadata\"\n        );\n\n        Ok(())\n    }\n\n    fn delete_by_repo(&self, writer: &IndexWriter, repo: &Repository) {\n        writer.delete_term(Term::from_field_text(\n            self.disk_path,\n            &repo.disk_path.to_string_lossy(),\n        ));\n    }\n\n    fn schema(&self) -> Schema {\n        self.schema.clone()\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/indexes/schema.rs",
    "content": "//! Every change in this file will trigger a reset of the databases.\n//! Use with care.\n//!\nuse tantivy::schema::{\n    BytesOptions, Field, IndexRecordOption, Schema, SchemaBuilder, TextFieldIndexing, TextOptions,\n    FAST, INDEXED, STORED, STRING, TEXT,\n};\n\n#[cfg(feature = \"debug\")]\nuse {\n    histogram::Histogram,\n    std::sync::{Arc, RwLock},\n};\n\n/// A schema for indexing all files and directories, linked to a\n/// single repository on disk.\n#[derive(Clone)]\npub struct File {\n    pub(super) schema: Schema,\n\n    #[cfg(feature = \"debug\")]\n    pub histogram: Arc<RwLock<Histogram>>,\n\n    /// Unique ID for the file in a repo\n    pub unique_hash: Field,\n\n    /// Path to the root of the repo on disk\n    pub repo_disk_path: Field,\n    /// Path to the file, relative to the repo root\n    pub relative_path: Field,\n\n    /// Unique repo identifier, of the form:\n    ///  local: local//path/to/repo\n    /// github: github.com/org/repo\n    pub repo_ref: Field,\n\n    /// Indexed repo name, of the form:\n    ///  local: repo\n    /// github: github.com/org/repo\n    pub repo_name: Field,\n\n    pub content: Field,\n    pub line_end_indices: Field,\n\n    /// a flat list of every symbol's text, for searching, e.g.:\n    /// [\"File\", \"Repo\", \"worker\"]\n    pub symbols: Field,\n    pub symbol_locations: Field,\n\n    /// fast fields for scoring\n    pub lang: Field,\n    pub avg_line_length: Field,\n    pub last_commit_unix_seconds: Field,\n\n    /// fast byte versions of certain fields for collector-level filtering\n    pub raw_content: Field,\n    pub raw_repo_name: Field,\n    pub raw_relative_path: Field,\n\n    /// list of branches in which this file can be found\n    pub branches: Field,\n\n    /// list of branches in which this file can be found\n    pub indexed: Field,\n\n    /// Whether this entry is a file or a directory\n    pub is_directory: Field,\n}\n\nimpl File {\n    pub fn new() -> Self {\n        let mut builder = SchemaBuilder::new();\n        let trigram = TextOptions::default().set_stored().set_indexing_options(\n            TextFieldIndexing::default()\n                .set_tokenizer(\"default\")\n                .set_index_option(IndexRecordOption::WithFreqsAndPositions),\n        );\n\n        let unique_hash = builder.add_text_field(\"unique_hash\", STRING | STORED);\n\n        let repo_disk_path = builder.add_text_field(\"repo_disk_path\", STRING);\n        let repo_ref = builder.add_text_field(\"repo_ref\", STRING | STORED);\n        let repo_name = builder.add_text_field(\"repo_name\", STRING | STORED);\n        let relative_path = builder.add_text_field(\"relative_path\", trigram.clone());\n\n        let content = builder.add_text_field(\"content\", trigram.clone());\n        let line_end_indices =\n            builder.add_bytes_field(\"line_end_indices\", BytesOptions::default().set_stored());\n\n        let symbols = builder.add_text_field(\"symbols\", trigram.clone());\n        let symbol_locations =\n            builder.add_bytes_field(\"symbol_locations\", BytesOptions::default().set_stored());\n\n        let branches = builder.add_text_field(\"branches\", trigram);\n\n        let lang = builder.add_bytes_field(\n            \"lang\",\n            BytesOptions::default().set_stored().set_indexed() | FAST,\n        );\n        let avg_line_length = builder.add_f64_field(\"line_length\", FAST);\n        let last_commit_unix_seconds = builder.add_u64_field(\"last_commit_unix_seconds\", FAST);\n\n        let raw_content = builder.add_bytes_field(\"raw_content\", FAST);\n        let raw_repo_name = builder.add_bytes_field(\"raw_repo_name\", FAST);\n        let raw_relative_path = builder.add_bytes_field(\"raw_relative_path\", FAST);\n\n        let is_directory = builder.add_bool_field(\"is_directory\", FAST | STORED);\n        let indexed = builder.add_bool_field(\"indexed\", STORED);\n\n        Self {\n            schema: builder.build(),\n            repo_disk_path,\n            relative_path,\n            unique_hash,\n            repo_ref,\n            repo_name,\n            content,\n            line_end_indices,\n            symbols,\n            symbol_locations,\n            lang,\n            avg_line_length,\n            last_commit_unix_seconds,\n            raw_content,\n            raw_repo_name,\n            raw_relative_path,\n            branches,\n            is_directory,\n            indexed,\n\n            #[cfg(feature = \"debug\")]\n            histogram: Arc::new(Histogram::builder().build().unwrap().into()),\n        }\n    }\n\n    pub fn schema(&self) -> Schema {\n        self.schema.clone()\n    }\n}\n\nimpl Default for File {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\n/// An index representing a repository to allow free-text search on\n/// repository names\npub struct Repo {\n    pub(super) schema: Schema,\n\n    /// Path to the root of the repo on disk\n    pub disk_path: Field,\n\n    /// Name of the org\n    pub org: Field,\n\n    /// Indexed repo name, of the form:\n    ///  local: repo\n    /// github: github.com/org/repo\n    pub name: Field,\n    pub raw_name: Field,\n\n    /// Unique repo identifier, of the form:\n    ///  local: local//path/to/repo\n    /// github: github.com/org/repo\n    pub repo_ref: Field,\n}\n\nimpl Repo {\n    pub fn new() -> Self {\n        let mut builder = SchemaBuilder::new();\n        let trigram = TextOptions::default().set_stored().set_indexing_options(\n            TextFieldIndexing::default()\n                .set_tokenizer(\"default\")\n                .set_index_option(IndexRecordOption::WithFreqsAndPositions),\n        );\n\n        let disk_path = builder.add_text_field(\"disk_path\", STRING);\n        let org = builder.add_text_field(\"org\", trigram.clone());\n        let name = builder.add_text_field(\"name\", trigram.clone());\n        let raw_name = builder.add_bytes_field(\"raw_name\", FAST);\n        let repo_ref = builder.add_text_field(\"repo_ref\", trigram);\n\n        Self {\n            disk_path,\n            org,\n            name,\n            raw_name,\n            repo_ref,\n            schema: builder.build(),\n        }\n    }\n}\n\n#[derive(Clone)]\npub struct Section {\n    pub(super) schema: Schema,\n\n    /// Monotonically increasing id, unique to each doc-provider indexed. This\n    /// is also stored in sqlite and is used to cross reference the two\n    pub doc_id: Field,\n\n    /// URL that identifies this doc-source\n    pub doc_source: Field,\n\n    /// Human readable doc-title, scraped from html\n    pub doc_title: Field,\n\n    /// Human readable doc-description, scraped from html\n    pub doc_description: Field,\n\n    // Content-addressable hash for each section in a page\n    pub point_id: Field,\n\n    /// Relative URL of the page containing this section\n    pub relative_url: Field,\n\n    /// Absolute URL of the page containing this section\n    pub absolute_url: Field,\n\n    /// Section header. All sections start with a header\n    pub header: Field,\n\n    /// List of headers of all parent sections above this section. This\n    /// is stored in the tantivy index as a string separated by the sequence \" > \".\n    ///\n    ///\n    /// The order of headers in this field is top to bottom, for example:\n    ///\n    /// # Introduction > ## What is tantivy? > ### API Migration\n    ///\n    pub ancestry: Field,\n\n    /// Text content of this section in raw markdown. This content also includes the header\n    pub text: Field,\n\n    /// Start location in bytes\n    pub start_byte: Field,\n\n    /// End location in bytes\n    pub end_byte: Field,\n\n    /// Number of items in this sections' ancestry\n    pub section_depth: Field,\n\n    /// Bytes indexed, fast, relative_url field, used for grouping and other fastfield business\n    pub raw_relative_url: Field,\n}\n\nimpl Default for Section {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Section {\n    pub fn new() -> Self {\n        let mut builder = SchemaBuilder::new();\n        let trigram = TextOptions::default().set_stored().set_indexing_options(\n            TextFieldIndexing::default()\n                .set_tokenizer(\"trigram\")\n                .set_index_option(IndexRecordOption::WithFreqsAndPositions),\n        );\n        let raw = TextOptions::default().set_stored().set_indexing_options(\n            TextFieldIndexing::default()\n                .set_tokenizer(\"raw\")\n                .set_index_option(IndexRecordOption::WithFreqsAndPositions),\n        );\n\n        let doc_id = builder.add_i64_field(\"doc_id\", FAST | STORED | INDEXED);\n        let point_id = builder.add_text_field(\"point_id\", raw.clone());\n        let doc_source = builder.add_text_field(\"doc_source\", STORED);\n        let doc_title = builder.add_text_field(\"doc_title\", TEXT | STORED);\n        let doc_description = builder.add_text_field(\"doc_description\", TEXT | STORED);\n        let relative_url = builder.add_text_field(\"relative_url\", raw.clone());\n        let absolute_url = builder.add_text_field(\"absolute_url\", raw);\n        let header = builder.add_text_field(\"header\", trigram.clone());\n        let ancestry = builder.add_text_field(\"ancestry\", trigram.clone());\n        let text = builder.add_text_field(\"text\", trigram);\n        let start_byte = builder.add_u64_field(\"start_byte\", STORED);\n        let end_byte = builder.add_u64_field(\"end_byte\", STORED);\n        let section_depth = builder.add_u64_field(\"section_depth\", FAST | STORED);\n\n        let raw_relative_url = builder.add_bytes_field(\"raw_relative_url\", FAST | STORED | INDEXED);\n\n        Self {\n            doc_id,\n            point_id,\n            doc_source,\n            doc_title,\n            doc_description,\n            relative_url,\n            absolute_url,\n            header,\n            ancestry,\n            text,\n            start_byte,\n            end_byte,\n            section_depth,\n            raw_relative_url,\n            schema: builder.build(),\n        }\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/indexes.rs",
    "content": "use std::{fs, ops::Deref, path::Path, sync::Arc};\n\nuse anyhow::{Context, Result};\nuse async_trait::async_trait;\nuse smallvec::SmallVec;\nuse tantivy::{\n    collector::{Collector, MultiFruit},\n    schema::Schema,\n    tokenizer::NgramTokenizer,\n    DocAddress, Document, IndexReader, IndexWriter, Score,\n};\n\nmod analytics;\npub mod doc;\npub mod file;\npub mod reader;\npub mod repo;\nmod schema;\n\npub use doc::Doc;\npub use file::File;\npub use repo::Repo;\nuse tracing::debug;\n\nuse crate::{\n    background::SyncHandle,\n    query::parser::Query,\n    repo::{RepoError, RepoMetadata, Repository},\n    Configuration,\n};\n\npub type GlobalWriteHandleRef<'a> = [IndexWriteHandle<'a>];\n\npub struct GlobalWriteHandle<'a> {\n    handles: Vec<IndexWriteHandle<'a>>,\n    _write_lock: tokio::sync::MutexGuard<'a, ()>,\n}\n\nimpl<'a> Deref for GlobalWriteHandle<'a> {\n    type Target = GlobalWriteHandleRef<'a>;\n\n    fn deref(&self) -> &Self::Target {\n        &self.handles\n    }\n}\n\nimpl<'a> GlobalWriteHandle<'a> {\n    pub(crate) fn rollback(self) -> Result<()> {\n        for mut handle in self.handles {\n            handle.rollback()?\n        }\n\n        Ok(())\n    }\n\n    pub(crate) fn commit(self) -> Result<()> {\n        for mut handle in self.handles {\n            handle.commit()?\n        }\n\n        Ok(())\n    }\n\n    pub(crate) async fn index(\n        &self,\n        sync_handle: &SyncHandle,\n        repo: &Repository,\n    ) -> Result<Arc<RepoMetadata>, RepoError> {\n        let metadata = repo.get_repo_metadata().await;\n\n        for h in &self.handles {\n            h.index(sync_handle, repo, &metadata).await?;\n        }\n\n        Ok(metadata)\n    }\n}\n\npub struct Indexes {\n    pub repo: Indexer<Repo>,\n    pub file: Indexer<File>,\n    pub doc: Doc,\n    was_index_reset: bool,\n    write_mutex: tokio::sync::Mutex<()>,\n}\n\nimpl Indexes {\n    pub async fn new(\n        config: &Configuration,\n        sql: crate::SqlDb,\n        was_index_reset: bool,\n    ) -> Result<Self> {\n        Ok(Self {\n            repo: Indexer::create(\n                Repo::new(),\n                config.index_path(\"repo\").as_ref(),\n                config.repo_buffer_size,\n                config.max_threads,\n            )?,\n            file: Indexer::create(\n                File::new(),\n                config.index_path(\"content\").as_ref(),\n                config.buffer_size,\n                config.max_threads,\n            )?,\n            doc: Doc::create(\n                sql,\n                config.index_path(\"doc\").as_ref(),\n                config.buffer_size,\n                config.max_threads,\n            )?,\n            write_mutex: Default::default(),\n            was_index_reset,\n        })\n    }\n\n    pub fn reset_databases(config: &Configuration) -> Result<()> {\n        // we don't support old schemas, and tantivy will hard\n        // error if we try to open a db with a different schema.\n        if config.index_path(\"repo\").as_ref().exists() {\n            fs::remove_dir_all(config.index_path(\"repo\"))?;\n            debug!(\"removed index repo dir\")\n        }\n        if config.index_path(\"content\").as_ref().exists() {\n            fs::remove_dir_all(config.index_path(\"content\"))?;\n            debug!(\"removed index content dir\")\n        }\n        Ok(())\n    }\n\n    pub async fn writers(&self) -> Result<GlobalWriteHandle<'_>> {\n        let id: u64 = rand::random();\n        debug!(id, \"waiting for other writers to finish\");\n        let _write_lock = self.write_mutex.lock().await;\n        debug!(id, \"lock acquired\");\n\n        Ok(GlobalWriteHandle {\n            handles: vec![self.repo.write_handle()?, self.file.write_handle()?],\n            _write_lock,\n        })\n    }\n}\n\n#[async_trait]\npub trait Indexable: Send + Sync {\n    /// This is where files are scanned and indexed.\n    async fn index_repository(\n        &self,\n        handle: &SyncHandle,\n        repo: &Repository,\n        metadata: &RepoMetadata,\n        writer: &IndexWriter,\n    ) -> Result<()>;\n\n    fn delete_by_repo(&self, writer: &IndexWriter, repo: &Repository);\n\n    /// Return the tantivy `Schema` of the current index\n    fn schema(&self) -> Schema;\n}\n\n#[async_trait]\npub trait DocumentRead: Send + Sync {\n    type Schema;\n    type Document;\n\n    /// Return whether this reader can process this query.\n    fn query_matches(&self, query: &Query<'_>) -> bool;\n\n    /// Compile a set of parsed queries into a single `tantivy` query.\n    fn compile<'a, I>(\n        &self,\n        schema: &Self::Schema,\n        queries: I,\n        index: &tantivy::Index,\n    ) -> Result<Box<dyn tantivy::query::Query>>\n    where\n        I: Iterator<Item = &'a Query<'a>>;\n\n    /// Read a tantivy document into the specified output type.\n    fn read_document(&self, schema: &Self::Schema, doc: Document) -> Self::Document;\n}\n\npub struct IndexWriteHandle<'a> {\n    source: &'a dyn Indexable,\n    reader: &'a IndexReader,\n    writer: IndexWriter,\n}\n\nimpl<'a> IndexWriteHandle<'a> {\n    pub fn delete(&self, repo: &Repository) {\n        self.source.delete_by_repo(&self.writer, repo)\n    }\n\n    pub async fn index(\n        &self,\n        handle: &SyncHandle,\n        repo: &Repository,\n        metadata: &RepoMetadata,\n    ) -> Result<()> {\n        self.source\n            .index_repository(handle, repo, metadata, &self.writer)\n            .await\n    }\n\n    pub fn commit(&mut self) -> Result<()> {\n        self.writer.commit()?;\n        self.reader.reload()?;\n\n        Ok(())\n    }\n\n    pub fn rollback(&mut self) -> Result<()> {\n        self.writer.rollback()?;\n        Ok(())\n    }\n}\n\n/// A wrapper around `tantivy::IndexReader`.\n///\n/// This contains the schema, and also additional fields used to enable re-indexing.\npub struct Indexer<T> {\n    pub source: T,\n    pub index: tantivy::Index,\n    pub reader: IndexReader,\n    pub reindex_buffer_size: usize,\n    pub reindex_threads: usize,\n}\n\nimpl<T: Indexable> Indexer<T> {\n    fn write_handle(&self) -> Result<IndexWriteHandle<'_>> {\n        Ok(IndexWriteHandle {\n            source: &self.source,\n            reader: &self.reader,\n            writer: self\n                .index\n                .writer_with_num_threads(self.reindex_threads, self.reindex_buffer_size)?,\n        })\n    }\n\n    fn init_index(schema: Schema, path: &Path, threads: usize) -> Result<tantivy::Index> {\n        fs::create_dir_all(path).context(\"failed to create index dir\")?;\n\n        let mut index =\n            tantivy::Index::open_or_create(tantivy::directory::MmapDirectory::open(path)?, schema)?;\n\n        index.set_multithread_executor(threads)?;\n        index\n            .tokenizers()\n            .register(\"default\", NgramTokenizer::new(1, 3, false)?);\n\n        Ok(index)\n    }\n\n    /// Create an index using `source` at the specified path.\n    pub fn create(source: T, path: &Path, buffer_size: usize, threads: usize) -> Result<Self> {\n        let index = Self::init_index(source.schema(), path, threads)?;\n        let reader = index.reader()?;\n        let instance = Self {\n            reader,\n            index,\n            source,\n            reindex_threads: threads,\n            reindex_buffer_size: buffer_size,\n        };\n\n        Ok(instance)\n    }\n\n    pub async fn query<'a, R, I, C>(\n        &'a self,\n        queries: I,\n        doc_reader: &'a R,\n        collector: C,\n    ) -> Result<SearchResults<'_, R::Document>>\n    where\n        I: Iterator<Item = &'a Query<'a>> + Send,\n        C: Collector<Fruit = (Vec<(Score, DocAddress)>, MultiFruit)>,\n        R: DocumentRead<Schema = T>,\n    {\n        let searcher = self.reader.searcher();\n        let queries = queries\n            .filter(|q| doc_reader.query_matches(q))\n            .collect::<SmallVec<[_; 2]>>();\n        let compiled_query =\n            doc_reader.compile(&self.source, queries.iter().copied(), &self.index)?;\n\n        let (top_k, metadata) = searcher\n            .search(&compiled_query, &collector)\n            .context(\"failed to execute search query\")?;\n\n        let iter = top_k.into_iter().map(move |(_score, addr)| {\n            let doc = searcher.doc(addr).unwrap();\n            doc_reader.read_document(&self.source, doc)\n        });\n\n        Ok(SearchResults {\n            docs: Box::new(iter),\n            metadata,\n        })\n    }\n}\n\npub struct SearchResults<'a, T> {\n    pub docs: Box<dyn Iterator<Item = T> + Sync + Send + 'a>,\n    pub metadata: MultiFruit,\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence/code_navigation.rs",
    "content": "//! Handlers for code-navigation:\n//! - scope-graph based handler that operates only in the owning file\n//! - search based handler that operates on any file belonging to the repo\n\nuse std::{collections::HashSet, ops::Not};\n\nuse super::NodeKind;\nuse crate::{\n    indexes::reader::ContentDocument,\n    repo::RepoRef,\n    snippet::{Snipper, Snippet},\n    text_range::TextRange,\n};\n\nuse rayon::prelude::*;\nuse serde::Serialize;\n\n#[derive(Debug, Serialize)]\npub struct FileSymbols {\n    /// The file to which the following occurrences belong\n    pub file: String,\n\n    pub repo: RepoRef,\n\n    /// A collection of symbol locations with context in this file\n    pub data: Vec<Occurrence>,\n}\n\n#[derive(Serialize, Debug)]\npub struct Occurrence {\n    pub kind: OccurrenceKind,\n    pub range: TextRange,\n    pub snippet: Snippet,\n}\n\nimpl Occurrence {\n    pub fn is_definition(&self) -> bool {\n        matches!(self.kind, OccurrenceKind::Definition)\n    }\n}\n\n#[derive(Serialize, Debug, Default)]\n#[serde(rename_all = \"snake_case\")]\npub enum OccurrenceKind {\n    #[default]\n    Reference,\n    Definition,\n}\n\npub enum CodeNavigationError {}\n\npub struct CodeNavigationContext<'a, 'b> {\n    pub token: Token<'a>,\n    pub all_docs: &'b [ContentDocument],\n    pub source_document_idx: usize,\n    pub snipper: Option<Snipper>,\n}\n\nimpl<'a, 'b> CodeNavigationContext<'a, 'b> {\n    /// Produces a list of docs that import items from source_document\n    ///\n    /// This works by going through every definition node in source_document, calculating\n    /// repo-wide references for all such nodes, and gathering the resulting file-set.\n    pub fn files_importing(\n        all_docs: &'b [ContentDocument],\n        source_document_idx: usize,\n    ) -> HashSet<&'b ContentDocument> {\n        // scope graph of the source document\n        let source_doc = all_docs.get(source_document_idx).unwrap();\n        let Some(source_sg) = source_doc.symbol_locations.scope_graph() else {\n            return HashSet::default();\n        };\n        source_sg\n            .graph\n            .node_indices()\n            .par_bridge()\n            .filter(|idx| source_sg.is_definition(*idx) && source_sg.is_top_level(*idx))\n            .flat_map_iter(|idx| {\n                let range = source_sg.graph[idx].range();\n                let token = Token {\n                    repo: source_doc.repo_ref.parse().unwrap(),\n                    relative_path: &source_doc.relative_path,\n                    start_byte: range.start.byte,\n                    end_byte: range.end.byte,\n                };\n                let active_token_range = token.start_byte..token.end_byte;\n                let active_token_text =\n                    source_doc.content.as_str().get(active_token_range).unwrap();\n                all_docs\n                    .par_iter()\n                    .filter(|doc| doc.relative_path != source_doc.relative_path)\n                    .filter(|doc| {\n                        let Some(scope_graph) = doc.symbol_locations.scope_graph() else {\n                            return false;\n                        };\n                        let content = doc.content.as_bytes();\n                        scope_graph\n                            .graph\n                            .node_indices()\n                            .par_bridge()\n                            .filter(|&idx| scope_graph.is_top_level(idx))\n                            .any(|idx| match scope_graph.get_node(idx).unwrap() {\n                                NodeKind::Import(n) => {\n                                    n.name(content) == active_token_text.as_bytes()\n                                }\n                                _ => false,\n                            })\n                    })\n                    .collect::<Vec<_>>()\n            })\n            .collect()\n    }\n\n    /// Produces a list of docs that are imported in source_document\n    ///\n    /// This works by going through every reference or import node in the current file, calculating\n    /// its non-local definition (if any) and gathering the resuting file-set.\n    pub fn files_imported(\n        all_docs: &'b [ContentDocument],\n        source_document_idx: usize,\n    ) -> HashSet<&'b ContentDocument> {\n        // scope graph of the source document\n        let source_doc = all_docs.get(source_document_idx).unwrap();\n        let Some(source_sg) = source_doc.symbol_locations.scope_graph() else {\n            return HashSet::default();\n        };\n\n        source_sg\n            .graph\n            .node_indices()\n            .par_bridge()\n            .filter(|idx| source_sg.is_reference(*idx) || source_sg.is_import(*idx))\n            .filter(|&idx| {\n                CodeNavigationContext {\n                    all_docs,\n                    source_document_idx,\n                    token: Token {\n                        repo: source_doc.repo_ref.parse().unwrap(),\n                        relative_path: &source_doc.relative_path,\n                        start_byte: source_sg.graph[idx].range().start.byte,\n                        end_byte: source_sg.graph[idx].range().end.byte,\n                    },\n                    snipper: None,\n                }\n                .local_definitions()\n                .is_none()\n            })\n            .flat_map_iter(|idx| {\n                let range = source_sg.graph[idx].range();\n                let token = Token {\n                    repo: source_doc.repo_ref.parse().unwrap(),\n                    relative_path: &source_doc.relative_path,\n                    start_byte: range.start.byte,\n                    end_byte: range.end.byte,\n                };\n                let active_token_range = token.start_byte..token.end_byte;\n                let active_token_text =\n                    source_doc.content.as_str().get(active_token_range).unwrap();\n                all_docs\n                    .par_iter()\n                    .filter(|doc| doc.relative_path != source_doc.relative_path)\n                    .filter(|doc| {\n                        let Some(scope_graph) = doc.symbol_locations.scope_graph() else {\n                            return false;\n                        };\n                        let content = doc.content.as_bytes();\n                        scope_graph\n                            .graph\n                            .node_indices()\n                            .par_bridge()\n                            .filter(|idx| scope_graph.is_top_level(*idx))\n                            .any(|idx| {\n                                if let Some(NodeKind::Def(d)) = scope_graph.get_node(idx) {\n                                    d.name(content) == active_token_text.as_bytes()\n                                } else {\n                                    false\n                                }\n                            })\n                    })\n                    .collect::<Vec<_>>()\n            })\n            .collect::<HashSet<_>>()\n    }\n\n    fn singleton(source_document: &'b ContentDocument, token: Token<'a>) -> Self {\n        Self {\n            all_docs: std::slice::from_ref(source_document),\n            source_document_idx: 0,\n            token,\n            snipper: None,\n        }\n    }\n\n    fn source_document(&self) -> &ContentDocument {\n        self.all_docs.get(self.source_document_idx).unwrap()\n    }\n\n    pub fn token_info(&self) -> Vec<FileSymbols> {\n        if self.is_definition() {\n            let local_references = self.local_references();\n            let repo_wide_references = self\n                .is_top_level()\n                .then(|| self.repo_wide_references())\n                .unwrap_or_default();\n\n            local_references\n                .into_iter()\n                .chain(repo_wide_references)\n                .collect()\n        } else if self.is_reference() {\n            let local_definitions = self.local_definitions();\n            let repo_wide_definitions = local_definitions\n                .is_none()\n                .then(|| self.repo_wide_definitions())\n                .unwrap_or_default();\n\n            let local_references = self.local_references();\n            let repo_wide_references = local_definitions\n                .is_none()\n                .then(|| self.repo_wide_references())\n                .unwrap_or_default();\n\n            let imports = self.imports();\n\n            local_definitions\n                .or(imports)\n                .into_iter()\n                .chain(repo_wide_definitions)\n                .chain(local_references)\n                .chain(repo_wide_references)\n                .collect()\n        } else if self.is_import() {\n            let local_references = self.local_references();\n            let repo_wide_definitions = self.repo_wide_definitions();\n\n            repo_wide_definitions\n                .into_iter()\n                .chain(local_references)\n                .collect()\n        } else {\n            Vec::new()\n        }\n    }\n\n    fn is_definition(&self) -> bool {\n        self.source_document()\n            .symbol_locations\n            .scope_graph()\n            .and_then(|sg| {\n                let idx = sg.node_by_range(self.token.start_byte, self.token.end_byte)?;\n                Some(matches!(sg.get_node(idx).unwrap(), NodeKind::Def(_)))\n            })\n            .unwrap_or_default()\n    }\n\n    fn is_reference(&self) -> bool {\n        self.source_document()\n            .symbol_locations\n            .scope_graph()\n            .and_then(|sg| {\n                let idx = sg.node_by_range(self.token.start_byte, self.token.end_byte)?;\n                Some(matches!(sg.get_node(idx).unwrap(), NodeKind::Ref(_)))\n            })\n            .unwrap_or_default()\n    }\n\n    fn is_import(&self) -> bool {\n        self.source_document()\n            .symbol_locations\n            .scope_graph()\n            .and_then(|sg| {\n                let idx = sg.node_by_range(self.token.start_byte, self.token.end_byte)?;\n                Some(matches!(sg.get_node(idx).unwrap(), NodeKind::Import(_)))\n            })\n            .unwrap_or_default()\n    }\n\n    fn is_top_level(&self) -> bool {\n        self.source_document()\n            .symbol_locations\n            .scope_graph()\n            .and_then(|sg| {\n                let idx = sg.node_by_range(self.token.start_byte, self.token.end_byte)?;\n                Some(sg.is_top_level(idx))\n            })\n            .unwrap_or_default()\n    }\n\n    fn non_source_documents(&self) -> impl Iterator<Item = &ContentDocument> {\n        self.all_docs\n            .iter()\n            .filter(|doc| doc.relative_path != self.source_document().relative_path)\n    }\n\n    pub fn active_token_range(&self) -> std::ops::Range<usize> {\n        self.token.start_byte..self.token.end_byte\n    }\n\n    pub fn active_token_text(&self) -> &str {\n        self.source_document()\n            .content\n            .as_str()\n            .get(self.active_token_range())\n            .unwrap()\n    }\n\n    fn local_definitions(&self) -> Option<FileSymbols> {\n        let scope_graph = self.source_document().symbol_locations.scope_graph()?;\n        let node_idx = scope_graph.node_by_range(self.token.start_byte, self.token.end_byte)?;\n        let mut data = scope_graph\n            .definitions(node_idx)\n            .map(|idx| Occurrence {\n                kind: OccurrenceKind::Definition,\n                range: scope_graph.graph[idx].range(),\n                snippet: to_occurrence(\n                    self.source_document(),\n                    scope_graph.graph[idx].range(),\n                    self.snipper,\n                ),\n            })\n            .collect::<Vec<_>>();\n\n        data.sort_by_key(|occurrence| occurrence.range.start.byte);\n\n        data.is_empty().not().then(|| FileSymbols {\n            file: self.token.relative_path.to_owned(),\n            repo: self.token.repo.clone(),\n            data,\n        })\n    }\n\n    fn repo_wide_definitions(&self) -> Vec<FileSymbols> {\n        self.non_source_documents()\n            .par_bridge()\n            .filter_map(|doc| {\n                let scope_graph = doc.symbol_locations.scope_graph()?;\n                let content = doc.content.as_bytes();\n                let mut data = scope_graph\n                    .graph\n                    .node_indices()\n                    .filter(|idx| scope_graph.is_top_level(*idx))\n                    .filter(|idx| {\n                        if let Some(NodeKind::Def(d)) = scope_graph.get_node(*idx) {\n                            d.name(content) == self.active_token_text().as_bytes()\n                        } else {\n                            false\n                        }\n                    })\n                    .map(|idx| Occurrence {\n                        kind: OccurrenceKind::Definition,\n                        range: scope_graph.graph[idx].range(),\n                        snippet: to_occurrence(doc, scope_graph.graph[idx].range(), self.snipper),\n                    })\n                    .collect::<Vec<_>>();\n\n                data.sort_by_key(|occurrence| occurrence.range.start.byte);\n\n                data.is_empty().not().then(|| FileSymbols {\n                    file: doc.relative_path.to_owned(),\n                    repo: doc.repo_ref.parse().unwrap(),\n                    data,\n                })\n            })\n            .collect()\n    }\n\n    fn local_references(&self) -> Option<FileSymbols> {\n        let scope_graph = self.source_document().symbol_locations.scope_graph()?;\n        let node_idx = scope_graph.node_by_range(self.token.start_byte, self.token.end_byte)?;\n        let mut data = scope_graph\n            .definitions(node_idx)\n            .chain(scope_graph.imports(node_idx))\n            .flat_map(|idx| scope_graph.references(idx))\n            .chain(scope_graph.references(node_idx))\n            .map(|idx| Occurrence {\n                kind: OccurrenceKind::Reference,\n                range: scope_graph.graph[idx].range(),\n                snippet: to_occurrence(\n                    self.source_document(),\n                    scope_graph.graph[idx].range(),\n                    self.snipper,\n                ),\n            })\n            .collect::<Vec<_>>();\n\n        data.retain(|occurrence| {\n            occurrence.range != scope_graph.get_node(node_idx).unwrap().range()\n        });\n        data.sort_by_key(|occurrence| occurrence.range.start.byte);\n\n        data.is_empty().not().then(|| FileSymbols {\n            file: self.token.relative_path.to_owned(),\n            repo: self.token.repo.clone(),\n            data,\n        })\n    }\n\n    fn repo_wide_references(&self) -> Vec<FileSymbols> {\n        self.non_source_documents()\n            .par_bridge()\n            .filter_map(|doc| {\n                let scope_graph = doc.symbol_locations.scope_graph()?;\n                let content = doc.content.as_bytes();\n                let mut data = scope_graph\n                    .graph\n                    .node_indices()\n                    .filter(|idx| scope_graph.is_top_level(*idx))\n                    .filter(|idx| match scope_graph.get_node(*idx).unwrap() {\n                        NodeKind::Def(n) => n.name(content) == self.active_token_text().as_bytes(),\n                        NodeKind::Import(n) => {\n                            n.name(content) == self.active_token_text().as_bytes()\n                        }\n                        _ => false,\n                    })\n                    .flat_map(|idx| scope_graph.references(idx))\n                    .map(|idx| Occurrence {\n                        kind: OccurrenceKind::Reference,\n                        range: scope_graph.graph[idx].range(),\n                        snippet: to_occurrence(doc, scope_graph.graph[idx].range(), self.snipper),\n                    })\n                    .collect::<Vec<_>>();\n\n                data.sort_by_key(|occurrence| occurrence.range.start.byte);\n\n                data.is_empty().not().then(|| FileSymbols {\n                    file: doc.relative_path.to_owned(),\n                    repo: doc.repo_ref.parse().unwrap(),\n                    data,\n                })\n            })\n            .collect()\n    }\n\n    fn imports(&self) -> Option<FileSymbols> {\n        let scope_graph = self.source_document().symbol_locations.scope_graph()?;\n        let node_idx = scope_graph.node_by_range(self.token.start_byte, self.token.end_byte)?;\n        let mut data = scope_graph\n            .imports(node_idx)\n            .map(|idx| Occurrence {\n                kind: OccurrenceKind::Definition,\n                range: scope_graph.graph[idx].range(),\n                snippet: to_occurrence(\n                    self.source_document(),\n                    scope_graph.graph[idx].range(),\n                    self.snipper,\n                ),\n            })\n            .collect::<Vec<_>>();\n\n        data.sort_by_key(|occurrence| occurrence.range.start.byte);\n\n        data.is_empty().not().then(|| FileSymbols {\n            file: self.token.relative_path.to_owned(),\n            repo: self.token.repo.clone(),\n            data,\n        })\n    }\n}\n\npub struct Token<'a> {\n    pub repo: RepoRef,\n    pub relative_path: &'a str,\n    pub start_byte: usize,\n    pub end_byte: usize,\n}\n\nfn to_occurrence(doc: &ContentDocument, range: TextRange, snipper: Option<Snipper>) -> Snippet {\n    let src = &doc.content;\n    let line_end_indices = &doc.line_end_indices;\n    let highlight = range.start.byte..range.end.byte;\n    snipper\n        .unwrap_or_default()\n        .expand(highlight, src, line_end_indices)\n        .reify(src, &[])\n}\n\n// ranges of defs in related_file_document used in source_document\npub fn imported_ranges(\n    source_document: &ContentDocument,\n    related_file_document: &ContentDocument,\n) -> HashSet<TextRange> {\n    // scope graph of the source document\n    let Some(source_sg) = source_document.symbol_locations.scope_graph() else {\n        return HashSet::new();\n    };\n\n    // scope graph of the related_file document\n    let Some(related_file_sg) = related_file_document.symbol_locations.scope_graph() else {\n        return HashSet::new();\n    };\n    let related_file_content = &related_file_document.content;\n\n    source_sg\n        .graph\n        .node_indices()\n        .par_bridge()\n        .filter(|idx| source_sg.is_reference(*idx) || source_sg.is_import(*idx))\n        .filter(|&idx| {\n            let token = Token {\n                repo: source_document.repo_ref.parse().unwrap(),\n                relative_path: &source_document.relative_path,\n                start_byte: source_sg.graph[idx].range().start.byte,\n                end_byte: source_sg.graph[idx].range().end.byte,\n            };\n            (CodeNavigationContext::singleton(source_document, token))\n                .local_definitions()\n                .is_none()\n        })\n        .flat_map(|idx| {\n            let range = source_sg.graph[idx].range();\n            let token = Token {\n                repo: source_document.repo_ref.parse().unwrap(),\n                relative_path: &source_document.relative_path,\n                start_byte: range.start.byte,\n                end_byte: range.end.byte,\n            };\n            let active_token_range = token.start_byte..token.end_byte;\n            let active_token_text = source_document\n                .content\n                .as_str()\n                .get(active_token_range)\n                .unwrap();\n\n            related_file_sg\n                .graph\n                .node_indices()\n                .par_bridge()\n                .filter(|idx| related_file_sg.is_top_level(*idx))\n                .filter(|idx| {\n                    if let Some(NodeKind::Def(d)) = related_file_sg.get_node(*idx) {\n                        d.name(related_file_content.as_bytes()) == active_token_text.as_bytes()\n                    } else {\n                        false\n                    }\n                })\n                .filter_map(|idx| related_file_sg.value_of_definition(idx))\n                .map(|idx| related_file_sg.graph[idx].range())\n        })\n        .collect()\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/c/mod.rs",
    "content": "use crate::intelligence::{MemoizedQuery, TSLanguageConfig};\n\npub static C: TSLanguageConfig = TSLanguageConfig {\n    language_ids: &[\"C\"],\n    file_extensions: &[\"c\", \"h\"],\n    grammar: tree_sitter_c::language,\n    scope_query: MemoizedQuery::new(include_str!(\"./scopes.scm\")),\n    hoverable_query: MemoizedQuery::new(\n        r\"\n        [(identifier)\n        (field_identifier)\n        (statement_identifier)\n        (type_identifier)] @hoverable\n        \",\n    ),\n    namespaces: &[&[\n        // imports\n        \"header\",\n        // functions\n        \"macro\",\n        \"function\",\n        // types\n        \"struct\",\n        \"enum\",\n        \"enumerator\",\n        \"union\",\n        \"typedef\",\n        // variables\n        \"variable\",\n        // misc.\n        \"label\",\n    ]],\n};\n\n#[cfg(test)]\nmod tests {\n    use crate::intelligence::language::test_utils::*;\n\n    #[test]\n    fn trivial() {\n        test_scopes(\n            \"C\",\n            r#\"\n            #include <stdio.h>\n\n            #define PI 355/113\n            #define AREA(r) PI * r * r\n\n            int main() {\n                int radius = 5;\n                printf(\"%d\", AREA(radius));\n            }\n            \"#\n            .as_bytes(),\n            expect![[r##\"\n                scope {\n                    definitions: [\n                        <stdio.h> {\n                            kind: \"header\",\n                            context: \"#include §<stdio.h>§\",\n                        },\n                        PI {\n                            kind: \"macro\",\n                            context: \"#define §PI§ 355/113\",\n                        },\n                        AREA {\n                            kind: \"macro\",\n                            context: \"#define §AREA§(r) PI * r * r\",\n                            referenced in (1): [\n                                `printf(\"%d\", §AREA§(radius));`,\n                            ],\n                        },\n                        main {\n                            kind: \"function\",\n                            context: \"int §main§() {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        radius {\n                                            kind: \"variable\",\n                                            context: \"int §radius§ = 5;\",\n                                            referenced in (1): [\n                                                `printf(\"%d\", AREA(§radius§));`,\n                                            ],\n                                        },\n                                    ],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"##]],\n        )\n    }\n\n    #[test]\n    fn declarations() {\n        test_scopes(\n            \"C\",\n            r#\"\n            int main() {\n                int a;\n                int *b;\n                struct S c;\n                struct S *d;\n                T1 *e(T2);\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        main {\n                            kind: \"function\",\n                            context: \"int §main§() {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        a {\n                                            kind: \"variable\",\n                                            context: \"int §a§;\",\n                                        },\n                                        b {\n                                            kind: \"variable\",\n                                            context: \"int *§b§;\",\n                                        },\n                                        c {\n                                            kind: \"variable\",\n                                            context: \"struct S §c§;\",\n                                        },\n                                        d {\n                                            kind: \"variable\",\n                                            context: \"struct S *§d§;\",\n                                        },\n                                        e {\n                                            kind: \"function\",\n                                            context: \"T1 *§e§(T2);\",\n                                        },\n                                    ],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [],\n                                        },\n                                    ],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn types() {\n        test_scopes(\n            \"C\",\n            r#\"\n\n            // defs\n            struct A {\n                int e;\n            };\n\n            enum B {\n                C, D, E\n            };\n\n            union F {\n                int x;\n                char *y;\n            };\n\n            typedef struct {\n                int x;\n            } G;\n\n            // refs\n            struct A *main(enum B b, void* g) {\n                union F *f = foo((struct G*) g);\n                switch (b) {\n                    case C:\n                    case D:\n                    case E:\n                }\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        A {\n                            kind: \"struct\",\n                            context: \"struct §A§ {\",\n                            referenced in (1): [\n                                `struct §A§ *main(enum B b, void* g) {`,\n                            ],\n                        },\n                        B {\n                            kind: \"enum\",\n                            context: \"enum §B§ {\",\n                            referenced in (1): [\n                                `struct A *main(enum §B§ b, void* g) {`,\n                            ],\n                        },\n                        C {\n                            kind: \"enumerator\",\n                            context: \"§C§, D, E\",\n                            referenced in (1): [\n                                `case §C§:`,\n                            ],\n                        },\n                        D {\n                            kind: \"enumerator\",\n                            context: \"C, §D§, E\",\n                            referenced in (1): [\n                                `case §D§:`,\n                            ],\n                        },\n                        E {\n                            kind: \"enumerator\",\n                            context: \"C, D, §E§\",\n                            referenced in (1): [\n                                `case §E§:`,\n                            ],\n                        },\n                        F {\n                            kind: \"union\",\n                            context: \"union §F§ {\",\n                            referenced in (1): [\n                                `union §F§ *f = foo((struct G*) g);`,\n                            ],\n                        },\n                        G {\n                            kind: \"typedef\",\n                            context: \"} §G§;\",\n                            referenced in (1): [\n                                `union F *f = foo((struct §G§*) g);`,\n                            ],\n                        },\n                        main {\n                            kind: \"function\",\n                            context: \"struct A *§main§(enum B b, void* g) {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                        scope {\n                            definitions: [\n                                b {\n                                    kind: \"variable\",\n                                    context: \"struct A *main(enum B §b§, void* g) {\",\n                                    referenced in (1): [\n                                        `switch (§b§) {`,\n                                    ],\n                                },\n                                g {\n                                    kind: \"variable\",\n                                    context: \"struct A *main(enum B b, void* §g§) {\",\n                                    referenced in (1): [\n                                        `union F *f = foo((struct G*) §g§);`,\n                                    ],\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        f {\n                                            kind: \"variable\",\n                                            context: \"union F *§f§ = foo((struct G*) g);\",\n                                        },\n                                    ],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [\n                                                scope {\n                                                    definitions: [],\n                                                    child scopes: [],\n                                                },\n                                                scope {\n                                                    definitions: [],\n                                                    child scopes: [],\n                                                },\n                                                scope {\n                                                    definitions: [],\n                                                    child scopes: [],\n                                                },\n                                            ],\n                                        },\n                                    ],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn function_parameters() {\n        test_scopes(\n            \"C\",\n            r#\"\n            void main(int argc, char **argv) {}\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        main {\n                            kind: \"function\",\n                            context: \"void §main§(int argc, char **argv) {}\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                argc {\n                                    kind: \"variable\",\n                                    context: \"void main(int §argc§, char **argv) {}\",\n                                },\n                                argv {\n                                    kind: \"variable\",\n                                    context: \"void main(int argc, char **§argv§) {}\",\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        );\n    }\n\n    // const sizes in array declarations should be refs\n    #[test]\n    fn const_dimension_array_declaration_regression() {\n        test_scopes(\n            \"C\",\n            r#\"\n            #define CLUSTER_SLOTS 16384\n\n            typedef struct clusterState {\n                clusterNode *migrating_slots_to[CLUSTER_SLOTS];\n            } clusterState;\n            \"#\n            .as_bytes(),\n            expect![[r##\"\n                scope {\n                    definitions: [\n                        CLUSTER_SLOTS {\n                            kind: \"macro\",\n                            context: \"#define §CLUSTER_SLOTS§ 16384\",\n                            referenced in (1): [\n                                `clusterNode *migrating_slots_to[§CLUSTER_SLOTS§];`,\n                            ],\n                        },\n                        clusterState {\n                            kind: \"struct\",\n                            context: \"typedef struct §clusterState§ {\",\n                        },\n                        clusterState {\n                            kind: \"typedef\",\n                            context: \"} §clusterState§;\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                migrating_slots_to {\n                                    kind: \"variable\",\n                                    context: \"clusterNode *§migrating_slots_to§[CLUSTER_SLOTS];\",\n                                },\n                            ],\n                            child scopes: [],\n                        },\n                    ],\n                }\n            \"##]],\n        )\n    }\n\n    // handle params correctly\n    #[test]\n    fn unresolved_function_parameters() {\n        test_scopes(\n            \"C\",\n            r#\"\n            sds getNewBaseFileNameAndMarkPreAsHistory(aofManifest *am) {\n                serverAssert(am != NULL);\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        getNewBaseFileNameAndMarkPreAsHistory {\n                            kind: \"function\",\n                            context: \"sds §getNewBaseFileNameAndMarkPreAsHistory§(aofManifest *am) {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                am {\n                                    kind: \"variable\",\n                                    context: \"sds getNewBaseFileNameAndMarkPreAsHistory(aofManifest *§am§) {\",\n                                    referenced in (1): [\n                                        `serverAssert(§am§ != NULL);`,\n                                    ],\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    // rhs of declarators should be references and not defs\n    #[test]\n    fn declarator_rhs_is_reference() {\n        test_scopes(\n            \"C\",\n            r#\"\n            void main(const char *pe) {\n                const char *end = pe + ind;\n                const char *curr = pe;\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        main {\n                            kind: \"function\",\n                            context: \"void §main§(const char *pe) {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                pe {\n                                    kind: \"variable\",\n                                    context: \"void main(const char *§pe§) {\",\n                                    referenced in (2): [\n                                        `const char *end = §pe§ + ind;`,\n                                        `const char *curr = §pe§;`,\n                                    ],\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        end {\n                                            kind: \"variable\",\n                                            context: \"const char *§end§ = pe + ind;\",\n                                        },\n                                        curr {\n                                            kind: \"variable\",\n                                            context: \"const char *§curr§ = pe;\",\n                                        },\n                                    ],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn function_prototype_vs_function_definition() {\n        test_scopes(\n            \"C\",\n            r#\"\n            void *foo(int *am, int ip);\n            void *foo(int *am, int ip) {\n                *am + ip;\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        foo {\n                            kind: \"function\",\n                            context: \"void *§foo§(int *am, int ip);\",\n                        },\n                        foo {\n                            kind: \"function\",\n                            context: \"void *§foo§(int *am, int ip) {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                am {\n                                    kind: \"variable\",\n                                    context: \"void *foo(int *§am§, int ip);\",\n                                },\n                                ip {\n                                    kind: \"variable\",\n                                    context: \"void *foo(int *am, int §ip§);\",\n                                },\n                            ],\n                            child scopes: [],\n                        },\n                        scope {\n                            definitions: [\n                                am {\n                                    kind: \"variable\",\n                                    context: \"void *foo(int *§am§, int ip) {\",\n                                    referenced in (1): [\n                                        `*§am§ + ip;`,\n                                    ],\n                                },\n                                ip {\n                                    kind: \"variable\",\n                                    context: \"void *foo(int *am, int §ip§) {\",\n                                    referenced in (1): [\n                                        `*am + §ip§;`,\n                                    ],\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn type_refs_in_field_declarations() {\n        test_scopes(\n            \"C\",\n            r#\"\n            typedef enum {\n                CONN_STATE_NONE = 0,\n            } ConnectionState;\n\n            struct connection {\n                ConnectionState state;\n            };\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        CONN_STATE_NONE {\n                            kind: \"enumerator\",\n                            context: \"§CONN_STATE_NONE§ = 0,\",\n                        },\n                        ConnectionState {\n                            kind: \"typedef\",\n                            context: \"} §ConnectionState§;\",\n                            referenced in (1): [\n                                `§ConnectionState§ state;`,\n                            ],\n                        },\n                        connection {\n                            kind: \"struct\",\n                            context: \"struct §connection§ {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn bug_report_ternary_expression() {\n        test_scopes(\n            \"C\",\n            r#\"\n            int main() {\n                int a, b, c;\n                a ? b : c;\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        main {\n                            kind: \"function\",\n                            context: \"int §main§() {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        a {\n                                            kind: \"variable\",\n                                            context: \"int §a§, b, c;\",\n                                            referenced in (1): [\n                                                `§a§ ? b : c;`,\n                                            ],\n                                        },\n                                        b {\n                                            kind: \"variable\",\n                                            context: \"int a, §b§, c;\",\n                                            referenced in (1): [\n                                                `a ? §b§ : c;`,\n                                            ],\n                                        },\n                                        c {\n                                            kind: \"variable\",\n                                            context: \"int a, b, §c§;\",\n                                            referenced in (1): [\n                                                `a ? b : §c§;`,\n                                            ],\n                                        },\n                                    ],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/c/scopes.scm",
    "content": ";; scopes\n\n;; blocks\n(compound_statement) @local.scope\n(for_statement) @local.scope\n(case_statement) @local.scope\n(field_declaration_list) @local.scope\n\n;; functions are finicky, the are of two forms:\n;;\n;; 1. \"declaration\" with a \"function_declarator\" descendant\n;; 2. \"function_definition\" node\n\n;; 1. function prototypes\n;; these further seem to appear in two forms:\n;;\n;;    type ident(...);\n;;    // or\n;;    type *ident(...);\n;;\n(declaration \n  [(function_declarator) @local.scope\n   (pointer_declarator\n     (function_declarator) @local.scope)])\n\n;; 2. function definitions\n(function_definition) @local.scope\n\n;; similar logic applies to typedefs with functions\n;; in them\n(type_definition\n  (function_declarator) @local.scope)\n\n\n;; defs\n\n;; #include <lib.h>\n(preproc_include\n  [(system_lib_string)\n   (string_literal)] @local.definition.header)\n\n;; #define PI 355/113\n(preproc_def\n  (identifier) @local.definition.macro)\n\n;; #define AREA(r) PI * r * r\n(preproc_function_def\n  (identifier) @local.definition.macro)\n\n;; a[SIZE] = {1, 2, ..}\n(array_declarator\n declarator: (identifier) @local.definition.variable)\n(array_declarator\n declarator: (field_identifier) @local.definition.variable)\n\n;; int (a) = 2;\n(parenthesized_declarator\n (identifier) @local.definition.variable)\n\n;; int *a = b;\n(pointer_declarator\n (identifier) @local.definition.variable)\n\n(declaration\n  (identifier) @local.definition.variable)\n\n(declaration\n  (init_declarator\n    declarator: (identifier) @local.definition.variable))\n\n(parameter_declaration\n  (identifier) @local.definition.variable)\n\n;; structs\n(struct_specifier\n  name: (type_identifier) @local.definition.struct\n  body: (_))\n\n;; unions\n(union_specifier\n  name: (type_identifier) @local.definition.union\n  body: (_))\n\n;; enums\n(enum_specifier\n  name: (type_identifier) @local.definition.enum\n  body: (_))\n(enumerator\n  name: (identifier) @local.definition.enumerator)\n\n;; typedef struct { int e; } X;\n(type_definition\n  (type_identifier) @local.definition.typedef)\n\n;; function definition\n(function_declarator\n  (identifier) @hoist.definition.function)\n\n;; labels\n(labeled_statement\n  (statement_identifier) @local.definition.label)\n\n\n\n;; refs\n\n;; abc;\n(expression_statement\n  (identifier) @local.reference)\n\n;; (abc)\n(parenthesized_expression\n  (identifier) @local.reference)\n\n;; !z\n(unary_expression\n  (identifier) @local.reference)\n\n;; a + b\n(binary_expression\n  (identifier) @local.reference)\n\n;; a ? b : c\n(conditional_expression\n  (identifier) @local.reference)\n\n;; ++a\n(update_expression\n  (identifier) @local.reference)\n\n;; call(_, _, _)\n(call_expression\n  (identifier) @local.reference)\n\n;; _(arg, arg, arg)\n(argument_list\n  (identifier) @local.reference)\n\n;; field access\n;;\n;; three types of field access:\n;; - a[b]: a and b are refs\n;; - a.b\n;; - a->b\n;;\n;; a[b]\n(subscript_expression\n  (identifier) @local.reference)\n;; a.b\n;; a->b\n(field_expression\n  .\n  (identifier) @local.reference)\n\n;; array[CONST]\n;;       ^^^^^ is a ref\n(array_declarator\n size: (identifier) @local.reference)\n\n;; comma operator\n;; (a, a++, a <= 2)\n(comma_expression\n  (identifier) @local.reference)\n\n;; ref and deref\n(pointer_expression\n  (identifier) @local.reference)\n\n;; assignment expressions\n(assignment_expression\n  (identifier) @local.reference)\n\n;; type refs in declarations\n(declaration\n  type:\n  [(struct_specifier (type_identifier) @local.reference)\n   (enum_specifier   (type_identifier) @local.reference)\n   (union_specifier  (type_identifier) @local.reference)\n                     (type_identifier) @local.reference])\n\n;; type refs in field declarations\n(field_declaration\n  type:\n  [(struct_specifier (type_identifier) @local.reference)\n   (enum_specifier   (type_identifier) @local.reference)\n   (union_specifier  (type_identifier) @local.reference)\n                     (type_identifier) @local.reference])\n\n;; type refs in return types\n(function_definition\n  type:\n  [(struct_specifier (type_identifier) @local.reference)\n   (enum_specifier   (type_identifier) @local.reference)\n   (union_specifier  (type_identifier) @local.reference)\n                     (type_identifier) @local.reference])\n\n;; type refs in params\n(parameter_declaration\n  type:\n  [(struct_specifier (type_identifier) @local.reference)\n   (enum_specifier   (type_identifier) @local.reference)\n   (union_specifier  (type_identifier) @local.reference)\n                     (type_identifier) @local.reference])\n\n;; type refs in casts\n(cast_expression\n  type: \n  (type_descriptor\n    [(struct_specifier (type_identifier) @local.reference)\n     (enum_specifier   (type_identifier) @local.reference)\n     (union_specifier  (type_identifier) @local.reference)\n                       (type_identifier) @local.reference]))\n\n;; type refs in casts\n\n;; rhs of a declaration\n(init_declarator\n  value: (identifier) @local.reference)\n\n;; (void *) a;\n(cast_expression\n  value: (identifier) @local.reference)\n\n;; (SomeStruct) { .field = ident }\n(initializer_pair\n  (identifier) @local.reference)\n(subscript_designator\n  (identifier) @local.reference)\n\n;; lists\n(initializer_list\n  (identifier) @local.reference)\n\n;; return a;\n(return_statement\n  (identifier) @local.reference)\n\n;; goto a;\n(goto_statement\n  (statement_identifier) @local.reference)\n\n;; case ident:\n;;    stmt;\n(case_statement\n  (identifier) @local.reference)\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/c_sharp/mod.rs",
    "content": "use crate::intelligence::{MemoizedQuery, TSLanguageConfig};\n\npub static C_SHARP: TSLanguageConfig = TSLanguageConfig {\n    language_ids: &[\"C#\"],\n    file_extensions: &[\"cs\"],\n    grammar: tree_sitter_c_sharp::language,\n    scope_query: MemoizedQuery::new(include_str!(\"./scopes.scm\")),\n    hoverable_query: MemoizedQuery::new(\n        r#\"\n        (identifier) @hoverable\n        \"#,\n    ),\n    namespaces: &[&[\n        // variables, functions\n        \"local\",\n        // types\n        \"class\",\n        \"struct\",\n        \"enum\",\n        \"typedef\",\n        \"interface\",\n        \"enumerator\",\n        // methods\n        \"method\",\n        // namespaces\n        \"namespace\",\n    ]],\n};\n\n#[cfg(test)]\nmod tests {\n    use crate::intelligence::language::test_utils::*;\n\n    // tests the following constructs:\n    //\n    // - class declarations\n    #[test]\n    fn trivial() {\n        test_scopes(\n            \"C#\",\n            r#\"\n            using System;\n            namespace HelloWorldApp {\n                class HelloWorld {\n                    static void Main(string[] args) {\n                        Console.WriteLine(\"Hello World!\");\n                    }\n                }\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        HelloWorldApp {\n                            kind: \"namespace\",\n                            context: \"namespace §HelloWorldApp§ {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                HelloWorld {\n                                    kind: \"class\",\n                                    context: \"class §HelloWorld§ {\",\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        Main {\n                                            kind: \"method\",\n                                            context: \"static void §Main§(string[] args) {\",\n                                        },\n                                    ],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [\n                                                args {\n                                                    kind: \"local\",\n                                                    context: \"static void Main(string[] §args§) {\",\n                                                },\n                                            ],\n                                            child scopes: [\n                                                scope {\n                                                    definitions: [],\n                                                    child scopes: [],\n                                                },\n                                            ],\n                                        },\n                                    ],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn generics_and_type_constraints() {\n        test_scopes(\n            \"C#\",\n            r#\"\n            namespace N {\n                public interface I1 {\n                    public void F1() {}\n                    public void F2() {}\n                }\n\n                public class C1<T, U> where T: I1 where U: struct {\n                    public string Prop1;\n\n                    public void M1(T t, U u) {\n                        this.Prop1 = t;\n                    }\n                }\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        N {\n                            kind: \"namespace\",\n                            context: \"namespace §N§ {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                I1 {\n                                    kind: \"interface\",\n                                    context: \"public interface §I1§ {\",\n                                    referenced in (1): [\n                                        `public class C1<T, U> where T: §I1§ where U: struct {`,\n                                    ],\n                                },\n                                C1 {\n                                    kind: \"class\",\n                                    context: \"public class §C1§<T, U> where T: I1 where U: struct {\",\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        F1 {\n                                            kind: \"method\",\n                                            context: \"public void §F1§() {}\",\n                                        },\n                                        F2 {\n                                            kind: \"method\",\n                                            context: \"public void §F2§() {}\",\n                                        },\n                                    ],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [\n                                                scope {\n                                                    definitions: [],\n                                                    child scopes: [],\n                                                },\n                                            ],\n                                        },\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [\n                                                scope {\n                                                    definitions: [],\n                                                    child scopes: [],\n                                                },\n                                            ],\n                                        },\n                                    ],\n                                },\n                                scope {\n                                    definitions: [\n                                        T {\n                                            kind: \"typedef\",\n                                            context: \"public class C1<§T§, U> where T: I1 where U: struct {\",\n                                            referenced in (2): [\n                                                `public class C1<T, U> where §T§: I1 where U: struct {`,\n                                                `public void M1(§T§ t, U u) {`,\n                                            ],\n                                        },\n                                        U {\n                                            kind: \"typedef\",\n                                            context: \"public class C1<T, §U§> where T: I1 where U: struct {\",\n                                            referenced in (2): [\n                                                `public class C1<T, U> where T: I1 where §U§: struct {`,\n                                                `public void M1(T t, §U§ u) {`,\n                                            ],\n                                        },\n                                        Prop1 {\n                                            kind: \"local\",\n                                            context: \"public string §Prop1§;\",\n                                            referenced in (1): [\n                                                `this.§Prop1§ = t;`,\n                                            ],\n                                        },\n                                        M1 {\n                                            kind: \"method\",\n                                            context: \"public void §M1§(T t, U u) {\",\n                                        },\n                                    ],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [\n                                                t {\n                                                    kind: \"local\",\n                                                    context: \"public void M1(T §t§, U u) {\",\n                                                    referenced in (1): [\n                                                        `this.Prop1 = §t§;`,\n                                                    ],\n                                                },\n                                                u {\n                                                    kind: \"local\",\n                                                    context: \"public void M1(T t, U §u§) {\",\n                                                },\n                                            ],\n                                            child scopes: [\n                                                scope {\n                                                    definitions: [],\n                                                    child scopes: [],\n                                                },\n                                            ],\n                                        },\n                                    ],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/c_sharp/scopes.scm",
    "content": ";; scopes\n[\n (block)\n (switch_expression_arm)\n (anonymous_method_expression)\n (lambda_expression)\n\n ;; functions\n (local_function_statement)\n (arrow_expression_clause)\n\n ;; switch statements\n (switch_section)\n (case_pattern_switch_label)\n\n ;; namespaces\n (namespace_declaration)\n\n ;; class items\n (class_declaration)\n (constructor_declaration)\n (destructor_declaration)\n (indexer_declaration)\n (method_declaration)\n \n ;; enum items\n (enum_member_declaration_list)\n\n ;; interface items\n (interface_declaration)\n\n ;; record items\n (record_declaration)\n (record_struct_declaration)\n\n ;; struct items\n (struct_declaration)\n\n ;; catch statements\n (catch_clause)\n\n ;; using statement:\n ;; \n ;; using (var a = b) { .. }\n (using_statement)\n\n ;; fixed statement:\n ;;\n ;; fixed (var a = b) { .. }\n (fixed_statement)\n\n ;; for (int i = 0; cond; step) { .. }\n (for_statement)\n\n ;; foreach(int x in y) { .. }\n (for_each_statement)\n] @local.scope\n\n;; defs\n\n;; var declarations\n(variable_declarator\n  (identifier) @local.definition.local)\n\n(declaration_expression\n  (identifier) @local.definition.local)\n\n;; namespaces\n(namespace_declaration \n  (identifier) @hoist.definition.namespace)\n\n;; classes\n;;\n;; - class name\n;; - type params\n;; - constructors\n;; - destructors\n\n(class_declaration\n  name: (identifier) @hoist.definition.class)\n(constructor_declaration\n  name: (identifier) @hoist.definition.method)\n(destructor_declaration \n  name: (identifier) @hoist.definition.method)\n(method_declaration \n  name: (identifier) @hoist.definition.method)\n\n;; enums\n(enum_declaration\n  (identifier) @local.definition.enum)\n(enum_member_declaration\n  (identifier) @local.definition.enumerator)\n\n;; interfaces\n(interface_declaration\n  name: (identifier) @hoist.definition.interface)\n\n;; records\n;;\n;; record F {}\n(record_declaration \n  name: (identifier) @hoist.definition.class)\n;; record struct F {}\n(record_struct_declaration \n  name: (identifier) @hoist.definition.struct)\n\n;; structs\n(struct_declaration\n  name: (identifier) @hoist.definition.struct)\n\n;; functions\n(local_function_statement\n  name: (identifier) @hoist.definition.local)\n\n\n;; patterns are defs\n;; x is a\n(constant_pattern\n  (identifier) @local.definition.local)\n\n;; x is var a\n(var_pattern\n  (identifier) @local.definition.local)\n\n;; (x, y, z) = _\n(tuple_pattern\n  (identifier) @local.definition.local)\n\n;; x is var (x, y)\n(parenthesized_variable_designation\n  (identifier) @local.definition.local)\n\n;; x is var a\n(declaration_pattern\n  name: (identifier) @local.definition.local)\n\n;; params are defs\n(parameter\n  name: (identifier) @local.definition.local)\n\n;; type params make defs\n(type_parameter \n  (identifier) @local.definition.typedef)\n\n;; [params string[] args]\n(bracketed_parameter_list\n  (identifier) @local.definition.local)\n\n;; lambda params\n(lambda_expression\n  (modifier)*\n  .\n  (identifier) @local.definition.local)\n\n;; catch (Exception ex) {}\n(catch_declaration\n  name: (identifier) @local.definition.local)\n\n;; foreach(Type x in y) { .. }\n;; \n;; `Type` is a ref\n;; `x` is a def\n;; `y` is a ref\n(for_each_statement\n  left: (identifier) @local.definition.local)\n\n;; imports\n\n;; using System.Text\n;; \n;; `Text` is an import\n(using_directive\n  .\n  (qualified_name\n    (_)\n    .\n    (identifier) @local.import))\n\n;; using Named = System.Text;\n;;\n;; `Named` is a def\n(using_directive\n  (name_equals\n    (identifier) @local.import))\n\n\n;; refs\n\n(binary_expression\n  (identifier) @local.reference)\n\n;; ternary expr\n(conditional_expression\n  (identifier) @local.reference)\n\n;; a;\n(expression_statement\n  (identifier) @local.reference)\n\n;; x is int\n(is_expression\n  (identifier) @local.reference)\n;; x is String \n(is_pattern_expression\n  (identifier) @local.reference)\n\n;; ident as Type\n(as_expression\n  (identifier) @local.reference)\n\n;; ++x\n(prefix_unary_expression\n  (identifier) @local.reference)\n;; x++\n(postfix_unary_expression\n  (identifier) @local.reference)\n\n;; a = b\n(assignment_expression\n  (identifier) @local.reference)\n\n;; rhs of equal to signs\n;;\n;; _ = b\n(equals_value_clause\n  (identifier) @local.reference)\n\n;; (Type)v\n(cast_expression\n  type: (identifier) @local.reference)\n(cast_expression\n  value: (identifier) @local.reference)\n\n;; a[]\n(element_access_expression\n  (identifier) @local.reference)\n\n;; range exprs\n(range_expression\n  (identifier) @local.reference)\n\n;; function or array args\n(argument \n  (identifier) @local.reference)\n\n;; ident switch {}\n(switch_expression\n  (identifier) @local.reference)\n(switch_expression_arm \n  (identifier) @local.reference)\n\n;; checked(ident)\n(checked_expression\n  (identifier) @local.reference)\n\n;; __makeref(ident)\n(make_ref_expression\n  (identifier) @local.reference)\n\n;; __reftype(ident)\n(ref_type_expression\n  (identifier) @local.reference)\n\n;; __refvalue(ident, type)\n(ref_value_expression\n  (identifier) @local.reference)\n\n;; sizeof(type)\n(size_of_expression\n  (identifier) @local.reference)\n\n;; typeof(ident)\n(type_of_expression\n  (identifier) @local.reference)\n\n;; default(Type)\n(default_expression\n  (identifier) @local.reference)\n\n;; new Obj {}\n(object_creation_expression\n  (identifier) @local.reference)\n\n;; foo() \n(invocation_expression\n  (identifier) @local.reference)\n\n;; A.b\n(member_access_expression\n  .\n  (identifier) @local.reference)\n\n;; this.b\n;;\n;; we can resolve `b` here\n(member_access_expression\n  (this_expression)\n  (identifier) @local.reference)\n\n;; return t\n(return_statement\n  (identifier) @local.reference)\n\n;; await t\n(await_expression\n  (identifier) @local.reference)\n\n;; throw t\n(throw_expression\n  (identifier) @local.reference)\n\n;; lock (mutex)\n(lock_statement\n  (identifier) @local.reference)\n\n;; lambda body\n(lambda_expression \n  body: (identifier) @local.reference)\n\n;; new [] {a, b, c}\n(initializer_expression\n  (identifier) @local.reference)\n\n;; b?.member\n(conditional_access_expression\n  condition: (identifier) @local.reference)\n\n;; (a)\n(parenthesized_expression\n  (identifier) @local.reference)\n\n;; A.b\n(qualified_name\n  .\n  (identifier) @local.reference)\n\n;; $\"Good morning {name}\"\n(interpolation\n  (identifier) @local.reference)\n\n;; record updates\n;;\n;; item with { field = new_field, }\n(with_expression\n  (identifier) @local.reference)\n(simple_assignment_expression\n  (identifier)\n  \"=\"\n  (identifier) @local.reference)\n\n;; while (ident) { .. }\n(while_statement\n  (identifier) @local.reference)\n\n;; do { .. } while (ident)\n(do_statement\n  (identifier) @local.reference)\n\n;; if (ident) { .. }\n(if_statement\n  (identifier) @local.reference)\n\n;; switch (ident) { .. }\n;;\n;; different from switch_expression\n(switch_statement\n  (identifier) @local.reference)\n(when_clause\n  (identifier) @local.reference)\n\n;; foreach(Type x in y) { .. }\n;; \n;; `Type` is a ref\n;; `x` is a def\n;; `y` is a ref\n(for_each_statement\n  type: (identifier) @local.reference)\n(for_each_statement\n  right: (identifier) @local.reference)\n\n\n;; type refs\n\n;; Type?\n(nullable_type\n  (identifier) @local.reference)\n\n;; types in params\n(parameter\n  type: (identifier) @local.reference)\n\n;; Type[]\n(array_type\n  (identifier) @local.reference)\n(array_rank_specifier\n  (identifier) @local.reference)\n\n;; tuple types\n(tuple_element\n  type: (identifier) @local.reference)\n\n;; generics\n(generic_name\n  (identifier) @local.reference)\n(generic_name\n  (type_argument_list\n    (identifier) @local.reference))\n\n;; type ref in pattern\n(declaration_pattern\n  type: (identifier) @local.reference)\n\n;; catch decl\n(catch_declaration\n  type: (identifier) @local.reference)\n\n;; type ref in object patterns\n(recursive_pattern\n  (identifier) @local.reference)\n\n;; type patterns\n(type_pattern\n  (identifier) @local.reference)\n\n;; type constraints\n(type_parameter_constraints_clause\n  (identifier) @local.reference)\n(type_constraint \n  (identifier) @local.reference)\n\n;; base types in enums & interfaces\n;;\n;; enum Direction: Type {\n;;    ...\n;; }\n(base_list\n  (identifier) @local.reference)\n\n(base_list\n  (primary_constructor_base_type\n    (identifier) @local.reference))\n\n;; function return type\n(local_function_statement \n  type: (identifier) @local.reference)\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/cobol/mod.rs",
    "content": "use crate::intelligence::{MemoizedQuery, TSLanguageConfig};\n\npub static COBOL: TSLanguageConfig = TSLanguageConfig {\n    language_ids: &[\"COBOL\"],\n    file_extensions: &[\"cbl\", \"cpy\", \"cob\", \"ccp\", \"cobol\"],\n    grammar: tree_sitter_COBOL::language,\n    scope_query: MemoizedQuery::new(include_str!(\"./scopes.scm\")),\n    hoverable_query: MemoizedQuery::new(\n        r\"\n        [(program_name)\n        (entry_name)\n        (WORD)] @hoverable\n        \",\n    ),\n    namespaces: &[&[\"program\", \"file\", \"data\", \"paragraph\"]],\n};\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/cobol/scopes.scm",
    "content": ";; there are no scopes to a cobol program\n\n(program_name) @local.definition.program\n\n;; defs\n(file_description\n  (file_description_entry\n    (WORD) @local.definition.file))\n(data_description\n  (entry_name) @local.definition.data)\n(paragraph_header\n  name: (_) @local.definition.paragraph)\n\n;; refs\n(select_statement\n  file_name: (_) @local.reference)\n(select_statement\n  (assign_clause\n    to: (qualified_word\n          (WORD) @local.reference)))\n\n(copy_statement\n  book: (_) @local.reference)\n\n(record_key_clause\n  reference: (qualified_word \n               (WORD) @local.reference))\n\n(file_status_clause\n  reference: (qualified_word \n               (WORD) @local.reference))\n\n(read_statement\n  file_name: (_) @local.reference)\n\n(read_statement\n  into: (qualified_word\n          (WORD) @local.reference))\n\n(release_statement\n  record: (qualified_word\n            (WORD) @local.reference))\n\n(release_statement\n  from: (qualified_word\n          (WORD) @local.reference))\n\n(return_statement\n  file_name: (WORD) @local.reference)\n\n(return_statement\n  into: (qualified_word\n          (WORD) @local.reference))\n\n(rewrite_statement\n  record: (qualified_word\n            (WORD) @local.reference)\n  from: (qualified_word\n            (WORD) @local.reference))\n\n(search_statement\n  table_name: (qualified_word\n                (WORD) @local.reference))\n\n(search_statement\n  varying: (qualified_word \n             (WORD) @local.reference))\n\n(set_statement\n  (set_environment\n    (qualified_word \n      (WORD) @local.reference)))\n\n(set_statement\n  (set_to\n    (qualified_word \n      (WORD) @local.reference)))\n\n(set_statement\n  (set_up_down\n    (qualified_word \n      (WORD) @local.reference)))\n\n(move_statement\n  (qualified_word\n    (WORD) @local.reference))\n\n(perform_statement_call_proc\n  procedure: (perform_procedure\n               (label\n                 (qualified_word\n                   (WORD) @local.reference))))\n\n(display_statement\n (qualified_word\n   (WORD) @local.reference))\n\n(accept_statement\n (qualified_word\n   (WORD) @local.reference))\n\n(add_statement\n  (qualified_word\n          (WORD) @local.reference))\n\n(multiply_statement\n  (qualified_word\n    (WORD) @local.reference))\n\n(subtract_statement\n  (qualified_word\n    (WORD) @local.reference))\n\n(allocate_statement\n  x: (WORD) @local.reference)\n\n(allocate_statement\n  returning: (qualified_word\n               (WORD) @local.reference))\n\n(alter_statement\n  (alter_option\n    proc_name: (qualified_word\n                 (WORD) @local.reference)))\n\n(alter_statement\n  (alter_option\n    to: (qualified_word\n                 (WORD) @local.reference)))\n\n(call_statement\n  x: (qualified_word \n       (WORD) @local.reference))\n\n;; todo GIVING FOO returns\n(call_statement\n  returning: (qualified_word \n       (WORD) @local.reference))\n\n(call_param_arg\n  (qualified_word \n    (WORD) @local.reference))\n\n(cancel_statement\n  (qualified_word \n    (WORD) @local.reference))\n\n(close_statement\n  (close_arg\n    (WORD) @local.reference))\n\n(delete_statement\n  file_name: (_) @local.reference)\n\n(divide_statement\n  x: (qualified_word\n       (WORD) @local.reference))\n\n(goto_statement\n  to: (label\n        (qualified_word\n          (WORD) @local.reference)))\n\n(initialize_statement\n  (qualified_word\n    (WORD) @local.reference))\n\n(inspect_statement\n  send: (qualified_word\n          (WORD) @local.reference))\n\n(inspect_converting\n  (qualified_word\n    (WORD) @local.reference))\n\n(inspect_tallying\n  (qualified_word\n    (WORD) @local.reference))\n\n(inspect_replacing\n  (replacing_item\n    (replacing_region\n      (qualified_word\n        (WORD) @local.reference))))\n\n(merge_statement\n  x: (qualified_word \n       (WORD) @local.reference))\n\n(merge_statement\n  collating: (qualified_word \n       (WORD) @local.reference))\n\n(merge_statement\n  output: (sort_output_giving\n            (WORD) @local.reference))\n\n(merge_statement\n  output: (sort_output_procedure\n            (perform_procedure\n              (label\n                (qualified_word\n                  (WORD) @local.reference)))))\n\n(start_statement\n  file_name: (WORD) @local.reference)\n\n(sort_key\n  key_list: (qualified_word \n              (WORD) @local.reference))\n\n; todo call this key list\n(start_key\n  keys: (qualified_word \n              (WORD) @local.reference))\n\n(stop_statement\n  (qualified_word\n    (WORD) @local.reference))\n\n(string_statement\n  (qualified_word\n    (WORD) @local.reference))\n\n(unstring_statement\n  (qualified_word\n    (WORD) @local.reference))\n\n(string_statement\n  (string_item\n    (qualified_word\n     (WORD) @local.reference)))\n\n(unstring_statement\n  (unstring_delimited\n    (unstring_delimited_item\n      (qualified_word\n        (WORD) @local.reference))))\n\n(unstring_statement\n  (unstring_into_item\n    (qualified_word\n      (WORD) @local.reference)))\n\n(use_statement\n  (WORD) @local.reference)\n\n(use_statement\n  (qualified_word\n    (WORD) @local.reference))\n\n(write_statement\n  (qualified_word\n    (WORD) @local.reference))\n\n(use_statement\n  (label\n    (qualified_word\n      (WORD) @local.reference)))\n\n(open_statement\n  (open_arg\n    (WORD) @local.reference))\n\n(expr\n  (qualified_word\n    (WORD) @local.reference))\n\n(arithmetic_x\n  (qualified_word\n    (WORD) @local.reference))\n\n\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/cpp/mod.rs",
    "content": "use crate::intelligence::{MemoizedQuery, TSLanguageConfig};\n\npub static CPP: TSLanguageConfig = TSLanguageConfig {\n    language_ids: &[\"C++\"],\n    file_extensions: &[\"cpp\", \"cc\", \"h\"],\n    grammar: tree_sitter_cpp::language,\n    scope_query: MemoizedQuery::new(include_str!(\"./scopes.scm\")),\n    hoverable_query: MemoizedQuery::new(\n        r#\"\n        [(identifier)\n        (field_identifier)\n        (type_identifier)\n        (statement_identifier)\n        (qualified_identifier)\n        (namespace_identifier)] @hoverable\n        \"#,\n    ),\n    namespaces: &[&[\n        // imports\n        \"header\",\n        // namespacing\n        \"namespace\",\n        // functions\n        \"macro\",\n        \"function\",\n        // types\n        \"class\",\n        \"struct\",\n        \"enum\",\n        \"enumerator\",\n        \"union\",\n        \"typedef\",\n        \"concept\",\n        // variables\n        \"variable\",\n        // misc.\n        \"label\",\n        \"alias\",\n    ]],\n};\n\n#[cfg(test)]\nmod tests {\n\n    use crate::intelligence::language::test_utils::*;\n\n    // tests the following constructs:\n    //\n    // - templates on classes\n    // - classes\n    // - class props\n    #[test]\n    fn trivial() {\n        test_scopes(\n            \"C++\",\n            r#\"\n            template <typename T>\n            class AdvancedColumnFamilyOptions {\n                private:\n                    const std::vector<T>& options;\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        AdvancedColumnFamilyOptions {\n                            kind: \"class\",\n                            context: \"class §AdvancedColumnFamilyOptions§ {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                T {\n                                    kind: \"typedef\",\n                                    context: \"template <typename §T§>\",\n                                    referenced in (1): [\n                                        `const std::vector<§T§>& options;`,\n                                    ],\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        options {\n                                            kind: \"variable\",\n                                            context: \"const std::vector<T>& §options§;\",\n                                        },\n                                    ],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    // this syntax is not present in C\n    #[test]\n    fn for_range_loops() {\n        test_scopes(\n            \"C++\",\n            r#\"\n            struct I {\n            };\n\n            int main() {\n                struct I *items = {};\n                for(I item: items) {\n                    print(item);\n                }\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        I {\n                            kind: \"struct\",\n                            context: \"struct §I§ {\",\n                            referenced in (2): [\n                                `struct §I§ *items = {};`,\n                                `for(§I§ item: items) {`,\n                            ],\n                        },\n                        main {\n                            kind: \"function\",\n                            context: \"int §main§() {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        items {\n                                            kind: \"variable\",\n                                            context: \"struct I *§items§ = {};\",\n                                            referenced in (1): [\n                                                `for(I item: §items§) {`,\n                                            ],\n                                        },\n                                    ],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [\n                                                item {\n                                                    kind: \"variable\",\n                                                    context: \"for(I §item§: items) {\",\n                                                    referenced in (1): [\n                                                        `print(§item§);`,\n                                                    ],\n                                                },\n                                            ],\n                                            child scopes: [\n                                                scope {\n                                                    definitions: [],\n                                                    child scopes: [],\n                                                },\n                                            ],\n                                        },\n                                    ],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    // the syntax nodes seem to have changed from the C impl.\n    #[test]\n    fn if_while_switch() {\n        test_scopes(\n            \"C++\",\n            r#\"\n            int main() {\n                int a;\n                if (a == 0) {};\n                switch (a) {};\n                while (a) {};\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        main {\n                            kind: \"function\",\n                            context: \"int §main§() {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        a {\n                                            kind: \"variable\",\n                                            context: \"int §a§;\",\n                                            referenced in (3): [\n                                                `if (§a§ == 0) {};`,\n                                                `switch (§a§) {};`,\n                                                `while (§a§) {};`,\n                                            ],\n                                        },\n                                    ],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [],\n                                        },\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [],\n                                        },\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [],\n                                        },\n                                    ],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn concepts() {\n        test_scopes(\n            \"C++\",\n            r#\"\n            template<typename T> concept C2 =\n            requires(T x) {\n                {*x} -> std::convertible_to<typename T::inner>;\n                {x + 1} -> std::same_as<int>;\n                {x * 1} -> std::convertible_to<T>;\n            };\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        C2 {\n                            kind: \"concept\",\n                            context: \"template<typename T> concept §C2§ =\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                T {\n                                    kind: \"typedef\",\n                                    context: \"template<typename §T§> concept C2 =\",\n                                    referenced in (3): [\n                                        `requires(§T§ x) {`,\n                                        `{*x} -> std::convertible_to<typename §T§::inner>;`,\n                                        `{x * 1} -> std::convertible_to<§T§>;`,\n                                    ],\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        x {\n                                            kind: \"variable\",\n                                            context: \"requires(T §x§) {\",\n                                            referenced in (3): [\n                                                `{*§x§} -> std::convertible_to<typename T::inner>;`,\n                                                `{§x§ + 1} -> std::same_as<int>;`,\n                                                `{§x§ * 1} -> std::convertible_to<T>;`,\n                                            ],\n                                        },\n                                    ],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    // variables in throw statements do not resolve, reported by @ggordonhall\n    #[test]\n    fn bug_report_throw_statement() {\n        test_scopes(\n            \"C++\",\n            r#\"\n            int main() {\n                try \n                { } \n                catch (Exception ex)\n                { throw ex; }\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        main {\n                            kind: \"function\",\n                            context: \"int §main§() {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [],\n                                        },\n                                        scope {\n                                            definitions: [\n                                                ex {\n                                                    kind: \"variable\",\n                                                    context: \"catch (Exception §ex§)\",\n                                                    referenced in (1): [\n                                                        `{ throw §ex§; }`,\n                                                    ],\n                                                },\n                                            ],\n                                            child scopes: [\n                                                scope {\n                                                    definitions: [],\n                                                    child scopes: [],\n                                                },\n                                            ],\n                                        },\n                                    ],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    // ternary expressions do not resolve correctly, reported by @ggordonhall\n    #[test]\n    fn bug_report_ternary_expression() {\n        test_scopes(\n            \"C++\",\n            r#\"\n            int main() {\n                int a, b, c;\n                a ? b : c;\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        main {\n                            kind: \"function\",\n                            context: \"int §main§() {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        a {\n                                            kind: \"variable\",\n                                            context: \"int §a§, b, c;\",\n                                            referenced in (1): [\n                                                `§a§ ? b : c;`,\n                                            ],\n                                        },\n                                        b {\n                                            kind: \"variable\",\n                                            context: \"int a, §b§, c;\",\n                                            referenced in (1): [\n                                                `a ? §b§ : c;`,\n                                            ],\n                                        },\n                                        c {\n                                            kind: \"variable\",\n                                            context: \"int a, b, §c§;\",\n                                            referenced in (1): [\n                                                `a ? b : §c§;`,\n                                            ],\n                                        },\n                                    ],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/cpp/scopes.scm",
    "content": ";; scopes\n\n;; blocks\n(compound_statement) @local.scope\n(for_statement) @local.scope\n(for_range_loop) @local.scope\n(case_statement) @local.scope\n(field_declaration_list) @local.scope\n(lambda_expression) @local.scope\n(requires_expression) @local.scope\n(namespace_definition) @local.scope\n\n;; functions are finicky, the are of two forms:\n;;\n;; 1. \"declaration\" with a \"function_declarator\" descendant\n;; 2. \"function_definition\" node\n\n;; 1. function prototypes\n;; these further seem to appear in two forms:\n;;\n;;    type ident(...);\n;;    // or\n;;    type *ident(...);\n;;\n(declaration \n  [(function_declarator) @local.scope\n   (pointer_declarator\n     (function_declarator) @local.scope)\n   (reference_declarator\n     (function_declarator) @local.scope)])\n\n;; 2. function definitions\n(function_definition) @local.scope\n\n;; similar logic applies to typedefs with functions\n;; in them\n(type_definition\n  (function_declarator) @local.scope)\n\n;; catch blocks create scopes\n(catch_clause) @local.scope\n\n;; templates create type variables\n;;\n;; FIXME: templates are limited to classes for\n;; now this is because templates add a level\n;; of nesting to all items except classes, which\n;; puts items in smaller scopes than the ones they\n;; are declared in. \n(template_declaration\n  (class_specifier)) @local.scope\n(template_declaration\n  (concept_definition)) @local.scope\n\n\n;; defs\n\n;; #include <lib.h>\n(preproc_include\n  [(system_lib_string)\n   (string_literal)] @local.definition.header)\n\n;; #define PI 355/113\n(preproc_def\n  (identifier) @local.definition.macro)\n\n;; #define AREA(r) PI * r * r\n(preproc_function_def\n  (identifier) @local.definition.macro)\n\n;; a[SIZE] = {1, 2, ..}\n(array_declarator\n declarator: (identifier) @local.definition.variable)\n(array_declarator\n declarator: (field_identifier) @local.definition.variable)\n\n;; int (a) = 2;\n(parenthesized_declarator\n (identifier) @local.definition.variable)\n\n;; int *a = b;\n(pointer_declarator\n (identifier) @local.definition.variable)\n\n;; int &&a = _;\n(reference_declarator\n  [(identifier)\n   (field_identifier)] @local.definition.variable)\n\n(declaration\n  (identifier) @local.definition.variable)\n\n(declaration\n  (init_declarator\n    declarator: (identifier) @local.definition.variable))\n\n;; rhs patterns of a declaration\n(structured_binding_declarator\n  (identifier) @local.definition.variable)\n\n(parameter_declaration\n  (identifier) @local.definition.variable)\n\n(optional_parameter_declaration\n  declarator:\n  (identifier) @local.definition.variable)\n\n(variadic_parameter_declaration\n  (variadic_declarator \n    (identifier) @local.definition.variable))\n\n;; type params in templates\n;;\n;; FIXME: limited to classes for now\n(template_declaration\n  (template_parameter_list\n    (type_parameter_declaration\n      (type_identifier) @local.definition.typedef))\n  [(class_specifier)\n   (concept_definition)])\n\n;; concepts\n(concept_definition\n  name: (identifier) @hoist.definition.concept)\n\n;; namespaces\n(namespace_definition\n  (identifier) @hoist.definition.namespace)\n\n;; for (int a: b) { .. }\n;;\n;; `a` is a def\n(for_range_loop\n  declarator: (identifier) @local.definition.variable)\n\n;; structs\n(struct_specifier\n  name: (type_identifier) @local.definition.struct\n  body: (_))\n\n;; unions\n(union_specifier\n  name: (type_identifier) @local.definition.union\n  body: (_))\n\n;; enums\n(enum_specifier\n  name: (type_identifier) @local.definition.enum\n  body: (_))\n(enumerator\n  name: (identifier) @local.definition.enumerator)\n\n;; classes\n(class_specifier\n  name: (type_identifier) @hoist.definition.class\n  body: (_))\n;; class fields\n(field_declaration \n  (field_identifier) @local.definition.variable)\n\n;; typedef struct { int e; } X;\n(type_definition\n  (type_identifier) @local.definition.typedef)\n\n;; function definition\n(function_declarator\n  (identifier) @hoist.definition.function)\n;; methods\n(function_declarator\n  (field_identifier) @hoist.definition.function)\n\n;; labels\n(labeled_statement\n  (statement_identifier) @local.definition.label)\n\n;; using statements are declarations\n(using_declaration\n  (identifier) @local.definition)\n\n;; using a::b;\n(using_declaration \n  (qualified_identifier \n    name: (identifier) @local.definition))\n\n;; using a = b;\n(alias_declaration\n  name: (type_identifier) @local.definition.alias)\n\n\n\n;; refs\n\n;; abc;\n(expression_statement\n  (identifier) @local.reference)\n\n;; (abc)\n(parenthesized_expression\n  (identifier) @local.reference)\n\n;; a::b\n(qualified_identifier\n  (namespace_identifier) @local.reference)\n\n;; !z\n(unary_expression\n  (identifier) @local.reference)\n\n;; a + b\n(binary_expression\n  (identifier) @local.reference)\n\n;; ++a\n(update_expression\n  (identifier) @local.reference)\n\n;; a? b : c\n(conditional_expression\n  (identifier) @local.reference)\n\n;; call(_, _, _)\n(call_expression\n  (identifier) @local.reference)\n\n;; _(arg, arg, arg)\n(argument_list\n  (identifier) @local.reference)\n\n;; field access\n;;\n;; three types of field access:\n;; - a[b]: a and b are refs\n;; - a.b\n;; - a->b\n;;\n;; a[b]\n(subscript_expression\n  (identifier) @local.reference)\n;; a.b\n;; a->b\n(field_expression\n  .\n  (identifier) @local.reference)\n;; if the ident is a destructor, we can \n;; attempt to resolve it to its class\n(field_expression \n  (_)\n  (destructor_name\n    (identifier) @local.reference))\n\n;; array[CONST]\n;;       ^^^^^ is a ref\n(array_declarator\n size: (identifier) @local.reference)\n\n;; comma operator\n;; (a, a++, a <= 2)\n(comma_expression\n  (identifier) @local.reference)\n\n;; ref and deref\n(pointer_expression\n  (identifier) @local.reference)\n\n;; assignment expressions\n(assignment_expression\n  (identifier) @local.reference)\n\n;; for (Type a: b) {.. }\n;;\n;; `Type` is a ref\n;; `b` is a ref\n(for_range_loop\n  type: (type_identifier) @local.reference)\n(for_range_loop\n  right: (identifier) @local.reference)\n\n(condition_clause\n  (identifier) @local.reference)\n\n;; rhs of a concept\n(concept_definition\n  name: (_)\n  (identifier) @local.reference)\n\n;; type refs in declarations\n(declaration\n  type:\n  [(struct_specifier (type_identifier) @local.reference)\n   (class_specifier  (type_identifier) @local.reference)\n   (enum_specifier   (type_identifier) @local.reference)\n   (union_specifier  (type_identifier) @local.reference)\n                     (type_identifier) @local.reference])\n\n;; type refs in return types\n(function_definition\n  type:\n  [(struct_specifier (type_identifier) @local.reference)\n   (class_specifier  (type_identifier) @local.reference)\n   (enum_specifier   (type_identifier) @local.reference)\n   (union_specifier  (type_identifier) @local.reference)\n                     (type_identifier) @local.reference])\n\n;; type refs in params\n(parameter_declaration\n  type:\n  [(struct_specifier (type_identifier) @local.reference)\n   (class_specifier  (type_identifier) @local.reference)\n   (enum_specifier   (type_identifier) @local.reference)\n   (union_specifier  (type_identifier) @local.reference)\n                     (type_identifier) @local.reference])\n\n;; type refs in optional params\n(optional_parameter_declaration\n  type:\n  [(struct_specifier (type_identifier) @local.reference)\n   (class_specifier  (type_identifier) @local.reference)\n   (enum_specifier   (type_identifier) @local.reference)\n   (union_specifier  (type_identifier) @local.reference)\n                     (type_identifier) @local.reference])\n\n;; type refs in casts\n(cast_expression\n  type: \n  (type_descriptor\n    [(struct_specifier (type_identifier) @local.reference)\n     (class_specifier  (type_identifier) @local.reference)\n     (enum_specifier   (type_identifier) @local.reference)\n     (union_specifier  (type_identifier) @local.reference)\n                       (type_identifier) @local.reference]))\n\n;; type refs in field declarations\n(field_declaration\n  type:\n  [(struct_specifier (type_identifier) @local.reference)\n   (class_specifier  (type_identifier) @local.reference)\n   (enum_specifier   (type_identifier) @local.reference)\n   (union_specifier  (type_identifier) @local.reference)\n                     (type_identifier) @local.reference])\n\n;; default value in rhs of field decls.\n(field_declaration \n  (identifier) @local.reference)\n\n;; rhs of optional parameter decls.\n(optional_parameter_declaration\n  default_value:\n  (identifier) @local.reference)\n\n\n;; type refs in friend declarations\n(friend_declaration \n  (type_identifier) @local.reference)\n\n;; rhs of a declaration\n(init_declarator\n  value: (identifier) @local.reference)\n\n;; (void *) a;\n(cast_expression\n  value: (identifier) @local.reference)\n\n;; (SomeStruct) { .field = ident }\n(initializer_pair\n  (identifier) @local.reference)\n(subscript_designator\n  (identifier) @local.reference)\n\n;; lists\n(initializer_list\n  (identifier) @local.reference)\n\n;; return a;\n(return_statement\n  (identifier) @local.reference)\n\n;; delete a;\n(delete_expression\n  (identifier) @local.reference)\n\n;; new T;\n(new_expression\n  (type_identifier) @local.reference)\n\n;; goto a;\n(goto_statement\n  (statement_identifier) @local.reference)\n\n;; co_await var;\n(co_await_expression \n  (identifier) @local.reference)\n\n;; throw ex;\n(throw_statement\n  (identifier) @local.reference)\n\n;; (a + ... + b)\n(fold_expression\n  (identifier) @local.reference)\n\n(lambda_capture_specifier\n  (identifier) @local.reference)\n\n;; case ident:\n;;    stmt;\n(case_statement\n  (identifier) @local.reference)\n\n;; inherited classes are refs\n(base_class_clause\n  (type_identifier) @local.reference)\n\n;; base types in enums are refs\n(enum_specifier\n  base: (type_identifier) @local.reference)\n\n;; Type{}\n(compound_literal_expression\n  (type_identifier) @local.reference)\n\n;; operator Type() T;\n(operator_cast\n  (type_identifier) @local.reference)\n\n;; template types\n;; T<_>\n(template_type\n  (type_identifier) @local.reference)\n\n;; V<_>()\n(template_function\n  (identifier) @local.reference)\n\n;; _<U, V, W>\n(template_argument_list\n  (type_descriptor \n    (type_identifier) @local.reference))\n\n;; variadic in type descriptors\n(parameter_pack_expansion\n  (type_descriptor \n    (type_identifier) @local.reference))\n\n;; a.template F<_>()\n(template_method \n  (field_identifier) @local.reference)\n\n;; type constraints\n(compound_requirement\n  (identifier) @local.reference)\n(trailing_return_type\n  (type_descriptor \n    (type_identifier) @local.reference))\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/go/mod.rs",
    "content": "use crate::intelligence::{MemoizedQuery, TSLanguageConfig};\n\npub static GO: TSLanguageConfig = TSLanguageConfig {\n    language_ids: &[\"Go\"],\n    file_extensions: &[\"go\"],\n    grammar: tree_sitter_go::language,\n    scope_query: MemoizedQuery::new(include_str!(\"./scopes.scm\")),\n    hoverable_query: MemoizedQuery::new(\n        r#\"\n        [(identifier)\n         (type_identifier)\n         (package_identifier)\n         (field_identifier)] @hoverable\n        \"#,\n    ),\n    namespaces: &[\n        // variables\n        &[\"const\", \"var\", \"func\", \"module\"],\n        // types\n        &[\"struct\", \"interface\", \"type\"],\n        // misc.\n        &[\"member\"],\n        &[\"label\"],\n    ],\n};\n\n#[cfg(test)]\nmod tests {\n    use crate::intelligence::language::test_utils::*;\n\n    #[test]\n    fn declare_const_no_type() {\n        let src = r#\"\n            const one = 1\n            const two, three = 2, 3\n            \"#;\n\n        let (_, d, _, _) = counts(src, \"Go\");\n        assert_eq!(d, 3);\n    }\n\n    #[test]\n    fn declare_const_with_type() {\n        let src = r#\"\n            const one uint64 = 1\n            const two, three uint64 = 2, 3\n            \"#;\n        let (_, d, _, _) = counts(src, \"Go\");\n        assert_eq!(d, 3);\n    }\n\n    #[test]\n    fn declare_const_grouped() {\n        let src = r#\"\n            const (\n                zero = 0\n                one = 1\n            )\n        \"#;\n        let (_, d, _, _) = counts(src, \"Go\");\n        assert_eq!(d, 2);\n    }\n\n    #[test]\n    fn declare_const_implicit_value() {\n        let src = r#\"\n            const (\n                zero = iota\n                one\n            )\n        \"#;\n        let (_, d, _, _) = counts(src, \"Go\");\n        assert_eq!(d, 2);\n    }\n\n    #[test]\n    fn declare_var_no_type() {\n        let src = r#\"\n            package main\n\n            var zero = 0\n            var one, two = 1, 2\n            var three, four, five = 3, 4, 5\n        \"#;\n        let (_, d, _, _) = counts(src, \"Go\");\n        assert_eq!(d, 6);\n    }\n\n    #[test]\n    fn declare_var_with_types() {\n        let src = r#\"\n            package main\n\n            var zero uint64 = 0\n            var one, two uint64 = 1, 2\n        \"#;\n        let (_, d, _, _) = counts(src, \"Go\");\n        assert_eq!(d, 3);\n    }\n\n    #[test]\n    fn declare_var_grouped() {\n        let src = r#\"\n            package main\n\n            var (\n                zero = 0\n                one = 1\n            )\n        \"#;\n        let (_, d, _, _) = counts(src, \"Go\");\n        assert_eq!(d, 2);\n    }\n\n    #[test]\n    fn declare_short_var() {\n        let src = r#\"\n            func main() {\n                x := 2\n                res, err := f()\n            }\n        \"#;\n\n        // main, x, res, err\n        let (_, d, _, _) = counts(src, \"Go\");\n        assert_eq!(d, 4);\n    }\n\n    #[test]\n    fn declare_func() {\n        let src = r#\"\n            package main\n\n            func f1() {}\n            func f2() int {}\n            func f3() (File, Thing) {}\n            func f4(result int, err error) {}       // declares result, err\n            func f5(x ... uint64, y ... uint64) {}  // declares x, y\n        \"#;\n        let (_, d, _, _) = counts(src, \"Go\");\n\n        // f1, f2, f3, f4, f5, result, err, x, y\n        assert_eq!(d, 9);\n    }\n\n    #[test]\n    fn declare_type() {\n        let src = r#\"\n            package main\n\n            type a uint64\n            type (\n                b uint64\n                c uint64\n            )\n            type s struct {}\n            type i interface {}\n        \"#;\n        let (_, d, _, _) = counts(src, \"Go\");\n        assert_eq!(d, 5);\n    }\n\n    #[test]\n    fn declare_type_grouped() {\n        let src = r#\"\n            package main\n\n            type (\n                a uint64\n                b uint64\n            )\n        \"#;\n        let (_, d, _, _) = counts(src, \"Go\");\n        assert_eq!(d, 2);\n    }\n\n    #[test]\n    fn declare_loop_label() {\n        let src = r#\"\n            func main() {\n                loop: for ;; {\n                    break loop\n                }\n            }\n        \"#;\n\n        // main, loop\n        let (_, d, _, _) = counts(src, \"Go\");\n        assert_eq!(d, 2);\n    }\n\n    #[test]\n    fn declare_func_literal() {\n        let src = r#\"\n            func main() {\n                const t := func () {}\n            }\n        \"#;\n\n        // main, t\n        let (_, d, _, _) = counts(src, \"Go\");\n        assert_eq!(d, 2);\n    }\n\n    #[test]\n    fn refer_binary_expr() {\n        let src = r#\"\n            const a = 2\n            const b = 2\n            const _ = a + b\n            const _ = a * b\n            const _ = a << b\n        \"#;\n\n        // 3 refs to a, 3 refs to b\n        let (_, _, r, _) = counts(src, \"Go\");\n        assert_eq!(r, 6);\n    }\n\n    #[test]\n    fn refer_func_call() {\n        let src = r#\"\n            func a() {\n                b()\n            }\n            func b() {\n                a()\n            }\n        \"#;\n\n        let (_, _, r, _) = counts(src, \"Go\");\n        assert_eq!(r, 2);\n    }\n\n    #[test]\n    fn refer_array_index() {\n        let src = r#\"\n            func main() {\n                a := [3] int{1, 2, 3}\n                a[0] = 3\n                a[2] = 1\n            }\n        \"#;\n\n        let (_, _, r, _) = counts(src, \"Go\");\n        assert_eq!(r, 2);\n    }\n\n    #[test]\n    fn refer_slice_expr() {\n        let src = r#\"\n            func main() {\n                a := [3] int{1, 2, 3}\n                b := a[0:3]\n            }\n        \"#;\n\n        let (_, _, r, _) = counts(src, \"Go\");\n        assert_eq!(r, 1);\n    }\n\n    #[test]\n    fn refer_parenthesized_expr() {\n        let src = r#\"\n            func main() {\n                a := 2\n                (a)\n            }\n        \"#;\n\n        let (_, _, r, _) = counts(src, \"Go\");\n        assert_eq!(r, 1);\n    }\n\n    #[test]\n    fn refer_selector_expr() {\n        let src = r#\"\n            type person struct {\n                name string\n                age  int\n            }\n            func main() {\n                p := person{ \"bob\", 20 };\n                p.age = 42\n            }\n        \"#;\n\n        let (_, _, r, _) = counts(src, \"Go\");\n\n        // p (variable ref), person (type ref)\n        assert_eq!(r, 2);\n    }\n\n    #[test]\n    fn refer_type_assert_expr() {\n        let src = r#\"\n            func main() {\n                a := 3\n                a.(uint64)\n            }\n        \"#;\n\n        let (_, _, r, _) = counts(src, \"Go\");\n        assert_eq!(r, 1);\n    }\n\n    #[test]\n    fn refer_unary_expr() {\n        let src = r#\"\n            func main() {\n                a := 2\n                !a\n            }\n        \"#;\n\n        let (_, _, r, _) = counts(src, \"Go\");\n        assert_eq!(r, 1);\n    }\n\n    #[test]\n    fn refer_statements() {\n        let src = r#\"\n            func main() {\n                a := 3\n\n                a++\n                a--\n                a = 3\n\n                // control flow\n                if a { }\n                switch a { }\n                defer a \n                go a\n                return a\n\n                label:\n                continue label\n                break label\n            }\n        \"#;\n\n        let (_, _, r, _) = counts(src, \"Go\");\n        assert_eq!(r, 10);\n    }\n\n    #[test]\n    fn no_ref() {\n        let src = r#\"\n            func f1() {\n                a := 3\n            }\n            func f2() {\n                return a\n            }\n            func f3() {}\n        \"#;\n        let (_, d, r, _) = counts(src, \"Go\");\n\n        // f1, f1::a, f2, f3\n        assert_eq!(d, 4);\n\n        // `a` in f2 found no defs, and is dropped from the graph\n        assert_eq!(r, 0);\n    }\n\n    #[test]\n    fn symbol_consts() {\n        let src = r#\"\n            package main\n\n            const one uint64 = 1\n            const (\n                two = 2\n                three = 2\n            )\n\n            func four() {}\n\n            var five = 3\n\n            func six() {\n                seven: for ;; {}\n            }\n\n            type eight struct {\n                nine string\n                ten uint64 \n            }\n\n            type eleven interface {}\n        \"#;\n        assert_eq_defs(\n            src.as_bytes(),\n            \"Go\",\n            vec![\n                (\"one\", \"const\"),\n                (\"two\", \"const\"),\n                (\"three\", \"const\"),\n                (\"four\", \"func\"),\n                (\"five\", \"var\"),\n                (\"five\", \"var\"),\n                (\"six\", \"func\"),\n                (\"seven\", \"label\"),\n                (\"eight\", \"struct\"),\n                (\"nine\", \"member\"),\n                (\"ten\", \"member\"),\n                (\"eleven\", \"interface\"),\n            ],\n        )\n    }\n\n    #[test]\n    fn scoping_rules() {\n        test_scopes(\n            \"Go\",\n            r#\"\n            func main() {\n                var args = os.Args;\n                var length = len(args);\n                fmt.Printf(\"%d\", l);\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        main {\n                            kind: \"func\",\n                            context: \"func §main§() {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        args {\n                                            kind: \"var\",\n                                            context: \"var §args§ = os.Args;\",\n                                            referenced in (1): [\n                                                `var length = len(§args§);`,\n                                            ],\n                                        },\n                                        length {\n                                            kind: \"var\",\n                                            context: \"var §length§ = len(args);\",\n                                        },\n                                    ],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn function_params() {\n        test_scopes(\n            \"Go\",\n            r#\"\n            func main(t string, u string) {\n                v := 0\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        main {\n                            kind: \"func\",\n                            context: \"func §main§(t string, u string) {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                t {\n                                    kind: \"var\",\n                                    context: \"func main(§t§ string, u string) {\",\n                                },\n                                u {\n                                    kind: \"var\",\n                                    context: \"func main(t string, §u§ string) {\",\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        v {\n                                            kind: \"var\",\n                                            context: \"§v§ := 0\",\n                                        },\n                                    ],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    // types and variables belong to different namespaces, preventing\n    // items in the variable position to resolve to typedefs, and vice-versa\n    #[test]\n    fn namespacing_of_types_and_variables() {\n        test_scopes(\n            \"Go\",\n            r#\"\n            type repoFilters struct {\n                topics []string\n            }\n\n            func (repoFilters repoFilters) {\n                repoFilters + 1\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        repoFilters {\n                            kind: \"struct\",\n                            context: \"type §repoFilters§ struct {\",\n                            referenced in (1): [\n                                `func (repoFilters §repoFilters§) {`,\n                            ],\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        topics {\n                                            kind: \"member\",\n                                            context: \"§topics§ []string\",\n                                        },\n                                    ],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                        scope {\n                            definitions: [\n                                repoFilters {\n                                    kind: \"var\",\n                                    context: \"func (§repoFilters§ repoFilters) {\",\n                                    referenced in (1): [\n                                        `§repoFilters§ + 1`,\n                                    ],\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    // modules and variables are in the same namespace, allowing\n    // things like module.Type to resolve correctly\n    #[test]\n    fn namespacing_of_modules_and_variables() {\n        test_scopes(\n            \"Go\",\n            r#\"\n            import x \"github.com/golang/go/x\"\n\n            var t x.Type := 2\n\n            t++\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        t {\n                            kind: \"var\",\n                            context: \"var §t§ x.Type := 2\",\n                            referenced in (1): [\n                                `§t§++`,\n                            ],\n                        },\n                    ],\n                    imports: [\n                        x {\n                            context: \"import §x§ \\\"github.com/golang/go/x\\\"\",\n                            referenced in (1): [\n                                `var t §x§.Type := 2`,\n                            ],\n                        },\n                    ],\n                    child scopes: [],\n                }\n            \"#]],\n        )\n    }\n\n    // labels can only be referred to in break and continue statements\n    #[test]\n    fn namespacing_of_labels() {\n        test_scopes(\n            \"Go\",\n            r#\"\n            func main() {\n                const OUTER = 2\n\n                OUTER:\n                for {\n                    continue OUTER\n                }\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        main {\n                            kind: \"func\",\n                            context: \"func §main§() {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        OUTER {\n                                            kind: \"const\",\n                                            context: \"const §OUTER§ = 2\",\n                                        },\n                                        OUTER {\n                                            kind: \"label\",\n                                            context: \"§OUTER§:\",\n                                            referenced in (1): [\n                                                `continue §OUTER§`,\n                                            ],\n                                        },\n                                    ],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [],\n                                        },\n                                    ],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    // bug report: https://www.notion.so/teambloop/Go-Bug-2a82ef59b72548f2ad51bac1ddad62b6\n    #[test]\n    fn bug_report_type_def_slice_type() {\n        test_scopes(\n            \"Go\",\n            r#\"\n            type runeOffsetMap []runeOffsetCorrection\n\n            func makeRuneOffsetMap(off []uint32) runeOffsetMap {\n\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        runeOffsetMap {\n                            kind: \"type\",\n                            context: \"type §runeOffsetMap§ []runeOffsetCorrection\",\n                            referenced in (1): [\n                                `func makeRuneOffsetMap(off []uint32) §runeOffsetMap§ {`,\n                            ],\n                        },\n                        makeRuneOffsetMap {\n                            kind: \"func\",\n                            context: \"func §makeRuneOffsetMap§(off []uint32) runeOffsetMap {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                        scope {\n                            definitions: [\n                                off {\n                                    kind: \"var\",\n                                    context: \"func makeRuneOffsetMap(§off§ []uint32) runeOffsetMap {\",\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    // bug report: https://www.notion.so/teambloop/Go-Bug-2a82ef59b72548f2ad51bac1ddad62b6\n    #[test]\n    fn bug_report_rhs_declaration() {\n        test_scopes(\n            \"Go\",\n            r#\"\n            // this used to create 2 definitions: x, y\n            x := y\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        x {\n                            kind: \"var\",\n                            context: \"§x§ := y\",\n                        },\n                    ],\n                    child scopes: [],\n                }\n            \"#]],\n        )\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/go/scopes.scm",
    "content": ";; scopes\n\n;; function declarations create a scope for\n;; their args, and another for their bodies\n;;\n;;     func f(x uint64, y uint64) {\n;;        var z = 2\n;;     }\n;;\n;; should resolve to:\n;;\n;;     scope: {\n;;       defs: f\n;;       scope: {\n;;         defs: x, y\n;;         scope: {\n;;           defs: z\n;;         }\n;;       }\n;;     }\n;;\n(function_declaration) @local.scope\n(method_declaration) @local.scope\n(func_literal) @local.scope\n(field_declaration_list) @local.scope\n(type_switch_statement) @local.scope\n(type_declaration) @local.scope\n\n(block) @local.scope\n\n;; select statements with assignments seem\n;; to create scopes, without using blocks\n;;\n;; select {\n;;    case x := <- channel:   // creates a scope and defines `x`\n;;      doThing(x)\n;;    case y := <- channel:\n;;      doThing(y)\n;; }\n(communication_case) @local.scope\n\n\n;; defs\n\n;; const x = ...\n(const_declaration\n  (const_spec\n    (identifier) @local.definition.const))\n\n;; var x = ...\n(var_declaration\n  (var_spec\n    (identifier) @local.definition.var))\n\n;; x := ...\n(short_var_declaration\n  left:\n  (expression_list\n    (identifier) @local.definition.var))\n\n;; func x() { ... }\n(function_declaration\n  name: (identifier) @hoist.definition.func)\n\n;; func (s S) x() { ... }\n(method_declaration\n  name: (field_identifier) @hoist.definition.func)\n\n;; type a struct { ... }\n(type_declaration\n  (type_spec \n    (type_identifier) @hoist.definition.struct\n    (struct_type)))\n\n;; type a interface { ... }\n(type_declaration\n  (type_spec \n    (type_identifier) @hoist.definition.interface\n    (interface_type)))\n\n;; interface methods\n(method_spec\n  (field_identifier) @local.definition.func)\n\n;; type a b\n;; all other type defs\n(type_declaration \n  (type_spec \n    (type_identifier) @hoist.definition.type\n    [(array_type)\n     (channel_type)\n     (function_type)\n     (map_type)\n     (pointer_type)\n     (qualified_type)\n     (slice_type)\n     (type_identifier) @local.reference.type]))\n\n;; type parameters\n(type_parameter_list\n  (parameter_declaration\n    (type_identifier) @local.definition.type))\n\n;; type alias lhs\n(type_alias\n  .\n  (type_identifier) @local.definition.type)\n\n;; type _ struct {\n;;    x T\n;; }\n(field_declaration_list\n  (field_declaration \n    (field_identifier) @local.definition.member))\n\n;; func _(x)\n(function_declaration\n  parameters: \n  (parameter_list\n    (parameter_declaration\n      (identifier) @local.definition.var)))\n\n;; method params\n(method_declaration \n  receiver:\n  (parameter_list\n    (parameter_declaration\n      (identifier) @local.definition.var)))\n\n(method_declaration\n  parameters:\n  (parameter_list\n    (parameter_declaration\n      (identifier) @local.definition.var)))\n\n;; variadic params\n;; func _(x ... T)\n(parameter_list\n  (variadic_parameter_declaration\n    (identifier) @local.definition.var))\n\n;; function literal syntax\n;; const _ = func(x) {}\n(func_literal\n  (parameter_list\n    (parameter_declaration\n      (identifier) @local.definition.var)))\n\n;; loop: for i := ...\n(labeled_statement \n  (label_name) @local.definition.label)\n\n;; imports\n(import_spec \n  (package_identifier) @local.import)\n\n;; switch t := q.(type)\n(type_switch_statement\n  (expression_list\n    (identifier) @local.definition.var))\n\n;; select {\n;;    case x := <- c\n;; }\n;;\n;; beats me why this is different from\n;; short_var_declaration :shrug:\n(receive_statement\n  left: \n  (expression_list\n    (identifier) @local.definition.var))\n\n;; for range\n;;\n;; for i, e := range\n;;\n;; `i` and `e` are def\n(for_statement\n  (range_clause\n    (expression_list \n      (identifier) @local.definition.var)))\n\n\n;; refs\n\n;; a op b\n(binary_expression\n  (identifier) @local.reference.var)\n\n;; x()\n(call_expression\n  (identifier) @local.reference.var)\n\n;; x(ident, ident)\n;;\n;; arguments to a call expression also create references\n(call_expression \n  (argument_list\n    (identifier) @local.reference.var))\n\n;; x[_]\n(index_expression\n  (identifier) @local.reference.var)\n\n;; (x)\n(parenthesized_expression\n  (identifier) @local.reference.var)\n\n;; x.b\n(selector_expression\n  . (identifier) @local.reference)\n\n;; x[y:z]\n(slice_expression\n  (identifier) @local.reference.var)\n\n;; a.(Type)\n(type_assertion_expression\n  (identifier) @local.reference.var)\n(type_assertion_expression\n  (type_identifier) @local.reference.type)\n\n;; Type(x)\n;;\n;; some type conversions are equivalent\n;; to call expressions, the grammar lacks\n;; info to distinguish among them\n(type_conversion_expression\n  (identifier) @local.reference)\n\n;; !a\n(unary_expression\n  (identifier) @local.reference.var)\n\n;; x <- item\n(send_statement\n  (identifier) @local.reference.var)\n\n;; x := <- c\n(send_statement\n  (identifier) @local.reference.var)\n\n;; x++\n(inc_statement\n  (identifier) @local.reference.var)\n\n;; x--\n(dec_statement\n  (identifier) @local.reference.var)\n\n;; assignment\n;; a = 2\n(assignment_statement\n  (expression_list\n    (identifier) @local.reference.var))\n\n;; if x { .. }\n(if_statement \n  (identifier) @local.reference.var)\n\n;; switch x { .. }\n(expression_switch_statement\n  (identifier) @local.reference.var)\n\n;; typed-switch\n(type_switch_statement\n  (identifier) @local.reference.var)\n\n;; defer x\n(defer_statement\n  (identifier) @local.reference.var)\n\n;; go x\n(go_statement\n  (identifier) @local.reference.var)\n\n;; return x\n(return_statement\n  (expression_list \n    (identifier) @local.reference.var))\n\n;; break x\n(break_statement\n  (label_name) @local.reference.label)\n\n;; continue x\n(continue_statement\n  (label_name) @local.reference.label)\n\n;; for range\n;;\n;; `i` and `e` are def\n(for_statement\n  (range_clause\n    right: (identifier) @local.reference.var))\n\n;; return parameter list\n(function_declaration\n  result: \n  (parameter_list\n    (parameter_declaration \n      (identifier) @local.reference.var)))\n(method_declaration\n  result: \n  (parameter_list\n    (parameter_declaration \n      (identifier) @local.reference.var)))\n(method_spec\n  result: \n  (type_identifier) @local.definition.type)\n\n;; struct literals\n(literal_value\n  [\n   ;; field: value\n   (keyed_element\n     (literal_element\n       (identifier) @local.reference.var))\n\n   ;; value\n   (literal_element\n     (identifier) @local.reference.var)\n   ])\n\n;; _ := y\n(short_var_declaration\n  right:\n  (expression_list\n    (identifier) @local.reference.var))\n\n\n;; type refs\n\n;; func _(var type)\n(parameter_list\n  (parameter_declaration\n    type:\n    (type_identifier) @local.reference.type))\n\n;; return type ident\n;;\n;; func _(..) type {}\n(function_declaration\n  result: (type_identifier) @local.reference.type)\n(method_declaration\n  result: (type_identifier) @local.reference.type)\n\n;; func (var... type)\n(variadic_parameter_declaration\n  type: (type_identifier) @local.reference.type)\n\n;; *T\n(pointer_type\n  (type_identifier) @local.reference.type)\n\n;; []T\n(slice_type\n  (type_identifier) @local.reference.type)\n\n;; map type\n(map_type\n  (type_identifier) @local.reference.type)\n\n;; chan <- Type\n(channel_type \n  (type_identifier) @local.reference.type)\n\n;; var x type; const x type\n(var_spec \n  (type_identifier) @local.reference.type)\n(const_spec\n  (type_identifier) @local.reference.type)\n\n;; module.Type\n(qualified_type\n  (package_identifier) @local.reference.module)\n\n;; struct literal\n(composite_literal\n  (type_identifier) @local.reference.type)\n\n;; type constraints\n(constraint_term\n  (type_identifier) @local.reference.type)\n\n;; type alias rhs\n(type_alias\n  (_)\n  (type_identifier) @local.reference.type)\n\n;; generic type\n;; Type[T, U]\n(generic_type\n  (type_identifier) @local.reference.type)\n(type_arguments\n  (type_identifier) @local.reference.type)\n\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/java/mod.rs",
    "content": "use crate::intelligence::{MemoizedQuery, TSLanguageConfig};\n\npub static JAVA: TSLanguageConfig = TSLanguageConfig {\n    language_ids: &[\"Java\"],\n    file_extensions: &[\"java\"],\n    grammar: tree_sitter_java::language,\n    scope_query: MemoizedQuery::new(include_str!(\"./scopes.scm\")),\n    hoverable_query: MemoizedQuery::new(\n        r#\"\n        [(identifier)\n         (type_identifier)] @hoverable\n        \"#,\n    ),\n    namespaces: &[&[\n        // variables\n        \"local\",\n        // functions\n        \"method\",\n        // namespacing, modules\n        \"package\",\n        \"module\",\n        // types\n        \"class\",\n        \"enum\",\n        \"enumConstant\",\n        \"record\",\n        \"interface\",\n        \"typedef\",\n        // misc.\n        \"label\",\n    ]],\n};\n\n#[cfg(test)]\nmod tests {\n    use crate::intelligence::language::test_utils::*;\n\n    // tests the following constructs:\n    //\n    // - class declarations\n    // - method declarations\n    // - formal parameters\n    // - method invocations\n    // - array access\n    #[test]\n    fn trivial() {\n        test_scopes(\n            \"Java\",\n            r#\"\n            class HelloWorld {\n                public static void main(string[] args) {\n                    System.Out.Println(\"Hello \" + args[0]);\n                }\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        HelloWorld {\n                            kind: \"class\",\n                            context: \"class §HelloWorld§ {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                main {\n                                    kind: \"method\",\n                                    context: \"public static void §main§(string[] args) {\",\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        args {\n                                            kind: \"local\",\n                                            context: \"public static void main(string[] §args§) {\",\n                                            referenced in (1): [\n                                                `System.Out.Println(\"Hello \" + §args§[0]);`,\n                                            ],\n                                        },\n                                    ],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [],\n                                        },\n                                    ],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    // tests the following constructs:\n    //\n    // - class declarations\n    // - interface declarations\n    // - super classes\n    // - interfaces implementations\n    // - generics\n    #[test]\n    fn classes_interfaces_generics() {\n        test_scopes(\n            \"Java\",\n            r#\"\n            public class C1 {}\n            public class C2 {}\n\n            public interface I1 {}\n            public interface I2 {}\n\n            public class C3<T extends C1> extends C2 implements I1, I2 {\n                private T element;\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        C1 {\n                            kind: \"class\",\n                            context: \"public class §C1§ {}\",\n                            referenced in (1): [\n                                `public class C3<T extends §C1§> extends C2 implements I1, I2 {`,\n                            ],\n                        },\n                        C2 {\n                            kind: \"class\",\n                            context: \"public class §C2§ {}\",\n                            referenced in (1): [\n                                `public class C3<T extends C1> extends §C2§ implements I1, I2 {`,\n                            ],\n                        },\n                        I1 {\n                            kind: \"interface\",\n                            context: \"public interface §I1§ {}\",\n                            referenced in (1): [\n                                `public class C3<T extends C1> extends C2 implements §I1§, I2 {`,\n                            ],\n                        },\n                        I2 {\n                            kind: \"interface\",\n                            context: \"public interface §I2§ {}\",\n                            referenced in (1): [\n                                `public class C3<T extends C1> extends C2 implements I1, §I2§ {`,\n                            ],\n                        },\n                        C3 {\n                            kind: \"class\",\n                            context: \"public class §C3§<T extends C1> extends C2 implements I1, I2 {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                        scope {\n                            definitions: [\n                                T {\n                                    kind: \"typedef\",\n                                    context: \"public class C3<§T§ extends C1> extends C2 implements I1, I2 {\",\n                                    referenced in (1): [\n                                        `private §T§ element;`,\n                                    ],\n                                },\n                                element {\n                                    kind: \"local\",\n                                    context: \"private T §element§;\",\n                                },\n                            ],\n                            child scopes: [],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    // `this` is specially handled:\n    //\n    // - `this.member` raises `member` as a reference\n    // - `this.method()` raises `method` as a reference\n    #[test]\n    fn this_keyword() {\n        test_scopes(\n            \"Java\",\n            r#\"\n            public class Adder {\n                private int a;\n                private int b;\n\n                Adder(int first, int second) {\n                    this.a = first;\n                    this.b = second;\n                }\n\n                private int add_helper() {\n                    return this.a + this.b;\n                }\n\n                public int add() {\n                    return this.add_helper();\n                }\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        Adder {\n                            kind: \"class\",\n                            context: \"public class §Adder§ {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                a {\n                                    kind: \"local\",\n                                    context: \"private int §a§;\",\n                                    referenced in (2): [\n                                        `this.§a§ = first;`,\n                                        `return this.§a§ + this.b;`,\n                                    ],\n                                },\n                                b {\n                                    kind: \"local\",\n                                    context: \"private int §b§;\",\n                                    referenced in (2): [\n                                        `this.§b§ = second;`,\n                                        `return this.a + this.§b§;`,\n                                    ],\n                                },\n                                Adder {\n                                    kind: \"method\",\n                                    context: \"§Adder§(int first, int second) {\",\n                                },\n                                add_helper {\n                                    kind: \"method\",\n                                    context: \"private int §add_helper§() {\",\n                                    referenced in (1): [\n                                        `return this.§add_helper§();`,\n                                    ],\n                                },\n                                add {\n                                    kind: \"method\",\n                                    context: \"public int §add§() {\",\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        first {\n                                            kind: \"local\",\n                                            context: \"Adder(int §first§, int second) {\",\n                                            referenced in (1): [\n                                                `this.a = §first§;`,\n                                            ],\n                                        },\n                                        second {\n                                            kind: \"local\",\n                                            context: \"Adder(int first, int §second§) {\",\n                                            referenced in (1): [\n                                                `this.b = §second§;`,\n                                            ],\n                                        },\n                                    ],\n                                    child scopes: [],\n                                },\n                                scope {\n                                    definitions: [],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [],\n                                        },\n                                    ],\n                                },\n                                scope {\n                                    definitions: [],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [],\n                                        },\n                                    ],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/java/scopes.scm",
    "content": ";; scopes\n\n[\n (block)\n\n ;; class items\n (class_declaration)\n (method_declaration)\n (constructor_declaration)\n\n ;; interface items\n (interface_declaration)\n ;; alternate syntax\n (annotation_type_declaration . \"@interface\")\n\n ;; enums\n (enum_declaration)\n\n ;; records\n (record_declaration)\n\n ;; modules\n (module_declaration)\n\n ;; switch\n ;;\n ;; traditional switch case block\n (switch_block_statement_group)\n ;; functional switch case block\n (switch_rule)\n\n ;; try-catch\n (try_with_resources_statement)\n (catch_clause)\n\n] @local.scope\n\n\n;; defs\n\n;; int a = ..;\n(variable_declarator\n  name: (identifier) @local.definition.local)\n\n;; package ident;\n(package_declaration \n  (identifier) @local.definition.package)\n\n;; module com.foo { .. }\n;;\n;; defines `foo` as a module\n(module_declaration\n  name: (identifier) @hoist.definition.module)\n(module_declaration\n  name: \n  (scoped_identifier\n    (_)\n    (identifier) @hoist.definition.module))\n\n\n;; class Main { .. }\n(class_declaration\n  (identifier) @hoist.definition.class)\n\n;; class Main<T, U> { .. }\n(type_parameters\n  (type_parameter\n    (type_identifier) @local.definition.typedef))\n\n;; methods\n(method_declaration \n  name: (identifier) @hoist.definition.method)\n\n;; constructors\n(constructor_declaration \n  (identifier) @hoist.definition.method)\n\n;; interface Iface { .. }\n(interface_declaration \n  (identifier) @hoist.definition.interface)\n\n;; alternate iface declaration syntax\n;;\n;; @interface Foo { .. }\n(annotation_type_declaration\n  \"@interface\" \n  (identifier) @hoist.definition.interface)\n\n;; enums\n(enum_declaration \n  name: (identifier) @hoist.definition.enum)\n;; enum variants\n(enum_constant\n  (identifier) @local.definition.enumConstant)\n\n;; records\n(record_declaration\n  name: (identifier) @hoist.definition.record)\n\n;; for (Type item: iterator) { .. }\n;; \n;; `item` is a def\n(enhanced_for_statement \n  name: (identifier) @local.definition.local)\n\n;; pattern matching creates defs\n(instanceof_expression\n  .\n  (identifier) \n  (identifier)* @local.definition.local)\n\n;; param list with types\n(formal_parameters\n  (formal_parameter \n    (identifier) @local.definition.local))\n;; param list without types\n(inferred_parameters\n  (identifier) @local.definition.local)\n\n;; catch declaration\n(catch_formal_parameter\n  (identifier) @local.definition.local)\n\n;; try-resource declaration\n(resource\n  name: (identifier) @local.definition.local)\n\n;; singluar lambda param \n;;\n;; arg -> body;\n(lambda_expression\n  parameters: (identifier) @local.definition.local)\n\n;; imports\n;;\n;; import item;\n;;        ^^^^ is an import\n(import_declaration \n  (identifier) @local.import)\n\n;; import java.util.Vector;\n;;                  ^^^^^^ is an import\n(import_declaration \n  (scoped_identifier\n    (_)\n    (identifier) @local.import))\n\n;; labels\n(labeled_statement \n  (identifier) @local.definition.label)\n\n\n;; refs\n\n;; a;\n(expression_statement\n  (identifier) @local.reference)\n\n;; a op b\n(binary_expression \n  (identifier) @local.reference)\n\n;; !a\n(unary_expression\n  (identifier) @local.reference)\n\n;; rhs of a decl. is a ref\n;;\n;; int _ = b;\n(variable_declarator\n  value: (identifier) @local.reference)\n\n;; a.b\n(field_access\n  .\n  (identifier) @local.reference)\n\n;; this.field\n(field_access\n  (this)\n  (identifier) @local.reference)\n\n;; a = b;\n;;\n;; both `a` and `b` are refs\n(assignment_expression\n  (identifier) @local.reference)\n\n;; a instanceOf pattern;\n;;\n;; the first ident is a ref\n;; subsequent idents should be defs\n(instanceof_expression\n  .\n  (identifier) @local.reference)\n\n;; (a)\n(parenthesized_expression \n  (identifier) @local.reference)\n\n;; a()\n(method_invocation\n  .\n  (identifier) @local.reference)\n\n;; this.b();\n(method_invocation\n  (this)\n  (identifier) @local.reference)\n\n;; class::method\n(method_reference\n  .\n  (identifier) @local.reference)\n\n;; _(x, y, z)\n(argument_list\n  (identifier) @local.reference)\n\n;; a ? b : c\n(ternary_expression \n  (identifier) @local.reference)\n\n;; i++\n(update_expression\n  (identifier) @local.reference)\n\n;; a[b]\n(array_access \n  (identifier) @local.reference)\n\n;; (T)ident;\n(cast_expression\n  (identifier) @local.reference)\n(cast_expression\n  (type_identifier) @local.reference)\n\n;; {a, b, c}\n(array_initializer\n  (identifier) @local.reference)\n\n;; new Object();\n;;     ^^^^^^\n(object_creation_expression \n  (type_identifier) @local.reference)\n;; Foo.new Object();\n;; ^^^\n(object_creation_expression \n  (identifier) @local.reference)\n\n;; for (Type item: iterator) { .. }\n;;\n;; `iterator` is a ref\n;; `Type` is a ref\n(enhanced_for_statement\n  value: (identifier) @local.reference)\n\n;; return ident;\n(return_statement \n  (identifier) @local.reference)\n\n;; assert ident;\n(assert_statement\n  (identifier) @local.reference)\n\n;; break label;\n(break_statement\n  (identifier) @local.reference)\n\n;; continue label;\n(continue_statement\n  (identifier) @local.reference)\n\n;; yield item;\n(yield_statement\n  (identifier) @local.reference)\n\n;; lambda body\n(lambda_expression \n  body: (identifier) @local.reference)\n\n;; annotations\n;;\n;; @Documented class C { .. }\n(annotation (identifier) @local.reference)\n(marker_annotation (identifier) @local.reference)\n\n;; case-patterns\n;;\n;; case IDENT -> { .. }\n(switch_label\n  (identifier) @local.reference)\n\n;; try-resource rhs\n(resource\n  value: (identifier) @local.reference)\n\n;; uses com.foo.item;\n(uses_module_directive . \"uses\" . (_) @local.reference)\n;; requires com.foo.item;\n(requires_module_directive . \"requires\" . (_) @local.reference)\n;; exports com.foo.submodule;\n(exports_module_directive . \"exports\" . (_) @local.reference)\n;; opens com.foo.item to some, other, modules;\n(opens_module_directive . \"opens\"\n                        . (_) @local.reference)\n;; provides com.foo.item with com.bar.item\n(provides_module_directive . \"provides\" \n                           . (_) @local.reference\n                           . \"with\"\n                           . (_) @local.reference)\n\n\n\n;; type refs\n\n;; variable declarations with types\n(local_variable_declaration \n  (type_identifier) @local.reference)\n\n;; class field declarations with type \n(field_declaration \n  (type_identifier) @local.reference)\n\n;; List<_, _>\n(generic_type\n  (type_identifier) @local.reference)\n\n;; _<T, U, V>\n;; type args in generics\n(type_arguments\n  (type_identifier) @local.reference)\n\n;; wildcard type\n;;\n;; ? Type\n(wildcard \n  (type_identifier) @local.reference)\n\n;; String[]\n(array_type\n  (type_identifier) @local.reference)\n\n;; type refs in the pattern\n(instanceof_expression\n  (type_identifier) @local.reference)\n\n;; for (Type _: _) { .. }\n(enhanced_for_statement\n  type: (type_identifier) @local.reference)\n\n;; <T extends Class>\n(type_bound \n  (type_identifier) @local.reference)\n\n;; class _ extends B\n(superclass \n  (type_identifier) @local.reference)\n\n;; class _ implements B\n(super_interfaces\n  (type_list\n    (type_identifier) @local.reference))\n\n;; interface _ extends I, J, K\n(extends_interfaces \n  (type_list \n    (type_identifier) @local.reference))\n\n;; sealed interface _ permits I, J, K\n(permits \n  (type_list\n    (type_identifier) @local.reference))\n\n;; parameter types\n(formal_parameter \n  (type_identifier) @local.reference)\n\n;; type refs in method signatures\n;;\n;; return type\n(method_declaration\n  type: (type_identifier) @local.reference)\n;; throws type\n(method_declaration\n  (throws \n    (type_identifier) @local.reference))\n\n;; catch Type1 | Type2 exception\n(catch_type \n  (type_identifier) @local.reference)\n\n;; A.B\n(scoped_type_identifier \n  .\n  (type_identifier) @local.reference)\n\n;; try-resource type\n(resource\n  type: (type_identifier) @local.reference)\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/javascript/mod.rs",
    "content": "use crate::intelligence::{MemoizedQuery, TSLanguageConfig};\n\npub static JAVASCRIPT: TSLanguageConfig = TSLanguageConfig {\n    language_ids: &[\"JavaScript\", \"JSX\"],\n    file_extensions: &[\"js\", \"jsx\"],\n    grammar: tree_sitter_javascript::language,\n    scope_query: MemoizedQuery::new(include_str!(\"./scopes.scm\")),\n    hoverable_query: MemoizedQuery::new(\n        r#\"\n        [(identifier)\n         (property_identifier)\n         (shorthand_property_identifier)\n         (shorthand_property_identifier_pattern)\n         (private_property_identifier)\n         (statement_identifier)] @hoverable\n        \"#,\n    ),\n    namespaces: &[&[\n        //variables\n        \"constant\",\n        \"variable\",\n        \"property\",\n        \"function\",\n        \"method\",\n        \"generator\",\n        // types\n        \"class\",\n        // misc.\n        \"label\",\n    ]],\n};\n\n#[cfg(test)]\nmod test {\n    use crate::intelligence::language::test_utils::*;\n\n    #[test]\n    fn declare_lexical() {\n        let src = r#\"\n            const a = 2;\n            var b = 2;\n            let c = 2;\n\n            // this is an \"assignment\", but introduces `d`\n            // if if does not exist, and hence counted as a decl.\n            d = a;\n        \"#;\n\n        let (_, def_count, _, _) = counts(src, \"JavaScript\");\n\n        // a, b, c, d\n        assert_eq!(def_count, 4);\n    }\n\n    #[test]\n    fn declare_functions() {\n        let src = r#\"\n            function one() {}\n            {\n                two() {},\n                get three() {},\n                set four() {}\n            };\n\n            function* five() {}\n        \"#;\n\n        let (_, def_count, _, _) = counts(src, \"JavaScript\");\n\n        assert_eq!(def_count, 5);\n    }\n\n    #[test]\n    fn declare_destructuring() {\n        let src = r#\"\n            var [a, b] = 5;\n\n            function(c, ...d) {}\n            function(e, f = y) {}\n\n            const g = (h) => {}\n            const i = (j, k) => {}\n\n            // TODO: object patterns with shorthand patterns are \n            // not handled in every situation right now (only in const/var decls.)\n            // function({field: {l, m}}) {}\n\n            function({...n}) {}\n        \"#;\n\n        let (_, def_count, _, _) = counts(src, \"JavaScript\");\n\n        assert_eq!(def_count, 12);\n    }\n\n    #[test]\n    fn declare_class() {\n        let src = r#\"\n            class One {\n                #two\n                static #three\n            }\n        \"#;\n\n        let (_, def_count, _, _) = counts(src, \"JavaScript\");\n\n        // class, prop, prop\n        assert_eq!(def_count, 3);\n    }\n\n    #[test]\n    fn declare_imports() {\n        let src = r#\"\n            import defaultOne from \"module\";\n            import { two, three } from \"module\";\n            import { four, member as five } from \"module\";\n        \"#;\n\n        let (_, _, _, import_count) = counts(src, \"JavaScript\");\n\n        assert_eq!(import_count, 5);\n    }\n\n    #[test]\n    fn declare_misc() {\n        let src = r#\"\n            for (one in items)\n                thing();\n\n            for (var two = 0; a <= 0; a++)\n                thing();\n\n            three:\n                for (;;)\n                    break three;\n        \"#;\n\n        let (_, def_count, _, _) = counts(src, \"JavaScript\");\n\n        assert_eq!(def_count, 3);\n    }\n\n    #[test]\n    fn refer_primitive_expressions() {\n        let src = r#\"\n            var a = 2;\n\n            a;\n            { \"field\": a };\n            [ a ];\n            (a);\n            a.length();\n        \"#;\n\n        let (_, _, ref_count, _) = counts(src, \"JavaScript\");\n\n        assert_eq!(ref_count, 5);\n    }\n\n    #[test]\n    fn refer_statements() {\n        let src = r#\"\n            var a = 2;\n\n            return a;\n            yield a;\n            await a;\n        \"#;\n\n        let (_, _, ref_count, _) = counts(src, \"JavaScript\");\n\n        assert_eq!(ref_count, 3);\n    }\n\n    #[test]\n    fn refer_operators() {\n        let src = r#\"\n            var a = 2;\n            var b = 3;\n            var c = 4;\n\n            // update expr\n            a++;\n\n            // unary\n            -a;\n\n            // binary\n            a + b;\n\n            // ternary\n            c ? a : b;\n\n            // spread\n            {a, b, ...c};\n\n            // index\n            a[b];\n\n            // member\n            // `b` is not a reference here\n            a.b\n        \"#;\n\n        let (_, _, ref_count, _) = counts(src, \"JavaScript\");\n\n        assert_eq!(ref_count, 13);\n    }\n\n    #[test]\n    fn refer_exports() {\n        let src = r#\"\n            var a = 2;\n            var b = 3;\n            var c = 4;\n\n            export { a, b };\n\n            // `alias` is ignored\n            export { a as alias, b };\n            export default c;\n        \"#;\n\n        let (_, _, ref_count, _) = counts(src, \"JavaScript\");\n\n        assert_eq!(ref_count, 5);\n    }\n\n    #[test]\n    fn refer_misc() {\n        let src = r#\"\n            function foo() {}\n\n            var a = 2;\n\n            for (item in a)  // ref a\n                foo(a);      // ref a, foo\n\n            for (var b = 0; b <= 5; b++)  // ref b, b\n                foo(a);                   // ref a, foo\n\n        \"#;\n\n        let (_, _, ref_count, _) = counts(src, \"JavaScript\");\n\n        assert_eq!(ref_count, 7);\n    }\n\n    #[test]\n    fn refer_embedded_jsx() {\n        let src = r#\"\n            const a = 5;\n            b = <Foo.Bar>{string(a)}<span>b</span> c</Foo.Bar>;\n        \"#;\n\n        let (_, _, ref_count, _) = counts(src, \"JavaScript\");\n\n        assert_eq!(ref_count, 1);\n    }\n\n    #[test]\n    fn refer_jsx_opening_element() {\n        let src = r#\"\n        import Button from '../../Button';\n        import ChevronRightIcon from '../../../icons/ChevronRightIcon';\n\n        const NavBarNoUser = () => {\n            return (\n                <span className=\"flex gap-2 justify-self-end\">\n                    <Button size={'medium'} variant={'tertiary'}>\n                    Sign in\n                    </Button>\n                    <Button size={'medium'} variant={'secondary'}>\n                    Sign Up <ChevronRightIcon />\n                    </Button>\n                </span>\n            );\n        };\n        export default NavBarNoUser;\n        \"#;\n\n        // Button           x 4,\n        // ChevronRightIcon x 1,\n        // NavBarNoUser     x 1\n        let (_, _, ref_count, _) = counts(src, \"JSX\");\n\n        assert_eq!(ref_count, 6);\n    }\n\n    // https://github.com/BloopAI/bloop/issues/213\n    #[test]\n    fn function_params() {\n        test_scopes(\n            \"JavaScript\",\n            r#\"\n            function main(a, b) { }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        main {\n                            kind: \"function\",\n                            context: \"function §main§(a, b) { }\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                a {\n                                    kind: \"variable\",\n                                    context: \"function main(§a§, b) { }\",\n                                },\n                                b {\n                                    kind: \"variable\",\n                                    context: \"function main(a, §b§) { }\",\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        );\n    }\n\n    #[test]\n    fn new_expression_regression() {\n        test_scopes(\n            \"JavaScript\",\n            r#\"\n            const { Client } = require(\"@elastic/elasticsearch\");\n            const elasticClient = new Client({node: ELASTIC_CONNECTION_STRING});\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        elasticClient {\n                            kind: \"constant\",\n                            context: \"const §elasticClient§ = new Client({node: ELASTIC_CONNECTION_STRING});\",\n                        },\n                    ],\n                    imports: [\n                        Client {\n                            context: \"const { §Client§ } = require(\\\"@elastic/elasticsearch\\\");\",\n                            referenced in (1): [\n                                `const elasticClient = new §Client§({node: ELASTIC_CONNECTION_STRING});`,\n                            ],\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn catch_clause_regression() {\n        test_scopes(\n            \"JavaScript\",\n            r#\"\n            try {\n                someFn();\n            } catch (err) {\n                return err;\n            } finally {\n                return 0;\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                        scope {\n                            definitions: [\n                                err {\n                                    kind: \"variable\",\n                                    context: \"} catch (§err§) {\",\n                                    referenced in (1): [\n                                        `return §err§;`,\n                                    ],\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/javascript/scopes.scm",
    "content": ";; scopes\n\n[\n  (statement_block)\n  (class_body)\n  (arrow_function)\n  (object)\n  ;; nameless functions create scopes, just like arrow functions\n  (function !name)\n  (function_declaration)\n  (method_definition)\n  (generator_function_declaration)\n  (for_statement)\n  (for_in_statement)\n  (switch_case)\n  (catch_clause)\n  ;; assignments are permitted inside sequence exprs:\n  ;;\n  ;;     const a = 2;\n  ;;     throw f = 1, f, a;\n  ;; \n  ;; should produce:\n  ;;\n  ;;     {\n  ;;       defs: [ a ],\n  ;;       scopes [{\n  ;;          defs: [ f ],\n  ;;          refs: [ f, a ]\n  ;;       }],\n  ;;     }\n  (sequence_expression)\n] @local.scope\n\n\n\n;; defs\n\n;; tree-sitter-javascript has 5 \"declaration\" kinds:\n;;\n;; - class\n;; - function\n;; - generator function\n;; - lexical\n;; - variable\n\n;; function x()\n(function_declaration\n  (identifier) @hoist.definition.function)\n\n(generator_function_declaration\n  (identifier) @hoist.definition.generator)\n\n;; function params\n(formal_parameters\n  (identifier) @local.definition.variable)\n\n;; patterns\n\n;; f(a, ...b)\n(rest_pattern\n  (identifier) @local.definition.variable)\n\n;; f(a, y = f)\n;;\n;; the lhs is a def, the rhs is a ref\n(assignment_pattern\n  (identifier) @local.definition.variable\n  (identifier) @local.reference)\n\n;; {x: y}\n(pair_pattern\n  (property_identifier)\n  (identifier) @local.definition.variable)\n\n;; var x = _\n;; var [x, y] = _\n;; var {x, y} = _\n(variable_declaration\n  (variable_declarator . (identifier) @local.definition.variable))\n(variable_declaration\n  (variable_declarator \n    name: (array_pattern\n            (identifier) @local.definition.variable)))\n(variable_declaration\n  (variable_declarator \n    name: (object_pattern\n            (shorthand_property_identifier_pattern) @local.definition.variable)))\n\n;; const _ = require(_) should produce imports\n(\n (lexical_declaration\n   [\"const\" \"let\"]\n   (variable_declarator \n     name: (identifier) @local.import\n     value: (call_expression \n              function: (identifier) @_req_call)))\n  (#match? @_req_call \"require\")\n )\n\n;; const _ = anything_else should produce const defs\n;; let _ = anything_else should produce var defs\n(\n (lexical_declaration\n   \"const\"\n   (variable_declarator \n     name: (identifier) @local.definition.constant\n     value: (_) @_rest))\n  (#not-match? @_rest \"require.*\")\n )\n(\n (lexical_declaration\n   \"let\"\n   (variable_declarator \n     name: (identifier) @local.definition.variable\n     value: (_) @_rest))\n  (#not-match? @_rest \"require.*\")\n )\n\n;; perform above dance for pattern matching in const/let patterns\n;; - import when\n;;   * const/let with object pattern\n;;   * const/let with array pattern\n;; - define a const when using\n;;   * const with object pattern\n;;   * const with array pattern\n;; - define a variable when using\n;;   * let with object pattern\n;;   * let with array pattern\n\n;; case 1 (imports):\n(\n (lexical_declaration\n   [\"const\" \"let\"]\n   (variable_declarator \n     name: \n     (object_pattern\n       (shorthand_property_identifier_pattern) @local.import)\n     value: (call_expression \n              function: (identifier) @_req_call)))\n (#match? @_req_call \"require\")\n)\n(\n (lexical_declaration\n   [\"const\" \"let\"]\n   (variable_declarator \n     name: \n      (array_pattern\n        (identifier) @local.import)\n     value: (call_expression \n              function: (identifier) @_req_call)))\n (#match? @_req_call \"require\")\n)\n\n;; case 2:\n(\n (lexical_declaration\n   \"const\"\n   (variable_declarator \n     name: \n     (object_pattern\n       (shorthand_property_identifier_pattern) @local.definition.constant)\n     value: (_) @_rest))\n  (#not-match? @_rest \"require.*\")\n)\n(\n (lexical_declaration\n   \"let\"\n   (variable_declarator \n     name: \n     (object_pattern\n       (shorthand_property_identifier_pattern) @local.definition.variable)\n     value: (_) @_rest))\n  (#not-match? @_rest \"require.*\")\n)\n\n;; case 3:\n(\n (lexical_declaration\n   \"const\"\n   (variable_declarator \n     name: \n      (array_pattern\n        (identifier) @local.definition.constant)\n     value: (_) @_rest))\n  (#not-match? @_rest \"require.*\")\n)\n(\n (lexical_declaration\n   \"let\"\n   (variable_declarator \n     name: \n      (array_pattern\n        (identifier) @local.definition.variable)\n     value: (_) @_rest))\n  (#not-match? @_rest \"require.*\")\n)\n\n\n;; a = b\n(assignment_expression\n  left: (identifier) @local.definition.variable)\n\n;; method def\n;;\n;; TODO: support getters and setters here, blocked on:\n;; https://github.com/tree-sitter/tree-sitter/issues/1461\n(method_definition\n  (property_identifier) @hoist.definition.method)\n\n;; class\n(class_declaration\n  (identifier) @local.definition.class)\n\n;; class fields\n(class_body\n  (field_definition\n    (private_property_identifier) @local.definition.property))\n\n;; arrow func\n(arrow_function\n  (identifier) @local.definition.variable)\n\n;; imports\n\n;; import defaultMember from \"module\";\n(import_statement\n  (import_clause (identifier) @local.import))\n\n;; import { member } from \"module\";\n;; import { member as alias } from \"module\";\n(import_statement\n  (import_clause\n    (named_imports\n      [(import_specifier !alias (identifier) @local.import)\n       (import_specifier alias: (identifier) @local.import)])))\n\n;; for (item in list)\n;;\n;; `item` is a def\n(for_in_statement \n  left: (identifier) @local.definition.variable)\n\n;; labels\n(labeled_statement\n  (statement_identifier) @local.definition.label)\n\n;; catch clauses\n(catch_clause\n  (identifier) @local.definition.variable)\n\n;; refs\n\n;; someVar;\n(expression_statement (identifier) @local.reference)\n\n;; { \"a\": value }\n(object\n  (pair\n    (identifier) @local.reference))\n\n;; y = {a, b}\n(object\n  (shorthand_property_identifier) @local.reference)\n\n\n;; [ a, b, c ]\n(array\n  (identifier) @local.reference)\n\n;; new Object()\n(new_expression\n  (identifier) @local.reference)\n\n;; return x;\n(return_statement \n  (identifier) @local.reference)\n\n;; yield t;\n(yield_expression\n  (identifier) @local.reference)\n\n;; call expression\n(call_expression\n  (identifier) @local.reference)\n\n;; call arguments\n(arguments\n  (identifier) @local.reference)\n\n;; index expression\n(subscript_expression\n  (identifier) @local.reference)\n\n;; member expression\n(member_expression\n  (identifier) @local.reference)\n\n;; await ident;\n(await_expression \n  (identifier) @local.reference)\n\n;; a + b\n(binary_expression\n  (identifier) @local.reference)\n\n;; -x\n(unary_expression\n  (identifier) @local.reference)\n\n;; x++\n(update_expression\n  (identifier) @local.reference)\n\n;; a = b\n;; `b` is a ref\n(assignment_expression\n  right: (identifier) @local.reference)\n\n;; a += b\n(augmented_assignment_expression\n  (identifier) @local.reference)\n\n;; (a)\n(parenthesized_expression\n  (identifier) @local.reference)\n\n;; tuples\n(sequence_expression\n  (identifier) @local.reference)\n\n;; c? a : b\n(ternary_expression\n  (identifier) @local.reference)\n\n;; {...object}\n(spread_element\n  (identifier) @local.reference)\n\n;; chass _ extends T\n;; `T` is a ref\n(class_heritage\n  (identifier) @local.reference)\n\n;; exports are refs\n;;\n;; export { name, name };\n;; export { name as alias };\n(export_statement\n  (export_clause\n    (export_specifier name: (identifier) @local.reference)))\n\n;; export default ident;\n(export_statement\n  (identifier) @local.reference)\n\n;; for (item in list)\n;;\n;; `list` is a def\n(for_in_statement \n  right: (identifier) @local.reference)\n\n;; break f;\n(break_statement (statement_identifier) @local.reference)\n\n;; continue f;\n(continue_statement (statement_identifier) @local.reference)\n\n;; jsx\n(jsx_expression\n  (identifier) @local.reference)\n\n(jsx_opening_element\n  (identifier) @local.reference)\n\n(jsx_closing_element\n  (identifier) @local.reference)\n\n(jsx_self_closing_element\n  (identifier) @local.reference)\n\n;; template strings\n(template_substitution\n  (identifier) @local.reference)\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/php/mod.rs",
    "content": "use crate::intelligence::{MemoizedQuery, TSLanguageConfig};\n\npub static PHP: TSLanguageConfig = TSLanguageConfig {\n    language_ids: &[\"PHP\"],\n    file_extensions: &[\"php\"],\n    grammar: tree_sitter_php::language,\n    scope_query: MemoizedQuery::new(include_str!(\"./scopes.scm\")),\n    hoverable_query: MemoizedQuery::new(\n        r#\"\n        (name) @hoverable\n        \"#,\n    ),\n    namespaces: &[&[\n        // variables\n        \"constant\",\n        \"function\",\n        \"method\",\n        \"parameter\",\n        \"variable\",\n        // types\n        \"class\",\n        \"enum\",\n        \"trait\",\n        \"interface\",\n        // fields\n        \"field\",\n        \"enumerator\",\n        // misc\n        \"label\",\n        // namespacing\n        \"namespace\",\n    ]],\n};\n\n#[cfg(test)]\nmod tests {\n    use crate::intelligence::language::test_utils::*;\n\n    #[test]\n    fn declarations() {\n        test_scopes(\n            \"PHP\",\n            r#\"\n            <?php\n            $a = 2;\n            const A = 2;\n            ?>\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        a {\n                            kind: \"variable\",\n                            context: \"$§a§ = 2;\",\n                        },\n                        A {\n                            kind: \"constant\",\n                            context: \"const §A§ = 2;\",\n                        },\n                    ],\n                    child scopes: [],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn functions() {\n        test_scopes(\n            \"PHP\",\n            r#\"\n            <?php\n            $default_value = 2;\n            function foo(A $a, B $b = $default_value) {\n                return $a + $b\n            }\n            ?>\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        default_value {\n                            kind: \"variable\",\n                            context: \"$§default_value§ = 2;\",\n                            referenced in (1): [\n                                `function foo(A $a, B $b = $§default_value§) {`,\n                            ],\n                        },\n                        foo {\n                            kind: \"function\",\n                            context: \"function §foo§(A $a, B $b = $default_value) {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                a {\n                                    kind: \"parameter\",\n                                    context: \"function foo(A $§a§, B $b = $default_value) {\",\n                                    referenced in (1): [\n                                        `return $§a§ + $b`,\n                                    ],\n                                },\n                                b {\n                                    kind: \"parameter\",\n                                    context: \"function foo(A $a, B $§b§ = $default_value) {\",\n                                    referenced in (1): [\n                                        `return $a + $§b§`,\n                                    ],\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn lambdas() {\n        test_scopes(\n            \"PHP\",\n            r#\"\n            <?php\n            $double = $x -> $x * 2;\n            $quadruple = fn($x) => $double($double($x));\n            ?>\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        double {\n                            kind: \"variable\",\n                            context: \"$§double§ = $x -> $x * 2;\",\n                        },\n                        quadruple {\n                            kind: \"variable\",\n                            context: \"$§quadruple§ = fn($x) => $double($double($x));\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                x {\n                                    kind: \"parameter\",\n                                    context: \"$quadruple = fn($§x§) => $double($double($x));\",\n                                    referenced in (1): [\n                                        `$quadruple = fn($x) => $double($double($§x§));`,\n                                    ],\n                                },\n                            ],\n                            child scopes: [],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn classes() {\n        test_scopes(\n            \"PHP\",\n            r#\"\n            <?php\n            interface Identity {\n                function id($arg) {\n                    return $arg;\n                }\n            }\n\n            class Set\n            implements Identity\n            {\n                Array $items;\n\n                function n(): Set {\n                    return (new Set());\n                }\n            }\n            ?>\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        Identity {\n                            kind: \"interface\",\n                            context: \"interface §Identity§ {\",\n                            referenced in (1): [\n                                `implements §Identity§`,\n                            ],\n                        },\n                        Set {\n                            kind: \"class\",\n                            context: \"class §Set§\",\n                            referenced in (2): [\n                                `function n(): §Set§ {`,\n                                `return (new §Set§());`,\n                            ],\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                id {\n                                    kind: \"method\",\n                                    context: \"function §id§($arg) {\",\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        arg {\n                                            kind: \"parameter\",\n                                            context: \"function id($§arg§) {\",\n                                            referenced in (1): [\n                                                `return $§arg§;`,\n                                            ],\n                                        },\n                                    ],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [],\n                                        },\n                                    ],\n                                },\n                            ],\n                        },\n                        scope {\n                            definitions: [\n                                items {\n                                    kind: \"field\",\n                                    context: \"Array $§items§;\",\n                                },\n                                n {\n                                    kind: \"method\",\n                                    context: \"function §n§(): Set {\",\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [],\n                                        },\n                                    ],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn control_flow() {\n        test_scopes(\n            \"PHP\",\n            r#\"\n            <?php\n            $a = TRUE;\n            $b = FALSE;\n            $c = 2;\n            $xs = [];\n\n            if ($a) $c;\n            if ($a) {\n                return $c;\n            } else {\n                return $c;\n            }\n            if ($a) {\n                return $c;\n            } else if ($b) {\n                return $c;\n            } else {\n                return $c;\n            }\n\n            for ($i = 0; $i < 10; $i++) {\n                return $c;\n            }\n\n            foreach($xs as $x) {\n                return $x;\n            }\n            ?>\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        a {\n                            kind: \"variable\",\n                            context: \"$§a§ = TRUE;\",\n                            referenced in (3): [\n                                `if ($§a§) $c;`,\n                                `if ($§a§) {`,\n                                `if ($§a§) {`,\n                            ],\n                        },\n                        b {\n                            kind: \"variable\",\n                            context: \"$§b§ = FALSE;\",\n                            referenced in (1): [\n                                `} else if ($§b§) {`,\n                            ],\n                        },\n                        c {\n                            kind: \"variable\",\n                            context: \"$§c§ = 2;\",\n                            referenced in (7): [\n                                `if ($a) $§c§;`,\n                                `return $§c§;`,\n                                `return $§c§;`,\n                                `return $§c§;`,\n                                `return $§c§;`,\n                                `return $§c§;`,\n                                `return $§c§;`,\n                            ],\n                        },\n                        xs {\n                            kind: \"variable\",\n                            context: \"$§xs§ = [];\",\n                            referenced in (1): [\n                                `foreach($§xs§ as $x) {`,\n                            ],\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [],\n                                },\n                                scope {\n                                    definitions: [],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [],\n                                        },\n                                    ],\n                                },\n                            ],\n                        },\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [],\n                                },\n                                scope {\n                                    definitions: [],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [\n                                                scope {\n                                                    definitions: [],\n                                                    child scopes: [],\n                                                },\n                                                scope {\n                                                    definitions: [],\n                                                    child scopes: [\n                                                        scope {\n                                                            definitions: [],\n                                                            child scopes: [],\n                                                        },\n                                                    ],\n                                                },\n                                            ],\n                                        },\n                                    ],\n                                },\n                            ],\n                        },\n                        scope {\n                            definitions: [\n                                i {\n                                    kind: \"variable\",\n                                    context: \"for ($§i§ = 0; $i < 10; $i++) {\",\n                                    referenced in (2): [\n                                        `for ($i = 0; $§i§ < 10; $i++) {`,\n                                        `for ($i = 0; $i < 10; $§i§++) {`,\n                                    ],\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                        scope {\n                            definitions: [\n                                x {\n                                    kind: \"variable\",\n                                    context: \"foreach($xs as $§x§) {\",\n                                    referenced in (1): [\n                                        `return $§x§;`,\n                                    ],\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/php/scopes.scm",
    "content": ";; scopes\n[(function_definition)\n (declaration_list)\n (compound_statement)\n (if_statement)\n (else_clause)\n (else_if_clause)\n (colon_block)\n (switch_block)\n (case_statement)\n (while_statement)\n (for_statement)\n (foreach_statement)\n (match_expression)\n (match_condition_list)\n (anonymous_function_creation_expression)\n (arrow_function)\n (enum_declaration_list)\n (method_declaration)\n ] @local.scope\n\n;; defs\n(assignment_expression\n  .\n  (variable_name\n    (name) @local.definition.variable))\n\n(function_definition\n  (name) @hoist.definition.function)\n\n(class_declaration\n  (name) @local.definition.class)\n\n(trait_declaration\n  (name) @local.definition.trait)\n\n(interface_declaration\n  (name) @local.definition.interface)\n\n(method_declaration\n  (name) @hoist.definition.method)\n\n(static_variable_declaration\n  .\n  (variable_name\n    (name) @local.definition.constant))\n\n(const_declaration\n  .\n  (const_element\n    (name) @local.definition.constant))\n\n(simple_parameter\n  name: (variable_name\n    (name) @local.definition.parameter))\n\n(variadic_parameter\n  (variable_name\n    (name) @local.definition.parameter))\n\n(foreach_statement\n  \"as\"\n  (variable_name\n    (name) @local.definition.variable))\n\n(list_literal\n  (variable_name\n    (name) @local.definition.variable))\n\n(named_label_statement\n  (name) @local.definition.label)\n\n(property_element\n  (variable_name\n    (name) @local.definition.field))\n\n(namespace_definition\n  (namespace_name) @local.definition.namespace)\n\n(enum_declaration\n  (name) @local.definition.enum)\n\n(enum_case\n  (name) @local.definition.enumerator)\n\n;; imports\n\n(namespace_use_declaration\n  (namespace_use_clause\n    .\n    (name) @local.import\n    .))\n\n(namespace_use_declaration\n  (namespace_use_clause\n    (namespace_aliasing_clause\n      (name) @local.import)))\n\n(namespace_use_declaration\n  (namespace_use_clause\n    (qualified_name\n      (name) @local.import)))\n\n(namespace_use_declaration\n  (namespace_use_group\n    (namespace_use_group_clause\n      (namespace_name\n        (name) @local.import))))\n\n;; refs\n\n(assignment_expression\n  (variable_name\n    (name)@local.reference)\n  .)\n\n(augmented_assignment_expression\n  (variable_name\n    (name) @local.reference))\n\n\n(static_variable_declaration\n  (variable_name\n    (name)@local.reference  )\n  .)\n\n(echo_statement\n  [(variable_name)\n   (name)] @local.reference)\n\n(expression_statement\n  (function_call_expression\n    (name) @local.reference))\n\n(arguments\n  (argument\n    (name) @local.reference\n    .))\n\n(arguments\n  (argument\n    (variable_name\n      (name) @local.reference)))\n\n(global_declaration\n  (variable_name\n    (name) @local.reference))\n\n(binary_expression\n  (variable_name (name)  @local.reference))\n(binary_expression\n  (name)  @local.reference)\n\n(unary_op_expression\n    (variable_name (name) @local.reference))\n(unary_op_expression\n    (name) @local.reference)\n\n(member_access_expression\n  (variable_name\n    (name) @local.reference))\n\n(member_call_expression\n  object: (variable_name\n    (name) @local.reference))\n(member_call_expression\n  name: (name) @local.reference)\n\n(subscript_expression\n  (variable_name (name) @local.reference))\n(subscript_expression\n  (name) @local.reference)\n\n(return_statement\n  (variable_name (name) @local.reference))\n(return_statement\n  (name) @local.reference)\n\n(update_expression\n  (variable_name (name) @local.reference))\n\n(conditional_expression\n  (variable_name (name) @local.reference))\n(conditional_expression\n  (name) @local.reference)\n\n(array_element_initializer\n  (variable_name (name) @local.reference))\n(array_element_initializer\n  (name) @local.reference)\n\n(object_creation_expression\n  (variable_name (name) @local.reference))\n(object_creation_expression\n  (name) @local.reference)\n\n(base_clause \n  (name) @local.reference)\n\n(class_interface_clause\n  (name) @local.reference)\n\n(parenthesized_expression\n  (variable_name (name) @local.reference))\n(parenthesized_expression\n  (name) @local.reference)\n\n(expression_statement \n  (variable_name (name) @local.reference))\n(expression_statement \n  (name) @local.reference)\n\n(case_statement\n  (variable_name (name) @local.reference))\n(case_statement\n  (name) @local.reference)\n\n(case_statement\n  (variable_name (name) @local.reference))\n(case_statement\n  (name) @local.reference)\n\n(foreach_statement\n  .\n  (variable_name (name) @local.reference))\n(foreach_statement\n  .\n  (name) @local.reference)\n\n(by_ref\n  (variable_name (name) @local.reference))\n\n(encapsed_string\n  (variable_name\n    (name) @local.reference))\n\n(match_condition_list\n  (variable_name\n    (name) @local.reference))\n\n(match_conditional_expression\n  return_expression: (variable_name\n                       (name) @local.reference))\n\n(goto_statement\n  (name) @local.reference)\n\n(anonymous_function_use_clause\n  (variable_name\n    (name) @local.reference))\n\n(scoped_call_expression\n  scope: (name) @local.reference)\n\n(scoped_call_expression\n  name: (name) @local.reference)\n\n(class_constant_access_expression\n  (name) @local.reference)\n\n(class_declaration\n  (attribute_list\n    (attribute_group\n      (attribute\n        (name) @local.reference))))\n\n(namespace_use_declaration\n  (namespace_name) @local.reference)\n\n(simple_parameter\n  default_value: \n   (name) @local.reference)\n\n(simple_parameter\n  default_value: \n   (variable_name\n     (name) @local.reference))\n\n;; types\n(named_type \n  (name) @local.reference)\n\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/python/mod.rs",
    "content": "use crate::intelligence::{MemoizedQuery, TSLanguageConfig};\n\npub static PYTHON: TSLanguageConfig = TSLanguageConfig {\n    language_ids: &[\"Python\"],\n    file_extensions: &[\"py\"],\n    grammar: tree_sitter_python::language,\n    scope_query: MemoizedQuery::new(include_str!(\"./scopes.scm\")),\n    hoverable_query: MemoizedQuery::new(\n        r#\"\n        (identifier) @hoverable\n        \"#,\n    ),\n    namespaces: &[&[\"class\", \"function\", \"parameter\", \"variable\"]],\n};\n\n#[cfg(test)]\nmod tests {\n    use crate::intelligence::language::test_utils::*;\n\n    // tests the following constructs:\n    // - function definitions\n    // - function parameters\n    // - default parameters\n    // - block scopes\n    // - assignments statements\n    // - augmented assignment statements\n    // - function calls\n    #[test]\n    fn basic() {\n        test_scopes(\n            \"Python\",\n            r#\"\n            def increment(value, by=1):\n                value += by\n\n            def main():\n                a = 5\n                b = 3\n\n                increment(a)\n                increment(a, by=b)\n\n            main()\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        increment {\n                            kind: \"function\",\n                            context: \"def §increment§(value, by=1):\",\n                            referenced in (2): [\n                                `§increment§(a)`,\n                                `§increment§(a, by=b)`,\n                            ],\n                        },\n                        main {\n                            kind: \"function\",\n                            context: \"def §main§():\",\n                            referenced in (1): [\n                                `§main§()`,\n                            ],\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                value {\n                                    kind: \"parameter\",\n                                    context: \"def increment(§value§, by=1):\",\n                                    referenced in (1): [\n                                        `§value§ += by`,\n                                    ],\n                                },\n                                by {\n                                    kind: \"parameter\",\n                                    context: \"def increment(value, §by§=1):\",\n                                    referenced in (1): [\n                                        `value += §by§`,\n                                    ],\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        a {\n                                            kind: \"variable\",\n                                            context: \"§a§ = 5\",\n                                            referenced in (2): [\n                                                `increment(§a§)`,\n                                                `increment(§a§, by=b)`,\n                                            ],\n                                        },\n                                        b {\n                                            kind: \"variable\",\n                                            context: \"§b§ = 3\",\n                                            referenced in (1): [\n                                                `increment(a, by=§b§)`,\n                                            ],\n                                        },\n                                    ],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    // tests the following constructs:\n    // - from imports\n    // - imports\n    // - list comprehensions\n    // - type annotations\n    #[test]\n    fn complex() {\n        test_scopes(\n            \"Python\",\n            r#\"\n            from typings import List\n            import math\n\n            def sines(items: List[int]) -> List[int]:\n                return [math.sin(item) for item in items]\n\n            list = [1, 2, 3]\n            sines(list)\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        sines {\n                            kind: \"function\",\n                            context: \"def §sines§(items: List[int]) -> List[int]:\",\n                            referenced in (1): [\n                                `§sines§(list)`,\n                            ],\n                        },\n                        list {\n                            kind: \"variable\",\n                            context: \"§list§ = [1, 2, 3]\",\n                            referenced in (1): [\n                                `sines(§list§)`,\n                            ],\n                        },\n                    ],\n                    imports: [\n                        List {\n                            context: \"from typings import §List§\",\n                            referenced in (2): [\n                                `def sines(items: §List§[int]) -> List[int]:`,\n                                `def sines(items: List[int]) -> §List§[int]:`,\n                            ],\n                        },\n                        math {\n                            context: \"import §math§\",\n                            referenced in (1): [\n                                `return [§math§.sin(item) for item in items]`,\n                            ],\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                items {\n                                    kind: \"parameter\",\n                                    context: \"def sines(§items§: List[int]) -> List[int]:\",\n                                    referenced in (1): [\n                                        `return [math.sin(item) for item in §items§]`,\n                                    ],\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [\n                                                item {\n                                                    kind: \"variable\",\n                                                    context: \"return [math.sin(item) for §item§ in items]\",\n                                                    referenced in (1): [\n                                                        `return [math.sin(§item§) for item in items]`,\n                                                    ],\n                                                },\n                                            ],\n                                            child scopes: [],\n                                        },\n                                    ],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    // tests class definitions\n    #[test]\n    fn classes() {\n        test_scopes(\n            \"Python\",\n            r#\"\n            class Foo():\n                def bar(self):\n                    return self\n\n            def main():\n                a = Foo()\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        Foo {\n                            kind: \"class\",\n                            context: \"class §Foo§():\",\n                            referenced in (1): [\n                                `a = §Foo§()`,\n                            ],\n                        },\n                        main {\n                            kind: \"function\",\n                            context: \"def §main§():\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                bar {\n                                    kind: \"function\",\n                                    context: \"def §bar§(self):\",\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        self {\n                                            kind: \"parameter\",\n                                            context: \"def bar(§self§):\",\n                                            referenced in (1): [\n                                                `return §self§`,\n                                            ],\n                                        },\n                                    ],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [],\n                                        },\n                                    ],\n                                },\n                            ],\n                        },\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        a {\n                                            kind: \"variable\",\n                                            context: \"§a§ = Foo()\",\n                                        },\n                                    ],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    // tests edge cases\n    #[test]\n    fn absurd() {\n        // circular assignment\n        test_scopes(\n            \"Python\",\n            \"\n            some_list = some_list[0] = [0, 1]\n            \"\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        some_list {\n                            kind: \"variable\",\n                            context: \"§some_list§ = some_list[0] = [0, 1]\",\n                            referenced in (1): [\n                                `some_list = §some_list§[0] = [0, 1]`,\n                            ],\n                        },\n                    ],\n                    child scopes: [],\n                }\n            \"#]],\n        );\n\n        // circular func call\n        test_scopes(\n            \"Python\",\n            \"\n            fix = lambda f: fix(f)\n            \"\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        fix {\n                            kind: \"variable\",\n                            context: \"§fix§ = lambda f: fix(f)\",\n                            referenced in (1): [\n                                `fix = lambda f: §fix§(f)`,\n                            ],\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                f {\n                                    kind: \"parameter\",\n                                    context: \"fix = lambda §f§: fix(f)\",\n                                    referenced in (1): [\n                                        `fix = lambda f: fix(§f§)`,\n                                    ],\n                                },\n                            ],\n                            child scopes: [],\n                        },\n                    ],\n                }\n            \"#]],\n        );\n    }\n\n    #[test]\n    fn decorators() {\n        test_scopes(\n            \"Python\",\n            r#\"\n            from module import decor\n\n            @decor\n            def foo():\n                pass\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        foo {\n                            kind: \"function\",\n                            context: \"def §foo§():\",\n                        },\n                    ],\n                    imports: [\n                        decor {\n                            context: \"from module import §decor§\",\n                            referenced in (1): [\n                                `@§decor§`,\n                            ],\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn types() {\n        test_scopes(\n            \"Python\",\n            r#\"\n            MyType = List[int]\n\n            def foo(t: MyType) -> MyType:\n                a: MyType = [1, 2, 3]\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        MyType {\n                            kind: \"variable\",\n                            context: \"§MyType§ = List[int]\",\n                            referenced in (3): [\n                                `def foo(t: §MyType§) -> MyType:`,\n                                `def foo(t: MyType) -> §MyType§:`,\n                                `a: §MyType§ = [1, 2, 3]`,\n                            ],\n                        },\n                        foo {\n                            kind: \"function\",\n                            context: \"def §foo§(t: MyType) -> MyType:\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                t {\n                                    kind: \"parameter\",\n                                    context: \"def foo(§t§: MyType) -> MyType:\",\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        a {\n                                            kind: \"variable\",\n                                            context: \"§a§: MyType = [1, 2, 3]\",\n                                        },\n                                    ],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/python/scopes.scm",
    "content": ";; scopes\n;;\n[(block)\n (lambda)\n\n ;; defs in comprehensions are limited to the\n ;; comprehension itself\n (list_comprehension)\n (dictionary_comprehension)\n (set_comprehension)\n (generator_expression)\n\n ;; compound statements\n (with_statement)\n (for_statement)\n\n (function_definition)\n ] @local.scope\n\n\n;; defs\n\n;; all assignments are counted as defs\n(assignment\n  left: (identifier) @local.definition.variable)\n\n;; assignment patterns\n;; a, b = 1, 2\n(pattern_list\n  (identifier) @local.definition.variable)\n\n;; walrus\n(named_expression\n  .\n  (identifier) @local.definition.variable)\n\n;; def a()\n(function_definition\n  (identifier) @hoist.definition.function)\n\n;; def _(a, b, c):\n(parameters\n  (identifier) @local.definition.parameter)\n\n;; def_(a: str)\n(typed_parameter\n  (identifier) @local.definition.parameter)\n\n;; lambda a, b, c: \n(lambda_parameters\n  (identifier) @local.definition.parameter)\n\n;; default params\n;;\n;;    def foo(printer=val):\n;;\n;; `printer` is a def\n;; `val` is ignored\n(default_parameter\n  .\n  (identifier) @local.definition.parameter)\n\n;; patterns\n(list_splat_pattern\n  (identifier) @local.definition.variable)\n(dictionary_splat_pattern\n  (identifier) @local.definition.variable)\n(tuple_pattern\n  (identifier) @local.definition.variable)\n\n;; with a as b:\n;;\n;; `b` is a def\n(as_pattern\n  (as_pattern_target\n    (identifier) @local.definition.variable))\n\n;; thing() for x in xs\n;;\n;; `x` is a def\n(for_in_clause\n  .\n  \"for\"\n  .\n  (identifier) @local.definition.variable)\n\n;; for a in b:\n;;\n;; `a` is a def\n(for_statement\n  .\n  \"for\"\n  .\n  (identifier) @local.definition.variable)\n\n;; imports:\n;;\n;;     import a, b\n;;     import module.submodule.c\n;;\n;; here, `a`, `b`, `c` are imports, `module` and\n;; `submodule` are ignored. so we capture the last\n;; child of the `dotted_name` node using an anchor.\n(import_statement\n  (dotted_name\n    (identifier) @local.import\n    .))\n\n;; import a as b\n;;\n;; `a` is ignored\n;; `b` is an import\n(import_statement\n  (aliased_import\n    \"as\"\n    (identifier) @local.import))\n\n;; from module import name1, name2\n(import_from_statement\n  name: \n  (dotted_name\n    (identifier) @local.import))\n\n;; from __future__ import name\n(future_import_statement\n  name:\n  (dotted_name \n    (identifier) @local.import))\n\n;; class A\n(class_definition\n  (identifier) @local.definition.class)\n\n;; global a, b\n(global_statement\n  (identifier) @local.definition.variable)\n\n\n;; refs\n\n;;[a, b, c]\n(list\n  (identifier) @local.reference)\n\n;; f-strings\n(interpolation\n  (identifier) @local.reference)\n\n;; [ *a ]\n(list_splat\n  (identifier) @local.reference)\n\n;; {a: A}\n;; a is ignored\n;; A is a ref\n(dictionary\n  (pair\n    (identifier)\n    (identifier) @local.reference))\n\n;; **dict\n(dictionary_splat\n  (identifier) @local.reference)\n\n;; {a, b, c}\n(set\n  (identifier) @local.reference)\n\n;; a.b\n;; `a` is a ref\n;; `b` is ignored\n(attribute\n  .\n  (identifier) @local.reference)\n\n;; if we have self.field(), we can resolve field\n;; safely\n(attribute \n  (identifier) @_self_ident\n  (identifier) @local.reference\n  (#eq? @_self_ident \"self\"))\n\n;; a[b]\n(subscript\n  (identifier) @local.reference)\n\n;; a[i:j]\n(slice\n  (identifier) @local.reference)\n\n;; a()\n(call\n  (identifier) @local.reference)\n\n;; call arguments\n(argument_list\n  (identifier) @local.reference)\n\n;; call(keyword=arg)\n;; `keyword` is ignored\n;; `arg` is a ref\n(keyword_argument\n  (_)\n  (identifier) @local.reference)\n\n;; (a, b, c)\n(tuple \n  (identifier) @local.reference)\n\n;; for t in item\n;;\n;; `item` is a reference\n(for_in_clause\n  \"in\"\n  (identifier) @local.reference)\n\n;; for a in b:\n;;\n;; `b` is a ref\n(for_statement\n  \"in\"\n  .\n  (identifier) @local.reference)\n\n;; with a as b:\n;;\n;; `a` is a ref\n(as_pattern\n  (identifier) @local.reference)\n\n;; (a for a in ..)\n(generator_expression\n  (identifier) @local.reference)\n\n;; await x\n(await\n  (identifier) @local.reference)\n\n;; return x\n(return_statement\n  (identifier) @local.reference)\n\n;; a + b\n(binary_operator\n  (identifier) @local.reference)\n\n;; ~a\n(unary_operator\n  (identifier) @local.reference)\n\n;; a and b\n(boolean_operator\n  (identifier) @local.reference)\n\n;; not a \n(not_operator\n  (identifier) @local.reference)\n\n;; a in b\n;; a < b\n(comparison_operator\n  (identifier) @local.reference)\n\n;; a += 1\n(augmented_assignment\n  (identifier) @local.reference)\n\n;; (a)\n(parenthesized_expression\n  (identifier) @local.reference)\n\n;; a, b, c\n(expression_list\n  (identifier) @local.reference)\n\n;; a;\n(expression_statement\n  (identifier) @local.reference)\n\n;; z if x else y\n(conditional_expression\n  (identifier) @local.reference)\n\n;; comprehensions\n(list_comprehension\n  (identifier) @local.reference)\n(dictionary_comprehension\n  (pair \n    (identifier) @local.reference))\n(set_comprehension\n  (identifier) @local.reference)\n\n;; decorators\n(decorator\n  (identifier) @local.reference)\n\n;; type refs\n;;\n;; def foo(a: T)\n(parameters\n  (typed_parameter\n    (type\n      (identifier) @local.reference)))\n\n;; def foo() -> T:\n(function_definition \n  return_type: \n  (type\n    (identifier) @local.reference))\n\n;; var: T = init()\n(assignment \n  type:\n  (type \n    (identifier) @local.reference))\n\n;; python 2\n;;\n;; print item\n(print_statement\n  (identifier) @local.reference)\n;; print >> a\n(chevron\n  (identifier) @local.reference)\n;; assert a, b, c\n(assert_statement\n  (identifier) @local.reference)\n;; exec '1+1'\n(exec_statement\n  (identifier) @local.reference)\n\n;; del a, b, c\n(delete_statement\n  (identifier) @local.reference)\n\n(while_statement\n  (identifier) @local.reference)\n\n(if_statement\n  (identifier) @local.reference)\n\n;; raise error from e \n(raise_statement\n  (identifier) @local.reference)\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/r/mod.rs",
    "content": "use crate::intelligence::{MemoizedQuery, TSLanguageConfig};\n\npub static R: TSLanguageConfig = TSLanguageConfig {\n    language_ids: &[\"R\"],\n    file_extensions: &[\"R\"],\n    grammar: tree_sitter_r::language,\n    scope_query: MemoizedQuery::new(include_str!(\"./scopes.scm\")),\n    hoverable_query: MemoizedQuery::new(\n        r#\"\n        (identifier) @hoverable\n        \"#,\n    ),\n    namespaces: &[&[\n        // variables\n        \"variable\",\n    ]],\n};\n\n#[cfg(test)]\nmod tests {\n    use crate::intelligence::language::test_utils::*;\n\n    // Self::method and self.method can be raised as references\n    #[test]\n    fn declarations() {\n        test_scopes(\n            \"R\",\n            r#\"\n            x <- value\n            value -> y\n            x[0] <<- value\n            value ->> y[0]\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        x {\n                            kind: \"variable\",\n                            context: \"§x§ <- value\",\n                            referenced in (1): [\n                                `§x§[0] <<- value`,\n                            ],\n                        },\n                        y {\n                            kind: \"variable\",\n                            context: \"value -> §y§\",\n                            referenced in (1): [\n                                `value ->> §y§[0]`,\n                            ],\n                        },\n                    ],\n                    child scopes: [],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn control_if() {\n        test_scopes(\n            \"R\",\n            r#\"\n            x <- TRUE\n            y <- value\n            if (x)\n                return(y)\n            else \n                return(y)\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        x {\n                            kind: \"variable\",\n                            context: \"§x§ <- TRUE\",\n                            referenced in (1): [\n                                `if (§x§)`,\n                            ],\n                        },\n                        y {\n                            kind: \"variable\",\n                            context: \"§y§ <- value\",\n                            referenced in (2): [\n                                `return(§y§)`,\n                                `return(§y§)`,\n                            ],\n                        },\n                    ],\n                    child scopes: [],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn control_loop() {\n        test_scopes(\n            \"R\",\n            r#\"\n            x <- TRUE\n            repeat x\n\n            while (x) return\n\n            y <- c(1, 2, 3)\n            for (item in y) {\n                y <- item + 1\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        x {\n                            kind: \"variable\",\n                            context: \"§x§ <- TRUE\",\n                            referenced in (2): [\n                                `repeat §x§`,\n                                `while (§x§) return`,\n                            ],\n                        },\n                        y {\n                            kind: \"variable\",\n                            context: \"§y§ <- c(1, 2, 3)\",\n                            referenced in (1): [\n                                `for (item in §y§) {`,\n                            ],\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                item {\n                                    kind: \"variable\",\n                                    context: \"for (§item§ in y) {\",\n                                    referenced in (1): [\n                                        `y <- §item§ + 1`,\n                                    ],\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        y {\n                                            kind: \"variable\",\n                                            context: \"§y§ <- item + 1\",\n                                        },\n                                    ],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn control_switch() {\n        test_scopes(\n            \"R\",\n            r#\"\n            x <- \"add\"\n\n            y <- 2\n            z <- 1\n            switch(x, \"add\" = y + z, \"subtract\" = y - z)\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        x {\n                            kind: \"variable\",\n                            context: \"§x§ <- \\\"add\\\"\",\n                            referenced in (1): [\n                                `switch(§x§, \"add\" = y + z, \"subtract\" = y - z)`,\n                            ],\n                        },\n                        y {\n                            kind: \"variable\",\n                            context: \"§y§ <- 2\",\n                            referenced in (2): [\n                                `switch(x, \"add\" = §y§ + z, \"subtract\" = y - z)`,\n                                `switch(x, \"add\" = y + z, \"subtract\" = §y§ - z)`,\n                            ],\n                        },\n                        z {\n                            kind: \"variable\",\n                            context: \"§z§ <- 1\",\n                            referenced in (2): [\n                                `switch(x, \"add\" = y + §z§, \"subtract\" = y - z)`,\n                                `switch(x, \"add\" = y + z, \"subtract\" = y - §z§)`,\n                            ],\n                        },\n                    ],\n                    child scopes: [],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn indexing() {\n        test_scopes(\n            \"R\",\n            r#\"\n            x <- c(1, 2, 3)\n\n            idx <- 1\n\n            y <- x[i]\n            z <- x $ i\n            w <- x[[i]]\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        x {\n                            kind: \"variable\",\n                            context: \"§x§ <- c(1, 2, 3)\",\n                            referenced in (3): [\n                                `y <- §x§[i]`,\n                                `z <- §x§ $ i`,\n                                `w <- §x§[[i]]`,\n                            ],\n                        },\n                        idx {\n                            kind: \"variable\",\n                            context: \"§idx§ <- 1\",\n                        },\n                        y {\n                            kind: \"variable\",\n                            context: \"§y§ <- x[i]\",\n                        },\n                        z {\n                            kind: \"variable\",\n                            context: \"§z§ <- x $ i\",\n                        },\n                        w {\n                            kind: \"variable\",\n                            context: \"§w§ <- x[[i]]\",\n                        },\n                    ],\n                    child scopes: [],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn value_of_function_definition() {\n        let src = r#\"foo <- function (a, b, c) { // 0\n                        a <- a + 1               // 1\n                        b <- a + 1               // 2\n                        c <- a + 1               // 3\n                    }                            // 4\"#;\n\n        let sg = build_graph(\"R\", src.as_bytes());\n        let foo_function = sg.find_node_by_name(src.as_bytes(), b\"foo\").unwrap();\n        let function_node = &sg.graph[sg.value_of_definition(foo_function).unwrap()];\n\n        assert_eq!(function_node.range().start.line, 0);\n        assert_eq!(function_node.range().end.line, 4);\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/r/scopes.scm",
    "content": ";; scopes\n[(brace_list)\n (function_definition)\n (for)] @local.scope\n\n;; defs\n\n;; lhs of assignment\n(left_assignment\n  .\n  (identifier) @local.definition.variable)\n(right_assignment\n  (identifier) @local.definition.variable\n  .)\n(super_assignment\n  .\n  (identifier) @local.definition.variable)\n(super_right_assignment\n  (identifier) @local.definition.variable\n  .)\n\n(for\n  .\n  (identifier) @local.definition.variable)\n\n(formal_parameters\n  (identifier) @local.definition.variable)\n\n;; refs\n\n;; rhs of assignment\n(left_assignment\n  (identifier) @local.reference\n  .)\n(right_assignment\n  .\n  (identifier) @local.reference)\n(super_assignment\n  (identifier) @local.reference\n  .)\n(super_right_assignment\n  .\n  (identifier) @local.reference)\n\n(call\n  (identifier) @local.reference @_call_name\n  (#not-eq? @_call_name \"c\")) ;; used to refer to vector inits, probably noisy\n\n(namespace_get\n  .\n  (identifier) @local.reference)\n\n(binary\n  (identifier) @local.reference)\n\n(dollar\n  .\n  (identifier) @local.reference)\n\n(subset\n  (identifier) @local.reference)\n\n(subset2\n  (identifier) @local.reference)\n\n;; TODO: this matches both a and b in foo(a = b)\n;; the grammar does not create a new structure for named arguments\n(arguments\n  (identifier) @local.reference)\n\n(if \n  (identifier) @local.reference)\n\n(repeat\n  (identifier) @local.reference)\n\n(while\n  (identifier) @local.reference)\n\n(for\n  \"in\"\n  (identifier) @local.reference)\n\n(switch \n  (identifier) @local.reference)\n\n(brace_list\n  (identifier) @local.reference)\n\n(function_definition\n  (identifier) @local.reference)\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/ruby/mod.rs",
    "content": "use crate::intelligence::{MemoizedQuery, TSLanguageConfig};\n\npub static RUBY: TSLanguageConfig = TSLanguageConfig {\n    language_ids: &[\"Ruby\"],\n    file_extensions: &[\"rb\"],\n    grammar: tree_sitter_ruby::language,\n    scope_query: MemoizedQuery::new(include_str!(\"./scopes.scm\")),\n    hoverable_query: MemoizedQuery::new(\n        r#\"\n        [(identifier)\n        (class_variable)\n        (instance_variable)\n        (constant)\n        (global_variable)\n        (hash_key_symbol)] @hoverable\n        \"#,\n    ),\n    namespaces: &[\n        // everything is an object\n        &[\"variable\", \"constant\", \"class\", \"method\", \"module\"],\n    ],\n};\n\n#[cfg(test)]\nmod tests {\n    use crate::intelligence::language::test_utils::*;\n\n    // tests the following constructs\n    //\n    // - variable assignment\n    // - if-then\n    #[test]\n    fn basic_decl() {\n        test_scopes(\n            \"Ruby\",\n            r#\"\n            favoriteNumber = 5\n            if favoriteNumber == 5\n                puts \"My favorite number is 5!\"\n            end\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        favoriteNumber {\n                            kind: \"variable\",\n                            context: \"§favoriteNumber§ = 5\",\n                            referenced in (1): [\n                                `if §favoriteNumber§ == 5`,\n                            ],\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    // tests the following constructs:\n    //\n    // - const decl.\n    // - class decl.\n    // - instance variable decl.\n    // - method decl.\n    // - method param decl.\n    // - default method params\n    #[test]\n    fn const_and_class_decl() {\n        test_scopes(\n            \"Ruby\",\n            r#\"\n            X, Y = 2, 3\n            class Human\n                @age, @height = 0, 0\n                def age(age=@age)\n                    @age\n                end\n            end\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        X {\n                            kind: \"constant\",\n                            context: \"§X§, Y = 2, 3\",\n                        },\n                        Y {\n                            kind: \"constant\",\n                            context: \"X, §Y§ = 2, 3\",\n                        },\n                        Human {\n                            kind: \"class\",\n                            context: \"class §Human§\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                @age {\n                                    kind: \"variable\",\n                                    context: \"§@age§, @height = 0, 0\",\n                                    referenced in (1): [\n                                        `§@age§`,\n                                    ],\n                                },\n                                @height {\n                                    kind: \"variable\",\n                                    context: \"@age, §@height§ = 0, 0\",\n                                },\n                                age {\n                                    kind: \"method\",\n                                    context: \"def §age§(age=@age)\",\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        age {\n                                            kind: \"variable\",\n                                            context: \"def age(§age§=@age)\",\n                                        },\n                                    ],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    // tests the following constructs\n    //\n    // - lambda assignment\n    // - lambda param decl.\n    // - lambda body\n    // - method decl.\n    // - method param decl.\n    #[test]\n    fn methods_and_lambdas() {\n        test_scopes(\n            \"Ruby\",\n            r#\"\n            l = -> (x) { x + 1 }\n            def update(l, v)\n                l.call v\n            end\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        l {\n                            kind: \"variable\",\n                            context: \"§l§ = -> (x) { x + 1 }\",\n                            referenced in (1): [\n                                `§l§.call v`,\n                            ],\n                        },\n                        update {\n                            kind: \"method\",\n                            context: \"def §update§(l, v)\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                x {\n                                    kind: \"variable\",\n                                    context: \"l = -> (§x§) { x + 1 }\",\n                                    referenced in (1): [\n                                        `l = -> (x) { §x§ + 1 }`,\n                                    ],\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                        scope {\n                            definitions: [\n                                l {\n                                    kind: \"variable\",\n                                    context: \"def update(§l§, v)\",\n                                    referenced in (1): [\n                                        `§l§.call v`,\n                                    ],\n                                },\n                                v {\n                                    kind: \"variable\",\n                                    context: \"def update(l, §v§)\",\n                                    referenced in (1): [\n                                        `l.call §v§`,\n                                    ],\n                                },\n                            ],\n                            child scopes: [],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    // tests the following constructs:\n    //\n    // - until\n    // - case-when-else\n    // - interpolation\n    // - binary ops\n    // - operator-assignment\n    #[test]\n    fn control_flow() {\n        test_scopes(\n            \"Ruby\",\n            r#\"\n            counter = 1\n            until counter > 10\n                case \n                    when (counter % 3 == 0) && (counter % 5 == 0)\n                        both_3_and_5 = true\n                        puts \" #{counter} is divisible by both 3 and 5!\"\n                    when counter % 3 == 0\n                        only_3 = true\n                        puts \" #{counter} is divisible by 3!\"\n                    when counter % 5 == 0\n                        only_5 = true\n                        puts \" #{counter} is divisible by 5!\"\n                    else\n                        neither = true\n                        puts \" #{counter} is not divisible by 3 or 5!\"\n                end\n\n                counter +=1\n            end\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        counter {\n                            kind: \"variable\",\n                            context: \"§counter§ = 1\",\n                            referenced in (10): [\n                                `until §counter§ > 10`,\n                                `when (§counter§ % 3 == 0) && (counter % 5 == 0)`,\n                                `when (counter % 3 == 0) && (§counter§ % 5 == 0)`,\n                                `puts \" #{§counter§} is divisible by both 3 and 5!\"`,\n                                `when §counter§ % 3 == 0`,\n                                `puts \" #{§counter§} is divisible by 3!\"`,\n                                `when §counter§ % 5 == 0`,\n                                `puts \" #{§counter§} is divisible by 5!\"`,\n                                `puts \" #{§counter§} is not divisible by 3 or 5!\"`,\n                                `§counter§ +=1`,\n                            ],\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [\n                                                scope {\n                                                    definitions: [\n                                                        both_3_and_5 {\n                                                            kind: \"variable\",\n                                                            context: \"§both_3_and_5§ = true\",\n                                                        },\n                                                    ],\n                                                    child scopes: [],\n                                                },\n                                            ],\n                                        },\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [\n                                                scope {\n                                                    definitions: [\n                                                        only_3 {\n                                                            kind: \"variable\",\n                                                            context: \"§only_3§ = true\",\n                                                        },\n                                                    ],\n                                                    child scopes: [],\n                                                },\n                                            ],\n                                        },\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [\n                                                scope {\n                                                    definitions: [\n                                                        only_5 {\n                                                            kind: \"variable\",\n                                                            context: \"§only_5§ = true\",\n                                                        },\n                                                    ],\n                                                    child scopes: [],\n                                                },\n                                            ],\n                                        },\n                                        scope {\n                                            definitions: [\n                                                neither {\n                                                    kind: \"variable\",\n                                                    context: \"§neither§ = true\",\n                                                },\n                                            ],\n                                            child scopes: [],\n                                        },\n                                    ],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    // tests global decls\n    #[test]\n    fn globals() {\n        test_scopes(\n            \"Ruby\",\n            r#\"\n            def foo()\n                $var = 2\n            end\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        foo {\n                            kind: \"method\",\n                            context: \"def §foo§()\",\n                        },\n                        $var {\n                            kind: \"constant\",\n                            context: \"§$var§ = 2\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/ruby/scopes.scm",
    "content": ";; scopes\n[\n (block)\n (do_block)\n (rescue)\n (when)\n (unless)\n (until)\n (begin)\n (case)\n (case_match)\n (in_clause)\n (then)\n (else)\n (method)\n (singleton_method)\n (class)\n (module)\n (lambda)\n ] @local.scope\n\n;; defs\n\n;; var = _\n(assignment \n  left: \n    [(identifier)\n     (class_variable)\n     (instance_variable)] @local.definition.variable)\n;; Var = _\n(assignment \n  left: (constant) @local.definition.constant)\n;; $var = _\n(assignment \n  left: (global_variable) @global.definition.constant)\n;; x, y =\n(left_assignment_list\n  [(identifier)\n   (class_variable)\n   (instance_variable)] @local.definition.variable)\n;; x, y =\n(left_assignment_list\n  (constant) @local.definition.constant)\n\n;; do block params\n(block_parameters\n  (identifier) @local.definition.variable)\n\n;; lambda params\n(lambda_parameters\n  (identifier) @local.definition.variable)\n\n;; Exception => variable\n(exception_variable\n  (identifier) @local.definition.variable)\n\n;; method def\n(method\n  (identifier) @hoist.definition.method)\n\n;; params\n(method_parameters\n  (identifier) @local.definition.variable)\n\n;; def foo(&block)\n(block_parameter\n  (identifier) @local.definition.variable)\n\n;; class def\n(class\n  (constant) @hoist.definition.class)\n\n;; def foo(*list)\n(splat_parameter\n  (identifier) @local.definition.variable)\n\n;; def foo(**hash)\n(hash_splat_parameter\n  (identifier) @local.definition.variable)\n\n;; def foo(arg = 0)\n(optional_parameter\n  (identifier) @local.definition.variable)\n\n;; module P\n(module \n  (constant) @hoist.definition.module)\n\n;; alias new_method existing_methdo\n(alias \n  name: (identifier) @local.definition.method)\n\n;; patterns\n;; pat => bind\n(as_pattern\n  name: (identifier) @local.definition.variable)\n;; Integer, a, String\n(array_pattern\n  (identifier) @local.definition.variable)\n;; {user: u}\n(hash_pattern\n  (keyword_pattern\n    value: (identifier) @local.definition.variable))\n;; user: (only key)\n(hash_pattern\n  (keyword_pattern\n    key: (hash_key_symbol) @local.definition.variable\n    !value))\n;; a | b\n(alternative_pattern\n  (identifier) @local.definition.variable)\n;; a | b\n(variable_reference_pattern\n  (identifier) @local.definition.variable)\n\n\n;; refs\n\n;; a and b\n(binary \n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n\n;; a ? b : c\n(conditional\n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n\n;; a += b\n;;\n;; b is a ref\n(operator_assignment\n  (identifier) @local.reference)\n\n;; a..b\n(range\n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n\n;; ++a\n(unary\n  (identifier) @local.reference)\n\n;; [a, b, c]\n(array \n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n\n;; {key: v}\n(pair\n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n\n\n;; a = b \n;;\n;; b is a ref\n(assignment \n  right: \n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n(right_assignment_list\n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n\n;; a.prop\n;; a.method()\n(call\n  receiver:\n   [(identifier)\n    (constant)\n    (instance_variable)] @local.reference)\n\n;; foo()\n(call\n  method: (identifier) @local.reference\n  !receiver)\n\n;; method(a, b, c)\n(argument_list\n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n\n;; *arg\n(splat_argument\n  (identifier) @local.reference)\n\n;; \"#{var}\"\n(interpolation\n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n\n;; arr[0]\n(element_reference \n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n\n;; if _ .. elif _ .. else .. end\n(if \n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n(then\n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n(else \n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n\n;; expr if condition\n(if_modifier \n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n\n;; while _ do _ end\n(while \n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n(do\n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n\n;; expr while condition\n(while_modifier \n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n\n;; case a when b end\n(case\n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n\n;; begin .. rescue .. else .. ensure\n(begin\n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n(ensure \n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n\n;; unless .. end\n(unless \n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n\n;; b unless a\n(unless_modifier \n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n\n;; until .. end\n(until \n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n\n;; b until a\n(until_modifier \n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n\n;; (a)\n(parenthesized_statements\n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n\n;; statements\n(body_statement \n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n\n(block_body \n  [(identifier)\n   (constant)\n   (instance_variable)] @local.reference)\n\n;; class _ < A\n(superclass \n  (constant) @local.reference)\n\n;; alias new_method existing_methdo\n(alias \n  alias: (identifier) @local.reference)\n\n;; A::B\n(scope_resolution\n  scope: (constant) @local.reference)\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/rust/mod.rs",
    "content": "use crate::intelligence::{MemoizedQuery, TSLanguageConfig};\n\npub static RUST: TSLanguageConfig = TSLanguageConfig {\n    language_ids: &[\"Rust\"],\n    file_extensions: &[\"rs\"],\n    grammar: tree_sitter_rust::language,\n    scope_query: MemoizedQuery::new(include_str!(\"./scopes.scm\")),\n    hoverable_query: MemoizedQuery::new(\n        r#\"\n        [(identifier)\n         (shorthand_field_identifier)\n         (field_identifier)\n         (type_identifier)] @hoverable\n        \"#,\n    ),\n    namespaces: &[&[\n        // variables\n        \"const\",\n        \"function\",\n        \"variable\",\n        // types\n        \"struct\",\n        \"enum\",\n        \"union\",\n        \"typedef\",\n        \"interface\",\n        // fields\n        \"field\",\n        \"enumerator\",\n        // namespacing\n        \"module\",\n        // misc\n        \"label\",\n        \"lifetime\",\n    ]],\n};\n\n#[cfg(test)]\nmod tests {\n    use crate::intelligence::language::test_utils::*;\n\n    #[test]\n    fn declare_const_and_static() {\n        let src = r#\"\n            const a: () = ();\n            static b: () = ();\n        \"#;\n\n        let (_, def_count, _, _) = counts(src, \"Rust\");\n\n        // a, b\n        assert_eq!(def_count, 2);\n    }\n\n    #[test]\n    fn declare_let_statement() {\n        let src = r#\"\n            fn main() {\n                let a = ();\n                let (b, c) = ();\n                let S { d, e } = ();\n                let S { field: f, g } = ();\n                let S { h, .. } = ();\n                let S { i, field: _ } = ();\n            }\n        \"#;\n        let (_, def_count, _, _) = counts(src, \"Rust\");\n\n        // main, a, b, c, d, e, f, g, h, i\n        assert_eq!(def_count, 10);\n    }\n\n    #[test]\n    fn declare_function_params() {\n        let src = r#\"\n            fn f1(a: T) {}\n            fn f2(b: T, c: T) {}\n            fn f3((d, e): (T, U)) {}\n            fn f4(S {f, g}: S) {}\n            fn f5(S {h, ..}: S) {}\n            fn f6(S { field: i }: S) {}\n        \"#;\n        let (_, def_count, _, _) = counts(src, \"Rust\");\n\n        // f1, f2, f3, f4, f5, f6, a, b, c, d, e, f, g, h, i\n        assert_eq!(def_count, 15);\n    }\n\n    #[test]\n    fn declare_closure_params() {\n        let src = r#\"\n            fn main() {\n                let _ = |x| {};\n                let _ = |x, y| {};\n                let _ = |x: ()| {};\n                let _ = |(x, y): ()| {};\n            }\n        \"#;\n        let (_, def_count, _, _) = counts(src, \"Rust\");\n\n        // main,\n        // x,\n        // x, y,\n        // x,\n        // x, y\n        assert_eq!(def_count, 7);\n    }\n\n    #[test]\n    fn declare_labels() {\n        let src = r#\"\n            fn main() {\n                'loop: loop {};\n                'loop: for _ in () {}\n                'loop: while true {}\n            }\n        \"#;\n        let (_, def_count, _, _) = counts(src, \"Rust\");\n\n        // main, 'loop x3\n        assert_eq!(def_count, 4);\n    }\n\n    #[test]\n    fn declare_types() {\n        let src = r#\"\n            struct One {\n                two: T,\n                three: T,\n            }\n\n            enum Four {\n                Five,\n                Six(T),\n                Seven {\n                    eight: T\n                }\n            }\n\n            union Nine {}\n\n            type Ten = ();\n        \"#;\n        let (_, def_count, _, _) = counts(src, \"Rust\");\n\n        assert_eq!(def_count, 10);\n    }\n\n    #[test]\n    fn declare_namespaces() {\n        let src = r#\"\n            mod one {}\n            pub mod two {}\n            mod three {\n                mod four {}\n            }\n        \"#;\n        let (_, def_count, _, _) = counts(src, \"Rust\");\n\n        assert_eq!(def_count, 4);\n    }\n\n    #[test]\n    fn declare_let_expr() {\n        let src = r#\"\n            if let a = () {}\n            if let Some(a) = () {}\n\n            while let a = () {}\n            while let Some(a) = () {}\n        \"#;\n        let (_, def_count, _, _) = counts(src, \"Rust\");\n\n        assert_eq!(def_count, 4);\n    }\n\n    #[test]\n    fn refer_unary_expr() {\n        let src = r#\"\n            fn main() {\n                let a = 2;\n                !a;\n                -a;\n                *a;\n            }\n        \"#;\n        let (_, _, ref_count, _) = counts(src, \"Rust\");\n\n        assert_eq!(ref_count, 3);\n    }\n\n    #[test]\n    fn refer_binary_expr() {\n        let src = r#\"\n            fn main() {\n                let a = 2;\n                let b = 3;\n                a + b;\n                a >> b;\n            }\n        \"#;\n        let (_, _, ref_count, _) = counts(src, \"Rust\");\n\n        assert_eq!(ref_count, 4);\n    }\n\n    #[test]\n    fn refer_control_flow() {\n        let src = r#\"\n            fn main() {\n                let a = 2;\n\n                // 1\n                if a {}\n\n                // 2\n                if _ {} else if a {}\n\n                // 3\n                while a {\n                    // 4\n                    break a;\n                }\n\n                // 5\n                a?;\n\n                // 6\n                return a;\n\n                // 7\n                a.await;\n\n                // 8\n                yield a;\n            }\n        \"#;\n        let (_, _, ref_count, _) = counts(src, \"Rust\");\n\n        assert_eq!(ref_count, 8);\n    }\n\n    #[test]\n    fn refer_assignment() {\n        let src = r#\"\n            fn main() {\n                let mut a = 2;\n                a += 2;\n                a = 2;\n                a *= 2;\n            }\n        \"#;\n        let (_, _, ref_count, _) = counts(src, \"Rust\");\n\n        assert_eq!(ref_count, 3);\n    }\n\n    #[test]\n    fn refer_struct_expr() {\n        let src = r#\"\n            fn main() {\n                let a = 2;\n                let b = 2;\n                S { a, b };\n                S { ..a };\n                S { field: a, b };\n            }\n        \"#;\n        let (_, _, ref_count, _) = counts(src, \"Rust\");\n\n        assert_eq!(ref_count, 5);\n    }\n\n    #[test]\n    fn refer_dot() {\n        let src = r#\"\n            fn main() {\n                let a = S {};\n\n                a.b;\n                a.foo();\n            }\n        \"#;\n        let (_, _, ref_count, _) = counts(src, \"Rust\");\n\n        assert_eq!(ref_count, 2);\n    }\n\n    #[test]\n    fn refer_arguments() {\n        let src = r#\"\n            fn main() {\n                let a = 2;\n                let b = 3;\n                foo(a, b);\n            }\n        \"#;\n        let (_, _, ref_count, _) = counts(src, \"Rust\");\n\n        assert_eq!(ref_count, 2);\n    }\n\n    #[test]\n    fn symbols() {\n        let src = r#\"\n            fn one() {\n                let two = 1;\n                let (three, four) = (2, 3);\n                let T { field: five} = t;\n                let _ = |six| {};\n                const seven: () = ();\n                static eight: () = ();\n            }\n\n            struct Nine {\n                ten: (),\n            }\n\n            union Eleven {}\n            enum Twelve {\n                Thirteen,\n                Fourteen(T)\n            }\n            \"#;\n        assert_eq_defs(\n            src.as_bytes(),\n            \"Rust\",\n            vec![\n                (\"one\", \"function\"),\n                (\"two\", \"variable\"),\n                (\"three\", \"variable\"),\n                (\"four\", \"variable\"),\n                (\"five\", \"variable\"),\n                (\"six\", \"variable\"),\n                (\"seven\", \"const\"),\n                (\"eight\", \"const\"),\n                (\"Nine\", \"struct\"),\n                (\"ten\", \"field\"),\n                (\"Eleven\", \"union\"),\n                (\"Twelve\", \"enum\"),\n                (\"Thirteen\", \"enumerator\"),\n                (\"Fourteen\", \"enumerator\"),\n            ],\n        );\n    }\n\n    #[test]\n    fn function_params() {\n        test_scopes(\n            \"Rust\",\n            r#\"\n            fn foo(t: T, u: U) -> R {}\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        foo {\n                            kind: \"function\",\n                            context: \"fn §foo§(t: T, u: U) -> R {}\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                t {\n                                    kind: \"variable\",\n                                    context: \"fn foo(§t§: T, u: U) -> R {}\",\n                                },\n                                u {\n                                    kind: \"variable\",\n                                    context: \"fn foo(t: T, §u§: U) -> R {}\",\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        );\n    }\n\n    #[test]\n    fn use_statements() {\n        test_scopes(\n            \"Rust\",\n            r#\"\n            mod intelligence;\n\n            use bleep;\n            use super::test_utils;\n            use intelligence::language as lang;\n            use crate::text_range::{TextRange, Point};\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        intelligence {\n                            kind: \"module\",\n                            context: \"mod §intelligence§;\",\n                            referenced in (1): [\n                                `use §intelligence§::language as lang;`,\n                            ],\n                        },\n                    ],\n                    imports: [\n                        bleep {\n                            context: \"use §bleep§;\",\n                        },\n                        test_utils {\n                            context: \"use super::§test_utils§;\",\n                        },\n                        lang {\n                            context: \"use intelligence::language as §lang§;\",\n                        },\n                        TextRange {\n                            context: \"use crate::text_range::{§TextRange§, Point};\",\n                        },\n                        Point {\n                            context: \"use crate::text_range::{TextRange, §Point§};\",\n                        },\n                    ],\n                    child scopes: [],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn lifetimes() {\n        test_scopes(\n            \"Rust\",\n            r#\"\n            impl<'a, T> Trait for Struct<'a, T> {\n                fn foo<'b>(&'a self) -> &'b T { }\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                'a {\n                                    kind: \"lifetime\",\n                                    context: \"impl<§'a§, T> Trait for Struct<'a, T> {\",\n                                    referenced in (2): [\n                                        `impl<'a, T> Trait for Struct<§'a§, T> {`,\n                                        `fn foo<'b>(&§'a§ self) -> &'b T { }`,\n                                    ],\n                                },\n                                T {\n                                    kind: \"typedef\",\n                                    context: \"impl<'a, §T§> Trait for Struct<'a, T> {\",\n                                    referenced in (2): [\n                                        `impl<'a, T> Trait for Struct<'a, §T§> {`,\n                                        `fn foo<'b>(&'a self) -> &'b §T§ { }`,\n                                    ],\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        foo {\n                                            kind: \"function\",\n                                            context: \"fn §foo§<'b>(&'a self) -> &'b T { }\",\n                                        },\n                                    ],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [\n                                                'b {\n                                                    kind: \"lifetime\",\n                                                    context: \"fn foo<§'b§>(&'a self) -> &'b T { }\",\n                                                    referenced in (1): [\n                                                        `fn foo<'b>(&'a self) -> &§'b§ T { }`,\n                                                    ],\n                                                },\n                                                self {\n                                                    kind: \"variable\",\n                                                    context: \"fn foo<'b>(&'a §self§) -> &'b T { }\",\n                                                },\n                                            ],\n                                            child scopes: [\n                                                scope {\n                                                    definitions: [],\n                                                    child scopes: [],\n                                                },\n                                            ],\n                                        },\n                                    ],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn generics_and_traits() {\n        test_scopes(\n            \"Rust\",\n            r#\"\n            trait Foo {}\n\n            fn foo<'a, 'b, T, U: Foo<T> + 'a>(t: T, u: U) \n              where T: Foo + 'b,\n            { }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        Foo {\n                            kind: \"interface\",\n                            context: \"trait §Foo§ {}\",\n                            referenced in (2): [\n                                `fn foo<'a, 'b, T, U: §Foo§<T> + 'a>(t: T, u: U)`,\n                                `where T: §Foo§ + 'b,`,\n                            ],\n                        },\n                        foo {\n                            kind: \"function\",\n                            context: \"fn §foo§<'a, 'b, T, U: Foo<T> + 'a>(t: T, u: U)\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                        scope {\n                            definitions: [\n                                'a {\n                                    kind: \"lifetime\",\n                                    context: \"fn foo<§'a§, 'b, T, U: Foo<T> + 'a>(t: T, u: U)\",\n                                    referenced in (1): [\n                                        `fn foo<'a, 'b, T, U: Foo<T> + §'a§>(t: T, u: U)`,\n                                    ],\n                                },\n                                'b {\n                                    kind: \"lifetime\",\n                                    context: \"fn foo<'a, §'b§, T, U: Foo<T> + 'a>(t: T, u: U)\",\n                                    referenced in (1): [\n                                        `where T: Foo + §'b§,`,\n                                    ],\n                                },\n                                T {\n                                    kind: \"typedef\",\n                                    context: \"fn foo<'a, 'b, §T§, U: Foo<T> + 'a>(t: T, u: U)\",\n                                    referenced in (3): [\n                                        `fn foo<'a, 'b, T, U: Foo<§T§> + 'a>(t: T, u: U)`,\n                                        `fn foo<'a, 'b, T, U: Foo<T> + 'a>(t: §T§, u: U)`,\n                                        `where §T§: Foo + 'b,`,\n                                    ],\n                                },\n                                U {\n                                    kind: \"typedef\",\n                                    context: \"fn foo<'a, 'b, T, §U§: Foo<T> + 'a>(t: T, u: U)\",\n                                    referenced in (1): [\n                                        `fn foo<'a, 'b, T, U: Foo<T> + 'a>(t: T, u: §U§)`,\n                                    ],\n                                },\n                                t {\n                                    kind: \"variable\",\n                                    context: \"fn foo<'a, 'b, T, U: Foo<T> + 'a>(§t§: T, u: U)\",\n                                },\n                                u {\n                                    kind: \"variable\",\n                                    context: \"fn foo<'a, 'b, T, U: Foo<T> + 'a>(t: T, §u§: U)\",\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn type_constructors() {\n        test_scopes(\n            \"Rust\",\n            r#\"\n            struct Highlight {}\n\n            enum Direction { Incoming, Outgoing }\n\n            fn foo() -> Highlight {\n                Highlight { }\n            }\n\n            fn bar() -> Direction {\n                Direction::Incoming\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        Highlight {\n                            kind: \"struct\",\n                            context: \"struct §Highlight§ {}\",\n                            referenced in (2): [\n                                `fn foo() -> §Highlight§ {`,\n                                `§Highlight§ { }`,\n                            ],\n                        },\n                        Direction {\n                            kind: \"enum\",\n                            context: \"enum §Direction§ { Incoming, Outgoing }\",\n                            referenced in (2): [\n                                `fn bar() -> §Direction§ {`,\n                                `§Direction§::Incoming`,\n                            ],\n                        },\n                        foo {\n                            kind: \"function\",\n                            context: \"fn §foo§() -> Highlight {\",\n                        },\n                        bar {\n                            kind: \"function\",\n                            context: \"fn §bar§() -> Direction {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                        scope {\n                            definitions: [\n                                Incoming {\n                                    kind: \"enumerator\",\n                                    context: \"enum Direction { §Incoming§, Outgoing }\",\n                                },\n                                Outgoing {\n                                    kind: \"enumerator\",\n                                    context: \"enum Direction { Incoming, §Outgoing§ }\",\n                                },\n                            ],\n                            child scopes: [],\n                        },\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn macros() {\n        test_scopes(\n            \"Rust\",\n            r#\"\n            fn main() {\n                let (a, b, c) = ();\n                // top-level tokens\n                assert_eq!(a, b + c);\n\n                // nested tokens\n                println!(\"{}\", if a { b } then { c });\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        main {\n                            kind: \"function\",\n                            context: \"fn §main§() {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        a {\n                                            kind: \"variable\",\n                                            context: \"let (§a§, b, c) = ();\",\n                                            referenced in (2): [\n                                                `assert_eq!(§a§, b + c);`,\n                                                `println!(\"{}\", if §a§ { b } then { c });`,\n                                            ],\n                                        },\n                                        b {\n                                            kind: \"variable\",\n                                            context: \"let (a, §b§, c) = ();\",\n                                            referenced in (2): [\n                                                `assert_eq!(a, §b§ + c);`,\n                                                `println!(\"{}\", if a { §b§ } then { c });`,\n                                            ],\n                                        },\n                                        c {\n                                            kind: \"variable\",\n                                            context: \"let (a, b, §c§) = ();\",\n                                            referenced in (2): [\n                                                `assert_eq!(a, b + §c§);`,\n                                                `println!(\"{}\", if a { b } then { §c§ });`,\n                                            ],\n                                        },\n                                    ],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    // Self::method and self.method can be raised as references\n    #[test]\n    fn handle_self_type_and_var() {\n        test_scopes(\n            \"Rust\",\n            r#\"\n            struct MyStruct {}\n\n            impl MyStruct {\n                fn foo() {\n                    Self::foo()\n                }\n\n                fn bar(&self) {\n                    self.bar()\n                }\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        MyStruct {\n                            kind: \"struct\",\n                            context: \"struct §MyStruct§ {}\",\n                            referenced in (1): [\n                                `impl §MyStruct§ {`,\n                            ],\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        foo {\n                                            kind: \"function\",\n                                            context: \"fn §foo§() {\",\n                                            referenced in (1): [\n                                                `Self::§foo§()`,\n                                            ],\n                                        },\n                                        bar {\n                                            kind: \"function\",\n                                            context: \"fn §bar§(&self) {\",\n                                            referenced in (1): [\n                                                `self.§bar§()`,\n                                            ],\n                                        },\n                                    ],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [\n                                                scope {\n                                                    definitions: [],\n                                                    child scopes: [],\n                                                },\n                                            ],\n                                        },\n                                        scope {\n                                            definitions: [\n                                                self {\n                                                    kind: \"variable\",\n                                                    context: \"fn bar(&§self§) {\",\n                                                },\n                                            ],\n                                            child scopes: [\n                                                scope {\n                                                    definitions: [],\n                                                    child scopes: [],\n                                                },\n                                            ],\n                                        },\n                                    ],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn let_else_1_65_support() {\n        test_scopes(\n            \"Rust\",\n            r#\"\n            fn main() {\n                let a = 3;\n                if let b = a \n                && let c = b \n                && let d = c {\n                    d\n                } else {\n                    return;\n                }\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        main {\n                            kind: \"function\",\n                            context: \"fn §main§() {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        a {\n                                            kind: \"variable\",\n                                            context: \"let §a§ = 3;\",\n                                            referenced in (1): [\n                                                `if let b = §a§`,\n                                            ],\n                                        },\n                                    ],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [\n                                                b {\n                                                    kind: \"variable\",\n                                                    context: \"if let §b§ = a\",\n                                                    referenced in (1): [\n                                                        `&& let c = §b§`,\n                                                    ],\n                                                },\n                                                c {\n                                                    kind: \"variable\",\n                                                    context: \"&& let §c§ = b\",\n                                                    referenced in (1): [\n                                                        `&& let d = §c§ {`,\n                                                    ],\n                                                },\n                                                d {\n                                                    kind: \"variable\",\n                                                    context: \"&& let §d§ = c {\",\n                                                    referenced in (1): [\n                                                        `§d§`,\n                                                    ],\n                                                },\n                                            ],\n                                            child scopes: [\n                                                scope {\n                                                    definitions: [],\n                                                    child scopes: [],\n                                                },\n                                                scope {\n                                                    definitions: [],\n                                                    child scopes: [],\n                                                },\n                                            ],\n                                        },\n                                    ],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn value_of_function_definition() {\n        let src = r#\"fn main() {   // 0\n                        let a = 2; // 1\n                        let b = 2; // 2\n                        let c = 2; // 3\n                     }             // 4\"#;\n\n        let sg = build_graph(\"Rust\", src.as_bytes());\n        let main_function = sg.find_node_by_name(src.as_bytes(), b\"main\").unwrap();\n        let function_node = &sg.graph[sg.value_of_definition(main_function).unwrap()];\n\n        assert_eq!(function_node.range().start.line, 0);\n        assert_eq!(function_node.range().end.line, 4);\n    }\n\n    #[test]\n    fn value_of_function_with_generics() {\n        let src = r#\"fn main<P, Q, R, S, T>(p: P, q: Q) { // 0\n                        let a = 2;                        // 1\n                        let b = 2;                        // 2\n                        let c = 2;                        // 3\n                        let d = 2;                        // 4\n                     }                                    // 5\"#;\n\n        let sg = build_graph(\"Rust\", src.as_bytes());\n        let main_function = sg.find_node_by_name(src.as_bytes(), b\"main\").unwrap();\n        let function_node = &sg.graph[sg.value_of_definition(main_function).unwrap()];\n\n        assert_eq!(function_node.range().start.line, 0);\n        assert_eq!(function_node.range().end.line, 5);\n    }\n\n    #[test]\n    fn value_of_struct_definition() {\n        let src = r#\"struct P { // 0\n                        s: Y,   // 1\n                        c: H,   // 2\n                    }           // 3\"#;\n\n        let sg = build_graph(\"Rust\", src.as_bytes());\n        let struct_p = sg.find_node_by_name(src.as_bytes(), b\"P\").unwrap();\n        let struct_node = &sg.graph[sg.value_of_definition(struct_p).unwrap()];\n\n        assert_eq!(struct_node.range().start.line, 0);\n        assert_eq!(struct_node.range().end.line, 3);\n    }\n\n    #[test]\n    fn value_of_let_definition() {\n        let src = r#\"fn main() {\n                        let a = 2;\n                        let b = 2;\n                    }\"#;\n\n        let sg = build_graph(\"Rust\", src.as_bytes());\n        let let_def_a = sg.find_node_by_name(src.as_bytes(), b\"a\").unwrap();\n\n        // no range produced for variable definitions\n        assert!(sg.value_of_definition(let_def_a).is_none());\n    }\n\n    #[test]\n    fn value_of_let_def_closures() {\n        let src = r#\"fn main() {       // 0\n                        let a = |x| {  // 1\n                            foo_bar(); // 2\n                        };             // 3\n                    }                  // 4\"#;\n\n        let sg = build_graph(\"Rust\", src.as_bytes());\n        let let_def_a = sg.find_node_by_name(src.as_bytes(), b\"a\").unwrap();\n        let let_def_node = &sg.graph[sg.value_of_definition(let_def_a).unwrap()];\n\n        assert_eq!(let_def_node.range().start.line, 1);\n        assert_eq!(let_def_node.range().end.line, 3);\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/rust/scopes.scm",
    "content": ";; see tree-sitter-rust/src/grammar.json for an exhaustive list of productions\n\n;; scopes\n(block) @local.scope              ; { ... }\n(function_item) @local.scope\n(declaration_list) @local.scope   ; mod { ... }\n\n;; impl items can define types and lifetimes:\n;;\n;;    impl<'a, T> Trait for Struct { .. }\n;;\n;; in order to constrain those to the impl block,\n;; we add a local scope here:\n(impl_item) @local.scope\n(struct_item) @local.scope\n(enum_item) @local.scope\n(union_item) @local.scope\n(type_item) @local.scope\n(trait_item) @local.scope\n\n;; let expressions create scopes\n(if_expression\n  [(let_condition)\n   (let_chain)]) @local.scope\n\n;; each match arm can bind variables with\n;; patterns, without creating a block scope;\n;;\n;;     match _ {\n;;        (a, b) => a,\n;;     }\n;;\n;; The bindings for a, b are constrained to\n;; the match arm.\n(match_arm) @local.scope\n\n;; loop labels are defs that are available only\n;; within the scope they create:\n;;\n;;     'outer: loop {\n;;         let x = 2;\n;;     };\n;;     let y = 2;\n;; \n;; Produces a scope graph like so:\n;;\n;; { \n;;   defs: [ y ],\n;;   scopes: [\n;;     {\n;;       defs: [ 'outer ],\n;;       scopes: [\n;;         {\n;;           defs: [ x ]\n;;         }\n;;       ]\n;;     }\n;;   ]\n;; }\n;;\n(loop_expression) @local.scope\n(for_expression) @local.scope\n(while_expression) @local.scope\n\n\n;; defs\n\n;; let x = ...;\n(let_declaration \n  pattern: (identifier) @local.definition.variable)\n\n;; if let x = ...;\n;; while let x = ...;\n(let_condition\n  .\n  (identifier) @local.definition.variable)\n\n;; let (a, b, ...) = ..;\n;; if let (a, b, ...) = {}\n;; while let (a, b, ...) = {}\n;; match _ { (a, b) => { .. } }\n(tuple_pattern (identifier) @local.definition.variable)\n\n;; Some(a)\n(tuple_struct_pattern\n  type: (_)\n  (identifier) @local.definition.variable)\n\n;; let S { field: a } = ..;\n(struct_pattern \n  (field_pattern \n    (identifier) @local.definition.variable))\n\n;; let S { a, b } = ..;\n(struct_pattern \n  (field_pattern \n    (shorthand_field_identifier) @local.definition.variable))\n\n;; (mut x: T)\n(mut_pattern (identifier) @local.definition.variable)\n\n;; (ref x: T)\n(ref_pattern (identifier) @local.definition.variable)\n\n;; const x = ...;\n(const_item (identifier) @local.definition.const)\n\n;; static x = ...;\n(static_item (identifier) @local.definition.const)\n\n;; fn _(x: _)\n(parameters \n  (parameter \n    pattern: (identifier) @local.definition.variable))\n;; fn _(self)\n(parameters \n  (self_parameter\n    (self) @local.definition.variable))\n\n;; type parameters\n(type_parameters\n  (type_identifier) @local.definition.typedef)\n(type_parameters\n  (lifetime) @local.definition.lifetime)\n(constrained_type_parameter\n  left: (type_identifier) @local.definition.typedef)\n\n;; |x| { ... }\n;; no type\n(closure_parameters (identifier) @local.definition.variable)\n\n;; |x: T| { ... }\n;; with type\n(closure_parameters\n  (parameter\n    (identifier) @local.definition.variable))\n\n;;fn x(..)\n(function_item (identifier) @hoist.definition.function)\n\n;; 'outer: loop { .. }\n(loop_expression\n  (loop_label) @local.definition.label)\n\n;; `for` exprs create two defs: a label (if any) and the\n;; loop variable \n(for_expression . (identifier) @local.definition.variable)\n(for_expression (loop_label) @local.definition.label)\n\n;; 'label: while cond { .. }\n(while_expression\n  (loop_label) @local.definition.label)\n\n;; type definitions\n(struct_item (type_identifier) @hoist.definition.struct)\n(enum_item (type_identifier) @hoist.definition.enum)\n(union_item (type_identifier) @hoist.definition.union)\n(type_item . (type_identifier) @hoist.definition.typedef)\n(trait_item (type_identifier) @hoist.definition.interface)\n\n;; struct and union fields\n(field_declaration_list\n  (field_declaration \n    (field_identifier) @local.definition.field))\n\n;; enum variants\n(enum_variant_list\n  (enum_variant \n    (identifier) @local.definition.enumerator))\n\n;; mod x;\n(mod_item (identifier) @local.definition.module)\n\n;; use statements\n\n;; use item;\n(use_declaration\n  (identifier) @local.import)\n\n;; use path as item;\n(use_as_clause\n  alias: (identifier) @local.import)\n\n;; use path::item;\n(use_declaration \n  (scoped_identifier \n    name: (identifier) @local.import))\n\n;; use module::{member1, member2, member3};\n(use_list\n  (identifier) @local.import)\n(use_list \n  (scoped_identifier\n    name: (identifier) @local.import))\n\n\n;; refs\n\n;; !x\n(unary_expression (identifier) @local.reference)\n\n;; &x\n(reference_expression (identifier) @local.reference)\n\n;; (x)\n(parenthesized_expression (identifier) @local.reference)\n\n;; x?\n(try_expression (identifier) @local.reference)\n\n;; a = b\n(assignment_expression (identifier) @local.reference)\n\n;; a op b\n(binary_expression (identifier) @local.reference)\n\n;; a op= b\n(compound_assignment_expr (identifier) @local.reference)\n\n;; a as b\n(type_cast_expression (identifier) @local.reference)\n\n;; a()\n(call_expression (identifier) @local.reference)\n\n;; Self::foo()\n;;\n;; `foo` can be resolved\n(call_expression\n  (scoped_identifier\n    (identifier) @_self_type\n    (identifier) @local.reference)\n  (#match? @_self_type \"Self\"))\n\n;; self.foo() \n;;\n;; `foo` can be resolved\n(call_expression \n  (field_expression\n    (self)\n    (field_identifier) @local.reference))\n\n;; return a\n(return_expression (identifier) @local.reference)\n\n;; break a\n(break_expression (identifier) @local.reference)\n\n;; break 'label\n(break_expression (loop_label) @local.reference)\n\n;; continue 'label;\n(continue_expression (loop_label) @local.reference)\n\n;; yield x;\n(yield_expression (identifier) @local.reference)\n\n;; await a\n(await_expression (identifier) @local.reference)\n\n;; (a, b)\n(tuple_expression (identifier) @local.reference)\n\n;; a[]\n(index_expression (identifier) @local.reference)\n\n;; ident;\n(expression_statement (identifier) @local.reference)\n\n;; a..b\n(range_expression (identifier) @local.reference)\n\n;; [ident; N]\n(array_expression (identifier) @local.reference)\n\n;; path::to::item\n;;\n;; `path` is a ref\n(scoped_identifier \n  path: (identifier) @local.reference)\n\n;; rhs of let decls\n(let_declaration \n  value: (identifier) @local.reference)\n\n;; type T = [T; N]\n;;\n;; N is a ident ref\n(array_type\n  length: (identifier) @local.reference)\n\n;; S { _ }\n(struct_expression\n  (type_identifier) @local.reference)\n\n;; S { a }\n(struct_expression\n  (field_initializer_list\n    (shorthand_field_initializer\n      (identifier) @local.reference)))\n\n;; S { a: value }\n(struct_expression\n  (field_initializer_list\n    (field_initializer \n      (identifier) @local.reference)))\n\n;; S { ..a }\n(struct_expression\n  (field_initializer_list\n    (base_field_initializer \n      (identifier) @local.reference)))\n\n;; if a {}\n(if_expression (identifier) @local.reference)\n\n;; for pattern in value {}\n;;\n;; `value` is a ref\n(for_expression\n  value: (identifier) @local.reference)\n\n;; while a {}\n(while_expression (identifier) @local.reference)\n\n;; if let _ = a {}\n;;\n;; the ident following the `=` is a ref\n;; the ident preceding the `=` is a def\n;; while let _ = a {}\n(let_condition \n  \"=\"\n  (identifier) @local.reference)\n\n\n;; match a\n(match_expression (identifier) @local.reference)\n\n;; match _ {\n;;     pattern => a,\n;; }\n;;\n;; this `a` is somehow not any expression form\n(match_arm (identifier) @local.reference)\n\n;; a.b\n;;\n;; `b` is ignored\n(field_expression\n  (identifier) @local.reference)\n\n;; { stmt; foo }\n(block\n  (identifier) @local.reference)\n\n;; arguments to method calls or function calls\n(arguments\n  (identifier) @local.reference)\n\n;; impl S { .. }\n(impl_item (type_identifier) @local.reference)\n\n;; where T: ...\n(where_predicate\n  left: (type_identifier) @local.reference)\n\n;; trait bounds\n(trait_bounds\n  (type_identifier) @local.reference)\n(trait_bounds\n  (lifetime) @local.reference)\n\n;; idents in macros\n(token_tree\n  (identifier) @local.reference)\n\n;; types\n\n;; (T, U)\n(tuple_type\n  (type_identifier) @local.reference)\n\n;; &T\n(reference_type\n  (type_identifier) @local.reference)\n\n;; &'a T\n(reference_type\n  (lifetime) @local.reference)\n\n;; &'a self\n(self_parameter \n  (lifetime) @local.reference)\n\n;; *mut T\n;; *const T\n(pointer_type\n  (type_identifier) @local.reference)\n\n;; A<_>\n(generic_type\n  (type_identifier) @local.reference)\n\n;; _<V>\n(type_arguments\n  (type_identifier) @local.reference)\n(type_arguments\n  (lifetime) @local.reference)\n\n;; T<U = V>\n;;\n;; U is ignored\n;; V is a ref\n(type_binding\n  name: (_)\n  type: (type_identifier) @local.reference)\n\n;; [T]\n(array_type\n  (type_identifier) @local.reference)\n\n;; type T = U;\n;;\n;; T is a def\n;; U is a ref\n(type_item \n  name: (_)\n  type: (type_identifier) @local.reference)\n\n(function_item \n  return_type: (type_identifier) @local.reference)\n\n;; type refs in params\n;;\n;; fn _(_: T)\n(parameters \n  (parameter \n    type: (type_identifier) @local.reference))\n\n;; dyn T\n(dynamic_type\n  (type_identifier) @local.reference)\n\n;; <T>::call()\n(bracketed_type\n  (type_identifier) @local.reference)\n\n;; T as Trait\n(qualified_type\n  (type_identifier) @local.reference)\n\n;; module::T\n;;\n;; `module` is a def\n;; `T` is a ref\n(scoped_type_identifier\n  path: (identifier) @local.reference)\n\n;; struct _ { field: Type }\n;; `Type` is a ref\n (field_declaration\n   name: (_)\n   type: (type_identifier) @local.reference)\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/test_utils.rs",
    "content": "pub use expect_test::expect;\n\nuse std::collections::HashSet;\n\nuse crate::intelligence::{scope_resolution::NodeKind, Language, TreeSitterFile};\n\nuse expect_test::Expect;\n\n#[rustfmt::skip]\npub fn counts(src: &str, lang_id: &str) -> (usize, usize, usize, usize) {\n    let tsf = TreeSitterFile::try_build(src.as_bytes(), lang_id).unwrap();\n    let scope_graph = tsf.scope_graph().unwrap();\n    let nodes = scope_graph.graph.node_weights();\n    nodes.fold((0, 0, 0, 0), |(s, d, r, i), node| match node {\n        NodeKind::Scope(_) => (s + 1, d,     r    , i    ),\n        NodeKind::Def(_)   => (s,     d + 1, r    , i    ),\n        NodeKind::Ref(_)   => (s,     d,     r + 1, i    ),\n        NodeKind::Import(_)=> (s,     d,     r    , i + 1),\n    })\n}\n\npub fn assert_eq_defs(src: &[u8], lang_id: &str, defs: Vec<(&str, &str)>) {\n    let language = match Language::from_id(lang_id) {\n        Language::Supported(config) => config,\n        _ => panic!(\"testing unsupported language\"),\n    };\n    let namespaces = language.namespaces;\n\n    let tsf = TreeSitterFile::try_build(src, lang_id).unwrap();\n    let scope_graph = tsf.scope_graph().unwrap();\n\n    let expected_defs: HashSet<_> = defs.into_iter().collect();\n    let observed_defs: HashSet<(&str, &str)> = scope_graph\n        .graph\n        .node_weights()\n        .filter_map(|node| match node {\n            NodeKind::Def(def) if def.symbol_id.is_some() => {\n                let name = std::str::from_utf8(def.name(src)).unwrap();\n                let symbol = def.symbol_id.map(|sym_id| sym_id.name(namespaces)).unwrap();\n                Some((name, symbol))\n            }\n            _ => None,\n        })\n        .collect();\n\n    assert_eq!(expected_defs, observed_defs)\n}\n\npub fn test_scopes(lang_id: &str, src: &[u8], expected: Expect) {\n    let graph = build_graph(lang_id, src);\n    let language = match Language::from_id(lang_id) {\n        Language::Supported(config) => config,\n        _ => panic!(\"testing unsupported language\"),\n    };\n    let observed = graph.debug(src, language);\n    expected.assert_debug_eq(&observed)\n}\n\npub fn build_graph(lang_id: &str, src: &[u8]) -> crate::intelligence::ScopeGraph {\n    let tsf = TreeSitterFile::try_build(src, lang_id).unwrap();\n    tsf.scope_graph().unwrap()\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/typescript/mod.rs",
    "content": "use crate::intelligence::{MemoizedQuery, TSLanguageConfig};\n\npub static TYPESCRIPT: TSLanguageConfig = TSLanguageConfig {\n    language_ids: &[\"TypeScript\", \"TSX\"],\n    file_extensions: &[\"ts\", \"tsx\"],\n    grammar: tree_sitter_typescript::language_tsx,\n    scope_query: MemoizedQuery::new(include_str!(\"./scopes.scm\")),\n    hoverable_query: MemoizedQuery::new(\n        r#\"\n        [(identifier)\n         (property_identifier)\n         (shorthand_property_identifier)\n         (shorthand_property_identifier_pattern)\n         (statement_identifier)\n         (type_identifier)] @hoverable\n        \"#,\n    ),\n    namespaces: &[&[\n        //variables\n        \"constant\",\n        \"variable\",\n        \"property\",\n        \"parameter\",\n        // functions\n        \"function\",\n        \"method\",\n        \"generator\",\n        // types\n        \"alias\",\n        \"enum\",\n        \"enumerator\",\n        \"class\",\n        \"interface\",\n        // misc.\n        \"label\",\n    ]],\n};\n\n#[cfg(test)]\nmod test {\n    use crate::intelligence::language::test_utils::*;\n\n    // tests the following constructs:\n    // - imports (inherited from js)\n    // - type aliases\n    // - type constructs (union types, nested types, function types)\n    // - generics\n    // - object property (should create an empty scope)\n    #[test]\n    fn simple() {\n        test_scopes(\n            \"TypeScript\",\n            r#\"\n            import React, { createContext } from 'react';\n            import { ExtendedItemType, ItemType } \n                from '../components/ContextMenu/ContextMenuItem/Item';\n\n            type SearchHistoryType = {\n                text: string;\n                type: ItemType | ExtendedItemType;\n                icon?: React.ReactElement;\n            };\n\n            type ContextType = {\n                inputValue: string;\n                setInputValue: (v: string) => void;\n                searchHistory: SearchHistoryType[];\n                setSearchHistory: (s: SearchHistoryType[]) => void;\n            };\n\n            export const SearchContext = createContext<ContextType>({\n                inputValue: '',\n                setInputValue: (value) => {},\n                searchHistory: [],\n                setSearchHistory: (newHistory) => {},\n            });\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        SearchHistoryType {\n                            kind: \"alias\",\n                            context: \"type §SearchHistoryType§ = {\",\n                            referenced in (2): [\n                                `searchHistory: §SearchHistoryType§[];`,\n                                `setSearchHistory: (s: §SearchHistoryType§[]) => void;`,\n                            ],\n                        },\n                        ContextType {\n                            kind: \"alias\",\n                            context: \"type §ContextType§ = {\",\n                            referenced in (1): [\n                                `export const SearchContext = createContext<§ContextType§>({`,\n                            ],\n                        },\n                        SearchContext {\n                            kind: \"constant\",\n                            context: \"export const §SearchContext§ = createContext<ContextType>({\",\n                        },\n                    ],\n                    imports: [\n                        React {\n                            context: \"import §React§, { createContext } from 'react';\",\n                            referenced in (1): [\n                                `icon?: §React§.ReactElement;`,\n                            ],\n                        },\n                        createContext {\n                            context: \"import React, { §createContext§ } from 'react';\",\n                            referenced in (1): [\n                                `export const SearchContext = §createContext§<ContextType>({`,\n                            ],\n                        },\n                        ExtendedItemType {\n                            context: \"import { §ExtendedItemType§, ItemType }\",\n                            referenced in (1): [\n                                `type: ItemType | §ExtendedItemType§;`,\n                            ],\n                        },\n                        ItemType {\n                            context: \"import { ExtendedItemType, §ItemType§ }\",\n                            referenced in (1): [\n                                `type: §ItemType§ | ExtendedItemType;`,\n                            ],\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                        scope {\n                            definitions: [\n                                v {\n                                    kind: \"parameter\",\n                                    context: \"setInputValue: (§v§: string) => void;\",\n                                },\n                            ],\n                            child scopes: [],\n                        },\n                        scope {\n                            definitions: [],\n                            child scopes: [],\n                        },\n                        scope {\n                            definitions: [\n                                s {\n                                    kind: \"parameter\",\n                                    context: \"setSearchHistory: (§s§: SearchHistoryType[]) => void;\",\n                                },\n                            ],\n                            child scopes: [],\n                        },\n                        scope {\n                            definitions: [],\n                            child scopes: [\n                                scope {\n                                    definitions: [\n                                        value {\n                                            kind: \"parameter\",\n                                            context: \"setInputValue: (§value§) => {},\",\n                                        },\n                                    ],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [],\n                                        },\n                                    ],\n                                },\n                                scope {\n                                    definitions: [\n                                        newHistory {\n                                            kind: \"parameter\",\n                                            context: \"setSearchHistory: (§newHistory§) => {},\",\n                                        },\n                                    ],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [],\n                                        },\n                                    ],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn tsx() {\n        test_scopes(\n            \"TSX\",\n            br#\"\n            import React from 'react';\n            import ReactDOM from 'react-dom/client';\n            import App from './App';\n            import './index.css';\n\n            ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(\n                <React.StrictMode>\n                <App />\n                </React.StrictMode>,\n            );\n            \"#,\n            expect![[r#\"\n                scope {\n                    definitions: [],\n                    imports: [\n                        React {\n                            context: \"import §React§ from 'react';\",\n                            referenced in (2): [\n                                `<§React§.StrictMode>`,\n                                `</§React§.StrictMode>,`,\n                            ],\n                        },\n                        ReactDOM {\n                            context: \"import §ReactDOM§ from 'react-dom/client';\",\n                            referenced in (1): [\n                                `§ReactDOM§.createRoot(document.getElementById('root') as HTMLElement).render(`,\n                            ],\n                        },\n                        App {\n                            context: \"import §App§ from './App';\",\n                            referenced in (1): [\n                                `<§App§ />`,\n                            ],\n                        },\n                    ],\n                    child scopes: [],\n                }\n            \"#]],\n        )\n    }\n\n    // https://github.com/BloopAI/bloop/issues/213\n    //\n    // type parameters and function parameters should belong to a scope\n    // that is smaller that the function definition itself.\n    #[test]\n    fn function_and_type_params() {\n        test_scopes(\n            \"TypeScript\",\n            r#\"\n            function foo<T, U>(t: T, u: U) {}\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        foo {\n                            kind: \"function\",\n                            context: \"function §foo§<T, U>(t: T, u: U) {}\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                T {\n                                    kind: \"none\",\n                                    context: \"function foo<§T§, U>(t: T, u: U) {}\",\n                                    referenced in (1): [\n                                        `function foo<T, U>(t: §T§, u: U) {}`,\n                                    ],\n                                },\n                                U {\n                                    kind: \"none\",\n                                    context: \"function foo<T, §U§>(t: T, u: U) {}\",\n                                    referenced in (1): [\n                                        `function foo<T, U>(t: T, u: §U§) {}`,\n                                    ],\n                                },\n                                t {\n                                    kind: \"parameter\",\n                                    context: \"function foo<T, U>(§t§: T, u: U) {}\",\n                                },\n                                u {\n                                    kind: \"parameter\",\n                                    context: \"function foo<T, U>(t: T, §u§: U) {}\",\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        );\n    }\n\n    #[test]\n    fn optional_param_regression() {\n        test_scopes(\n            \"TypeScript\",\n            r#\"\n            function foo(a?: string, b: string) {\n                return (a, b)\n            }\n            \"#\n            .as_bytes(),\n            expect![[r#\"\n                scope {\n                    definitions: [\n                        foo {\n                            kind: \"function\",\n                            context: \"function §foo§(a?: string, b: string) {\",\n                        },\n                    ],\n                    child scopes: [\n                        scope {\n                            definitions: [\n                                a {\n                                    kind: \"parameter\",\n                                    context: \"function foo(§a§?: string, b: string) {\",\n                                    referenced in (1): [\n                                        `return (§a§, b)`,\n                                    ],\n                                },\n                                b {\n                                    kind: \"parameter\",\n                                    context: \"function foo(a?: string, §b§: string) {\",\n                                    referenced in (1): [\n                                        `return (a, §b§)`,\n                                    ],\n                                },\n                            ],\n                            child scopes: [\n                                scope {\n                                    definitions: [],\n                                    child scopes: [\n                                        scope {\n                                            definitions: [],\n                                            child scopes: [],\n                                        },\n                                    ],\n                                },\n                            ],\n                        },\n                    ],\n                }\n            \"#]],\n        );\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence/language/typescript/scopes.scm",
    "content": ";; typescript, javascript, and tsx inherit the same common\n;; grammar, and add onto it. this file contains additional\n;; queries for typescript types and ADTs, it also revises\n;; certain queries (classes, function parameters).\n\n;; scopes\n\n[\n  (statement_block)\n  (class_body)\n  (arrow_function)\n  (object)\n  ;; nameless functions create scopes, just like arrow functions\n  (function !name)\n  (function_declaration)\n  (generator_function_declaration)\n  (for_statement)\n  (for_in_statement)\n  (switch_case)\n  (catch_clause)\n  ;; assignments are permitted inside sequence exprs:\n  ;;\n  ;;     const a = 2;\n  ;;     throw f = 1, f, a;\n  ;; \n  ;; should produce:\n  ;;\n  ;;     {\n  ;;       defs: [ a ],\n  ;;       scopes [{\n  ;;          defs: [ f ],\n  ;;          refs: [ f, a ]\n  ;;       }],\n  ;;     }\n  (sequence_expression)\n\n  ;; type signatures in properties may contain parameter\n  ;; definitions, which can never have references. this\n  ;; scope \"seals\" off this definitions.\n  ;;\n  ;;     type S = {\n  ;;        getter: (f: string) => string;\n  ;;     }\n  ;;\n  ;; should produce one top-level definition: `S`. without\n  ;; sealing the property signature, it also produces `f`\n  ;; as a top-level definition.\n  (property_signature)\n] @local.scope\n\n\n\n;; defs\n\n;; tree-sitter-javascript has 5 \"declaration\" kinds:\n;;\n;; - class\n;; - function\n;; - generator function\n;; - lexical\n;; - variable\n\n;; function x()\n(function_declaration\n  (identifier) @hoist.definition.function)\n\n(generator_function_declaration\n  (identifier) @hoist.definition.generator)\n\n;; function params\n(formal_parameters\n  (required_parameter\n    (identifier) @local.definition.parameter))\n(formal_parameters\n  (optional_parameter\n    (identifier) @local.definition.parameter))\n\n;; patterns\n\n;; f(a, ...b)\n(rest_pattern\n  (identifier) @local.definition.variable)\n\n;; f(a, y = f)\n;;\n;; the lhs is a def, the rhs is a ref\n(assignment_pattern\n  (identifier) @local.definition.variable\n  (identifier) @local.reference)\n\n;; for ([a, b] in thing)\n;;\n;; `a` & `b` are defs\n(array_pattern\n  (identifier) @local.definition.variable)\n\n;; let {a, b} = obj;\n(object_pattern\n  (shorthand_property_identifier_pattern) @local.definition.variable)\n\n;; var x = _\n(variable_declaration\n  (variable_declarator . (identifier) @local.definition.variable))\n\n;; const x = _\n(lexical_declaration\n  \"const\"\n  (variable_declarator . (identifier) @local.definition.constant))\n\n;; let x = _\n(lexical_declaration\n  \"let\"\n  (variable_declarator . (identifier) @local.definition.variable))\n\n;; a = b\n(assignment_expression\n  .\n  (identifier) @local.definition.variable)\n\n;; method def\n;;\n;; TODO: support getters and setters here, blocked on:\n;; https://github.com/tree-sitter/tree-sitter/issues/1461\n(method_definition\n  (property_identifier) @local.definition.method)\n\n;; class\n(class_declaration\n  (type_identifier) @local.definition.class)\n\n;; arrow func\n(arrow_function\n  (identifier) @local.definition.variable)\n\n\n;; imports\n\n;; import defaultMember from \"module\";\n(import_statement\n  (import_clause (identifier) @local.import))\n\n;; import { member } from \"module\";\n;; import { member as alias } from \"module\";\n(import_statement\n  (import_clause\n    (named_imports\n      [(import_specifier !alias (identifier) @local.import)\n       (import_specifier alias: (identifier) @local.import)])))\n\n;; for (item in list)\n;;\n;; `item` is a def\n(for_in_statement \n  left: (identifier) @local.definition.variable)\n\n;; labels\n(labeled_statement\n  (statement_identifier) @local.definition.label)\n\n;; type T\n(type_alias_declaration\n  name:\n  (type_identifier) @local.definition.alias)\n\n;; type parameters in generic\n;; functions or interfaces\n(type_parameters\n  (type_parameter\n    (type_identifier) @local.definition))\n\n;; enum T\n(enum_declaration\n  (identifier) @local.definition.enum)\n\n;; enumerators\n;;\n;; enum Direction {\n;;     L           // property_identifier\n;;     D = \"Down\"  // enum_assignment\n;; }\n(enum_body\n  (property_identifier) @local.definition.enumerator)\n(enum_body\n  (enum_assignment\n    (property_identifier) @local.definition.enumerator))\n\n;; abstract class T\n(abstract_class_declaration\n  (type_identifier) @local.definition.class)\n\n;; class _ {\n;;    t: T\n;; }\n(public_field_definition\n  (property_identifier) @local.definition.property)\n\n;; class {\n;;    abstract f(T): U;\n;; }\n(abstract_method_signature\n  (property_identifier) @local.definition.property)\n\n;; interface T\n(interface_declaration\n  (type_identifier) @local.definition.interface)\n\n;; catch clauses\n(catch_clause\n  (identifier) @local.definition.variable)\n\n\n;; refs\n\n;; someVar;\n(expression_statement (identifier) @local.reference)\n\n;; { \"a\": value }\n(object\n  (pair\n    (identifier) @local.reference))\n\n;; y = {a, b}\n(object\n  (shorthand_property_identifier) @local.reference)\n\n\n;; [ a, b, c ]\n(array\n  (identifier) @local.reference)\n\n;; new Object()\n(new_expression\n  (identifier) @local.reference)\n\n;; return x;\n(return_statement \n  (identifier) @local.reference)\n\n;; yield t;\n(yield_expression\n  (identifier) @local.reference)\n\n;; call expression\n(call_expression\n  (identifier) @local.reference)\n\n;; call arguments\n(arguments\n  (identifier) @local.reference)\n\n;; type arguments\n(type_arguments\n  (type_identifier) @local.reference)\n\n;; index expression\n(subscript_expression\n  (identifier) @local.reference)\n\n;; member expression: a.b\n(member_expression\n  (identifier) @local.reference)\n\n;; nested identifier: <React.StrictMode>\n;;\n;; `React` is a ref\n;; `StrictMode` is ignored\n(nested_identifier\n  .\n  (identifier) @local.reference)\n\n;; await ident;\n(await_expression \n  (identifier) @local.reference)\n\n;; a + b\n(binary_expression\n  (identifier) @local.reference)\n\n;; -x\n(unary_expression\n  (identifier) @local.reference)\n\n;; x++\n(update_expression\n  (identifier) @local.reference)\n\n;; a += b\n(augmented_assignment_expression\n  (identifier) @local.reference)\n\n;; (a)\n(parenthesized_expression\n  (identifier) @local.reference)\n\n;; tuples\n(sequence_expression\n  (identifier) @local.reference)\n\n;; c? a : b\n(ternary_expression\n  (identifier) @local.reference)\n\n;; {...object}\n(spread_element\n  (identifier) @local.reference)\n\n;; exports are refs\n;;\n;; export { name, name };\n;; export { name as alias };\n(export_statement\n  (export_clause\n    (export_specifier name: (identifier) @local.reference)))\n\n;; export default ident;\n(export_statement\n  (identifier) @local.reference)\n\n;; for (item in list)\n;;\n;; `list` is a def\n(for_in_statement \n  right: (identifier) @local.reference)\n\n;; break f;\n(break_statement (statement_identifier) @local.reference)\n\n;; continue f;\n(continue_statement (statement_identifier) @local.reference)\n\n;; jsx\n;; (jsx_expression\n;;   (identifier) @local.reference)\n;; \n;; (jsx_opening_element\n;;   (identifier) @local.reference)\n;; \n;; (jsx_closing_element\n;;   (identifier) @local.reference)\n;; \n;; (jsx_self_closing_element\n;;   (identifier) @local.reference)\n\n\n;; type refs\n\n;; type _ = T\n(type_alias_declaration\n  value:\n  (type_identifier) @local.reference)\n\n;; (T)\n(parenthesized_type\n  (type_identifier) @local.reference)\n\n;; T[]\n(array_type\n  (type_identifier) @local.reference)\n\n;; A extends B ? C : D\n(conditional_type\n  (type_identifier) @local.reference)\n\n;; ?T\n(flow_maybe_type\n  (type_identifier) @local.reference)\n\n;; T<_>\n(generic_type\n  (type_identifier) @local.reference)\n\n;; T & U\n(intersection_type\n  (type_identifier) @local.reference)\n\n;; T | U\n(union_type\n  (type_identifier) @local.reference)\n\n;; (T, U) => V\n(function_type\n  (type_identifier) @local.reference)\n\n;; keyof T\n(index_type_query\n  (type_identifier) @local.reference)\n\n;; val as T\n(as_expression\n  (identifier) @local.reference\n  (type_identifier) @local.reference)\n\n;; let t: T = foo();\n;; {t: T, u: U}\n(type_annotation\n  (type_identifier) @local.reference)\n\n;; [T, U]\n(tuple_type\n  (type_identifier) @local.reference)\n\n;; T[U]\n(lookup_type\n  (type_identifier) @local.reference)\n\n;; T.U\n;;\n;; `T` is ref\n;; `U` is ignored\n(nested_type_identifier\n  .\n  (identifier) @local.reference)\n\n;; t is T\n(type_predicate_annotation\n  (type_predicate\n    (identifier) @local.reference\n    (type_identifier) @local.reference))\n\n;; jsx\n(jsx_expression\n  (identifier) @local.reference)\n\n(jsx_opening_element\n  (identifier) @local.reference)\n\n(jsx_closing_element\n  (identifier) @local.reference)\n\n(jsx_self_closing_element\n  (identifier) @local.reference)\n"
  },
  {
    "path": "server/bleep/src/intelligence/language.rs",
    "content": "mod c;\nmod c_sharp;\nmod cobol;\nmod cpp;\nmod go;\nmod java;\nmod javascript;\nmod php;\nmod python;\nmod r;\nmod ruby;\nmod rust;\nmod typescript;\n\n#[cfg(test)]\nmod test_utils;\n\nuse once_cell::sync::OnceCell;\n\nuse super::NameSpaces;\n\n/// A collection of all language definitions\npub static ALL_LANGUAGES: &[&TSLanguageConfig] = &[\n    &c::C,\n    &go::GO,\n    &javascript::JAVASCRIPT,\n    &python::PYTHON,\n    &rust::RUST,\n    &typescript::TYPESCRIPT,\n    &c_sharp::C_SHARP,\n    &java::JAVA,\n    &cpp::CPP,\n    &ruby::RUBY,\n    &r::R,\n    &php::PHP,\n    &cobol::COBOL,\n];\n\n/// A generic language wrapper type.\n///\n/// The backing grammars/parser are supplied through the `Config` type.\npub enum Language<Config: 'static> {\n    /// A supported language, with some `Config`.\n    Supported(&'static Config),\n\n    /// An unsupported language\n    Unsupported,\n}\n\n/// Languages based on tree-sitter grammars\n#[derive(Debug)]\npub struct TSLanguageConfig {\n    /// A list of language names that can be processed by these scope queries\n    /// e.g.: [\"Typescript\", \"TSX\"], [\"Rust\"]\n    pub language_ids: &'static [&'static str],\n\n    /// Extensions that can help classify the file: .rs, .rb, .cabal\n    pub file_extensions: &'static [&'static str],\n\n    /// tree-sitter grammar for this language\n    pub grammar: fn() -> tree_sitter::Language,\n\n    /// Compiled tree-sitter scope query for this language.\n    pub scope_query: MemoizedQuery,\n\n    /// Compiled tree-sitter hoverables query\n    pub hoverable_query: MemoizedQuery,\n\n    /// Namespaces defined by this language,\n    /// E.g.: type namespace, variable namespace, function namespace\n    pub namespaces: NameSpaces,\n}\n\n#[derive(Debug)]\npub struct MemoizedQuery {\n    slot: OnceCell<tree_sitter::Query>,\n    scope_query: &'static str,\n}\n\nimpl MemoizedQuery {\n    pub const fn new(scope_query: &'static str) -> Self {\n        Self {\n            slot: OnceCell::new(),\n            scope_query,\n        }\n    }\n\n    /// Get a reference to the relevant tree sitter compiled query.\n    ///\n    /// This method compiles the query if it has not already been compiled.\n    pub fn query(\n        &self,\n        grammar: fn() -> tree_sitter::Language,\n    ) -> Result<&tree_sitter::Query, tree_sitter::QueryError> {\n        self.slot\n            .get_or_try_init(|| tree_sitter::Query::new(grammar(), self.scope_query))\n    }\n}\n\npub type TSLanguage = Language<TSLanguageConfig>;\n\nimpl TSLanguage {\n    /// Find a tree-sitter language configuration from a language identifier\n    ///\n    /// See [0] for a list of valid language identifiers.\n    ///\n    /// [0]: https://github.com/monkslc/hyperpolyglot/blob/master/src/codegen/languages.rs\n    pub fn from_id(lang_id: &str) -> Self {\n        ALL_LANGUAGES\n            .iter()\n            .copied()\n            .find(|target| {\n                target\n                    .language_ids\n                    .iter()\n                    .any(|&id| id.to_lowercase() == lang_id.to_lowercase())\n            })\n            .map_or(Language::Unsupported, Language::Supported)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n\n    use super::*;\n    use crate::intelligence::NameSpaceMethods;\n\n    use std::collections::HashSet;\n\n    use tree_sitter::Query;\n\n    // ensure that the symbols in all queries files are supported symbols\n    #[test]\n    fn verify_all_symbol_kinds() {\n        let mut failed_languages = Vec::new();\n\n        for language in ALL_LANGUAGES {\n            let kinds = language.namespaces.all_symbols();\n            if !has_valid_symbol_kinds(language.scope_query.query(language.grammar).unwrap(), kinds)\n            {\n                for id in language.language_ids {\n                    failed_languages.push(*id);\n                }\n            }\n        }\n\n        if !failed_languages.is_empty() {\n            panic!(\"invalid symbol kinds for {}\", failed_languages.join(\", \"))\n        }\n    }\n\n    fn has_valid_symbol_kinds(query: &Query, kinds: Vec<&str>) -> bool {\n        let query_file_symbol_names = query\n            .capture_names()\n            .iter()\n            .filter_map(|name| name.split('.').nth(2))\n            .map(ToOwned::to_owned)\n            .collect::<HashSet<_>>();\n\n        let supported_symbol_kinds = kinds\n            .iter()\n            .map(ToString::to_string)\n            .collect::<HashSet<_>>();\n\n        query_file_symbol_names == supported_symbol_kinds\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence/namespace.rs",
    "content": "use serde::{Deserialize, Serialize};\n\n/// An opaque identifier for every symbol in a language\n#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]\npub struct SymbolId {\n    pub namespace_idx: usize,\n    pub symbol_idx: usize,\n}\n\nimpl SymbolId {\n    pub fn name(&self, namespaces: NameSpaces) -> &'static str {\n        namespaces[self.namespace_idx][self.symbol_idx]\n    }\n}\n\n/// A grouping of symbol kinds that allow references among them.\n/// A variable can refer only to other variables, and not types, for example.\npub type NameSpace = &'static [&'static str];\n\n/// A collection of namespaces\npub type NameSpaces = &'static [NameSpace];\n\n/// Helper trait\npub trait NameSpaceMethods {\n    fn all_symbols(self) -> Vec<&'static str>;\n\n    fn symbol_id_of(&self, symbol: &str) -> Option<SymbolId>;\n}\n\nimpl NameSpaceMethods for NameSpaces {\n    fn all_symbols(self) -> Vec<&'static str> {\n        self.iter().flat_map(|ns| ns.iter().cloned()).collect()\n    }\n\n    fn symbol_id_of(&self, symbol: &str) -> Option<SymbolId> {\n        self.iter()\n            .enumerate()\n            .find_map(|(namespace_idx, namespace)| {\n                namespace\n                    .iter()\n                    .position(|s| s == &symbol)\n                    .map(|symbol_idx| SymbolId {\n                        namespace_idx,\n                        symbol_idx,\n                    })\n            })\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence/scope_resolution/debug.rs",
    "content": "use std::fmt;\n\nuse super::{EdgeKind, LocalDef, NodeKind};\nuse crate::{intelligence::TSLanguageConfig, text_range::TextRange};\n\nuse petgraph::{\n    graph::{Graph, NodeIndex},\n    visit::EdgeRef,\n    Direction,\n};\n\npub struct ScopeDebug {\n    range: TextRange,\n    defs: Vec<DefDebug>,\n    imports: Vec<ImportDebug>,\n    scopes: Vec<ScopeDebug>,\n    language: &'static TSLanguageConfig,\n}\n\nstruct DefDebug {\n    name: String,\n    range: TextRange,\n    context: String,\n    refs: Vec<RefDebug>,\n    symbol: String,\n}\n\nstruct RefDebug {\n    context: String,\n}\n\nstruct ImportDebug {\n    name: String,\n    range: TextRange,\n    context: String,\n    refs: Vec<RefDebug>,\n}\n\nimpl DefDebug {\n    fn new(\n        range: TextRange,\n        name: String,\n        refs: Vec<TextRange>,\n        symbol: String,\n        src: &[u8],\n    ) -> Self {\n        Self {\n            name,\n            range,\n            context: context(range, src),\n            refs: refs\n                .into_iter()\n                .map(|r| context(r, src))\n                .map(|context| RefDebug { context })\n                .collect(),\n            symbol,\n        }\n    }\n}\n\nimpl ImportDebug {\n    fn new(range: TextRange, name: String, refs: Vec<TextRange>, src: &[u8]) -> Self {\n        Self {\n            name,\n            range,\n            context: context(range, src),\n            refs: refs\n                .into_iter()\n                .map(|r| context(r, src))\n                .map(|context| RefDebug { context })\n                .collect(),\n        }\n    }\n}\n\nimpl ScopeDebug {\n    fn empty(range: TextRange, language: &'static TSLanguageConfig) -> Self {\n        Self {\n            range,\n            defs: Vec::new(),\n            imports: Vec::new(),\n            scopes: Vec::new(),\n            language,\n        }\n    }\n\n    fn build(&mut self, graph: &Graph<NodeKind, EdgeKind>, start: NodeIndex<u32>, src: &[u8]) {\n        let mut defs = graph\n            .edges_directed(start, Direction::Incoming)\n            .filter(|edge| *edge.weight() == EdgeKind::DefToScope)\n            .map(|edge| {\n                let def_node = edge.source();\n\n                // range of this def\n                let range = graph[def_node].range();\n\n                // text source of this def\n                let text = std::str::from_utf8(&src[range.start.byte..range.end.byte])\n                    .unwrap()\n                    .to_owned();\n\n                // all references of this def, sorted by range\n                let mut refs = graph\n                    .edges_directed(def_node, Direction::Incoming)\n                    .filter(|edge| *edge.weight() == EdgeKind::RefToDef)\n                    .map(|edge| graph[edge.source()].range())\n                    .collect::<Vec<_>>();\n\n                refs.sort();\n\n                // symbol, if any\n                let symbol = match &graph[def_node] {\n                    NodeKind::Def(LocalDef {\n                        symbol_id: Some(symbol_id),\n                        ..\n                    }) => symbol_id.name(self.language.namespaces).to_string(),\n                    _ => \"none\".to_string(),\n                };\n\n                DefDebug::new(range, text, refs, symbol, src)\n            })\n            .collect::<Vec<_>>();\n\n        let mut imports = graph\n            .edges_directed(start, Direction::Incoming)\n            .filter(|edge| *edge.weight() == EdgeKind::ImportToScope)\n            .map(|edge| {\n                let imp_node = edge.source();\n\n                // range of this import\n                let range = graph[imp_node].range();\n\n                // text source of this import\n                let text = std::str::from_utf8(&src[range.start.byte..range.end.byte])\n                    .unwrap()\n                    .to_owned();\n\n                // all references of this import, sorted by range\n                let mut refs = graph\n                    .edges_directed(imp_node, Direction::Incoming)\n                    .filter(|edge| *edge.weight() == EdgeKind::RefToImport)\n                    .map(|edge| graph[edge.source()].range())\n                    .collect::<Vec<_>>();\n\n                refs.sort();\n\n                ImportDebug::new(range, text, refs, src)\n            })\n            .collect::<Vec<_>>();\n\n        let mut scopes = graph\n            .edges_directed(start, Direction::Incoming)\n            .filter(|edge| *edge.weight() == EdgeKind::ScopeToScope)\n            .map(|edge| {\n                let source_scope = edge.source();\n                let mut scope_debug = ScopeDebug::empty(graph[source_scope].range(), self.language);\n                scope_debug.build(graph, source_scope, src);\n                scope_debug\n            })\n            .collect::<Vec<_>>();\n\n        // sort defs by their ranges\n        defs.sort_by(|a, b| a.range.cmp(&b.range));\n        // sort imports by their ranges\n        imports.sort_by(|a, b| a.range.cmp(&b.range));\n        // sort scopes by their ranges\n        scopes.sort_by(|a, b| a.range.cmp(&b.range));\n\n        self.defs = defs;\n        self.imports = imports;\n        self.scopes = scopes;\n    }\n\n    pub fn new(\n        graph: &Graph<NodeKind, EdgeKind>,\n        start: NodeIndex<u32>,\n        src: &[u8],\n        lang_config: &'static TSLanguageConfig,\n    ) -> Self {\n        let mut scope_debug = Self::empty(graph[start].range(), lang_config);\n        scope_debug.build(graph, start, src);\n        scope_debug\n    }\n}\n\nimpl fmt::Debug for ScopeDebug {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        if self.imports.is_empty() {\n            f.debug_struct(\"scope\")\n                .field(\"definitions\", &self.defs)\n                .field(\"child scopes\", &self.scopes)\n                .finish()\n        } else {\n            f.debug_struct(\"scope\")\n                .field(\"definitions\", &self.defs)\n                .field(\"imports\", &self.imports)\n                .field(\"child scopes\", &self.scopes)\n                .finish()\n        }\n    }\n}\n\nimpl fmt::Debug for DefDebug {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let mut s = f.debug_struct(&self.name);\n        let d = s\n            .field(\"kind\", &self.symbol)\n            .field(\"context\", &self.context);\n\n        if self.refs.is_empty() {\n            d\n        } else {\n            d.field(&format!(\"referenced in ({})\", self.refs.len()), &self.refs)\n        }\n        .finish()\n    }\n}\n\nimpl fmt::Debug for ImportDebug {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let mut s = f.debug_struct(&self.name);\n        let d = s.field(\"context\", &self.context);\n\n        if self.refs.is_empty() {\n            d\n        } else {\n            d.field(&format!(\"referenced in ({})\", self.refs.len()), &self.refs)\n        }\n        .finish()\n    }\n}\n\nimpl fmt::Debug for RefDebug {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"`{}`\", self.context)\n    }\n}\n\nfn context(range: TextRange, src: &[u8]) -> String {\n    // first new line before start\n    let context_start = src\n        .iter()\n        .enumerate()\n        .take(range.start.byte)\n        .rev()\n        .find_map(|(idx, &c)| (c == b'\\n').then_some(idx))\n        .unwrap_or(range.start.byte - 1)\n        .saturating_add(1);\n\n    // first new line after end\n    let context_end: usize = src\n        .iter()\n        .enumerate()\n        .skip(range.end.byte)\n        .find_map(|(idx, &c)| (c == b'\\n').then_some(idx))\n        .unwrap_or(range.end.byte + 1)\n        .saturating_sub(1);\n\n    let from_utf8 = |bytes| std::str::from_utf8(bytes).unwrap();\n    format!(\n        \"{}§{}§{}\",\n        from_utf8(&src[context_start..range.start.byte]).trim_start(),\n        from_utf8(&src[range.start.byte..range.end.byte]),\n        from_utf8(&src[range.end.byte..=context_end]).trim_end()\n    )\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence/scope_resolution/def.rs",
    "content": "use crate::{intelligence::namespace::SymbolId, text_range::TextRange};\n\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]\npub struct LocalDef {\n    pub range: TextRange,\n    pub symbol_id: Option<SymbolId>,\n}\n\nimpl LocalDef {\n    /// Initialize a new definition\n    pub fn new(range: TextRange, symbol_id: Option<SymbolId>) -> Self {\n        Self { range, symbol_id }\n    }\n\n    pub fn name<'a>(&self, buffer: &'a [u8]) -> &'a [u8] {\n        &buffer[self.range.start.byte..self.range.end.byte]\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence/scope_resolution/import.rs",
    "content": "use crate::text_range::TextRange;\n\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]\npub struct LocalImport {\n    pub range: TextRange,\n}\n\nimpl LocalImport {\n    /// Initialize a new import\n    pub fn new(range: TextRange) -> Self {\n        Self { range }\n    }\n\n    pub fn name<'a>(&self, buffer: &'a [u8]) -> &'a [u8] {\n        &buffer[self.range.start.byte..self.range.end.byte]\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence/scope_resolution/reference.rs",
    "content": "use crate::{intelligence::namespace::SymbolId, text_range::TextRange};\n\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct Reference {\n    pub range: TextRange,\n    pub symbol_id: Option<SymbolId>,\n}\n\nimpl Reference {\n    /// Initialize a new reference\n    pub fn new(range: TextRange, symbol_id: Option<SymbolId>) -> Self {\n        Self { range, symbol_id }\n    }\n\n    pub fn name<'a>(&self, buffer: &'a [u8]) -> &'a [u8] {\n        &buffer[self.range.start.byte..self.range.end.byte]\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence/scope_resolution/scope.rs",
    "content": "use super::{EdgeKind, ScopeGraph};\nuse crate::text_range::TextRange;\n\nuse petgraph::{graph::NodeIndex, visit::EdgeRef, Direction};\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Clone, Copy, Serialize, Deserialize)]\npub struct LocalScope {\n    pub range: TextRange,\n}\n\nimpl LocalScope {\n    pub fn new(range: TextRange) -> Self {\n        Self { range }\n    }\n}\n\npub struct ScopeStack<'a> {\n    pub scope_graph: &'a ScopeGraph,\n    pub start: Option<NodeIndex<u32>>,\n}\n\nimpl<'a> Iterator for ScopeStack<'a> {\n    type Item = NodeIndex<u32>;\n    fn next(&mut self) -> Option<Self::Item> {\n        if let Some(start) = self.start {\n            let parent = self\n                .scope_graph\n                .graph\n                .edges_directed(start, Direction::Outgoing)\n                .find(|edge| *edge.weight() == EdgeKind::ScopeToScope)\n                .map(|edge| edge.target());\n            let original = start;\n            self.start = parent;\n            Some(original)\n        } else {\n            None\n        }\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence/scope_resolution.rs",
    "content": "#[cfg(test)]\nmod debug;\nmod def;\nmod import;\nmod reference;\nmod scope;\n\npub use def::LocalDef;\npub use import::LocalImport;\npub use reference::Reference;\npub use scope::{LocalScope, ScopeStack};\n\nuse super::{NameSpaceMethods, TSLanguageConfig, ALL_LANGUAGES};\nuse crate::{symbol::Symbol, text_range::TextRange};\n\nuse std::{collections::HashMap, str::FromStr};\n\nuse petgraph::{graph::Graph, visit::EdgeRef, Direction};\nuse serde::{Deserialize, Serialize};\nuse tracing::warn;\nuse tree_sitter::{Node, Query, QueryCursor};\n\npub type NodeIndex = petgraph::graph::NodeIndex<u32>;\n\n/// The algorithm used to resolve scopes.\n///\n/// The resolution method may be parametrized on language.\n#[derive(Debug, PartialEq, Eq, Clone, Copy)]\n#[non_exhaustive]\npub enum ResolutionMethod {\n    /// `Generic` refers to a basic lexical scoping algorithm.\n    Generic,\n}\n\nimpl ResolutionMethod {\n    /// Build a lexical scope-graph with a scope query and a tree-sitter tree. The `src`\n    /// parameter is required by tree-sitter to resolve certain kinds of query predicates\n    /// such as #match? and #eq?.\n    pub fn build_scope(\n        &self,\n        query: &Query,\n        root_node: Node<'_>,\n        src: &[u8],\n        language: &TSLanguageConfig,\n    ) -> ScopeGraph {\n        match self {\n            ResolutionMethod::Generic => scope_res_generic(query, root_node, src, language),\n        }\n    }\n}\n\n/// The type of a node in the ScopeGraph\n#[derive(Serialize, Deserialize, Debug, Clone)]\npub enum NodeKind {\n    /// A scope node\n    Scope(LocalScope),\n\n    /// A definition node\n    Def(LocalDef),\n\n    /// An import node\n    Import(LocalImport),\n\n    /// A reference node\n    Ref(Reference),\n}\n\nimpl NodeKind {\n    /// Construct a scope node from a range\n    pub fn scope(range: TextRange) -> Self {\n        Self::Scope(LocalScope::new(range))\n    }\n\n    /// Produce the range spanned by this node\n    pub fn range(&self) -> TextRange {\n        match self {\n            Self::Scope(l) => l.range,\n            Self::Def(d) => d.range,\n            Self::Ref(r) => r.range,\n            Self::Import(i) => i.range,\n        }\n    }\n}\n\n/// Describes the relation between two nodes in the ScopeGraph\n#[derive(Serialize, Deserialize, PartialEq, Eq, Copy, Clone, Debug)]\npub enum EdgeKind {\n    /// The edge weight from a nested scope to its parent scope\n    ScopeToScope,\n\n    /// The edge weight from a definition to its definition scope\n    DefToScope,\n\n    /// The edge weight from an import to its definition scope\n    ImportToScope,\n\n    /// The edge weight from a reference to its definition\n    RefToDef,\n\n    /// The edge weight from a reference to its import\n    RefToImport,\n}\n\n/// A graph representation of scopes and names in a single syntax tree\n#[derive(Debug, Serialize, Deserialize, Clone)]\npub struct ScopeGraph {\n    /// The raw graph\n    pub graph: Graph<NodeKind, EdgeKind>,\n\n    // Graphs do not have the concept of a `root`, but lexical scopes follow the syntax\n    // tree, and as a result, have a \"root\" node. The root_idx points to a scope node that\n    // encompasses the entire file: the global scope.\n    root_idx: NodeIndex,\n\n    /// An index into ALL_LANGUAGES which corresponds to the language for this graph\n    lang_id: usize,\n}\n\nimpl ScopeGraph {\n    pub fn new(range: TextRange, lang_id: usize) -> Self {\n        let mut graph = Graph::new();\n        let root_idx = graph.add_node(NodeKind::scope(range));\n        Self {\n            graph,\n            root_idx,\n            lang_id,\n        }\n    }\n\n    pub fn get_node(&self, node_idx: NodeIndex) -> Option<&NodeKind> {\n        self.graph.node_weight(node_idx)\n    }\n\n    /// Insert a local scope into the scope-graph\n    pub fn insert_local_scope(&mut self, new: LocalScope) {\n        if let Some(parent_scope) = self.scope_by_range(new.range, self.root_idx) {\n            let new_scope = NodeKind::Scope(new);\n            let new_idx = self.graph.add_node(new_scope);\n            self.graph\n                .add_edge(new_idx, parent_scope, EdgeKind::ScopeToScope);\n        }\n    }\n\n    /// Insert a def into the scope-graph\n    pub fn insert_local_def(&mut self, new: LocalDef) {\n        if let Some(defining_scope) = self.scope_by_range(new.range, self.root_idx) {\n            let new_def = NodeKind::Def(new);\n            let new_idx = self.graph.add_node(new_def);\n            self.graph\n                .add_edge(new_idx, defining_scope, EdgeKind::DefToScope);\n        }\n    }\n\n    /// Insert a def into the scope-graph, at the parent scope of the defining scope\n    pub fn insert_hoisted_def(&mut self, new: LocalDef) {\n        if let Some(defining_scope) = self.scope_by_range(new.range, self.root_idx) {\n            let new_def = NodeKind::Def(new);\n            let new_idx = self.graph.add_node(new_def);\n\n            // if the parent scope exists, insert this def there, if not,\n            // insert into the defining scope\n            let target_scope = self.parent_scope(defining_scope).unwrap_or(defining_scope);\n\n            self.graph\n                .add_edge(new_idx, target_scope, EdgeKind::DefToScope);\n        }\n    }\n\n    /// Insert a def into the scope-graph, at the root scope\n    pub fn insert_global_def(&mut self, new: LocalDef) {\n        let new_def = NodeKind::Def(new);\n        let new_idx = self.graph.add_node(new_def);\n        self.graph\n            .add_edge(new_idx, self.root_idx, EdgeKind::DefToScope);\n    }\n\n    /// Insert an import into the scope-graph\n    pub fn insert_local_import(&mut self, new: LocalImport) {\n        if let Some(defining_scope) = self.scope_by_range(new.range, self.root_idx) {\n            let new_imp = NodeKind::Import(new);\n            let new_idx = self.graph.add_node(new_imp);\n            self.graph\n                .add_edge(new_idx, defining_scope, EdgeKind::ImportToScope);\n        }\n    }\n\n    /// Insert a ref into the scope-graph\n    pub fn insert_ref(&mut self, new: Reference, src: &[u8]) {\n        let mut possible_defs = vec![];\n        let mut possible_imports = vec![];\n        if let Some(local_scope_idx) = self.scope_by_range(new.range, self.root_idx) {\n            // traverse the scopes from the current-scope to the root-scope\n            for scope in self.scope_stack(local_scope_idx) {\n                // find candidate definitions in each scope\n                for local_def in self\n                    .graph\n                    .edges_directed(scope, Direction::Incoming)\n                    .filter(|edge| *edge.weight() == EdgeKind::DefToScope)\n                    .map(|edge| edge.source())\n                {\n                    if let NodeKind::Def(def) = &self.graph[local_def] {\n                        if new.name(src) == def.name(src) {\n                            match (&def.symbol_id, &new.symbol_id) {\n                                // both contain symbols, but they don't belong to the same namepspace\n                                (Some(d), Some(r)) if d.namespace_idx != r.namespace_idx => {}\n\n                                // in all other cases, form an edge from the ref to def.\n                                // an empty symbol belongs to all namespaces:\n                                // * (None, None)\n                                // * (None, Some(_))\n                                // * (Some(_), None)\n                                // * (Some(_), Some(_)) if def.namespace == ref.namespace\n                                _ => {\n                                    possible_defs.push(local_def);\n                                }\n                            };\n                        }\n                    }\n                }\n\n                // find candidate imports in each scope\n                for local_import in self\n                    .graph\n                    .edges_directed(scope, Direction::Incoming)\n                    .filter(|edge| *edge.weight() == EdgeKind::ImportToScope)\n                    .map(|edge| edge.source())\n                {\n                    if let NodeKind::Import(import) = &self.graph[local_import] {\n                        if new.name(src) == import.name(src) {\n                            possible_imports.push(local_import);\n                        }\n                    }\n                }\n            }\n        }\n\n        if !possible_defs.is_empty() || !possible_imports.is_empty() {\n            let new_ref = NodeKind::Ref(new);\n            let ref_idx = self.graph.add_node(new_ref);\n            for def_idx in possible_defs {\n                self.graph.add_edge(ref_idx, def_idx, EdgeKind::RefToDef);\n            }\n            for imp_idx in possible_imports {\n                self.graph.add_edge(ref_idx, imp_idx, EdgeKind::RefToImport);\n            }\n        }\n    }\n\n    fn scope_stack(&self, start: NodeIndex) -> ScopeStack<'_> {\n        ScopeStack {\n            scope_graph: self,\n            start: Some(start),\n        }\n    }\n\n    // The smallest scope that encompasses `range`. Start at `start` and narrow down if possible.\n    fn scope_by_range(&self, range: TextRange, start: NodeIndex) -> Option<NodeIndex> {\n        let target_range = self.graph[start].range();\n        if target_range.contains(&range) {\n            let child_scopes = self\n                .graph\n                .edges_directed(start, Direction::Incoming)\n                .filter(|edge| *edge.weight() == EdgeKind::ScopeToScope)\n                .map(|edge| edge.source())\n                .collect::<Vec<_>>();\n            for child_scope in child_scopes {\n                if let Some(t) = self.scope_by_range(range, child_scope) {\n                    return Some(t);\n                }\n            }\n            return Some(start);\n        }\n        None\n    }\n\n    // Produce the parent scope of a given scope\n    fn parent_scope(&self, start: NodeIndex) -> Option<NodeIndex> {\n        if matches!(self.graph[start], NodeKind::Scope(_)) {\n            return self\n                .graph\n                .edges_directed(start, Direction::Outgoing)\n                .filter(|edge| *edge.weight() == EdgeKind::ScopeToScope)\n                .map(|edge| edge.target())\n                .next();\n        }\n        None\n    }\n\n    /// Produce a list of interesting ranges: ranges of defs and refs\n    pub fn hoverable_ranges(&self) -> Box<dyn Iterator<Item = TextRange> + '_> {\n        let iterator =\n            self.graph\n                .node_indices()\n                .filter_map(|node_idx| match &self.graph[node_idx] {\n                    NodeKind::Scope(_) => None,\n                    NodeKind::Def(d) => Some(d.range),\n                    NodeKind::Ref(r) => Some(r.range),\n                    NodeKind::Import(i) => Some(i.range),\n                });\n        Box::new(iterator)\n    }\n\n    /// Produce possible definitions for a reference\n    pub fn definitions(\n        &self,\n        reference_node: NodeIndex,\n    ) -> Box<dyn Iterator<Item = NodeIndex> + '_> {\n        let iterator = self\n            .graph\n            .edges_directed(reference_node, Direction::Outgoing)\n            .filter(|edge| *edge.weight() == EdgeKind::RefToDef)\n            .map(|edge| edge.target());\n        Box::new(iterator)\n    }\n\n    /// Produce possible imports for a reference\n    pub fn imports(&self, reference_node: NodeIndex) -> Box<dyn Iterator<Item = NodeIndex> + '_> {\n        let iterator = self\n            .graph\n            .edges_directed(reference_node, Direction::Outgoing)\n            .filter(|edge| *edge.weight() == EdgeKind::RefToImport)\n            .map(|edge| edge.target());\n        Box::new(iterator)\n    }\n\n    /// Produce possible references for a definition/import node\n    pub fn references(\n        &self,\n        definition_node: NodeIndex,\n    ) -> Box<dyn Iterator<Item = NodeIndex> + '_> {\n        let iterator = self\n            .graph\n            .edges_directed(definition_node, Direction::Incoming)\n            .filter(|edge| {\n                *edge.weight() == EdgeKind::RefToDef || *edge.weight() == EdgeKind::RefToImport\n            })\n            .map(|edge| edge.source());\n        Box::new(iterator)\n    }\n\n    pub fn node_by_range(&self, start_byte: usize, end_byte: usize) -> Option<NodeIndex> {\n        self.graph\n            .node_indices()\n            .filter(|&idx| self.is_definition(idx) || self.is_reference(idx) || self.is_import(idx))\n            .find(|&idx| {\n                let node = self.graph[idx].range();\n                start_byte >= node.start.byte && end_byte <= node.end.byte\n            })\n    }\n\n    /// The \"value\" of a definition is loosely characterized as\n    ///\n    /// - the body of a function block\n    /// - the body of a class\n    /// - the parameters list defining generic types\n    /// - the RHS of a value\n    ///\n    /// The heuristic used here is\n    ///  - the smallest scope-node that encompasses the definition_node\n    ///  - or the largest scope-node on the same line as the to the definition_node\n    pub fn value_of_definition(&self, def_idx: NodeIndex) -> Option<NodeIndex> {\n        let smallest_scope_node = self\n            .scope_by_range(self.graph[def_idx].range(), self.root_idx)\n            .filter(|&idx| {\n                self.graph[idx].range().start.line == self.graph[def_idx].range().start.line\n            });\n        let largest_adjacent_node = self\n            .graph\n            .node_indices()\n            .filter(|&idx| match self.graph[idx] {\n                NodeKind::Scope(scope) => {\n                    scope.range.start.line == self.graph[def_idx].range().start.line\n                }\n                _ => false,\n            })\n            .max_by_key(|idx| self.graph[*idx].range().size());\n\n        smallest_scope_node.or(largest_adjacent_node)\n    }\n\n    pub fn node_by_position(&self, line: usize, column: usize) -> Option<NodeIndex> {\n        self.graph\n            .node_indices()\n            .filter(|&idx| self.is_definition(idx) || self.is_reference(idx))\n            .find(|&idx| {\n                let node = self.graph[idx].range();\n                node.start.line == line\n                    && node.end.line == line\n                    && node.start.column <= column\n                    && node.end.column >= column\n            })\n    }\n\n    pub fn symbols(&self) -> Vec<Symbol> {\n        let namespaces = ALL_LANGUAGES[self.lang_id].namespaces;\n        self.graph\n            .node_weights()\n            .filter_map(|weight| match weight {\n                NodeKind::Def(LocalDef {\n                    range,\n                    symbol_id: Some(symbol_id),\n                    ..\n                }) => Some(Symbol {\n                    kind: symbol_id.name(namespaces).to_owned(), // FIXME: this should use SymbolId::name\n                    range: *range,\n                }),\n                _ => None,\n            })\n            .collect()\n    }\n\n    // produce a stringified name of a def/ref's symbol\n    pub fn symbol_name_of(&self, idx: NodeIndex) -> Option<&'static str> {\n        let namespaces = ALL_LANGUAGES[self.lang_id].namespaces;\n        match &self.graph[idx] {\n            NodeKind::Def(d) => d.symbol_id.map(|s| s.name(namespaces)),\n            NodeKind::Ref(r) => r.symbol_id.map(|s| s.name(namespaces)),\n            _ => None,\n        }\n    }\n\n    // is the given ref/def a direct child of the root scope\n    pub fn is_top_level(&self, idx: NodeIndex) -> bool {\n        self.graph.contains_edge(idx, self.root_idx)\n    }\n\n    #[cfg(test)]\n    pub fn debug(&self, src: &[u8], language: &'static TSLanguageConfig) -> debug::ScopeDebug {\n        let graph = &self.graph;\n        let start = self.root_idx;\n        debug::ScopeDebug::new(graph, start, src, language)\n    }\n\n    #[cfg(test)]\n    pub fn find_node_by_name(&self, src: &[u8], name: &[u8]) -> Option<NodeIndex> {\n        self.graph.node_indices().find(|idx| {\n            matches!(\n                    &self.graph[*idx],\n                    NodeKind::Def(d) if d.name(src) == name)\n        })\n    }\n\n    pub fn is_definition(&self, node_idx: NodeIndex) -> bool {\n        matches!(self.graph[node_idx], NodeKind::Def(_))\n    }\n\n    pub fn is_reference(&self, node_idx: NodeIndex) -> bool {\n        matches!(self.graph[node_idx], NodeKind::Ref(_))\n    }\n\n    pub fn is_scope(&self, node_idx: NodeIndex) -> bool {\n        matches!(self.graph[node_idx], NodeKind::Scope(_))\n    }\n\n    pub fn is_import(&self, node_idx: NodeIndex) -> bool {\n        matches!(self.graph[node_idx], NodeKind::Import(_))\n    }\n}\n\nfn scope_res_generic(\n    query: &Query,\n    root_node: Node<'_>,\n    src: &[u8],\n    language: &TSLanguageConfig,\n) -> ScopeGraph {\n    let namespaces = language.namespaces;\n\n    enum Scoping {\n        Global,\n        Hoisted,\n        Local,\n    }\n\n    // extract supported capture groups\n    struct LocalDefCapture<'a> {\n        index: u32,\n        symbol: Option<&'a str>,\n        scoping: Scoping,\n    }\n\n    struct LocalRefCapture<'a> {\n        index: u32,\n        symbol: Option<&'a str>,\n    }\n\n    impl FromStr for Scoping {\n        type Err = String;\n        fn from_str(s: &str) -> Result<Self, Self::Err> {\n            match s {\n                \"hoist\" => Ok(Self::Hoisted),\n                \"global\" => Ok(Self::Global),\n                \"local\" => Ok(Self::Local),\n                s => Err(s.to_owned()),\n            }\n        }\n    }\n\n    // every capture of the form:\n    //  - local.definition.<symbol>\n    //  - hoist.definition.<symbol>\n    // is a local_def\n    let mut local_def_captures = Vec::<LocalDefCapture<'_>>::new();\n\n    // every capture of the form local.import is a local_import\n    let mut local_import_capture_index = None;\n\n    // every capture of the form local.reference.<symbol> is a local_ref\n    let mut local_ref_captures = Vec::<LocalRefCapture<'_>>::new();\n\n    // every capture of the form local.scope is a local_scope\n    let mut local_scope_capture_index = None;\n\n    // determine indices of every capture group in the query file\n    for (i, name) in query.capture_names().iter().enumerate() {\n        let i = i as u32;\n        let parts: Vec<_> = name.split('.').collect();\n\n        match parts.as_slice() {\n            [scoping, \"definition\", sym] => {\n                let index = i;\n                let symbol = Some(sym.to_owned());\n                let scoping = Scoping::from_str(scoping).expect(\"invalid scope keyword\");\n\n                let l = LocalDefCapture {\n                    index,\n                    symbol,\n                    scoping,\n                };\n                local_def_captures.push(l)\n            }\n            [scoping, \"definition\"] => {\n                let index = i;\n                let symbol = None;\n                let scoping = Scoping::from_str(scoping).expect(\"invalid scope keyword\");\n\n                let l = LocalDefCapture {\n                    index,\n                    symbol,\n                    scoping,\n                };\n                local_def_captures.push(l)\n            }\n            [\"local\", \"reference\", sym] => {\n                let index = i;\n                let symbol = Some(sym.to_owned());\n\n                let l = LocalRefCapture { index, symbol };\n                local_ref_captures.push(l);\n            }\n            [\"local\", \"reference\"] => {\n                let index = i;\n                let symbol = None;\n\n                let l = LocalRefCapture { index, symbol };\n                local_ref_captures.push(l);\n            }\n            [\"local\", \"scope\"] => local_scope_capture_index = Some(i),\n            [\"local\", \"import\"] => local_import_capture_index = Some(i),\n            _ if !name.starts_with('_') => warn!(?name, \"unrecognized query capture\"),\n            _ => (), // allow captures that start with underscore to fly under the radar\n        }\n    }\n\n    // run scope-query upon the syntax-tree\n    let mut cursor = QueryCursor::new();\n    let captures = cursor.captures(query, root_node, src);\n\n    let lang_id = ALL_LANGUAGES\n        .iter()\n        .position(|l| l.language_ids == language.language_ids)\n        .unwrap();\n    let mut scope_graph = ScopeGraph::new(root_node.range().into(), lang_id);\n\n    let capture_map = captures.fold(\n        HashMap::<_, Vec<_>>::new(),\n        |mut map, (match_, capture_idx)| {\n            let capture = match_.captures[capture_idx];\n            let range: TextRange = capture.node.range().into();\n            map.entry(capture.index).or_default().push(range);\n            map\n        },\n    );\n\n    // insert scopes first\n    if let Some(ranges) = local_scope_capture_index.and_then(|idx| capture_map.get(&idx)) {\n        for range in ranges {\n            let scope = LocalScope::new(*range);\n            scope_graph.insert_local_scope(scope);\n        }\n    }\n\n    // followed by imports\n    if let Some(ranges) = local_import_capture_index.and_then(|idx| capture_map.get(&idx)) {\n        for range in ranges {\n            let import = LocalImport::new(*range);\n            scope_graph.insert_local_import(import);\n        }\n    }\n\n    // followed by defs\n    for LocalDefCapture {\n        index,\n        symbol,\n        scoping,\n    } in local_def_captures\n    {\n        if let Some(ranges) = capture_map.get(&index) {\n            for range in ranges {\n                // if the symbol is present, is it one of the supported symbols for this language?\n                let symbol_id = symbol.and_then(|s| namespaces.symbol_id_of(s));\n                let local_def = LocalDef::new(*range, symbol_id);\n\n                match scoping {\n                    Scoping::Hoisted => scope_graph.insert_hoisted_def(local_def),\n                    Scoping::Global => scope_graph.insert_global_def(local_def),\n                    Scoping::Local => scope_graph.insert_local_def(local_def),\n                };\n            }\n        }\n    }\n\n    // and then refs\n    for LocalRefCapture { index, symbol } in local_ref_captures {\n        if let Some(ranges) = capture_map.get(&index) {\n            for range in ranges {\n                // if the symbol is present, is it one of the supported symbols for this language?\n                let symbol_id = symbol.and_then(|s| namespaces.symbol_id_of(s));\n                let ref_ = Reference::new(*range, symbol_id);\n\n                scope_graph.insert_ref(ref_, src);\n            }\n        }\n    }\n\n    scope_graph\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::{\n        intelligence::SymbolId,\n        text_range::{Point, TextRange},\n    };\n    use expect_test::expect;\n\n    const DUMMY_LANG_ID: usize = 0;\n\n    // test-utility to build byte-only text-ranges\n    //\n    // assumes one byte per line\n    fn r(start: usize, end: usize) -> TextRange {\n        TextRange {\n            start: Point {\n                byte: start,\n                line: start,\n                column: 0,\n            },\n            end: Point {\n                byte: end,\n                line: end,\n                column: 0,\n            },\n        }\n    }\n\n    // test-utility to create a local scope\n    fn scope(start: usize, end: usize) -> LocalScope {\n        LocalScope {\n            range: r(start, end),\n        }\n    }\n\n    // test-utility to create a local def\n    fn definition(start: usize, end: usize) -> LocalDef {\n        LocalDef {\n            range: r(start, end),\n            symbol_id: None,\n        }\n    }\n\n    // test-utility to create a reference\n    fn reference(start: usize, end: usize) -> Reference {\n        Reference {\n            range: r(start, end),\n            symbol_id: None,\n        }\n    }\n\n    // test-utility to build a stringified edge-list from a graph\n    fn test_edges(graph: &Graph<NodeKind, EdgeKind>, expected: expect_test::Expect) {\n        let edge_list = graph\n            .edge_references()\n            .map(|edge| {\n                let source = graph[edge.source()].range();\n                let target = graph[edge.target()].range();\n                let weight = edge.weight();\n                format!(\n                    \"{:02}..{:02} --{weight:?}-> {:02}..{:02}\\n\",\n                    source.start.byte, source.end.byte, target.start.byte, target.end.byte,\n                )\n            })\n            .collect::<String>();\n\n        expected.assert_eq(&edge_list)\n    }\n\n    #[test]\n    fn insert_scopes() {\n        let mut s = ScopeGraph::new(r(0, 20), DUMMY_LANG_ID);\n\n        let a = scope(0, 10);\n        let c = scope(0, 5);\n        let d = scope(6, 10);\n\n        let b = scope(11, 20);\n        let e = scope(11, 15);\n        let f = scope(16, 20);\n\n        for scope in [a, b, c, d, e, f] {\n            s.insert_local_scope(scope);\n        }\n\n        // should build:\n        //\n        //     root\n        //       `- a\n        //          `- c\n        //          `- d\n        //       `- b\n        //          `- e\n        //          `- f\n        //\n        // |n| = 7\n        // |e| = 6\n\n        assert_eq!(s.graph.node_count(), 7);\n        assert_eq!(s.graph.edge_count(), 6);\n\n        // a -> root\n        // b -> root\n        // c -> a\n        // d -> a\n        // e -> b\n        // f -> b\n        test_edges(\n            &s.graph,\n            expect![[r#\"\n                00..10 --ScopeToScope-> 00..20\n                11..20 --ScopeToScope-> 00..20\n                00..05 --ScopeToScope-> 00..10\n                06..10 --ScopeToScope-> 00..10\n                11..15 --ScopeToScope-> 11..20\n                16..20 --ScopeToScope-> 11..20\n            \"#]],\n        );\n    }\n\n    #[test]\n    fn insert_defs() {\n        let mut s = ScopeGraph::new(r(0, 20), DUMMY_LANG_ID);\n\n        // modeling the following code:\n        //\n        //     fn main() {\n        //        let a = 2;\n        //        let b = 3;\n        //     }\n\n        let main = scope(0, 10);\n        let a = definition(1, 2);\n        let b = definition(4, 5);\n\n        s.insert_local_scope(main);\n        s.insert_local_def(a);\n        s.insert_local_def(b);\n\n        // should build:\n        //\n        //     root\n        //       `- main\n        //           `- a\n        //           `- b\n\n        test_edges(\n            &s.graph,\n            expect![[r#\"\n                00..10 --ScopeToScope-> 00..20\n                01..02 --DefToScope-> 00..10\n                04..05 --DefToScope-> 00..10\n            \"#]],\n        );\n    }\n\n    #[test]\n    fn insert_hoisted_defs() {\n        let mut s = ScopeGraph::new(r(0, 20), DUMMY_LANG_ID);\n\n        let main = scope(0, 10);\n        let a = definition(1, 2);\n        let b = definition(4, 5);\n\n        s.insert_local_scope(main);\n        s.insert_local_def(a);\n        // should hoist `b` from `main` to `root`\n        s.insert_hoisted_def(b);\n\n        // should build:\n        //\n        //     root\n        //       `- b\n        //       `- main\n        //           `- a\n\n        // root has 2 incoming edges:\n        // main -> root\n        // b -> root\n        assert_eq!(\n            s.graph\n                .edges_directed(s.root_idx, Direction::Incoming)\n                .count(),\n            2\n        );\n\n        test_edges(\n            &s.graph,\n            expect![[r#\"\n                00..10 --ScopeToScope-> 00..20\n                01..02 --DefToScope-> 00..10\n                04..05 --DefToScope-> 00..20\n            \"#]],\n        );\n    }\n\n    #[test]\n    fn insert_hoisted_no_parent() {\n        let mut s = ScopeGraph::new(r(0, 20), DUMMY_LANG_ID);\n\n        let a = definition(1, 2);\n\n        s.insert_hoisted_def(a);\n\n        // should build:\n        //\n        //     root\n        //       `- a\n        //\n        // `a` cannot be hoisted beyond `root`\n\n        test_edges(\n            &s.graph,\n            expect![[r#\"\n                01..02 --DefToScope-> 00..20\n            \"#]],\n        );\n    }\n\n    #[test]\n    fn insert_ref() {\n        let mut s = ScopeGraph::new(r(0, 20), DUMMY_LANG_ID);\n\n        let foo = definition(0, 3);\n        let foo_ref = reference(5, 8);\n\n        let src = r\"foo\\nfoo\".as_bytes();\n\n        s.insert_local_def(foo);\n        s.insert_ref(foo_ref, src);\n\n        // should build\n        //\n        //     root\n        //       `- foo <- foo_ref\n\n        test_edges(\n            &s.graph,\n            expect![[r#\"\n            00..03 --DefToScope-> 00..20\n            05..08 --RefToDef-> 00..03\n        \"#]],\n        )\n    }\n\n    #[test]\n    fn insert_ref_namespaced() {\n        let mut s = ScopeGraph::new(r(0, 50), DUMMY_LANG_ID);\n\n        // we assume the following namespaces:\n        // - 0: [ 0: function, 1: method, 2: getter ]\n        // - 1: [ 0: var       1: const,  2: static ]\n        //\n        // defs from namespace 0 should be unreachable from\n        // refs from namespace 1 and vice-versa\n\n        // create two defs:\n        // - fn foo\n        // - var foo\n        //\n        // every function call is annotated with the `function` symbol\n        // every variable ref  is annotated with the `var`      symbol\n        // every const ref     is annotated with the `const`    symbol\n        let src = r#\"fn foo() {};\nvar foo;\nfoo();\nfoo + 1;\n[0; foo]\"#\n            .as_bytes();\n\n        // function ∈ {namespace=0, symbol=0}\n        let foo_func_def = {\n            let mut d = definition(3, 6);\n            d.symbol_id = Some(SymbolId {\n                namespace_idx: 0,\n                symbol_idx: 0,\n            });\n            d\n        };\n\n        // var ∈ {namespace=1, symbol=0}\n        let foo_var_def = {\n            let mut d = definition(17, 20);\n            d.symbol_id = Some(SymbolId {\n                namespace_idx: 1,\n                symbol_idx: 0,\n            });\n            d\n        };\n\n        // function ∈ {namespace=0, symbol=0}\n        let foo_func_ref = {\n            let mut r = reference(22, 25);\n            r.symbol_id = Some(SymbolId {\n                namespace_idx: 0,\n                symbol_idx: 0,\n            });\n            r\n        };\n\n        // var ∈ {namespace=1, symbol=0}\n        let foo_var_ref = {\n            let mut r = reference(29, 32);\n            r.symbol_id = Some(SymbolId {\n                namespace_idx: 1,\n                symbol_idx: 0,\n            });\n            r\n        };\n\n        // const ∈ {namespace=1, symbol=1}\n        let foo_const_ref = {\n            let mut r = reference(42, 45);\n            r.symbol_id = Some(SymbolId {\n                namespace_idx: 1,\n                symbol_idx: 1,\n            });\n            r\n        };\n\n        s.insert_local_def(foo_func_def);\n        s.insert_local_def(foo_var_def);\n        s.insert_ref(foo_func_ref, src);\n        s.insert_ref(foo_var_ref, src);\n        s.insert_ref(foo_const_ref, src);\n\n        // should build\n        //\n        //     root\n        //       `- foo_func <- foo_func_ref\n        //       `- foo_var <- foo_var_ref, foo_const_ref\n\n        test_edges(\n            &s.graph,\n            expect![[r#\"\n                03..06 --DefToScope-> 00..50\n                17..20 --DefToScope-> 00..50\n                22..25 --RefToDef-> 03..06\n                29..32 --RefToDef-> 17..20\n                42..45 --RefToDef-> 17..20\n            \"#]],\n        )\n    }\n\n    #[test]\n    fn insert_ref_no_namespace() {\n        let mut s = ScopeGraph::new(r(0, 50), DUMMY_LANG_ID);\n\n        // modeling the following code:\n        //\n        //     fn foo() {}\n        //     var foo;\n        //\n        //     foo + 1\n        //\n        // `foo` should refer to both, `fn foo` and `var foo`,\n        // the lack of namespacing should raise both defs as\n        // possible defs.\n        //\n        // once again, we assume the following namespaces:\n        // - 0: [ 0: function, 1: method, 2: getter ]\n        // - 1: [ 0: var       1: const,  2: static ]\n\n        // function ∈ {namespace=0, symbol=0}\n        let foo_func_def = {\n            let mut d = definition(3, 6);\n            d.symbol_id = Some(SymbolId {\n                namespace_idx: 0,\n                symbol_idx: 0,\n            });\n            d\n        };\n\n        // var ∈ {namespace=1, symbol=0}\n        let foo_var_def = {\n            let mut d = definition(17, 20);\n            d.symbol_id = Some(SymbolId {\n                namespace_idx: 1,\n                symbol_idx: 0,\n            });\n            d\n        };\n\n        let foo_ambiguous_ref = reference(23, 26);\n\n        let src = r#\"fn foo() {};\nvar foo;\n\nfoo + 1\"#\n            .as_bytes();\n\n        s.insert_local_def(foo_func_def);\n        s.insert_local_def(foo_var_def);\n        s.insert_ref(foo_ambiguous_ref, src);\n\n        // should build;\n        //\n        //    root\n        //      `- foo_func_def <- foo_ambiguous_ref\n        //      `- foo_var_def  <- foo_ambiguous_ref\n\n        test_edges(\n            &s.graph,\n            expect![[r#\"\n            03..06 --DefToScope-> 00..50\n            17..20 --DefToScope-> 00..50\n            23..26 --RefToDef-> 17..20\n            23..26 --RefToDef-> 03..06\n        \"#]],\n        )\n    }\n\n    #[test]\n    fn hoverable_ranges() {\n        let mut s = ScopeGraph::new(r(0, 50), DUMMY_LANG_ID);\n\n        // modeling the following code:\n        //\n        //    let t = 2;\n        //    t + 1;\n        //\n        // contains 1 def, 1 ref\n\n        let src = \"let t = 2;\\nt + 1;\".as_bytes();\n\n        let t_def = definition(4, 5);\n        let t_ref = reference(11, 12);\n\n        s.insert_local_def(t_def);\n        s.insert_ref(t_ref, src);\n\n        let hoverable_ranges = s.hoverable_ranges().collect::<Vec<_>>();\n        assert_eq!(hoverable_ranges, vec![r(4, 5), r(11, 12)])\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/intelligence.rs",
    "content": "pub mod code_navigation;\nmod language;\nmod namespace;\nmod scope_resolution;\n\npub use {\n    language::{Language, MemoizedQuery, TSLanguage, TSLanguageConfig, ALL_LANGUAGES},\n    namespace::*,\n    scope_resolution::{NodeKind, ScopeGraph},\n};\n\nuse scope_resolution::ResolutionMethod;\nuse tree_sitter::{Parser, Tree};\n\n/// A tree-sitter representation of a file\npub struct TreeSitterFile<'a> {\n    /// The original source that was used to generate this file.\n    src: &'a [u8],\n\n    /// The syntax tree of this file.\n    tree: Tree,\n\n    /// The supplied language for this file.\n    language: &'static TSLanguageConfig,\n}\n\n#[derive(Debug)]\npub enum TreeSitterFileError {\n    UnsupportedLanguage,\n    ParseTimeout,\n    LanguageMismatch,\n    QueryError(tree_sitter::QueryError),\n    FileTooLarge,\n}\n\nimpl<'a> TreeSitterFile<'a> {\n    /// Create a TreeSitterFile out of a sourcefile\n    pub fn try_build(src: &'a [u8], lang_id: &str) -> Result<Self, TreeSitterFileError> {\n        // no scope-res for files larger than 500kb\n        if src.len() > 500 * 10usize.pow(3) {\n            return Err(TreeSitterFileError::FileTooLarge);\n        }\n\n        let language = match TSLanguage::from_id(lang_id) {\n            Language::Supported(language) => Ok(language),\n            Language::Unsupported => Err(TreeSitterFileError::UnsupportedLanguage),\n        }?;\n\n        let mut parser = Parser::new();\n        parser\n            .set_language((language.grammar)())\n            .map_err(|_| TreeSitterFileError::LanguageMismatch)?;\n\n        // do not permit files that take >1s to parse\n        parser.set_timeout_micros(10u64.pow(6));\n\n        let tree = parser\n            .parse(src, None)\n            .ok_or(TreeSitterFileError::ParseTimeout)?;\n\n        Ok(Self {\n            src,\n            tree,\n            language,\n        })\n    }\n\n    pub fn hoverable_ranges(\n        self,\n    ) -> Result<Vec<crate::text_range::TextRange>, TreeSitterFileError> {\n        let query = self\n            .language\n            .hoverable_query\n            .query(self.language.grammar)\n            .map_err(TreeSitterFileError::QueryError)?;\n        let root_node = self.tree.root_node();\n        let mut cursor = tree_sitter::QueryCursor::new();\n        Ok(cursor\n            .matches(query, root_node, self.src)\n            .flat_map(|m| m.captures)\n            .map(|c| c.node.range().into())\n            .collect::<Vec<_>>())\n    }\n\n    /// Produce a lexical scope-graph for this TreeSitterFile.\n    pub fn scope_graph(self) -> Result<ScopeGraph, TreeSitterFileError> {\n        let query = self\n            .language\n            .scope_query\n            .query(self.language.grammar)\n            .map_err(TreeSitterFileError::QueryError)?;\n        let root_node = self.tree.root_node();\n\n        Ok(ResolutionMethod::Generic.build_scope(query, root_node, self.src, self.language))\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/lib.rs",
    "content": "#![deny(\n    clippy::all,\n    arithmetic_overflow,\n    future_incompatible,\n    nonstandard_style,\n    rust_2018_idioms,\n    unused_lifetimes,\n    unused_qualifications\n)]\n#![warn(unused_crate_dependencies)]\n#![allow(elided_lifetimes_in_paths, clippy::diverging_sub_expression)]\n\n#[cfg(all(feature = \"onnx\", feature = \"metal\"))]\ncompile_error!(\"cannot enable `onnx` and `metal` at the same time\");\n\n// only used in the binary\n#[cfg(feature = \"color-eyre\")]\nuse color_eyre as _;\n\nuse db::SqlDb;\n#[cfg(any(bench, test))]\nuse git_version as _;\n\n#[cfg(all(feature = \"debug\", not(tokio_unstable)))]\nuse console_subscriber as _;\n\nuse remotes::github;\nuse secrecy::ExposeSecret;\nuse state::PersistedState;\nuse std::fs::canonicalize;\nuse user::UserProfile;\n\nuse crate::{\n    background::SyncQueue, indexes::Indexes, semantic::Semantic, state::RepositoryPool,\n    webserver::middleware::User,\n};\nuse anyhow::{Context, Result};\n\nuse once_cell::sync::OnceCell;\n\nuse std::{path::Path, sync::Arc};\nuse tracing::{debug, error, info, warn};\nuse tracing_subscriber::{\n    filter::{LevelFilter, Targets},\n    fmt,\n    prelude::*,\n    EnvFilter,\n};\n\nmod agent;\nmod background;\nmod cache;\nmod collector;\nmod commits;\nmod config;\nmod db;\nmod env;\nmod llm;\nmod remotes;\nmod repo;\nmod scraper;\nmod webserver;\n\npub mod indexes;\npub mod intelligence;\npub mod periodic;\npub mod query;\npub mod semantic;\npub mod snippet;\npub mod state;\npub mod symbol;\npub mod text_range;\npub mod user;\n\npub use config::{default_parallelism, minimum_parallelism, Configuration};\npub use env::Environment;\n\nconst LOG_ENV_VAR: &str = \"BLOOP_LOG\";\nstatic LOGGER_INSTALLED: OnceCell<bool> = OnceCell::new();\nstatic LOGGER_GUARD: OnceCell<tracing_appender::non_blocking::WorkerGuard> = OnceCell::new();\n\n/// The global state\n#[derive(Clone)]\npub struct Application {\n    /// Environmental restrictions on the app\n    env: Environment,\n\n    /// User-provided configuration\n    pub config: Arc<Configuration>,\n\n    /// Repositories managed by Bloop\n    repo_pool: RepositoryPool,\n\n    /// Background & maintenance tasks are executed on a separate\n    /// executor\n    sync_queue: SyncQueue,\n\n    /// Semantic search subsystem\n    semantic: Semantic,\n\n    /// Tantivy indexes\n    indexes: Arc<Indexes>,\n\n    /// Remote backend credentials\n    credentials: PersistedState<remotes::Backends>,\n\n    /// Store for user profiles\n    user_profiles: PersistedState<scc::HashMap<String, UserProfile>>,\n\n    /// SQL database for persistent storage\n    pub sql: SqlDb,\n}\n\nimpl Application {\n    pub async fn initialize(env: Environment, mut config: Configuration) -> Result<Application> {\n        debug!(\"This is where we are\");\n        config.max_threads = config.max_threads.max(minimum_parallelism());\n        let threads = config.max_threads;\n\n        // 15MiB buffer size is minimum for Tantivy\n        config.buffer_size = config.buffer_size.max(threads * 15_000_000);\n        config.repo_buffer_size = config.repo_buffer_size.max(threads * 15_000_000);\n        config.source.set_default_dir(&config.index_dir);\n\n        // Finalize config\n        let config = Arc::new(config);\n        info!(?config, \"effective configuration\");\n\n        // Load repositories\n        let repo_pool = config.source.initialize_pool()?;\n\n        // Databases & indexes\n        let sql = Arc::new(db::initialize(&config).await?);\n        let semantic =\n            Semantic::initialize(&config.model_dir, &config.qdrant_url, Arc::clone(&config))\n                .await\n                .context(\"qdrant initialization failed\")?;\n\n        // Wipe existing dbs & caches if the schema has changed\n        let mut was_index_reset = false;\n        if config.source.index_version_mismatch() {\n            debug!(\"schema version mismatch, resetting state\");\n            was_index_reset = true;\n            Indexes::reset_databases(&config)?;\n            debug!(\"tantivy indexes deleted\");\n\n            cache::FileCache::new(sql.clone(), semantic.clone())\n                .reset(&repo_pool)\n                .await?;\n            debug!(\"caches deleted\");\n\n            semantic.reset_collection_blocking().await?;\n            debug!(\"semantic indexes deleted\");\n            debug!(\"state reset complete\");\n        }\n\n        config.source.save_index_version()?;\n        debug!(\"index version saved\");\n\n        let indexes = Indexes::new(&config, sql.clone(), was_index_reset)\n            .await?\n            .into();\n        info!(\"indexes initialized\");\n\n        Ok(Self {\n            sync_queue: SyncQueue::start(config.clone()),\n            credentials: config\n                .source\n                .load_state_or(\"credentials\", remotes::Backends::default())?,\n            user_profiles: config.source.load_or_default(\"user_profiles\")?,\n            sql,\n            indexes,\n            repo_pool,\n            semantic,\n            config,\n            env,\n        })\n    }\n\n    pub fn install_logging(config: &Configuration) {\n        if let Some(true) = LOGGER_INSTALLED.get() {\n            return;\n        }\n\n        if !tracing_subscribe(config) {\n            warn!(\"Failed to install tracing_subscriber. There's probably one already...\");\n        };\n\n        let hook = std::panic::take_hook();\n        std::panic::set_hook(Box::new(move |info| {\n            tracing::error!(\"panic occurred: {info}\");\n            hook(info);\n        }));\n\n        LOGGER_INSTALLED.set(true).unwrap();\n    }\n\n    pub async fn run(self) -> Result<()> {\n        Self::install_logging(&self.config);\n\n        self.credentials.set_github(github::Auth::new(\n            self.config.github_access_token.clone().unwrap(),\n        ));\n        if let Err(err) = self.credentials.store() {\n            error!(?err, \"failed to save credentials to disk\");\n        }\n\n        let mut joins = tokio::task::JoinSet::new();\n\n        if self.config.index_only {\n            joins.spawn(self.write_index().startup_scan());\n        } else {\n            if !self.config.disable_background {\n                periodic::start_background_jobs(self.clone());\n            }\n\n            joins.spawn(webserver::start(self));\n        }\n\n        while let Some(result) = joins.join_next().await {\n            if let Ok(Err(err)) = result {\n                error!(?err, \"bleep failure\");\n                return Err(err);\n            }\n        }\n\n        Ok(())\n    }\n\n    fn allow_path(&self, path: impl AsRef<Path>) -> bool {\n        if self.env.allow(env::Feature::AnyPathScan) {\n            return true;\n        }\n\n        if self.env.allow(env::Feature::SafePathScan) {\n            let source_dir = self.config.source.directory();\n            return state::get_relative_path(path.as_ref(), &source_dir).starts_with(&source_dir);\n        }\n\n        false\n    }\n\n    pub async fn username(&self) -> Option<String> {\n        self.credentials.github().unwrap().username().await.ok()\n    }\n\n    pub(crate) async fn user(&self) -> User {\n        self.username()\n            .await\n            .zip(self.credentials.github())\n            .and_then(|(user, gh)| {\n                self.config\n                    .github_access_token\n                    .as_ref()\n                    .map(|token| User::Desktop {\n                        access_token: token.expose_secret().clone(),\n                        login: user,\n                        crab: Arc::new(move || Ok(gh.client()?)),\n                    })\n            })\n            .unwrap_or_else(|| User::Unknown)\n    }\n\n    fn write_index(&self) -> background::BoundSyncQueue {\n        background::BoundSyncQueue(self.clone())\n    }\n}\n\nfn tracing_subscribe(config: &Configuration) -> bool {\n    let env_filter_layer = fmt::layer().with_filter(EnvFilter::from_env(LOG_ENV_VAR));\n    let log_writer_layer = (!config.disable_log_write).then(|| {\n        let file_appender = tracing_appender::rolling::daily(config.log_dir(), \"bloop.log\");\n        let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);\n        _ = LOGGER_GUARD.set(guard);\n        fmt::layer()\n            .with_writer(non_blocking)\n            .with_ansi(false)\n            .with_filter(\n                Targets::new()\n                    .with_target(\"bleep\", LevelFilter::DEBUG)\n                    .with_target(\"bleep::indexes::file\", LevelFilter::WARN)\n                    .with_target(\"bleep::semantic\", LevelFilter::DEBUG)\n                    .with_target(\"bloop::qdrant\", LevelFilter::INFO),\n            )\n    });\n\n    #[cfg(all(tokio_unstable, feature = \"debug\"))]\n    let console_subscriber_layer = Some(console_subscriber::spawn());\n    #[cfg(not(all(tokio_unstable, feature = \"debug\")))]\n    let console_subscriber_layer: Option<Box<dyn tracing_subscriber::Layer<_> + Send + Sync>> =\n        None;\n\n    tracing_subscriber::registry()\n        .with(log_writer_layer)\n        .with(env_filter_layer)\n        .with(console_subscriber_layer)\n        .try_init()\n        .is_ok()\n}\n"
  },
  {
    "path": "server/bleep/src/llm/call.rs",
    "content": "use std::{fmt, sync::Arc, time::Duration};\n\nuse async_stream::try_stream;\nuse futures_util::{pin_mut, Stream, StreamExt, TryStreamExt};\nuse reqwest_eventsource::EventSource;\nuse tokio::sync::Mutex;\nuse tracing::error;\n\nuse super::client::api;\n\nconst MAX_TOKEN_DURATION: Duration = Duration::from_secs(16);\n\n#[derive(Debug, serde::Serialize, serde::Deserialize)]\nstruct ChatCompletion {\n    choices: Vec<ChatChoice>,\n}\n\n#[derive(Debug, serde::Serialize, serde::Deserialize)]\nstruct ChatChoice {\n    name: Option<String>,\n    delta: serde_json::Map<String, serde_json::Value>,\n}\n\n#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]\n#[serde(rename_all = \"snake_case\")]\npub enum Delta {\n    Content(Option<String>),\n    FunctionCall(FunctionCallDelta),\n}\n\nimpl fmt::Display for Delta {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        match self {\n            Self::Content(Some(opt)) => write!(f, \"{opt}\"),\n            Self::Content(None) => write!(f, \"null\"),\n            Self::FunctionCall(delta) => {\n                let json = serde_json::to_string(&delta).map_err(|_| fmt::Error)?;\n                write!(f, \"{json}\")\n            }\n        }\n    }\n}\n\n#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]\npub struct FunctionCallDelta {\n    name: Option<String>,\n    #[serde(default)]\n    arguments: String,\n}\n\n#[derive(Debug, serde::Serialize, serde::Deserialize)]\nstruct ChatMessage {\n    content: Option<String>,\n}\n\n#[derive(Debug, serde::Serialize, serde::Deserialize)]\nstruct OpenAiMessage {\n    role: String,\n    content: String,\n}\n\n#[derive(serde::Serialize, Debug)]\nstruct OpenAiRequest {\n    model: String,\n    messages: Vec<api::Message>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    functions: Option<Vec<api::Function>>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    function_call: Option<String>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    max_tokens: Option<u32>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    presence_penalty: Option<f32>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    frequency_penalty: Option<f32>,\n    temperature: f32,\n    stream: bool,\n}\n\npub async fn llm_call(\n    req: api::LLMRequest,\n) -> anyhow::Result<impl Stream<Item = Result<Delta, api::Error>>> {\n    // print req.messages.messages\n    for message in &req.messages.messages {\n        println!(\"OpenAiMessage: {:?}\", message);\n    }\n\n    let model = match req.model.as_deref() {\n        Some(model) => model.to_owned(),\n        None => \"gpt-4o\".into(),\n    };\n    //filter out Null values from messages\n\n    let builder = {\n        let request = OpenAiRequest {\n            messages: req.messages.messages.clone(),\n            function_call: if req.functions.is_some() {\n                Some(\"auto\".to_string()) // `auto` allows the model to respond dynamically with a function call\n            } else {\n                None\n            },\n            functions: req.functions.as_ref().map(|t| &t.functions).cloned(),\n            model: model.clone(),\n            max_tokens: req.max_tokens,\n            temperature: req.temperature.unwrap_or(0.0),\n            presence_penalty: req.presence_penalty,\n            frequency_penalty: req.frequency_penalty,\n            stream: true,\n        };\n\n        reqwest::Client::new()\n            .post(\"https://api.openai.com/v1/chat/completions\")\n            .bearer_auth(req.openai_key.clone())\n            .json(&request)\n    };\n\n    // This should never fail, as our request body is not a stream.\n    let mut response = EventSource::new(builder).expect(\"failed to build request\");\n    response.set_retry_policy(Box::new(reqwest_eventsource::retry::Never));\n\n    match response.next().await {\n        Some(Ok(reqwest_eventsource::Event::Open)) => {}\n        Some(Err(reqwest_eventsource::Error::InvalidStatusCode(status, _))) => {\n            error!(\"{}\", &status);\n            if status == 429 {\n                response.close();\n                println!(\"Rate limit exceeded, try again after 5s\");\n                tokio::time::sleep(Duration::from_secs(5)).await;\n                let openai_key = req.openai_key.clone();\n                return Box::pin(llm_call(api::LLMRequest {\n                    openai_key,\n                    ..req.clone()\n                })).await;\n            }\n\n            return Err(api::Error::BadOpenAiRequest.into());\n        }\n        Some(Err(e)) => {\n            error!(\"{}\", e);\n            return Err(api::Error::BadOpenAiRequest.into());\n        }\n        _ => return Err(api::Error::BadOpenAiRequest.into()),\n    }\n\n    let previous = Arc::new(Mutex::new(\"\".to_string()));\n\n    let message_stream = try_stream! {\n        for await result in response {\n            // The `reqwest_eventsource` library uses errors to signal a successful stream close,\n            // so we make sure to avoid passing down this close message as an error.\n            if matches!(result, Err(reqwest_eventsource::Error::StreamEnded)) {\n                break;\n            }\n\n            let msg = match result {\n                Ok(reqwest_eventsource::Event::Message(msg)) => msg,\n                Ok(_) => Err(api::Error::BadOpenAiRequest)?,\n                Err(e) => {\n                    error!(\"{}\", e);\n                    Err(api::Error::BadOpenAiRequest)?\n                }\n            };\n\n            if msg.data == \"[DONE]\" {\n                break;\n            }\n\n            yield msg.data;\n        }\n    }\n    .map_ok(move |d| (d, Arc::clone(&previous)))\n    .try_filter_map(move |(msg_data, _)| async move {\n        let mut data: ChatCompletion = serde_json::from_str(&msg_data).map_err(|e| {\n            error!(%msg_data, \"{}\", e);\n            api::Error::BadOpenAiRequest\n        })?;\n\n        match data.choices.first_mut() {\n            Some(ChatChoice { delta, .. }) if delta.is_empty() => Ok(None),\n            Some(ChatChoice { ref mut delta, .. }) => {\n                // The first message contains a redundant `role` field. We remove it.\n                delta.remove(\"role\");\n\n                if delta.len() == 2 {\n                    delta.remove(\"content\");\n                }\n\n                if delta.contains_key(\"content\") {\n                    if delta.get_key_value(\"content\").unwrap().1.is_null() {\n                        delta.remove(\"content\");\n                    }\n                }\n\n                if delta.contains_key(\"refusal\") {\n                    if delta.get_key_value(\"refusal\").unwrap().1.is_null() {\n                        delta.remove(\"refusal\");\n                    }\n                }\n\n                if delta.is_empty() {\n                    return Ok(None);\n                }\n\n                let delta = serde_json::from_value(delta.clone().into()).map_err(|e| {\n                    error!(?delta, \"{}\", e);\n                    api::Error::BadOpenAiRequest\n                })?;\n\n                match delta {\n                    Delta::Content(Some(content)) if content.is_empty() => Ok(None),\n                    delta @ Delta::Content(Some(_)) => Ok(Some(delta)),\n                    Delta::FunctionCall(_) => Ok(Some(delta)),\n                    Delta::Content(None) => Ok(None),\n                }\n            }\n            _ => Ok(None),\n        }\n    });\n\n    let stream = try_stream! {\n        // We modify the message stream to include a timeout.\n        let message_stream = tokio_stream::StreamExt::timeout(message_stream, MAX_TOKEN_DURATION)\n            .map(|r| r.map_err(|_| api::Error::TokenDelayTooLarge).and_then(|r2| r2));\n\n        pin_mut!(message_stream);\n\n        for await result in message_stream {\n            let delta: Delta = result?;\n            yield delta;\n        }\n    };\n\n    Ok(stream)\n}\n"
  },
  {
    "path": "server/bleep/src/llm/client.rs",
    "content": "use std::time::Duration;\n\nuse anyhow::{anyhow, bail};\nuse futures::{Stream, StreamExt};\nuse secrecy::ExposeSecret;\nuse tracing::{debug, error, warn};\n\nuse super::call::llm_call;\nuse crate::{periodic::sync_github_status_once, Application};\n\npub mod api {\n    use std::collections::HashMap;\n\n    #[derive(Debug, Default, Clone, PartialEq, serde::Serialize, serde::Deserialize)]\n    pub struct FunctionCall {\n        pub name: Option<String>,\n        pub arguments: String,\n    }\n\n    #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]\n    pub struct Function {\n        pub name: String,\n        pub description: String,\n        pub parameters: Parameters,\n    }\n\n    #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]\n    pub struct Parameters {\n        #[serde(rename = \"type\")]\n        pub _type: String,\n        pub properties: HashMap<String, Parameter>,\n        pub required: Vec<String>,\n    }\n\n    #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]\n    pub struct Parameter {\n        #[serde(rename = \"type\")]\n        pub _type: String,\n        #[serde(skip_serializing_if = \"Option::is_none\")]\n        pub description: Option<String>,\n        #[serde(skip_serializing_if = \"Option::is_none\")]\n        pub items: Option<Box<Parameter>>,\n    }\n    #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]\n    #[serde(untagged)]\n    pub enum Message {\n        FunctionReturn {\n            role: String,\n            name: String,\n            content: String,\n        },\n        FunctionCall {\n            role: String,\n            function_call: FunctionCall,\n            content: (),\n        },\n        // NB: This has to be the last variant as this enum is marked `#[serde(untagged)]`, so\n        // deserialization will always try this variant last. Otherwise, it is possible to\n        // accidentally deserialize a `FunctionReturn` value as `PlainText`.\n        PlainText {\n            role: String,\n            content: String,\n        },\n    }\n\n    #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]\n    pub struct Messages {\n        pub messages: Vec<Message>,\n    }\n\n    #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]\n    pub struct Functions {\n        pub functions: Vec<Function>,\n    }\n\n    #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]\n    pub struct LLMRequest {\n        pub openai_key: String,\n        pub messages: Messages,\n        pub functions: Option<Functions>,\n        pub max_tokens: Option<u32>,\n        pub temperature: Option<f32>,\n        pub presence_penalty: Option<f32>,\n        pub frequency_penalty: Option<f32>,\n        pub model: Option<String>,\n        #[serde(default)]\n        pub extra_stop_sequences: Vec<String>,\n    }\n\n    #[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]\n    #[serde(rename_all = \"lowercase\")]\n    pub enum FunctionCallOptions {\n        Auto,\n        None,\n    }\n\n    #[derive(thiserror::Error, Debug, serde::Deserialize, serde::Serialize)]\n    pub enum Error {\n        #[error(\"bad OpenAI request\")]\n        BadOpenAiRequest,\n\n        #[error(\"incorrect configuration\")]\n        BadConfiguration,\n\n        #[error(\"waiting for the next token took longer than allowed\")]\n        TokenDelayTooLarge,\n    }\n}\n\nimpl api::Message {\n    pub fn new_text(role: &str, content: &str) -> Self {\n        Self::PlainText {\n            role: role.to_owned(),\n            content: content.to_owned(),\n        }\n    }\n\n    pub fn system(content: &str) -> Self {\n        Self::new_text(\"system\", content)\n    }\n\n    pub fn user(content: &str) -> Self {\n        Self::new_text(\"user\", content)\n    }\n\n    pub fn assistant(content: &str) -> Self {\n        Self::new_text(\"assistant\", content)\n    }\n\n    pub fn function_call(call: &api::FunctionCall) -> Self {\n        Self::FunctionCall {\n            role: \"assistant\".to_string(),\n            function_call: call.clone(),\n            content: (),\n        }\n    }\n\n    pub fn function_return(name: &str, content: &str) -> Self {\n        Self::FunctionReturn {\n            role: \"function\".to_string(),\n            name: name.to_string(),\n            content: content.to_string(),\n        }\n    }\n}\n\nimpl From<&api::Message> for tiktoken_rs::ChatCompletionRequestMessage {\n    fn from(m: &api::Message) -> tiktoken_rs::ChatCompletionRequestMessage {\n        match m {\n            api::Message::PlainText { role, content } => {\n                tiktoken_rs::ChatCompletionRequestMessage {\n                    role: role.clone(),\n                    content: Some(content.clone()),\n                    name: None,\n                    function_call: None,\n                }\n            }\n            api::Message::FunctionReturn {\n                role,\n                name,\n                content,\n            } => tiktoken_rs::ChatCompletionRequestMessage {\n                role: role.clone(),\n                content: Some(content.clone()),\n                name: Some(name.clone()),\n                function_call: None,\n            },\n            api::Message::FunctionCall {\n                role,\n                function_call,\n                content: _,\n            } => tiktoken_rs::ChatCompletionRequestMessage {\n                role: role.clone(),\n                content: None,\n                name: None,\n                function_call: Some(tiktoken_rs::FunctionCall {\n                    name: function_call\n                        .name\n                        .clone()\n                        .expect(\"FunctionCall has no name\"),\n                    arguments: function_call.arguments.clone(),\n                }),\n            },\n        }\n    }\n}\n\nenum ChatError {\n    BadRequest(String),\n    #[allow(dead_code)]\n    TooManyRequests(String),\n    #[allow(dead_code)]\n    InvalidToken,\n    Other(anyhow::Error),\n}\n\nimpl From<anyhow::Error> for ChatError {\n    fn from(error: anyhow::Error) -> Self {\n        ChatError::BadRequest(error.to_string())\n    }\n}\n\n#[derive(Clone)]\npub struct Client {\n    app: Application,\n    pub max_retries: u32,\n    pub temperature: Option<f32>,\n    pub max_tokens: Option<u32>,\n    pub presence_penalty: Option<f32>,\n    pub frequency_penalty: Option<f32>,\n    pub model: Option<String>,\n}\n\nimpl Client {\n    pub fn new(app: Application) -> Self {\n        Self {\n            app,\n            max_retries: 5,\n            temperature: None,\n            max_tokens: None,\n            presence_penalty: None,\n            frequency_penalty: None,\n            model: None,\n        }\n    }\n\n    pub fn model(mut self, model: &str) -> Self {\n        if model.is_empty() {\n            self.model = None;\n        } else {\n            self.model = Some(model.to_owned());\n        }\n\n        self\n    }\n\n    #[allow(unused)]\n    pub fn frequency_penalty(mut self, frequency: impl Into<Option<f32>>) -> Self {\n        self.frequency_penalty = frequency.into();\n        self\n    }\n\n    #[allow(unused)]\n    pub fn presence_penalty(mut self, presence_penalty: impl Into<Option<f32>>) -> Self {\n        self.presence_penalty = presence_penalty.into();\n        self\n    }\n\n    pub fn temperature(mut self, temperature: impl Into<Option<f32>>) -> Self {\n        self.temperature = temperature.into();\n        self\n    }\n\n    #[allow(unused)]\n    pub fn max_tokens(mut self, max_tokens: impl Into<Option<u32>>) -> Self {\n        self.max_tokens = max_tokens.into();\n        self\n    }\n\n    pub async fn chat(\n        &self,\n        messages: &[api::Message],\n        functions: Option<&[api::Function]>,\n    ) -> anyhow::Result<String> {\n        const TOTAL_CHAT_RETRIES: usize = 5;\n\n        'retry_loop: for _ in 0..TOTAL_CHAT_RETRIES {\n            let mut buf = String::new();\n            let stream = self.chat_stream(messages, functions).await?;\n            tokio::pin!(stream);\n\n            loop {\n                match stream.next().await {\n                    None => break,\n                    Some(Ok(s)) => buf += &s,\n                    Some(Err(e)) => {\n                        warn!(?e, \"token stream errored out, retrying...\");\n                        continue 'retry_loop;\n                    }\n                }\n            }\n\n            return Ok(buf);\n        }\n\n        Err(anyhow!(\n            \"chat stream errored too many times, failed to generate response\"\n        ))\n    }\n\n    pub async fn chat_stream(\n        &self,\n        messages: &[api::Message],\n        functions: Option<&[api::Function]>,\n    ) -> anyhow::Result<impl Stream<Item = anyhow::Result<String>>> {\n        const INITIAL_DELAY: Duration = Duration::from_millis(100);\n        const SCALE_FACTOR: f32 = 1.5;\n\n        let mut delay = INITIAL_DELAY;\n        for _ in 0..self.max_retries {\n            match self.chat_stream_oneshot(messages, functions).await {\n                Err(ChatError::TooManyRequests(_)) => {\n                    warn!(?delay, \"too many LLM requests, retrying with delay...\");\n                    tokio::time::sleep(delay).await;\n                    delay = Duration::from_millis((delay.as_millis() as f32 * SCALE_FACTOR) as u64);\n                }\n                Err(ChatError::BadRequest(body)) => {\n                    // We log the messages in a separate `debug!` statement so that they can be\n                    // filtered out, due to their verbosity.\n                    debug!(\"LLM message list: {messages:?}\");\n                    error!(\"LLM request failed, request not eligible for retry: {body}\");\n                    bail!(\"request failed (not eligible for retry): {body}\");\n                }\n                Err(ChatError::InvalidToken) => {\n                    warn!(\"invalid token, retrying LLM request\");\n                    sync_github_status_once(&self.app).await;\n                }\n                Err(ChatError::Other(e)) => {\n                    // We log the messages in a separate `debug!` statement so that they can be\n                    // filtered out, due to their verbosity.\n                    debug!(\"LLM message list: {messages:?}\");\n                    error!(\"LLM request failed due to unknown reason: {e:?}\");\n                    return Err(e);\n                }\n                Ok(stream) => return Ok(stream),\n            }\n        }\n\n        bail!(\"request failed {} times\", self.max_retries)\n    }\n\n    /// Like `chat`, but without exponential backoff.\n    async fn chat_stream_oneshot(\n        &self,\n        messages: &[api::Message],\n        functions: Option<&[api::Function]>,\n    ) -> Result<impl Stream<Item = anyhow::Result<String>>, ChatError> {\n        let mut stream = Box::pin(\n            llm_call(api::LLMRequest {\n                openai_key: self\n                    .app\n                    .config\n                    .openai_api_key\n                    .as_ref()\n                    .expect(\"OpenAI API key not set\")\n                    .expose_secret()\n                    .to_string(),\n                messages: api::Messages {\n                    messages: messages.to_owned(),\n                },\n                functions: functions.map(|funcs| api::Functions {\n                    functions: funcs.to_owned(),\n                }),\n                max_tokens: self.max_tokens,\n                temperature: self.temperature,\n                presence_penalty: self.presence_penalty,\n                frequency_penalty: self.frequency_penalty,\n                model: self.model.clone(),\n                extra_stop_sequences: vec![],\n            })\n            .await?,\n        );\n\n        let first_item = stream.next().await;\n        match first_item {\n            Some(Ok(_)) => {}\n            Some(Err(api::Error::BadOpenAiRequest)) => {\n                warn!(\"bad request to LLM\");\n                return Err(ChatError::BadRequest(\"Bad request to LLM\".into()));\n            }\n            Some(Err(e)) => {\n                warn!(?e, \"LLM request failed\");\n                return Err(ChatError::Other(anyhow!(\n                    \"failed to make event source request to answer API: {e}\",\n                )));\n            }\n            _ => {\n                warn!(\"Failed to open Event Source\");\n                return Err(ChatError::Other(anyhow!(\"event source failed to open\")));\n            }\n        }\n\n        Ok(futures::stream::once(async { first_item.unwrap() })\n            .chain(stream)\n            .filter_map(|result| async move {\n                match result {\n                    Ok(d) => Some(Ok(d)),\n                    Err(e) => Some(Err(e)),\n                }\n            })\n            .map(|result| match result {\n                Ok(s) => Ok(s.to_string()),\n                Err(e) => bail!(\"event source error {e:?}\"),\n            }))\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/llm.rs",
    "content": "pub mod call;\npub mod client;\n"
  },
  {
    "path": "server/bleep/src/periodic/logrotate.rs",
    "content": "use std::collections::HashSet;\n\nuse chrono::{Duration, Utc};\nuse rand::{distributions, thread_rng, Rng};\nuse rayon::prelude::{IntoParallelRefIterator, ParallelIterator};\nuse tracing::{error, info};\n\nuse crate::{query::parser, repo::BranchFilterConfig, state::RepositoryPool};\n\npub(crate) async fn log_and_branch_rotate(app: crate::Application) {\n    let log = crate::db::QueryLog::new(&app.sql);\n    loop {\n        let jitter = thread_rng().sample(distributions::Uniform::new(100, 300));\n        tokio::time::sleep(\n            tokio::time::Duration::from_secs(3600) + tokio::time::Duration::from_secs(jitter),\n        )\n        .await;\n\n        let cutoff = Utc::now() - Duration::days(1);\n        let queries = log.since(cutoff).await.unwrap();\n\n        let used_branches = collect_branches_for_repos(queries);\n        let to_sync = update_branch_filters(used_branches, &app.repo_pool);\n\n        app.write_index().enqueue_all(to_sync).await;\n\n        if let Err(err) = log.prune(cutoff).await {\n            error!(?err, \"failed to prune old log entries\");\n        };\n    }\n}\n\n/// Remove log files older than 7 days.\n///\n/// Runs on startup and every hour thereafter\npub(crate) async fn clear_disk_logs(app: crate::Application) {\n    let log_dir = app.config.log_dir();\n    let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(3600));\n    loop {\n        interval.tick().await;\n        info!(\"removing old logs\");\n\n        let today = Utc::now().date_naive();\n        let allowed_files = (0..7)\n            .map(|offset| today - Duration::days(offset))\n            .map(|d| format!(\"bloop.log.{}\", d.format(\"%Y-%m-%d\")))\n            .collect::<HashSet<_>>();\n\n        if let Ok(mut r) = tokio::fs::read_dir(&log_dir).await {\n            while let Ok(Some(entry)) = r.next_entry().await {\n                if !entry\n                    .file_name()\n                    .to_str()\n                    .map(|f| allowed_files.contains(f))\n                    .unwrap_or_default()\n                {\n                    if tokio::fs::remove_file(entry.path()).await.is_ok() {\n                        info!(\"removed old log file {:?}\", entry.file_name())\n                    } else {\n                        info!(\n                            \"failed to remove log file {:?} ... skipping\",\n                            entry.file_name()\n                        )\n                    };\n                }\n            }\n        }\n    }\n}\n\nfn update_branch_filters(\n    map: scc::HashMap<String, HashSet<String>>,\n    repo_pool: &RepositoryPool,\n) -> Vec<crate::repo::RepoRef> {\n    let mut to_sync = vec![];\n    map.for_each(|repo, branches| {\n        let Ok(reporef) = repo.parse() else {\n            return;\n        };\n\n        repo_pool.update(&reporef, |_, v| {\n            let effective = std::mem::take(branches).into_iter().collect();\n            let new_filter = Some(BranchFilterConfig::Select(effective));\n            if new_filter != v.branch_filter {\n                v.branch_filter = new_filter;\n                to_sync.push(reporef.clone());\n            }\n        });\n    });\n    to_sync\n}\n\nfn collect_branches_for_repos(queries: Vec<String>) -> scc::HashMap<String, HashSet<String>> {\n    let map = scc::HashMap::default();\n    queries.par_iter().for_each(|q| {\n        if let Ok(parsed) = parser::parse_nl(q) {\n            for r in parsed.repos() {\n                for b in parsed.branch() {\n                    map.entry(r.to_string())\n                        .or_insert_with(HashSet::default)\n                        .get_mut()\n                        .insert(b.to_string());\n                }\n            }\n        }\n    });\n    map\n}\n"
  },
  {
    "path": "server/bleep/src/periodic/remotes.rs",
    "content": "use std::{\n    ops::Not,\n    sync::Arc,\n    time::{Duration, SystemTime, UNIX_EPOCH},\n};\n\nuse notify_debouncer_mini::{\n    new_debouncer_opt,\n    notify::{Config, RecommendedWatcher, RecursiveMode},\n    DebounceEventResult, Debouncer,\n};\nuse rand::{distributions, thread_rng, Rng};\nuse tokio::task::JoinHandle;\nuse tracing::{debug, error, info, warn};\n\nuse crate::{\n    repo::{Backend, RepoRef, SyncStatus},\n    Application,\n};\n\nconst POLL_INTERVAL_MINUTE: &[Duration] = &[\n    Duration::from_secs(60),\n    Duration::from_secs(3 * 60),\n    Duration::from_secs(10 * 60),\n    Duration::from_secs(20 * 60),\n    Duration::from_secs(30 * 60),\n];\n\n/// Like `tokio::time::sleep`, but sleeps based on wall clock time rather than uptime.\n///\n/// This internally sleeps in uptime increments of 2 seconds, checking whether the wall clock\n/// duration has passed. We do this to support better updates when a system goes into a suspended\n/// state, because `tokio::time::sleep` does not sleep according to wall clock time on some\n/// systems.\n///\n/// For short sleep durations, this will simply call `tokio::time::sleep`, as drift due to suspend\n/// is not usually relevant here.\nasync fn sleep_systime(duration: Duration) {\n    if duration <= Duration::from_secs(2) {\n        return tokio::time::sleep(duration).await;\n    }\n\n    let start = SystemTime::now();\n\n    loop {\n        tokio::time::sleep(Duration::from_secs(1)).await;\n        let Ok(elapsed) = start.elapsed() else {\n            // There was a drift in system time probably because of\n            // sleep.\n            return;\n        };\n        if elapsed >= duration {\n            return;\n        }\n    }\n}\n\npub(crate) async fn sync_github_status(app: Application) {\n    const POLL_PERIOD: Duration = POLL_INTERVAL_MINUTE[0];\n\n    // In case this is a GitHub App installation, we get the\n    // credentials from CLI/config\n    loop {\n        // then retrieve username & other maintenance\n        sync_github_status_once(&app).await;\n        sleep_systime(POLL_PERIOD).await;\n    }\n}\n\npub async fn sync_github_status_once(app: &Application) {\n    update_repo_list(app).await;\n}\n\npub(crate) async fn update_repo_list(app: &Application) {\n    if let Some(gh) = app.credentials.github() {\n        let repos = match gh.current_repo_list().await {\n            Ok(repos) => {\n                debug!(\"fetched new repo list\");\n                repos\n            }\n            Err(err) => {\n                debug!(?err, \"failed to update repo list\");\n                return;\n            }\n        };\n\n        let new = gh.update_repositories(repos);\n        app.credentials.set_github(new);\n    }\n}\n\n#[derive(serde::Serialize, serde::Deserialize)]\nstruct RefreshedAccessToken {\n    access_token: String,\n}\n\npub(crate) async fn check_repo_updates(app: Application) {\n    while app.credentials.github().is_none() {\n        sleep_systime(Duration::from_millis(100)).await\n    }\n\n    let handles: Arc<scc::HashMap<RepoRef, JoinHandle<_>>> = Arc::default();\n    loop {\n        app.repo_pool\n            .scan_async(|reporef, repo| match handles.entry(reporef.to_owned()) {\n                scc::hash_map::Entry::Occupied(value) => {\n                    if value.get().is_finished() {\n                        _ = value.remove_entry();\n                    }\n                }\n                scc::hash_map::Entry::Vacant(vacant) => {\n                    if repo.sync_status.indexable() {\n                        vacant.insert_entry(tokio::spawn(periodic_repo_poll(\n                            app.clone(),\n                            reporef.to_owned(),\n                        )));\n                    }\n                }\n            })\n            .await;\n\n        sleep_systime(Duration::from_secs(5)).await\n    }\n}\n\n// We only return Option<()> here so we can clean up a bunch of error\n// handling code with `?`\n//\n// In reality this doesn't carry any meaning currently\nasync fn periodic_repo_poll(app: Application, reporef: RepoRef) -> Option<()> {\n    debug!(?reporef, \"monitoring repo for changes\");\n    let mut poller = Poller::start(&app, &reporef)?;\n\n    loop {\n        use SyncStatus::*;\n        let (last_index, last_updated, status) = check_repo(&app, &reporef)?;\n        if status.indexable().not() {\n            debug!(?status, \"skipping indexing of repo\");\n            return None;\n        }\n\n        if (UNIX_EPOCH + Duration::from_secs(last_index)) > SystemTime::now() - poller.interval() {\n            app.repo_pool\n                .update_async(&reporef, |_, repo| {\n                    if !matches!(repo.sync_status, Queued) {\n                        repo.pub_sync_status = repo.sync_status.clone();\n                    }\n                })\n                .await;\n\n            // dividing by 10, because this may actually add up to\n            // quite a few minutes\n            tokio::time::sleep(poller.interval() / 10).await;\n            continue;\n        }\n\n        debug!(\"starting sync\");\n        if let Err(err) = app.write_index().block_until_synced(reporef.clone()).await {\n            error!(?err, ?reporef, \"failed to sync & index repo\");\n            return None;\n        }\n\n        debug!(\"sync done\");\n        let (_, updated, status) = check_repo(&app, &reporef)?;\n        if status.indexable().not() {\n            warn!(?status, ?reporef, \"terminating monitoring for repo\");\n            return None;\n        }\n\n        if last_updated == updated && status == Done {\n            let poll_interval = poller.increase_interval();\n\n            debug!(\n                ?reporef,\n                ?poll_interval,\n                \"repo unchanged, increasing backoff\"\n            )\n        } else {\n            let poll_interval = poller.reset_interval();\n\n            debug!(\n                ?reporef,\n                ?last_updated,\n                ?updated,\n                ?poll_interval,\n                \"repo updated\"\n            )\n        }\n\n        match tokio::time::timeout(poller.jittery_interval(), poller.git_change()).await {\n            Ok(_) => debug!(?reporef, \"git changes triggered reindexing\"),\n            Err(_) => debug!(?reporef, \"timeout; reindexing\"),\n        }\n    }\n}\n\nstruct Poller {\n    poll_interval_index: usize,\n    minimum_interval_index: usize,\n    git_events: flume::Receiver<()>,\n    debouncer: Option<Debouncer<RecommendedWatcher>>,\n}\n\nimpl Poller {\n    fn start(app: &Application, reporef: &RepoRef) -> Option<Self> {\n        let mut poll_interval_index = 2;\n        let mut minimum_interval_index = 0;\n\n        let (tx, rx) = flume::bounded(10);\n\n        let mut _debouncer = None;\n        if !app.config.disable_fsevents && reporef.backend() == Backend::Local {\n            let disk_path = app.repo_pool.read(reporef, |_, v| v.disk_path.clone())?;\n\n            let mut debouncer = debounced_events(tx);\n            debouncer\n                .watcher()\n                .watch(&disk_path, RecursiveMode::Recursive)\n                .map_err(|e| {\n                    let d = disk_path.display();\n                    warn!(error = %e, path = %d, \"path does not exist anymore\");\n                })\n                .ok()?;\n            _debouncer = Some(debouncer);\n\n            info!(?reporef, ?disk_path, \"will reindex repo on file changes\");\n\n            poll_interval_index = POLL_INTERVAL_MINUTE.len() - 1;\n            minimum_interval_index = POLL_INTERVAL_MINUTE.len() - 1;\n        }\n\n        Some(Self {\n            poll_interval_index,\n            minimum_interval_index,\n            debouncer: _debouncer,\n            git_events: rx,\n        })\n    }\n\n    fn increase_interval(&mut self) -> Duration {\n        self.poll_interval_index =\n            (self.poll_interval_index + 1).min(POLL_INTERVAL_MINUTE.len() - 1);\n        self.interval()\n    }\n\n    fn reset_interval(&mut self) -> Duration {\n        self.poll_interval_index = self.minimum_interval_index;\n        self.interval()\n    }\n\n    fn interval(&self) -> Duration {\n        POLL_INTERVAL_MINUTE[self.poll_interval_index]\n    }\n\n    fn jittery_interval(&self) -> Duration {\n        let poll_interval = self.interval();\n\n        // add random jitter to avoid contention when jobs start at the same time\n        let jitter = thread_rng().sample(distributions::Uniform::new(\n            10,\n            30 + poll_interval.as_secs() / 2,\n        ));\n        poll_interval + Duration::from_secs(jitter)\n    }\n\n    async fn git_change(&mut self) {\n        if self.debouncer.is_some() {\n            _ = self.git_events.recv_async().await;\n            _ = self.git_events.drain().collect::<Vec<_>>();\n        } else {\n            loop {\n                futures::pending!()\n            }\n        }\n    }\n}\n\nfn check_repo(app: &Application, reporef: &RepoRef) -> Option<(u64, i64, SyncStatus)> {\n    app.repo_pool.read(reporef, |_, repo| {\n        (\n            repo.last_index_unix_secs,\n            repo.last_commit_unix_secs,\n            repo.sync_status.clone(),\n        )\n    })\n}\n\nfn debounced_events(tx: flume::Sender<()>) -> Debouncer<RecommendedWatcher> {\n    new_debouncer_opt(\n        Duration::from_secs(5),\n        None,\n        move |event: DebounceEventResult| match event {\n            Ok(events) if events.is_empty().not() => {\n                if let Err(e) = tx.send(()) {\n                    error!(\"{e}\");\n                }\n            }\n            Ok(_) => debug!(\"no events received from debouncer\"),\n            Err(err) => {\n                error!(?err, \"repository monitoring\");\n            }\n        },\n        Config::default().with_compare_contents(true),\n    )\n    .unwrap()\n}\n"
  },
  {
    "path": "server/bleep/src/periodic.rs",
    "content": "mod logrotate;\nmod remotes;\n\nuse logrotate::*;\npub(crate) use remotes::*;\n\nuse crate::Application;\n\nfn single_threaded_executor<F: std::future::Future<Output = ()> + Send>(\n    app: &Application,\n    f: impl (FnOnce(Application) -> F) + Send + 'static,\n) {\n    let app = app.clone();\n    std::thread::spawn(move || {\n        tokio::runtime::Builder::new_current_thread()\n            .enable_all()\n            .build()\n            .expect(\"failed to start background jobs\")\n            .block_on((f)(app));\n    });\n}\n\npub(crate) fn start_background_jobs(app: Application) {\n    if !app.env.is_cloud_instance() {\n        single_threaded_executor(&app, clear_disk_logs);\n    }\n\n    single_threaded_executor(&app, sync_github_status);\n    single_threaded_executor(&app, check_repo_updates);\n    single_threaded_executor(&app, log_and_branch_rotate);\n}\n"
  },
  {
    "path": "server/bleep/src/query/compiler.rs",
    "content": "use std::{\n    borrow::Cow,\n    collections::{HashMap, HashSet},\n    mem,\n};\n\nuse anyhow::{Context, Result};\nuse compact_str::CompactString;\nuse either::Either;\nuse smallvec::SmallVec;\nuse tantivy::{\n    query::{AllQuery, BooleanQuery, BoostQuery, TermQuery},\n    schema::{Field, IndexRecordOption},\n    Index, Term,\n};\n\nuse crate::query::{\n    parser::{Literal, Query},\n    planner,\n};\n\nconst MAX_CASE_PERMUTATION_LEN: usize = 5;\n\ntype DynQuery = Box<dyn tantivy::query::Query>;\n\nenum Extraction<'a> {\n    /// Match a literal against a tantivy `text` field.\n    Literal(Literal<'a>),\n\n    /// Match a string against a tantivy `bytes` field.\n    ByteString(&'a Cow<'a, str>),\n}\n\n/// A closure that tries to pull out an `Extraction` variant, given a `Query` reference.\ntype Extractor = dyn for<'a> FnMut(&'a Query<'a>) -> Option<Extraction<'a>>;\n\n#[derive(Default)]\npub struct Compiler {\n    priority: HashSet<Field>,\n    extractors: HashMap<Field, Box<Extractor>>,\n}\n\nimpl Compiler {\n    /// Create a new Compiler.\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Mark a list of fields as being high priority in compiled search queries.\n    pub fn priority(mut self, fields: &[Field]) -> Self {\n        self.priority = fields.iter().copied().collect();\n        self\n    }\n\n    /// Add a literal field to the compiler.\n    ///\n    /// This takes a Tantivy `Field`, alongside a closure that returns an `Option<&Literal>` when\n    /// given `&Query`. The compiler will craft a query that matches the literal against the index\n    /// `Field`, using the indexer specified in the Tantivy schema.\n    pub fn literal<F>(mut self, tantivy_field: Field, mut extractor: F) -> Self\n    where\n        F: for<'b> FnMut(&'b Query<'b>) -> Option<Literal<'b>> + 'static,\n    {\n        self.extractors.insert(\n            tantivy_field,\n            Box::new(move |q| extractor(q).map(Extraction::Literal)),\n        );\n\n        self\n    }\n\n    /// Add a byte string field to the compiler.\n    ///\n    /// Matches `Cow<str>` against a tantivy `bytes` field.\n    pub fn byte_string<F>(mut self, tantivy_field: Field, mut extractor: F) -> Self\n    where\n        F: for<'b> FnMut(&'b Query<'b>) -> Option<&'b Cow<'b, str>> + 'static,\n    {\n        self.extractors.insert(\n            tantivy_field,\n            Box::new(move |q| extractor(q).map(Extraction::ByteString)),\n        );\n        self\n    }\n\n    /// Compile a list of queries into a single Tantivy query that matches any\n    /// of them.\n    pub fn compile<'a, I>(mut self, queries: I, index: &Index) -> Result<DynQuery>\n    where\n        I: Iterator<Item = &'a Query<'a>>,\n    {\n        let mut sub_queries: SmallVec<[DynQuery; 2]> = SmallVec::new();\n\n        for query in queries {\n            let mut intersection = Vec::new();\n\n            for (field, extractor) in &mut self.extractors {\n                let Some(extraction) = extractor(query) else {\n                    continue;\n                };\n\n                let field_query = match extraction {\n                    Extraction::Literal(Literal::Plain(text)) => {\n                        let mut tokenizer = index\n                            .tokenizer_for_field(*field)\n                            .context(\"field is missing tokenizer\")?;\n\n                        let mut token_stream = tokenizer.token_stream(&text);\n                        let tokens = std::iter::from_fn(move || {\n                            token_stream.next().map(|tok| CompactString::new(&tok.text))\n                        })\n                        .collect::<Vec<_>>();\n\n                        // We skip case insensitive matching if a token\n                        let terms = if query.is_case_sensitive()\n                            || tokens.iter().any(|t| t.len() > MAX_CASE_PERMUTATION_LEN)\n                        {\n                            tokens\n                                .into_iter()\n                                .map(|s| str_to_query(*field, &s))\n                                .collect::<Vec<_>>()\n                        } else {\n                            tokens\n                                .into_iter()\n                                .map(|s| {\n                                    let terms = case_permutations(&s)\n                                        .map(|s| str_to_query(*field, &s))\n                                        .collect();\n\n                                    Box::new(BooleanQuery::union(terms)) as DynQuery\n                                })\n                                .collect()\n                        };\n\n                        let mut field_query: DynQuery = Box::new(BooleanQuery::intersection(terms));\n\n                        if self.priority.contains(field) {\n                            field_query = Box::new(BoostQuery::new(field_query, 10.0));\n                        }\n\n                        field_query\n                    }\n                    Extraction::Literal(Literal::Regex(regex)) => {\n                        let plan = planner::plan(&regex)?;\n                        plan_to_query(plan, *field, query.is_case_sensitive())\n                    }\n\n                    Extraction::ByteString(bs) => {\n                        let term = Term::from_field_bytes(*field, bs.as_bytes());\n                        let q = TermQuery::new(term, IndexRecordOption::Basic);\n                        Box::new(q) as DynQuery\n                    }\n                };\n\n                intersection.push(field_query);\n            }\n\n            sub_queries.push(Box::new(BooleanQuery::intersection(intersection)));\n        }\n\n        Ok(if sub_queries.len() == 1 {\n            sub_queries.pop().unwrap()\n        } else {\n            Box::new(BooleanQuery::union(sub_queries.into_vec()))\n        })\n    }\n}\n\nfn plan_to_query(plan: planner::Fragment, field: Field, case_sensitive: bool) -> DynQuery {\n    match plan {\n        planner::Fragment::Literal(s) => {\n            let queries = trigrams(&s)\n                .map(|s| {\n                    let tokens = if case_sensitive {\n                        Either::Left(std::iter::once(s))\n                    } else {\n                        Either::Right(case_permutations(&s))\n                    };\n\n                    let subqueries = tokens\n                        .map(|token| Term::from_field_text(field, &token))\n                        .map(|term| TermQuery::new(term, IndexRecordOption::WithFreqs))\n                        .map(|q| Box::new(q) as DynQuery)\n                        .collect::<Vec<_>>();\n\n                    Box::new(BooleanQuery::union(subqueries)) as DynQuery\n                })\n                .collect::<Vec<_>>();\n\n            Box::new(BooleanQuery::intersection(queries))\n        }\n\n        planner::Fragment::Dense(op, children) => {\n            let subqueries = children\n                .into_iter()\n                .map(|f| plan_to_query(f, field, case_sensitive))\n                .collect();\n\n            Box::new(match op {\n                planner::Op::Or => BooleanQuery::union(subqueries),\n                planner::Op::And => BooleanQuery::intersection(subqueries),\n            })\n        }\n\n        planner::Fragment::Break => Box::new(AllQuery),\n    }\n}\n\nfn str_to_query(field: Field, s: &str) -> DynQuery {\n    let term = Term::from_field_text(field, s);\n    let q = TermQuery::new(term, IndexRecordOption::WithFreqs);\n    Box::new(q) as DynQuery\n}\n\n/// Split a string into trigrams, returning a bigram or unigram if the string is shorter than 3\n/// characters.\npub fn trigrams(s: &str) -> impl Iterator<Item = CompactString> {\n    let mut chars = s.chars().collect::<SmallVec<[char; 6]>>();\n\n    std::iter::from_fn(move || match chars.len() {\n        0 => None,\n        1..=3 => Some(mem::take(&mut chars).into_iter().collect()),\n        _ => {\n            let out = chars.iter().take(3).collect();\n            chars.remove(0);\n            Some(out)\n        }\n    })\n}\n\n/// Get all case permutations of a string.\n///\n/// This permutes each character by ASCII lowercase and uppercase variants. Characters which do not\n/// have case variants remain unchanged.\npub fn case_permutations(s: &str) -> impl Iterator<Item = CompactString> {\n    // This implements a bitmask-based algorithm. The purpose is not speed; rather, a bitmask is\n    // a simple way to get all combinations of a set of flags without allocating, sorting, or doing\n    // anything else that is fancy.\n    //\n    // For example, given a list of 4 characters, we can represent which one is uppercased with a\n    // bitmask: `0011` means that the last two characters are uppercased. To make things simpler\n    // for the algorithm, we can reverse the bitmask to get `1100`; this allows us to create a\n    // *new* bitmask specific to that character by simply doing `(1 << character_index)`. To see\n    // this clearer, we can use a real string and break down all the masks:\n    //\n    //  - Example string: \"abCD\"\n    //  - uppercase_bitmask: 1100\n    //\n    //  - \"a\" @ index 0, bitmask: (1 << 0) = 0001\n    //  - \"b\" @ index 1, bitmask: (1 << 1) = 0010\n    //  - \"C\" @ index 1, bitmask: (1 << 2) = 0100   (uppercased)\n    //  - \"D\" @ index 1, bitmask: (1 << 3) = 1000   (uppercased)\n    //                                 ----------\n    //  - OR all of the uppercased masks   = 1100   (the uppercase bitmask)\n    //\n    // Using this, we can iterate through all combinations of letter casings by simply incrementing\n    // the mask number, resulting in `0000`, `0001`, `0010`, `0011`, `0100`, etc...\n    //\n    // The algorithm below uses this mask to create all permutations of casings.\n\n    let chars = s\n        .chars()\n        .map(|c| c.to_ascii_lowercase())\n        .collect::<SmallVec<[char; 3]>>();\n\n    // A string that is too long leads to an exponential explosion here, growing with 2^n.\n    debug_assert!(chars.len() <= MAX_CASE_PERMUTATION_LEN);\n\n    let num_chars = chars.len();\n\n    let mut mask = 0b000;\n    let end_mask = 1 << num_chars;\n    let non_ascii_mask = chars\n        .iter()\n        .enumerate()\n        .filter_map(|(i, c)| {\n            if *c == c.to_ascii_uppercase() {\n                Some(i)\n            } else {\n                None\n            }\n        })\n        .map(|i| 1 << i)\n        .fold(0u32, |a, e| a | e);\n\n    std::iter::from_fn(move || {\n        // Skip over variants that try to uppercase non-ascii letters.\n        while mask < end_mask && (mask & non_ascii_mask) != 0 {\n            mask += 1;\n        }\n\n        if mask >= end_mask {\n            return None;\n        }\n\n        let permutation = chars\n            .iter()\n            .enumerate()\n            .map(|(i, c)| {\n                if mask & (1 << i) != 0 {\n                    c.to_ascii_uppercase()\n                } else {\n                    *c\n                }\n            })\n            .collect::<CompactString>();\n\n        mask += 1;\n\n        Some(permutation)\n    })\n}\n\n#[cfg(test)]\nmod tests {\n    use tantivy::query::Occur;\n\n    use super::*;\n\n    #[test]\n    fn test_trigrams() {\n        let out = trigrams(\"abcde\").collect::<Vec<_>>();\n        assert_eq!(out, &[\"abc\", \"bcd\", \"cde\"]);\n\n        let out = trigrams(\"abc\").collect::<Vec<_>>();\n        assert_eq!(out, &[\"abc\"]);\n\n        let out = trigrams(\"ab\").collect::<Vec<_>>();\n        assert_eq!(out, &[\"ab\"]);\n\n        let out = trigrams(\"a\").collect::<Vec<_>>();\n        assert_eq!(out, &[\"a\"]);\n\n        let out = trigrams(\"\").count();\n        assert_eq!(out, 0);\n\n        let out = trigrams(\"ab㐀de\").collect::<Vec<_>>();\n        assert_eq!(out, &[\"ab㐀\", \"b㐀d\", \"㐀de\"]);\n    }\n\n    #[test]\n    fn test_case_permutations() {\n        let out = case_permutations(\"abc\").collect::<Vec<_>>();\n        assert_eq!(\n            out,\n            &[\"abc\", \"Abc\", \"aBc\", \"ABc\", \"abC\", \"AbC\", \"aBC\", \"ABC\"]\n        );\n\n        let out = case_permutations(\"ab\").collect::<Vec<_>>();\n        assert_eq!(out, &[\"ab\", \"Ab\", \"aB\", \"AB\"]);\n\n        let out = case_permutations(\"a\").collect::<Vec<_>>();\n        assert_eq!(out, &[\"a\", \"A\"]);\n\n        let out = case_permutations(\"\").collect::<Vec<_>>();\n        assert_eq!(out, &[\"\"]);\n\n        let out = case_permutations(\"a㐀\").collect::<Vec<_>>();\n        assert_eq!(out, &[\"a㐀\", \"A㐀\"]);\n\n        let out = case_permutations(\"a㐀b\").collect::<Vec<_>>();\n        assert_eq!(out, &[\"a㐀b\", \"A㐀b\", \"a㐀B\", \"A㐀B\"]);\n    }\n\n    #[test]\n    fn test_plan_to_query() {\n        // Call the planner directly for simplicity; it has its own tests.\n        let plan = planner::plan(\"async\").unwrap();\n        let field = Field::from_field_id(123);\n        let case_sensitive = true;\n\n        let query = plan_to_query(plan, field, case_sensitive);\n        let query = query.downcast_ref::<BooleanQuery>().unwrap();\n\n        assert_eq!(query.clauses().len(), 3);\n\n        let expected = [\"asy\", \"syn\", \"ync\"];\n        for (clause, expected) in query.clauses().iter().zip(expected) {\n            let (occur, query) = clause;\n            assert_eq!(*occur, Occur::Must);\n\n            let subquery = query.downcast_ref::<BooleanQuery>().unwrap();\n            assert_eq!(subquery.clauses().len(), 1);\n\n            let (occur, term) = &subquery.clauses()[0];\n            let term = term.downcast_ref::<TermQuery>().unwrap();\n            assert_eq!(*occur, Occur::Should);\n            assert_eq!(term.term().value().as_str().unwrap(), expected);\n        }\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/query/execute.rs",
    "content": "use std::{\n    collections::{HashMap, HashSet},\n    sync::Arc,\n};\n\nuse super::{parser, ranking::DocumentTweaker};\nuse crate::{\n    collector::{BytesFilterCollector, FrequencyCollector},\n    indexes::{\n        reader::{base_name, ContentReader, FileReader, OpenReader, RepoReader},\n        DocumentRead, File, Indexable, Indexer, Indexes, Repo,\n    },\n    repo::RepoRef,\n    snippet::{HighlightedString, SnippedFile, Snipper},\n    Application,\n};\n\nuse anyhow::{bail, Result};\nuse async_trait::async_trait;\nuse regex::{bytes::RegexBuilder as ByteRegexBuilder, RegexBuilder};\nuse serde::{Deserialize, Serialize};\nuse smallvec::SmallVec;\nuse tantivy::collector::{MultiCollector, TopDocs};\n\nconst fn default_page_size() -> usize {\n    100\n}\n\nconst fn default_context() -> usize {\n    1\n}\n\nconst fn default_true() -> bool {\n    true\n}\n\n// FIXME: use usize::div_ceil soon\nfn div_ceil(a: usize, b: usize) -> usize {\n    let d = a / b;\n    let r = a % b;\n    d + usize::from(r > 0)\n}\n\n#[derive(Debug, Deserialize)]\npub struct ApiQuery {\n    /// A query written in the bloop query language\n    pub q: String,\n\n    /// Project ID.\n    // NB: We implement methods directly on this struct, which need access to the project ID\n    // associated with this request. This doesn't fit our API; we obtain the project ID via the\n    // router and not via URL query parameters. The abstraction here likely needs to be reworked a\n    // bit, as this can be improved. For now, we just add a skipped field, and manually set it\n    // after deserialization. TODO: Fix this.\n    #[serde(skip)]\n    pub project_id: i64,\n\n    #[serde(default)]\n    pub page: usize,\n\n    #[serde(default = \"default_page_size\")]\n    pub page_size: usize,\n\n    /// Whether to calculate total_pages and total_count.\n    ///\n    /// This value can be set to false when browsing through pages of the same\n    /// query, after retrieving totals from the first page, in order to cut down\n    /// on calculations.\n    ///\n    /// To calculate totals, we run an additional `count` query against each\n    /// index.\n    #[serde(default = \"default_true\")]\n    pub calculate_totals: bool,\n\n    /// The number of lines of context in the snippet before the search result\n    #[serde(alias = \"cb\", default = \"default_context\")]\n    pub context_before: usize,\n\n    /// The number of lines of context in the snippet after the search result\n    #[serde(alias = \"ca\", default = \"default_context\")]\n    pub context_after: usize,\n}\n\n#[derive(Serialize)]\npub struct QueryResponse {\n    /// Number of search results in this response\n    pub count: usize,\n    /// Paging metadata\n    pub(crate) metadata: PagingMetadata,\n    /// Search result data\n    pub data: Vec<QueryResult>,\n    /// Stats for nerds\n    pub stats: ResultStats,\n}\n\nimpl crate::webserver::ApiResponse for QueryResponse {}\n\n/// Metadata pertaining to the query response, such as paging info\n#[derive(Default, Serialize)]\n#[non_exhaustive]\npub struct PagingMetadata {\n    /// Page number passed in the request\n    page: usize,\n\n    /// Number of items per-page\n    page_size: usize,\n\n    /// total number of pages, only populated if the client requests it\n    page_count: Option<usize>,\n\n    /// total number of search results across all pages, only populated\n    /// if the client requests it\n    total_count: Option<usize>,\n}\n\n#[derive(Default, Serialize, Deserialize, Debug)]\npub struct ResultStats {\n    pub lang: HashMap<String, usize>,\n    pub repo: HashMap<String, usize>,\n}\n\n#[derive(Serialize)]\n#[non_exhaustive]\n#[serde(tag = \"kind\", content = \"data\")]\npub enum QueryResult {\n    #[serde(rename = \"snippets\")]\n    Snippets(SnippedFile),\n\n    #[serde(rename = \"repository_result\")]\n    RepositoryResult(RepositoryResultData),\n\n    #[serde(rename = \"file_result\")]\n    FileResult(FileResultData),\n\n    #[serde(rename = \"file\")]\n    File(FileData),\n\n    #[serde(rename = \"dir\")]\n    Directory(DirectoryData),\n\n    // Only returned by autocomplete\n    #[serde(rename = \"flag\")]\n    Flag(String),\n\n    #[serde(rename = \"lang\")]\n    Lang(String),\n}\n\n#[derive(Serialize)]\npub struct RepositoryResultData {\n    name: HighlightedString,\n    repo_ref: String,\n}\n\n#[derive(Serialize)]\npub struct FileResultData {\n    repo_name: String,\n    relative_path: HighlightedString,\n    repo_ref: RepoRef,\n    lang: Option<String>,\n    branches: String,\n    indexed: bool,\n    is_dir: bool,\n}\n\nimpl FileResultData {\n    pub fn new(\n        repo_name: String,\n        relative_path: String,\n        repo_ref: RepoRef,\n        lang: Option<String>,\n        branches: String,\n        indexed: bool,\n        is_dir: bool,\n    ) -> Self {\n        Self {\n            repo_name,\n            relative_path: HighlightedString::new(relative_path),\n            repo_ref,\n            lang,\n            branches,\n            indexed,\n            is_dir,\n        }\n    }\n}\n\n#[derive(Serialize, Debug)]\npub struct FileData {\n    repo_name: String,\n    relative_path: String,\n    repo_ref: String,\n    lang: Option<String>,\n    contents: String,\n    siblings: Vec<DirEntry>,\n    indexed: bool,\n    size: usize,\n    loc: usize,\n    sloc: usize,\n}\n\n#[derive(Serialize)]\npub struct DirectoryData {\n    repo_name: String,\n    relative_path: String,\n    repo_ref: String,\n    entries: Vec<DirEntry>,\n}\n\n#[derive(Serialize, PartialEq, Eq, Hash, Clone, Debug)]\npub struct DirEntry {\n    name: String,\n    entry_data: EntryData,\n}\n\n#[derive(Serialize, PartialEq, Eq, Hash, Clone, Debug)]\nenum EntryData {\n    Directory,\n    File { lang: Option<String>, indexed: bool },\n}\n\n#[async_trait]\npub trait ExecuteQuery {\n    type Index: Indexable;\n\n    async fn execute(\n        &self,\n        indexer: &Indexer<Self::Index>,\n        queries: &[parser::Query<'_>],\n        q: &ApiQuery,\n    ) -> Result<QueryResponse>;\n}\n\nimpl ApiQuery {\n    pub async fn query(self: Arc<Self>, app: &Application) -> Result<QueryResponse> {\n        let raw_query = self.q.clone();\n        let queries = self\n            .restrict_queries(parser::parse(&raw_query)?, app)\n            .await?;\n        tracing::debug!(\"compiled query as {queries:?}\");\n        self.query_with(Arc::clone(&app.indexes), queries).await\n    }\n\n    /// This restricts a set of input parser queries.\n    ///\n    /// We trim down the input by:\n    ///\n    /// 1. Discarding all queries that reference repos not in the queried project\n    /// 2. Regenerating more specific queries for those without repo restrictions, such that there\n    ///    is a new query generated per repo that exists in the project.\n    ///\n    /// The idea here is to allow us to restrict the possible input space of queried documents to\n    /// be more specific as required by the project state.\n    ///\n    /// The `subset` flag indicates whether repo name matching is whole-string, or whether the\n    /// string must only be a substring of an existing repo. This is useful in autocomplete\n    /// scenarios, where we want to restrict queries such that they are not fully typed out.\n    pub async fn restrict_queries<'a>(\n        &self,\n        queries: impl IntoIterator<Item = parser::Query<'a>>,\n        app: &Application,\n    ) -> Result<Vec<parser::Query<'a>>> {\n        let repo_branches = sqlx::query! {\n            \"SELECT repo_ref, branch\n            FROM project_repos\n            WHERE project_id = ?\",\n            self.project_id,\n        }\n        .fetch_all(&*app.sql)\n        .await?\n        .into_iter()\n        .map(|row| {\n            (\n                row.repo_ref.parse::<RepoRef>().unwrap().indexed_name(),\n                row.branch,\n            )\n        })\n        .collect::<HashMap<_, _>>();\n\n        let mut out = Vec::new();\n\n        for q in queries {\n            if let Some(r) = q.repo_str() {\n                // The branch that this project has loaded this repo with.\n                let project_branch = repo_branches.get(&r).and_then(Option::as_ref);\n\n                // If the branch doesn't match what we expect, drop the query.\n                if q.branch_str().as_ref() == project_branch {\n                    out.push(q);\n                }\n            } else {\n                for (r, b) in &repo_branches {\n                    out.push(parser::Query {\n                        repo: Some(parser::Literal::from(r)),\n                        branch: b.as_ref().map(parser::Literal::from),\n                        ..q.clone()\n                    });\n                }\n            }\n        }\n\n        Ok(out)\n    }\n\n    /// This restricts a set of input repo-only queries.\n    ///\n    /// This is useful for autocomplete queries, which are effectively just `repo:foo`, where the\n    /// repo name may be partially written.\n    pub async fn restrict_repo_queries<'a>(\n        &self,\n        queries: impl IntoIterator<Item = parser::Query<'a>>,\n        app: &Application,\n    ) -> Result<Vec<parser::Query<'a>>> {\n        let repo_refs = sqlx::query! {\n            \"SELECT repo_ref\n            FROM project_repos\n            WHERE project_id = ?\",\n            self.project_id,\n        }\n        .fetch_all(&*app.sql)\n        .await?\n        .into_iter()\n        .map(|row| row.repo_ref.parse::<RepoRef>().unwrap().indexed_name())\n        .collect::<Vec<_>>();\n\n        let mut out = Vec::new();\n\n        for q in queries {\n            if let Some(r) = q.repo_str() {\n                for m in repo_refs.iter().filter(|r2| r2.contains(&r)) {\n                    out.push(parser::Query {\n                        repo: Some(parser::Literal::from(m)),\n                        ..Default::default()\n                    });\n                }\n            }\n        }\n\n        out.dedup();\n\n        Ok(out)\n    }\n\n    pub async fn query_with(\n        self: Arc<Self>,\n        indexes: Arc<Indexes>,\n        queries: Vec<parser::Query<'_>>,\n    ) -> Result<QueryResponse> {\n        // FIXME: this for-loop prevents us from ever producing heterogenous\n        // results.\n        //\n        // It picks up the first query in the query list and produces results only\n        // for that query. A query containing an `or` operator; such as:\n        //\n        //     symbol:foo or repo:bar\n        //\n        //\n        // is actually broken down into two separate queries:\n        //\n        //  - `symbol:foo` which operates on the `File` index\n        //  - `repo:bar` which operates on the `Repo` index\n        //\n        // However, merging the results of a query that operates on multiple indices\n        // poses a few difficult questions:\n        //\n        //  - how do we implement pagination or establish a limit on the number of\n        //    results?\n        //  - how do we rank these results?\n        //\n        // For the time-being, we take the easy way out by prioritizing the first\n        // target of the query, in this case `symbol:foo`. Queries that produce\n        // homogenous results will work as expected: `repo:foo or repo:bar`.\n        for q in &queries {\n            if ContentReader.query_matches(q) {\n                tracing::trace!(\"executing with ContentReader\");\n                return ContentReader.execute(&indexes.file, &queries, &self).await;\n            } else if RepoReader.query_matches(q) {\n                tracing::trace!(\"executing with RepoReader\");\n                return RepoReader.execute(&indexes.repo, &queries, &self).await;\n            } else if FileReader.query_matches(q) {\n                tracing::trace!(\"executing with FileReader\");\n                return FileReader.execute(&indexes.file, &queries, &self).await;\n            } else if OpenReader.query_matches(q) {\n                tracing::trace!(\"executing with OpenReader\");\n                return OpenReader.execute(&indexes.file, &queries, &self).await;\n            }\n        }\n\n        bail!(\"mangled query\")\n    }\n\n    fn limit(&self) -> usize {\n        // do not permit a page-size of 0\n        self.page_size.max(1)\n    }\n\n    fn offset(&self) -> usize {\n        self.page_size * self.page\n    }\n}\n\nimpl PagingMetadata {\n    pub fn new(page: usize, page_size: usize, total_count: Option<usize>) -> Self {\n        Self {\n            page,\n            page_size,\n            page_count: total_count.map(|t| div_ceil(t, page_size)),\n            total_count,\n        }\n    }\n}\n\nimpl ResultStats {\n    fn with_lang_freqs(mut self, mut lang_freqs: HashMap<Vec<u8>, usize>) -> Self {\n        self.lang = lang_freqs\n            .iter_mut()\n            .filter(|(k, _)| !k.is_empty())\n            .map(|(k, v)| {\n                let k =\n                    crate::query::languages::proper_case(String::from_utf8_lossy(k)).to_string();\n                (k, *v)\n            })\n            .collect();\n        self\n    }\n\n    fn with_repo_freqs(mut self, mut repo_freqs: HashMap<Vec<u8>, usize>) -> Self {\n        self.repo = repo_freqs\n            .iter_mut()\n            .filter(|(k, _)| !k.is_empty())\n            .map(|(k, v)| {\n                let k = String::from_utf8_lossy(k).to_string();\n                (k, *v)\n            })\n            .collect();\n        self\n    }\n}\n\n#[async_trait]\nimpl ExecuteQuery for ContentReader {\n    type Index = File;\n\n    async fn execute(\n        &self,\n        indexer: &Indexer<Self::Index>,\n        queries: &[parser::Query<'_>],\n        q: &ApiQuery,\n    ) -> Result<QueryResponse> {\n        // queries that produce content results\n        let relevant_queries = queries.iter().filter(|q| self.query_matches(q));\n\n        // a list of targets, for a query of the form `symbol:foo or bar`, this is:\n        // - a symbol target: foo\n        // - a content target: bar\n        let targets = relevant_queries\n            .filter_map(|q| Some((q.target.as_ref()?, q.is_case_sensitive())))\n            .collect::<SmallVec<[_; 2]>>();\n\n        // a regex filter to get rid of docs that contain the trigrams but not the text\n        let byte_regexes = targets\n            .iter()\n            .filter_map(|(target, case)| {\n                ByteRegexBuilder::new(&target.literal().regex_str())\n                    .multi_line(true)\n                    .case_insensitive(!case)\n                    .build()\n                    .ok()\n            })\n            .collect::<Vec<_>>();\n\n        let raw_content = indexer.source.raw_content;\n        let repo_field = indexer.source.raw_repo_name;\n        let lang_field = indexer.source.lang;\n\n        // our results will consist of the top-k docs...\n        let top_k = TopDocs::with_limit(q.limit())\n            .and_offset(q.offset())\n            .tweak_score(DocumentTweaker(indexer.source.clone()));\n\n        // ...plus some rich search metadata\n        let total_count_collector = tantivy::collector::Count;\n        let lang_stats_collector = FrequencyCollector(lang_field);\n        let repo_stats_collector = FrequencyCollector(repo_field);\n\n        let mut metadata_collector = MultiCollector::new();\n        let total_count_handle = metadata_collector.add_collector(total_count_collector);\n        let lang_stats_handle = metadata_collector.add_collector(lang_stats_collector);\n        let repo_stats_handle = metadata_collector.add_collector(repo_stats_collector);\n\n        // our final search results contain top-k, total count, language stats, repo stats,\n        // filtered by the target regex\n        let collector = BytesFilterCollector::new(\n            raw_content,\n            move |b| byte_regexes.iter().any(|r| r.is_match(b)), // a doc is accepted if it contains at least 1 target\n            (top_k, metadata_collector),\n        );\n\n        let mut results = indexer.query(queries.iter(), self, collector).await?;\n        let data = results\n            .docs\n            .filter_map(|doc| {\n                let snipper = Snipper::default().context(q.context_before, q.context_after);\n                let mut all_snippets = None::<SnippedFile>;\n\n                for (target, case_sensitive) in &targets {\n                    let (is_symbol, lit) = match target {\n                        parser::Target::Symbol(lit) => (true, lit),\n                        parser::Target::Content(lit) => (false, lit),\n                    };\n\n                    if let Some(snippets) = snipper\n                        .find_symbols(is_symbol)\n                        .case_sensitive(*case_sensitive)\n                        .all_for_doc(&lit.regex_str(), &doc)\n                        .unwrap()\n                    {\n                        all_snippets = if let Some(data) = all_snippets {\n                            Some(data.merge(snippets))\n                        } else {\n                            Some(snippets)\n                        };\n                    }\n                }\n\n                Some(QueryResult::Snippets(all_snippets?))\n            })\n            .collect::<Vec<QueryResult>>();\n\n        let total_count = total_count_handle.extract(&mut results.metadata);\n\n        let stats = ResultStats::default()\n            .with_lang_freqs(lang_stats_handle.extract(&mut results.metadata))\n            .with_repo_freqs(repo_stats_handle.extract(&mut results.metadata));\n\n        let metadata = PagingMetadata::new(q.page, q.page_size, Some(total_count));\n\n        let count = data.len();\n        let response = QueryResponse {\n            count,\n            metadata,\n            data,\n            stats,\n        };\n        Ok(response)\n    }\n}\n\n#[async_trait]\nimpl ExecuteQuery for FileReader {\n    type Index = File;\n\n    async fn execute(\n        &self,\n        indexer: &Indexer<File>,\n        queries: &[parser::Query<'_>],\n        q: &ApiQuery,\n    ) -> Result<QueryResponse> {\n        let (filter_regexes, byte_filter_regexes): (Vec<_>, Vec<_>) = queries\n            .iter()\n            .filter(|q| self.query_matches(q))\n            .filter_map(|q| {\n                let regex_str = q.path.as_ref()?.regex_str();\n                let case_insensitive = !q.is_case_sensitive();\n                let regex = RegexBuilder::new(&regex_str)\n                    .case_insensitive(case_insensitive)\n                    .build()\n                    .ok()?;\n                let byte_regex = ByteRegexBuilder::new(&regex_str)\n                    .case_insensitive(case_insensitive)\n                    .build()\n                    .ok()?;\n                Some((regex, byte_regex))\n            })\n            .unzip();\n\n        let top_k = TopDocs::with_limit(q.limit()).and_offset(q.offset());\n\n        let path_field = indexer.source.raw_relative_path;\n        let repo_field = indexer.source.raw_repo_name;\n        let lang_field = indexer.source.lang;\n\n        let total_count_collector = tantivy::collector::Count;\n        let lang_stats_collector = FrequencyCollector(lang_field);\n        let repo_stats_collector = FrequencyCollector(repo_field);\n\n        let mut metadata_collector = MultiCollector::new();\n        let total_count_handle = metadata_collector.add_collector(total_count_collector);\n        let lang_stats_handle = metadata_collector.add_collector(lang_stats_collector);\n        let repo_stats_handle = metadata_collector.add_collector(repo_stats_collector);\n\n        let collector = BytesFilterCollector::new(\n            path_field,\n            move |b| byte_filter_regexes.iter().any(|r| r.is_match(b)), // a doc is accepted if it contains at least 1 target\n            (top_k, metadata_collector),\n        );\n\n        let mut results = indexer.query(queries.iter(), self, collector).await?;\n\n        let data = results\n            .docs\n            .map(|f| {\n                let mut relative_path = HighlightedString::new(f.relative_path);\n\n                for regex in &filter_regexes {\n                    relative_path.apply_regex(regex);\n                }\n\n                QueryResult::FileResult(FileResultData {\n                    relative_path,\n                    repo_name: f.repo_name,\n                    repo_ref: f.repo_ref,\n                    lang: f.lang,\n                    branches: f.branches,\n                    indexed: f.indexed,\n                    is_dir: f.is_dir,\n                })\n            })\n            .collect::<Vec<QueryResult>>();\n\n        let total_count = total_count_handle.extract(&mut results.metadata);\n\n        let stats = ResultStats::default()\n            .with_lang_freqs(lang_stats_handle.extract(&mut results.metadata))\n            .with_repo_freqs(repo_stats_handle.extract(&mut results.metadata));\n\n        let metadata = PagingMetadata::new(q.page, q.page_size, Some(total_count));\n\n        let response = QueryResponse {\n            count: data.len(),\n            data,\n            metadata,\n            stats,\n        };\n\n        Ok(response)\n    }\n}\n\n#[async_trait]\nimpl ExecuteQuery for RepoReader {\n    type Index = Repo;\n\n    async fn execute(\n        &self,\n        indexer: &Indexer<Self::Index>,\n        queries: &[parser::Query<'_>],\n        q: &ApiQuery,\n    ) -> Result<QueryResponse> {\n        let (filter_regexes, byte_filter_regexes): (Vec<_>, Vec<_>) = queries\n            .iter()\n            .filter(|q| self.query_matches(q))\n            .filter_map(|q| {\n                let regex_str = q.repo.as_ref()?.regex_str();\n                let case_insensitive = !q.is_case_sensitive();\n                let regex = RegexBuilder::new(&regex_str)\n                    .case_insensitive(case_insensitive)\n                    .build()\n                    .ok()?;\n                let byte_regex = ByteRegexBuilder::new(&regex_str)\n                    .case_insensitive(case_insensitive)\n                    .build()\n                    .ok()?;\n                Some((regex, byte_regex))\n            })\n            .unzip();\n\n        let top_k = TopDocs::with_limit(q.limit()).and_offset(q.offset());\n\n        let name_field = indexer.source.raw_name;\n        let repo_stats_collector = FrequencyCollector(name_field);\n        let total_count_collector = tantivy::collector::Count;\n\n        let mut metadata_collector = MultiCollector::new();\n        let repo_stats_handle = metadata_collector.add_collector(repo_stats_collector);\n        let total_count_handle = metadata_collector.add_collector(total_count_collector);\n\n        let collector = BytesFilterCollector::new(\n            name_field,\n            move |b| byte_filter_regexes.iter().any(|r| r.is_match(b)), // a doc is accepted if it contains at least 1 target\n            (top_k, metadata_collector),\n        );\n\n        let mut results = indexer.query(queries.iter(), self, collector).await?;\n\n        let data = results\n            .docs\n            .map(|r| {\n                let mut name = HighlightedString::new(r.name);\n\n                for r in &filter_regexes {\n                    name.apply_regex(r);\n                }\n\n                QueryResult::RepositoryResult(RepositoryResultData {\n                    name,\n                    repo_ref: r.repo_ref,\n                })\n            })\n            .collect::<Vec<QueryResult>>();\n\n        let stats = ResultStats::default()\n            .with_repo_freqs(repo_stats_handle.extract(&mut results.metadata));\n\n        let total_count = total_count_handle.extract(&mut results.metadata);\n        let metadata = PagingMetadata::new(q.page, q.page_size, Some(total_count));\n\n        let response = QueryResponse {\n            count: data.len(),\n            data,\n            metadata,\n            stats,\n        };\n\n        Ok(response)\n    }\n}\n\n#[async_trait]\nimpl ExecuteQuery for OpenReader {\n    type Index = File;\n\n    async fn execute(\n        &self,\n        indexer: &Indexer<Self::Index>,\n        queries: &[parser::Query<'_>],\n        _q: &ApiQuery,\n    ) -> Result<QueryResponse> {\n        #[derive(Debug)]\n        struct Directive {\n            relative_path: String,\n            repo_name: String,\n        }\n\n        let open_directives = queries\n            .iter()\n            .filter(|q| self.query_matches(q))\n            .filter_map(|q| {\n                Some(Directive {\n                    relative_path: match q.path.as_ref() {\n                        None => \"\".into(),\n                        Some(parser::Literal::Plain(p)) => p.to_string(),\n                        Some(parser::Literal::Regex(..)) => return None,\n                    },\n                    repo_name: q.repo.as_ref()?.as_plain()?.into(),\n                })\n            })\n            .collect::<SmallVec<[_; 2]>>();\n\n        let top_docs = TopDocs::with_limit(50000);\n        let empty_collector = MultiCollector::new();\n\n        let relative_paths = open_directives\n            .iter()\n            .map(|d| d.relative_path.to_owned())\n            .collect::<Vec<_>>();\n\n        tracing::trace!(?relative_paths, \"creating collector\");\n\n        let collector = BytesFilterCollector::new(\n            indexer.source.raw_relative_path,\n            move |b| {\n                let Ok(relative_path) = std::str::from_utf8(b) else {\n                    return false;\n                };\n\n                tracing::trace!(?relative_path, \"filtering relative path\");\n\n                // Check if *any* of the relative paths match. We can't compare repositories here\n                // because the `BytesFilterCollector` operates on one field. So we sort through this\n                // later. It's unlikely that a search will use more than one open query.\n                relative_paths.iter().any(|rp| {\n                    let rp = rp.trim_end_matches(|c| c != '/');\n\n                    matches!(\n                        // Trim trailing suffix and avoid returning results for an empty string\n                        // (this means that the document we are looking at is the folder itself; a\n                        // redundant result).\n                        relative_path.strip_prefix(rp).map(|p| p.trim_end_matches('/')),\n                        Some(p) if !p.is_empty() && !p.contains('/')\n                    )\n                })\n            },\n            (top_docs, empty_collector),\n        );\n\n        let results = indexer.query(queries.iter(), self, collector).await?;\n\n        // Map of (repo_name, relative_path) -> (String, entry set)\n        //\n        // This is used to build up the directory and file return values.\n        let mut dir_entries: HashMap<(&str, &str), (String, HashSet<DirEntry>)> = HashMap::new();\n\n        // List of files that should be returned.\n        let mut files = Vec::new();\n        // Set of (repo_name, relative_path) that should be returned.\n        let directories = open_directives\n            .iter()\n            .filter(|d| d.relative_path.is_empty() || d.relative_path.ends_with('/'))\n            .map(|d| (&d.repo_name, &d.relative_path))\n            .collect::<HashSet<_>>();\n\n        // Iterate over each combination of (document, directive).\n        //\n        // The total document list is a result of a combination of queries, so here we categorize\n        // the relevant results.\n        for doc in results.docs {\n            for directive in open_directives\n                .iter()\n                .filter(|d| d.repo_name == doc.repo_name)\n            {\n                // Exact hit.\n                if directive.relative_path == doc.relative_path {\n                    files.push(FileData {\n                        repo_name: doc.repo_name.clone(),\n                        relative_path: doc.relative_path.clone(),\n                        repo_ref: doc.repo_ref.to_owned(),\n                        lang: doc.lang.clone(),\n                        contents: doc.content.clone(),\n                        size: doc.content.len(),\n                        loc: doc.line_end_indices.len(),\n                        indexed: doc.indexed,\n                        sloc: doc\n                            .line_end_indices\n                            .iter()\n                            .zip(doc.line_end_indices.iter().skip(1))\n                            .filter(|(&prev, &next)| next - prev != 1)\n                            .count()\n                            .saturating_add(1),\n                        siblings: vec![],\n                    });\n\n                    continue;\n                }\n\n                let relative_path = base_name(&directive.relative_path);\n\n                if let Some(entry) = doc\n                    .relative_path\n                    .strip_prefix(relative_path)\n                    .and_then(|s| s.split_inclusive('/').next())\n                {\n                    dir_entries\n                        .entry((&directive.repo_name, relative_path))\n                        .or_insert_with(|| (doc.repo_ref.to_owned(), HashSet::default()))\n                        .1\n                        .insert(DirEntry {\n                            name: entry.to_owned(),\n                            entry_data: if entry.contains('/') {\n                                EntryData::Directory\n                            } else {\n                                EntryData::File {\n                                    lang: doc.lang.clone(),\n                                    indexed: doc.indexed,\n                                }\n                            },\n                        });\n                }\n            }\n        }\n\n        // Assign sibling data now that we have crawled all the results.\n        for file in &mut files {\n            file.siblings = dir_entries\n                .get(&(&file.repo_name, base_name(&file.relative_path)))\n                .map(|(_ref, entries)| entries)\n                .into_iter()\n                .flatten()\n                .cloned()\n                .collect();\n        }\n\n        let data = directories\n            .into_iter()\n            .filter_map(|(repo_name, relative_path)| {\n                let (repo_ref, entries) = dir_entries.get(&(repo_name, relative_path))?;\n\n                Some(DirectoryData {\n                    repo_name: repo_name.to_owned(),\n                    relative_path: relative_path.to_owned(),\n                    repo_ref: repo_ref.clone(),\n                    entries: entries.iter().cloned().collect(),\n                })\n            })\n            .map(QueryResult::Directory)\n            .chain(files.into_iter().map(QueryResult::File))\n            .collect::<Vec<QueryResult>>();\n\n        let response = QueryResponse {\n            count: data.len(),\n            data,\n            metadata: PagingMetadata::default(),\n            stats: ResultStats::default(),\n        };\n\n        Ok(response)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::snippet::*;\n    use pretty_assertions::assert_eq;\n\n    #[test]\n    fn serialize_response() {\n        let expected = serde_json::json!({\n          \"count\": 1,\n          \"data\": [\n            {\n              \"data\": {\n                \"lang\": \"Rust\",\n                \"relative_path\": \"./bleep/src/indexes/repo.rs\",\n                \"repo_name\": \"local//bleep\",\n                \"repo_ref\": \"/User/bloop/bleep\",\n                \"snippets\": [\n                  {\n                    \"highlights\": [{\n                      \"start\": 51,\n                      \"end\": 56,\n                    }],\n                    \"symbols\": [],\n                    \"data\": r\"        mut writer: IndexWriter,\\n        _threads: usize,\\n    ) -> Result<()> {\",\n                    \"line_range\": {\n                      \"start\": 49,\n                      \"end\": 51\n                    }\n                  }\n                ]\n              },\n              \"kind\": \"snippets\"\n            }\n          ],\n          \"metadata\": {\n              \"page\": 0,\n              \"page_size\": 100,\n              \"page_count\": 6,\n              \"total_count\": 520\n          },\n          \"stats\": {\n            \"repo\": {\"local//bleep\": 1},\n            \"lang\": {\n                \"Rust\": 1\n            }\n          },\n        });\n\n        let repos = HashMap::from([(\"local//bleep\".into(), 1)]);\n        let langs = HashMap::from([(\"Rust\".into(), 1)]);\n\n        let observed = serde_json::to_value(QueryResponse {\n            count: 1,\n            data: vec![QueryResult::Snippets(SnippedFile {\n                relative_path: \"./bleep/src/indexes/repo.rs\".into(),\n                repo_name: \"local//bleep\".into(),\n                repo_ref: \"/User/bloop/bleep\".into(),\n                lang: Some(\"Rust\".into()),\n                snippets: vec![Snippet {\n                    data: r\"        mut writer: IndexWriter,\\n        _threads: usize,\\n    ) -> Result<()> {\".to_owned(),\n                    line_range: 49..51,\n                    highlights: vec![51..56],\n                    symbols: vec![],\n                }],\n            })],\n            metadata: PagingMetadata {\n                page: 0,\n                page_size: 100,\n                page_count: Some(6),\n                total_count: Some(520)\n            },\n            stats: ResultStats { repo: repos, lang: langs },\n        })\n        .unwrap();\n\n        assert_eq!(expected, observed);\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/query/grammar.pest",
    "content": "query = _{ SOI ~ intersection ~ EOI }\n\nelement = ${ label | mode | literal | group }\n\nliteral = _{ !(or ~ terminator) ~ (\n                 (quote ~ quoted_literal ~ quote)\n               | (single_quote ~ single_quoted_literal ~ single_quote)\n               | (regex_quote ~ regex_quoted_literal ~ regex_quote)\n               | unquoted_literal\n             )\n           }\n\nquote = _{ \"\\\"\" }\nsingle_quote = _{ \"'\" }\nregex_quote = _{ \"/\" }\nterminator = @{ group_start | group_end | WHITESPACE }\n\nunquoted_literal = @{ (!terminator ~ ANY)+ ~ (escape ~ unquoted_literal)? }\nquoted_literal   = @{ (!(quote | \"\\\\\") ~ ANY)* ~ (escape ~ quoted_literal)? }\nsingle_quoted_literal = @{ (!(single_quote | \"\\\\\") ~ ANY)* ~ (escape ~ single_quoted_literal)? }\nregex_quoted_literal  = @{ (!(regex_quote | \"\\\\\") ~ ANY)* ~ (escape ~ regex_quoted_literal)? }\n\nescape  = @{ \"\\\\\" ~ ANY }\n\n// Labels are broken out to rules so we can add arguments and options.\nlabel = _{ content | repo | org | symbol | path | lang | branch }\n\ncontent = ${ \"content:\" ~ literal }\nrepo = ${ \"repo:\" ~ literal }\norg = ${ \"org:\" ~ literal }\nsymbol = ${ \"symbol:\" ~ literal }\npath = ${ \"path:\" ~ literal }\nbranch = ${ \"branch:\" ~ literal }\nlang = ${ \"lang:\" ~ unquoted_literal }\n\nmode = _{ case | open | global_regex }\n\ncase = ${ \"case:\" ~ ( case_ignore | case_sensitive ) }\ncase_ignore = { \"ignore\" }\ncase_sensitive = { \"sensitive\" }\nopen = ${ \"open:\" ~ boolean }\nglobal_regex = ${ \"global_regex:\" ~ boolean }\n\n// a b or c = (a and b) or c\nor = { \"or\" }\nboolean = { \"true\" | \"false\" }\nintersection = { element+ ~ (or ~ element+)* }\n\ngroup_start = _{ \"(\" }\ngroup_end = _{ \")\" }\ngroup = !{ group_start ~ intersection ~ group_end }\n\nWHITESPACE = _{ \" \" }\n\n// natural language queries\nraw_text = @{ (!WHITESPACE ~ ANY)+ }\nnl_query = _{ SOI ~ (label | mode | raw_text)* ~ EOI }\n"
  },
  {
    "path": "server/bleep/src/query/languages.rs",
    "content": "use std::{borrow::Cow, collections::HashSet};\n\ninclude!(concat!(env!(\"OUT_DIR\"), \"/languages.rs\"));\n\npub fn parse_alias(lang: &str) -> Cow<'static, str> {\n    if let Some(s) = EXT_MAP.get(lang) {\n        (*s).into()\n    } else {\n        lang.to_ascii_lowercase().into()\n    }\n}\n\npub fn proper_case(lower: Cow<str>) -> Cow<str> {\n    if let Some(s) = PROPER_CASE_MAP.get(&lower) {\n        (*s).into()\n    } else {\n        lower\n    }\n}\n\npub fn list() -> impl Iterator<Item = &'static str> {\n    EXT_MAP\n        .entries()\n        .flat_map(|e| [*e.0, *e.1])\n        .collect::<HashSet<_>>()\n        .into_iter()\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn sample_language_aliases() {\n        assert_eq!(parse_alias(\"rs\".into()), \"rust\");\n        assert_eq!(parse_alias(\"cpp\".into()), \"c++\");\n        assert_eq!(parse_alias(\"as3\".into()), \"actionscript\");\n        assert_eq!(parse_alias(\"bat\".into()), \"batchfile\");\n        assert_eq!(parse_alias(\"md\".into()), \"markdown\");\n    }\n\n    #[test]\n    fn sample_proper_case() {\n        assert_eq!(proper_case(\"rust\".into()), \"Rust\");\n        assert_eq!(proper_case(\"c++\".into()), \"C++\");\n        assert_eq!(proper_case(\"actionscript\".into()), \"ActionScript\");\n        assert_eq!(proper_case(\"batchfile\".into()), \"Batchfile\");\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/query/parser.rs",
    "content": "use pest::{iterators::Pair, Parser};\nuse regex::Regex;\nuse smallvec::{smallvec, SmallVec};\nuse std::{borrow::Cow, mem, ops::Deref};\n\n#[derive(Default, Clone, Debug, PartialEq, Eq)]\npub struct Query<'a> {\n    pub open: Option<bool>,\n    pub case_sensitive: Option<bool>,\n    pub global_regex: Option<bool>,\n\n    pub org: Option<Literal<'a>>,\n    pub repo: Option<Literal<'a>>,\n    pub path: Option<Literal<'a>>,\n    pub lang: Option<Literal<'a>>,\n    pub branch: Option<Literal<'a>>,\n    pub target: Option<Target<'a>>,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, Hash)]\npub enum Target<'a> {\n    Symbol(Literal<'a>),\n    Content(Literal<'a>),\n}\n\n#[derive(Default, Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]\npub struct SemanticQuery<'a> {\n    pub raw_query: String,\n    pub repos: Vec<Literal<'a>>,\n    pub paths: Vec<Literal<'a>>,\n    pub langs: Vec<Literal<'a>>,\n    pub branch: Vec<Literal<'a>>,\n    pub target: Option<Literal<'a>>,\n}\n\nimpl<'a> SemanticQuery<'a> {\n    pub fn repos(&'a self) -> impl Iterator<Item = Cow<'a, str>> {\n        self.repos.iter().filter_map(|t| t.as_plain())\n    }\n\n    pub fn paths(&'a self) -> impl Iterator<Item = Cow<'a, str>> {\n        self.paths.iter().filter_map(|t| t.as_plain())\n    }\n\n    pub fn langs(&'a self) -> impl Iterator<Item = Cow<'a, str>> {\n        self.langs.iter().filter_map(|t| t.as_plain())\n    }\n\n    pub fn target(&self) -> Option<Cow<'a, str>> {\n        self.target.as_ref().and_then(|t| t.as_plain())\n    }\n\n    pub fn branch(&'a self) -> impl Iterator<Item = Cow<'a, str>> {\n        self.branch.iter().filter_map(|t| t.as_plain())\n    }\n\n    // TODO (@calyptobai): This is a quirk of the current conversation logic. We take only the\n    // first branch because the UX operates on a single \"current\" branch. We can likely update\n    // `SemanticQuery` to remove multiple branches altogether.\n    pub fn first_branch(&self) -> Option<Cow<'_, str>> {\n        self.branch.first().map(|t| t.clone().unwrap())\n    }\n\n    // Ditto for repo\n    pub fn first_repo(&self) -> Option<Cow<'_, str>> {\n        self.repos.first().map(|t| t.clone().unwrap())\n    }\n\n    pub fn from_str(query: String, repo_ref: String) -> Self {\n        Self {\n            target: Some(Literal::Plain(query.into())),\n            repos: [Literal::Plain(repo_ref.into())].into(),\n            ..Default::default()\n        }\n    }\n\n    pub fn into_owned(self) -> SemanticQuery<'static> {\n        SemanticQuery {\n            raw_query: self.raw_query.clone(),\n            repos: self.repos.into_iter().map(Literal::into_owned).collect(),\n            paths: self.paths.into_iter().map(Literal::into_owned).collect(),\n            langs: self.langs.into_iter().map(Literal::into_owned).collect(),\n            branch: self.branch.into_iter().map(Literal::into_owned).collect(),\n            target: self.target.map(Literal::into_owned),\n        }\n    }\n}\n\nimpl<'a> Query<'a> {\n    /// Merge this query with another, overwriting current terms by terms in the new query, if they\n    /// exist.\n    fn merge(self, rhs: Self) -> Self {\n        Self {\n            open: rhs.open.or(self.open),\n            case_sensitive: rhs.case_sensitive.or(self.case_sensitive),\n            global_regex: rhs.global_regex.or(self.global_regex),\n\n            org: rhs.org.or(self.org),\n            repo: rhs.repo.or(self.repo),\n            path: rhs.path.or(self.path),\n            lang: rhs.lang.or(self.lang),\n            branch: rhs.branch.or(self.branch),\n\n            target: match (self.target, rhs.target) {\n                (Some(Target::Content(lhs)), Some(Target::Content(rhs))) => {\n                    Some(Target::Content(lhs.join_as_regex(rhs)))\n                }\n\n                // TODO: Do we want to return an error here?\n                (lhs, rhs) => rhs.or(lhs),\n            },\n        }\n    }\n\n    /// Fetch all `merge`s of this `Query` with a list of query groups.\n    ///\n    /// This is useful to flatten out a nested tree of queries. For example, with the following input\n    /// string:\n    ///\n    /// ```text\n    /// (repo:enterprise-search or repo:query-planner) (org:bloop or org:google) ParseError\n    /// ```\n    ///\n    /// This method will effectively flatten out the combinators, resulting in the equivalent list\n    /// of queries:\n    ///\n    /// ```text\n    ///    (repo:enterprise-search org:bloop  ParseError)\n    /// or (repo:query-planner     org:bloop  ParseError)\n    /// or (repo:enterprise-search org:google ParseError)\n    /// or (repo:query-planner     org:google ParseError)\n    /// ```\n    ///\n    /// The input argument `iter` expects an iterator, where each item is another iterator over a\n    /// set of queries joined with `or`. Because a list of queries is effectively an `or`, you can\n    /// interpret the input as being a list of `or` groups that are `and`ed together.\n    fn cross<I>(self, mut iter: I) -> SmallVec<[Self; 1]>\n    where\n        I: Clone + Iterator,\n        I::Item: Iterator<Item = Self>,\n    {\n        if let Some(queries) = iter.next() {\n            let mut list = smallvec![];\n\n            for rhs in queries {\n                list.extend(self.clone().merge(rhs).cross(iter.clone()));\n            }\n\n            list\n        } else {\n            smallvec![self]\n        }\n    }\n\n    pub fn is_case_sensitive(&self) -> bool {\n        // defaults to false if unset\n        self.case_sensitive.unwrap_or_default()\n    }\n\n    fn set_global_regex(&mut self, value: Option<bool>) {\n        self.global_regex = value;\n        if let Some(true) = value {\n            self.org.as_mut().map(Literal::make_regex);\n            self.repo.as_mut().map(Literal::make_regex);\n            self.path.as_mut().map(Literal::make_regex);\n            self.target.as_mut().map(Target::make_regex);\n        }\n    }\n}\n\nimpl<'a> Query<'a> {\n    /// Get the `repo` value for this query as a plain string.\n    pub fn repo_str(&self) -> Option<String> {\n        self.repo\n            .as_ref()\n            .and_then(Literal::as_plain)\n            .map(Cow::into_owned)\n    }\n\n    /// Get the `branch` value for this query as a plain string.\n    pub fn branch_str(&self) -> Option<String> {\n        self.branch\n            .as_ref()\n            .and_then(Literal::as_plain)\n            .map(Cow::into_owned)\n    }\n}\n\nimpl<'a> Target<'a> {\n    /// Get the inner literal for this target, regardless of the variant.\n    pub fn literal_mut(&'a mut self) -> &mut Literal<'a> {\n        match self {\n            Self::Symbol(lit) => lit,\n            Self::Content(lit) => lit,\n        }\n    }\n\n    /// Get the inner literal for this target, regardless of the variant.\n    pub fn literal(&self) -> &Literal<'_> {\n        match self {\n            Self::Symbol(lit) => lit,\n            Self::Content(lit) => lit,\n        }\n    }\n\n    /// Get the symbol literal, if present\n    pub fn symbol(&self) -> Option<&Literal<'_>> {\n        match self {\n            Self::Symbol(lit) => Some(lit),\n            Self::Content(_) => None,\n        }\n    }\n\n    /// Get the content literal, if present\n    pub fn content(&self) -> Option<&Literal<'_>> {\n        match self {\n            Self::Symbol(_) => None,\n            Self::Content(lit) => Some(lit),\n        }\n    }\n\n    fn make_regex(&mut self) {\n        match self {\n            Self::Symbol(lit) => lit.make_regex(),\n            Self::Content(lit) => lit.make_regex(),\n        }\n    }\n}\n\n#[derive(pest_derive::Parser)]\n#[grammar = \"query/grammar.pest\"] // relative to src\nstruct PestParser;\n\n#[derive(Debug, PartialEq, thiserror::Error)]\npub enum ParseError {\n    #[error(\"parse error: {0:?}\")]\n    Pest(#[from] Box<pest::error::Error<Rule>>),\n    #[error(\"unparsed token: {0:?}\")]\n    UnparsedToken(String),\n    #[error(\"multiple mode designators\")]\n    MultiMode,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]\npub enum Literal<'a> {\n    Plain(LiteralInner<'a>),\n    Regex(LiteralInner<'a>),\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]\npub struct LiteralInner<'a> {\n    start: usize,\n    end: usize,\n    content: Cow<'a, str>,\n}\n\nimpl<'a> LiteralInner<'a> {\n    fn new(start: usize, end: usize, content: impl Into<Cow<'a, str>>) -> Self {\n        Self {\n            start,\n            end,\n            content: content.into(),\n        }\n    }\n\n    fn to_owned(&self) -> LiteralInner<'static> {\n        LiteralInner {\n            start: self.start,\n            end: self.end,\n            content: Cow::Owned(self.content.to_string()),\n        }\n    }\n}\n\nimpl<'a, T: AsRef<str>> From<T> for LiteralInner<'a> {\n    fn from(value: T) -> Self {\n        Self {\n            start: 0,\n            end: 0,\n            content: value.as_ref().to_owned().into(),\n        }\n    }\n}\n\nimpl<'a> Deref for LiteralInner<'a> {\n    type Target = str;\n\n    fn deref(&self) -> &Self::Target {\n        self.content.as_ref()\n    }\n}\n\nimpl<'a> Default for LiteralInner<'a> {\n    fn default() -> Self {\n        Self {\n            start: 0,\n            end: 0,\n            content: Cow::Borrowed(\"\"),\n        }\n    }\n}\n\nimpl<'a> From<Cow<'a, str>> for Literal<'a> {\n    fn from(value: Cow<'a, str>) -> Self {\n        Literal::Plain(value.clone().into())\n    }\n}\n\nimpl From<&String> for Literal<'static> {\n    fn from(value: &String) -> Self {\n        Literal::Plain(value.to_owned().into())\n    }\n}\n\nimpl From<&str> for Literal<'static> {\n    fn from(value: &str) -> Self {\n        Literal::Plain(value.to_owned().into())\n    }\n}\n\nimpl<'a> Default for Literal<'a> {\n    fn default() -> Self {\n        Literal::Plain(Default::default())\n    }\n}\n\nimpl<'a> Literal<'a> {\n    /// This drops position information, as it's not intelligible after the merge\n    fn join_as_regex(self, rhs: Self) -> Literal<'static> {\n        let lhs = self.regex_str();\n        let rhs = rhs.regex_str();\n        Literal::Regex(format!(\"{lhs}\\\\s+{rhs}\").into())\n    }\n\n    /// Convert this literal into a regex string.\n    ///\n    /// If this literal is a regex, it is returned as-is. If it is a plain text literal, it is\n    /// escaped first before returning.\n    pub fn regex_str(&self) -> Cow<'a, str> {\n        match self {\n            Self::Plain(text) => regex::escape(text).into(),\n            Self::Regex(r) => r.content.clone(),\n        }\n    }\n\n    pub fn regex(&self) -> Result<Regex, regex::Error> {\n        Regex::new(&self.regex_str())\n    }\n\n    pub fn as_plain(&self) -> Option<Cow<'a, str>> {\n        match self {\n            Self::Plain(p) => Some(p.content.clone()),\n            Self::Regex(..) => None,\n        }\n    }\n\n    /// Force this literal into the `Regex` variant.\n    fn make_regex(&mut self) {\n        *self = match mem::take(self) {\n            Self::Plain(s) | Self::Regex(s) => Self::Regex(s),\n        }\n    }\n\n    pub fn unwrap(self) -> Cow<'a, str> {\n        match self {\n            Literal::Plain(v) => v.content,\n            Literal::Regex(v) => v.content,\n        }\n    }\n\n    pub fn into_owned(self) -> Literal<'static> {\n        match self {\n            Literal::Plain(cow) => Literal::Plain(cow.to_owned()),\n            Literal::Regex(cow) => Literal::Regex(cow.to_owned()),\n        }\n    }\n\n    pub fn start(&self) -> usize {\n        match self {\n            Literal::Plain(inner) => inner.start,\n            Literal::Regex(inner) => inner.start,\n        }\n    }\n}\n\nimpl<'a> From<Pair<'a, Rule>> for Literal<'a> {\n    fn from(pair: Pair<'a, Rule>) -> Self {\n        let start = pair.as_span().start();\n        let end = pair.as_span().end();\n\n        match pair.as_rule() {\n            Rule::unquoted_literal => {\n                Self::Plain(LiteralInner::new(start, end, pair.as_str().trim()))\n            }\n            Rule::quoted_literal => {\n                Self::Plain(LiteralInner::new(start, end, unescape(pair.as_str(), '\"')))\n            }\n            Rule::single_quoted_literal => {\n                Self::Plain(LiteralInner::new(start, end, unescape(pair.as_str(), '\\'')))\n            }\n            Rule::regex_quoted_literal => {\n                Self::Regex(LiteralInner::new(start, end, unescape(pair.as_str(), '/')))\n            }\n            Rule::raw_text => Self::Plain(LiteralInner::new(start, end, pair.as_str().trim())),\n            _ => unreachable!(),\n        }\n    }\n}\n\nimpl<'a, 'b: 'a> AsRef<Cow<'a, str>> for Literal<'b> {\n    fn as_ref(&self) -> &Cow<'a, str> {\n        match self {\n            Literal::Plain(inner) => &inner.content,\n            Literal::Regex(inner) => &inner.content,\n        }\n    }\n}\n\nimpl Deref for Literal<'_> {\n    type Target = str;\n\n    fn deref(&self) -> &Self::Target {\n        match self {\n            Literal::Plain(inner) => inner.as_ref(),\n            Literal::Regex(inner) => inner.as_ref(),\n        }\n    }\n}\n\n/// Unescape a string, with a specific terminating character.\n///\n/// Newline and tab strings (`\\n` and `\\t`) are replaced with the respective character. Backslashes\n/// are preserved with a double escape (`\\\\`). If the terminating character is encountered, it is\n/// returned without a preceding backslash. All other escape characters are not interpreted, and\n/// backslashes are preserved.\n///\n///\n/// ```rust,ignore\n/// unescape(\"ab\\\\/c\", '/') = \"ab/c\"\n/// unescape(\"ab\\\\/c\", '\"') = \"ab\\\\/c\"\n/// unescape(\"ab\\\\nc\", '\"') = \"ab\\nc\"\n/// unescape(\"ab\\\\\\\"c\", '\"') = \"ab\\\\\\\"c\"\n/// ```\nfn unescape(s: &str, term: char) -> String {\n    let mut result = String::new();\n    let mut chars = s.chars();\n\n    while let Some(c) = chars.next() {\n        if c != '\\\\' {\n            result.push(c);\n            continue;\n        }\n\n        match chars.next() {\n            Some('n') => result.push('\\n'),\n            Some('t') => result.push('\\t'),\n            Some(c) if c == term => result.push(c),\n            Some(c) => {\n                result.push('\\\\');\n                result.push(c);\n            }\n            None => continue,\n        }\n    }\n\n    result\n}\n\n#[derive(Debug, PartialEq, Clone)]\nenum Expr<'a> {\n    Or(Vec<Expr<'a>>),\n    And(Vec<Expr<'a>>),\n\n    Org(Literal<'a>),\n    Repo(Literal<'a>),\n    Symbol(Literal<'a>),\n    Path(Literal<'a>),\n    Lang(Literal<'a>),\n    Content(Literal<'a>),\n    Branch(Literal<'a>),\n\n    CaseSensitive(bool),\n    Open(bool),\n    GlobalRegex(bool),\n}\n\nimpl<'a> Expr<'a> {\n    fn parse(pair: Pair<'a, Rule>, top_level: bool) -> Result<Self, Pair<'a, Rule>> {\n        use Expr::*;\n\n        Ok(match pair.as_rule() {\n            Rule::unquoted_literal\n            | Rule::quoted_literal\n            | Rule::single_quoted_literal\n            | Rule::regex_quoted_literal => Content(Literal::from(pair)),\n\n            Rule::content => Content(Literal::from(pair.into_inner().next().unwrap())),\n            Rule::path => Path(Literal::from(pair.into_inner().next().unwrap())),\n            Rule::repo => Repo(Literal::from(pair.into_inner().next().unwrap())),\n            Rule::symbol => Symbol(Literal::from(pair.into_inner().next().unwrap())),\n            Rule::org => Org(Literal::from(pair.into_inner().next().unwrap())),\n            Rule::branch => Branch(Literal::from(pair.into_inner().next().unwrap())),\n            Rule::lang => Lang(Literal::from(pair.into_inner().next().unwrap())),\n\n            Rule::open => {\n                let inner = pair.into_inner().next().unwrap();\n                match inner.as_str() {\n                    \"true\" => Open(true),\n                    \"false\" => Open(false),\n                    _ => unreachable!(),\n                }\n            }\n\n            Rule::case => {\n                // Avoid parsing this flag unless it's at the top level.\n                if !top_level {\n                    return Err(pair);\n                }\n\n                let inner = pair.into_inner().next().unwrap();\n                match inner.as_rule() {\n                    Rule::case_sensitive => CaseSensitive(true),\n                    Rule::case_ignore => CaseSensitive(false),\n                    _ => unreachable!(),\n                }\n            }\n\n            Rule::global_regex => {\n                // Avoid parsing this flag unless it's at the top level.\n                if !top_level {\n                    return Err(pair);\n                }\n\n                let inner = pair.into_inner().next().unwrap();\n                match inner.as_str() {\n                    \"true\" => GlobalRegex(true),\n                    \"false\" => GlobalRegex(false),\n                    _ => unreachable!(),\n                }\n            }\n\n            Rule::group => {\n                // Descend into the group, disabling the `top_level` flag.\n                Self::parse(pair.into_inner().next().unwrap(), false)?\n            }\n\n            Rule::intersection => {\n                let mut unions = Vec::new();\n                let mut els = Vec::new();\n\n                for pair in pair.into_inner() {\n                    match pair.as_rule() {\n                        Rule::or => unions.push(mem::take(&mut els)),\n                        _ => els.push(Self::parse(pair, top_level)?),\n                    }\n                }\n\n                if !unions.is_empty() {\n                    let unions = unions.into_iter().chain(Some(els)).map(And).collect();\n                    Or(unions)\n                } else {\n                    And(els)\n                }\n            }\n\n            // It's not possible to declare this rule as both silent and atomic in pest, so we\n            // descend when we encounter it, simulating a silent rule.\n            //\n            // https://github.com/pest-parser/pest/issues/520\n            Rule::element => Self::parse(pair.into_inner().next().unwrap(), top_level)?,\n\n            _ => Err(pair)?,\n        })\n    }\n}\n\n/// Parse an input query string into a list of top-level `Query`s.\npub fn parse(query: &str) -> Result<Vec<Query<'_>>, ParseError> {\n    let pair = PestParser::parse(Rule::query, query)\n        .map_err(Box::new)?\n        .next()\n        .unwrap();\n    let root =\n        Expr::parse(pair, true).map_err(|pair| ParseError::UnparsedToken(pair.to_string()))?;\n\n    let mut qs = flatten(root);\n\n    // Find and redistribute global options.\n    let global_regex = qs.iter().fold(None, |a, e| e.global_regex.or(a));\n    let case_sensitive = qs.iter().fold(None, |a, e| e.case_sensitive.or(a));\n\n    for q in qs.iter_mut() {\n        q.set_global_regex(global_regex);\n        q.case_sensitive = case_sensitive;\n    }\n\n    Ok(qs.into_vec())\n}\n\npub fn parse_nl(query: &str) -> Result<SemanticQuery<'_>, ParseError> {\n    let raw_query = query.to_string();\n    let mut target = \"\".to_string();\n\n    let pairs = PestParser::parse(Rule::nl_query, query).map_err(Box::new)?;\n\n    let mut repos = Vec::new();\n    let mut paths = Vec::new();\n    let mut langs = Vec::new();\n    let mut branch = Vec::new();\n\n    let mut extend_query = |q: &str| {\n        if !target.is_empty() {\n            target += \" \";\n        }\n        target += q;\n    };\n\n    for pair in pairs {\n        match pair.as_rule() {\n            Rule::repo => {\n                let item = Literal::from(pair.into_inner().next().unwrap());\n                repos.push(item);\n            }\n            Rule::path => {\n                let item = Literal::from(pair.into_inner().next().unwrap());\n                extend_query(&item);\n                paths.push(item);\n            }\n            Rule::branch => {\n                let item = Literal::from(pair.into_inner().next().unwrap());\n                branch.push(item);\n            }\n            Rule::lang => {\n                let inner = pair.into_inner().next().unwrap();\n                let item = Literal::Plain(LiteralInner {\n                    content: super::languages::parse_alias(inner.as_str()),\n                    start: inner.as_span().start(),\n                    end: inner.as_span().end(),\n                });\n\n                extend_query(&item);\n                langs.push(item);\n            }\n            Rule::raw_text => {\n                let rhs = Literal::from(pair);\n                extend_query(&rhs);\n            }\n            _ => {}\n        }\n    }\n\n    Ok(SemanticQuery {\n        raw_query,\n        repos,\n        paths,\n        langs,\n        branch,\n        target: if target.is_empty() {\n            None\n        } else {\n            Some(Literal::from(&target))\n        },\n    })\n}\n\nfn flatten(root: Expr<'_>) -> SmallVec<[Query<'_>; 1]> {\n    match root {\n        Expr::Repo(repo) => smallvec![Query {\n            repo: Some(repo),\n            ..Default::default()\n        }],\n        Expr::Branch(branch) => smallvec![Query {\n            branch: Some(branch),\n            ..Default::default()\n        }],\n        Expr::Org(org) => smallvec![Query {\n            org: Some(org),\n            ..Default::default()\n        }],\n        Expr::Path(path) => smallvec![Query {\n            path: Some(path),\n            ..Default::default()\n        }],\n\n        Expr::Symbol(sym) => smallvec![Query {\n            target: Some(Target::Symbol(sym)),\n            ..Default::default()\n        }],\n        Expr::Lang(lang) => smallvec![Query {\n            lang: Some(super::languages::parse_alias(&lang).into()),\n            ..Default::default()\n        }],\n        Expr::Content(lit) => smallvec![Query {\n            target: Some(Target::Content(lit)),\n            ..Default::default()\n        }],\n\n        Expr::CaseSensitive(case_sensitive) => smallvec![Query {\n            case_sensitive: Some(case_sensitive),\n            ..Default::default()\n        }],\n        Expr::Open(open) => smallvec![Query {\n            open: Some(open),\n            ..Default::default()\n        }],\n        Expr::GlobalRegex(flag) => smallvec![Query {\n            global_regex: Some(flag),\n            ..Default::default()\n        }],\n\n        // Simple merge\n        Expr::Or(exprs) => {\n            let mut queries = smallvec![];\n            for e in exprs {\n                queries.extend(flatten(e));\n            }\n            queries\n        }\n\n        // A more complex cross merge.\n        Expr::And(els) => {\n            Query::default().cross(els.iter().cloned().map(flatten).map(SmallVec::into_iter))\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use pretty_assertions::assert_eq;\n\n    #[test]\n    fn basic_parse() {\n        assert_eq!(\n            parse(\"ParseError\").unwrap(),\n            vec![Query {\n                target: Some(Target::Content(Literal::Plain(LiteralInner {\n                    start: 0,\n                    end: 10,\n                    content: \"ParseError\".into()\n                }))),\n                ..Query::default()\n            }],\n        );\n\n        assert_eq!(\n            parse(\"org:bloopai repo:enterprise-search branch:origin/main ParseError\").unwrap(),\n            vec![Query {\n                repo: Some(Literal::Plain(LiteralInner {\n                    start: 17,\n                    end: 34,\n                    content: \"enterprise-search\".into()\n                })),\n                org: Some(Literal::Plain(LiteralInner {\n                    start: 4,\n                    end: 11,\n                    content: \"bloopai\".into()\n                })),\n                branch: Some(Literal::Plain(LiteralInner {\n                    start: 42,\n                    end: 53,\n                    content: \"origin/main\".into()\n                })),\n                target: Some(Target::Content(Literal::Plain(LiteralInner {\n                    start: 54,\n                    end: 64,\n                    content: \"ParseError\".into()\n                }))),\n                ..Query::default()\n            }],\n        );\n\n        assert_eq!(\n            parse(\"org:bloopai repo:enterprise-search ParseError\").unwrap(),\n            vec![Query {\n                repo: Some(Literal::Plain(LiteralInner {\n                    start: 17,\n                    end: 34,\n                    content: \"enterprise-search\".into()\n                })),\n                org: Some(Literal::Plain(LiteralInner {\n                    start: 4,\n                    end: 11,\n                    content: \"bloopai\".into()\n                })),\n                target: Some(Target::Content(Literal::Plain(LiteralInner {\n                    start: 35,\n                    end: 45,\n                    content: \"ParseError\".into()\n                }))),\n                ..Query::default()\n            }],\n        );\n\n        assert_eq!(\n            parse(\"content:ParseError\").unwrap(),\n            vec![Query {\n                target: Some(Target::Content(Literal::Plain(LiteralInner {\n                    start: 8,\n                    end: 18,\n                    content: \"ParseError\".into()\n                }))),\n                ..Query::default()\n            }],\n        );\n\n        // Here the last target operator takes precedence. Should we return an error instead?\n        assert_eq!(\n            parse(\"path:foo.c create_foo symbol:bar\").unwrap(),\n            vec![Query {\n                path: Some(Literal::Plain(LiteralInner {\n                    start: 5,\n                    end: 10,\n                    content: \"foo.c\".into()\n                })),\n                target: Some(Target::Symbol(Literal::Plain(LiteralInner {\n                    start: 29,\n                    end: 32,\n                    content: \"bar\".into()\n                }))),\n                ..Query::default()\n            }],\n        );\n\n        assert_eq!(\n            parse(\"case:ignore Parse\").unwrap(),\n            vec![Query {\n                case_sensitive: Some(false),\n                target: Some(Target::Content(Literal::Plain(LiteralInner {\n                    start: 12,\n                    end: 17,\n                    content: \"Parse\".into()\n                }))),\n                ..Query::default()\n            }],\n        );\n    }\n\n    #[test]\n    fn intersection_parse() {\n        assert_eq!(\n            parse(\"repo:foo ParseError or repo:bar\").unwrap(),\n            vec![\n                Query {\n                    repo: Some(Literal::Plain(LiteralInner {\n                        start: 5,\n                        end: 8,\n                        content: \"foo\".into()\n                    })),\n                    target: Some(Target::Content(Literal::Plain(LiteralInner {\n                        start: 9,\n                        end: 19,\n                        content: \"ParseError\".into()\n                    }))),\n                    ..Query::default()\n                },\n                Query {\n                    repo: Some(Literal::Plain(LiteralInner {\n                        start: 28,\n                        end: 31,\n                        content: \"bar\".into()\n                    })),\n                    ..Query::default()\n                },\n            ],\n        );\n\n        // Flip the intersection order.\n        assert_eq!(\n            parse(\"repo:bar or repo:foo ParseError\").unwrap(),\n            vec![\n                Query {\n                    repo: Some(Literal::Plain(LiteralInner {\n                        start: 5,\n                        end: 8,\n                        content: \"bar\".into()\n                    })),\n                    ..Query::default()\n                },\n                Query {\n                    repo: Some(Literal::Plain(LiteralInner {\n                        start: 17,\n                        end: 20,\n                        content: \"foo\".into()\n                    })),\n                    target: Some(Target::Content(Literal::Plain(LiteralInner {\n                        start: 21,\n                        end: 31,\n                        content: \"ParseError\".into()\n                    }))),\n                    ..Query::default()\n                },\n            ],\n        );\n    }\n\n    #[test]\n    fn complex_nested_combinators_expr() {\n        // (((repo:foo xyz) or repo:abc) (repo:fred or repo:grub) org:bloop)\n        //\n        // -> 4 independent terms [\n        //    (org:bloop repo:fred xyz),\n        //    (org:bloop repo:grub xyz),\n        //    (org:bloop repo:fred),\n        //    (org:bloop repo:grub),\n        // ]\n\n        let terms = flatten(Expr::And(vec![\n            Expr::Or(vec![\n                Expr::And(vec![\n                    Expr::Repo(Literal::Plain(\"foo\".into())),\n                    Expr::Content(Literal::Plain(\"xyz\".into())),\n                ]),\n                Expr::Repo(Literal::Plain(\"abc\".into())),\n            ]),\n            Expr::Or(vec![\n                Expr::Repo(Literal::Plain(\"fred\".into())),\n                Expr::Repo(Literal::Plain(\"grub\".into())),\n            ]),\n            Expr::Org(Literal::Plain(\"bloop\".into())),\n        ]));\n\n        assert_eq!(\n            terms.into_vec(),\n            vec![\n                Query {\n                    repo: Some(Literal::Plain(\"fred\".into())),\n                    org: Some(Literal::Plain(\"bloop\".into())),\n                    target: Some(Target::Content(Literal::Plain(\"xyz\".into()))),\n                    ..Query::default()\n                },\n                Query {\n                    repo: Some(Literal::Plain(\"grub\".into())),\n                    org: Some(Literal::Plain(\"bloop\".into())),\n                    target: Some(Target::Content(Literal::Plain(\"xyz\".into()))),\n                    ..Query::default()\n                },\n                Query {\n                    repo: Some(Literal::Plain(\"fred\".into())),\n                    org: Some(Literal::Plain(\"bloop\".into())),\n                    ..Query::default()\n                },\n                Query {\n                    repo: Some(Literal::Plain(\"grub\".into())),\n                    org: Some(Literal::Plain(\"bloop\".into())),\n                    ..Query::default()\n                },\n            ]\n        );\n    }\n\n    #[test]\n    fn complex_nested_combinators_parse() {\n        assert_eq!(\n            parse(\"(((repo:foo xyz) or repo:abc) (repo:fred or repo:grub) org:bloop)\").unwrap(),\n            vec![\n                Query {\n                    repo: Some(Literal::Plain(LiteralInner {\n                        start: 36,\n                        end: 40,\n                        content: \"fred\".into()\n                    })),\n                    org: Some(Literal::Plain(LiteralInner {\n                        start: 59,\n                        end: 64,\n                        content: \"bloop\".into()\n                    })),\n                    target: Some(Target::Content(Literal::Plain(LiteralInner {\n                        start: 12,\n                        end: 15,\n                        content: \"xyz\".into()\n                    }))),\n                    ..Query::default()\n                },\n                Query {\n                    repo: Some(Literal::Plain(LiteralInner {\n                        start: 49,\n                        end: 53,\n                        content: \"grub\".into()\n                    })),\n                    org: Some(Literal::Plain(LiteralInner {\n                        start: 59,\n                        end: 64,\n                        content: \"bloop\".into()\n                    })),\n                    target: Some(Target::Content(Literal::Plain(LiteralInner {\n                        start: 12,\n                        end: 15,\n                        content: \"xyz\".into()\n                    }))),\n                    ..Query::default()\n                },\n                Query {\n                    repo: Some(Literal::Plain(LiteralInner {\n                        start: 36,\n                        end: 40,\n                        content: \"fred\".into()\n                    })),\n                    org: Some(Literal::Plain(LiteralInner {\n                        start: 59,\n                        end: 64,\n                        content: \"bloop\".into()\n                    })),\n                    ..Query::default()\n                },\n                Query {\n                    repo: Some(Literal::Plain(LiteralInner {\n                        start: 49,\n                        end: 53,\n                        content: \"grub\".into()\n                    })),\n                    org: Some(Literal::Plain(LiteralInner {\n                        start: 59,\n                        end: 64,\n                        content: \"bloop\".into()\n                    })),\n                    ..Query::default()\n                },\n            ],\n        );\n    }\n\n    #[test]\n    fn complex_multiple_parse_types() {\n        assert_eq!(\n            parse(\"(repo:bloop or repo:google) Parser or repo:zoekt Parsing or (symbol:Compiler or (org:bloop repo:enterprise-search))\").unwrap(),\n            vec![\n                Query {\n                    repo: Some(Literal::Plain(LiteralInner {\n                        start: 6,\n                        end: 11,\n                        content: \"bloop\".into()\n                    })),\n                    target: Some(Target::Content(Literal::Plain(LiteralInner {\n                        start: 28,\n                        end: 34,\n                        content: \"Parser\".into()\n                    }))),\n                    ..Query::default()\n                },\n                Query {\n                    repo: Some(Literal::Plain(LiteralInner {\n                        start: 20,\n                        end: 26,\n                        content: \"google\".into()\n                    })),\n                    target: Some(Target::Content(Literal::Plain(LiteralInner {\n                        start: 28,\n                        end: 34,\n                        content: \"Parser\".into()\n                    }))),\n                    ..Query::default()\n                },\n                Query {\n                    repo: Some(Literal::Plain(LiteralInner {\n                        start: 43,\n                        end: 48,\n                        content: \"zoekt\".into()\n                    })),\n                    target: Some(Target::Content(Literal::Plain(LiteralInner {\n                        start: 49,\n                        end: 56,\n                        content: \"Parsing\".into()\n                    }))),\n                    ..Query::default()\n                },\n                Query {\n                    target: Some(Target::Symbol(Literal::Plain(LiteralInner {\n                        start: 68,\n                        end: 76,\n                        content: \"Compiler\".into()\n                    }))),\n                    ..Query::default()\n                },\n                Query {\n                    repo: Some(Literal::Plain(LiteralInner {\n                        start: 96,\n                        end: 113,\n                        content: \"enterprise-search\".into()\n                    })),\n                    org: Some(Literal::Plain(LiteralInner {\n                        start: 85,\n                        end: 90,\n                        content: \"bloop\".into()\n                    })),\n                    ..Query::default()\n                },\n            ],\n        );\n    }\n\n    #[test]\n    fn slash_in_path() {\n        assert_eq!(\n            parse(\"path:foo/bar.js\").unwrap(),\n            vec![Query {\n                path: Some(Literal::Plain(LiteralInner {\n                    start: 5,\n                    end: 15,\n                    content: \"foo/bar.js\".into(),\n                })),\n                ..Query::default()\n            }],\n        );\n    }\n\n    #[test]\n    fn literal_join_as_regex() {\n        let out = Literal::Plain(\"foo\".into()).join_as_regex(Literal::Plain(\"bar\".into()));\n        assert_eq!(out, Literal::Regex(\"foo\\\\s+bar\".into()));\n\n        let out = Literal::Regex(\"f(oo)\".into()).join_as_regex(Literal::Regex(\"(bar|quux)\".into()));\n        assert_eq!(out, Literal::Regex(\"f(oo)\\\\s+(bar|quux)\".into()));\n\n        // Test escaping.\n        let out = Literal::Plain(\"f(oo)\".into()).join_as_regex(Literal::Plain(\"(bar|quux)\".into()));\n        assert_eq!(out, Literal::Regex(\"f\\\\(oo\\\\)\\\\s+\\\\(bar\\\\|quux\\\\)\".into()));\n    }\n\n    #[test]\n    fn lang_path_filter() {\n        assert_eq!(\n            parse(\"lang:Rust path:server\").unwrap(),\n            vec![Query {\n                path: Some(Literal::Plain(LiteralInner {\n                    start: 15,\n                    end: 21,\n                    content: \"server\".into()\n                })),\n                lang: Some(Literal::Plain(\"rust\".into())),\n                ..Query::default()\n            }],\n        );\n    }\n\n    #[test]\n    fn enable_open() {\n        assert_eq!(\n            parse(\"open:true path:server/bleep/Cargo.toml\").unwrap(),\n            vec![Query {\n                open: Some(true),\n                path: Some(Literal::Plain(LiteralInner {\n                    start: 15,\n                    end: 38,\n                    content: \"server/bleep/Cargo.toml\".into()\n                })),\n                ..Query::default()\n            }],\n        );\n\n        assert_eq!(\n            parse(\"open:false path:server/bleep/Cargo.toml\").unwrap(),\n            vec![Query {\n                open: Some(false),\n                path: Some(Literal::Plain(LiteralInner {\n                    start: 16,\n                    end: 39,\n                    content: \"server/bleep/Cargo.toml\".into()\n                })),\n                ..Query::default()\n            }],\n        );\n\n        assert_eq!(\n            parse(\"path:server/bleep/Cargo.toml\").unwrap(),\n            vec![Query {\n                open: None,\n                path: Some(Literal::Plain(LiteralInner {\n                    start: 5,\n                    end: 28,\n                    content: \"server/bleep/Cargo.toml\".into()\n                })),\n                ..Query::default()\n            }],\n        );\n    }\n\n    #[test]\n    fn special_chars() {\n        assert_eq!(\n            parse(\"foo\\\\nbar\\\\tquux\").unwrap(),\n            vec![Query {\n                target: Some(Target::Content(Literal::Plain(LiteralInner {\n                    start: 0,\n                    end: 14,\n                    content: \"foo\\\\nbar\\\\tquux\".into()\n                }))),\n                ..Query::default()\n            }],\n        );\n\n        assert_eq!(\n            parse(\"/^\\\\b\\\\B\\\\w\\\\Wfoo\\\\d\\\\D$/\").unwrap(),\n            vec![Query {\n                target: Some(Target::Content(Literal::Regex(LiteralInner {\n                    start: 1,\n                    end: 18,\n                    content: \"^\\\\b\\\\B\\\\w\\\\Wfoo\\\\d\\\\D$\".into()\n                }))),\n                ..Query::default()\n            }],\n        );\n    }\n\n    #[test]\n    fn test_global_regex() {\n        assert_eq!(\n            parse(\"global_regex:true foo\").unwrap(),\n            vec![Query {\n                global_regex: Some(true),\n                target: Some(Target::Content(Literal::Regex(LiteralInner {\n                    start: 18,\n                    end: 21,\n                    content: \"foo\".into()\n                }))),\n                ..Query::default()\n            }],\n        );\n\n        // Don't conflict with per-term regexes.\n        assert_eq!(\n            parse(\"global_regex:true /foo/\").unwrap(),\n            vec![Query {\n                global_regex: Some(true),\n                target: Some(Target::Content(Literal::Regex(LiteralInner {\n                    start: 19,\n                    end: 22,\n                    content: \"foo\".into()\n                }))),\n                ..Query::default()\n            }],\n        );\n\n        // Lack of the flag should result in a `None` value.\n        assert_eq!(\n            parse(\"foo\").unwrap(),\n            vec![Query {\n                target: Some(Target::Content(Literal::Plain(LiteralInner {\n                    start: 0,\n                    end: 3,\n                    content: \"foo\".into()\n                }))),\n                ..Query::default()\n            }],\n        );\n\n        // Can only apply this flag at the top-level, not inside groups.\n        assert!(parse(\"(global_regex:true foo)\").is_err());\n\n        // Later uses at the top-level override previous uses.\n        assert_eq!(\n            parse(\"global_regex:false org:bloopai repo:bloop path:server foo or repo:google bar global_regex:true\").unwrap(),\n            vec![\n                Query {\n                    global_regex: Some(true),\n                    org: Some(Literal::Regex(LiteralInner {\n                        start: 23,\n                        end: 30,\n                        content: \"bloopai\".into(),\n                    })),\n                    repo: Some(Literal::Regex(LiteralInner {\n                        start: 36,\n                        end: 41,\n                        content: \"bloop\".into(),\n                    })),\n                    path: Some(Literal::Regex(LiteralInner {\n                        start: 47,\n                        end: 53,\n                        content: \"server\".into(),\n                    })),\n                    target: Some(Target::Content(Literal::Regex(LiteralInner {\n                        start: 54,\n                        end: 57,\n                        content: \"foo\".into(),\n                    }))),\n                    ..Query::default()\n                },\n                Query {\n                    global_regex: Some(true),\n                    repo: Some(Literal::Regex(LiteralInner {\n                        start: 66,\n                        end: 72,\n                        content: \"google\".into(),\n                    })),\n                    target: Some(Target::Content(Literal::Regex(LiteralInner {\n                        start: 73,\n                        end: 76,\n                        content: \"bar\".into(),\n                    }))),\n                    ..Query::default()\n                },\n            ],\n        );\n\n        // Make sure that later values of `false` override previous values of `true`.\n        assert_eq!(\n            parse(\"global_regex:true foo or bar global_regex:false\").unwrap(),\n            vec![\n                Query {\n                    global_regex: Some(false),\n                    target: Some(Target::Content(Literal::Plain(LiteralInner {\n                        start: 18,\n                        end: 21,\n                        content: \"foo\".into(),\n                    }))),\n                    ..Query::default()\n                },\n                Query {\n                    global_regex: Some(false),\n                    target: Some(Target::Content(Literal::Plain(LiteralInner {\n                        start: 25,\n                        end: 28,\n                        content: \"bar\".into(),\n                    }))),\n                    ..Query::default()\n                },\n            ],\n        );\n    }\n\n    #[test]\n    fn case_ignore_affinity() {\n        // `case:` is special, it binds globally to the entire query string.\n\n        assert_eq!(\n            parse(\"foo or bar case:ignore\").unwrap(),\n            vec![\n                Query {\n                    case_sensitive: Some(false),\n                    target: Some(Target::Content(Literal::Plain(LiteralInner {\n                        start: 0,\n                        end: 3,\n                        content: \"foo\".into()\n                    }))),\n                    ..Query::default()\n                },\n                Query {\n                    case_sensitive: Some(false),\n                    target: Some(Target::Content(Literal::Plain(LiteralInner {\n                        start: 7,\n                        end: 10,\n                        content: \"bar\".into()\n                    }))),\n                    ..Query::default()\n                },\n            ],\n        );\n    }\n\n    #[test]\n    fn or_prefix() {\n        assert_eq!(\n            parse(\"org\").unwrap(),\n            vec![Query {\n                target: Some(Target::Content(Literal::Plain(LiteralInner {\n                    start: 0,\n                    end: 3,\n                    content: \"org\".into()\n                }))),\n                ..Query::default()\n            },],\n        );\n\n        assert_eq!(\n            parse(\"org or orange\").unwrap(),\n            vec![\n                Query {\n                    target: Some(Target::Content(Literal::Plain(LiteralInner {\n                        start: 0,\n                        end: 3,\n                        content: \"org\".into()\n                    }))),\n                    ..Query::default()\n                },\n                Query {\n                    target: Some(Target::Content(Literal::Plain(LiteralInner {\n                        start: 7,\n                        end: 13,\n                        content: \"orange\".into()\n                    }))),\n                    ..Query::default()\n                },\n            ],\n        );\n    }\n\n    #[test]\n    fn or_suffix() {\n        assert_eq!(\n            parse(\"for\").unwrap(),\n            vec![Query {\n                target: Some(Target::Content(Literal::Plain(LiteralInner {\n                    start: 0,\n                    end: 3,\n                    content: \"for\".into()\n                }))),\n                ..Query::default()\n            },],\n        );\n\n        assert_eq!(\n            parse(\"for or error\").unwrap(),\n            vec![\n                Query {\n                    target: Some(Target::Content(Literal::Plain(LiteralInner {\n                        start: 0,\n                        end: 3,\n                        content: \"for\".into()\n                    }))),\n                    ..Query::default()\n                },\n                Query {\n                    target: Some(Target::Content(Literal::Plain(LiteralInner {\n                        start: 7,\n                        end: 12,\n                        content: \"error\".into()\n                    }))),\n                    ..Query::default()\n                },\n            ],\n        );\n    }\n\n    #[test]\n    fn test_complex_parse() {\n        let mut q = parse(r#\"(?:[a-z0-9!#$%&'*+\\/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+\\/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])\"#).unwrap();\n\n        // Make sure that this regex successfully compiles.\n        q[0].target\n            .take()\n            .unwrap()\n            .content()\n            .unwrap()\n            .regex()\n            .unwrap();\n    }\n\n    #[test]\n    fn nl_parse() {\n        assert_eq!(\n            parse_nl(\"what is background color? lang:tsx repo:bloop\").unwrap(),\n            SemanticQuery {\n                raw_query: \"what is background color? lang:tsx repo:bloop\".to_string(),\n                target: Some(Literal::Plain(\"what is background color? tsx\".into())),\n                langs: [Literal::Plain(LiteralInner {\n                    start: 31,\n                    end: 34,\n                    content: \"tsx\".into()\n                })]\n                .into(),\n                repos: [Literal::Plain(LiteralInner {\n                    start: 40,\n                    end: 45,\n                    content: \"bloop\".into()\n                })]\n                .into(),\n                paths: [].into(),\n                branch: [].into()\n            },\n        );\n    }\n\n    #[test]\n    fn nl_parse_dedup_similar_filters() {\n        let q = parse_nl(\"what is background color? lang:tsx repo:bloop repo:bloop\").unwrap();\n        assert_eq!(q.repos().count(), 2);\n    }\n\n    #[test]\n    fn nl_parse_multiple_filters() {\n        assert_eq!(\n            parse_nl(\"what is background color? lang:tsx lang:ts repo:bloop repo:bar path:server/bleep repo:baz\").unwrap(),\n            SemanticQuery {\n                raw_query: \"what is background color? lang:tsx lang:ts repo:bloop repo:bar path:server/bleep repo:baz\".to_string(),\n                target: Some(Literal::Plain(\"what is background color? tsx typescript server/bleep\".into())),\n                langs: [\n                    Literal::Plain(LiteralInner {\n                        start: 31,\n                        end: 34,\n                        content: \"tsx\".into()\n                    }),\n                    Literal::Plain(LiteralInner {\n                        start: 40,\n                        end: 42,\n                        content: \"typescript\".into()\n                    })\n                ]\n                .into(),\n                branch: [].into(),\n                repos: [\n                    Literal::Plain(LiteralInner {\n                        start: 48,\n                        end: 53,\n                        content: \"bloop\".into(),\n                    }),\n                    Literal::Plain(LiteralInner {\n                        start: 59,\n                        end: 62,\n                        content: \"bar\".into(),\n                    }),\n                    Literal::Plain(LiteralInner {\n                        start: 86,\n                        end: 89,\n                        content: \"baz\".into(),\n                    }),\n                ]\n                .into(),\n                paths: [Literal::Plain(LiteralInner {\n                    start: 68,\n                    end: 80,\n                    content: \"server/bleep\".into(),\n                })]\n                .into(),\n            },\n        );\n    }\n\n    #[test]\n    fn nl_consume_flags() {\n        assert_eq!(\n            parse_nl(\n                \"what is background color of lang:tsx files? repo:bloop org:bloop symbol:foo open:true\"\n            )\n            .unwrap(),\n            SemanticQuery {\n                raw_query:\n                    \"what is background color of lang:tsx files? repo:bloop org:bloop symbol:foo open:true\"\n                        .to_string(),\n                target: Some(Literal::Plain(\"what is background color of tsx files?\".into())),\n                langs: [Literal::Plain(LiteralInner {\n                    start: 33,\n                    end: 36,\n                    content: \"tsx\".into()\n                })]\n                .into(),\n                repos: [Literal::Plain(LiteralInner {\n                    start: 49,\n                    end: 54,\n                    content: \"bloop\".into()\n                })]\n                .into(),\n                paths: [].into(),\n                branch: [].into(),\n            }\n        );\n\n        assert_eq!(\n            parse_nl(\"case:ignore why are languages excluded from ctags? branch:main\").unwrap(),\n            SemanticQuery {\n                raw_query: \"case:ignore why are languages excluded from ctags? branch:main\"\n                    .to_string(),\n                target: Some(Literal::Plain(\n                    \"why are languages excluded from ctags?\".into()\n                )),\n                branch: [Literal::Plain(LiteralInner {\n                    start: 58,\n                    end: 62,\n                    content: \"main\".into()\n                })]\n                .into(),\n                ..Default::default()\n            }\n        );\n    }\n\n    // NL queries should permit arbitrary text in the `target` field, such as `(` and `|`\n    #[test]\n    fn nl_parse_arbitrary_text() {\n        let queries = [\n            \"explain analytics (in the frontend)\",\n            \"repo:bloop path:server start the server\",\n            \":613330{})/.[|^%@!Z\",\n        ];\n        for q in queries {\n            assert!(parse_nl(q).is_ok());\n        }\n    }\n\n    #[test]\n    fn escape_characters() {\n        assert_eq!(\n            parse(\"'foo\\\\'bar'\").unwrap(),\n            vec![Query {\n                target: Some(Target::Content(Literal::Plain(LiteralInner {\n                    start: 1,\n                    end: 9,\n                    content: \"foo'bar\".into()\n                }))),\n                ..Query::default()\n            }],\n        );\n\n        assert_eq!(\n            parse(r#\"\"foo\\\"bar\"\"#).unwrap(),\n            vec![Query {\n                target: Some(Target::Content(Literal::Plain(LiteralInner {\n                    start: 1,\n                    end: 9,\n                    content: \"foo\\\"bar\".into()\n                }))),\n                ..Query::default()\n            }],\n        );\n\n        assert_eq!(\n            parse(\"/foo\\\\/bar/\").unwrap(),\n            vec![Query {\n                target: Some(Target::Content(Literal::Regex(LiteralInner {\n                    start: 1,\n                    end: 9,\n                    content: \"foo/bar\".into()\n                }))),\n                ..Query::default()\n            }],\n        );\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/query/planner/optimize.rs",
    "content": "use super::{Fragment, Op};\n\n/// Recursively optimize a fragment.\npub(super) fn run(fragment: Fragment) -> Fragment {\n    match fragment {\n        f @ Fragment::Literal(_) | f @ Fragment::Break => f,\n        Fragment::Dense(op, sub) => {\n            let children = sub.into_iter().map(run).collect();\n            let fragment = Fragment::Dense(op, children);\n\n            match op {\n                Op::And => inline(flatten_and(fragment)),\n                Op::Or => flatten_or(fragment),\n            }\n        }\n    }\n}\n\nfn flatten_or(fragment: Fragment) -> Fragment {\n    if let Fragment::Dense(Op::Or, sub) = fragment {\n        let mut simplified = Vec::new();\n\n        for child in sub {\n            if let Fragment::Dense(Op::Or, sub) = child {\n                simplified.extend(sub);\n            } else {\n                simplified.push(child);\n            }\n        }\n\n        Fragment::Dense(Op::Or, simplified)\n    } else {\n        fragment\n    }\n}\n\nfn flatten_and(fragment: Fragment) -> Fragment {\n    if let Fragment::Dense(Op::And, sub) = fragment {\n        let mut simplified = Vec::new();\n\n        for child in sub {\n            if let Fragment::Dense(Op::And, sub) = child {\n                simplified.extend(sub);\n            } else {\n                simplified.push(child);\n            }\n        }\n\n        Fragment::Dense(Op::And, simplified)\n    } else {\n        fragment\n    }\n}\n\nfn inline(fragment: Fragment) -> Fragment {\n    if let Fragment::Dense(Op::And, parent) = fragment {\n        let mut out = Vec::new();\n\n        for rhs in parent {\n            let lhs = if let Some(lhs) = out.pop() {\n                lhs\n            } else {\n                out.push(rhs);\n                continue;\n            };\n\n            match (lhs, rhs) {\n                (Fragment::Dense(Op::Or, sub), rhs) => {\n                    let mut sub2 = Vec::new();\n                    for lhs in sub.into_iter() {\n                        sub2.push(lhs.and(rhs.clone()));\n                    }\n                    out.push(Fragment::Dense(Op::Or, sub2));\n                }\n\n                (lhs, Fragment::Dense(Op::Or, sub)) => {\n                    let mut sub2 = Vec::new();\n                    for rhs in sub.into_iter() {\n                        sub2.push(lhs.clone().and(rhs));\n                    }\n                    out.push(Fragment::Dense(Op::Or, sub2));\n                }\n\n                (lhs, rhs) => {\n                    out.push(lhs.and(rhs));\n                }\n            }\n        }\n\n        if out.len() == 1 {\n            out.pop().unwrap()\n        } else {\n            Fragment::Dense(Op::Or, out)\n        }\n    } else {\n        fragment\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use pretty_assertions::assert_eq;\n\n    #[test]\n    fn basic_nested_or() {\n        let fragment = Fragment::Dense(\n            Op::Or,\n            vec![\n                Fragment::Dense(\n                    Op::Or,\n                    vec![\n                        Fragment::Literal(\"abc\".into()),\n                        Fragment::Literal(\"def\".into()),\n                    ],\n                ),\n                Fragment::Literal(\"ghi\".into()),\n            ],\n        );\n\n        assert_eq!(\n            flatten_or(fragment),\n            Fragment::Dense(\n                Op::Or,\n                vec![\n                    Fragment::Literal(\"abc\".into()),\n                    Fragment::Literal(\"def\".into()),\n                    Fragment::Literal(\"ghi\".into()),\n                ],\n            )\n        );\n    }\n\n    #[test]\n    fn basic_inline() {\n        let fragment = Fragment::Dense(\n            Op::And,\n            vec![\n                Fragment::Literal(\"123\".into()),\n                Fragment::Dense(\n                    Op::Or,\n                    vec![\n                        Fragment::Literal(\"abc\".into()),\n                        Fragment::Literal(\"def\".into()),\n                    ],\n                ),\n                Fragment::Literal(\"ghi\".into()),\n            ],\n        );\n\n        assert_eq!(\n            inline(fragment),\n            Fragment::Dense(\n                Op::Or,\n                vec![\n                    Fragment::Literal(\"123abcghi\".into()),\n                    Fragment::Literal(\"123defghi\".into()),\n                ],\n            )\n        );\n    }\n\n    #[test]\n    fn inline_nested() {\n        // (a|b|c)(de|fg)h\n\n        let fragment = Fragment::Dense(\n            Op::And,\n            vec![\n                Fragment::Dense(\n                    Op::Or,\n                    vec![\n                        Fragment::Literal(\"a\".into()),\n                        Fragment::Literal(\"b\".into()),\n                        Fragment::Literal(\"c\".into()),\n                    ],\n                ),\n                Fragment::Dense(\n                    Op::Or,\n                    vec![\n                        Fragment::Literal(\"de\".into()),\n                        Fragment::Literal(\"fg\".into()),\n                    ],\n                ),\n                Fragment::Literal(\"h\".into()),\n            ],\n        );\n\n        let expected = Fragment::Dense(\n            Op::Or,\n            vec![\n                Fragment::Dense(\n                    Op::And,\n                    vec![\n                        Fragment::Literal(\"a\".into()),\n                        Fragment::Dense(\n                            Op::Or,\n                            vec![\n                                Fragment::Literal(\"de\".into()),\n                                Fragment::Literal(\"fg\".into()),\n                            ],\n                        ),\n                        Fragment::Literal(\"h\".into()),\n                    ],\n                ),\n                Fragment::Dense(\n                    Op::And,\n                    vec![\n                        Fragment::Literal(\"b\".into()),\n                        Fragment::Dense(\n                            Op::Or,\n                            vec![\n                                Fragment::Literal(\"de\".into()),\n                                Fragment::Literal(\"fg\".into()),\n                            ],\n                        ),\n                        Fragment::Literal(\"h\".into()),\n                    ],\n                ),\n                Fragment::Dense(\n                    Op::And,\n                    vec![\n                        Fragment::Literal(\"c\".into()),\n                        Fragment::Dense(\n                            Op::Or,\n                            vec![\n                                Fragment::Literal(\"de\".into()),\n                                Fragment::Literal(\"fg\".into()),\n                            ],\n                        ),\n                        Fragment::Literal(\"h\".into()),\n                    ],\n                ),\n            ],\n        );\n\n        assert_eq!(inline(fragment), expected);\n        assert_eq!(\n            run(expected),\n            Fragment::Dense(\n                Op::Or,\n                vec![\n                    Fragment::Literal(\"adeh\".into()),\n                    Fragment::Literal(\"afgh\".into()),\n                    Fragment::Literal(\"bdeh\".into()),\n                    Fragment::Literal(\"bfgh\".into()),\n                    Fragment::Literal(\"cdeh\".into()),\n                    Fragment::Literal(\"cfgh\".into()),\n                ]\n            )\n        );\n    }\n\n    #[test]\n    fn inline_break() {\n        let fragment = Fragment::Dense(\n            Op::And,\n            vec![\n                Fragment::Literal(\"abc\".into()),\n                Fragment::Break,\n                Fragment::Literal(\"def\".into()),\n            ],\n        );\n\n        assert_eq!(run(fragment.clone()), fragment);\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/query/planner.rs",
    "content": "use std::fmt::Display;\n\nuse regex_syntax::hir::{Class, Hir, HirKind, Literal, RepetitionKind, RepetitionRange};\n\nmod optimize;\n\n/// The maximum number of characters allowed in a character class before a break occurs.\nconst MAX_CLASS_RANGE_LEN: u32 = 10;\n\n#[derive(Debug, thiserror::Error)]\npub enum Error {\n    #[error(\"encountered unexpected byte literal\")]\n    LiteralByte,\n    #[error(\"got invalid regex\")]\n    InvalidRegex(#[from] Box<regex_syntax::Error>),\n}\n\npub fn plan(regex: &str) -> Result<Fragment, Error> {\n    let hir = regex_syntax::Parser::new().parse(regex).map_err(Box::new)?;\n\n    // TODO: Optimizations are run twice in order to capture new available passes when possible.\n    // Instead, the optimizer should be smart enough to re-run relevant passes recursively only\n    // when applicable.\n    step(hir).map(optimize::run).map(optimize::run)\n}\n\nfn step(hir: Hir) -> Result<Fragment, Error> {\n    let fragment = match hir.into_kind() {\n        HirKind::Empty => Fragment::empty(),\n        HirKind::Literal(Literal::Unicode(lit)) => Fragment::Literal(lit.into()),\n        HirKind::Literal(Literal::Byte(_)) => Err(Error::LiteralByte)?,\n\n        HirKind::Class(Class::Unicode(cls)) => {\n            let total_size = cls\n                .iter()\n                .map(|range| {\n                    let start = range.start();\n                    let end = range.end();\n                    // Add 1, because this is an inclusive range.\n                    (end as u32 + 1) - start as u32\n                })\n                .sum::<u32>();\n\n            if total_size > MAX_CLASS_RANGE_LEN {\n                Fragment::Break\n            } else {\n                let chars = cls\n                    .iter()\n                    .flat_map(|range| range.start() as u32..=range.end() as u32)\n                    .map(|n| char::from_u32(n).unwrap().to_string())\n                    .map(Fragment::Literal)\n                    .collect();\n\n                Fragment::Dense(Op::Or, chars)\n            }\n        }\n        HirKind::Class(Class::Bytes(_)) => Err(Error::LiteralByte)?,\n        HirKind::Anchor(_) => Fragment::Break,\n        HirKind::WordBoundary(_) => Fragment::Break,\n        HirKind::Repetition(repetition) => match repetition.kind {\n            RepetitionKind::OneOrMore => step(*repetition.hir)?.and(Fragment::Break),\n            RepetitionKind::Range(RepetitionRange::Bounded(n, _))\n            | RepetitionKind::Range(RepetitionRange::Exactly(n))\n            | RepetitionKind::Range(RepetitionRange::AtLeast(n))\n                if n > 0 =>\n            {\n                step(*repetition.hir)?.and(Fragment::Break)\n            }\n\n            RepetitionKind::ZeroOrMore | RepetitionKind::Range(_) | RepetitionKind::ZeroOrOne => {\n                Fragment::Break\n            }\n        },\n        HirKind::Group(group) => step(*group.hir)?,\n\n        HirKind::Concat(hirs) => hirs\n            .into_iter()\n            .map(step)\n            .try_fold(Fragment::empty(), |a, r| r.map(|e| Fragment::and(a, e)))?,\n\n        HirKind::Alternation(alts) => alts\n            .into_iter()\n            .map(step)\n            .reduce(|a, e| Ok(a?.or(e?)))\n            .transpose()?\n            .unwrap_or_else(Fragment::empty),\n    };\n\n    Ok(fragment)\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub enum Fragment {\n    /// A dense fragment.\n    ///\n    /// This is a fragment that cannot yet be collapsed into a `Query` because its children are not\n    /// long enough to be trigrams.\n    Dense(Op, Vec<Self>),\n\n    /// A literal string.\n    ///\n    /// This can be any length, including zero.\n    Literal(String),\n\n    /// An arbitrary length fragment of any content.\n    Break,\n}\n\nimpl Fragment {\n    fn empty() -> Self {\n        Self::Literal(String::new())\n    }\n\n    fn and(self, other: Self) -> Self {\n        match (self, other) {\n            // Remove empty strings.\n            (Fragment::Literal(s), rhs) if s.is_empty() => rhs,\n            (lhs, Fragment::Literal(s)) if s.is_empty() => lhs,\n\n            // Join breaks.\n            (Fragment::Break, Fragment::Break) => Fragment::Break,\n\n            // Join literals.\n            (Fragment::Literal(lit), Fragment::Literal(olit)) => Fragment::Literal(lit + &olit),\n\n            (Fragment::Dense(Op::And, mut lhs), Fragment::Dense(Op::And, rhs)) => {\n                lhs.extend(rhs);\n                Fragment::Dense(Op::And, lhs)\n            }\n\n            // String joining optimization.\n            (Fragment::Dense(Op::And, mut lhs), Fragment::Literal(rhs))\n                if lhs.last().and_then(Fragment::as_literal).is_some() =>\n            {\n                *lhs.last_mut().unwrap().as_literal_mut().unwrap() += &rhs;\n                Fragment::Dense(Op::And, lhs)\n            }\n\n            (Fragment::Dense(Op::And, mut lhs), rhs) => {\n                lhs.push(rhs);\n                Fragment::Dense(Op::And, lhs)\n            }\n\n            (lhs, Fragment::Dense(Op::And, mut rhs)) => {\n                rhs.insert(0, lhs);\n                Fragment::Dense(Op::And, rhs)\n            }\n\n            (lhs, rhs) => Fragment::Dense(Op::And, vec![lhs, rhs]),\n        }\n    }\n\n    fn or(self, other: Self) -> Self {\n        match (self, other) {\n            // (()|xyz) matches empty string\n            (Fragment::Literal(s), _) if s.is_empty() => Fragment::Literal(String::new()),\n            (_, Fragment::Literal(s)) if s.is_empty() => Fragment::Literal(String::new()),\n\n            // Join breaks.\n            (Fragment::Break, Fragment::Break) => Fragment::Break,\n\n            (Fragment::Literal(lhs), Fragment::Literal(rhs)) => {\n                Fragment::Dense(Op::Or, vec![Fragment::Literal(lhs), Fragment::Literal(rhs)])\n            }\n\n            (Fragment::Dense(Op::Or, mut lhs), Fragment::Dense(Op::Or, rhs)) => {\n                lhs.extend(rhs);\n                Fragment::Dense(Op::Or, lhs)\n            }\n\n            (Fragment::Dense(Op::Or, mut sub), rhs) => {\n                sub.push(rhs);\n                Fragment::Dense(Op::Or, sub)\n            }\n\n            (lhs, Fragment::Dense(Op::Or, mut sub)) => {\n                sub.insert(0, lhs);\n                Fragment::Dense(Op::Or, sub)\n            }\n\n            (lhs, rhs) => Fragment::Dense(Op::Or, vec![lhs, rhs]),\n        }\n    }\n\n    fn as_literal(&self) -> Option<&String> {\n        if let Self::Literal(s) = self {\n            Some(s)\n        } else {\n            None\n        }\n    }\n\n    fn as_literal_mut(&mut self) -> Option<&mut String> {\n        if let Self::Literal(s) = self {\n            Some(s)\n        } else {\n            None\n        }\n    }\n}\n\nimpl Display for Fragment {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Break => write!(f, \" * \"),\n            Self::Literal(s) => write!(f, \"{s}\"),\n            Self::Dense(op, children) => {\n                let mut join = \"\";\n                let op = match op {\n                    Op::And => \" AND \",\n                    Op::Or => \" OR \",\n                };\n\n                for fragment in children {\n                    write!(f, \"{join}({fragment})\")?;\n                    join = op;\n                }\n\n                Ok(())\n            }\n        }\n    }\n}\n\n#[derive(Debug, PartialEq, Eq, Default, Copy, Clone)]\npub enum Op {\n    #[default]\n    And,\n    Or,\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use pretty_assertions::assert_eq;\n\n    #[test]\n    fn string_literal() {\n        assert_eq!(plan(\"abcde\").unwrap(), Fragment::Literal(\"abcde\".into()),);\n    }\n\n    #[test]\n    fn simple_inline() {\n        assert_eq!(plan(\"ab(cd)\").unwrap(), Fragment::Literal(\"abcd\".into()));\n    }\n\n    #[test]\n    fn double_alternation() {\n        // (a|b|c)(de|fg)h\n        //\n        // optimization:\n        //\n        // inline        => ((a(de|fg)h) | (b(de|fg)h) | (c(de|fg)h))\n        // inline        => (((ade|afg)h) | ((bde|bfg)h) | ((cde|cfg)h))\n        // inline        => ((adeh|afgh) | (bdeh|bfgh) | (cdeh|cfgh))\n        // flatten_or    => adeh | afgh | bdeh | bfgh | cdeh | cfgh\n\n        let fragment = plan(\"(a|b|c)(de|fg)h\").unwrap();\n        let expected = Fragment::Dense(\n            Op::Or,\n            vec![\n                Fragment::Literal(\"adeh\".into()),\n                Fragment::Literal(\"afgh\".into()),\n                Fragment::Literal(\"bdeh\".into()),\n                Fragment::Literal(\"bfgh\".into()),\n                Fragment::Literal(\"cdeh\".into()),\n                Fragment::Literal(\"cfgh\".into()),\n            ],\n        );\n\n        assert_eq!(fragment, expected);\n    }\n\n    #[test]\n    fn nested_or() {\n        // (((abc|def)|ghi)|jkl|(123|(456|(789)))|000)\n        // => abc|def|ghi|jkl|123|456|789|000\n\n        let fragment = plan(\"(((abc|def)|ghi)|jkl|((123|(456|(789))))|000)\").unwrap();\n        let expected = Fragment::Dense(\n            Op::Or,\n            vec![\n                Fragment::Literal(\"abc\".into()),\n                Fragment::Literal(\"def\".into()),\n                Fragment::Literal(\"ghi\".into()),\n                Fragment::Literal(\"jkl\".into()),\n                Fragment::Literal(\"123\".into()),\n                Fragment::Literal(\"456\".into()),\n                Fragment::Literal(\"789\".into()),\n                Fragment::Literal(\"000\".into()),\n            ],\n        );\n\n        assert_eq!(fragment, expected);\n    }\n\n    #[test]\n    fn basic_inline() {\n        // ab(de|fg)\n        // inline        => abde|abfg\n        let fragment = plan(\"ab(de|fg)\").unwrap();\n        let expected = Fragment::Dense(\n            Op::Or,\n            vec![\n                Fragment::Literal(\"abde\".into()),\n                Fragment::Literal(\"abfg\".into()),\n            ],\n        );\n\n        assert_eq!(fragment, expected);\n    }\n\n    #[test]\n    fn small_literal_alt() {\n        let frag = plan(\"ab|cd\").unwrap();\n        assert_eq!(\n            frag,\n            Fragment::Dense(\n                Op::Or,\n                vec![\n                    Fragment::Literal(\"ab\".into()),\n                    Fragment::Literal(\"cd\".into()),\n                ],\n            )\n        );\n    }\n\n    #[test]\n    fn simple_wildcard() {\n        let frag = plan(\"abc.def\").unwrap();\n        assert_eq!(\n            frag,\n            Fragment::Dense(\n                Op::And,\n                vec![\n                    Fragment::Literal(\"abc\".into()),\n                    Fragment::Break,\n                    Fragment::Literal(\"def\".into()),\n                ],\n            ),\n        );\n    }\n\n    #[test]\n    fn repetition() {\n        let frag = plan(\"abc.*def\").unwrap();\n        assert_eq!(\n            frag,\n            Fragment::Dense(\n                Op::And,\n                vec![\n                    Fragment::Literal(\"abc\".into()),\n                    Fragment::Break,\n                    Fragment::Literal(\"def\".into()),\n                ],\n            ),\n        );\n\n        let frag = plan(\"abcz*def\").unwrap();\n        assert_eq!(\n            frag,\n            Fragment::Dense(\n                Op::And,\n                vec![\n                    Fragment::Literal(\"abc\".into()),\n                    Fragment::Break,\n                    Fragment::Literal(\"def\".into()),\n                ],\n            ),\n        );\n\n        let frag = plan(\"abcz+def\").unwrap();\n        assert_eq!(\n            frag,\n            Fragment::Dense(\n                Op::And,\n                vec![\n                    Fragment::Literal(\"abcz\".into()),\n                    Fragment::Break,\n                    Fragment::Literal(\"def\".into()),\n                ],\n            ),\n        );\n\n        let frag = plan(\"async+.fn.main\").unwrap();\n        assert_eq!(\n            frag,\n            Fragment::Dense(\n                Op::And,\n                vec![\n                    Fragment::Literal(\"async\".into()),\n                    Fragment::Break,\n                    Fragment::Break,\n                    Fragment::Literal(\"fn\".into()),\n                    Fragment::Break,\n                    Fragment::Literal(\"main\".into()),\n                ],\n            ),\n        );\n    }\n\n    #[test]\n    fn simple_range() {\n        let frag = plan(\"abc[d-f]g\").unwrap();\n        assert_eq!(\n            frag,\n            Fragment::Dense(\n                Op::Or,\n                vec![\n                    Fragment::Literal(\"abcdg\".into()),\n                    Fragment::Literal(\"abceg\".into()),\n                    Fragment::Literal(\"abcfg\".into()),\n                ],\n            ),\n        );\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/query/ranking.rs",
    "content": "use std::time::SystemTime;\n\nuse tantivy::{\n    collector::{ScoreSegmentTweaker, ScoreTweaker},\n    fastfield::Column,\n    DocId, Score,\n};\nuse tantivy_columnar::{column_values::ColumnValues, BytesColumn};\n\nuse crate::indexes::file::File;\n\npub struct DocumentTweaker(pub File);\npub struct SegmentScorer {\n    line_length: Column<f64>,\n    lang: BytesColumn,\n    last_commit: Column<u64>,\n}\n\nimpl ScoreSegmentTweaker<Score> for SegmentScorer {\n    fn score(&mut self, doc: DocId, mut score: Score) -> Score {\n        // * 1000 if it's a language we understand\n        let mut bytes = Vec::new();\n        self.lang.ords().values_for_doc(doc).for_each(|ord| {\n            self.lang.ord_to_bytes(ord, &mut bytes).unwrap();\n        });\n        score *= 1.0 + bytes.len().min(1) as f32 * 999.0;\n\n        // Penalty for lines that are too long\n        score /= self.line_length.values.get_val(doc).clamp(20.0, 1000.0) as f32;\n        score /= SystemTime::now()\n            .duration_since(SystemTime::UNIX_EPOCH)\n            .unwrap()\n            .as_secs()\n            .saturating_sub(self.last_commit.values.get_val(doc))\n            .min(5_000_000) as f32;\n\n        score\n    }\n}\n\nimpl ScoreTweaker<Score> for DocumentTweaker {\n    type Child = SegmentScorer;\n\n    fn segment_tweaker(\n        &self,\n        segment_reader: &tantivy::SegmentReader,\n    ) -> tantivy::Result<Self::Child> {\n        let Self(file) = self;\n        let schema = file.schema();\n        let avg_line_length_field = schema.get_field_name(file.avg_line_length);\n        let lang_field = schema.get_field_name(file.lang);\n        let last_commit_unix_seconds_field = schema.get_field_name(file.last_commit_unix_seconds);\n        Ok(SegmentScorer {\n            line_length: segment_reader.fast_fields().f64(avg_line_length_field)?,\n            lang: segment_reader.fast_fields().bytes(lang_field)?.unwrap(),\n            last_commit: segment_reader\n                .fast_fields()\n                .u64(last_commit_unix_seconds_field)?,\n        })\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/query/stopwords.rs",
    "content": "// Portions of this code (the `phrases` function) are modifications of\n// https://github.com/yaa110/rake-rs/blob/master/src/rake.rs\n// licensed under the MIT License:\n/*\nCopyright (c) 2018 Navid Fathollahzade\n\nPermission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n*/\n\nuse lazy_regex::regex;\nuse once_cell::sync::Lazy;\nuse std::collections::HashSet;\n\ntype StopWords = HashSet<&'static str>;\n\nstatic STOPWORDS: Lazy<StopWords> = Lazy::new(|| {\n    let word_list = include_str!(\"stopwords.txt\");\n    let mut sw = StopWords::new();\n    for w in word_list.lines() {\n        sw.insert(w);\n    }\n    sw\n});\n\n/// Extract `phrases`, where each phrase is a sequence of non-stopwords\nfn phrases<'a>(phrases_iter: impl IntoIterator<Item = &'a str>) -> Vec<Vec<&'a str>> {\n    let phrases_iter = phrases_iter.into_iter();\n    let mut phrases = Vec::with_capacity(2 * phrases_iter.size_hint().0);\n    for s in phrases_iter.filter(|s| !s.is_empty()) {\n        let mut phrase = Vec::new();\n        for word in s.split_whitespace() {\n            if STOPWORDS.contains(word.to_lowercase().as_str()) {\n                if !phrase.is_empty() {\n                    phrases.push(phrase.clone());\n                    phrase.clear();\n                }\n            } else {\n                phrase.push(word);\n            }\n        }\n        if !phrase.is_empty() {\n            phrases.push(phrase);\n        }\n    }\n    phrases\n}\n\npub fn remove_stopwords(text: &str) -> String {\n    let phrases = phrases(regex!(\"[^a-zA-Z0-9_/ -]\").split(text));\n    phrases.into_iter().flatten().collect::<Vec<_>>().join(\" \")\n}\n"
  },
  {
    "path": "server/bleep/src/query/stopwords.txt",
    "content": "a\na's\nable\nabout\nabove\naccording\naccordingly\nacross\nactually\nafter\nafterwards\nagain\nagainst\nain't\nall\nallow\nallows\nalmost\nalone\nalong\nalready\nalso\nalthough\nalways\nam\namong\namongst\nan\nand\nanother\nany\nanybody\nanyhow\nanyone\nanything\nanyway\nanyways\nanywhere\napart\nappear\nappreciate\nappropriate\nare\naren't\naround\nas\naside\nask\nasking\nassociated\nat\navailable\naway\nawfully\nb\nbe\nbecame\nbecause\nbecome\nbecomes\nbecoming\nbeen\nbefore\nbeforehand\nbehind\nbeing\nbelieve\nbelow\nbeside\nbesides\nbest\nbetter\nbetween\nbeyond\nboth\nbrief\nbut\nby\nc\nc'mon\nc's\ncame\ncan\ncan't\ncannot\ncant\ncause\ncauses\ncertain\ncertainly\nchanges\nclearly\nco\ncom\ncome\ncomes\nconcerning\nconsequently\nconsider\nconsidering\ncontain\ncontaining\ncontains\ncorresponding\ncould\ncouldn't\ncourse\ncurrently\nd\ndefinitely\ndescribed\ndespite\ndid\ndidn't\ndifferent\ndo\ndoes\ndoesn't\ndoing\ndon't\ndone\ndown\ndownwards\nduring\ne\neach\nedu\neg\neight\neither\nelse\nelsewhere\nenough\nentirely\nespecially\net\netc\neven\never\nevery\neverybody\neveryone\neverything\neverywhere\nex\nexactly\nexample\nexcept\nf\nfar\nfew\nfifth\nfirst\nfive\nfollowed\nfollowing\nfollows\nfor\nformer\nformerly\nforth\nfour\nfrom\nfurther\nfurthermore\ng\nget\ngets\ngetting\ngiven\ngives\ngo\ngoes\ngoing\ngone\ngot\ngotten\ngreetings\nh\nhad\nhadn't\nhappens\nhardly\nhas\nhasn't\nhave\nhaven't\nhaving\nhe\nhe's\nhello\nhelp\nhence\nher\nhere\nhere's\nhereafter\nhereby\nherein\nhereupon\nhers\nherself\nhi\nhim\nhimself\nhis\nhither\nhopefully\nhow\nhowbeit\nhowever\ni\ni'd\ni'll\ni'm\ni've\nie\nif\nignored\nimmediate\nin\ninasmuch\ninc\nindeed\nindicate\nindicated\nindicates\ninner\ninsofar\ninstead\ninto\ninward\nis\nisn't\nit\nit'd\nit'll\nit's\nits\nitself\nj\njust\nk\nkeep\nkeeps\nkept\nknow\nknows\nknown\nl\nlast\nlately\nlater\nlatter\nlatterly\nleast\nless\nlest\nlet\nlet's\nlike\nliked\nlikely\nlittle\nlook\nlooking\nlooks\nltd\nm\nmainly\nmany\nmay\nmaybe\nme\nmean\nmeanwhile\nmerely\nmight\nmore\nmoreover\nmost\nmostly\nmuch\nmust\nmy\nmyself\nn\nname\nnamely\nnd\nnear\nnearly\nnecessary\nneed\nneeds\nneither\nnever\nnevertheless\nnew\nnext\nnine\nno\nnobody\nnon\nnone\nnoone\nnor\nnormally\nnot\nnothing\nnovel\nnow\nnowhere\no\nobviously\nof\noff\noften\noh\nok\nokay\nold\non\nonce\none\nones\nonly\nonto\nor\nother\nothers\notherwise\nought\nour\nours\nourselves\nout\noutside\nover\noverall\nown\np\nparticular\nparticularly\nper\nperhaps\nplaced\nplease\nplus\npossible\npresumably\nprobably\nprovides\nq\nque\nquite\nqv\nr\nrather\nrd\nre\nreally\nreasonably\nregarding\nregardless\nregards\nrelatively\nrespectively\nright\ns\nsaid\nsame\nsaw\nsay\nsaying\nsays\nsecond\nsecondly\nsee\nseeing\nseem\nseemed\nseeming\nseems\nseen\nself\nselves\nsensible\nsent\nserious\nseriously\nseven\nseveral\nshall\nshe\nshould\nshouldn't\nsince\nsix\nso\nsome\nsomebody\nsomehow\nsomeone\nsomething\nsometime\nsometimes\nsomewhat\nsomewhere\nsoon\nsorry\nspecified\nspecify\nspecifying\nstill\nsub\nsuch\nsup\nsure\nt\nt's\ntake\ntaken\ntell\ntends\nth\nthan\nthank\nthanks\nthanx\nthat\nthat's\nthats\nthe\ntheir\ntheirs\nthem\nthemselves\nthen\nthence\nthere\nthere's\nthereafter\nthereby\ntherefore\ntherein\ntheres\nthereupon\nthese\nthey\nthey'd\nthey'll\nthey're\nthey've\nthink\nthird\nthis\nthorough\nthoroughly\nthose\nthough\nthree\nthrough\nthroughout\nthru\nthus\nto\ntogether\ntoo\ntook\ntoward\ntowards\ntried\ntries\ntruly\ntry\ntrying\ntwice\ntwo\nu\nun\nunder\nunfortunately\nunless\nunlikely\nuntil\nunto\nup\nupon\nus\nuse\nused\nuseful\nuses\nusing\nusually\nuucp\nv\nvalue\nvarious\nvery\nvia\nviz\nvs\nw\nwant\nwants\nwas\nwasn't\nway\nwe\nwe'd\nwe'll\nwe're\nwe've\nwelcome\nwell\nwent\nwere\nweren't\nwhat\nwhat's\nwhatever\nwhen\nwhence\nwhenever\nwhere\nwhere's\nwhereafter\nwhereas\nwhereby\nwherein\nwhereupon\nwherever\nwhether\nwhich\nwhile\nwhither\nwho\nwho's\nwhoever\nwhole\nwhom\nwhose\nwhy\nwill\nwilling\nwish\nwith\nwithin\nwithout\nwon't\nwonder\nwould\nwould\nwouldn't\nx\ny\nyes\nyet\nyou\nyou'd\nyou'll\nyou're\nyou've\nyour\nyours\nyourself\nyourselves\nz\nzero\n"
  },
  {
    "path": "server/bleep/src/query.rs",
    "content": "pub mod compiler;\npub mod execute;\npub mod languages;\npub mod parser;\npub mod planner;\npub mod ranking;\npub mod stopwords;\n"
  },
  {
    "path": "server/bleep/src/remotes/github.rs",
    "content": "use octocrab::Octocrab;\nuse secrecy::{ExposeSecret, SecretString};\nuse serde::{Deserialize, Serialize};\nuse std::sync::Arc;\n\nuse crate::repo::{GitRemote, RepoRemote, Repository};\n\nuse super::*;\n\n#[derive(Serialize, Deserialize, Clone, Debug)]\npub(crate) struct State {\n    pub auth: Auth,\n    #[serde(skip)]\n    pub repositories: Arc<Vec<octocrab::models::Repository>>,\n}\n\nimpl State {\n    pub(crate) fn with_auth(auth: Auth) -> Self {\n        Self {\n            auth,\n            repositories: Arc::default(),\n        }\n    }\n\n    pub(crate) async fn username(&self) -> Result<String> {\n        self.auth.username().await\n    }\n\n    pub fn client(&self) -> octocrab::Result<Octocrab> {\n        self.auth.client()\n    }\n\n    /// Get a representative list of repositories currently accessible\n    pub async fn current_repo_list(&self) -> Result<Vec<octocrab::models::Repository>> {\n        self.auth.list_repos().await\n    }\n\n    /// Create a new object with the updated repositories list\n    ///\n    /// This is a separate step from refreshing the repo list to avoid\n    /// async locking\n    pub fn update_repositories(self, repos: Vec<octocrab::models::Repository>) -> Self {\n        Self {\n            auth: self.auth,\n            repositories: repos.into(),\n        }\n    }\n}\n\n#[derive(Serialize, Deserialize, Clone, Debug)]\npub(crate) struct Auth {\n    #[serde(serialize_with = \"crate::config::serialize_secret_str\")]\n    token: SecretString,\n}\n\nimpl From<Auth> for State {\n    fn from(value: Auth) -> Self {\n        State::with_auth(value)\n    }\n}\n\nimpl Auth {\n    pub(crate) fn new(token: SecretString) -> Self {\n        Self { token }\n    }\n\n    /// Return credentials for private repositories, and no credentials for public ones.\n    pub(crate) async fn creds(&self, repo: &Repository) -> Result<Option<GitCreds>> {\n        let RepoRemote::Git(GitRemote { ref address, .. }) = repo.remote else {\n            return Err(RemoteError::NotSupported(\"github without git backend\"));\n        };\n\n        let (org, reponame) = address\n            .split_once('/')\n            .ok_or(RemoteError::NotSupported(\"invalid repo address\"))?;\n\n        let response = self.client()?.repos(org, reponame).get().await;\n        let repo = match response {\n            Err(octocrab::Error::GitHub { ref source, .. })\n                if \"Not Found\" == source.message.as_str() =>\n            {\n                // GitHub API will send 403 for API-level issues, not object-level permissions\n                // A user having had their permissions removed will receive 404.\n                return Err(RemoteError::RemoteNotFound);\n            }\n            // I'm leaving this here for completeness' sake, this likely isn't exercised\n            // Octocrab seems to treat GitHub application-layer errors as higher priority\n            Err(octocrab::Error::Http { .. }) => return Err(RemoteError::PermissionDenied),\n            Err(err) => return Err(err)?,\n            Ok(details) => details,\n        };\n\n        Ok(match repo.private {\n            // No credentials for public repos\n            Some(false) => None,\n            // Not sure there's a reason GitHub API wouldn't return a value,\n            // but provide credentials by default to be on the safe side.\n            _ => Some(self.git_cred()),\n        })\n    }\n\n    pub(crate) async fn username(&self) -> Result<String> {\n        let client = self.client()?;\n        let user = client.current().user().await?.login;\n        Ok(user)\n    }\n\n    fn git_cred(&self) -> GitCreds {\n        GitCreds {\n            username: \"x-access-token\".into(),\n            password: self.token.expose_secret().into(),\n        }\n    }\n\n    fn client(&self) -> octocrab::Result<Octocrab> {\n        let token = octocrab::auth::OAuth {\n            access_token: self.token.clone(),\n            token_type: \"Bearer\".into(),\n            scope: vec![],\n        };\n\n        Octocrab::builder().oauth(token).build()\n    }\n\n    async fn list_repos(&self) -> Result<Vec<octocrab::models::Repository>> {\n        let gh_client = self.client().expect(\"failed to build github client\");\n        let mut results = vec![];\n        for page in 1.. {\n            let mut resp = gh_client\n                .current()\n                .list_repos_for_authenticated_user()\n                .per_page(100)\n                .page(page)\n                .send()\n                .await?;\n\n            if resp.items.is_empty() {\n                break;\n            }\n\n            results.extend(resp.take_items())\n        }\n\n        Ok(results)\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/remotes/poll.rs",
    "content": "\n"
  },
  {
    "path": "server/bleep/src/remotes.rs",
    "content": "use std::{\n    collections::HashMap,\n    path::{Path, PathBuf},\n    sync::Arc,\n};\n\nuse anyhow::Context;\nuse gix::{remote::fetch::Shallow, sec::identity::Account};\nuse ignore::WalkBuilder;\nuse serde::{Deserialize, Serialize};\nuse tracing::error;\n\nuse crate::{\n    background::{SyncHandle, SyncPipes},\n    repo::{Backend, RepoError, RepoRef, Repository, SyncStatus},\n};\n\npub mod github;\n\ntype GitCreds = Account;\n\npub(crate) type Result<T> = std::result::Result<T, RemoteError>;\n#[derive(thiserror::Error, Debug)]\npub(crate) enum RemoteError {\n    #[error(\"remote not found\")]\n    RemoteNotFound,\n\n    #[error(\"permission denied\")]\n    PermissionDenied,\n\n    #[error(\"operation not supported: {0}\")]\n    NotSupported(&'static str),\n\n    #[error(\"persistence error: {0}\")]\n    Repo(#[from] RepoError),\n\n    #[error(\"IO error: {0}\")]\n    IO(#[from] std::io::Error),\n\n    #[error(\"github access error: {0}\")]\n    GitHub(#[from] octocrab::Error),\n\n    #[error(\"anyhow: {0:?}\")]\n    Anyhow(#[from] anyhow::Error),\n\n    #[error(\"underlying thread died: {0:?}\")]\n    JoinError(#[from] tokio::task::JoinError),\n\n    #[error(\"git open: {0:?}\")]\n    GitOpen(#[from] gix::open::Error),\n\n    #[error(\"git prepare fetch: {0:?}\")]\n    GitPrepareFetch(#[from] gix::remote::fetch::prepare::Error),\n\n    #[error(\"git fetch: {0:?}\")]\n    GitFetch(#[from] gix::remote::fetch::Error),\n\n    #[error(\"git find remote: {0:?}\")]\n    GitFindRemote(#[from] gix::remote::find::existing::Error),\n\n    #[error(\"git find remote: {0:?}\")]\n    GitConnect(#[from] gix::remote::connect::Error),\n\n    #[error(\"git clone: {0:?}\")]\n    GitClone(#[from] gix::clone::Error),\n\n    #[error(\"git clone fetch: {0:?}\")]\n    GitCloneFetch(#[from] gix::clone::fetch::Error),\n\n    #[error(\"interrupted\")]\n    Interrupted,\n}\n\nimpl From<&RemoteError> for SyncStatus {\n    fn from(value: &RemoteError) -> Self {\n        SyncStatus::Error {\n            message: value.to_string(),\n        }\n    }\n}\n\nimpl From<Result<SyncStatus>> for SyncStatus {\n    fn from(value: Result<SyncStatus>) -> Self {\n        match value {\n            Ok(status) => status,\n            Err(err) => (&err).into(),\n        }\n    }\n}\n\nmacro_rules! creds_callback(($auth:ident) => {{\n    use gix::{\n\tcredentials::{\n            helper::{Action, NextAction},\n            protocol::Outcome,\n\t}};\n\n    let auth = $auth.clone();\n    move |action| match action {\n        Action::Get(ctx) => Ok(Some(Outcome {\n            identity: auth.clone(),\n            next: NextAction::from(ctx),\n        })),\n        Action::Store(_) => Ok(None),\n        Action::Erase(_) => Ok(None),\n    }\n}});\n\nasync fn git_clone(\n    auth: &Option<GitCreds>,\n    url: &str,\n    target: &Path,\n    pipes: &SyncPipes,\n    shallow: Shallow,\n) -> Result<()> {\n    let url = url.to_owned();\n    let target = target.to_owned();\n    let auth = auth.clone();\n\n    let git_status = pipes.git_sync_progress();\n    let interrupt = pipes.is_interrupted();\n\n    tokio::task::spawn_blocking(move || {\n        let mut clone = {\n            let c = gix::prepare_clone_bare(url, target)?.with_shallow(shallow);\n            match auth {\n                Some(auth) => c.configure_connection(move |con| {\n                    con.set_credentials(creds_callback!(auth));\n                    Ok(())\n                }),\n                None => c,\n            }\n        };\n\n        let (_repo, _outcome) = clone.fetch_only(git_status, &interrupt)?;\n        Ok(())\n    })\n    .await?\n}\n\nasync fn git_pull(\n    auth: &Option<GitCreds>,\n    repo: &Repository,\n    pipes: &SyncPipes,\n    shallow: Shallow,\n) -> Result<()> {\n    use gix::remote::Direction;\n\n    let auth = auth.clone();\n    let disk_path = repo.disk_path.to_owned();\n\n    let interrupt = pipes.is_interrupted();\n\n    tokio::task::spawn_blocking(move || {\n        let repo = gix::open(disk_path)?;\n        let remote = repo\n            .find_default_remote(Direction::Fetch)\n            .context(\"no remote found\")??;\n\n        let connection = {\n            let c = remote.connect(Direction::Fetch)?;\n            match auth {\n                Some(auth) => c.with_credentials(creds_callback!(auth)),\n                None => c,\n            }\n        };\n\n        connection\n            .prepare_fetch(gix::progress::Discard, Default::default())?\n            .with_shallow(shallow)\n            .receive(gix::progress::Discard, &interrupt)?;\n\n        Ok(())\n    })\n    .await?\n}\n\npub(crate) fn gather_repo_roots(\n    path: impl AsRef<Path>,\n    exclude: Option<PathBuf>,\n) -> std::collections::HashSet<RepoRef> {\n    const RECOGNIZED_VCS_DIRS: &[&str] = &[\".git\"];\n\n    let repos = Arc::new(scc::HashSet::new());\n\n    WalkBuilder::new(path)\n        .ignore(true)\n        .hidden(false)\n        .git_ignore(true)\n        .git_global(false)\n        .git_exclude(false)\n        .filter_entry(move |entry| {\n            exclude\n                .as_ref()\n                .and_then(|path| {\n                    crate::canonicalize(entry.path())\n                        .ok()\n                        .map(|canonical_path| !canonical_path.starts_with(path))\n                })\n                .unwrap_or(true)\n        })\n        .build_parallel()\n        .run(|| {\n            let repos = repos.clone();\n            Box::new(move |entry| {\n                use ignore::WalkState::*;\n\n                let Ok(de) = entry else {\n                    return Continue;\n                };\n\n                let Some(ft) = de.file_type() else {\n                    return Continue;\n                };\n\n                if ft.is_dir()\n                    && RECOGNIZED_VCS_DIRS.contains(&de.file_name().to_string_lossy().as_ref())\n                {\n                    _ = repos.insert(RepoRef::from(\n                        &crate::canonicalize(\n                            de.path().parent().expect(\"/ shouldn't be a git repo\"),\n                        )\n                        .expect(\"repo root is both a dir and exists\"),\n                    ));\n\n                    // we've already taken this repo, do not search subdirectories\n                    return Skip;\n                }\n\n                Continue\n            })\n        });\n\n    let mut output = std::collections::HashSet::default();\n    repos.scan(|entry| {\n        output.insert(entry.clone());\n    });\n\n    output\n}\n\nstruct BackendEntry {\n    inner: BackendCredential,\n}\n\nimpl Serialize for BackendEntry {\n    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>\n    where\n        S: serde::Serializer,\n    {\n        self.inner.serialize(serializer)\n    }\n}\n\nimpl<'de> Deserialize<'de> for BackendEntry {\n    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>\n    where\n        D: serde::Deserializer<'de>,\n    {\n        let inner = BackendCredential::deserialize(deserializer)?;\n        Ok(inner.into())\n    }\n}\n\nimpl From<BackendCredential> for BackendEntry {\n    fn from(inner: BackendCredential) -> Self {\n        BackendEntry { inner }\n    }\n}\n\n#[derive(Serialize, Deserialize, Default, Clone)]\npub struct Backends {\n    /// If the environment is a Tauri app, or auth is instance-wide,\n    /// This will refresh the correct user.\n    authenticated_user: Arc<std::sync::RwLock<Option<String>>>,\n    backends: Arc<scc::HashMap<Backend, BackendEntry>>,\n}\n\nimpl From<HashMap<Backend, BackendCredential>> for Backends {\n    fn from(mut value: HashMap<Backend, BackendCredential>) -> Self {\n        let backends = Arc::new(scc::HashMap::default());\n        for (k, v) in value.drain() {\n            _ = backends.insert(k, v.into());\n        }\n\n        Self {\n            backends,\n            authenticated_user: Arc::default(),\n        }\n    }\n}\n\nimpl Backends {\n    pub(crate) fn for_repo(&self, repo: &RepoRef) -> Option<BackendCredential> {\n        self.backends.read(&repo.backend(), |_, v| v.inner.clone())\n    }\n\n    pub(crate) fn github(&self) -> Option<github::State> {\n        self.backends.read(&Backend::Github, |_, v| {\n            let BackendCredential::Github(ref github) = v.inner;\n            github.clone()\n        })\n    }\n\n    pub(crate) fn set_github(&self, gh: impl Into<github::State>) {\n        let gh = gh.into();\n        self.backends\n            .entry(Backend::Github)\n            .and_modify(|existing| {\n                existing.inner = BackendCredential::Github(gh.clone());\n            })\n            .or_insert_with(|| BackendCredential::Github(gh).into());\n    }\n}\n\n#[derive(Serialize, Deserialize, Clone, Debug)]\npub(crate) enum BackendCredential {\n    Github(github::State),\n}\n\nimpl BackendCredential {\n    #[tracing::instrument(fields(repo=%handle.reporef), skip_all)]\n    pub(crate) async fn clone_or_pull(\n        &self,\n        handle: &SyncHandle,\n        repo: Repository,\n    ) -> Result<SyncStatus> {\n        use BackendCredential::*;\n        let Github(gh) = self;\n\n        let creds = gh.auth.creds(&repo).await?;\n        let clone = || async {\n            handle.set_status(|_| SyncStatus::Syncing);\n            git_clone(\n                &creds,\n                &repo.remote.to_string(),\n                &repo.disk_path,\n                &handle.pipes,\n                handle.shallow_config.clone(),\n            )\n            .await\n        };\n        let pull = || async {\n            git_pull(&creds, &repo, &handle.pipes, handle.shallow_config.clone()).await\n        };\n\n        let synced = if repo.last_index_unix_secs == 0 && repo.disk_path.exists() {\n            // it is possible syncing was killed, but the repo is\n            // intact. pull if the dir exists, then quietly revert\n            // to cloning if that fails\n            match pull().await {\n                Ok(success) => Ok(success),\n                Err(_) if handle.pipes.is_cancelled() => Err(RemoteError::Interrupted),\n                Err(_) => clone().await,\n            }\n        } else if repo.last_index_unix_secs == 0 {\n            clone().await\n        } else {\n            let pulled = pull().await;\n            if pulled.is_err() && !handle.pipes.is_cancelled() {\n                clone().await\n            } else {\n                pulled\n            }\n        };\n\n        synced.map(|_| SyncStatus::Queued).map_err(|e| {\n            if handle.pipes.is_cancelled() {\n                RemoteError::Interrupted\n            } else {\n                e\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/repo/iterator/filters.rs",
    "content": "use std::{\n    collections::{BTreeSet, HashSet},\n    path::Path,\n};\n\nuse regex::RegexSet;\nuse serde::{Deserialize, Serialize};\n\n/// Update filter configs for a repository\n#[derive(serde::Deserialize, Clone, Debug, Default)]\npub struct FilterUpdate {\n    pub branch_filter: Option<BranchFilterConfig>,\n    pub file_filter: Option<FileFilterConfig>,\n}\n\n/// Configure branch filters\n#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]\n#[serde(rename_all = \"snake_case\")]\npub enum BranchFilterConfig {\n    All,\n    Head,\n    Select(Vec<String>),\n}\n\nimpl BranchFilterConfig {\n    /// Extend the existing list if `Select`, or override the branch config with the new one.\n    pub(crate) fn patch_into(\n        &self,\n        old: Option<&BranchFilterConfig>,\n    ) -> Option<BranchFilterConfig> {\n        let Some(BranchFilterConfig::Select(ref old_list)) = old else {\n            return Some(self.clone());\n        };\n\n        let BranchFilterConfig::Select(new_list) = self else {\n            return Some(self.clone());\n        };\n\n        let mut updated = old_list.iter().collect::<BTreeSet<_>>();\n        updated.extend(new_list);\n\n        Some(BranchFilterConfig::Select(\n            updated.into_iter().cloned().collect(),\n        ))\n    }\n}\n\nimpl From<&BranchFilterConfig> for BranchFilter {\n    fn from(value: &BranchFilterConfig) -> Self {\n        match value {\n            BranchFilterConfig::All => BranchFilter::All,\n            BranchFilterConfig::Head => BranchFilter::Head,\n            BranchFilterConfig::Select(regexes) => {\n                let mut regexes = regexes.clone();\n                regexes.push(\"HEAD\".into());\n                BranchFilter::Select(RegexSet::new(regexes).unwrap())\n            }\n        }\n    }\n}\n\n/// Filter branches with simple rules or regexes.\npub enum BranchFilter {\n    All,\n    Head,\n    Select(RegexSet),\n}\n\nimpl BranchFilter {\n    pub fn filter(&self, is_head: bool, branch: &str) -> bool {\n        match self {\n            BranchFilter::All => true,\n            BranchFilter::Select(patterns) => is_head || patterns.is_match(branch),\n            BranchFilter::Head => is_head,\n        }\n    }\n}\n\nimpl Default for BranchFilter {\n    fn default() -> Self {\n        Self::Head\n    }\n}\n\n/// Configure file filters\n#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq)]\npub struct FileFilterConfig {\n    pub(crate) rules: Vec<FileFilterRule>,\n}\n\n/// Rules for what gets included in a repository\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Hash, Eq)]\n#[serde(rename_all = \"snake_case\")]\npub enum FileFilterRule {\n    /// Include file with the exact relative path\n    IncludeFile(String),\n\n    /// Include files matching the regex pattern\n    IncludeRegex(String),\n\n    /// Exclude file with the exact relative path\n    ExcludeFile(String),\n\n    /// Exclude files matchin the regex pattern\n    ExcludeRegex(String),\n}\n\nimpl FileFilterConfig {\n    pub(crate) fn patch_into(&self, old: &FileFilterConfig) -> FileFilterConfig {\n        let mut rules = old.rules.iter().cloned().collect::<HashSet<_>>();\n\n        for rule in &self.rules {\n            if rules.contains(rule) {\n                continue;\n            }\n\n            match rule {\n                r @ FileFilterRule::IncludeFile(f) => {\n                    rules.remove(&FileFilterRule::ExcludeFile(f.to_string()));\n                    rules.insert(r.clone());\n                }\n                r @ FileFilterRule::IncludeRegex(x) => {\n                    rules.remove(&FileFilterRule::ExcludeRegex(x.to_string()));\n                    rules.insert(r.clone());\n                }\n                r @ FileFilterRule::ExcludeFile(f) => {\n                    rules.remove(&FileFilterRule::IncludeFile(f.to_string()));\n                    rules.insert(r.clone());\n                }\n                r @ FileFilterRule::ExcludeRegex(x) => {\n                    rules.remove(&FileFilterRule::IncludeRegex(x.to_string()));\n                    rules.insert(r.clone());\n                }\n            }\n        }\n\n        Self {\n            rules: rules.into_iter().collect(),\n        }\n    }\n}\n\n/// Compiled file filter.\npub struct FileFilter {\n    exclude_list: HashSet<String>,\n    include_list: HashSet<String>,\n    exclude_patterns: RegexSet,\n    include_patterns: RegexSet,\n}\n\nimpl FileFilter {\n    pub fn compile(config: &FileFilterConfig) -> anyhow::Result<Self> {\n        let mut exclude_list = HashSet::new();\n        let mut include_list = HashSet::new();\n        let mut exclude_patterns = HashSet::new();\n        let mut include_patterns = HashSet::new();\n\n        for rule in &config.rules {\n            match rule {\n                FileFilterRule::IncludeFile(name) => include_list.insert(name.to_string()),\n                FileFilterRule::IncludeRegex(pattern) => include_patterns.insert(pattern),\n                FileFilterRule::ExcludeFile(name) => exclude_list.insert(name.to_string()),\n                FileFilterRule::ExcludeRegex(pattern) => exclude_patterns.insert(pattern),\n            };\n        }\n\n        Ok(Self {\n            include_list,\n            exclude_list,\n            include_patterns: RegexSet::new(include_patterns)?,\n            exclude_patterns: RegexSet::new(exclude_patterns)?,\n        })\n    }\n\n    /// Returns:\n    ///  * `Some(true)` if the file is allowed\n    ///  * `Some(false)` if rejected\n    ///  * `None` if not mentioned at all\n    ///\n    /// Includes must take priority.\n    pub fn is_allowed<P: AsRef<Path> + ?Sized>(&self, path: &P) -> Option<bool> {\n        let lossy = path.as_ref().to_string_lossy();\n        let name = lossy.as_ref();\n\n        if self.include_list.contains(name) || self.include_patterns.is_match(name) {\n            Some(true)\n        } else if self.exclude_list.contains(name) || self.exclude_patterns.is_match(name) {\n            Some(false)\n        } else {\n            None\n        }\n    }\n}\n\nimpl From<&FileFilterConfig> for FileFilter {\n    fn from(value: &FileFilterConfig) -> Self {\n        Self::compile(value).unwrap()\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/repo/iterator/fs.rs",
    "content": "use crate::background;\n\nuse super::*;\n\nuse tracing::{debug, warn};\n\nuse std::path::{Path, PathBuf};\n\npub struct FileWalker {\n    file_list: Vec<PathBuf>,\n\n    /// Branch to index files as part of.\n    branch: String,\n}\n\nimpl FileWalker {\n    pub fn index_directory(dir: impl AsRef<Path>, branch: String) -> impl FileSource {\n        // note: this WILL observe .gitignore files for the respective repos.\n        let walker = ignore::WalkBuilder::new(&dir)\n            .standard_filters(true)\n            .hidden(false)\n            .build();\n\n        let file_list = walker\n            .filter_map(|de| match de {\n                Ok(de) => Some(de),\n                Err(err) => {\n                    warn!(%err, \"access failure; skipping\");\n                    None\n                }\n            })\n            .filter(|de| !de.path().strip_prefix(&dir).unwrap().starts_with(\".git\"))\n            .filter_map(|de| crate::canonicalize(de.into_path()).ok())\n            .collect();\n\n        Self { file_list, branch }\n    }\n}\n\nimpl FileSource for FileWalker {\n    fn len(&self) -> usize {\n        self.file_list.len()\n    }\n\n    fn for_each(self, pipes: &SyncPipes, iterator: impl Fn(RepoDirEntry) + Sync + Send) {\n        use rayon::prelude::*;\n        background::rayon_pool().install(|| {\n            self.file_list\n                .into_par_iter()\n                .filter_map(|entry_disk_path| {\n                    if entry_disk_path.is_file() {\n                        let path = entry_disk_path.clone();\n                        let buffer = Box::new(move || match std::fs::read_to_string(&path) {\n                            Err(err) => {\n                                warn!(%err, entry_disk_path=?path, \"read failed; skipping\");\n                                Err(err)\n                            }\n                            Ok(buffer) => Ok(buffer),\n                        });\n                        Some(RepoDirEntry::File(RepoFile {\n                            buffer,\n                            len: entry_disk_path.metadata().ok()?.len(),\n                            path: entry_disk_path.to_string_lossy().to_string(),\n                            branches: vec![self.branch.clone()],\n                        }))\n                    } else if entry_disk_path.is_dir() {\n                        Some(RepoDirEntry::Dir(RepoDir {\n                            path: entry_disk_path.to_string_lossy().to_string(),\n                            branches: vec![self.branch.clone()],\n                        }))\n                    } else {\n                        debug!(?entry_disk_path, \"skipping entry, not a file or directory\");\n                        None\n                    }\n                })\n                .take_any_while(|_| !pipes.is_cancelled())\n                .for_each(iterator);\n        })\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/repo/iterator/git.rs",
    "content": "use crate::{background, repo::RepoRef};\n\nuse super::{filters::BranchFilter, *};\n\nuse anyhow::Result;\nuse gix::ThreadSafeRepository;\nuse tracing::trace;\n\nuse std::{\n    collections::{BTreeSet, HashMap},\n    path::Path,\n};\n\nfn human_readable_branch_name(r: &gix::Reference<'_>) -> String {\n    use gix::bstr::ByteSlice;\n    r.name().shorten().to_str_lossy().to_string()\n}\n\npub struct GitWalker {\n    git: ThreadSafeRepository,\n    entries: HashMap<(String, FileType, gix::ObjectId), BTreeSet<String>>,\n}\n\nimpl GitWalker {\n    pub fn open_repository(\n        reporef: &RepoRef,\n        dir: impl AsRef<Path>,\n        branch_filter: impl Into<Option<BranchFilter>>,\n    ) -> Result<Self> {\n        let root_dir = dir.as_ref();\n\n        let branches = branch_filter.into().unwrap_or_default();\n        let git = gix::open::Options::isolated()\n            .filter_config_section(|_| false)\n            .open(dir.as_ref())?;\n\n        let local_git = git.to_thread_local();\n        let mut head = local_git.head()?;\n\n        // HEAD name needs to be pinned to the remote pointer\n        //\n        // Otherwise the local branch will never advance to the\n        // remote's branch ref\n        //\n        // The easiest here is to check by name, and assume the\n        // default remote is `origin`, since we don't configure it\n        // otherwise.\n        let head_name = head.clone().try_into_referent().map(|r| {\n            if reporef.is_local() {\n                human_readable_branch_name(&r)\n            } else {\n                format!(\"origin/{}\", human_readable_branch_name(&r))\n            }\n        });\n\n        let refs = local_git.references()?;\n        let trees = if head_name.is_none() && matches!(branches, BranchFilter::Head) {\n            // the current checkout is not a branch, so HEAD will not\n            // point to a real reference.\n            vec![(\n                true,\n                \"HEAD\".to_string(),\n                head.peel_to_commit_in_place()?.tree()?,\n            )]\n        } else {\n            refs.all()?\n                .filter_map(Result::ok)\n                // Check if it's HEAD\n                // Normalize the name of the branch for further steps\n                //\n                .map(|r| {\n                    let name = human_readable_branch_name(&r);\n                    (\n                        head_name\n                            .as_ref()\n                            .map(|head| head == &name)\n                            .unwrap_or_default(),\n                        name,\n                        r,\n                    )\n                })\n                .filter(|(_, name, _)| {\n                    if reporef.is_local() {\n                        true\n                    } else {\n                        // Only consider remote branches\n                        //\n                        name.starts_with(\"origin/\")\n                    }\n                })\n                // Apply branch filters, along whether it's HEAD\n                //\n                .filter(|(is_head, name, _)| branches.filter(*is_head, name))\n                .filter_map(|(is_head, branch, r)| -> Option<_> {\n                    Some((\n                        is_head,\n                        branch,\n                        r.into_fully_peeled_id()\n                            .ok()?\n                            .object()\n                            .ok()?\n                            .peel_to_tree()\n                            .ok()?,\n                    ))\n                })\n                .collect()\n        };\n\n        let entries = trees\n            .into_iter()\n            .flat_map(|(is_head, branch, tree)| {\n                let files = tree.traverse().breadthfirst.files().unwrap().into_iter();\n\n                files.map(move |entry| {\n                    let strpath = String::from_utf8_lossy(entry.filepath.as_ref());\n                    let full_path = root_dir.join(strpath.as_ref());\n                    trace!(?strpath, ?full_path, \"got path from gix\");\n                    (\n                        is_head,\n                        branch.clone(),\n                        full_path.to_string_lossy().to_string(),\n                        entry.mode,\n                        entry.oid,\n                    )\n                })\n            })\n            .fold(\n                HashMap::new(),\n                |mut acc, (is_head, branch, file, mode, oid)| {\n                    let kind = if mode.is_tree() {\n                        FileType::Dir\n                    } else if mode.is_blob() {\n                        FileType::File\n                    } else {\n                        FileType::Other\n                    };\n\n                    let branches: &mut BTreeSet<String> = acc.entry((file, kind, oid)).or_default();\n                    if is_head {\n                        branches.insert(\"HEAD\".to_string());\n                    }\n\n                    branches.insert(branch);\n                    acc\n                },\n            );\n\n        Ok(Self { git, entries })\n    }\n}\n\nimpl FileSource for GitWalker {\n    fn len(&self) -> usize {\n        self.entries.len()\n    }\n\n    fn for_each(self, pipes: &SyncPipes, iterator: impl Fn(RepoDirEntry) + Sync + Send) {\n        use rayon::prelude::*;\n        background::rayon_pool().install(|| {\n            self.entries\n                .into_par_iter()\n                .filter_map(|((path, kind, oid), branches)| {\n                    trace!(?path, \"walking over path\");\n                    let git = self.git.to_thread_local();\n                    let Ok(Some(object)) = git.try_find_object(oid) else {\n                        warn!(?path, ?branches, \"can't find object for file\");\n                        return None;\n                    };\n\n                    let entry = match kind {\n                        FileType::File => {\n                            let buffer = String::from_utf8_lossy(&object.data).to_string();\n                            RepoDirEntry::File(RepoFile {\n                                path,\n                                len: buffer.len() as u64,\n                                branches: branches.into_iter().collect(),\n                                buffer: Box::new(move || Ok(buffer.clone())),\n                            })\n                        }\n                        FileType::Dir => RepoDirEntry::Dir(RepoDir {\n                            path,\n                            branches: branches.into_iter().collect(),\n                        }),\n                        FileType::Other => return None,\n                    };\n\n                    Some(entry)\n                })\n                .take_any_while(|_| !pipes.is_cancelled())\n                .for_each(iterator)\n        })\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/repo/iterator/language.rs",
    "content": "use hyperpolyglot::detect_buffer;\nuse scc::hash_map::Entry;\nuse std::{\n    io::Cursor,\n    path::{Path, PathBuf},\n};\n\n#[derive(Debug, Default)]\npub struct LanguageInfo {\n    path_map: scc::HashMap<PathBuf, Option<&'static str>>,\n}\n\nimpl LanguageInfo {\n    pub fn get(&self, path: &Path, buf: &[u8]) -> Option<&'static str> {\n        match self.path_map.entry(path.to_owned()) {\n            Entry::Occupied(existing) => existing.get().to_owned(),\n            Entry::Vacant(vacant) => {\n                let detected = detect_language(path, buf);\n                vacant.insert_entry(detected);\n                detected\n            }\n        }\n    }\n\n    pub fn most_common_lang(&self) -> Option<&'static str> {\n        let counts = scc::HashMap::<&'static str, usize>::default();\n\n        self.path_map.scan(|_, lang| {\n            if let Some(l) = lang {\n                *counts.entry(l).or_default().get_mut() += 1;\n            }\n        });\n\n        let (mut max_k, mut max_v) = (None, 0);\n        counts.scan(|k, v| {\n            if *v > max_v {\n                (max_k, max_v) = (Some(*k), *v)\n            }\n        });\n\n        max_k\n    }\n}\n\nfn detect_language(path: &Path, buf: &[u8]) -> Option<&'static str> {\n    detect_buffer(path, |_| Ok(Cursor::new(buf)))\n        .ok()\n        .flatten()\n        .map(|d| d.language())\n}\n"
  },
  {
    "path": "server/bleep/src/repo/iterator.rs",
    "content": "use std::{collections::HashMap, path::Path};\n\nuse once_cell::sync::Lazy;\nuse regex::Regex;\nuse smallvec::SmallVec;\nuse tracing::warn;\n\nmod filters;\nmod fs;\nmod git;\npub(super) mod language;\n\npub use filters::*;\npub use fs::FileWalker;\npub use git::GitWalker;\n\nuse crate::background::SyncPipes;\n\n// Empirically calculated using:\n//     cat **/*.rs | awk '{SUM+=length;N+=1}END{print SUM/N}'\npub const AVG_LINE_LEN: u64 = 30;\npub const MAX_LINE_COUNT: u64 = 20000;\npub const MAX_FILE_LEN: u64 = AVG_LINE_LEN * MAX_LINE_COUNT;\n\n#[rustfmt::skip]\npub const EXT_BLACKLIST: &[&str] = &[\n    // graphics\n    \"png\", \"jpg\", \"jpeg\", \"ico\", \"bmp\", \"bpg\", \"eps\", \"pcx\", \"ppm\", \"tga\", \"tiff\", \"wmf\", \"xpm\",\n    \"svg\", \"riv\", \"gif\",\n    // fonts\n    \"ttf\", \"woff2\", \"fnt\", \"fon\", \"otf\",\n    // documents\n    \"pdf\", \"ps\", \"doc\", \"dot\", \"docx\", \"dotx\", \"xls\", \"xlsx\", \"xlt\", \"odt\", \"ott\", \"ods\", \"ots\", \"dvi\", \"pcl\",\n    // media\n    \"mp3\", \"ogg\", \"ac3\", \"aac\", \"mod\", \"mp4\", \"mkv\", \"avi\", \"m4v\", \"mov\", \"flv\",\n    // compiled\n    \"jar\", \"pyc\", \"war\", \"ear\",\n    // compression\n    \"tar\", \"gz\", \"bz2\", \"xz\", \"7z\", \"bin\", \"apk\", \"deb\", \"rpm\", \"rar\", \"zip\",\n    // binary\n    \"pkg\", \"pyd\", \"pyz\", \"lib\", \"pack\", \"idx\", \"dylib\", \"so\",\n    // executable\n    \"com\", \"exe\", \"out\", \"coff\", \"obj\", \"dll\", \"app\", \"class\",\n    // misc.\n    \"log\", \"wad\", \"bsp\", \"bak\", \"sav\", \"dat\", \"lock\",\n];\n\npub trait FileSource {\n    fn len(&self) -> usize;\n    fn for_each(self, signal: &SyncPipes, iterator: impl Fn(RepoDirEntry) + Sync + Send);\n}\n\npub enum RepoDirEntry {\n    Dir(RepoDir),\n    File(RepoFile),\n}\n\nimpl RepoDirEntry {\n    pub fn path(&self) -> &str {\n        match self {\n            Self::File(file) => file.path.as_str(),\n            Self::Dir(dir) => dir.path.as_str(),\n        }\n    }\n\n    pub fn buffer(&self) -> Option<String> {\n        match self {\n            Self::File(file) => (file.buffer)().ok(),\n            _ => None,\n        }\n    }\n\n    pub fn branches(&self) -> &[String] {\n        match self {\n            RepoDirEntry::Dir(d) => &d.branches,\n            RepoDirEntry::File(f) => &f.branches,\n        }\n    }\n}\n\npub struct RepoDir {\n    pub path: String,\n    pub branches: Vec<String>,\n}\n\nimpl RepoDir {\n    pub fn size(&self) -> usize {\n        use std::io::{Cursor, Seek, SeekFrom};\n        Cursor::new(&self.path).seek(SeekFrom::End(0)).unwrap_or(0) as usize\n    }\n}\n\npub struct RepoFile {\n    /// Path to file\n    pub path: String,\n    /// Branches which include the file\n    pub branches: Vec<String>,\n    /// Length of the buffer\n    pub len: u64,\n    /// Lazily loaded buffer that contains the file contents\n    buffer: Box<dyn Fn() -> std::io::Result<String> + Send + Sync>,\n}\n\nimpl RepoFile {\n    pub fn should_index(&self) -> bool {\n        should_index_path(&self.path) && self.len < MAX_FILE_LEN\n    }\n\n    pub fn buffer(&self) -> std::io::Result<String> {\n        (self.buffer)()\n    }\n\n    pub fn size(&self) -> usize {\n        self.len as usize\n    }\n}\n\n#[derive(Hash, Eq, PartialEq)]\npub enum FileType {\n    File,\n    Dir,\n    Other,\n}\n\nfn should_index_path<P: AsRef<Path> + ?Sized>(p: &P) -> bool {\n    let path = p.as_ref();\n\n    // TODO: Make this more robust\n    if path.components().any(|c| c.as_os_str() == \".git\") {\n        return false;\n    }\n\n    let Some(ext) = path.extension() else {\n        return true;\n    };\n\n    let ext = ext.to_string_lossy();\n    if EXT_BLACKLIST.contains(&&*ext) {\n        return false;\n    }\n\n    static VENDOR_PATTERNS: Lazy<HashMap<&'static str, SmallVec<[Regex; 1]>>> = Lazy::new(|| {\n        let patterns: &[(&[&str], &[&str])] = &[\n            (\n                &[\"go\", \"proto\"],\n                &[\"^(vendor|third_party)/.*\\\\.\\\\w+$\", \"\\\\w+\\\\.pb\\\\.go$\"],\n            ),\n            (\n                &[\"js\", \"jsx\", \"ts\", \"tsx\", \"css\", \"md\", \"json\", \"txt\", \"conf\"],\n                &[\"^(node_modules|vendor|dist)/.*\\\\.\\\\w+$\"],\n            ),\n        ];\n\n        patterns\n            .iter()\n            .flat_map(|(exts, rxs)| exts.iter().map(move |&e| (e, rxs)))\n            .map(|(ext, rxs)| {\n                let regexes = rxs\n                    .iter()\n                    .filter_map(|source| match Regex::new(source) {\n                        Ok(r) => Some(r),\n                        Err(e) => {\n                            warn!(%e, \"failed to compile vendor regex {source:?}\");\n                            None\n                        }\n                    })\n                    .collect();\n\n                (ext, regexes)\n            })\n            .collect()\n    });\n\n    match VENDOR_PATTERNS.get(&*ext) {\n        None => true,\n        Some(rxs) => !rxs.iter().any(|r| r.is_match(&path.to_string_lossy())),\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_should_index() {\n        let tests = [\n            // Ignore these extensions completely.\n            (\"image.png\", false),\n            (\"image.jpg\", false),\n            (\"image.jpeg\", false),\n            (\"font.ttf\", false),\n            (\"font.otf\", false),\n            (\"font.woff2\", false),\n            (\"icon.ico\", false),\n            // Simple paths that should be indexed.\n            (\"foo.js\", true),\n            (\"bar.ts\", true),\n            (\"quux/fred.ts\", true),\n            // Typical vendored paths.\n            (\"vendor/jquery.js\", false),\n            (\"dist/react.js\", false),\n            (\"vendor/github.com/Microsoft/go-winio/file.go\", false),\n            (\n                \"third_party/protobuf/google/protobuf/descriptor.proto\",\n                false,\n            ),\n            (\"src/defs.pb.go\", false),\n            // These are not typically vendored in Rust.\n            (\"dist/main.rs\", true),\n            (\"vendor/foo.rs\", true),\n            // Ignore .git directory.\n            (\".git/HEAD\", false),\n            (\".git/config\", false),\n            (\".gitignore\", true),\n            (\".github/workflows/ci.yml\", true),\n        ];\n\n        for (path, index) in tests {\n            assert_eq!(should_index_path(&Path::new(path)), index);\n        }\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/repo.rs",
    "content": "use anyhow::Context;\nuse serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};\nuse std::{\n    fmt::{self, Display},\n    path::{Path, PathBuf},\n    str::FromStr,\n    sync::Arc,\n    time::SystemTime,\n};\nuse tracing::debug;\n\nuse crate::state::get_relative_path;\n\npub(crate) mod iterator;\nuse iterator::language;\n\npub use iterator::{BranchFilterConfig, FileFilterConfig, FilterUpdate};\n\n#[derive(thiserror::Error, Debug)]\n#[error(\"repository locked\")]\npub struct RepoLocked;\n\n// Types of repo\n#[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Clone, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum Backend {\n    Local,\n    Github,\n}\n\n// Repository identifier\n#[derive(Hash, Eq, PartialEq, Debug, Clone)]\npub struct RepoRef {\n    pub backend: Backend,\n    pub name: String,\n}\n\nimpl RepoRef {\n    pub fn new(backend: Backend, name: &(impl AsRef<str> + ?Sized)) -> Result<Self, RepoError> {\n        use Backend::*;\n\n        match backend {\n            Github => Ok(RepoRef {\n                backend,\n                name: name.as_ref().to_owned(),\n            }),\n            Local => {\n                let path = Path::new(name.as_ref());\n\n                if !path.is_absolute() {\n                    return Err(RepoError::NonAbsoluteLocal);\n                }\n\n                for component in path.components() {\n                    use std::path::Component::*;\n                    match component {\n                        CurDir | ParentDir => return Err(RepoError::InvalidPath),\n                        _ => continue,\n                    }\n                }\n\n                Ok(RepoRef {\n                    backend,\n                    name: name.as_ref().to_owned(),\n                })\n            }\n        }\n    }\n\n    pub fn from_components(root: &Path, components: Vec<String>) -> Result<Self, RepoError> {\n        let refstr = components.join(\"/\");\n        let pathstr = match refstr.trim_start_matches('/').split_once('/') {\n            Some((\"github.com\", name)) => return RepoRef::new(Backend::Github, name),\n            Some((\"local\", name)) => name,\n            _ => &refstr,\n        };\n\n        let local_path = get_relative_path(Path::new(pathstr), root);\n        Self::new(Backend::Local, &local_path.to_string_lossy())\n    }\n\n    pub fn backend(&self) -> Backend {\n        self.backend.clone()\n    }\n\n    pub fn name(&self) -> &str {\n        &self.name\n    }\n\n    pub fn is_local(&self) -> bool {\n        self.backend == Backend::Local\n    }\n\n    pub fn is_remote(&self) -> bool {\n        self.backend != Backend::Local\n    }\n\n    pub fn indexed_name(&self) -> String {\n        // Local repos indexed as: dirname\n        // Github repos indexed as: org/repo\n        match self.backend {\n            Backend::Local => Path::new(&self.name)\n                .file_name()\n                .expect(\"last component is `..`\")\n                .to_string_lossy()\n                .into(),\n            Backend::Github => self.name.to_owned(),\n        }\n    }\n\n    pub fn local_path(&self) -> Option<PathBuf> {\n        match self.backend {\n            Backend::Local => Some(PathBuf::from(&self.name)),\n            _ => None,\n        }\n    }\n}\n\nimpl AsRef<RepoRef> for RepoRef {\n    fn as_ref(&self) -> &RepoRef {\n        self\n    }\n}\n\nimpl<P: AsRef<Path>> From<&P> for RepoRef {\n    fn from(path: &P) -> Self {\n        assert!(path.as_ref().is_absolute());\n        RepoRef {\n            backend: Backend::Local,\n            name: path.as_ref().to_string_lossy().to_string(),\n        }\n    }\n}\n\nimpl From<&str> for RepoRef {\n    fn from(refstr: &str) -> Self {\n        Self::from_str(refstr).unwrap()\n    }\n}\n\nimpl FromStr for RepoRef {\n    type Err = RepoError;\n\n    fn from_str(refstr: &str) -> Result<Self, Self::Err> {\n        match refstr.trim_start_matches('/').split_once('/') {\n            // github.com/...\n            Some((\"github.com\", name)) => RepoRef::new(Backend::Github, name),\n            // local/...\n            Some((\"local\", name)) => RepoRef::new(Backend::Local, name),\n            _ => Err(RepoError::InvalidBackend),\n        }\n    }\n}\n\nimpl Display for RepoRef {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self.backend() {\n            Backend::Github => write!(f, \"github.com/{}\", self.name()),\n            Backend::Local => write!(f, \"local/{}\", self.name()),\n        }\n    }\n}\n\nimpl Serialize for RepoRef {\n    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        serializer.serialize_str(&self.to_string())\n    }\n}\n\nimpl<'de> Deserialize<'de> for RepoRef {\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        String::deserialize(deserializer).and_then(|s| {\n            RepoRef::from_str(s.as_str()).map_err(|e| D::Error::custom(e.to_string()))\n        })\n    }\n}\n\n#[derive(Serialize, Deserialize, Debug, Clone)]\npub struct Repository {\n    /// Path to the physical location of the repo root\n    pub disk_path: PathBuf,\n\n    /// Configuration of the remote to sync with\n    pub remote: RepoRemote,\n\n    /// Current user-readable status of syncing\n    pub sync_status: SyncStatus,\n\n    /// Time of last commit at the last successful index\n    pub last_commit_unix_secs: i64,\n\n    /// Time of last successful index\n    pub last_index_unix_secs: u64,\n\n    /// Most common language\n    pub most_common_lang: Option<String>,\n\n    /// Filters which branches to index\n    pub branch_filter: Option<BranchFilterConfig>,\n\n    /// Custom file filter overrides\n    #[serde(default)]\n    pub file_filter: FileFilterConfig,\n\n    /// Indicate that this repository is to be cloned as a shallow copy\n    ///\n    /// Defaults to `false for existing repos.\n    ///\n    /// This is a set-once value, meaning there's no meaningful\n    /// reversal of a `false` value to `true`, but `true` to `false`\n    /// is ok.\n    #[serde(default)]\n    pub shallow: bool,\n\n    /// Sync lock\n    #[serde(skip)]\n    pub locked: bool,\n\n    /// Current user-readable status of syncing\n    #[serde(skip)]\n    pub pub_sync_status: SyncStatus,\n}\n\nimpl Repository {\n    /// Only use this with local refs\n    ///\n    /// # Panics\n    ///\n    /// When used with non-local refs\n    pub(crate) fn local_from(reporef: &RepoRef) -> Self {\n        use gix::remote::Direction;\n        let disk_path = reporef.local_path().unwrap();\n\n        let remote = gix::open(&disk_path)\n            .map_err(anyhow::Error::from)\n            .and_then(|git| {\n                let origin = git\n                    .find_default_remote(Direction::Fetch)\n                    .context(\"no git remote\")??;\n                let url = origin.url(Direction::Fetch).context(\"no fetch url\")?;\n                let remote = url\n                    .to_bstring()\n                    .to_string()\n                    .parse()\n                    .map_err(|_| anyhow::format_err!(\"remote url not understood\"))?;\n\n                debug!(%reporef, origin=?remote, \"found git repo with remote\");\n                Ok(remote)\n            })\n            .unwrap_or_else(|_| RepoRemote::from(reporef));\n\n        Self {\n            sync_status: SyncStatus::Queued,\n            pub_sync_status: SyncStatus::Queued,\n            last_index_unix_secs: 0,\n            last_commit_unix_secs: 0,\n            most_common_lang: None,\n            branch_filter: None,\n            file_filter: Default::default(),\n            locked: false,\n            shallow: false,\n            disk_path,\n            remote,\n        }\n    }\n\n    /// Delete the on-disk data for this repository asynchronously.\n    pub async fn remove_all(&self) -> Result<(), std::io::Error> {\n        if self.disk_path.exists() {\n            tokio::fs::remove_dir_all(&self.disk_path).await\n        } else {\n            Ok(())\n        }\n    }\n\n    /// Pre-scan the repository to provide supporting metadata for a\n    /// new indexing operation\n    pub async fn get_repo_metadata(&self) -> Arc<RepoMetadata> {\n        let last_commit_unix_secs = gix::open(&self.disk_path)\n            .context(\"failed to open git repo\")\n            .and_then(|repo| Ok(repo.head()?.peel_to_commit_in_place()?.time()?.seconds))\n            .ok();\n\n        let langs = Default::default();\n\n        RepoMetadata {\n            last_commit_unix_secs,\n            langs,\n        }\n        .into()\n    }\n\n    /// Marks the repository for removal on the next sync\n    /// Does not initiate a new sync.\n    pub(crate) fn mark_removed(&mut self) {\n        self.sync_status = SyncStatus::Removed;\n        self.pub_sync_status = SyncStatus::Removed;\n    }\n\n    /// Marks the repository for indexing on the next sync\n    /// Does not initiate a new sync.\n    pub(crate) fn mark_queued(&mut self) {\n        self.sync_status = SyncStatus::Queued;\n    }\n\n    /// Locks the repository or returns with an error if already locked.\n    ///\n    /// The returned error helps track conflicting sync processes, and\n    /// avoids ambiguity about the lifecycle the repository's in.\n    pub(crate) fn lock(&mut self) -> Result<(), RepoLocked> {\n        if self.locked {\n            Err(RepoLocked)\n        } else {\n            self.locked = true;\n            Ok(())\n        }\n    }\n\n    pub(crate) fn sync_done_with(\n        &mut self,\n        shallow: bool,\n        filter_update: &FilterUpdate,\n        metadata: Arc<RepoMetadata>,\n    ) {\n        self.last_index_unix_secs = get_unix_time(SystemTime::now());\n        self.last_commit_unix_secs = metadata.last_commit_unix_secs.unwrap_or(0);\n        self.most_common_lang = metadata\n            .langs\n            .most_common_lang()\n            .map(|l| l.to_string())\n            .or_else(|| self.most_common_lang.take());\n\n        if let Some(ref bf) = filter_update.branch_filter {\n            self.branch_filter = bf.patch_into(self.branch_filter.as_ref());\n        }\n\n        self.shallow = shallow;\n        self.locked = false;\n\n        if shallow {\n            self.sync_status = SyncStatus::Shallow\n        } else if let Some(ref ff) = filter_update.file_filter {\n            self.file_filter = ff.patch_into(&self.file_filter);\n        };\n    }\n}\n\nfn get_unix_time(time: SystemTime) -> u64 {\n    time.duration_since(SystemTime::UNIX_EPOCH)\n        .expect(\"system time error\")\n        .as_secs()\n}\n\n#[derive(Debug)]\npub struct RepoMetadata {\n    pub last_commit_unix_secs: Option<i64>,\n    pub langs: language::LanguageInfo,\n}\n\n#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Hash, Default)]\n#[serde(rename_all = \"snake_case\")]\npub enum SyncStatus {\n    /// There was an error during last sync & index\n    Error { message: String },\n\n    /// Repository is not yet managed by bloop\n    Uninitialized,\n\n    /// Removed by the user\n    Removed,\n\n    /// The user requested cancelling the process\n    Cancelling,\n\n    /// Last sync & index cancelled by the user\n    Cancelled,\n\n    /// Queued for sync & index\n    #[default]\n    Queued,\n\n    /// Active Git clone in progress (we don't report fetch/pull)\n    Syncing,\n\n    /// Active indexing in progress\n    Indexing,\n\n    /// VCS remote has been removed\n    RemoteRemoved,\n\n    /// There's a clone, but only usable for directory listings\n    /// through a dedicated API based on Git\n    Shallow,\n\n    /// Successfully indexed\n    Done,\n}\n\nimpl SyncStatus {\n    pub(crate) fn indexable(&self) -> bool {\n        use SyncStatus::*;\n        matches!(self, Queued | Done | Error { .. })\n    }\n}\n\n#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub struct GitRemote {\n    /// protocol to use during git operations\n    pub protocol: GitProtocol,\n    /// Hostname of provider\n    pub host: String,\n    /// any kind of `protocol` and [`Backend`]-dependent address\n    pub address: String,\n}\n\n#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum GitProtocol {\n    Https,\n    Ssh,\n}\n\n#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum RepoRemote {\n    Git(GitRemote),\n    None,\n}\n\nimpl<T: AsRef<RepoRef>> From<T> for RepoRemote {\n    fn from(reporef: T) -> Self {\n        match reporef.as_ref() {\n            RepoRef {\n                backend: Backend::Github,\n                name,\n            } => RepoRemote::Git(GitRemote {\n                protocol: GitProtocol::Https,\n                host: \"github.com\".to_owned(),\n                address: name.to_owned(),\n            }),\n            RepoRef {\n                backend: Backend::Local,\n                name: _name,\n            } => RepoRemote::None,\n        }\n    }\n}\n\nimpl Display for RepoRemote {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            RepoRemote::Git(GitRemote {\n                protocol,\n                host,\n                address,\n            }) => match protocol {\n                GitProtocol::Https => write!(f, \"https://{host}/{address}.git\"),\n                GitProtocol::Ssh => write!(f, \"git@{host}:{address}.git\"),\n            },\n            RepoRemote::None => write!(f, \"none\"),\n        }\n    }\n}\n\nimpl FromStr for RepoRemote {\n    type Err = ();\n\n    fn from_str(value: &str) -> Result<Self, Self::Err> {\n        if let Some(stripped) = value.strip_prefix(\"https://github.com/\") {\n            return Ok(RepoRemote::Git(GitRemote {\n                protocol: GitProtocol::Https,\n                host: \"github.com\".to_owned(),\n                address: stripped\n                    .trim_end_matches('/')\n                    .trim_end_matches(\".git\")\n                    .to_owned(),\n            }));\n        }\n\n        if let Some(stripped) = value.strip_prefix(\"git@github.com:\") {\n            return Ok(RepoRemote::Git(GitRemote {\n                protocol: GitProtocol::Ssh,\n                host: \"github.com\".to_owned(),\n                address: stripped\n                    .trim_start_matches('/')\n                    .trim_end_matches('/')\n                    .trim_end_matches(\".git\")\n                    .to_owned(),\n            }));\n        }\n\n        Err(())\n    }\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum RepoError {\n    #[error(\"no source configured\")]\n    NoSourceGiven,\n    #[error(\"local repository must have an absolute path\")]\n    NonAbsoluteLocal,\n    #[error(\"paths can't contain `..` or `.`\")]\n    InvalidPath,\n    #[error(\"backend not recognized\")]\n    InvalidBackend,\n    #[error(\"IO error: {error}\")]\n    IO {\n        #[from]\n        error: std::io::Error,\n    },\n    #[error(\"invalid state file\")]\n    Decode {\n        #[from]\n        error: serde_json::Error,\n    },\n    #[error(\"indexing error\")]\n    Anyhow {\n        #[from]\n        error: anyhow::Error,\n    },\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn parse_reporef() {\n        assert_eq!(\n            \"github.com/bloopai/bloop\".parse::<RepoRef>().unwrap(),\n            RepoRef::new(Backend::Github, \"bloopai/bloop\").unwrap()\n        );\n        assert_eq!(\n            \"local//tmp/repository\".parse::<RepoRef>().unwrap(),\n            RepoRef::new(Backend::Local, \"/tmp/repository\").unwrap()\n        );\n        assert_eq!(\n            \"local//tmp/repository\".parse::<RepoRef>().unwrap(),\n            RepoRef::new(Backend::Local, \"/tmp/repository\").unwrap()\n        );\n        if \"repository\".parse::<RepoRef>().is_ok() {\n            panic!(\"non-absolute local allowed\")\n        }\n    }\n\n    #[test]\n    fn serialize_reporef() {\n        assert_eq!(\n            r#\"\"github.com/org/repo\"\"#,\n            &serde_json::to_string(&RepoRef::new(Backend::Github, \"org/repo\").unwrap()).unwrap()\n        );\n        assert_eq!(\n            r#\"\"local//org/repo\"\"#,\n            &serde_json::to_string(&RepoRef::new(Backend::Local, \"/org/repo\").unwrap()).unwrap()\n        );\n    }\n\n    #[test]\n    fn parse_reporemote() {\n        let https = RepoRemote::Git(GitRemote {\n            host: \"github.com\".into(),\n            address: \"org/repo\".into(),\n            protocol: GitProtocol::Https,\n        });\n\n        let ssh = RepoRemote::Git(GitRemote {\n            host: \"github.com\".into(),\n            address: \"org/repo\".into(),\n            protocol: GitProtocol::Ssh,\n        });\n\n        assert_eq!(https, \"https://github.com/org/repo\".parse().unwrap());\n        assert_eq!(https, \"https://github.com/org/repo.git\".parse().unwrap());\n        assert_eq!(ssh, \"git@github.com:/org/repo.git\".parse().unwrap());\n        assert_eq!(ssh, \"git@github.com:/org/repo\".parse().unwrap());\n        assert_eq!(ssh, \"git@github.com:org/repo\".parse().unwrap());\n        assert_eq!(ssh, \"git@github.com:org/repo.git\".parse().unwrap());\n        assert_eq!(ssh, \"git@github.com:org/repo.git/\".parse().unwrap());\n        assert_eq!(ssh, \"git@github.com:/org/repo.git/\".parse().unwrap());\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/scraper/article.rs",
    "content": "use anyhow::{Context, Result};\nuse once_cell::sync::Lazy;\nuse regex::Regex;\nuse reqwest::{\n    header::{HeaderMap, USER_AGENT},\n    redirect::Policy,\n    IntoUrl,\n};\nuse select::{\n    document::Document,\n    node::{Descendants, Node},\n    predicate::{Attr, Name, Predicate},\n};\nuse url::Url;\n\nuse crate::query::languages::{EXT_MAP, PROPER_CASE_MAP};\n\nuse std::{\n    borrow::Cow,\n    collections::{HashMap, HashSet},\n    ops::Deref,\n    str::FromStr,\n    time::Duration,\n};\n\nstatic RE_BAD_NODES_ATTR: Lazy<Regex> = Lazy::new(|| {\n    Regex::new(r###\"(?mi)^side$|combx|retweet|mediaarticlerelated|menucontainer|navbar|storytopbar-bucket|utility-bar|inline-share-tools|comment|PopularQuestions|contact|foot(er|note)?|cnn_strycaptiontxt|cnn_html_slideshow|cnn_strylftcntnt|links|meta$|shoutbox|sponsor|tags|socialnetworking|socialNetworking|cnnStryHghLght|cnn_stryspcvbx|^inset$|pagetools|post-attributes|welcome_form|contentTools2|the_answers|communitypromo|runaroundLeft|subscribe|vcard|articleheadings|date|^print$|popup|author-dropdown|socialtools|byline|konafilter|breadcrumbs|^fn$|wp-caption-text|legende|ajoutVideo|timestamp|js_replies|[^-]facebook(-broadcasting)?|google|[^-]twitter|styln-briefing-block|read-more-link|js-body-read-more\"###).unwrap()\n});\nconst PUNCTUATION: &str = r#\",.\"'!?&-/:;()#$%*+<=>@[\\]^_`{|}~\"#;\nconst ARTICLE_BODY_ATTR: &[(&str, &str); 3] = &[\n    (\"itemprop\", \"articleBody\"),\n    (\"data-testid\", \"article-body\"),\n    (\"name\", \"articleBody\"),\n];\nconst BAD_NODE_NAMES: &[&str; 9] = &[\n    \"nav\",\n    \"script\",\n    \"style\",\n    \"figcaption\",\n    \"figure\",\n    \"button\",\n    \"summary\",\n    \"aside\",\n    // astro components - the top level astro-island should suffice\n    \"astro-island\",\n];\nconst ATTR_TO_CHECK: [&str; 3] = [\"id\", \"class\", \"name\"];\n\n#[derive(Debug)]\nstruct DefaultExtractor {\n    url: Url,\n}\n\nimpl Extractor for DefaultExtractor {\n    fn url(&self) -> &Url {\n        &self.url\n    }\n}\n\ntrait Extractor {\n    fn title<'a>(&self, doc: &'a Document) -> Option<Cow<'a, str>> {\n        if let Some(title) = doc.find(Name(\"title\")).next() {\n            return Some(Cow::Owned(title.text()));\n        }\n\n        if let Some(title) = self.meta_content(doc, Attr(\"property\", \"og:title\")) {\n            return Some(title);\n        }\n\n        if let Some(title) = self.meta_content(doc, Attr(\"name\", \"og:title\")) {\n            return Some(title);\n        }\n\n        if let Some(title) = doc\n            .find(Name(\"h1\"))\n            .filter_map(|node| node.as_text().map(str::trim))\n            .next()\n        {\n            return Some(Cow::Borrowed(title));\n        }\n        None\n    }\n\n    fn base_url(&self, doc: &Document) -> Option<Url> {\n        doc.find(Name(\"base\"))\n            .filter_map(|n| n.attr(\"href\"))\n            .filter_map(|href| Url::parse(href).ok())\n            .next()\n    }\n\n    fn meta_language(&self, doc: &Document) -> Option<Language> {\n        let mut unknown_lang = None;\n\n        if let Some(meta) = self.meta_content(doc, Attr(\"http-equiv\", \"Content-Language\")) {\n            match Language::from_str(&meta) {\n                Ok(lang) => return Some(lang),\n                Err(lang) => {\n                    unknown_lang = Some(lang);\n                }\n            }\n        }\n\n        if let Some(meta) = self.meta_content(doc, Attr(\"name\", \"lang\")) {\n            match Language::from_str(&meta) {\n                Ok(lang) => return Some(lang),\n                Err(lang) => {\n                    unknown_lang = Some(lang);\n                }\n            }\n        }\n        unknown_lang\n    }\n\n    fn meta_content<'a, 'b>(\n        &self,\n        doc: &'a Document,\n        attr: Attr<&'b str, &'b str>,\n    ) -> Option<Cow<'a, str>> {\n        doc.find(Name(\"head\").descendant(Name(\"meta\").and(attr)))\n            .filter_map(|node| {\n                node.attr(\"content\")\n                    .map(str::trim)\n                    .filter(|s| !s.is_empty())\n                    .map(Cow::Borrowed)\n            })\n            .next()\n    }\n\n    fn meta_site_name<'a>(&self, doc: &'a Document) -> Option<Cow<'a, str>> {\n        self.meta_content(doc, Attr(\"property\", \"og:site_name\"))\n    }\n\n    /// If the article has meta description set in the source, use that\n    fn meta_description<'a>(&self, doc: &'a Document) -> Option<Cow<'a, str>> {\n        [(\"property\", \"description\"), (\"name\", \"description\")]\n            .iter()\n            .filter_map(|(k, v)| self.meta_content(doc, Attr(k, v)))\n            .next()\n    }\n\n    fn icon<'a>(&self, doc: &'a Document) -> Cow<'a, str> {\n        Cow::Borrowed(\n            doc.find(Name(\"head\").descendant(\n                Name(\"link\").and(Attr(\"rel\", \"icon\").or(Attr(\"rel\", \"shortcut icon\"))),\n            ))\n            .find_map(|node| node.attr(\"href\").map(str::trim).filter(|s| !s.is_empty()))\n            .unwrap_or(\"/favicon.ico\"),\n        )\n    }\n\n    fn text<'a>(&self, doc: &'a Document, lang: Language) -> Option<Cow<'a, str>> {\n        self.text_with_cleaner(\n            doc,\n            lang,\n            DefaultDocumentCleaner {\n                url: self.url().clone(),\n            },\n        )\n    }\n\n    fn text_with_cleaner<'a, T: DocumentCleaner>(\n        &self,\n        doc: &'a Document,\n        lang: Language,\n        cleaner: T,\n    ) -> Option<Cow<'a, str>> {\n        self.article_node(doc, lang)\n            .map(|n| cleaner.clean_node_text(*n).into())\n    }\n\n    fn article_node<'a>(&self, doc: &'a Document, lang: Language) -> Option<ArticleTextNode<'a>> {\n        let mut iter =\n            doc.find(Name(\"body\").descendant(ArticleTextNodeExtractor::article_body_predicate()));\n        if let Some(node) = iter.next() {\n            if iter.next().is_none() {\n                return Some(ArticleTextNode::new(node));\n            }\n        }\n        ArticleTextNodeExtractor::calculate_best_node(doc, lang)\n    }\n\n    fn all_urls<'a>(&self, doc: &'a Document) -> Vec<Cow<'a, str>> {\n        let mut uniques = HashSet::new();\n        doc.find(Name(\"a\"))\n            .filter_map(|n| n.attr(\"href\").map(str::trim))\n            .filter(|href| uniques.insert(*href))\n            .map(Cow::Borrowed)\n            .collect()\n    }\n\n    fn article_content<'a>(&self, doc: &'a Document, lang: Option<Language>) -> ArticleContent<'a> {\n        let mut builder = ArticleContent::builder();\n\n        let lang = if let Some(meta_lang) = self.meta_language(doc) {\n            builder = builder.language(meta_lang.clone());\n            meta_lang\n        } else {\n            lang.unwrap_or_default()\n        };\n\n        if let Some(description) = self.meta_description(doc) {\n            builder = builder.description(description);\n        }\n\n        if let Some(title) = self.title(doc) {\n            builder = builder.title(title);\n        }\n\n        builder = builder.icon(self.icon(doc));\n\n        if let Some(txt_node) = self.article_node(doc, lang) {\n            builder = builder.text(txt_node.clean_text(self.url()).into());\n        }\n\n        builder.build()\n    }\n\n    fn canonical_link(&self, doc: &Document) -> Option<Url> {\n        if let Some(link) = doc\n            .find(Name(\"link\").and(Attr(\"rel\", \"canonical\")))\n            .filter_map(|node| node.attr(\"href\"))\n            .next()\n        {\n            return Url::parse(link).ok();\n        }\n\n        if let Some(meta) = self.meta_content(doc, Attr(\"property\", \"og:url\")) {\n            return Url::parse(&meta).ok();\n        }\n\n        None\n    }\n\n    fn url(&self) -> &Url;\n}\n\n#[derive(Debug)]\npub struct Article {\n    pub url: Url,\n    pub doc: Document,\n    pub content: ArticleContent<'static>,\n    pub language: Language,\n}\n\nimpl Article {\n    pub fn builder<T: IntoUrl>(url: T) -> Result<ArticleBuilder> {\n        ArticleBuilder::new(url)\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct ArticleContent<'a> {\n    pub title: Option<Cow<'a, str>>,\n    pub icon: Option<Cow<'a, str>>,\n    pub language: Option<Language>,\n    pub description: Option<Cow<'a, str>>,\n    pub text: Option<Cow<'a, str>>,\n}\n\nimpl<'a> ArticleContent<'a> {\n    fn builder() -> ArticleContentBuilder<'a> {\n        ArticleContentBuilder::default()\n    }\n\n    fn into_owned(self) -> ArticleContent<'static> {\n        ArticleContent {\n            title: self.title.map(Cow::into_owned).map(Cow::Owned),\n            icon: self.icon.map(Cow::into_owned).map(Cow::Owned),\n            language: self.language,\n            description: self.description.map(Cow::into_owned).map(Cow::Owned),\n            text: self.text.map(Cow::into_owned).map(Cow::Owned),\n        }\n    }\n}\n#[derive(Debug, Default)]\nstruct ArticleContentBuilder<'a> {\n    title: Option<Cow<'a, str>>,\n    icon: Option<Cow<'a, str>>,\n    text: Option<Cow<'a, str>>,\n    language: Option<Language>,\n    description: Option<Cow<'a, str>>,\n}\n\nimpl<'a> ArticleContentBuilder<'a> {\n    fn title(mut self, title: Cow<'a, str>) -> Self {\n        self.title = Some(title);\n        self\n    }\n\n    fn icon(mut self, icon: Cow<'a, str>) -> Self {\n        self.icon = Some(icon);\n        self\n    }\n\n    fn text(mut self, text: Cow<'a, str>) -> Self {\n        self.text = Some(text);\n        self\n    }\n\n    fn language(mut self, language: Language) -> Self {\n        self.language = Some(language);\n        self\n    }\n\n    fn description(mut self, description: Cow<'a, str>) -> Self {\n        self.description = Some(description);\n        self\n    }\n\n    fn build(self) -> ArticleContent<'a> {\n        ArticleContent {\n            title: self.title,\n            icon: self.icon,\n            text: self.text,\n            description: self.description,\n            language: self.language,\n        }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Default)]\npub enum Language {\n    Arabic,\n    Russian,\n    Dutch,\n    German,\n    #[default]\n    English,\n    Spanish,\n    French,\n    Hebrew,\n    Italian,\n    Korean,\n    Norwegian,\n    Persian,\n    Polish,\n    Portuguese,\n    Swedish,\n    Hungarian,\n    Finnish,\n    Danish,\n    Chinese,\n    Indonesian,\n    Vietnamese,\n    Swahili,\n    Turkish,\n    Greek,\n    Ukrainian,\n    Other(String),\n}\n\nimpl Language {\n    fn stopword_count(&self, txt: &str) -> Option<usize> {\n        Some(ArticleTextNodeExtractor::words(txt).count())\n    }\n}\n\npub struct ArticleBuilder {\n    url: Option<Url>,\n    timeout: Option<Duration>,\n    language: Option<Language>,\n    browser_user_agent: Option<String>,\n}\n\nimpl ArticleBuilder {\n    fn new<T: IntoUrl>(url: T) -> Result<Self> {\n        let url = url.into_url()?;\n\n        Ok(ArticleBuilder {\n            url: Some(url),\n            timeout: None,\n            language: None,\n            browser_user_agent: None,\n        })\n    }\n\n    pub async fn get(self) -> Result<Article> {\n        let url = self.url.clone();\n        self.get_with_extractor(&DefaultExtractor {\n            url: url.context(\"url not initialized\")?,\n        })\n        .await\n    }\n\n    async fn get_with_extractor<TExtract: Extractor>(\n        self,\n        extractor: &TExtract,\n    ) -> Result<Article> {\n        let url = self\n            .url\n            .context(\"Url of the article must be initialized.\")?;\n\n        let builder = {\n            let timeout = self.timeout.unwrap_or_else(|| Duration::from_secs(5));\n\n            let mut headers = HeaderMap::with_capacity(1);\n\n            headers.insert(\n                USER_AGENT,\n                self.browser_user_agent\n                    .map(|x| x.parse())\n                    .unwrap_or_else(|| {\n                        format!(\"bloop/{} bloop-doc-scraper\", env!(\"CARGO_PKG_VERSION\")).parse()\n                    })\n                    .context(\"Failed to parse user agent header.\")?,\n            );\n\n            reqwest::Client::builder()\n                .default_headers(headers)\n                .redirect(Policy::limited(2))\n                .timeout(timeout)\n        };\n\n        let client = builder.build()?;\n        let resp = client.get(url).send().await?;\n\n        if !resp.status().is_success() {\n            return Err(anyhow::anyhow!(\n                \"Unsuccessful request to {:?} ({})\",\n                resp.url(),\n                resp.status()\n            ));\n        }\n\n        let url = resp.url().to_owned();\n        let doc = Document::from_read(&*resp.bytes().await?)\n            .context(format!(\"Failed to read {:?} html as document.\", url))?;\n\n        let content = extractor\n            .article_content(&doc, self.language.clone())\n            .into_owned();\n\n        Ok(Article {\n            url,\n            doc,\n            content,\n            language: self.language.unwrap_or_default(),\n        })\n    }\n}\n\nstruct MetaNode<'a> {\n    inner: Node<'a>,\n}\n\nimpl<'a> Deref for MetaNode<'a> {\n    type Target = Node<'a>;\n\n    fn deref(&self) -> &Self::Target {\n        &self.inner\n    }\n}\n\nstruct DefaultDocumentCleaner {\n    url: Url,\n}\n\nimpl DocumentCleaner for DefaultDocumentCleaner {\n    fn url(&self) -> &Url {\n        &self.url\n    }\n}\n\ntrait DocumentCleaner {\n    fn clean_node_text(&self, node: Node) -> String {\n        fn recur_text<T: DocumentCleaner + ?Sized>(\n            node: Node,\n            txt: &mut String,\n            cleaner: &T,\n            mut classes: Vec<String>,\n        ) -> bool {\n            if cleaner.is_bad_node_name(node) {\n                return false;\n            }\n\n            // maintain a hierarchy of classes\n            classes.extend(extract_language_classes(node));\n\n            let mut txt_added = false;\n            if cleaner.is_good_node(node) {\n                for child in node.children() {\n                    if child.is(header()) {\n                        let header_level = child\n                            .name()\n                            .and_then(|tag| tag.strip_prefix('h'))\n                            .and_then(|level| level.parse::<usize>().ok())\n                            .unwrap_or(1);\n                        txt.push('\\n');\n                        txt.push('\\n');\n                        for _ in 0..header_level {\n                            txt.push('#');\n                        }\n                        txt.push(' ');\n                        txt.push_str(\n                            child\n                                .text()\n                                .chars()\n                                .filter(|c| c.is_ascii() && *c != '\\n')\n                                .collect::<String>()\n                                .trim(),\n                        );\n                        txt.push('\\n');\n                        txt_added |= true;\n                    } else if child.is(pre()) {\n                        // extract language tag from `pre` class and all child classes\n                        // that are `code` tagged\n                        let child_classes = extract_language_classes(child)\n                            .chain(\n                                child\n                                    .children()\n                                    .filter(|c| c.is(code()))\n                                    .flat_map(extract_language_classes),\n                            )\n                            .collect::<Vec<_>>();\n\n                        let language = EXT_MAP\n                            .keys()\n                            .chain(PROPER_CASE_MAP.keys())\n                            .find(|&k| child_classes.iter().chain(classes.iter()).any(|c| c == k));\n\n                        txt.push_str(\"\\n```\");\n                        if let Some(language) = language {\n                            txt.push_str(language);\n                        }\n                        txt.push('\\n');\n                        txt.push_str(&child.text());\n                        if !child.text().ends_with('\\n') {\n                            txt.push('\\n');\n                        }\n                        txt.push_str(\"```\\n\");\n                        txt_added |= true;\n                    } else if child.is(link()) {\n                        let link_text = child.text();\n\n                        // check if this link is an anchor link, typically used to share permalinks to headers\n                        if link_text.chars().count() == 1 {\n                            txt_added |= false\n                        } else {\n                            let link_href = child.attr(\"href\");\n                            if !link_text.trim().is_empty() {\n                                if let Some(href) = link_href {\n                                    // make href absolute if possible\n                                    let absolute_href = match Url::parse(href) {\n                                        Err(url::ParseError::RelativeUrlWithoutBase) => {\n                                            cleaner.url().join(href).ok()\n                                        }\n                                        _ => None,\n                                    };\n                                    if let Some(ah) = absolute_href {\n                                        txt.push_str(&format!(\n                                            \"[{}]({})\",\n                                            link_text.trim(),\n                                            ah.as_str()\n                                        ));\n                                    } else {\n                                        txt.push_str(&link_text);\n                                    }\n                                } else {\n                                    txt.push_str(&link_text);\n                                }\n                            }\n                            txt_added |= true;\n                        }\n                    } else if child.is(code()) {\n                        txt.push('`');\n                        txt.push_str(&child.text());\n                        txt.push('`');\n                        txt_added |= true;\n                    } else if child.is(Name(\"td\")) {\n                        txt.push_str(&child.text());\n                        txt.push(';');\n                        txt_added |= true;\n                    } else if child.is(list()) {\n                        txt.push('-');\n                        txt.push(' ');\n                        txt.push_str(&child.text());\n                        txt_added |= true;\n                    } else {\n                        let mut a = String::new();\n                        if recur_text(child, &mut a, cleaner, classes.clone()) {\n                            txt.push_str(&a);\n                            txt_added |= true;\n                        } else if !cleaner.is_bad_node_name(child) {\n                            txt.push_str(&child.text());\n                            txt_added |= true;\n                        }\n                    }\n\n                    if child.is(para()) {\n                        txt.push('\\n');\n                    }\n                }\n            }\n            txt_added\n        }\n\n        let mut txt = String::new();\n        let classes = Vec::new();\n        recur_text(node, &mut txt, self, classes);\n        txt\n    }\n\n    fn is_good_node(&self, node: Node) -> bool {\n        !has_bad_attr(node)\n    }\n\n    fn is_bad_node_name(&self, node: Node) -> bool {\n        is_bad_node(node)\n    }\n\n    fn iter_clean_nodes<'a>(&'a self, node: Node<'a>) -> CleanNodeIter<'a, Self>\n    where\n        Self: Sized,\n    {\n        CleanNodeIter {\n            cleaner: self,\n            inner: node.descendants(),\n        }\n    }\n\n    fn url(&self) -> &Url;\n}\nstruct CleanNodeIter<'a, T: DocumentCleaner> {\n    cleaner: &'a T,\n    inner: Descendants<'a>,\n}\n\nimpl<'a, T: DocumentCleaner> Iterator for CleanNodeIter<'a, T> {\n    type Item = Node<'a>;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        let node = self.inner.next()?;\n        if self.cleaner.is_bad_node_name(node) || !self.cleaner.is_good_node(node) {\n            // skip every node under this bad node\n            for ignore in node.descendants() {\n                let next = self.inner.next()?;\n                if ignore.index() != next.index() {\n                    return Some(next);\n                }\n            }\n        }\n        Some(node)\n    }\n}\n\nfn has_bad_attr(node: Node) -> bool {\n    for attr in ATTR_TO_CHECK.iter() {\n        if let Some(id) = node.attr(attr) {\n            if RE_BAD_NODES_ATTR.is_match(id) {\n                return true;\n            }\n        }\n    }\n    false\n}\n\nfn is_bad_node(node: Node) -> bool {\n    if let Some(n) = node.name() {\n        BAD_NODE_NAMES.contains(&n)\n    } else {\n        false\n    }\n}\n\nfn para() -> impl Predicate {\n    Name(\"blockquote\")\n        .or(Name(\"dl\"))\n        .or(Name(\"div\"))\n        .or(Name(\"img\"))\n        .or(Name(\"ol\"))\n        .or(Name(\"p\"))\n        .or(Name(\"pre\"))\n        .or(Name(\"table\"))\n        .or(Name(\"tr\"))\n        .or(Name(\"thead\"))\n        .or(Name(\"ul\"))\n}\n\nfn header() -> impl Predicate {\n    Name(\"h1\")\n        .or(Name(\"h2\"))\n        .or(Name(\"h3\"))\n        .or(Name(\"h4\"))\n        .or(Name(\"h5\"))\n        .or(Name(\"h6\"))\n}\n\nfn pre() -> impl Predicate {\n    Name(\"pre\")\n}\n\nfn code() -> impl Predicate {\n    Name(\"code\")\n}\n\nfn link() -> impl Predicate {\n    Name(\"a\")\n}\n\nfn list() -> impl Predicate {\n    Name(\"li\")\n}\n\nfn extract_language_classes(node: Node) -> impl Iterator<Item = String> + '_ {\n    node.attr(\"class\")\n        .map(|s| s.split(' '))\n        .into_iter()\n        .flatten()\n        .map(|s| {\n            // some heuristic prefixes & suffixes to remove\n            s.replace(\"language\", \"\")\n                .replace(\"source\", \"\")\n                .replace(\"highlight\", \"\")\n                .replace('-', \"\")\n        })\n}\n\nimpl FromStr for Language {\n    type Err = Language;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match s {\n            \"ar\" | \"arabic\" => Ok(Language::Arabic),\n            \"ru\" | \"russian\" => Ok(Language::Russian),\n            \"nl\" | \"dutch\" => Ok(Language::Dutch),\n            \"de\" | \"german\" => Ok(Language::German),\n            \"en\" | \"english\" => Ok(Language::English),\n            \"es\" | \"spanish\" => Ok(Language::Spanish),\n            \"fr\" | \"french\" => Ok(Language::French),\n            \"he\" | \"hebrew\" => Ok(Language::Hebrew),\n            \"it\" | \"italian\" => Ok(Language::Italian),\n            \"ko\" | \"korean\" => Ok(Language::Korean),\n            \"no\" | \"norwegian\" => Ok(Language::Norwegian),\n            \"fa\" | \"persian\" => Ok(Language::Persian),\n            \"pl\" | \"polish\" => Ok(Language::Polish),\n            \"pt\" | \"portuguese\" => Ok(Language::Portuguese),\n            \"sv\" | \"swedish\" => Ok(Language::Swedish),\n            \"hu\" | \"hungarian\" => Ok(Language::Hungarian),\n            \"fi\" | \"finnish\" => Ok(Language::Finnish),\n            \"da\" | \"danish\" => Ok(Language::Danish),\n            \"zh\" | \"chinese\" => Ok(Language::Chinese),\n            \"id\" | \"indonesian\" => Ok(Language::Indonesian),\n            \"vi\" | \"vietnamese\" => Ok(Language::Vietnamese),\n            \"sw\" | \"swahili\" => Ok(Language::Swahili),\n            \"tr\" | \"turkish\" => Ok(Language::Turkish),\n            \"el\" | \"greek\" => Ok(Language::Greek),\n            \"uk\" | \"ukrainian\" => Ok(Language::Ukrainian),\n            s => Err(Language::Other(s.to_string())),\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\nstruct ArticleTextNode<'a> {\n    inner: Node<'a>,\n}\n\nimpl<'a> ArticleTextNode<'a> {\n    fn new(inner: Node<'a>) -> Self {\n        Self { inner }\n    }\n\n    fn clean_text(&self, url: &Url) -> String {\n        DefaultDocumentCleaner { url: url.clone() }.clean_node_text(self.inner)\n    }\n}\n\nimpl<'a> Deref for ArticleTextNode<'a> {\n    type Target = Node<'a>;\n\n    fn deref(&self) -> &Self::Target {\n        &self.inner\n    }\n}\n\nstruct ArticleTextNodeExtractor;\n\nimpl ArticleTextNodeExtractor {\n    const MINIMUM_STOPWORD_COUNT: usize = 5;\n    const MAX_STEPSAWAY_FROM_NODE: usize = 3;\n\n    fn article_body_predicate() -> for<'r, 's> fn(&'r Node<'s>) -> bool {\n        |node| {\n            for (k, v) in ARTICLE_BODY_ATTR.iter().cloned() {\n                if Attr(k, v).matches(node) {\n                    return true;\n                }\n            }\n            false\n        }\n    }\n\n    fn calculate_best_node(doc: &Document, lang: Language) -> Option<ArticleTextNode> {\n        let mut starting_boost = 1.0;\n\n        let mut common_best_nodes = doc.find(\n            Name(\"article\")\n                .or(Name(\"main\"))\n                .or(Attr(\"id\", \"main\"))\n                .or(Attr(\"id\", \"content\"))\n                .or(Attr(\"id\", \"doc-content\"))\n                .or(Attr(\"id\", \"contents\"))\n                .or(Attr(\"class\", \"book-body\")),\n        );\n\n        // heuristics for commonly occurring nodes with main content\n        if let Some(main_tag) = common_best_nodes.next() {\n            // if exactly one, say, article node was found, use it\n            // else, the site may be misusing these tags\n            if common_best_nodes.next().is_none() {\n                return Some(ArticleTextNode::new(main_tag));\n            }\n        }\n\n        let txt_nodes: Vec<_> = ArticleTextNodeExtractor::nodes_to_check(doc)\n            .filter(|n| !ArticleTextNodeExtractor::is_high_link_density(n))\n            .filter_map(|node| {\n                if let Some(stats) = node\n                    .children()\n                    .find_map(|n| n.as_text())\n                    .and_then(|txt| lang.stopword_count(txt))\n                {\n                    if stats > 2 {\n                        return Some((node, stats));\n                    }\n                }\n                None\n            })\n            .collect();\n\n        let mut nodes_scores = HashMap::with_capacity(txt_nodes.len());\n\n        let nodes_number = txt_nodes.len();\n        let negative_scoring = 0.0;\n        let bottom_negativescore_nodes = nodes_number as f64 * 0.25;\n\n        for (i, (node, stats)) in txt_nodes.iter().enumerate() {\n            let mut boost_score = 0.0;\n\n            if ArticleTextNodeExtractor::is_boostable(node, lang.clone()) {\n                boost_score = (1.0 / starting_boost) * 50.0;\n                starting_boost += 1.0;\n            }\n\n            if nodes_number > 15 {\n                let score = (nodes_number - i) as f64;\n                if score <= bottom_negativescore_nodes {\n                    let booster = bottom_negativescore_nodes - score;\n                    boost_score = booster.powf(2.0) * -1.0;\n\n                    let negscore = boost_score.abs() + negative_scoring;\n                    if negscore > 40.0 {\n                        boost_score = 5.0;\n                    }\n                }\n            }\n\n            let upscore = stats + boost_score as usize;\n\n            if let Some(parent) = node.parent() {\n                let (score, cnt) = nodes_scores\n                    .entry(parent.index())\n                    .or_insert((0usize, 0usize));\n                *score += upscore;\n                *cnt += 1;\n\n                // also update additional parent levels\n\n                if let Some(parent_parent) = parent.parent() {\n                    let (score, cnt) = nodes_scores\n                        .entry(parent_parent.index())\n                        .or_insert((0usize, 0usize));\n                    *cnt += 1;\n                    *score += upscore / 2;\n\n                    if let Some(parent_2) = parent_parent.parent() {\n                        let (score, cnt) = nodes_scores\n                            .entry(parent_2.index())\n                            .or_insert((0usize, 0usize));\n                        *cnt += 1;\n                        *score += upscore / 3;\n                    }\n                }\n            }\n        }\n\n        let mut index = nodes_scores.keys().cloned().next();\n        let mut top_score = 0;\n        for (idx, (score, _)) in nodes_scores {\n            if score > top_score {\n                top_score = score;\n                index = Some(idx);\n            }\n        }\n\n        index.map(|i| ArticleTextNode::new(Node::new(doc, i).unwrap()))\n    }\n\n    fn nodes_to_check(doc: &Document) -> impl Iterator<Item = Node> {\n        TextNodeFind::new(doc)\n    }\n\n    fn is_boostable(node: &Node, lang: Language) -> bool {\n        let mut steps_away = 0;\n        while let Some(sibling) = node.prev().filter(|n| n.is(Name(\"p\"))) {\n            if steps_away >= ArticleTextNodeExtractor::MAX_STEPSAWAY_FROM_NODE {\n                return false;\n            }\n            if let Some(stats) = sibling\n                .children()\n                .find_map(|n| n.as_text())\n                .and_then(|txt| lang.stopword_count(txt))\n            {\n                if stats > ArticleTextNodeExtractor::MINIMUM_STOPWORD_COUNT {\n                    return true;\n                }\n            }\n            steps_away += 1;\n        }\n        false\n    }\n\n    fn is_high_link_density(node: &Node) -> bool {\n        let links = node\n            .find(Name(\"a\"))\n            .filter_map(|n| n.children().find_map(|n| n.as_text()));\n\n        if let Some(words) = node.as_text().map(|s| s.split_whitespace()) {\n            let words_number = words.count();\n            if words_number == 0 {\n                return true;\n            }\n\n            let (num_links, num_link_words) = links.fold((0usize, 0usize), |(links, sum), n| {\n                (links + 1, sum + n.split_whitespace().count())\n            });\n\n            if num_links == 0 {\n                return false;\n            }\n\n            let link_divisor = num_link_words as f64 / words_number as f64;\n            let score = link_divisor * num_links as f64;\n\n            score >= 1.0\n        } else {\n            links.count() > 0\n        }\n    }\n\n    fn words(txt: &str) -> impl Iterator<Item = &str> {\n        txt.split(|c: char| c.is_whitespace() || is_punctuation(c))\n            .filter(|s| !s.is_empty())\n    }\n}\n\nfn is_punctuation(c: char) -> bool {\n    PUNCTUATION.contains(c)\n}\n\nstruct TextNodeFind<'a> {\n    document: &'a Document,\n    next: usize,\n}\n\nimpl<'a> TextNodeFind<'a> {\n    fn is_text_node(node: &Node<'a>) -> bool {\n        Name(\"p\").or(Name(\"pre\").or(Name(\"td\"))).matches(node)\n    }\n\n    fn is_bad(node: &Node<'a>) -> bool {\n        Name(\"figure\")\n            .or(Name(\"media\"))\n            .or(Name(\"aside\"))\n            .matches(node)\n    }\n\n    fn new(document: &'a Document) -> Self {\n        Self { document, next: 0 }\n    }\n}\n\nimpl<'a> Iterator for TextNodeFind<'a> {\n    type Item = Node<'a>;\n\n    fn next(&mut self) -> Option<Node<'a>> {\n        while self.next < self.document.nodes.len() {\n            let node = self.document.nth(self.next).unwrap();\n            self.next += 1;\n            if Self::is_bad(&node) {\n                self.next += node.descendants().count();\n            }\n            if Self::is_text_node(&node) {\n                return Some(node);\n            }\n        }\n        None\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/scraper/chunk.rs",
    "content": "use tree_sitter::{Node, Parser};\n\nuse crate::text_range::TextRange;\n\npub struct Section<'s> {\n    pub data: &'s str,\n    pub ancestry: Vec<&'s str>,\n    pub header: Option<&'s str>,\n    pub section_range: TextRange,\n    pub node_range: TextRange,\n}\n\nimpl<'a> Section<'a> {\n    pub fn ancestry_str(&self) -> String {\n        self.ancestry.join(\" > \")\n    }\n\n    /// may not be idempotent\n    pub fn ancestry_from_str(s: &str) -> Vec<&str> {\n        s.split(\" > \").filter(|h| !h.is_empty()).collect()\n    }\n}\n\n// - collect non-section child-nodes for the current node\n// - these form a single chunk to be embedded\n// - repeat above on every section child-node\nconst MAX_DEPTH: usize = 1;\npub fn sectionize<'s, 'b>(\n    start_node: &'b Node,\n    sections: &'b mut Vec<Section<'s>>,\n    mut ancestry: Vec<&'s str>,\n    depth: usize,\n    src: &'s str,\n) {\n    let mut cursor = start_node.walk();\n\n    // discover section and non-section nodes among direct child nodes\n    let (section_nodes, non_section_nodes): (Vec<_>, Vec<_>) = start_node\n        .named_children(&mut cursor)\n        .partition(|child| child.kind() == \"section\");\n\n    // extract header of start_node\n    let own_header = non_section_nodes\n        .iter()\n        .find(|child| child.kind() == \"atx_heading\")\n        .map(|child| src[child.byte_range()].trim());\n\n    // do not sectionize after h4\n    if depth > MAX_DEPTH {\n        sections.push(Section {\n            data: &src[start_node.byte_range()],\n            ancestry: ancestry.clone(),\n            header: own_header,\n            section_range: start_node.range().into(),\n            node_range: start_node.range().into(),\n        });\n        return;\n    }\n\n    // collect ranges of all non-section nodes\n    let own_section_range = non_section_nodes\n        .into_iter()\n        .map(|node| node.range())\n        .reduce(cover);\n\n    if let Some(r) = own_section_range {\n        sections.push(Section {\n            data: &src[r.start_byte..r.end_byte],\n            ancestry: ancestry.clone(),\n            section_range: r.into(),\n            node_range: start_node.range().into(),\n            header: own_header,\n        });\n    }\n\n    // add current header to ancestry and recurse\n    if let Some(h) = own_header {\n        ancestry.push(h.trim());\n    }\n\n    for sub_section in section_nodes {\n        sectionize(&sub_section, sections, ancestry.clone(), depth + 1, src);\n    }\n}\n\nfn cover(a: tree_sitter::Range, b: tree_sitter::Range) -> tree_sitter::Range {\n    let start_byte = a.start_byte.min(b.start_byte);\n    let end_byte = a.end_byte.max(b.end_byte);\n    let start_point = a.start_point.min(b.start_point);\n    let end_point = a.end_point.max(b.end_point);\n\n    tree_sitter::Range {\n        start_byte,\n        end_byte,\n        start_point,\n        end_point,\n    }\n}\n\npub fn by_section(src: &str) -> Vec<Section<'_>> {\n    let mut parser = Parser::new();\n    parser.set_language(tree_sitter_md::language()).unwrap();\n\n    let tree = parser.parse(src.as_bytes(), None).unwrap();\n    let root_node = tree.root_node();\n\n    let mut sections = Vec::new();\n    sectionize(&root_node, &mut sections, vec![], 0, src);\n\n    sections\n}\n"
  },
  {
    "path": "server/bleep/src/scraper.rs",
    "content": "use anyhow::Result;\nuse async_stream::stream;\nuse futures::stream::Stream;\nuse select::predicate::Name;\nuse tokio::{sync::RwLock, task};\nuse tracing::{trace, warn};\nuse url::Url;\n\nuse std::{\n    collections::{HashMap, HashSet, VecDeque},\n    path::PathBuf,\n    sync::Arc,\n};\n\nmod article;\npub mod chunk;\n\nuse article::Article;\n\npub struct Scraper {\n    pub queued_requests: Arc<RwLock<VecDeque<ScraperRequest>>>,\n    pub handles: Vec<task::JoinHandle<Result<ScraperResult>>>,\n    visited_links: HashSet<String>,\n    config: Config,\n}\n\nimpl Scraper {\n    pub fn with_config(config: Config) -> Self {\n        Self {\n            queued_requests: Arc::new(RwLock::new(VecDeque::new())),\n            handles: Vec::new(),\n            visited_links: HashSet::new(),\n            config,\n        }\n    }\n\n    fn base_url(&self) -> &Url {\n        &self.config.base_url\n    }\n\n    async fn queue_request(&self, req: ScraperRequest) {\n        self.queued_requests.write().await.push_back(req);\n    }\n\n    async fn queue_requests(&self, reqs: impl Iterator<Item = ScraperRequest>) {\n        self.queued_requests.write().await.extend(reqs);\n    }\n\n    fn active_tasks(&self) -> usize {\n        self.handles.iter().filter(|h| !h.is_finished()).count()\n    }\n\n    // decides which urls to actually scrape\n    //\n    // every url that contains base_url exactly is eligible for scraping\n    fn is_permitted(&self, url: &Url) -> bool {\n        url.as_str()\n            .strip_prefix(self.base_url().as_str())\n            .is_some()\n    }\n\n    fn finished_tasks(&mut self) -> Vec<task::JoinHandle<Result<ScraperResult>>> {\n        let (finished, unfinished) = self.handles.drain(..).partition(|h| h.is_finished());\n        self.handles = unfinished;\n        finished\n    }\n\n    pub fn complete(&mut self) -> impl Stream<Item = Document> + '_ {\n        stream! {\n            self.queue_request(ScraperRequest {\n                url: self.base_url().clone(),\n                depth: 1,\n            })\n            .await;\n\n            loop {\n                // extract results from finished tasks\n                for h in self.finished_tasks().into_iter() {\n                    trace!(\"task finished\");\n                    match h.await {\n                        Ok(Ok(mut scraper_result)) => {\n                            // insert doc into the stream\n                            yield scraper_result.doc;\n\n                            // there could be dupes among the new urls, collect them into a set first\n                            let new_urls = scraper_result\n                                .new_urls\n                                .drain(..)\n                                .fold(HashMap::new(), |mut map, (depth, url)| {\n                                    map.entry(url)\n                                        .and_modify(|d| {\n                                            *d = depth.min(*d);\n                                        })\n                                    .or_insert(depth);\n                                    map\n                                })\n                                .into_iter()\n                                    .filter(|(url, depth)| {\n                                        *depth <= self.config.max_depth\n                                            && !self.visited_links.contains(&url.to_string())\n                                            && self.is_permitted(url)\n                                    })\n                                .map(|(url, depth)| ScraperRequest {\n                                    url,\n                                    depth,\n                                })\n                                .collect::<Vec<_>>();\n\n                            trace!(\"{} new urls collected\", new_urls.len());\n\n                            self.queue_requests(new_urls.into_iter()).await;\n                        }\n                        Ok(Err(e)) => warn!(\"task failed with: {e}\"),\n                        Err(e) => warn!(\"task aborted with: {e}\"),\n                    }\n                }\n\n                // add new tasks to queue if possible\n                let active_tasks = self.active_tasks();\n                if active_tasks <= self.config.max_concurrency {\n                    let new_task_count = (self.config.max_concurrency - active_tasks)\n                        .min(self.queued_requests.read().await.len());\n                    let new_requests = self\n                        .queued_requests\n                        .write()\n                        .await\n                        .drain(..new_task_count)\n                        .collect::<Vec<_>>(); // we collect here to drop the lock over the request queue\n\n                    for request in new_requests.into_iter() {\n                        if !self.visited_links.contains(&request.url.to_string()) {\n                            trace!(\"{} queued\", request.url.as_str());\n                            self.visited_links.insert(request.url.to_string());\n                            let handle = task::spawn(async { visit(request).await });\n                            self.handles.push(handle);\n                        }\n                    }\n                }\n\n                if self.queued_requests.read().await.is_empty() && self.handles.is_empty() {\n                    trace!(\"no more tasks\");\n                    break;\n                }\n            }\n        }\n    }\n}\n\npub struct ScraperRequest {\n    url: Url,\n    depth: usize,\n}\n\npub struct ScraperResult {\n    pub doc: Document,\n    pub new_urls: Vec<(usize, Url)>,\n}\n\npub struct Config {\n    max_depth: usize,\n    pub base_url: Url,\n    _delay: std::time::Duration,\n    max_concurrency: usize,\n}\n\nimpl Config {\n    pub fn new(base_url: Url) -> Self {\n        Self {\n            max_depth: 5,\n            base_url,\n            _delay: std::time::Duration::from_millis(0),\n            max_concurrency: std::thread::available_parallelism()\n                .map(|t| t.get())\n                .unwrap_or(4),\n        }\n    }\n}\n\npub struct Document {\n    pub url: Url,\n    pub path: PathBuf,\n    pub content: Option<String>,\n    pub meta: Meta,\n}\n\nimpl Document {\n    pub fn relative_url(&self, base: &Url) -> Option<String> {\n        let mut other = self.url.clone();\n\n        // scheme difference does not matter to us\n        other.set_scheme(base.scheme()).ok()?;\n        other.set_host(base.host_str()).ok()?;\n\n        base.make_relative(&other)\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.content.is_none()\n    }\n}\n\n#[derive(Default, Clone, serde::Serialize)]\npub struct Meta {\n    pub title: Option<String>,\n    pub description: Option<String>,\n    pub icon: Option<String>,\n}\n\nimpl Meta {\n    pub fn is_empty(&self) -> bool {\n        self.title.is_none() && self.description.is_none() && self.icon.is_none()\n    }\n}\n\nasync fn visit(ScraperRequest { url, depth }: ScraperRequest) -> Result<ScraperResult> {\n    trace!(\"visited - {}\", url);\n\n    // calculate the location on disk to store this url\n    let mut doc_path = PathBuf::from(url.path().strip_prefix('/').unwrap()); // infallible\n\n    if !doc_path.ends_with(\"index.html\") {\n        doc_path.push(\"index.html\");\n    }\n\n    // TODO stagger this request by config.delay\n    // tokio::time::sleep(self.config.delay).await;\n\n    // fetch and parse article\n    let article = Article::builder(url.clone())?.get().await?;\n    let url = article.url; // in the case of a redirect - we store the updated url\n\n    // scrape all relative links from this doc and add onto stack\n    let html = article.doc;\n\n    // these new urls must be relative to the current page url\n    let new_urls = html\n        .find(Name(\"a\"))\n        .filter_map(|l| l.attr(\"href\"))\n        .filter(|v| {\n            match Url::parse(v) {\n                Err(url::ParseError::RelativeUrlWithoutBase) => true, // we want only relative links\n                _ => false,\n            }\n        })\n        .filter_map(|new_path| url.join(new_path).ok())\n        .filter(|u| u.fragment().is_none()) // ignore urls with fragments\n        .filter(|u| u.query().is_none()) // ignore urls with query params\n        .map(|u| (depth + 1, u))\n        .collect();\n    // self.link_stack.extend(new_links);\n\n    // build document\n    let content = article.content.text.map(|c| c.to_string());\n\n    let meta = Meta {\n        title: article.content.title.map(|c| c.to_string()),\n        description: article.content.description.map(|c| c.to_string()),\n        icon: article.content.icon.map(|c| c.to_string()),\n    };\n\n    let doc = Document {\n        url,\n        path: doc_path,\n        content,\n        meta,\n    };\n\n    Ok(ScraperResult { doc, new_urls })\n}\n"
  },
  {
    "path": "server/bleep/src/semantic/chunk.rs",
    "content": "use std::{\n    fmt::{Display, Write},\n    ops::Range,\n};\n\nuse crate::text_range::{Point, TextRange};\n\nuse clap::{builder::PossibleValue, ValueEnum};\nuse serde::{Deserialize, Serialize};\nuse tokenizers::Tokenizer;\nuse tracing::{error, trace, warn};\n\n#[derive(Debug)]\npub enum ChunkError {\n    UnsupportedLanguage(String),\n    Query(tree_sitter::QueryError),\n}\n\n/// A Chunk type, containing the plain text (borrowed from the source)\n/// and a `TextRange` with byte, line and column positions\n#[derive(Debug)]\npub struct Chunk<'a> {\n    pub data: &'a str,\n    pub range: TextRange,\n}\n\nimpl<'a> Chunk<'a> {\n    pub fn new(data: &'a str, start: Point, end: Point) -> Self {\n        Self {\n            data,\n            range: TextRange { start, end },\n        }\n    }\n\n    pub fn len(&self) -> usize {\n        self.data.len()\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.data.len() < 1\n    }\n}\n\n/// This calculates the line and column for a given byte position. The last_line and last_byte\n/// parameters can be used to reduce the amount of searching for the line position from quadratic\n/// to linear. If in doubt, just use `0` for last_line and `0` for last_byte.\n///\n/// # Examples\n///\n/// ```no_run\n/// assert_eq!(\n///     bleep::semantic::chunk::point(\"fn hello() {\\n    \\\"world\\\"\\n}\\n\", 16, 0, 0),\n///     bleep::text_range::Point::new(16, 1, 4)\n/// );\n/// ```\npub fn point(src: &str, byte: usize, last_line: usize, last_byte: usize) -> Point {\n    assert!(\n        byte >= last_byte,\n        \"byte={byte} < last_byte={last_byte}, last_line={last_line}\"\n    );\n    let line = src.as_bytes()[last_byte..byte]\n        .iter()\n        .filter(|&&b| b == b'\\n')\n        .count()\n        + last_line;\n    let column = if let Some(last_nl) = src[..byte].rfind('\\n') {\n        byte - last_nl\n    } else {\n        byte\n    };\n    Point { byte, column, line }\n}\n\n/// The strategy for overlapping chunks\n#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq)]\n#[serde(try_from = \"&str\", into = \"String\")]\npub enum OverlapStrategy {\n    /// go back _ lines from the end\n    ByLines(usize),\n    /// A value > 0 and < 1 that indicates the target overlap in tokens.\n    Partial(f64),\n}\n\nimpl Display for OverlapStrategy {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::ByLines(n) => n.fmt(f),\n            Self::Partial(p) => {\n                (*p / 100.0).fmt(f)?;\n                f.write_char('%')\n            }\n        }\n    }\n}\n\nimpl From<OverlapStrategy> for String {\n    fn from(val: OverlapStrategy) -> Self {\n        val.to_string()\n    }\n}\n\nstatic OVERLAP_STRATEGY_VARIANTS: &[OverlapStrategy] =\n    &[OverlapStrategy::ByLines(1), OverlapStrategy::Partial(0.5)];\n\nimpl ValueEnum for OverlapStrategy {\n    fn value_variants<'a>() -> &'a [Self] {\n        OVERLAP_STRATEGY_VARIANTS\n    }\n\n    fn to_possible_value(&self) -> Option<PossibleValue> {\n        if self == &OVERLAP_STRATEGY_VARIANTS[0] {\n            Some(PossibleValue::new(\"1\"))\n        } else if self == &OVERLAP_STRATEGY_VARIANTS[1] {\n            Some(PossibleValue::new(\"50%\"))\n        } else {\n            None\n        }\n    }\n\n    fn from_str(input: &str, _ignore_case: bool) -> Result<Self, String> {\n        Self::try_from(input)\n            .map_err(|_| String::from(\"overlap should be a number of lines or a percentage\"))\n    }\n}\n\nimpl TryFrom<&'_ str> for OverlapStrategy {\n    type Error = &'static str;\n\n    fn try_from(input: &str) -> Result<Self, &'static str> {\n        Ok(if let Some(percentage) = input.strip_suffix('%') {\n            Self::Partial(\n                str::parse::<f64>(percentage).map_err(|_| \"failure parsing overlap strategy\")?\n                    * 0.01,\n            )\n        } else {\n            Self::ByLines(str::parse(input).map_err(|_| \"failure parsing overlap strategy\")?)\n        })\n    }\n}\n\nimpl OverlapStrategy {\n    // returns the next startpoint for overlong lines\n    fn next_subdivision(&self, max_tokens: usize) -> usize {\n        (match self {\n            OverlapStrategy::ByLines(n) => max_tokens - n,\n            OverlapStrategy::Partial(part) => ((max_tokens as f64) * part) as usize,\n        })\n        .max(1) // ensure we make forward progress\n    }\n}\n\nimpl Default for OverlapStrategy {\n    fn default() -> Self {\n        Self::Partial(0.5)\n    }\n}\n\n/// Heuristics for determining if a chunk is noisy\n///\n/// We filter chunks where over 50% of non-whitespace tokens are numeric or punctuation\nfn is_noisy(chunk: &str) -> bool {\n    let non_whitespace_count = chunk.chars().filter(|c| !c.is_whitespace()).count();\n    let numeric_or_punctuation_count = chunk\n        .chars()\n        .filter(|c| c.is_numeric() || c.is_ascii_punctuation())\n        .count();\n\n    // Chunks that are all whitespace are noisy\n    if non_whitespace_count == 0 {\n        return true;\n    }\n\n    (numeric_or_punctuation_count as f64 / non_whitespace_count as f64) > 0.5\n}\n\n/// This should take care of [CLS], [SEP] etc. which could be introduced during per-chunk tokenization\npub const DEDUCT_SPECIAL_TOKENS: usize = 2;\n\nfn add_token_range<'s>(\n    chunks: &mut Vec<Chunk<'s>>,\n    src: &'s str,\n    offsets: &[(usize, usize)],\n    o: Range<usize>,\n    last_line: &mut usize,\n    last_byte: &mut usize,\n) {\n    let start_byte = offsets[o.start].0;\n    let end_byte = offsets.get(o.end).map_or(src.len(), |&(s, _)| s);\n\n    if end_byte <= start_byte {\n        return;\n    }\n\n    if is_noisy(&src[start_byte..end_byte]) {\n        trace!(\"skipping noisy chunk\");\n        return;\n    }\n\n    debug_assert!(\n        o.end - o.start < 256,\n        \"chunk too large: {} tokens in {:?} bytes {:?}\",\n        o.end - o.start,\n        o,\n        start_byte..end_byte\n    );\n\n    let start = point(src, start_byte, *last_line, *last_byte);\n    let end = point(src, end_byte, *last_line, *last_byte);\n    (*last_line, *last_byte) = (start.line, start.byte);\n    chunks.push(Chunk::new(&src[start_byte..end_byte], start, end));\n}\n\n/// This tries to split the code by lines and add as much tokens as possible until reaching\n/// `max_tokens`. Then it'll reduce to the last newline.\npub fn by_tokens<'s>(\n    repo: &str,\n    file: &str,\n    src: &'s str,\n    tokenizer: &Tokenizer, // we count from line\n    token_bounds: Range<usize>,\n    strategy: OverlapStrategy,\n) -> Vec<Chunk<'s>> {\n    if tokenizer.get_padding().is_some() || tokenizer.get_truncation().is_some() {\n        error!(\n            \"This code can panic if padding and truncation are not turned off. Please make sure padding is off. p = {}, t = {}\",\n            tokenizer.get_padding().is_some(),\n            tokenizer.get_truncation().is_some(),\n        );\n    }\n    let min_tokens = token_bounds.start;\n    // no need to even tokenize files too small to contain our min number of tokens\n    if src.len() < min_tokens {\n        return Vec::new();\n    }\n    let Ok(encoding) = tokenizer.encode(src, true) else {\n        warn!(\"Could not encode \\\"{}\\\"\", src);\n        return by_lines(src, 15);\n    };\n\n    let offsets = encoding.get_offsets();\n    // again, if we have less than our minimum number of tokens, we may skip the file\n    if offsets.len() < min_tokens {\n        return Vec::new();\n    }\n\n    let repo_plus_file = repo.to_owned() + \"\\t\" + file + \"\\n\";\n    let repo_tokens = match tokenizer.encode(repo_plus_file, true) {\n        Ok(encoding) => encoding.get_ids().len(),\n        Err(e) => {\n            error!(\"failure during encoding repo + file {:?}\", e);\n            return Vec::new();\n        }\n    };\n\n    if token_bounds.end <= DEDUCT_SPECIAL_TOKENS + repo_tokens {\n        error!(\"too few tokens\");\n        return Vec::new();\n    }\n\n    let max_tokens = token_bounds.end - DEDUCT_SPECIAL_TOKENS - repo_tokens;\n    let max_newline_tokens = max_tokens * 3 / 4; //TODO: make this configurable\n    let max_boundary_tokens = max_tokens * 7 / 8; //TODO: make this configurable\n    trace!(\"max tokens reduced to {max_tokens}\");\n\n    let offsets_len = offsets.len() - 1;\n    // remove the SEP token which has (0, 0) offsets for some reason\n    let offsets = if offsets[offsets_len].0 == 0 {\n        &offsets[..offsets_len]\n    } else {\n        offsets\n    };\n    let ids = encoding.get_ids();\n    let mut chunks = Vec::new();\n    let mut start = 0;\n    let (mut last_line, mut last_byte) = (0, 0);\n    loop {\n        let next_limit = start + max_tokens;\n        let end_limit = if next_limit >= offsets_len {\n            offsets_len\n        } else if let Some(next_newline) = (start + max_newline_tokens..next_limit)\n            .rfind(|&i| src[offsets[i].0..offsets[i + 1].0].contains('\\n'))\n        {\n            next_newline\n        } else if let Some(next_boundary) = (start + max_boundary_tokens..next_limit).rfind(|&i| {\n            !tokenizer\n                .id_to_token(ids[i + 1])\n                .map_or(false, |s| s.starts_with(\"##\"))\n        }) {\n            next_boundary\n        } else {\n            next_limit\n        };\n        if end_limit - start >= min_tokens {\n            add_token_range(\n                &mut chunks,\n                src,\n                offsets,\n                start..end_limit + 1,\n                &mut last_line,\n                &mut last_byte,\n            );\n        }\n        if end_limit == offsets_len {\n            return chunks;\n        }\n        let diff = strategy.next_subdivision(end_limit - start);\n        let mid = start + diff;\n        // find nearest newlines or boundaries, set start accordingly\n        let next_newline_diff =\n            (mid..end_limit).find(|&i| src[offsets[i].0..offsets[i + 1].0].contains('\\n'));\n        let prev_newline_diff = (start + (diff / 2)..mid)\n            .rfind(|&i| src[offsets[i].0..offsets[i + 1].0].contains('\\n'))\n            .map(|t| t + 1);\n        start = match (next_newline_diff, prev_newline_diff) {\n            (Some(n), None) | (None, Some(n)) => n,\n            (Some(n), Some(p)) => {\n                if n - mid < mid - p {\n                    n\n                } else {\n                    p\n                }\n            }\n            (None, None) => (mid..end_limit)\n                .find(|&i| {\n                    !tokenizer\n                        .id_to_token(ids[i + 1])\n                        .map_or(false, |s| s.starts_with(\"##\"))\n                })\n                .unwrap_or(mid),\n        };\n    }\n}\n\npub fn by_lines(src: &str, size: usize) -> Vec<Chunk<'_>> {\n    let ends = std::iter::once(0)\n        .chain(src.match_indices('\\n').map(|(i, _)| i))\n        .enumerate()\n        .collect::<Vec<_>>();\n\n    let s = ends.iter().copied();\n    let last = src.len().saturating_sub(1);\n    let last_line = *ends.last().map(|(idx, _)| idx).unwrap_or(&0);\n\n    ends.iter()\n        .copied()\n        .step_by(size)\n        .zip(s.step_by(size).skip(1).chain([(last_line, last)]))\n        .filter(|((_, start_byte), (_, end_byte))| start_byte < end_byte)\n        .map(|((start_line, start_byte), (end_line, end_byte))| Chunk {\n            data: &src[start_byte..end_byte],\n            range: TextRange {\n                start: Point {\n                    byte: start_byte,\n                    line: start_line,\n                    column: 0,\n                },\n                end: Point {\n                    byte: end_byte,\n                    line: end_line,\n                    column: 0,\n                },\n            },\n        })\n        .collect()\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use std::{env, path::PathBuf};\n\n    fn minilm() -> Tokenizer {\n        let tok_json = PathBuf::from(env!(\"CARGO_MANIFEST_DIR\"))\n            .parent()\n            .unwrap()\n            .parent()\n            .unwrap()\n            .join(\"model\")\n            .join(\"tokenizer.json\");\n        println!(\"{tok_json:?}\");\n        tokenizers::Tokenizer::from_file(tok_json).unwrap()\n    }\n\n    #[test]\n    pub fn empty() {\n        let tokenizer = minilm();\n        let token_bounds = 50..256;\n        let no_tokens = super::by_tokens(\n            \"bloop\",\n            \"rmpty.rs\",\n            \"\",\n            &tokenizer,\n            token_bounds,\n            OverlapStrategy::ByLines(1),\n        );\n        assert!(no_tokens.is_empty());\n    }\n\n    #[test]\n    pub fn by_tokens() {\n        let tokenizer = minilm();\n        let token_bounds = 50..256;\n        let cur_dir = env::current_dir().unwrap();\n        let base_dir = cur_dir.ancestors().nth(2).unwrap();\n        let walk = ignore::WalkBuilder::new(base_dir)\n            .standard_filters(true)\n            .filter_entry(|de| {\n                let Some(ft) = de.file_type() else {\n                    return false;\n                };\n\n                // pretty crude, but do ignore generated files\n                if ft.is_dir() && de.file_name() == \"target\" {\n                    return false;\n                }\n\n                let is_file = ft.is_file() || ft.is_symlink();\n\n                !is_file || de.path().extension().map(|e| e == \"rs\").unwrap_or_default()\n            })\n            .build();\n        let mut num_chunks = 0;\n        let mut combined_size = 0;\n        for file in walk {\n            let file = file.unwrap();\n            if file.metadata().unwrap().is_dir() {\n                continue;\n            }\n            let Ok(src) = std::fs::read_to_string(file.path()) else {\n                continue;\n            };\n            let chunks = super::by_tokens(\n                \"bloop\",\n                &file.path().to_string_lossy(),\n                &src,\n                &tokenizer,\n                token_bounds.clone(),\n                OverlapStrategy::Partial(0.5),\n            );\n            num_chunks += chunks.len();\n            combined_size += chunks.iter().map(Chunk::len).sum::<usize>();\n        }\n        let avg_size = combined_size / num_chunks;\n        // we use string length as a stand in for token length, seeing as tokens will\n        // on average be two chars long, and our distribution should be skewed towards\n        // longer chunks.\n        let min_avg_size = 512;\n        assert!(\n            avg_size > min_avg_size,\n            \"Average chunk size should be more than {min_avg_size}, was {avg_size}\",\n        );\n    }\n\n    #[test]\n    pub fn chunks_within_token_limit() {\n        let tokenizer = minilm();\n\n        let chunks = super::by_tokens(\n            \"bloop\",\n            \"src/config.rs\",\n            SRC,\n            &tokenizer,\n            50..256,\n            OverlapStrategy::Partial(0.5),\n        );\n\n        for chunk in chunks {\n            let len = tokenizer.encode(chunk.data, false).unwrap().len();\n            assert!(\n                len.saturating_sub(256) < 10,\n                \"chunk length ({len}) was not less than 256\\n\\n{}\\n\",\n                chunk.data\n            )\n        }\n    }\n\n    #[test]\n    pub fn chunks_over_long_lined_file() {\n        let tokenizer = minilm();\n\n        // squish SRC into one big single-lined string\n        let src = SRC.lines().collect::<String>();\n\n        let chunks = super::by_tokens(\n            \"bloop\",\n            \"src/config.rs\",\n            &src,\n            &tokenizer,\n            50..256,\n            OverlapStrategy::Partial(0.5),\n        );\n\n        for chunk in chunks {\n            let len = tokenizer.encode(chunk.data, false).unwrap().len();\n            assert!(\n                len.saturating_sub(256) < 10,\n                \"chunk length ({len}) was not less than 256\\n\\n{}\\n\",\n                chunk.data\n            )\n        }\n    }\n\n    static SRC: &str = r#\"\nuse crate::{semantic::chunk::OverlapStrategy, state::StateSource};\nuse anyhow::{Context, Result};\nuse clap::Parser;\n\nuse secrecy::{ExposeSecret, SecretString};\nuse serde::{Deserialize, Serialize, Serializer};\nuse std::path::{Path, PathBuf};\n\n#[derive(Serialize, Deserialize, Parser, Debug)]\n#[clap(author, version, about, long_about = None)]\npub struct Configuration {\n    //\n    // Core configuration options\n    //\n    #[clap(short, long)]\n    #[serde(skip)]\n    /// If a config file is given, it will override _all_ command line parameters!\n    pub config_file: Option<PathBuf>,\n\n    #[clap(flatten)]\n    #[serde(default)]\n    pub source: StateSource,\n\n    #[clap(short, long, default_value_os_t = default_data_dir())]\n    #[serde(default = \"default_data_dir\")]\n    /// Directory to store indexes\n    pub index_dir: PathBuf,\n\n    /// Directory to store persistent data\n    #[clap(long, default_value_os_t = default_data_dir())]\n    #[serde(default = \"default_data_dir\")]\n    pub data_dir: PathBuf,\n\n    #[clap(long, default_value_t = false)]\n    #[serde(skip)]\n    /// Quit after indexing the specified repos\n    pub index_only: bool,\n\n    #[clap(long, default_value_t = false)]\n    #[serde(default)]\n    /// Disable periodic reindexing, and `git pull` on remote repositories.\n    pub disable_background: bool,\n\n    #[clap(long, default_value_t = false)]\n    #[serde(default)]\n    /// Disable system-native notification backends to detect new git commits immediately.\n    pub disable_fsevents: bool,\n\n    #[clap(short, long, default_value_t = default_buffer_size())]\n    #[serde(default = \"default_buffer_size\")]\n    /// Size of memory to use for file indexes\n    pub buffer_size: usize,\n\n    #[clap(short, long, default_value_t = default_repo_buffer_size())]\n    #[serde(default = \"default_repo_buffer_size\")]\n    /// Size of memory to use for repo indexes\n    pub repo_buffer_size: usize,\n\n    #[clap(short, long, default_value_t = default_parallelism())]\n    #[serde(default = \"default_parallelism\")]\n    /// Maximum number of parallel background threads\n    pub max_threads: usize,\n\n    #[clap(long, default_value_t = default_host())]\n    #[serde(default = \"default_host\")]\n    /// Bind the webserver to `<port>`\n    pub host: String,\n\n    #[clap(long, default_value_t = default_port())]\n    #[serde(default = \"default_port\")]\n    /// Bind the webserver to `<host>`\n    pub port: u16,\n\n    //\n    // External dependencies\n    //\n    #[clap(long, default_value_t = default_answer_api_url())]\n    #[serde(default = \"default_answer_api_url\")]\n    /// URL for the answer-api\n    pub answer_api_url: String,\n\n    #[clap(long)]\n    /// Key for analytics backend\n    pub analytics_key: Option<String>,\n\n    #[clap(long)]\n    /// Key for analytics backend for frontend\n    pub analytics_key_fe: Option<String>,\n\n    #[clap(long)]\n    /// Analytics data plane identifier\n    pub analytics_data_plane: Option<String>,\n\n    #[clap(long)]\n    /// Sentry Data Source Name\n    pub sentry_dsn: Option<String>,\n\n    #[clap(long)]\n    /// Sentry Data Source Name for frontend\n    pub sentry_dsn_fe: Option<String>,\n\n    //\n    // Semantic values\n    //\n    #[clap(long)]\n    /// URL for the qdrant server\n    pub qdrant_url: Option<String>,\n\n    #[clap(long, default_value_os_t = default_model_dir())]\n    #[serde(default = \"default_model_dir\")]\n    /// Path to the embedding model directory\n    pub model_dir: PathBuf,\n\n    #[clap(long, default_value_t = default_max_chunk_tokens())]\n    #[serde(default = \"default_max_chunk_tokens\")]\n    /// Maximum number of tokens in a chunk (should be the model's input size)\n    pub max_chunk_tokens: usize,\n\n    #[clap(long)]\n    /// Chunking strategy\n    pub overlap: Option<OverlapStrategy>,\n\n    //\n    // Installation-specific values\n    //\n    #[clap(long)]\n    #[serde(serialize_with = \"serialize_secret_opt_str\", default)]\n    /// Github Client ID for either OAuth or GitHub Apps\n    pub github_client_id: Option<SecretString>,\n\n    // Github client secret\n    #[clap(long)]\n    #[serde(serialize_with = \"serialize_secret_opt_str\", default)]\n    pub github_client_secret: Option<SecretString>,\n\n    #[clap(long)]\n    /// GitHub App ID\n    pub github_app_id: Option<u64>,\n\n    #[clap(long)]\n    /// GitHub App installation ID\n    pub github_app_install_id: Option<u64>,\n\n    #[clap(long)]\n    /// Path to a GitHub private key file, for signing access token requests\n    pub github_app_private_key: Option<PathBuf>,\n\n    #[clap(long)]\n    #[serde(serialize_with = \"serialize_secret_opt_str\", default)]\n    /// Bot secret token\n    pub bot_secret: Option<SecretString>,\n\n    //\n    // Cloud deployment values\n    //\n    #[clap(long)]\n    /// Full instance domain, e.g. `foo.bloop.ai`\n    pub instance_domain: Option<String>,\n\n    /// Path to built front-end folder\n    #[clap(long)]\n    pub frontend_dist: Option<PathBuf>,\n}\n\nmacro_rules! right_if_default {\n    ($left:expr, $right:expr, $default:expr) => {\n        if $left == $default {\n            $right\n        } else {\n            $left\n        }\n    };\n}\n\nimpl Configuration {\n    pub fn read(file: impl AsRef<Path>) -> Result<Self> {\n        let file = std::fs::File::open(file)?;\n        Ok(serde_json::from_reader::<_, Self>(file)?)\n    }\n\n    pub fn from_cli() -> Result<Self> {\n        Ok(Self::try_parse()?)\n    }\n\n    pub fn index_path(&self, name: impl AsRef<Path>) -> impl AsRef<Path> {\n        self.index_dir.join(name)\n    }\n\n    pub fn github_client_id_and_secret(&self) -> Option<(&str, &str)> {\n        let id = self.github_client_id.as_ref()?.expose_secret();\n        let secret = self.github_client_secret.as_ref()?.expose_secret();\n        Some((id, secret))\n    }\n\n    pub fn cli_overriding_config_file() -> Result<Self> {\n        let cli = Self::from_cli()?;\n        let Ok(file) = cli\n\t    .config_file\n\t    .as_ref()\n\t    .context(\"no config file specified\")\n\t    .and_then(Self::read) else\n\t{\n\t    return Ok(cli);\n\t};\n\n        Ok(Self::merge(file, cli))\n    }\n\n    /// Merge 2 configurations with values from `b` taking precedence\n    ///\n    /// In case a default value is recognized in *either* sides,\n    /// always the non-default value will be used for the resulting\n    /// configuration.\n    pub fn merge(a: Self, b: Self) -> Self {\n        // the values here are in the order they're listed in the\n        // original `Configuration` declaration\n        Self {\n            config_file: b.config_file.or(a.config_file),\n\n            source: right_if_default!(b.source, a.source, Default::default()),\n\n            index_dir: right_if_default!(b.index_dir, a.index_dir, default_data_dir()),\n\n            data_dir: b.data_dir,\n\n            index_only: b.index_only | a.index_only,\n\n            disable_background: b.disable_background | a.disable_background,\n\n            disable_fsevents: b.disable_fsevents | a.disable_fsevents,\n\n            buffer_size: right_if_default!(b.buffer_size, a.buffer_size, default_buffer_size()),\n\n            repo_buffer_size: right_if_default!(\n                b.repo_buffer_size,\n                a.repo_buffer_size,\n                default_repo_buffer_size()\n            ),\n\n            max_threads: right_if_default!(b.max_threads, a.max_threads, default_parallelism()),\n\n            host: right_if_default!(b.host, a.host, default_host()),\n\n            port: right_if_default!(b.port, a.port, default_port()),\n\n            model_dir: right_if_default!(b.model_dir, a.model_dir, default_model_dir()),\n\n            max_chunk_tokens: right_if_default!(\n                b.max_chunk_tokens,\n                a.max_chunk_tokens,\n                default_max_chunk_tokens()\n            ),\n\n            overlap: b.overlap.or(a.overlap),\n\n            frontend_dist: b.frontend_dist.or(a.frontend_dist),\n\n            qdrant_url: b.qdrant_url.or(a.qdrant_url),\n\n            answer_api_url: right_if_default!(\n                b.answer_api_url,\n                a.answer_api_url,\n                default_answer_api_url()\n            ),\n\n            github_client_id: b.github_client_id.or(a.github_client_id),\n\n            github_client_secret: b.github_client_secret.or(a.github_client_secret),\n\n            github_app_id: b.github_app_id.or(a.github_app_id),\n\n            github_app_install_id: b.github_app_install_id.or(a.github_app_install_id),\n\n            github_app_private_key: b.github_app_private_key.or(a.github_app_private_key),\n\n            instance_domain: b.instance_domain.or(a.instance_domain),\n\n            bot_secret: b.bot_secret.or(a.bot_secret),\n\n            analytics_key: b.analytics_key.or(a.analytics_key),\n            analytics_key_fe: b.analytics_key_fe.or(a.analytics_key_fe),\n\n            analytics_data_plane: b.analytics_data_plane.or(a.analytics_data_plane),\n\n            sentry_dsn: b.sentry_dsn.or(a.sentry_dsn),\n\n            sentry_dsn_fe: b.sentry_dsn_fe.or(a.sentry_dsn_fe),\n        }\n    }\n}\n\npub fn serialize_secret_opt_str<S>(\n    opt_secstr: &Option<SecretString>,\n    ser: S,\n) -> Result<S::Ok, S::Error>\nwhere\n    S: Serializer,\n{\n    match opt_secstr {\n        Some(secstr) => ser.serialize_some(secstr.expose_secret()),\n        None => ser.serialize_none(),\n    }\n}\n\npub fn serialize_secret_str<S>(secstr: &SecretString, ser: S) -> Result<S::Ok, S::Error>\nwhere\n    S: Serializer,\n{\n    ser.serialize_str(secstr.expose_secret())\n}\n\n//\n// Configuration defaults\n//\nfn default_data_dir() -> PathBuf {\n    match directories::ProjectDirs::from(\"ai\", \"bloop\", \"bleep\") {\n        Some(dirs) => dirs.data_dir().to_owned(),\n        None => \"bloop_index\".into(),\n    }\n}\n\nfn default_model_dir() -> PathBuf {\n    \"model\".into()\n}\n\npub fn default_parallelism() -> usize {\n    std::thread::available_parallelism().unwrap().get()\n}\n\npub const fn minimum_parallelism() -> usize {\n    1\n}\n\nconst fn default_buffer_size() -> usize {\n    100_000_000\n}\n\nconst fn default_repo_buffer_size() -> usize {\n    30_000_000\n}\n\nconst fn default_port() -> u16 {\n    7878\n}\n\nfn default_host() -> String {\n    String::from(\"127.0.0.1\")\n}\n\nfn default_answer_api_url() -> String {\n    String::from(\"http://127.0.0.1:7879\")\n}\n\nfn default_max_chunk_tokens() -> usize {\n    256\n}\n    \"#;\n}\n"
  },
  {
    "path": "server/bleep/src/semantic/embedder.rs",
    "content": "use std::{\n    collections::HashMap,\n    path::Path,\n    sync::{\n        atomic::{AtomicUsize, Ordering},\n        Arc, Mutex,\n    },\n};\n\nuse async_trait::async_trait;\nuse tokenizers::Tokenizer;\n\nuse super::Embedding;\n\n#[derive(Default)]\npub struct EmbedQueue {\n    log: scc::Queue<Mutex<Option<EmbedChunk>>>,\n    len: AtomicUsize,\n}\n\nimpl EmbedQueue {\n    pub fn pop(&self) -> Option<EmbedChunk> {\n        let Some(val) = self.log.pop() else {\n            return None;\n        };\n\n        // wrapping shouldn't happen, because only decrements when\n        // `log` is non-empty.\n        self.len.fetch_sub(1, Ordering::SeqCst);\n\n        let val = val.lock().unwrap().take().unwrap();\n        Some(val)\n    }\n\n    pub fn push(&self, chunk: EmbedChunk) {\n        self.log.push(Mutex::new(Some(chunk)));\n        self.len.fetch_add(1, Ordering::SeqCst);\n    }\n\n    pub fn len(&self) -> usize {\n        self.len.load(Ordering::SeqCst)\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.len() == 0\n    }\n}\n\n#[derive(Default)]\npub struct EmbedChunk {\n    pub id: String,\n    pub data: String,\n    pub payload: HashMap<String, qdrant_client::qdrant::Value>,\n}\n\n#[async_trait]\npub trait Embedder: Send + Sync {\n    async fn embed(&self, data: &str) -> anyhow::Result<Embedding>;\n    fn tokenizer(&self) -> &Tokenizer;\n    async fn batch_embed(&self, log: Vec<&str>) -> anyhow::Result<Vec<Embedding>>;\n}\n\n#[cfg(all(not(feature = \"metal\"), feature = \"onnx\"))]\npub use cpu::LocalEmbedder;\n#[cfg(all(not(feature = \"onnx\"), feature = \"metal\"))]\npub use gpu::LocalEmbedder;\n\n#[cfg(all(not(feature = \"metal\"), feature = \"onnx\"))]\nmod cpu {\n    use super::*;\n    #[cfg(feature = \"ee\")]\n    pub use crate::ee::embedder::*;\n    use ndarray::Axis;\n    use ort::{\n        tensor::OrtOwnedTensor, value::Value, Environment, ExecutionProvider,\n        GraphOptimizationLevel, LoggingLevel, SessionBuilder,\n    };\n    use tracing::trace;\n\n    pub struct LocalEmbedder {\n        session: ort::Session,\n        tokenizer: Tokenizer,\n    }\n\n    impl LocalEmbedder {\n        pub fn new(model_dir: &Path) -> anyhow::Result<Self> {\n            let environment = Arc::new(\n                Environment::builder()\n                    .with_name(\"Encode\")\n                    .with_log_level(LoggingLevel::Warning)\n                    .with_execution_providers([ExecutionProvider::CPU(Default::default())])\n                    .with_telemetry(false)\n                    .build()?,\n            );\n\n            let threads = if let Ok(v) = std::env::var(\"NUM_OMP_THREADS\") {\n                str::parse(&v).unwrap_or(1)\n            } else {\n                1\n            };\n\n            let session = SessionBuilder::new(&environment)?\n                .with_optimization_level(GraphOptimizationLevel::Level3)?\n                .with_intra_threads(threads)?\n                .with_model_from_file(model_dir.join(\"model.onnx\"))?;\n\n            let tokenizer = Tokenizer::from_file(model_dir.join(\"tokenizer.json\")).unwrap();\n\n            Ok(Self { session, tokenizer })\n        }\n    }\n\n    #[async_trait]\n    impl Embedder for LocalEmbedder {\n        async fn embed(&self, sequence: &str) -> anyhow::Result<Embedding> {\n            let tokenizer_output = self.tokenizer.encode(sequence, true).unwrap();\n\n            let input_ids = tokenizer_output.get_ids();\n            let attention_mask = tokenizer_output.get_attention_mask();\n            let token_type_ids = tokenizer_output.get_type_ids();\n            let length = input_ids.len();\n            trace!(\"embedding {} tokens {:?}\", length, sequence);\n\n            let inputs_ids_array = ndarray::Array::from_shape_vec(\n                (1, length),\n                input_ids.iter().map(|&x| x as i64).collect(),\n            )?;\n\n            let attention_mask_array = ndarray::Array::from_shape_vec(\n                (1, length),\n                attention_mask.iter().map(|&x| x as i64).collect(),\n            )?;\n\n            let token_type_ids_array = ndarray::Array::from_shape_vec(\n                (1, length),\n                token_type_ids.iter().map(|&x| x as i64).collect(),\n            )?;\n\n            let outputs = self.session.run(vec![\n                Value::from_array(\n                    self.session.allocator(),\n                    &ndarray::CowArray::from(inputs_ids_array).into_dyn(),\n                )\n                .unwrap(),\n                Value::from_array(\n                    self.session.allocator(),\n                    &ndarray::CowArray::from(attention_mask_array).into_dyn(),\n                )\n                .unwrap(),\n                Value::from_array(\n                    self.session.allocator(),\n                    &ndarray::CowArray::from(token_type_ids_array).into_dyn(),\n                )\n                .unwrap(),\n            ])?;\n\n            let output_tensor: OrtOwnedTensor<f32, _> = outputs[0].try_extract().unwrap();\n            let sequence_embedding = &*output_tensor.view();\n            let pooled = sequence_embedding.mean_axis(Axis(1)).unwrap();\n            Ok(pooled.to_owned().as_slice().unwrap().to_vec())\n        }\n\n        fn tokenizer(&self) -> &Tokenizer {\n            &self.tokenizer\n        }\n\n        async fn batch_embed(&self, log: Vec<&str>) -> anyhow::Result<Vec<Embedding>> {\n            log.into_iter()\n                .map(|entry| {\n                    tokio::task::block_in_place(|| {\n                        tokio::runtime::Handle::current()\n                            .block_on(async { self.embed(entry).await })\n                    })\n                })\n                .collect::<anyhow::Result<Vec<Embedding>>>()\n        }\n    }\n}\n\n#[cfg(all(not(feature = \"onnx\"), feature = \"metal\"))]\nmod gpu {\n    use super::*;\n    use tracing::{error, info};\n\n    pub struct LocalEmbedder {\n        model: Box<dyn llm::Model>,\n        sessions: Vec<Arc<tokio::sync::Mutex<llm::InferenceSession>>>,\n        tokenizer: Tokenizer,\n        permits: Arc<tokio::sync::Semaphore>,\n    }\n\n    // InferenceSession is explicitly not Sync because it uses ggml::Tensor internally,\n    // Bert does not make use of these tensors however\n    unsafe impl Sync for LocalEmbedder {}\n\n    impl LocalEmbedder {\n        pub fn new(model_dir: &Path) -> anyhow::Result<Self> {\n            let model_params = llm::ModelParameters {\n                use_gpu: true,\n                ..Default::default()\n            };\n\n            let model = llm::load_dynamic(\n                Some(llm::ModelArchitecture::Bert),\n                &model_dir.join(\"ggml\").join(\"ggml-model-q4_0.bin\"),\n                // this tokenizer is used for embedding\n                llm::TokenizerSource::HuggingFaceTokenizerFile(\n                    model_dir.join(\"ggml\").join(\"tokenizer.json\"),\n                ),\n                model_params,\n                llm::load_progress_callback_stdout,\n            )?;\n\n            // TODO: this can be parameterized\n            //\n            // the lower this number, the more time we might spend waiting to run an embedding.\n            // the higher this number, the more vram we use, currently we use ~2G per session. this\n            // can be fixed by disabling scratch buffers in ggml, bert has no use for this.\n            let session_count = 3;\n\n            info!(%session_count, \"spawned inference sessions\");\n\n            let sessions = (0..session_count)\n                .map(|_| {\n                    model.start_session(llm::InferenceSessionConfig {\n                        ..Default::default()\n                    })\n                })\n                .map(tokio::sync::Mutex::new)\n                .map(Arc::new)\n                .collect();\n\n            // this tokenizer is used for chunking - do not pad or truncate chunks\n            let mut tokenizer =\n                Tokenizer::from_file(model_dir.join(\"ggml\").join(\"tokenizer.json\")).unwrap();\n            let _ = tokenizer.with_padding(None).with_truncation(None);\n\n            Ok(Self {\n                model,\n                sessions,\n                tokenizer,\n                permits: Arc::new(tokio::sync::Semaphore::new(session_count)),\n            })\n        }\n    }\n\n    #[async_trait]\n    impl Embedder for LocalEmbedder {\n        async fn embed(&self, sequence: &str) -> anyhow::Result<Embedding> {\n            let mut output_request = llm::OutputRequest {\n                all_logits: None,\n                embeddings: Some(Vec::new()),\n            };\n            let vocab = self.model.tokenizer();\n            let beginning_of_sentence = true;\n            let query_token_ids = vocab\n                .tokenize(sequence, beginning_of_sentence)\n                .unwrap()\n                .iter()\n                .map(|(_, tok)| *tok)\n                .collect::<Vec<_>>();\n\n            if let Ok(_permit) = self.permits.acquire().await {\n                for s in &self.sessions {\n                    if let Ok(mut session) = s.try_lock() {\n                        self.model\n                            .evaluate(&mut session, &query_token_ids, &mut output_request);\n                        return Ok(output_request.embeddings.unwrap());\n                    }\n                }\n            }\n\n            unreachable!();\n        }\n\n        fn tokenizer(&self) -> &Tokenizer {\n            &self.tokenizer\n        }\n\n        async fn batch_embed(&self, log: Vec<&str>) -> anyhow::Result<Vec<Embedding>> {\n            // do not send empty batches to the model\n            if log.is_empty() {\n                return Ok(vec![]);\n            }\n            let mut output_request = llm::OutputRequest {\n                all_logits: None,\n                embeddings: Some(Vec::new()),\n            };\n            let vocab = self.model.tokenizer();\n            let beginning_of_sentence = true;\n            let query_token_ids = log\n                .iter()\n                .map(|sequence| {\n                    vocab\n                        .tokenize(sequence, beginning_of_sentence)\n                        .unwrap()\n                        .iter()\n                        .map(|(_, tok)| *tok)\n                        .collect::<Vec<_>>()\n                })\n                .collect::<Vec<_>>();\n            let query_token_ids: Vec<_> = query_token_ids.iter().map(AsRef::as_ref).collect();\n\n            if let Ok(_permit) = self.permits.acquire().await {\n                for s in &self.sessions {\n                    if let Ok(mut session) = s.try_lock() {\n                        self.model.batch_evaluate(\n                            &mut session,\n                            &query_token_ids,\n                            &mut output_request,\n                        );\n                        let embedding: Vec<Vec<f32>> = output_request\n                            .embeddings\n                            .unwrap()\n                            .chunks(crate::semantic::schema::EMBEDDING_DIM)\n                            .inspect(|chunk| {\n                                if chunk.iter().any(|f| f.is_nan()) {\n                                    error!(\"found nan in sequence\");\n                                }\n                            })\n                            .map(|chunk| chunk.to_vec())\n                            .collect();\n                        return Ok(embedding);\n                    }\n                }\n            }\n            unreachable!()\n        }\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/semantic/execute.rs",
    "content": "use std::collections::HashMap;\n\nuse crate::{\n    query::{\n        execute::{ApiQuery, PagingMetadata, QueryResponse, QueryResult, ResultStats},\n        parser::SemanticQuery,\n    },\n    semantic::SemanticSearchParams,\n    snippet::Snippet,\n};\n\nuse super::Semantic;\n\nuse anyhow::Result;\n\npub async fn execute(\n    semantic: Semantic,\n    query: SemanticQuery<'_>,\n    params: ApiQuery,\n) -> Result<QueryResponse> {\n    let results = semantic\n        .search(\n            &query,\n            SemanticSearchParams {\n                limit: params.page_size as u64,\n                offset: ((params.page + 1) * params.page_size) as u64,\n                threshold: 0.0,\n                exact_match: false,\n            },\n        )\n        .await?;\n\n    let data = results\n        .into_iter()\n        .fold(HashMap::<_, Vec<_>>::new(), |mut acc, payload| {\n            acc.entry((\n                payload.relative_path.to_string(),\n                payload.repo_name.to_string(),\n                payload.repo_ref.to_string(),\n                Some(payload.lang.to_string()),\n            ))\n            .or_default()\n            .push(Snippet {\n                data: payload.text.to_string(),\n                line_range: payload.start_line as usize..payload.end_line as usize,\n                highlights: vec![],\n                symbols: vec![],\n            });\n\n            acc\n        })\n        .into_iter()\n        .map(|((relative_path, repo_name, repo_ref, lang), snippets)| {\n            QueryResult::Snippets(crate::snippet::SnippedFile {\n                relative_path,\n                repo_name,\n                repo_ref,\n                snippets,\n                lang,\n            })\n        })\n        .collect::<Vec<_>>();\n    Ok(QueryResponse {\n        count: data.len(),\n        metadata: PagingMetadata::new(params.page, params.page_size, None),\n        stats: ResultStats::default(),\n        data,\n    })\n}\n"
  },
  {
    "path": "server/bleep/src/semantic/schema.rs",
    "content": "//! Every change in this file will trigger a reset of the databases.\n//! Use with care.\n//!\nuse qdrant_client::{\n    prelude::QdrantClient,\n    qdrant::payload_index_params::IndexParams,\n    qdrant::{\n        vectors_config, CollectionOperationResponse, CreateCollection, Distance, FieldType,\n        PayloadIndexParams, PointsOperationResponse, TextIndexParams, TokenizerType, VectorParams,\n        VectorsConfig,\n    },\n};\n\nuse crate::repo::RepoRef;\n\npub(super) const EMBEDDING_DIM: usize = 384;\npub type Embedding = Vec<f32>;\n\n#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]\npub struct Payload {\n    pub lang: String,\n    pub repo_name: String,\n    pub repo_ref: RepoRef,\n    pub relative_path: String,\n    pub content_hash: String,\n    pub text: String,\n    pub start_line: u64,\n    pub end_line: u64,\n    pub start_byte: u64,\n    pub end_byte: u64,\n    pub branches: Vec<String>,\n\n    #[serde(skip)]\n    pub id: Option<String>,\n    #[serde(skip)]\n    pub embedding: Option<Embedding>,\n    #[serde(skip)]\n    pub score: Option<f32>,\n}\n\nimpl PartialEq for Payload {\n    fn eq(&self, other: &Self) -> bool {\n        self.lang == other.lang\n            && self.repo_name == other.repo_name\n            && self.repo_ref == other.repo_ref\n            && self.relative_path == other.relative_path\n            && self.content_hash == other.content_hash\n            && self.text == other.text\n            && self.start_line == other.start_line\n            && self.end_line == other.end_line\n            && self.start_byte == other.start_byte\n            && self.end_byte == other.end_byte\n            && self.branches == other.branches\n\n        // ignoring deserialized fields that will not exist on a newly\n        // created payload\n    }\n}\n\npub(super) async fn create_collection(\n    name: &str,\n    qdrant: &QdrantClient,\n) -> anyhow::Result<CollectionOperationResponse> {\n    qdrant\n        .create_collection(&CreateCollection {\n            collection_name: name.to_string(),\n            vectors_config: Some(VectorsConfig {\n                config: Some(vectors_config::Config::Params(VectorParams {\n                    size: EMBEDDING_DIM as u64,\n                    distance: Distance::Cosine.into(),\n                    on_disk: Some(true),\n                    ..Default::default()\n                })),\n            }),\n            on_disk_payload: Some(true),\n            ..Default::default()\n        })\n        .await\n}\n\npub(super) async fn create_lexical_index(\n    name: &str,\n    qdrant: &QdrantClient,\n) -> anyhow::Result<PointsOperationResponse> {\n    qdrant\n        .create_field_index(\n            name.to_string(),\n            \"snippet\",\n            FieldType::Text,\n            Some(&PayloadIndexParams {\n                index_params: Some(IndexParams::TextIndexParams(TextIndexParams {\n                    tokenizer: TokenizerType::Word.into(),\n                    lowercase: Some(true),\n                    min_token_len: Some(2),\n                    max_token_len: Some(20),\n                })),\n            }),\n            None,\n        )\n        .await\n}\n"
  },
  {
    "path": "server/bleep/src/semantic.rs",
    "content": "use std::{borrow::Cow, collections::HashMap, env, path::Path, sync::Arc};\n\nuse crate::{query::parser::SemanticQuery, Configuration};\n\nuse anyhow::bail;\nuse qdrant_client::{\n    prelude::{QdrantClient, QdrantClientConfig},\n    qdrant::{\n        point_id::PointIdOptions, r#match::MatchValue, vectors::VectorsOptions,\n        with_payload_selector, with_vectors_selector, CollectionOperationResponse, FieldCondition,\n        FieldType, Filter, Match, PointId, PointsOperationResponse, RetrievedPoint, ScoredPoint,\n        SearchParams, SearchPoints, Value, Vectors, WithPayloadSelector, WithVectorsSelector,\n    },\n};\n\nuse futures::{stream, StreamExt, TryStreamExt};\nuse rayon::prelude::*;\nuse thiserror::Error;\nuse tracing::{debug, error, info, trace, warn};\n\npub mod chunk;\npub mod embedder;\npub mod execute;\nmod schema;\n\npub use embedder::Embedder;\nuse embedder::LocalEmbedder;\nuse schema::{create_collection, create_lexical_index, EMBEDDING_DIM};\npub use schema::{Embedding, Payload};\n\nuse itertools::Itertools;\n\n#[derive(Error, Debug)]\npub enum SemanticError {\n    /// Represents failure to initialize Qdrant client\n    #[error(\"Qdrant initialization failed. Is Qdrant running on `qdrant-url`?\")]\n    QdrantInitializationError,\n\n    #[cfg(feature = \"onnx\")]\n    #[error(\"ONNX runtime error\")]\n    OnnxRuntimeError {\n        #[from]\n        error: ort::OrtError,\n    },\n\n    #[error(\"semantic error\")]\n    Anyhow {\n        #[from]\n        error: anyhow::Error,\n    },\n}\n\n#[derive(Debug, Clone)]\npub struct SemanticSearchParams {\n    pub limit: u64,\n    pub offset: u64,\n    pub threshold: f32,\n    pub exact_match: bool, // keyword match for all filters\n}\n\n#[derive(Clone)]\npub struct Semantic {\n    qdrant: Arc<QdrantClient>,\n    embedder: Arc<dyn Embedder>,\n    pub(crate) config: Arc<Configuration>,\n}\n\nmacro_rules! val_str(($hash:ident, $val:expr) => { serde_json::from_value($hash.remove($val).unwrap()).unwrap() });\nmacro_rules! val_parse_str(($hash:ident, $val:expr) => {\n    serde_json::from_value::<Cow<'_, str>>($hash.remove($val).unwrap())\n        .unwrap()\n        .parse()\n        .unwrap()\n});\n\nimpl Payload {\n    pub fn from_qdrant(orig: ScoredPoint) -> Payload {\n        let ScoredPoint {\n            id,\n            payload,\n            score,\n            vectors,\n            ..\n        } = orig;\n\n        parse_payload(id, vectors, payload, score)\n    }\n\n    pub fn from_scroll(orig: RetrievedPoint) -> Payload {\n        let RetrievedPoint {\n            id,\n            payload,\n            vectors,\n            ..\n        } = orig;\n\n        parse_payload(id, vectors, payload, 0.0)\n    }\n\n    pub(crate) fn into_qdrant(self) -> HashMap<String, Value> {\n        HashMap::from([\n            (\"lang\".into(), self.lang.to_ascii_lowercase().into()),\n            (\"repo_name\".into(), self.repo_name.into()),\n            (\"repo_ref\".into(), self.repo_ref.to_string().into()),\n            (\"relative_path\".into(), self.relative_path.into()),\n            (\"content_hash\".into(), self.content_hash.into()),\n            (\"snippet\".into(), self.text.into()),\n            (\"start_line\".into(), self.start_line.to_string().into()),\n            (\"end_line\".into(), self.end_line.to_string().into()),\n            (\"start_byte\".into(), self.start_byte.to_string().into()),\n            (\"end_byte\".into(), self.end_byte.to_string().into()),\n            (\"branches\".into(), self.branches.into()),\n        ])\n    }\n}\n\nfn parse_payload(\n    id: Option<PointId>,\n    vectors: Option<Vectors>,\n    payload: HashMap<String, Value>,\n    score: f32,\n) -> Payload {\n    let Some(PointId {\n        point_id_options: Some(PointIdOptions::Uuid(id)),\n    }) = id\n    else {\n        // unless the db was corrupted/written by someone else,\n        // this shouldn't happen\n        unreachable!(\"corrupted db\");\n    };\n\n    let embedding = match vectors {\n        None => None,\n        Some(Vectors {\n            vectors_options: Some(VectorsOptions::Vector(v)),\n        }) => Some(v.data),\n        _ => {\n            // this also should probably never happen\n            unreachable!(\"got non-vector value\");\n        }\n    };\n\n    let mut converted = payload\n        .into_iter()\n        .map(|(key, value)| (key, kind_to_value(value.kind)))\n        .collect::<HashMap<String, serde_json::Value>>();\n\n    Payload {\n        lang: val_str!(converted, \"lang\"),\n        repo_name: val_str!(converted, \"repo_name\"),\n        repo_ref: val_str!(converted, \"repo_ref\"),\n        relative_path: val_str!(converted, \"relative_path\"),\n        content_hash: val_str!(converted, \"content_hash\"),\n        text: val_str!(converted, \"snippet\"),\n        branches: val_str!(converted, \"branches\"),\n        start_line: val_parse_str!(converted, \"start_line\"),\n        end_line: val_parse_str!(converted, \"end_line\"),\n        start_byte: val_parse_str!(converted, \"start_byte\"),\n        end_byte: val_parse_str!(converted, \"end_byte\"),\n\n        id: Some(id),\n        score: Some(score),\n        embedding,\n    }\n}\n\nfn kind_to_value(kind: Option<qdrant_client::qdrant::value::Kind>) -> serde_json::Value {\n    use qdrant_client::qdrant::value::Kind;\n    match kind {\n        Some(Kind::NullValue(_)) => serde_json::Value::Null,\n        Some(Kind::BoolValue(v)) => serde_json::Value::Bool(v),\n        Some(Kind::DoubleValue(v)) => {\n            serde_json::Value::Number(serde_json::Number::from_f64(v).unwrap())\n        }\n        Some(Kind::IntegerValue(v)) => serde_json::Value::Number(v.into()),\n        Some(Kind::StringValue(v)) => serde_json::Value::String(v),\n        Some(Kind::ListValue(v)) => serde_json::Value::Array(\n            v.values\n                .into_iter()\n                .map(|v| kind_to_value(v.kind))\n                .collect(),\n        ),\n        Some(Kind::StructValue(_v)) => todo!(),\n        None => serde_json::Value::Null,\n    }\n}\n\nasync fn create_indexes(collection_name: &str, qdrant: &QdrantClient) -> anyhow::Result<()> {\n    let text_fields = &[\"repo_ref\", \"content_hash\", \"branches\", \"relative_path\"];\n    for field in text_fields {\n        qdrant\n            .create_field_index(collection_name, field, FieldType::Text, None, None)\n            .await?;\n    }\n\n    Ok(())\n}\n\nimpl Semantic {\n    #[tracing::instrument(fields(collection=%config.collection_name, %qdrant_url), skip_all)]\n    pub async fn initialize(\n        model_dir: &Path,\n        qdrant_url: &str,\n        config: Arc<Configuration>,\n    ) -> Result<Self, SemanticError> {\n        let qdrant = QdrantClient::new(Some(QdrantClientConfig::from_url(qdrant_url))).unwrap();\n        debug!(\"initialized client\");\n\n        match qdrant.has_collection(&config.collection_name).await {\n            Ok(false) => {\n                let CollectionOperationResponse { result, time } =\n                    create_collection(&config.collection_name, &qdrant)\n                        .await\n                        .unwrap();\n\n                debug!(time, created = result, \"collection created\");\n                assert!(result);\n                let PointsOperationResponse { result, time: _ } =\n                    create_lexical_index(&config.collection_name, &qdrant)\n                        .await\n                        .unwrap();\n\n                debug!(\"lexical index created\");\n                debug!(\"{:?}\", result);\n            }\n            Ok(true) => {\n                debug!(\"collection already exists\");\n            }\n            Err(_) => return Err(SemanticError::QdrantInitializationError),\n        }\n\n        create_indexes(&config.collection_name, &qdrant).await?;\n        debug!(\"indexes created\");\n\n        if let Some(dylib_dir) = config.dylib_dir.as_ref() {\n            init_ort_dylib(dylib_dir);\n            debug!(\n                dylib_dir = dylib_dir.to_string_lossy().as_ref(),\n                \"initialized ORT dylib\"\n            );\n        }\n\n        let embedder: Arc<dyn Embedder> = Arc::new(LocalEmbedder::new(model_dir)?);\n        debug!(\"using local embedder\");\n\n        Ok(Self {\n            qdrant: qdrant.into(),\n            embedder,\n            config,\n        })\n    }\n\n    pub fn collection_name(&self) -> &str {\n        &self.config.collection_name\n    }\n\n    pub fn qdrant_client(&self) -> &QdrantClient {\n        &self.qdrant\n    }\n\n    pub fn embedder(&self) -> &dyn Embedder {\n        self.embedder.as_ref()\n    }\n\n    pub async fn reset_collection_blocking(&self) -> anyhow::Result<()> {\n        _ = self\n            .qdrant\n            .delete_collection(&self.config.collection_name)\n            .await?;\n\n        let deleted = 'deleted: {\n            for _ in 0..60 {\n                match self\n                    .qdrant\n                    .has_collection(&self.config.collection_name)\n                    .await\n                {\n                    Ok(true) => {\n                        tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n                    }\n                    Ok(false) => {\n                        break 'deleted true;\n                    }\n                    Err(err) => {\n                        error!(?err, \"failed to delete qdrant collection for migration\");\n                    }\n                }\n            }\n            false\n        };\n\n        if !deleted {\n            error!(\"failed to delete qdrant collection after 60s\");\n            bail!(\"deletion failed\")\n        }\n\n        let CollectionOperationResponse { result, .. } =\n            create_collection(&self.config.collection_name, &self.qdrant)\n                .await\n                .unwrap();\n\n        assert!(result);\n\n        let PointsOperationResponse { result, time: _ } =\n            create_lexical_index(&self.config.collection_name, &self.qdrant)\n                .await\n                .unwrap();\n\n        debug!(\"lexical index created\");\n        debug!(\"{:?}\", result);\n\n        Ok(())\n    }\n\n    pub async fn health_check(&self) -> anyhow::Result<()> {\n        self.qdrant.health_check().await?;\n        Ok(())\n    }\n\n    // Search Qdrant snippets (payload)\n    pub async fn search_lexical<'a>(\n        &self,\n        parsed_query: &SemanticQuery<'a>,\n        vector: Embedding,\n        limit: u64,\n        offset: u64,\n        threshold: f32,\n        exact: bool,\n    ) -> anyhow::Result<Vec<ScoredPoint>> {\n        let hybrid_filter = Some(Filter {\n            should: build_conditions_lexical(parsed_query),\n            must: build_conditions(parsed_query, exact),\n            ..Default::default()\n        });\n\n        let response = self\n            .qdrant\n            .search_points(&SearchPoints {\n                limit,\n                vector,\n                collection_name: self.config.collection_name.to_string(),\n                offset: Some(offset),\n                score_threshold: Some(threshold),\n                with_payload: Some(true.into()),\n                filter: hybrid_filter,\n                with_vectors: Some(true.into()),\n                ..Default::default()\n            })\n            .await?;\n\n        Ok(response.result)\n    }\n\n    pub async fn search_with<'a>(\n        &self,\n        parsed_query: &SemanticQuery<'a>,\n        vector: Embedding,\n        limit: u64,\n        offset: u64,\n        threshold: f32,\n        exact: bool,\n    ) -> anyhow::Result<Vec<ScoredPoint>> {\n        let response = self\n            .qdrant\n            .search_points(&SearchPoints {\n                limit,\n                vector,\n                collection_name: self.config.collection_name.to_string(),\n                offset: Some(offset),\n                score_threshold: Some(threshold),\n                with_payload: Some(WithPayloadSelector {\n                    selector_options: Some(with_payload_selector::SelectorOptions::Enable(true)),\n                }),\n                filter: Some(Filter {\n                    must: build_conditions(parsed_query, exact),\n                    ..Default::default()\n                }),\n                with_vectors: Some(WithVectorsSelector {\n                    selector_options: Some(with_vectors_selector::SelectorOptions::Enable(true)),\n                }),\n                params: Some(SearchParams {\n                    indexed_only: Some(true),\n                    ..Default::default()\n                }),\n                ..Default::default()\n            })\n            .await?;\n\n        Ok(response.result)\n    }\n\n    pub async fn batch_search_with<'a>(\n        &self,\n        parsed_queries: &[&SemanticQuery<'a>],\n        vectors: Vec<Embedding>,\n        limit: u64,\n        offset: u64,\n        threshold: f32,\n        exact: bool,\n    ) -> anyhow::Result<Vec<ScoredPoint>> {\n        // FIXME: This method uses `search_points` internally, and not `search_batch_points`. It's\n        // not clear why, but it seems that the `batch` variant of the `qdrant` calls leads to\n        // HTTP2 errors on some deployment configurations. A typical example error:\n        //\n        // ```\n        // hyper::proto::h2::client: client response error: stream error received: stream no longer needed\n        // ```\n        //\n        // Given that qdrant uses `tonic`, this may be a `tonic` issue, possibly similar to:\n        // https://github.com/hyperium/tonic/issues/222\n\n        // Queries should contain the same filters, so we get the first one\n        let parsed_query = parsed_queries.first().unwrap();\n        let filters = &build_conditions(parsed_query, exact);\n\n        let responses = stream::iter(vectors.into_iter())\n            .map(|vector| async move {\n                let points = SearchPoints {\n                    limit,\n                    vector,\n                    collection_name: self.config.collection_name.to_string(),\n                    offset: Some(offset),\n                    score_threshold: Some(threshold),\n                    with_payload: Some(WithPayloadSelector {\n                        selector_options: Some(with_payload_selector::SelectorOptions::Enable(\n                            true,\n                        )),\n                    }),\n                    filter: Some(Filter {\n                        must: filters.clone(),\n                        ..Default::default()\n                    }),\n                    with_vectors: Some(WithVectorsSelector {\n                        selector_options: Some(with_vectors_selector::SelectorOptions::Enable(\n                            true,\n                        )),\n                    }),\n                    ..Default::default()\n                };\n\n                self.qdrant.search_points(&points).await\n            })\n            .buffered(10)\n            .try_collect::<Vec<_>>()\n            .await?;\n\n        Ok(responses.into_iter().flat_map(|r| r.result).collect())\n    }\n\n    // Rank Qdrant lexical results by counts of word matches\n    // Multiple word hits count more\n    // Score is 10 * (# of query words matched) + (total number of hits)\n    fn rank_lexical(payloads: Vec<Payload>, query: &str) -> Vec<Payload> {\n        let keywords: Vec<&str> = query.split_whitespace().collect();\n        let counts = payloads.iter().map(|p| {\n            (\n                keywords\n                    .iter()\n                    .map(|&k| p.text.matches(k).count())\n                    .collect::<Vec<usize>>(),\n                p.clone(),\n            )\n        });\n        let mut scores: Vec<(f32, Payload)> = counts\n            .map(|(count, payload)| {\n                let score: f32 = (count.iter().sum::<usize>()\n                    + 10 * count.iter().filter(|&&c| c != 0).count())\n                    as f32;\n                (score, payload.clone())\n            })\n            .collect();\n        scores.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));\n        scores.into_iter().map(|(_, payload)| payload).collect()\n    }\n\n    // Join semantic and lexical results using Reciprocal Rank Fusion (RRF)\n    // For item i present in j rankings (rank_i,j) score_i = sum_j (1/ (rank_i,j + k))\n    // k controls how much weight to give to lower ranks and is set to 60 which is\n    // the default value in multiple implementations and in http://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf\n    fn merge_rrf(payloads_lexical: Vec<Payload>, payloads_semantic: Vec<Payload>) -> Vec<Payload> {\n        let k = 60;\n        let lexical_scores = payloads_lexical\n            .into_iter()\n            .enumerate()\n            .map(|(rank, payload)| (1.0 / ((rank + 1 + k) as f32), payload.clone()));\n        let payload_scores = payloads_semantic\n            .into_iter()\n            .enumerate()\n            .map(|(rank, payload)| (1.0 / ((rank + 1 + k) as f32), payload.clone()));\n        let mut concatenated: Vec<(f32, Payload)> = lexical_scores.chain(payload_scores).collect();\n        // group by requires pre-sorting\n        concatenated.sort_by(|a, b| {\n            b.1.id\n                .partial_cmp(&a.1.id)\n                .unwrap_or(std::cmp::Ordering::Equal)\n        });\n        let mut merged: Vec<(f32, Payload)> = concatenated\n            .iter()\n            .group_by(|(_, payload)| payload.id.clone())\n            .into_iter()\n            .map(|(_id, group)| {\n                let group_vec: Vec<_> = group.collect();\n\n                let sum: f32 = group_vec.iter().map(|(score, _payload)| score).sum();\n                let payload = group_vec\n                    .into_iter()\n                    .map(|(_score, payload)| payload)\n                    .next();\n                (sum, payload.cloned().unwrap())\n            })\n            .collect();\n        merged.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));\n        merged.into_iter().map(|(_, payload)| payload).collect()\n    }\n\n    pub async fn search<'a>(\n        &self,\n        query: &SemanticQuery<'a>,\n        params: SemanticSearchParams,\n    ) -> anyhow::Result<Vec<Payload>> {\n        let Some(query_target) = query.target() else {\n            anyhow::bail!(\"no search target for query\");\n        };\n        let vector = self.embedder.embed(&query_target).await?;\n        let SemanticSearchParams {\n            limit,\n            offset,\n            threshold,\n            exact_match: exact,\n        } = params;\n\n        // TODO: Remove the need for `retrieve_more`. It's here because:\n        // In /q `limit` is the maximum number of results returned (the actual number will often be lower due to deduplication)\n        // In /answer we want to retrieve `limit` results exactly\n        let results = self\n            .search_with(\n                query,\n                vector.clone(),\n                limit * 4, // Retrieve double `limit` and deduplicate\n                offset,\n                threshold,\n                exact,\n            )\n            .await\n            .map(|raw| {\n                raw.into_iter()\n                    .map(Payload::from_qdrant)\n                    .collect::<Vec<_>>()\n            })?;\n        let results = deduplicate_snippets(results, vector.clone(), limit);\n\n        let results_lexical = self\n            .search_lexical(\n                query,\n                vector.clone(),\n                limit * 2, // Retrieve double `limit` and deduplicate\n                offset,\n                0.0,\n                exact,\n            )\n            .await\n            .map(|raw| {\n                raw.into_iter()\n                    .map(Payload::from_qdrant)\n                    .collect::<Vec<_>>()\n            })?;\n        let results_lexical = deduplicate_snippets(results_lexical, vector.clone(), limit);\n        let results_lexical = Self::rank_lexical(results_lexical, &query_target);\n\n        let merged_results = Self::merge_rrf(results_lexical, results);\n\n        Ok(merged_results\n            .iter()\n            .take(limit.try_into().unwrap())\n            .cloned()\n            .collect())\n    }\n\n    pub async fn batch_search<'a>(\n        &self,\n        parsed_queries: &[&SemanticQuery<'a>],\n        params: SemanticSearchParams,\n    ) -> anyhow::Result<Vec<Payload>> {\n        if parsed_queries.iter().any(|q| q.target().is_none()) {\n            anyhow::bail!(\"no search target for query\");\n        };\n\n        let vectors = futures::future::join_all(\n            parsed_queries\n                .iter()\n                .map(|q| async { self.embedder.embed(&q.target().unwrap()).await }),\n        )\n        .await\n        .into_iter()\n        .collect::<anyhow::Result<Vec<_>>>()?;\n\n        let SemanticSearchParams {\n            limit,\n            offset,\n            threshold,\n            exact_match: exact,\n        } = params;\n\n        trace!(?parsed_queries, \"performing qdrant batch search\");\n\n        let result = self\n            .batch_search_with(\n                parsed_queries,\n                vectors.clone(),\n                limit * 2, // Retrieve double `limit` and deduplicate\n                offset,\n                threshold,\n                exact,\n            )\n            .await;\n\n        trace!(?result, \"qdrant batch search returned\");\n\n        let results = result?\n            .into_iter()\n            .map(Payload::from_qdrant)\n            .collect::<Vec<_>>();\n\n        // deduplicate with mmr with respect to the mean of query vectors\n        // TODO: implement a more robust multi-vector deduplication strategy\n        let target_vector = mean_pool(vectors);\n        Ok(deduplicate_snippets(results, target_vector, limit))\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    #[tracing::instrument(skip(self, repo_name, buffer))]\n    pub fn chunks_for_buffer<'a>(\n        &'a self,\n        file_cache_key: String,\n        repo_name: &'a str,\n        repo_ref: &'a str,\n        relative_path: &'a str,\n        buffer: &'a str,\n        lang_str: &'a str,\n        branches: &'a [String],\n    ) -> impl ParallelIterator<Item = (String, Payload)> + 'a {\n        const MIN_CHUNK_TOKENS: usize = 50;\n\n        let chunks = chunk::by_tokens(\n            repo_name,\n            relative_path,\n            buffer,\n            self.embedder.tokenizer(),\n            MIN_CHUNK_TOKENS..self.config.max_chunk_tokens,\n            chunk::OverlapStrategy::default(),\n        );\n        trace!(chunk_count = chunks.len(), \"found chunks\");\n\n        chunks.into_par_iter().map(move |chunk| {\n            let data = format!(\"{repo_name}\\t{relative_path}\\n{}\", chunk.data);\n            let payload = Payload {\n                repo_name: repo_name.to_owned(),\n                repo_ref: repo_ref.parse().unwrap(),\n                relative_path: relative_path.to_owned(),\n                content_hash: file_cache_key.to_string(),\n                text: chunk.data.to_owned(),\n                lang: lang_str.to_ascii_lowercase(),\n                branches: branches.to_owned(),\n                start_line: chunk.range.start.line as u64,\n                end_line: chunk.range.end.line as u64,\n                start_byte: chunk.range.start.byte as u64,\n                end_byte: chunk.range.end.byte as u64,\n                id: Default::default(),\n                embedding: Default::default(),\n                score: Default::default(),\n            };\n\n            (data, payload)\n        })\n    }\n\n    pub async fn delete_points_for_hash(\n        &self,\n        repo_ref: &str,\n        paths: impl Iterator<Item = String>,\n    ) {\n        let repo_filter = make_kv_keyword_filter(\"repo_ref\", repo_ref).into();\n        let file_filter = paths\n            .map(|p| make_kv_keyword_filter(\"content_hash\", &p).into())\n            .collect::<Vec<_>>();\n\n        let selector = Filter {\n            must: vec![repo_filter],\n            should: file_filter,\n            ..Default::default()\n        }\n        .into();\n\n        let _ = self\n            .qdrant\n            .delete_points(&self.config.collection_name, &selector, None)\n            .await;\n    }\n}\n\n/// Initialize the `ORT_DYLIB_PATH` variable, consumed by the `ort` crate.\n///\n/// This doesn't do anything on Windows, as tauri on Windows will automatically bundle any `.dll`\n/// files found in the `target/$profile` folder. The `ort` crate by default will also copy the\n/// built dynamic library over to the `target/$profile` folder, when using the download strategy.\nfn init_ort_dylib(dylib_dir: impl AsRef<Path>) {\n    #[cfg(target_os = \"linux\")]\n    let lib_name = \"libonnxruntime.so\";\n    #[cfg(target_os = \"macos\")]\n    let lib_name = \"libonnxruntime.dylib\";\n    #[cfg(windows)]\n    let lib_name = \"onnxruntime.dll\";\n\n    let ort_dylib_path = dylib_dir.as_ref().join(lib_name);\n\n    if env::var(\"ORT_DYLIB_PATH\").is_err() {\n        env::set_var(\"ORT_DYLIB_PATH\", ort_dylib_path);\n    }\n}\n\n/// Exact match filter\npub(crate) fn make_kv_keyword_filter(key: &str, value: &str) -> FieldCondition {\n    let key = key.to_owned();\n    let value = value.to_owned();\n    FieldCondition {\n        key,\n        r#match: Some(Match {\n            match_value: MatchValue::Keyword(value).into(),\n        }),\n        ..Default::default()\n    }\n}\n\n// Substring match filter\nfn make_kv_text_filter(key: &str, value: &str) -> FieldCondition {\n    let key = key.to_owned();\n    let value = value.to_owned();\n    FieldCondition {\n        key,\n        r#match: Some(Match {\n            match_value: MatchValue::Text(value).into(),\n        }),\n        ..Default::default()\n    }\n}\n\n// add a filter that matches any of the keywords in the query\nfn build_conditions_lexical(\n    parsed_query: &SemanticQuery<'_>,\n) -> Vec<qdrant_client::qdrant::Condition> {\n    let Some(query) = parsed_query.target() else {\n        debug!(\"empty query for lexical search\");\n        return Vec::new();\n    };\n    let conditions = query\n        .split(' ')\n        .map(|s| make_kv_text_filter(\"snippet\", s))\n        .map(Into::into)\n        .collect::<Vec<FieldCondition>>();\n    conditions\n        .iter()\n        .map(|c| qdrant_client::qdrant::Condition {\n            condition_one_of: Some(qdrant_client::qdrant::condition::ConditionOneOf::Field(\n                c.clone(),\n            )),\n        })\n        .collect()\n}\n\nfn build_conditions(\n    query: &SemanticQuery<'_>,\n    exact_match: bool,\n) -> Vec<qdrant_client::qdrant::Condition> {\n    let path_filter = {\n        let conditions = query\n            .paths()\n            .map(|r| {\n                if exact_match {\n                    make_kv_keyword_filter(\"relative_path\", r.as_ref())\n                } else {\n                    make_kv_text_filter(\"relative_path\", r.as_ref())\n                }\n                .into()\n            })\n            .collect::<Vec<_>>();\n        if conditions.is_empty() {\n            None\n        } else {\n            Some(Filter {\n                should: conditions,\n                ..Default::default()\n            })\n        }\n    };\n\n    let repo_filter = {\n        let conditions = query\n            .repos()\n            .map(|r| make_kv_keyword_filter(\"repo_name\", &r).into())\n            .collect::<Vec<_>>();\n        // one of the above repos should match\n        if conditions.is_empty() {\n            None\n        } else {\n            Some(Filter {\n                should: conditions,\n                ..Default::default()\n            })\n        }\n    };\n\n    let lang_filter = {\n        let conditions = query\n            .langs()\n            .map(|l| make_kv_keyword_filter(\"lang\", l.as_ref()).into())\n            .collect::<Vec<_>>();\n        // one of the above langs should match\n        if conditions.is_empty() {\n            None\n        } else {\n            Some(Filter {\n                should: conditions,\n                ..Default::default()\n            })\n        }\n    };\n\n    let branch_filter = {\n        let conditions = query\n            .branch()\n            .map(|l| make_kv_keyword_filter(\"branches\", l.as_ref()).into())\n            .collect::<Vec<_>>();\n\n        if conditions.is_empty() {\n            None\n        } else {\n            Some(Filter {\n                should: conditions,\n                ..Default::default()\n            })\n        }\n    };\n\n    let filters: Vec<_> = [repo_filter, path_filter, lang_filter, branch_filter]\n        .into_iter()\n        .flatten()\n        .map(Into::into)\n        .collect();\n\n    filters\n}\n\nfn dot(a: &[f32], b: &[f32]) -> f32 {\n    a.iter().zip(b.iter()).map(|(ai, bi)| ai * bi).sum()\n}\n\nfn norm(a: &[f32]) -> f32 {\n    dot(a, a)\n}\n\nfn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {\n    dot(a, b) / (norm(a) * norm(b))\n}\n\n// Calculate the element-wise mean of the embeddings\nfn mean_pool(embeddings: Vec<Vec<f32>>) -> Vec<f32> {\n    let len = embeddings.len() as f32;\n    let mut result = vec![0.0; EMBEDDING_DIM];\n    for embedding in embeddings {\n        for (i, v) in embedding.iter().enumerate() {\n            result[i] += v;\n        }\n    }\n    result.iter_mut().for_each(|v| *v /= len);\n    result\n}\n\n// returns a list of indices to preserve from `snippets`\n//\n// query_embedding: the embedding of the query terms\n// embeddings: the list of embeddings to select from\n// lambda: MMR is a weighted selection of two opposing factors:\n//    - relevance to the query\n//    - \"novelty\" or, the measure of how minimal the similarity is\n//      to existing documents in the selection\n//      The value of lambda skews the weightage in favor of either relevance or novelty.\n//    - we add a language diversity factor to the score to encourage a range of languages in the results\n//    - we also add a path diversity factor to the score to encourage a range of paths in the results\n//    - we also add a repo diversity factor to the score to encourage a range of repos in the results\n//  k: the number of embeddings to select\npub fn deduplicate_with_mmr(\n    query_embedding: &[f32],\n    embeddings: &[&[f32]],\n    languages: &[&str],\n    paths: &[&str],\n    repos: &[&str],\n    lambda: f32,\n    k: usize,\n) -> Vec<usize> {\n    let mut idxs = vec![];\n    let mut lang_counts = HashMap::new();\n    let mut path_counts = HashMap::new();\n    let mut repo_counts = HashMap::new();\n\n    if embeddings.len() < k {\n        return (0..embeddings.len()).collect();\n    }\n\n    while idxs.len() < k {\n        let mut best_score = f32::NEG_INFINITY;\n        let mut idx_to_add = None;\n\n        for (i, emb) in embeddings.iter().enumerate() {\n            if idxs.contains(&i) {\n                continue;\n            }\n            let first_part = cosine_similarity(query_embedding, emb);\n            let mut second_part = 0.;\n            for j in idxs.iter() {\n                let cos_sim = cosine_similarity(emb, embeddings[*j]);\n                if cos_sim > second_part {\n                    second_part = cos_sim;\n                }\n            }\n            let mut equation_score = lambda * first_part - (1. - lambda) * second_part;\n\n            // MMR + (1/2)^n where n is the number of times a language has been selected\n            let lang_count = lang_counts.get(languages[i]).unwrap_or(&1);\n            equation_score += 0.5_f32.powi(*lang_count);\n\n            // MMR + (3/4)^n where n is the number of times a path has been selected\n            let path_count = path_counts.get(paths[i]).unwrap_or(&1);\n            equation_score += 0.75_f32.powi(*path_count);\n\n            // MMR + (3/4)^n where n is the number of times a repo has been selected\n            let repo_count = repo_counts.get(repos[i]).unwrap_or(&1);\n            equation_score += 0.75_f32.powi(*repo_count);\n\n            if equation_score > best_score {\n                best_score = equation_score;\n                idx_to_add = Some(i);\n            }\n        }\n        if let Some(i) = idx_to_add {\n            idxs.push(i);\n            *lang_counts.entry(languages[i]).or_insert(0) += 1;\n            *path_counts.entry(paths[i]).or_insert(0) += 1;\n            *repo_counts.entry(repos[i]).or_insert(0) += 1;\n        }\n    }\n    idxs\n}\n\nfn filter_overlapping_snippets(mut snippets: Vec<Payload>) -> Vec<Payload> {\n    snippets.sort_by(|a, b| {\n        a.relative_path\n            .cmp(&b.relative_path)\n            .then(a.start_line.cmp(&b.start_line))\n    });\n\n    snippets = snippets\n        .into_iter()\n        .fold(Vec::<Payload>::new(), |mut deduped_snippets, snippet| {\n            if let Some(prev) = deduped_snippets.last_mut() {\n                if prev.relative_path == snippet.relative_path\n                    && prev.end_line >= snippet.start_line\n                {\n                    debug!(\n                        \"Filtering overlapping snippets. End: {:?} - Start: {:?} from {:?}\",\n                        prev.end_line, snippet.start_line, prev.relative_path\n                    );\n                    return deduped_snippets;\n                }\n            }\n            deduped_snippets.push(snippet);\n            deduped_snippets\n        });\n\n    snippets.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap());\n    snippets\n}\n\npub fn deduplicate_snippets(\n    mut all_snippets: Vec<Payload>,\n    query_embedding: Embedding,\n    output_count: u64,\n) -> Vec<Payload> {\n    all_snippets = filter_overlapping_snippets(all_snippets);\n\n    let idxs = {\n        let lambda = 0.5;\n        let k = output_count; // number of snippets\n        let embeddings = all_snippets\n            .iter()\n            .map(|s| s.embedding.as_deref().unwrap())\n            .collect::<Vec<_>>();\n        let languages = all_snippets\n            .iter()\n            .map(|s| s.lang.as_ref())\n            .collect::<Vec<_>>();\n        let paths = all_snippets\n            .iter()\n            .map(|s| s.relative_path.as_ref())\n            .collect::<Vec<_>>();\n        let repos = all_snippets\n            .iter()\n            .map(|s| s.repo_name.as_ref())\n            .collect::<Vec<_>>();\n        deduplicate_with_mmr(\n            &query_embedding,\n            &embeddings,\n            &languages,\n            &paths,\n            &repos,\n            lambda,\n            k as usize,\n        )\n    };\n\n    info!(\"preserved idxs after MMR are {:?}\", idxs);\n\n    all_snippets\n        .drain(..)\n        .enumerate()\n        .filter_map(|(ref i, payload)| {\n            if idxs.contains(i) {\n                Some(payload)\n            } else {\n                None\n            }\n        })\n        .collect()\n}\n"
  },
  {
    "path": "server/bleep/src/snippet.rs",
    "content": "use anyhow::Result;\nuse regex::{Regex, RegexBuilder};\nuse serde::Serialize;\nuse smallvec::{smallvec, SmallVec};\n\nuse crate::{indexes, symbol::Symbol};\nuse std::ops::Range;\n\n#[derive(Serialize, Debug, PartialEq, Eq)]\npub struct SnippedFile {\n    pub relative_path: String,\n    pub repo_name: String,\n    pub repo_ref: String,\n    pub lang: Option<String>,\n    pub snippets: Vec<Snippet>,\n}\n\n#[derive(Serialize, Debug, PartialEq, Eq)]\npub struct Snippet {\n    pub data: String,\n    pub highlights: Vec<Range<usize>>,\n    pub symbols: Vec<Symbol>,\n    pub line_range: Range<usize>,\n}\n\n/// A marker indicating a subset of some source text, with a list of highlighted ranges.\n///\n/// This doesn't store the actual text data itself, just the position information for simplified\n/// merging.\n#[derive(Serialize, Debug, PartialEq, Eq)]\npub struct Location {\n    /// The subset's byte range in the original input string.\n    pub byte_range: Range<usize>,\n\n    /// The subset's line range in the original input string.\n    pub line_range: Range<usize>,\n\n    /// A set of byte ranges denoting highlighted text indices, on the subset string.\n    pub highlights: SmallVec<[Range<usize>; 2]>,\n}\n\nimpl Location {\n    // This is not a real error type, it communicates that the argument was not consumed.\n    #[allow(clippy::result_large_err)]\n    fn join(&mut self, rhs: Self) -> Result<(), Self> {\n        // Override empty snippets.\n        if self.highlights.is_empty() {\n            *self = rhs;\n            return Ok(());\n        }\n\n        // Fail if the locations don't overlap.\n        if self.line_range.end < rhs.line_range.start {\n            return Err(rhs);\n        }\n\n        let offset = rhs.byte_range.start - self.byte_range.start;\n        self.line_range.end = rhs.line_range.end;\n        self.byte_range.end = rhs.byte_range.end;\n        self.highlights\n            .extend(rhs.highlights.into_iter().map(|mut h| {\n                h.start += offset;\n                h.end += offset;\n                h\n            }));\n\n        Ok(())\n    }\n\n    /// Reify this `Location` into a `Snippet`, given the source string and symbols list.\n    pub fn reify(self, s: &str, symbols: &[Symbol]) -> Snippet {\n        Snippet {\n            data: s[self.byte_range.clone()].to_owned(),\n            line_range: self.line_range.clone(),\n            highlights: self.highlights.into_vec(),\n            symbols: symbols\n                .iter()\n                .filter(|s| {\n                    s.range.start.line >= self.line_range.start\n                        && s.range.end.line <= self.line_range.end\n                })\n                .cloned()\n                .map(|mut sym| {\n                    sym.range.start.byte -= self.byte_range.start;\n                    sym.range.end.byte -= self.byte_range.start;\n                    sym\n                })\n                .collect(),\n        }\n    }\n\n    pub fn line_count(&self) -> usize {\n        self.line_range.end - self.line_range.start\n    }\n}\n\nimpl SnippedFile {\n    pub fn merge(mut self, rhs: Self) -> Self {\n        self.snippets.extend(rhs.snippets);\n        Self {\n            snippets: self.snippets,\n            ..rhs\n        }\n    }\n}\n\n#[derive(Copy, Clone, Debug)]\npub struct Snipper {\n    pub context_before: usize,\n    pub context_after: usize,\n    pub find_symbols: bool,\n    pub case_sensitive: bool,\n}\n\nimpl Default for Snipper {\n    fn default() -> Self {\n        Self {\n            context_before: 0,\n            context_after: 0,\n            find_symbols: false,\n            case_sensitive: true,\n        }\n    }\n}\n\nimpl Snipper {\n    pub fn context(mut self, before: usize, after: usize) -> Self {\n        self.context_before = before;\n        self.context_after = after;\n        self\n    }\n\n    pub fn find_symbols(mut self, find_symbols: bool) -> Self {\n        self.find_symbols = find_symbols;\n        self\n    }\n\n    pub fn case_sensitive(mut self, case_sensitive: bool) -> Self {\n        self.case_sensitive = case_sensitive;\n        self\n    }\n\n    pub fn all_for_doc(\n        &self,\n        regex: &str,\n        doc: &indexes::reader::ContentDocument,\n    ) -> Result<Option<SnippedFile>> {\n        let query = RegexBuilder::new(regex)\n            .multi_line(true)\n            .case_insensitive(!self.case_sensitive)\n            .build()?;\n\n        let snippets = if self.find_symbols {\n            // a symbol search should perform an intersection of\n            // search results with the symbol list present in a document.\n            //\n            let mut symbols = doc.symbol_locations.list();\n            let symbol_ranges = symbols\n                .iter()\n                .map(|sym| sym.range.into())\n                .collect::<Vec<Range<usize>>>();\n\n            // limit highlights to only symbols\n            //\n            // for a search query of `symbol:n` on this text:\n            //\n            //    const cool_beans = beans();\n            //\n            // only the `n` from `cool_beans` should be highlighted, if\n            // `cool_beans` is the only symbol in the document:\n            //\n            //    const cool_beans = beans();\n            //                  ^-- expected\n            //\n            //    const cool_beans = beans();\n            //      ^           ^       ^-- incorrect\n            //\n            let highlights = query\n                .find_iter(&doc.content)\n                .map(|m| m.range())\n                .filter(|hl_range| {\n                    symbol_ranges.iter().any(|sym_range| {\n                        hl_range.start >= sym_range.start && hl_range.end <= sym_range.end\n                    })\n                })\n                .collect::<Vec<Range<usize>>>();\n\n            // limit symbols to only those in our highlight list\n            //\n            // for a search query of `symbol:loud` on this text:\n            //\n            //    const (loud, clear) = audio();\n            //\n            // the symbols returned should be just `loud`, even though `clear`\n            // is also a symbol present in the same snippet.\n            symbols.retain(|sym_range| {\n                highlights.iter().any(|hl_range| {\n                    hl_range.start >= sym_range.range.start.byte\n                        && hl_range.end <= sym_range.range.end.byte\n                })\n            });\n\n            self.expand_many(highlights.into_iter(), &doc.content, &doc.line_end_indices)\n                .map(|loc| loc.reify(&doc.content, &symbols))\n                .collect::<Vec<_>>()\n        } else {\n            let highlights = query.find_iter(&doc.content).map(|m| m.range());\n            self.expand_many(highlights.into_iter(), &doc.content, &doc.line_end_indices)\n                .map(|loc| loc.reify(&doc.content, &[]))\n                .collect::<Vec<_>>()\n        };\n\n        Ok(if snippets.is_empty() {\n            None\n        } else {\n            Some(SnippedFile {\n                relative_path: doc.relative_path.clone(),\n                repo_name: doc.repo_name.clone(),\n                repo_ref: doc.repo_ref.clone(),\n                lang: doc.lang.clone(),\n                snippets,\n            })\n        })\n    }\n\n    fn expand_many<'a>(\n        &'a self,\n        mut highlights: impl Iterator<Item = Range<usize>> + 'a,\n        text: &'a str,\n        line_ends: &'a [u32],\n    ) -> impl Iterator<Item = Location> + 'a {\n        // We store the \"next\" location here, in case we run into an early split down below due to 2\n        // locations not joining together.\n        let mut next = None;\n        std::iter::from_fn(move || {\n            let mut loc = next.take().unwrap_or(Location {\n                byte_range: 0..0,\n                line_range: 0..0,\n                highlights: SmallVec::new(),\n            });\n\n            for highlight in &mut highlights {\n                let next_loc = self.expand(highlight, text, line_ends);\n                if let Err(next_loc) = loc.join(next_loc) {\n                    next = Some(next_loc);\n                    break;\n                }\n            }\n\n            if !loc.highlights.is_empty() {\n                Some(loc)\n            } else {\n                None\n            }\n        })\n    }\n\n    pub fn expand<'a>(\n        &'a self,\n        highlight: Range<usize>,\n        text: &'a str,\n        line_ends: &'a [u32],\n    ) -> Location {\n        let start = text[..highlight.start]\n            .rmatch_indices('\\n')\n            .nth(self.context_before)\n            .map(|(i, _)| i + 1)\n            .unwrap_or(0);\n\n        let end = text[highlight.end..]\n            .match_indices('\\n')\n            .nth(self.context_after)\n            .map(|(i, _)| i + highlight.end)\n            .unwrap_or(text.len());\n\n        let line_end = line_ends\n            .iter()\n            .position(|i| end <= *i as usize)\n            .unwrap_or(line_ends.len());\n\n        let line_start = line_ends\n            .iter()\n            .rev()\n            .position(|i| (*i as usize) < start)\n            .map(|i| line_ends.len() - i)\n            .unwrap_or(0);\n\n        Location {\n            byte_range: start..end,\n            line_range: line_start..line_end,\n            highlights: smallvec![(highlight.start - start)..(highlight.end - start)],\n        }\n    }\n}\n\n#[derive(Serialize)]\npub struct HighlightedString {\n    pub text: String,\n\n    /// Index ranges that are highlighted as matched.\n    pub highlights: SmallVec<[Range<usize>; 2]>,\n}\n\nimpl HighlightedString {\n    /// Create a new highlighted string with no highlights.\n    pub fn new<T: Into<String>>(text: T) -> Self {\n        Self {\n            text: text.into(),\n            highlights: Default::default(),\n        }\n    }\n\n    /// Apply a regex to this string, recording the match ranges, if any.\n    pub fn apply_regex(&mut self, regex: &Regex) {\n        self.highlights\n            .extend(regex.find_iter(&self.text).map(|m| m.range()));\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use pretty_assertions::assert_eq;\n    use regex::Regex;\n\n    impl Snippet {\n        fn line_count(&self) -> usize {\n            self.line_range.end - self.line_range.start\n        }\n    }\n\n    /// Test helper to ensure a string is newline-terminated, and also return an array of newline\n    /// indices.\n    fn with_line_ends(s: &str) -> (&str, Vec<u32>) {\n        assert!(s.ends_with('\\n'));\n        let line_ends = s\n            .match_indices('\\n')\n            .map(|(i, _)| i as u32)\n            .collect::<Vec<_>>();\n        (s, line_ends)\n    }\n\n    #[test]\n    fn simple_snip() {\n        let (text, line_ends) = with_line_ends(\"foobar\\n\");\n        let highlight = 0..3;\n\n        let snipper = Snipper::default();\n\n        assert_eq!(\n            snipper.expand(highlight, text, &line_ends).reify(text, &[]),\n            Snippet {\n                data: \"foobar\".into(),\n                line_range: 0..0,\n                highlights: vec![0..3],\n                symbols: vec![],\n            }\n        );\n    }\n\n    #[test]\n    fn empty_lines() {\n        let (text, line_ends) = with_line_ends(\"\\n\\nfoo\\nbar\\nquux\\n\\n\\n\\n\\n\");\n        let highlight = 6..9;\n        let snipper = Snipper::default().context(1, 1);\n\n        assert_eq!(\n            snipper.expand(highlight, text, &line_ends).reify(text, &[]),\n            Snippet {\n                data: \"foo\\nbar\\nquux\".into(),\n                line_range: 2..4,\n                highlights: vec![4..7],\n                symbols: vec![],\n            }\n        );\n    }\n\n    #[test]\n    fn crlf_line_ends() {\n        let (text, line_ends) = with_line_ends(\"foo\\r\\nbar\\r\\nquux\\n\");\n        let highlight = 5..8;\n        let snipper = Snipper::default().context(1, 1);\n\n        assert_eq!(\n            snipper.expand(highlight, text, &line_ends).reify(text, &[]),\n            Snippet {\n                data: \"foo\\r\\nbar\\r\\nquux\".into(),\n                line_range: 0..2,\n                highlights: vec![5..8],\n                symbols: vec![],\n            }\n        );\n    }\n\n    #[test]\n    fn mixed_line_ends() {\n        let (text, line_ends) = with_line_ends(\"foo\\nbar\\r\\nquux\\n\");\n        let highlight = 4..7;\n        let snipper = Snipper::default().context(1, 1);\n\n        assert_eq!(\n            snipper.expand(highlight, text, &line_ends).reify(text, &[]),\n            Snippet {\n                data: \"foo\\nbar\\r\\nquux\".to_owned(),\n                line_range: 0..2,\n                highlights: vec![4..7],\n                symbols: vec![],\n            }\n        );\n    }\n\n    #[test]\n    fn context_before() {\n        let (text, line_ends) = with_line_ends(\"a\\nfoo\\nbar\\nquux\\nz\\n\");\n        let highlight = 6..9;\n        let snipper = Snipper::default().context(1, 0);\n\n        assert_eq!(\n            snipper.expand(highlight, text, &line_ends).reify(text, &[]),\n            Snippet {\n                data: \"foo\\nbar\".to_owned(),\n                line_range: 1..2,\n                highlights: vec![4..7],\n                symbols: vec![],\n            }\n        );\n    }\n\n    /// Check that `context_before` being larger than the available line count works.\n    #[test]\n    fn context_before_underflow() {\n        let (text, line_ends) = with_line_ends(\"bar\\nquux\\nz\\n\");\n        let highlight = 0..3;\n        let snipper = Snipper::default().context(1, 0);\n\n        assert_eq!(\n            snipper.expand(highlight, text, &line_ends).reify(text, &[]),\n            Snippet {\n                data: \"bar\".to_owned(),\n                line_range: 0..0,\n                highlights: vec![0..3],\n                symbols: vec![],\n            }\n        );\n    }\n\n    #[test]\n    fn context_after() {\n        let (text, line_ends) = with_line_ends(\"a\\nfoo\\nbar\\nquux\\nz\\n\");\n        let highlight = 6..9;\n        let snipper = Snipper::default().context(0, 1);\n\n        assert_eq!(\n            snipper.expand(highlight, text, &line_ends).reify(text, &[]),\n            Snippet {\n                data: \"bar\\nquux\".to_owned(),\n                line_range: 2..3,\n                highlights: vec![0..3],\n                symbols: vec![],\n            }\n        );\n    }\n\n    /// Check that `context_after` being larger than the available line count works.\n    #[test]\n    fn context_after_overflow() {\n        let (text, line_ends) = with_line_ends(\"a\\nfoo\\nbar\\n\");\n        let highlight = 6..9;\n        let snipper = Snipper::default().context(0, 1);\n\n        assert_eq!(\n            snipper.expand(highlight, text, &line_ends).reify(text, &[]),\n            Snippet {\n                data: \"bar\\n\".to_owned(),\n                line_range: 2..3,\n                highlights: vec![0..3],\n                symbols: vec![],\n            }\n        );\n    }\n\n    #[test]\n    fn merge_into_one() {\n        let text = &[\n            r#\"pub const SLICE_FROM_RAW_PARTS: [&str; 4] = [\"core\", \"slice\", \"raw\", \"from_raw_parts\"];\"#,\n            r#\"pub const SLICE_FROM_RAW_PARTS_MUT: [&str; 4] = [\"core\", \"slice\", \"raw\", \"from_raw_parts_mut\"];\"#,\n            r#\"pub const SLICE_GET: [&str; 4] = [\"core\", \"slice\", \"<impl [T]>\", \"get\"];\"#,\n            r#\"pub const SLICE_INTO_VEC: [&str; 4] = [\"alloc\", \"slice\", \"<impl [T]>\", \"into_vec\"];\"#,\n            r#\"pub const SLICE_INTO: [&str; 4] = [\"core\", \"slice\", \"<impl [T]>\", \"iter\"];\"#,\n            r#\"pub const SLICE_ITER: [&str; 4] = [\"core\", \"slice\", \"iter\", \"Iter\"];\"#,\n            \"\"\n        ]\n        .join(\"\\n\");\n\n        let (text, line_ends) = with_line_ends(text);\n        let regex = Regex::new(\"SLICE\").unwrap();\n        let highlights = regex.find_iter(text).map(|m| m.range());\n\n        let snipper = Snipper::default().context(1, 1);\n        let observed = snipper\n            .expand_many(highlights, text, &line_ends)\n            .collect::<Vec<_>>();\n\n        assert_eq!(observed.len(), 1);\n        assert_eq!(observed[0].line_count(), 6);\n    }\n\n    #[test]\n    fn merge_into_two() {\n        let text = &[\n            r#\"pub const SLICE_FROM_RAW_PARTS: [&str; 4] = [\"core\", \"slice\", \"raw\", \"from_raw_parts\"];\"#,\n            r#\"pub const SLICE_FROM_RAW_PARTS_MUT: [&str; 4] = [\"core\", \"slice\", \"raw\", \"from_raw_parts_mut\"];\"#,\n            r#\"pub const GET: [&str; 4] = [\"core\", \"slice\", \"<impl [T]>\", \"get\"];\"#,\n            r#\"pub const VEC: [&str; 4] = [\"alloc\", \"slice\", \"<impl [T]>\", \"into_vec\"];\"#,\n            r#\"pub const INTO: [&str; 4] = [\"core\", \"slice\", \"<impl [T]>\", \"iter\"];\"#,\n            r#\"pub const SLICE_ITER: [&str; 4] = [\"core\", \"slice\", \"iter\", \"Iter\"];\"#,\n            \"\",\n        ]\n        .join(\"\\n\");\n\n        let (text, line_ends) = with_line_ends(text);\n        let regex = Regex::new(\"SLICE\").unwrap();\n        let highlights = regex.find_iter(text).map(|m| m.range());\n\n        let observed = Snipper::default()\n            .context(1, 1)\n            .expand_many(highlights, text, &line_ends)\n            .map(|l| l.reify(text, &[]))\n            .collect::<Vec<_>>();\n\n        assert_eq!(observed.len(), 2);\n        assert_eq!(\n            observed[0].data,\n            [r#\"pub const SLICE_FROM_RAW_PARTS: [&str; 4] = [\"core\", \"slice\", \"raw\", \"from_raw_parts\"];\"#,\n                r#\"pub const SLICE_FROM_RAW_PARTS_MUT: [&str; 4] = [\"core\", \"slice\", \"raw\", \"from_raw_parts_mut\"];\"#,\n                r#\"pub const GET: [&str; 4] = [\"core\", \"slice\", \"<impl [T]>\", \"get\"];\"#].join(\"\\n\")\n        );\n        assert_eq!(observed[0].line_count(), 2);\n        assert_eq!(\n            observed[1].data,\n            [\n                r#\"pub const INTO: [&str; 4] = [\"core\", \"slice\", \"<impl [T]>\", \"iter\"];\"#,\n                r#\"pub const SLICE_ITER: [&str; 4] = [\"core\", \"slice\", \"iter\", \"Iter\"];\"#,\n                \"\"\n            ]\n            .join(\"\\n\")\n        );\n        assert_eq!(observed[1].line_count(), 2);\n    }\n\n    #[test]\n    fn multiline() {\n        let (text, line_ends) = with_line_ends(\n            \"pub const SLICE_FROM_RAW_PARTS\\n\\\n             pub const SLICE_FROM_RAW_PARTS_MUT\\n\\\n             pub const VEC\\n\\\n             pub const INTO\\n\",\n        );\n\n        let regex = Regex::new(\"SLICE\").unwrap();\n        let highlights = regex.find_iter(text).map(|m| m.range());\n\n        let observed = Snipper::default()\n            .context(1, 1)\n            .expand_many(highlights, text, &line_ends)\n            .map(|l| l.reify(text, &[]))\n            .collect::<Vec<_>>();\n\n        assert_eq!(observed.len(), 1);\n        assert_eq!(\n            observed[0].data,\n            \"pub const SLICE_FROM_RAW_PARTS\\npub const SLICE_FROM_RAW_PARTS_MUT\\npub const VEC\",\n        );\n        assert_eq!(observed[0].line_count(), 2);\n    }\n\n    #[test]\n    fn non_ascii() {\n        let (text, line_ends) = with_line_ends(\"pub ようこそ SLICE_FROM_RAW_PARTS\\npub ようこそ SLICE_FROM_RAW_ようこそ_MUT\\npub const VECようこそ\\npub ようこそ INTO\\n\");\n        let regex = Regex::new(\"SLICE\").unwrap();\n        let highlights = regex.find_iter(text).map(|m| m.range());\n\n        let observed = Snipper::default()\n            .context(1, 1)\n            .expand_many(highlights, text, &line_ends)\n            .map(|l| l.reify(text, &[]))\n            .collect::<Vec<_>>();\n\n        assert_eq!(observed.len(), 1);\n        assert_eq!(observed[0].line_count(), 2);\n        assert_eq!(\n            observed[0].data,\n            \"pub ようこそ SLICE_FROM_RAW_PARTS\\npub ようこそ SLICE_FROM_RAW_ようこそ_MUT\\npub const VECようこそ\"\n        )\n    }\n\n    #[test]\n    fn avoids_empty_snippets() {\n        let (text, line_end_indices) = with_line_ends(\"function foo() {}\\n\");\n        let doc = indexes::reader::ContentDocument {\n            content: text.into(),\n            line_end_indices,\n            ..Default::default()\n        };\n        assert_eq!(None, Snipper::default().all_for_doc(\"bar\", &doc).unwrap());\n        assert!(Snipper::default()\n            .all_for_doc(\"foo\", &doc)\n            .unwrap()\n            .is_some());\n    }\n\n    #[test]\n    fn test_highlighted_string() {\n        let mut s = HighlightedString::new(\"foo bar quux\");\n\n        s.apply_regex(&Regex::new(\"foo\").unwrap());\n        s.apply_regex(&Regex::new(\"b.r.\").unwrap());\n        s.apply_regex(&Regex::new(\"ux$\").unwrap());\n\n        assert_eq!(s.text, \"foo bar quux\");\n        assert_eq!(s.highlights.to_vec(), &[0..3, 4..8, 10..12]);\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/state.rs",
    "content": "use crate::{\n    remotes::gather_repo_roots,\n    repo::{RepoError, RepoRef, Repository},\n};\nuse anyhow::Result;\nuse clap::Args;\nuse rand::Rng;\nuse relative_path::RelativePath;\nuse serde::{de::DeserializeOwned, Deserialize, Serialize};\nuse std::{\n    ops::Deref,\n    path::{Path, PathBuf},\n    sync::Arc,\n};\nuse tracing::debug;\n\ninclude!(concat!(env!(\"OUT_DIR\"), \"/schema_version.rs\"));\n\npub(crate) type RepositoryPool = Arc<scc::HashMap<RepoRef, Repository>>;\n\n#[derive(Serialize, Deserialize, Args, Debug, Clone, Default, PartialEq)]\n#[serde(rename_all = \"snake_case\")]\npub struct StateSource {\n    /// Directory where repositories are located\n    #[clap(short, long = \"source-dir\")]\n    #[serde(default)]\n    directory: Option<PathBuf>,\n\n    /// State file for all repositories\n    #[clap(short, long)]\n    #[serde(default)]\n    state_file: Option<PathBuf>,\n\n    /// Version of the current schema\n    #[clap(short, long)]\n    #[serde(default)]\n    version_file: Option<PathBuf>,\n\n    /// The root directory that contains all other paths in this structure\n    /// (unless otherwise configured)\n    #[serde(skip)]\n    #[clap(skip)]\n    root_dir: PathBuf,\n}\n\n/// Unified wrapper to persist state in the central state-store.\n/// Every model is stored in its own file as a pretty-printed json.\npub struct PersistedState<T> {\n    path: PathBuf,\n    state: Arc<T>,\n}\n\nimpl<T: Serialize + DeserializeOwned + Default + Send + Sync> PersistedState<T> {\n    fn load_or_default(name: &'static str, source: &StateSource) -> Result<Self> {\n        let path = source.directory().join(name).with_extension(\"json\");\n        Ok(Self {\n            state: Arc::new(read_file_or_default(&path)?),\n            path,\n        })\n    }\n\n    fn load_or(name: &'static str, source: &StateSource, val: T) -> Self {\n        let path = source.directory().join(name).with_extension(\"json\");\n        let new = Self {\n            state: Arc::new(read_file(&path).unwrap_or(val)),\n            path,\n        };\n\n        new.store().unwrap();\n        new\n    }\n\n    pub fn store(&self) -> Result<()> {\n        Ok(pretty_write_file(&self.path, self.state.as_ref())?)\n    }\n}\n\nimpl<T> Deref for PersistedState<T> {\n    type Target = T;\n\n    fn deref(&self) -> &Self::Target {\n        &self.state\n    }\n}\n\nimpl<T> Clone for PersistedState<T> {\n    fn clone(&self) -> Self {\n        Self {\n            path: self.path.clone(),\n            state: self.state.clone(),\n        }\n    }\n}\n\nimpl StateSource {\n    pub(crate) fn set_default_dir(&mut self, dir: &Path) {\n        std::fs::create_dir_all(dir).expect(\"the index folder can't be created\");\n\n        self.state_file\n            .get_or_insert_with(|| dir.join(\"repo_state.json\"));\n\n        self.version_file\n            .get_or_insert_with(|| dir.join(\"version.json\"));\n\n        self.directory.get_or_insert_with(|| {\n            let target = dir.join(\"local_cache\");\n            std::fs::create_dir_all(&target).unwrap();\n\n            target\n        });\n\n        self.root_dir = dir.to_owned();\n    }\n\n    pub(crate) fn exists(&self, path: &(impl AsRef<Path> + ?Sized)) -> bool {\n        self.root_dir.join(path.as_ref()).exists()\n    }\n\n    pub(crate) fn load_or_default<T: Serialize + DeserializeOwned + Default + Send + Sync>(\n        &self,\n        name: &'static str,\n    ) -> Result<PersistedState<T>> {\n        PersistedState::load_or_default(name, self)\n    }\n\n    pub(crate) fn load_state_or<T: Serialize + DeserializeOwned + Default + Send + Sync>(\n        &self,\n        name: &'static str,\n        val: impl Into<T>,\n    ) -> Result<PersistedState<T>> {\n        let val = PersistedState::load_or(name, self, val.into());\n        val.store()?;\n        Ok(val)\n    }\n\n    pub(crate) fn repo_dir(&self) -> Option<PathBuf> {\n        self.directory.clone()\n    }\n\n    pub fn directory(&self) -> PathBuf {\n        let dir = self.directory.as_deref().unwrap();\n        get_relative_path(dir, std::env::current_dir().unwrap())\n    }\n\n    pub(crate) fn repo_path_for_name(&self, name: &str) -> PathBuf {\n        self.directory.as_ref().unwrap().join(name)\n    }\n\n    pub(crate) fn initialize_pool(&self) -> Result<RepositoryPool, RepoError> {\n        use std::fs::canonicalize;\n\n        match (self.directory.as_ref(), self.state_file.as_ref()) {\n            // Load RepositoryPool from path\n            (None, Some(path)) => read_file_or_default(path).map(Arc::new),\n\n            // Initialize RepositoryPool from repos under `root`\n            (Some(root), None) => {\n                let out = scc::HashMap::default();\n                for reporef in gather_repo_roots(root, None) {\n                    let repo = Repository::local_from(&reporef);\n                    _ = out.insert(reporef, repo);\n                }\n\n                let pool = Arc::new(out);\n                self.save_pool(pool.clone())?;\n                Ok(pool)\n            }\n            // Update RepositoryPool with repos under `root`\n            (Some(root), Some(path)) => {\n                // Load RepositoryPool from path\n                let state: RepositoryPool = Arc::new(read_file_or_default(path)?);\n\n                let current_repos = gather_repo_roots(root, None);\n                let root = canonicalize(root)?;\n\n                // mark repositories from the index which are no longer present\n                state.for_each(|k, repo| {\n                    if let Some(path) = k.local_path() {\n                        // Clippy suggestion causes the code to break, revisit after 1.66\n                        if path.starts_with(&root) && !current_repos.contains(k) {\n                            debug!(reporef=%k, \"repo scheduled to be removed;\");\n                            repo.mark_removed();\n                        }\n                    }\n\n                    // in case the app terminated during indexing, make sure to re-queue it\n                    if !repo.sync_status.indexable() {\n                        repo.mark_queued();\n                    }\n                });\n\n                // then add anything new that's appeared\n                let mut per_path = std::collections::HashMap::new();\n                state.scan(|k, v| {\n                    per_path.insert(v.disk_path.to_string_lossy().to_string(), k.clone());\n                });\n\n                for reporef in current_repos {\n                    // skip all paths that are already in the index,\n                    // bearing in mind they may not be local repos\n                    if per_path.contains_key(reporef.name()) {\n                        debug!(%reporef, \"repo has already been initialized;\");\n                        continue;\n                    }\n\n                    state\n                        .entry(reporef.to_owned())\n                        .or_insert_with(|| Repository::local_from(&reporef));\n                }\n\n                self.save_pool(state.clone())?;\n                Ok(state)\n            }\n            (None, None) => Err(RepoError::NoSourceGiven),\n        }\n    }\n\n    pub fn save_pool(&self, pool: RepositoryPool) -> Result<(), RepoError> {\n        match self.state_file {\n            None => Err(RepoError::NoSourceGiven),\n            Some(ref path) => pretty_write_file(path, pool.as_ref()),\n        }\n    }\n\n    pub fn index_version_mismatch(&self) -> bool {\n        let current: String = read_file_or_default(self.version_file.as_ref().unwrap()).unwrap();\n\n        !current.is_empty() && current != SCHEMA_VERSION\n    }\n\n    pub fn save_index_version(&self) -> Result<(), RepoError> {\n        pretty_write_file(self.version_file.as_ref().unwrap(), SCHEMA_VERSION)\n    }\n}\n\npub fn pretty_write_file<T: Serialize + ?Sized>(\n    path: impl AsRef<Path>,\n    val: &T,\n) -> Result<(), RepoError> {\n    let (tmpfile, file) = {\n        let mut tries = 0;\n        const MAX_TRIES: u8 = 10;\n\n        loop {\n            let tmpfile = path\n                .as_ref()\n                .with_extension(format!(\"new.{}\", rand::thread_rng().gen_range(0..=99999)));\n\n            let file = std::fs::File::options()\n                .write(true)\n                .create_new(true)\n                .open(&tmpfile);\n\n            match file {\n                Ok(f) => break (tmpfile, f),\n                Err(e) => {\n                    if tries == MAX_TRIES {\n                        // this will always be an error\n                        // would have broken just before\n                        return Err(e.into());\n                    }\n\n                    tries += 1;\n                }\n            }\n        }\n    };\n\n    serde_json::to_writer_pretty(file, val)?;\n    std::fs::rename(tmpfile, path)?;\n\n    Ok(())\n}\n\npub fn read_file<T: Default + DeserializeOwned>(path: &Path) -> Result<T, RepoError> {\n    let file = std::fs::File::open(path)?;\n    Ok(serde_json::from_reader::<_, T>(file)?)\n}\n\npub fn read_file_or_default<T: Default + DeserializeOwned>(path: &Path) -> Result<T, RepoError> {\n    if !path.exists() {\n        return Ok(Default::default());\n    }\n\n    let file = std::fs::File::open(path)?;\n    Ok(serde_json::from_reader::<_, T>(file)?)\n}\n\npub fn get_relative_path<P>(path: &Path, base: P) -> PathBuf\nwhere\n    P: AsRef<Path>,\n{\n    RelativePath::from_path(path)\n        .map(|rp| rp.to_logical_path(base))\n        .unwrap_or_else(|_| path.to_owned())\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use tempdir::TempDir;\n\n    #[test]\n    fn test_repos_in() {\n        let tmpdir = TempDir::new(\"test-find-repos\").unwrap();\n        let path = tmpdir.path();\n\n        let repos = [\n            \"bloopai/enterprise-search\",\n            \"bloopai/query-parser\",\n            \"bloopai/foo-repo\",\n            \"bloopai/foo-repo/bar-submodule\",\n        ];\n\n        let files = [\n            \"foo/README.md\",\n            \"bar.txt\",\n            \"bloopai/enterprise-search/src/main.rs\",\n            \"bloopai/query-parser/target/release/libenterprise_search.so\",\n        ];\n\n        for repo in repos {\n            std::fs::create_dir_all(path.join(repo).join(\".git\")).unwrap();\n        }\n\n        for file in files {\n            let path = path.join(file);\n            std::fs::create_dir_all(path.parent().unwrap()).unwrap();\n            std::fs::write(path, \"\").unwrap();\n        }\n\n        let repo_pool = StateSource {\n            directory: Some(path.to_path_buf()),\n            state_file: Some(path.join(\"state.json\")),\n            version_file: None,\n            root_dir: Default::default(),\n        }\n        .initialize_pool()\n        .unwrap();\n\n        let mut found_repos = vec![];\n        repo_pool.scan(|_k, repo| found_repos.push(repo.disk_path.to_str().unwrap().to_string()));\n        found_repos.sort();\n\n        let repo = |subdir| {\n            crate::canonicalize(path.join(subdir))\n                .unwrap()\n                .to_str()\n                .unwrap()\n                .to_owned()\n        };\n        let mut expected_repos = vec![\n            repo(\"bloopai/enterprise-search\"),\n            repo(\"bloopai/query-parser\"),\n            repo(\"bloopai/foo-repo\"),\n            repo(\"bloopai/foo-repo/bar-submodule\"),\n        ];\n        expected_repos.sort();\n\n        assert_eq!(found_repos, expected_repos);\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/symbol.rs",
    "content": "use crate::{intelligence::ScopeGraph, text_range::TextRange};\n\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub struct Symbol {\n    pub kind: String,\n    pub range: TextRange,\n}\n\n/// Collection of symbol locations for *single* file\n#[derive(Default, Debug, Clone, Deserialize, Serialize)]\n#[non_exhaustive]\npub enum SymbolLocations {\n    /// tree-sitter powered symbol-locations (and more!)\n    TreeSitter(ScopeGraph),\n\n    /// no symbol-locations for this file\n    #[default]\n    Empty,\n}\n\nimpl SymbolLocations {\n    pub fn list(&self) -> Vec<Symbol> {\n        match self {\n            Self::TreeSitter(graph) => graph.symbols(),\n            Self::Empty => Vec::new(),\n        }\n    }\n\n    pub fn scope_graph(&self) -> Option<&ScopeGraph> {\n        match self {\n            Self::TreeSitter(graph) => Some(graph),\n            Self::Empty => None,\n        }\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/text_range.rs",
    "content": "use std::cmp::{Ord, Ordering};\n\nuse serde::{Deserialize, Serialize};\n\n/// A singular position in a text document\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]\npub struct Point {\n    /// The byte index\n    pub byte: usize,\n\n    /// 0-indexed line number\n    pub line: usize,\n\n    /// Position within the line\n    pub column: usize,\n}\n\nimpl PartialOrd for Point {\n    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl Ord for Point {\n    fn cmp(&self, other: &Self) -> Ordering {\n        self.byte.cmp(&other.byte)\n    }\n}\n\nimpl Point {\n    pub fn new(byte: usize, line: usize, column: usize) -> Self {\n        Self { byte, line, column }\n    }\n\n    pub fn from_byte(byte: usize, line_end_indices: &[u32]) -> Self {\n        let line = line_end_indices\n            .iter()\n            .position(|&line_end_byte| (line_end_byte as usize) > byte)\n            .unwrap_or(0);\n\n        let column = line\n            .checked_sub(1)\n            .and_then(|idx| line_end_indices.get(idx))\n            .map(|&prev_line_end| byte.saturating_sub(prev_line_end as usize))\n            .unwrap_or(byte);\n\n        Self::new(byte, line, column)\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]\npub struct TextRange {\n    pub start: Point,\n    pub end: Point,\n}\n\nimpl PartialOrd for TextRange {\n    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl Ord for TextRange {\n    fn cmp(&self, other: &Self) -> Ordering {\n        let compare_start_byte = self.start.byte.cmp(&other.start.byte);\n        let compare_size = self.size().cmp(&other.size());\n\n        compare_start_byte.then(compare_size)\n    }\n}\n\nimpl TextRange {\n    pub fn new(start: Point, end: Point) -> Self {\n        assert!(start <= end);\n        Self { start, end }\n    }\n\n    pub fn contains(&self, other: &TextRange) -> bool {\n        // (self.start ... [other.start ... other.end] ... self.end)\n        self.start <= other.start && other.end <= self.end\n    }\n\n    #[allow(unused)]\n    pub fn contains_strict(&self, other: TextRange) -> bool {\n        // (self.start ... (other.start ... other.end) ... self.end)\n        self.start < other.start && other.end <= self.end\n    }\n\n    pub fn size(&self) -> usize {\n        self.end.byte.saturating_sub(self.start.byte)\n    }\n\n    pub fn from_byte_range(range: std::ops::Range<usize>, line_end_indices: &[u32]) -> Self {\n        let start = Point::from_byte(range.start, line_end_indices);\n        let end = Point::from_byte(range.end, line_end_indices);\n        Self::new(start, end)\n    }\n}\n\nimpl From<tree_sitter::Range> for TextRange {\n    fn from(r: tree_sitter::Range) -> Self {\n        Self {\n            start: Point {\n                byte: r.start_byte,\n                line: r.start_point.row,\n                column: r.start_point.column,\n            },\n            end: Point {\n                byte: r.end_byte,\n                line: r.end_point.row,\n                column: r.end_point.column,\n            },\n        }\n    }\n}\n\nimpl From<TextRange> for std::ops::Range<usize> {\n    fn from(r: TextRange) -> std::ops::Range<usize> {\n        r.start.byte..r.end.byte\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/user.rs",
    "content": "use serde::{Deserialize, Serialize};\n\n#[derive(Serialize, Deserialize, Clone, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum PromptGuideState {\n    Dismissed,\n    Active,\n}\n\n#[derive(Serialize, Deserialize, Clone, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub struct UserProfile {\n    pub username: Option<String>,\n    prompt_guide: PromptGuideState,\n    #[serde(default = \"default_is_tutorial_finished\")]\n    is_tutorial_finished: bool,\n}\n\nimpl Default for UserProfile {\n    fn default() -> Self {\n        UserProfile {\n            username: None,\n            prompt_guide: PromptGuideState::Active,\n            is_tutorial_finished: default_is_tutorial_finished(),\n        }\n    }\n}\n\nfn default_is_tutorial_finished() -> bool {\n    false\n}\n"
  },
  {
    "path": "server/bleep/src/webserver/answer.rs",
    "content": "use std::{panic::AssertUnwindSafe, time::Duration};\n\nuse anyhow::{anyhow, Context, Result};\nuse axum::{\n    extract::{Path, Query},\n    response::{\n        sse::{self, Sse},\n        IntoResponse,\n    },\n    Extension,\n};\nuse futures::{future::Either, stream, StreamExt};\nuse tracing::{debug, error, info, warn};\n\nuse super::middleware::User;\nuse crate::{\n    agent::{\n        self,\n        exchange::{CodeChunk, Exchange, FocusedChunk, RepoPath},\n        Action, Agent, ExchangeState,\n    },\n    db::QueryLog,\n    query::parser::{self, Literal},\n    repo::RepoRef,\n    webserver::conversation::Conversation,\n    Application,\n};\n\nconst TIMEOUT_SECS: u64 = 60;\n\n#[derive(Clone, Debug, serde::Deserialize)]\npub struct Vote {\n    pub feedback: VoteFeedback,\n    pub thread_id: uuid::Uuid,\n    pub query_id: uuid::Uuid,\n    pub repo_ref: Option<RepoRef>,\n}\n\n#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]\n#[serde(rename_all = \"lowercase\", tag = \"type\")]\npub enum VoteFeedback {\n    Positive,\n    Negative { feedback: String },\n}\n\n#[derive(Clone, Debug, serde::Deserialize)]\npub struct Answer {\n    pub q: String,\n    #[serde(default = \"default_answer_model\")]\n    pub answer_model: agent::model::LLMModel,\n    #[serde(default = \"default_agent_model\")]\n    pub agent_model: agent::model::LLMModel,\n    /// Optional id of the parent of the exchange to overwrite\n    /// If this UUID is nil, then overwrite the first exchange in the thread\n    pub parent_exchange_id: Option<uuid::Uuid>,\n    pub conversation_id: Option<i64>,\n}\n\nfn default_answer_model() -> agent::model::LLMModel {\n    agent::model::GPT_4_TURBO_24K\n}\n\nfn default_agent_model() -> agent::model::LLMModel {\n    agent::model::GPT_4\n}\n\npub(super) async fn answer(\n    Query(params): Query<Answer>,\n    Extension(app): Extension<Application>,\n    Extension(user): Extension<User>,\n    Path(project_id): Path<i64>,\n) -> super::Result<impl IntoResponse> {\n    info!(?params.q, \"handling /answer query\");\n    let query_id = uuid::Uuid::new_v4();\n\n    let user_id = user.username().ok_or_else(super::no_user_id)?;\n\n    let mut conversation = match params.conversation_id {\n        Some(conversation_id) => {\n            Conversation::load(&app.sql, user_id, project_id, conversation_id).await?\n        }\n        None => Conversation::new(project_id),\n    };\n\n    let Answer {\n        parent_exchange_id,\n        q,\n        ..\n    } = &params;\n\n    if let Some(parent_exchange_id) = parent_exchange_id {\n        let truncate_from_index = if parent_exchange_id.is_nil() {\n            0\n        } else {\n            conversation\n                .exchanges\n                .iter()\n                .position(|e| e.id == *parent_exchange_id)\n                .ok_or_else(|| super::Error::user(\"parent query id not found in exchanges\"))?\n                + 1\n        };\n\n        conversation.exchanges.truncate(truncate_from_index);\n    }\n\n    let query = parser::parse_nl(q).context(\"parse error\")?.into_owned();\n    let query_target = query\n        .target\n        .as_ref()\n        .context(\"query was empty\")?\n        .as_plain()\n        .context(\"user query was not plain text\")?\n        .clone()\n        .into_owned();\n\n    debug!(?query_target, \"parsed query target\");\n\n    let action = Action::Query(query_target);\n    conversation.exchanges.push(Exchange::new(query_id, query));\n\n    AgentExecutor {\n        params: params.clone(),\n        app: app.clone(),\n        user: user.clone(),\n        query_id,\n        project_id,\n        conversation,\n        action,\n    }\n    .execute()\n    .await\n}\n\n#[derive(Clone)]\nstruct AgentExecutor {\n    params: Answer,\n    app: Application,\n    user: User,\n    query_id: uuid::Uuid,\n    project_id: i64,\n    conversation: Conversation,\n    action: Action,\n}\n\n#[allow(clippy::large_enum_variant)]\n#[derive(serde::Serialize)]\nenum AnswerEvent {\n    ChatEvent(Exchange),\n    StreamEnd(StreamEnd),\n}\n\n#[derive(serde::Serialize)]\nstruct StreamEnd {\n    thread_id: String,\n    query_id: uuid::Uuid,\n    conversation_id: i64,\n}\n\ntype SseDynStream<T> = Sse<std::pin::Pin<Box<dyn tokio_stream::Stream<Item = T> + Send>>>;\n\nimpl AgentExecutor {\n    /// Like `try_execute`, but additionally logs errors in our analytics.\n    async fn execute(&mut self) -> super::Result<SseDynStream<Result<sse::Event>>> {\n        let response = self.try_execute().await;\n\n        if let Err(err) = response.as_ref() {\n            error!(?err, \"failed to handle /answer query\");\n        }\n\n        response\n    }\n\n    async fn try_execute(&mut self) -> super::Result<SseDynStream<Result<sse::Event>>> {\n        QueryLog::new(&self.app.sql).insert(&self.params.q).await?;\n\n        let username = self.user.username().ok_or_else(super::no_user_id)?;\n\n        let repo_refs = sqlx::query! {\n            \"SELECT repo_ref\n            FROM project_repos\n            WHERE project_id = $1 AND EXISTS (\n                SELECT id\n                FROM projects\n                WHERE id = $1 AND user_id = $2\n            )\",\n            self.project_id,\n            username,\n        }\n        .fetch_all(&*self.app.sql)\n        .await?\n        .into_iter()\n        .filter_map(|row| row.repo_ref.parse().ok())\n        .collect();\n\n        let llm_gateway = self\n            .user\n            .llm_gateway(&self.app)\n            .await?\n            .temperature(0.0)\n            .model(self.params.agent_model.model_name);\n\n        // let project: Project = serde_json::from_str(&self.params.project).unwrap();\n        let Answer {\n            agent_model,\n            answer_model,\n            ..\n        } = self.params.clone();\n\n        let (exchange_tx, exchange_rx) = tokio::sync::mpsc::channel(10);\n\n        let mut action = self.action.clone();\n        let mut agent = Agent {\n            app: self.app.clone(),\n            conversation: self.conversation.clone(),\n            exchange_tx,\n            llm_gateway,\n            user: self.user.clone(),\n            query_id: self.query_id,\n            repo_refs,\n            exchange_state: ExchangeState::Pending,\n            answer_model,\n            agent_model,\n        };\n\n        let stream = async_stream::try_stream! {\n            let mut exchange_rx = tokio_stream::wrappers::ReceiverStream::new(exchange_rx);\n\n            let result = 'outer: loop {\n                // The main loop. Here, we create two streams that operate simultaneously; the update\n                // stream, which sends updates back to the HTTP event stream response, and the action\n                // stream, which returns a single item when there is a new action available to execute.\n                // Both of these operate together, and we repeat the process for every new action.\n\n                use futures::future::FutureExt;\n\n                let left_stream = (&mut exchange_rx).map(Either::Left);\n                let right_stream = agent\n                    .step(action)\n                    .into_stream()\n                    .map(Either::Right);\n\n                let timeout = Duration::from_secs(TIMEOUT_SECS);\n\n                let mut next = None;\n                for await item in tokio_stream::StreamExt::timeout(\n                    stream::select(left_stream, right_stream),\n                    timeout,\n                ) {\n                    match item {\n                        Ok(Either::Left(exchange)) => yield AnswerEvent::ChatEvent(exchange.compressed()),\n                        Ok(Either::Right(next_action)) => match next_action {\n                            Ok(n) => break next = n,\n                            Err(e) => break 'outer Err(agent::Error::Processing(e)),\n                        },\n                        Err(_) => break 'outer Err(agent::Error::Timeout(timeout)),\n                    }\n                }\n\n                // NB: Sending updates after all other `await` points in the final `step` call will\n                // likely not return a pending future due to the internal receiver queue. So, the call\n                // stack usually continues onwards, ultimately resulting in a `Poll::Ready`, backing out\n                // of the above loop without ever processing the final message. Here, we empty the\n                // queue.\n                while let Some(Some(exchange)) = exchange_rx.next().now_or_never() {\n                    yield AnswerEvent::ChatEvent(exchange.compressed());\n                }\n\n                match next {\n                    Some(a) => action = a,\n                    None => break Ok(()),\n                }\n            };\n\n            agent.complete(result.is_ok());\n\n            match result {\n                Ok(_) => {\n                    let conversation_id = agent.conversation.store(\n                        &agent.app.sql,\n                        agent.user.username().context(\"agent failed to get user ID\")?,\n                    )\n                    .await?;\n\n                    let final_message = StreamEnd {\n                        thread_id: agent.conversation.thread_id.to_string(),\n                        query_id: agent.query_id,\n                        conversation_id,\n                    };\n\n                    yield AnswerEvent::StreamEnd(final_message);\n                }\n                Err(agent::Error::Timeout(duration)) => {\n                    warn!(\"Timeout reached.\");\n                    Err(anyhow!(\"reached timeout of {duration:?}\"))?;\n                }\n                Err(agent::Error::Processing(e)) => {\n                    Err(e)?;\n                }\n            };\n        };\n\n        // We know the stream is unwind safe as it doesn't use synchronization primitives like locks.\n        let stream = AssertUnwindSafe(stream)\n            .catch_unwind()\n            .map(|res| res.unwrap_or_else(|_| Err(anyhow!(\"stream panicked\"))))\n            .map(|ex: Result<AnswerEvent>| {\n                sse::Event::default()\n                    .json_data(ex.map_err(|e| format!(\"{e:?}\")))\n                    .map_err(anyhow::Error::new)\n            });\n\n        Ok(Sse::new(Box::pin(stream)))\n    }\n}\n\n#[derive(serde::Deserialize)]\npub struct Explain {\n    pub relative_path: String,\n    pub line_start: usize,\n    pub line_end: usize,\n    pub branch: Option<String>,\n    pub repo_ref: RepoRef,\n    pub conversation_id: Option<i64>,\n    pub q: String,\n}\n\npub async fn explain(\n    Query(params): Query<Explain>,\n    Extension(app): Extension<Application>,\n    Extension(user): Extension<User>,\n    Path(project_id): Path<i64>,\n) -> super::Result<impl IntoResponse> {\n    let query_id = uuid::Uuid::new_v4();\n    let repo_path = RepoPath {\n        repo: params.repo_ref.clone(),\n        path: params.relative_path.clone(),\n    };\n\n    // We synthesize a virtual `/answer` request.\n    let virtual_req = Answer {\n        q: params.q,\n        conversation_id: params.conversation_id,\n        parent_exchange_id: None,\n        answer_model: agent::model::GPT_4_TURBO_24K,\n        agent_model: agent::model::GPT_4,\n    };\n\n    let mut query = parser::parse_nl(&virtual_req.q)\n        .context(\"failed to parse virtual answer query\")?\n        .into_owned();\n\n    if let Some(branch) = params.branch {\n        query.branch.push(Literal::Plain(branch.into()));\n    }\n\n    let file_content = app\n        .indexes\n        .file\n        // this unwrap is ok, because we instantiate the `virtual_req` above\n        .by_path(&params.repo_ref, &params.relative_path, None)\n        .await\n        .context(\"file retrieval failed\")?\n        .context(\"did not find requested file\")?\n        .content;\n\n    let snippet = file_content\n        .lines()\n        .skip(params.line_start)\n        .take(params.line_end - params.line_start)\n        .collect::<Vec<_>>()\n        .join(\"\\n\");\n\n    let mut exchange = Exchange::new(query_id, query);\n    exchange.focused_chunk = Some(FocusedChunk {\n        repo_path: repo_path.clone(),\n        start_line: params.line_start,\n        end_line: params.line_end,\n    });\n\n    exchange.paths.push(repo_path.clone());\n    exchange.code_chunks.push(CodeChunk {\n        repo_path: repo_path.clone(),\n        alias: 0,\n        start_line: params.line_start,\n        end_line: params.line_end,\n        snippet,\n        start_byte: None,\n        end_byte: None,\n    });\n\n    let mut conversation = Conversation::new(project_id);\n    conversation.exchanges.push(exchange);\n\n    let action = Action::Answer { paths: vec![0] };\n\n    AgentExecutor {\n        params: virtual_req,\n        app,\n        user,\n        query_id,\n        project_id,\n        conversation,\n        action,\n    }\n    .execute()\n    .await\n}\n"
  },
  {
    "path": "server/bleep/src/webserver/autocomplete.rs",
    "content": "use std::collections::HashMap;\n\nuse super::prelude::*;\nuse crate::{\n    indexes::reader::{ContentReader, FileReader, RepoReader},\n    query::{\n        execute::{ApiQuery, ExecuteQuery, QueryResult},\n        languages, parser,\n        parser::{Literal, Target},\n    },\n    Application,\n};\n\nuse axum::{\n    extract::{Path, Query},\n    response::IntoResponse as IntoAxumResponse,\n    Extension,\n};\nuse futures::{stream, StreamExt, TryStreamExt};\nuse serde::Serialize;\n\nfn default_true() -> bool {\n    true\n}\n\n#[derive(Deserialize)]\npub struct AutocompleteParams {\n    #[serde(default = \"default_true\")]\n    content: bool,\n    #[serde(default = \"default_true\")]\n    file: bool,\n    #[serde(default = \"default_true\")]\n    repo: bool,\n    #[serde(default = \"default_true\")]\n    lang: bool,\n}\n\npub(super) async fn handle(\n    Query(mut api_params): Query<ApiQuery>,\n    Query(ac_params): Query<AutocompleteParams>,\n    Path(project_id): Path<i64>,\n    Extension(app): Extension<Application>,\n) -> Result<impl IntoAxumResponse> {\n    // Override page_size and set to low value\n    api_params.page = 0;\n\n    api_params.project_id = project_id;\n\n    let mut partial_lang = None;\n    let mut has_target = false;\n\n    let queries = parser::parse(&api_params.q)\n        .map_err(Error::user)?\n        .into_iter()\n        .map(|q| parser::Query {\n            case_sensitive: Some(true),\n            ..q\n        })\n        .map(|mut q| {\n            let keywords = &[\"lang:\", \"path:\", \"repo:\"];\n\n            if ac_params.content {\n                if let Some(ref t) = q.target {\n                    if !keywords.iter().any(|k| k == t.literal().as_ref()) {\n                        has_target = true;\n                    }\n                }\n\n                let target = q\n                    .target\n                    .get_or_insert_with(|| Target::Content(Literal::Regex(\".*\".into())));\n\n                for keyword in keywords {\n                    if let Some(pos) = target.literal().find(keyword) {\n                        let new = format!(\n                            \"{}{}\",\n                            &target.literal()[..pos],\n                            &target.literal()[pos + keyword.len()..]\n                        );\n\n                        *target = Target::Content(Literal::Regex(if new.is_empty() {\n                            \".*\".into()\n                        } else {\n                            new.into()\n                        }));\n                    }\n                }\n            } else {\n                q.target = None;\n            }\n\n            if let Some(lang) = q.lang.as_ref() {\n                partial_lang = q.lang.as_ref().map(|l| l.to_lowercase());\n                if languages::list()\n                    .filter(|l| l.to_lowercase() == lang.as_ref().to_lowercase())\n                    .count()\n                    == 0\n                {\n                    q.lang = None;\n                }\n            }\n\n            if q.path.is_none() && ac_params.file {\n                q.path = Some(Literal::Regex(\".*\".into()));\n            }\n\n            q\n        })\n        .collect::<Vec<_>>();\n    let mut autocomplete_results = vec![];\n\n    // Only execute prefix search on flag names if there is a non-regex content target.\n    // Always matches against the last query.\n    //\n    //      `la repo:bloop or sy` -> search with prefix `sy`\n    //      `repo:bloop re path:src` -> search with prefix `re`\n    if let Some(Target::Content(Literal::Plain(q))) = queries.last().unwrap().target.clone() {\n        autocomplete_results.append(\n            &mut complete_flag(&q)\n                .map(|f| QueryResult::Flag(f.to_string()))\n                .collect(),\n        );\n    }\n\n    // NB: This restricts queries in a repo-specific way. This might need to be generalized if\n    // we still use the other autocomplete fields.\n    let repo_queries = api_params\n        .restrict_repo_queries(queries.clone(), &app)\n        .await?;\n\n    let restricted_queries = api_params.restrict_queries(queries.clone(), &app).await?;\n\n    let mut engines = vec![];\n    if ac_params.content {\n        engines.push(ContentReader.execute(&app.indexes.file, &restricted_queries, &api_params));\n    }\n\n    if ac_params.repo {\n        engines.push(RepoReader.execute(&app.indexes.repo, &repo_queries, &api_params));\n    }\n\n    if ac_params.file {\n        engines.push(FileReader.execute(&app.indexes.file, &restricted_queries, &api_params));\n    }\n\n    let (langs, list) = stream::iter(engines)\n        // Buffer several readers at the same time. The exact number is not important; this is\n        // simply an upper bound.\n        .buffered(10)\n        .try_fold(\n            (HashMap::<String, usize>::new(), Vec::new()),\n            |(mut langs, mut list), e| async {\n                for (lang, count) in e.stats.lang {\n                    // The exact number here isn't relevant, and\n                    // this may be off.\n                    //\n                    // We're trying to scale the results compared\n                    // to each other which means this will still\n                    // serve the purpose for ranking.\n                    *langs.entry(lang).or_default() += count;\n                }\n                list.extend(e.data.into_iter());\n                Ok((langs, list))\n            },\n        )\n        .await\n        .map_err(Error::internal)?;\n\n    autocomplete_results.extend(\n        list.into_iter()\n            .filter(|q| has_target || !matches!(q, QueryResult::Snippets(_))),\n    );\n\n    if ac_params.lang && api_params.q.contains(\"lang:\") {\n        let mut ranked_langs = langs.into_iter().collect::<Vec<_>>();\n        if let Some(partial) = partial_lang {\n            ranked_langs.retain(|(l, _)| l.to_lowercase().contains(&partial));\n\n            if ranked_langs.is_empty() {\n                ranked_langs.extend(\n                    languages::list()\n                        .filter(|l| l.to_lowercase().starts_with(&partial))\n                        .map(|l| (l.to_lowercase(), 0)),\n                );\n\n                ranked_langs.sort_by(|(a, _), (b, _)| a.len().cmp(&b.len()));\n                ranked_langs.truncate(5);\n            }\n        }\n\n        ranked_langs.sort_by(|(_, a_count), (_, b_count)| b_count.cmp(a_count));\n        ranked_langs.truncate(5);\n\n        autocomplete_results.extend(ranked_langs.into_iter().map(|(l, _)| QueryResult::Lang(l)));\n    }\n\n    Ok(json(AutocompleteResponse {\n        count: autocomplete_results.len(),\n        data: autocomplete_results,\n    }))\n}\n\nfn complete_flag(q: &str) -> impl Iterator<Item = &str> + '_ {\n    QUERY_FLAGS\n        .iter()\n        .filter(move |f| f.starts_with(q))\n        .copied()\n}\n\n#[derive(Serialize)]\npub(super) struct AutocompleteResponse {\n    count: usize,\n    data: Vec<QueryResult>,\n}\n\nimpl super::ApiResponse for AutocompleteResponse {}\n\nconst QUERY_FLAGS: &[&str; 8] = &[\n    \"repo\", \"path\", \"content\", \"symbol\", \"lang\", \"case\", \"or\", \"open\",\n];\n"
  },
  {
    "path": "server/bleep/src/webserver/commits.rs",
    "content": "use crate::{commits::Question, repo::RepoRef, Application};\nuse anyhow::Context;\nuse axum::{extract::State, Json};\n\nuse super::prelude::*;\n\n#[derive(Debug, serde::Deserialize)]\npub(super) struct Params {\n    pub repo_ref: RepoRef,\n}\n\n#[derive(serde::Serialize)]\npub(super) struct TutorialQuestionsResponse {\n    questions: Vec<Question>,\n}\n\nimpl super::ApiResponse for TutorialQuestionsResponse {}\n\npub(super) async fn tutorial_questions<'a>(\n    Query(Params { repo_ref }): Query<Params>,\n    State(app): State<Application>,\n) -> Result<Json<super::Response<'a>>, Error> {\n    let repo_str = repo_ref.to_string();\n    let questions = sqlx::query_as!(\n        Question,\n        \"SELECT question, tag FROM tutorial_questions \\\n         WHERE repo_ref = ?\",\n        repo_str\n    )\n    .fetch_all(app.sql.as_ref())\n    .await\n    .context(\"database error\")?;\n\n    Ok(json(TutorialQuestionsResponse { questions }))\n}\n"
  },
  {
    "path": "server/bleep/src/webserver/config.rs",
    "content": "use axum::{extract::State, Json};\n\nuse super::{middleware::User, prelude::*};\nuse crate::{user::UserProfile, Application};\n\n#[derive(Serialize, Debug)]\npub(super) struct ConfigResponse {\n    user_login: Option<String>,\n    org_name: Option<String>,\n    schema_version: String,\n    github_user: Option<octocrab::models::Author>,\n    bloop_user_profile: UserProfile,\n    bloop_version: String,\n    bloop_commit: String,\n    credentials_upgrade: bool,\n}\n\nimpl super::ApiResponse for ConfigResponse {}\n\npub(super) async fn get(\n    State(app): State<Application>,\n    Extension(user): Extension<User>,\n) -> impl IntoResponse {\n    let user_profile = user\n        .username()\n        .and_then(|login| app.user_profiles.read(login, |_, v| v.clone()))\n        .unwrap_or_default();\n\n    let user_login = user.username().map(str::to_owned);\n\n    let github_user = 'user: {\n        let (Some(login), Some(crab)) = (&user_login, user.github_client()) else {\n            break 'user None;\n        };\n\n        crab.get(format!(\"/users/{login}\"), None::<&()>).await.ok()\n    };\n\n    json(ConfigResponse {\n        schema_version: crate::state::SCHEMA_VERSION.into(),\n        bloop_version: env!(\"CARGO_PKG_VERSION\").into(),\n        bloop_commit: git_version::git_version!(fallback = \"unknown\").into(),\n        bloop_user_profile: user_profile,\n        credentials_upgrade: app.config.source.exists(\"credentials.json\"),\n        user_login,\n        github_user,\n        org_name: None,\n    })\n}\n\n#[derive(Serialize, Deserialize)]\npub(super) struct ConfigUpdate {\n    bloop_user_profile: UserProfile,\n}\n\npub(super) async fn put(\n    State(app): State<Application>,\n    Extension(user): Extension<User>,\n    Json(update): Json<ConfigUpdate>,\n) -> impl IntoResponse {\n    let user = user.username().expect(\"authentication required\").to_owned();\n    app.user_profiles\n        .entry_async(user)\n        .await\n        .or_default()\n        .insert(update.bloop_user_profile);\n    app.user_profiles.store().expect(\"failed to persist\");\n}\n"
  },
  {
    "path": "server/bleep/src/webserver/conversation.rs",
    "content": "use anyhow::Result;\nuse axum::{\n    extract::{Path, State},\n    Extension, Json,\n};\nuse reqwest::StatusCode;\nuse std::mem;\nuse uuid::Uuid;\n\nuse crate::{\n    agent::exchange::Exchange,\n    db::SqlDb,\n    webserver::{self, middleware::User, Error},\n    Application,\n};\n\n#[derive(Clone, serde::Serialize)]\npub struct Conversation {\n    pub exchanges: Vec<Exchange>,\n    pub thread_id: Uuid,\n    #[serde(skip)]\n    pub project_id: i64,\n}\n\nimpl Conversation {\n    pub fn new(project_id: i64) -> Self {\n        Self {\n            exchanges: Vec::new(),\n            thread_id: Uuid::new_v4(),\n            project_id,\n        }\n    }\n\n    pub async fn store(&self, db: &SqlDb, user_id: &str) -> Result<i64> {\n        let mut transaction = db.begin().await?;\n\n        let thread_id = self.thread_id.to_string();\n\n        // Delete the old conversation for simplicity. This also deletes all its messages.\n        let id = sqlx::query! {\n            \"DELETE FROM conversations\n            WHERE thread_id = ? AND EXISTS (\n                SELECT p.id\n                FROM projects p\n                WHERE p.id = project_id AND p.user_id = ?\n            )\n            RETURNING id\",\n            thread_id,\n            user_id,\n        }\n        .fetch_optional(&mut transaction)\n        .await?\n        .map(|row| row.id.unwrap());\n\n        let title = self\n            .exchanges\n            .first()\n            .and_then(|list| list.query())\n            .and_then(|q| q.split('\\n').next().map(|s| s.to_string()))\n            .unwrap_or_else(|| \"New Conversation\".to_owned());\n\n        let exchanges = serde_json::to_string(&self.exchanges)?;\n\n        let id = if let Some(id) = id {\n            sqlx::query! {\n                \"INSERT INTO conversations (\n                    id, thread_id, title, exchanges, project_id, created_at\n                )\n                VALUES (?, ?, ?, ?, ?, strftime('%s', 'now'))\",\n                id,\n                thread_id,\n                title,\n                exchanges,\n                self.project_id,\n            }\n            .execute(&mut transaction)\n            .await?;\n            id\n        } else {\n            sqlx::query! {\n                \"INSERT INTO conversations (\n                    thread_id, title, exchanges, project_id, created_at\n                )\n                VALUES (?, ?, ?, ?, strftime('%s', 'now'))\n                RETURNING id\",\n                thread_id,\n                title,\n                exchanges,\n                self.project_id,\n            }\n            .fetch_one(&mut transaction)\n            .await?\n            .id\n        };\n\n        transaction.commit().await?;\n\n        Ok(id)\n    }\n\n    pub async fn load(\n        db: &SqlDb,\n        user_id: &str,\n        project_id: i64,\n        conversation_id: i64,\n    ) -> webserver::Result<Self> {\n        let row = sqlx::query! {\n            \"SELECT c.exchanges, c.thread_id\n            FROM conversations c\n            JOIN projects p ON p.id = c.project_id AND p.user_id = ?\n            WHERE c.project_id = ? AND c.id = ?\",\n            user_id,\n            project_id,\n            conversation_id,\n        }\n        .fetch_optional(db.as_ref())\n        .await?\n        .ok_or_else(|| Error::not_found(\"conversation not found\"))?;\n\n        let exchanges = serde_json::from_str(&row.exchanges).map_err(Error::internal)?;\n\n        Ok(Self {\n            exchanges,\n            thread_id: row.thread_id.parse().map_err(Error::internal)?,\n            project_id,\n        })\n    }\n}\n\n#[derive(Hash, PartialEq, Eq, Clone)]\npub struct ConversationId {\n    pub conversation_id: i64,\n    pub project_id: i64,\n    pub user_id: String,\n}\n\n#[derive(serde::Serialize)]\npub struct ListItem {\n    pub id: i64,\n    pub thread_id: String,\n    pub created_at: i64,\n    pub title: String,\n}\n\npub(in crate::webserver) async fn list(\n    Extension(user): Extension<User>,\n    State(app): State<Application>,\n    Path(project_id): Path<i64>,\n) -> webserver::Result<Json<Vec<ListItem>>> {\n    let db = app.sql.as_ref();\n    let user_id = user\n        .username()\n        .ok_or_else(|| Error::user(\"missing user ID\"))?;\n\n    sqlx::query_as! {\n        ListItem,\n        \"SELECT c.id as 'id!', c.thread_id, c.created_at, c.title \\\n        FROM conversations c \\\n        JOIN projects p ON p.id = c.project_id AND p.user_id = ? \\\n        WHERE p.id = ?\n        ORDER BY c.created_at DESC\",\n        user_id,\n        project_id,\n    }\n    .fetch_all(db)\n    .await\n    .map(Json)\n    .map_err(Error::internal)\n}\n\npub(in crate::webserver) async fn delete(\n    Extension(user): Extension<User>,\n    State(app): State<Application>,\n    Path((project_id, conversation_id)): Path<(i64, i64)>,\n) -> webserver::Result<()> {\n    let db = app.sql.as_ref();\n    let user_id = user\n        .username()\n        .ok_or_else(|| Error::user(\"missing user ID\"))?;\n\n    let result = sqlx::query! {\n        \"DELETE FROM conversations\n        WHERE id = $1 AND project_id = $2 AND EXISTS (\n            SELECT p.id\n            FROM projects p\n            WHERE p.id = $2 AND p.user_id = $3\n        )\",\n        conversation_id,\n        project_id,\n        user_id,\n    }\n    .execute(db)\n    .await\n    .map_err(Error::internal)?;\n\n    if result.rows_affected() == 0 {\n        return Err(Error::user(\"conversation not found\").with_status(StatusCode::NOT_FOUND));\n    }\n\n    Ok(())\n}\n\n#[axum::debug_handler]\npub(in crate::webserver) async fn get(\n    Extension(user): Extension<User>,\n    Path((project_id, conversation_id)): Path<(i64, i64)>,\n    State(app): State<Application>,\n) -> webserver::Result<Json<Conversation>> {\n    let user_id = user.username().ok_or_else(super::no_user_id)?;\n\n    let mut conversation =\n        Conversation::load(&app.sql, user_id, project_id, conversation_id).await?;\n\n    for ex in &mut conversation.exchanges {\n        *ex = mem::take(ex).compressed();\n    }\n\n    Ok(Json(conversation))\n}\n"
  },
  {
    "path": "server/bleep/src/webserver/docs.rs",
    "content": "use axum::{\n    extract::{Json, Path, Query, State},\n    response::{\n        sse::{Event, KeepAlive},\n        Sse,\n    },\n};\nuse futures::stream::{Stream, StreamExt};\nuse tracing::error;\n\nuse crate::{\n    indexes::doc,\n    webserver::{Error, Result},\n    Application,\n};\n\nuse std::convert::Infallible;\n\n// schema\n#[derive(serde::Deserialize)]\npub struct Enqueue {\n    url: url::Url,\n}\n\n#[derive(serde::Deserialize)]\npub struct List {\n    limit: usize,\n}\n\n#[derive(serde::Deserialize)]\npub struct Search {\n    pub q: Option<String>,\n    pub limit: usize,\n}\n\n#[derive(serde::Deserialize)]\npub struct Fetch {\n    pub relative_url: String,\n}\n\n#[derive(serde::Deserialize)]\npub struct Verify {\n    url: url::Url,\n}\n\n// handlers\npub async fn list(State(app): State<Application>) -> Result<Json<Vec<doc::SqlRecord>>> {\n    Ok(Json(app.indexes.doc.list().await?))\n}\n\npub async fn list_one(\n    State(app): State<Application>,\n    Path(id): Path<i64>,\n) -> Result<Json<doc::SqlRecord>> {\n    Ok(Json(app.indexes.doc.list_one(id).await?))\n}\n\npub async fn delete(State(app): State<Application>, Path(id): Path<i64>) -> Result<Json<i64>> {\n    Ok(Json(app.indexes.doc.delete(id).await?))\n}\n\npub async fn enqueue(\n    State(app): State<Application>,\n    Query(params): Query<Enqueue>,\n) -> Result<Json<i64>> {\n    Ok(Json(app.indexes.doc.clone().enqueue(params.url).await?))\n}\n\npub async fn status(\n    State(app): State<Application>,\n    Path(id): Path<i64>,\n) -> Sse<impl Stream<Item = Result<Event, Infallible>>> {\n    Sse::new(Box::pin(app.indexes.doc.clone().status(id).await.map(\n        |result| {\n            Ok(Event::default()\n                .json_data(result.as_ref().map_err(ToString::to_string))\n                .unwrap())\n        },\n    )))\n    .keep_alive(KeepAlive::default())\n}\n\npub async fn cancel(State(app): State<Application>, Path(id): Path<i64>) -> Result<Json<i64>> {\n    Ok(Json(app.indexes.doc.clone().cancel(id).await?))\n}\n\npub async fn resync(State(app): State<Application>, Path(id): Path<i64>) -> Result<Json<i64>> {\n    Ok(Json(app.indexes.doc.clone().resync(id).await?))\n}\n\npub async fn search(\n    State(app): State<Application>,\n    Query(params): Query<Search>,\n) -> Result<Json<Vec<doc::SqlRecord>>> {\n    Ok(Json(match params.q {\n        Some(q) => app.indexes.doc.search(q, params.limit).await?,\n        None => app.indexes.doc.list().await?,\n    }))\n}\n\npub async fn search_with_id(\n    State(app): State<Application>,\n    Path(id): Path<i64>,\n    Query(params): Query<Search>,\n) -> Result<Json<Vec<doc::Section>>> {\n    Ok(Json(match params.q {\n        Some(query) => {\n            app.indexes\n                .doc\n                .search_sections(query, params.limit, id)\n                .await?\n        }\n        None => app.indexes.doc.list_sections(params.limit, id).await?,\n    }))\n}\n\npub async fn list_with_id(\n    State(app): State<Application>,\n    Path(id): Path<i64>,\n    Query(params): Query<List>,\n) -> Result<Json<Vec<doc::Page>>> {\n    Ok(Json(app.indexes.doc.list_pages(params.limit, id).await?))\n}\n\npub async fn fetch(\n    State(app): State<Application>,\n    Path(id): Path<i64>,\n    Query(params): Query<Fetch>,\n) -> Result<Json<Vec<doc::Section>>> {\n    Ok(Json(app.indexes.doc.fetch(id, params.relative_url).await?))\n}\n\npub async fn verify(\n    State(app): State<Application>,\n    Query(params): Query<Verify>,\n) -> Result<reqwest::StatusCode> {\n    Ok(app.indexes.doc.verify(params.url).await?)\n}\n\nimpl From<doc::Error> for Error {\n    fn from(value: doc::Error) -> Self {\n        match value {\n            doc::Error::Sql(_)\n            | doc::Error::UrlParse(..)\n            | doc::Error::Io(..)\n            | doc::Error::Tantivy(..)\n            | doc::Error::Network(..)\n            | doc::Error::Initialize(_) => {\n                error!(%value, \"internal docs error\");\n                Self::internal(value)\n            }\n            doc::Error::InvalidUrl(..)\n            | doc::Error::DuplicateUrl(..)\n            | doc::Error::EmptyDocs(..) => Self::user(value),\n            doc::Error::InvalidDocId(_) => Self::not_found(value),\n        }\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/webserver/file.rs",
    "content": "use std::{path::PathBuf, sync::Arc};\n\nuse anyhow::Context;\nuse axum::{extract::Query, Extension, Json};\nuse tracing::warn;\n\nuse crate::{\n    indexes::reader::OpenReader,\n    query::{\n        execute::{ApiQuery, DirectoryData, ExecuteQuery, QueryResult},\n        parser,\n    },\n    repo::RepoRef,\n};\n\nuse super::prelude::*;\n\n#[derive(Debug, serde::Deserialize)]\npub(super) struct FileParams {\n    pub repo_ref: RepoRef,\n    pub path: PathBuf,\n    pub branch: Option<String>,\n\n    /// 1-indexed line number at which to start the snippet\n    pub line_start: Option<isize>,\n\n    /// 1-indexed line number at which to end the snippet\n    pub line_end: Option<usize>,\n}\n\n#[derive(serde::Serialize)]\npub(super) struct FileResponse {\n    contents: String,\n    lang: Option<String>,\n}\n\nimpl super::ApiResponse for FileResponse {}\n\npub(super) async fn handle<'a>(\n    Query(params): Query<FileParams>,\n    Extension(indexes): Extension<Arc<Indexes>>,\n) -> Result<Json<super::Response<'a>>, Error> {\n    let doc = indexes\n        .file\n        .by_path(\n            &params.repo_ref,\n            params.path.to_str().context(\"invalid file path\")?,\n            params.branch.as_deref(),\n        )\n        .await\n        .map_err(Error::internal)?\n        .ok_or_else(|| Error::user(\"file not found\").with_status(StatusCode::NOT_FOUND))?;\n\n    Ok(json(FileResponse {\n        contents: split_by_lines(&doc.content, &doc.line_end_indices, &params)?.to_string(),\n        lang: doc.lang,\n    }))\n}\n\nfn split_by_lines<'a>(\n    text: &'a str,\n    indices: &[u32],\n    params: &FileParams,\n) -> Result<&'a str, Error> {\n    let char_start = match params.line_start {\n        Some(1) => 0,\n        Some(line_start) if line_start > 1 => {\n            (indices\n                .get(line_start as usize - 2)\n                .ok_or_else(|| Error::user(\"invalid line number\"))?\n                + 1) as usize\n        }\n        Some(_) => return Err(Error::user(\"line numbers are 1-indexed!\")),\n        _ => 0,\n    };\n\n    let line_end = params.line_end.unwrap_or(indices.len()) - 1;\n    let char_end = *indices\n        .get(line_end)\n        .ok_or_else(|| Error::user(\"invalid line number\"))? as usize;\n\n    Ok(&text[char_start..=char_end])\n}\n\n#[derive(Debug, serde::Deserialize)]\npub(super) struct FolderParams {\n    pub repo_ref: RepoRef,\n    pub path: PathBuf,\n    pub branch: Option<String>,\n}\n\npub(super) async fn folder(\n    Query(params): Query<FolderParams>,\n    Extension(indexes): Extension<Arc<Indexes>>,\n) -> Result<Json<DirectoryData>, Error> {\n    let reader = OpenReader;\n\n    let query = parser::Query {\n        open: Some(true),\n        repo: Some(parser::Literal::from(&params.repo_ref.indexed_name())),\n        path: Some(parser::Literal::from(params.path.to_string_lossy())),\n        branch: params.branch.map(|b| parser::Literal::from(&b)),\n        case_sensitive: Some(true),\n        ..Default::default()\n    };\n\n    // NB: This argument is not actually used in `OpenReader::execute`. We have two options to\n    // simplify this:\n    //\n    // 1. Refactor the open reader in order to extract common logic so that we can re-use it here\n    // 2. Remove the open reader entirely, replacing it with this route and the `/file` route\n    //\n    // Until we decide what to do here, we continue by just creating a dummy parameter.\n    let api_query = ApiQuery {\n        q: String::new(),\n        project_id: 0,\n        page: 0,\n        page_size: 0,\n        calculate_totals: false,\n        context_before: 0,\n        context_after: 0,\n    };\n\n    let mut results = reader\n        .execute(&indexes.file, &[query], &api_query)\n        .await\n        .context(\"failed to execute open query\")?\n        .data\n        .into_iter()\n        .filter_map(|qr| match qr {\n            QueryResult::Directory(d) => Some(d),\n            _ => None,\n        });\n\n    let output = results\n        .next()\n        .context(\"`open:` query returned no results\")?;\n\n    if results.next().is_some() {\n        warn!(\"`open:` query returned multiple results, ignoring all but first\");\n    }\n\n    Ok(Json(output))\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn no_params() {\n        let text = r#\"aaaaaa\nbbbbbb\ncccccc\n\"#;\n\n        let indices = text\n            .match_indices('\\n')\n            .map(|(i, _)| i as u32)\n            .collect::<Vec<_>>();\n\n        println!(\"{indices:?}\");\n\n        assert_eq!(\n            split_by_lines(\n                text,\n                &indices,\n                &FileParams {\n                    repo_ref: \"local//repo\".into(),\n                    path: \"file\".into(),\n                    line_start: None,\n                    line_end: None,\n                    branch: None,\n                }\n            )\n            .unwrap_or_else(|_| panic!(\"bad\")),\n            text\n        );\n\n        assert_eq!(\n            split_by_lines(\n                text,\n                &indices,\n                &FileParams {\n                    repo_ref: \"local//repo\".into(),\n                    path: \"file\".into(),\n                    line_start: Some(1),\n                    line_end: None,\n                    branch: None,\n                }\n            )\n            .unwrap_or_else(|_| panic!(\"bad\")),\n            text\n        );\n\n        assert_eq!(\n            split_by_lines(\n                text,\n                &indices,\n                &FileParams {\n                    repo_ref: \"local//repo\".into(),\n                    path: \"file\".into(),\n                    line_start: Some(2),\n                    line_end: None,\n                    branch: None,\n                }\n            )\n            .unwrap_or_else(|_| panic!(\"bad\")),\n            &text[7..]\n        );\n\n        assert_eq!(\n            split_by_lines(\n                text,\n                &indices,\n                &FileParams {\n                    repo_ref: \"local//repo\".into(),\n                    path: \"file\".into(),\n                    line_start: Some(3),\n                    line_end: Some(3),\n                    branch: None,\n                }\n            )\n            .unwrap_or_else(|_| panic!(\"bad\")),\n            &text[14..]\n        );\n\n        assert_eq!(\n            split_by_lines(\n                text,\n                &indices,\n                &FileParams {\n                    repo_ref: \"local//repo\".into(),\n                    path: \"file\".into(),\n                    line_start: Some(2),\n                    line_end: Some(3),\n                    branch: None,\n                }\n            )\n            .unwrap_or_else(|_| panic!(\"bad\")),\n            &text[7..]\n        );\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/webserver/hoverable.rs",
    "content": "use std::sync::Arc;\n\nuse super::prelude::*;\nuse crate::{indexes::Indexes, repo::RepoRef, text_range::TextRange};\n\nuse axum::{extract::Query, response::IntoResponse, Extension};\nuse serde::{Deserialize, Serialize};\n\n/// The request made to the `hoverable` endpoint.\n#[derive(Debug, Deserialize)]\npub struct HoverableRequest {\n    /// The repo_ref of the file of interest\n    repo_ref: String,\n\n    /// The path to the file of interest, relative to the repo root\n    relative_path: String,\n\n    /// Branch name to use for the lookup,\n    branch: Option<String>,\n}\n\n/// The response from the `hoverable` endpoint.\n#[derive(Serialize)]\npub struct HoverableResponse {\n    pub ranges: Vec<TextRange>,\n}\n\nimpl super::ApiResponse for HoverableResponse {}\n\npub(super) async fn handle(\n    Query(payload): Query<HoverableRequest>,\n    Extension(indexes): Extension<Arc<Indexes>>,\n) -> impl IntoResponse {\n    let repo_ref = &payload.repo_ref.parse::<RepoRef>().map_err(Error::user)?;\n\n    let document = match indexes\n        .file\n        .by_path(repo_ref, &payload.relative_path, payload.branch.as_deref())\n        .await\n    {\n        Ok(Some(doc)) => doc,\n        Ok(None) => return Err(Error::user(\"file not found\").with_status(StatusCode::NOT_FOUND)),\n        Err(e) => return Err(Error::user(e)),\n    };\n\n    let ranges = document\n        .hoverable_ranges()\n        .ok_or(Error::user(\"no hoverable ranges for language\"))?;\n\n    Ok(json(HoverableResponse { ranges }))\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::text_range::Point;\n\n    #[test]\n    fn serialize_response() {\n        let expected = serde_json::json!(\n            {\n              \"ranges\": [\n                {\n                  \"start\": { \"byte\": 50, \"line\": 60, \"column\": 0  },\n                  \"end\":   { \"byte\": 80, \"line\": 90, \"column\": 0  }\n                },\n                {\n                  \"start\": { \"byte\":  5, \"line\": 15, \"column\": 0  },\n                  \"end\":   { \"byte\": 35, \"line\": 45, \"column\": 0  }\n                },\n              ]\n            }\n        );\n\n        let observed = serde_json::to_value(HoverableResponse {\n            ranges: vec![\n                TextRange::new(Point::new(50, 60, 0), Point::new(80, 90, 0)),\n                TextRange::new(Point::new(5, 15, 0), Point::new(35, 45, 0)),\n            ],\n        })\n        .unwrap();\n\n        assert_eq!(expected, observed)\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/webserver/index.rs",
    "content": "use crate::Application;\n\nuse axum::{response::IntoResponse, Extension};\n\npub(super) async fn handle(Extension(app): Extension<Application>) -> impl IntoResponse {\n    tokio::task::spawn(async move { app.write_index().startup_scan().await });\n}\n"
  },
  {
    "path": "server/bleep/src/webserver/intelligence.rs",
    "content": "use std::{ops::Not, sync::Arc};\n\nuse super::prelude::*;\nuse crate::{\n    indexes::{reader::ContentDocument, Indexes},\n    intelligence::{\n        code_navigation::{\n            self, CodeNavigationContext, FileSymbols, Occurrence, OccurrenceKind, Token,\n        },\n        Language, NodeKind, TSLanguage,\n    },\n    repo::RepoRef,\n    snippet::Snipper,\n    text_range::TextRange,\n};\n\nuse axum::{extract::Query, response::IntoResponse, Extension};\nuse serde::{Deserialize, Serialize};\n\n/// The request made to the `local-intel` endpoint.\n#[derive(Debug, Deserialize)]\npub struct TokenInfoRequest {\n    /// The repo_ref of the file of interest\n    pub repo_ref: String,\n\n    /// The path to the file of interest, relative to the repo root\n    pub relative_path: String,\n\n    /// Branch name to use for the lookup,\n    pub branch: Option<String>,\n\n    /// The byte range to look for\n    pub start: usize,\n    pub end: usize,\n}\n\n/// The response from the `local-intel` endpoint.\n#[derive(Serialize, Debug)]\npub(super) struct TokenInfoResponse {\n    data: Vec<FileSymbols>,\n}\n\nimpl TokenInfoResponse {\n    fn new(data: Vec<FileSymbols>) -> Self {\n        Self { data }\n    }\n}\n\nimpl super::ApiResponse for TokenInfoResponse {}\n\npub(super) async fn handle(\n    Query(payload): Query<TokenInfoRequest>,\n    Extension(indexes): Extension<Arc<Indexes>>,\n) -> Result<impl IntoResponse> {\n    let repo_ref = payload.repo_ref.parse::<RepoRef>().map_err(Error::user)?;\n\n    let source_doc = indexes\n        .file\n        .by_path(&repo_ref, &payload.relative_path, payload.branch.as_deref())\n        .await\n        .map_err(Error::user)?\n        .ok_or_else(|| Error::user(\"path not found\").with_status(StatusCode::NOT_FOUND))?;\n    let lang = source_doc.lang.as_deref();\n    let all_docs = {\n        let associated_langs = match lang.map(TSLanguage::from_id) {\n            Some(Language::Supported(config)) => config.language_ids,\n            _ => &[],\n        };\n        indexes\n            .file\n            .by_repo(\n                &repo_ref,\n                associated_langs.iter(),\n                payload.branch.as_deref(),\n            )\n            .await\n    };\n\n    let symbols = get_token_info(\n        payload,\n        &repo_ref,\n        indexes,\n        &source_doc,\n        &all_docs,\n        None,\n        None,\n    )\n    .await\n    .map_err(Error::internal)?;\n\n    Ok(json(TokenInfoResponse::new(symbols)))\n}\n\n/// The request made to the `related-files` endpoint.\n#[derive(Debug, Deserialize)]\npub(super) struct RelatedFilesRequest {\n    /// The repo_ref of the file of interest\n    repo_ref: RepoRef,\n\n    /// The path to the file of interest, relative to the repo root\n    relative_path: String,\n\n    /// Branch name to use for the lookup,\n    branch: Option<String>,\n}\n\n/// The response from the `related-files` endpoint.\n#[derive(Serialize, Debug)]\npub struct RelatedFilesResponse {\n    /// Files importing `target`, across this repo\n    files_importing: Vec<String>,\n\n    /// Files imported in `target`\n    files_imported: Vec<String>,\n}\n\nimpl super::ApiResponse for RelatedFilesResponse {}\n\npub(super) async fn related_files(\n    Query(payload): Query<RelatedFilesRequest>,\n    Extension(indexes): Extension<Arc<Indexes>>,\n) -> Result<impl IntoResponse> {\n    let source_document = indexes\n        .file\n        .by_path(\n            &payload.repo_ref,\n            &payload.relative_path,\n            payload.branch.as_deref(),\n        )\n        .await\n        .map_err(Error::user)?\n        .ok_or_else(|| Error::user(\"path not found\").with_status(StatusCode::NOT_FOUND))?;\n    let lang = source_document.lang.as_deref();\n    let all_docs = {\n        let associated_langs = match lang.map(TSLanguage::from_id) {\n            Some(Language::Supported(config)) => config.language_ids,\n            _ => &[],\n        };\n        indexes\n            .file\n            .by_repo(\n                &payload.repo_ref,\n                associated_langs.iter(),\n                payload.branch.as_deref(),\n            )\n            .await\n    };\n\n    let source_document_idx = all_docs\n        .iter()\n        .position(|doc| doc.relative_path == payload.relative_path)\n        .ok_or(Error::internal(\"invalid language\"))?;\n\n    let (h1, h2) = std::thread::scope(|s| {\n        let h1 = s.spawn(|| {\n            CodeNavigationContext::files_imported(&all_docs, source_document_idx)\n                .into_iter()\n                .map(|doc| doc.relative_path.clone())\n                .collect()\n        });\n        let h2 = s.spawn(|| {\n            CodeNavigationContext::files_importing(&all_docs, source_document_idx)\n                .into_iter()\n                .map(|doc| doc.relative_path.clone())\n                .collect()\n        });\n        (h1.join(), h2.join())\n    });\n\n    let files_imported = h1.map_err(|_| Error::internal(\"failed to find imported files\"))?;\n    let files_importing = h2.map_err(|_| Error::internal(\"failed to find importing files\"))?;\n\n    return Ok(json(RelatedFilesResponse {\n        files_imported,\n        files_importing,\n    }));\n}\n\n#[derive(Debug, Deserialize, PartialEq, Eq)]\npub(super) enum RelatedFileKind {\n    Imported,\n    Importing,\n}\n\n#[derive(Debug, Deserialize)]\npub(super) struct WithRangesRequest {\n    /// The repo_ref of the file of interest\n    repo_ref: RepoRef,\n\n    /// Branch name to use for the lookup,\n    branch: Option<String>,\n\n    /// The path to the source-file\n    source_file_path: String,\n\n    /// The path to the related-file\n    related_file_path: String,\n\n    /// Whether this is an importing file or an imported file\n    kind: RelatedFileKind,\n}\n\n#[derive(Debug, Serialize, Default)]\npub(super) struct WithRangesResponse {\n    ranges: Vec<TextRange>,\n}\n\nimpl WithRangesResponse {\n    fn empty() -> Self {\n        Self::default()\n    }\n}\n\nimpl super::ApiResponse for WithRangesResponse {}\n\npub(super) async fn related_file_with_ranges(\n    Query(payload): Query<WithRangesRequest>,\n    Extension(indexes): Extension<Arc<Indexes>>,\n) -> Result<impl IntoResponse> {\n    let source_document = indexes\n        .file\n        .by_path(\n            &payload.repo_ref,\n            &payload.source_file_path,\n            payload.branch.as_deref(),\n        )\n        .await\n        .map_err(Error::user)?\n        .ok_or_else(|| Error::user(\"path not found\").with_status(StatusCode::NOT_FOUND))?;\n\n    let related_file_document = indexes\n        .file\n        .by_path(\n            &payload.repo_ref,\n            &payload.related_file_path,\n            payload.branch.as_deref(),\n        )\n        .await\n        .map_err(Error::user)?\n        .ok_or_else(|| Error::user(\"path not found\").with_status(StatusCode::NOT_FOUND))?;\n\n    match payload.kind {\n        RelatedFileKind::Imported => {\n            return Ok(json(WithRangesResponse {\n                ranges: code_navigation::imported_ranges(&source_document, &related_file_document)\n                    .into_iter()\n                    .collect(),\n            }))\n        }\n        RelatedFileKind::Importing => return Ok(json(WithRangesResponse::empty())),\n    }\n}\n\n/// The request made to the `token-value` endpoint.\n#[derive(Debug, Deserialize)]\npub(super) struct TokenValueRequest {\n    /// The repo_ref of the file of interest\n    repo_ref: RepoRef,\n\n    /// The path to the file of interest, relative to the repo root\n    relative_path: String,\n\n    /// Branch name to use for the lookup,\n    branch: Option<String>,\n\n    /// The byte range to look for\n    start: usize,\n    end: usize,\n}\n\n/// The response from the `related-files` endpoint.\n#[derive(Serialize, Debug)]\npub struct TokenValueResponse {\n    range: TextRange,\n    content: String,\n}\n\nimpl super::ApiResponse for TokenValueResponse {}\n\npub(super) async fn token_value(\n    Query(payload): Query<TokenValueRequest>,\n    Extension(indexes): Extension<Arc<Indexes>>,\n) -> Result<impl IntoResponse> {\n    let source_document = indexes\n        .file\n        .by_path(\n            &payload.repo_ref,\n            &payload.relative_path,\n            payload.branch.as_deref(),\n        )\n        .await\n        .map_err(Error::user)?\n        .ok_or_else(|| Error::user(\"path not found\").with_status(StatusCode::NOT_FOUND))?;\n\n    let sg = source_document\n        .symbol_locations\n        .scope_graph()\n        .ok_or_else(|| Error::internal(\"path not supported for /token-value\"))?;\n\n    let node_idx = sg\n        .node_by_range(payload.start, payload.end)\n        .ok_or_else(|| Error::internal(\"token not supported for /token-value\"))?;\n\n    let range = sg.graph[sg.value_of_definition(node_idx).unwrap_or(node_idx)].range();\n\n    // extend the range to cover the entire start line and the entire end line\n    let new_start = range.start.byte - range.start.column;\n    let new_end = source_document\n        .line_end_indices\n        .get(range.end.line)\n        .map(|l| *l as usize)\n        .unwrap_or(range.end.byte);\n    let content = source_document.content[new_start..new_end].to_string();\n\n    Ok(json(TokenValueResponse { range, content }))\n}\n\npub async fn get_token_info(\n    params: TokenInfoRequest,\n    repo_ref: &RepoRef,\n    indexes: Arc<Indexes>,\n    source_doc: &ContentDocument,\n    all_docs: &Vec<ContentDocument>,\n    context_before: Option<usize>,\n    context_after: Option<usize>,\n) -> anyhow::Result<Vec<FileSymbols>> {\n    let source_document_idx = all_docs\n        .iter()\n        .position(|doc| doc.relative_path == source_doc.relative_path)\n        .ok_or(anyhow::anyhow!(\"invalid language\"))?;\n\n    let snipper =\n        Some(Snipper::default().context(context_before.unwrap_or(0), context_after.unwrap_or(0)));\n\n    let ctx: CodeNavigationContext<'_, '_> = CodeNavigationContext {\n        token: Token {\n            repo: repo_ref.clone(),\n            relative_path: params.relative_path.as_str(),\n            start_byte: params.start,\n            end_byte: params.end,\n        },\n        all_docs,\n        source_document_idx,\n        snipper,\n    };\n\n    let data = ctx.token_info();\n    if data.is_empty() {\n        search_nav(\n            Arc::clone(&indexes),\n            repo_ref,\n            ctx.active_token_text(),\n            ctx.active_token_range(),\n            params.branch.as_deref(),\n            source_doc,\n            snipper,\n        )\n        .await\n    } else {\n        Ok(data)\n    }\n}\n\nasync fn search_nav(\n    indexes: Arc<Indexes>,\n    repo_ref: &RepoRef,\n    hovered_text: &str,\n    payload_range: std::ops::Range<usize>,\n    branch: Option<&str>,\n    source_document: &ContentDocument,\n    snipper: Option<Snipper>,\n) -> anyhow::Result<Vec<FileSymbols>> {\n    use crate::{\n        indexes::{reader::ContentReader, DocumentRead},\n        query::compiler::trigrams,\n    };\n    use tantivy::{\n        collector::TopDocs,\n        query::{BooleanQuery, TermQuery},\n        schema::{IndexRecordOption, Term},\n    };\n\n    let associated_langs = match source_document.lang.as_deref().map(TSLanguage::from_id) {\n        Some(Language::Supported(config)) => config.language_ids,\n        _ => &[],\n    };\n\n    // produce search based results here\n    let regex_str = regex::escape(hovered_text);\n    let target = regex::Regex::new(&format!(r\"\\b{regex_str}\\b\")).expect(\"failed to build regex\");\n    // perform a text search for hovered_text\n    let file_source = &indexes.file.source;\n    let indexer = &indexes.file;\n    let query = {\n        let repo_filter = Term::from_field_text(indexer.source.repo_ref, &repo_ref.to_string());\n        let terms = trigrams(hovered_text)\n            .map(|token| Term::from_field_text(indexer.source.content, token.as_str()))\n            .map(|term| {\n                Box::new(TermQuery::new(term, IndexRecordOption::Basic))\n                    as Box<dyn tantivy::query::Query>\n            })\n            .chain(std::iter::once(\n                Box::new(TermQuery::new(repo_filter, IndexRecordOption::Basic))\n                    as Box<dyn tantivy::query::Query>,\n            ))\n            .chain(\n                branch\n                    .into_iter()\n                    .map(|b| {\n                        trigrams(b)\n                            .map(|token| {\n                                Term::from_field_text(indexer.source.branches, token.as_str())\n                            })\n                            .map(|term| TermQuery::new(term, IndexRecordOption::Basic))\n                            .map(Box::new)\n                            .map(|q| q as Box<dyn tantivy::query::Query>)\n                            .collect::<Vec<_>>()\n                    })\n                    .map(BooleanQuery::intersection)\n                    .map(Box::new)\n                    .map(|b| b as Box<dyn tantivy::query::Query>),\n            )\n            .chain(std::iter::once(Box::new(BooleanQuery::union(\n                associated_langs\n                    .iter()\n                    .map(|l| {\n                        Term::from_field_bytes(\n                            indexer.source.lang,\n                            l.to_ascii_lowercase().as_bytes(),\n                        )\n                    })\n                    .map(|l| {\n                        Box::new(TermQuery::new(l, IndexRecordOption::Basic))\n                            as Box<dyn tantivy::query::Query>\n                    })\n                    .collect::<Vec<_>>(),\n            ))\n                as Box<dyn tantivy::query::Query>))\n            .collect::<Vec<Box<dyn tantivy::query::Query>>>();\n\n        BooleanQuery::intersection(terms)\n    };\n    let collector = TopDocs::with_limit(500);\n    let searcher = indexes.file.reader.searcher();\n    let results = searcher\n        .search(&query, &collector)\n        .expect(\"failed to search index\");\n\n    // if the hovered token is a def, ignore all other search-based defs\n    let ignore_defs = {\n        source_document\n            .symbol_locations\n            .scope_graph()\n            .and_then(|graph| {\n                graph\n                    .node_by_range(payload_range.start, payload_range.end)\n                    .map(|idx| matches!(graph.graph[idx], NodeKind::Def(_)))\n            })\n            .unwrap_or_default()\n    };\n\n    let data = results\n        .into_iter()\n        .filter_map(|(_, doc_addr)| {\n            let retrieved_doc = searcher\n                .doc(doc_addr)\n                .expect(\"failed to get document by address\");\n            let doc = ContentReader.read_document(file_source, retrieved_doc);\n            let hoverable_ranges = doc.hoverable_ranges()?;\n            let data = target\n                .find_iter(&doc.content)\n                .map(|m| TextRange::from_byte_range(m.range(), &doc.line_end_indices))\n                .filter(|range| hoverable_ranges.iter().any(|r| r.contains(range)))\n                .filter(|range| {\n                    !(payload_range.start >= range.start.byte\n                        && payload_range.end <= range.end.byte)\n                })\n                .map(|range| {\n                    let start_byte = range.start.byte;\n                    let end_byte = range.end.byte;\n                    let is_def = doc\n                        .symbol_locations\n                        .scope_graph()\n                        .and_then(|graph| {\n                            graph\n                                .node_by_range(start_byte, end_byte)\n                                .map(|idx| matches!(graph.graph[idx], NodeKind::Def(_)))\n                        })\n                        .map(|d| {\n                            if d {\n                                OccurrenceKind::Definition\n                            } else {\n                                OccurrenceKind::Reference\n                            }\n                        })\n                        .unwrap_or_default();\n                    let highlight = start_byte..end_byte;\n                    let snippet = snipper\n                        .unwrap_or_default()\n                        .expand(highlight, &doc.content, &doc.line_end_indices)\n                        .reify(&doc.content, &[]);\n\n                    Occurrence {\n                        kind: is_def,\n                        range,\n                        snippet,\n                    }\n                })\n                .filter(|o| !(ignore_defs && o.is_definition())) // if ignore_defs is true & o is a def, omit it\n                .collect::<Vec<_>>();\n\n            let file = doc.relative_path;\n\n            data.is_empty().not().then(|| FileSymbols {\n                file: file.clone(),\n                repo: repo_ref.clone(),\n                data,\n            })\n        })\n        .collect::<Vec<_>>();\n\n    Ok(data)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::{snippet::Snippet, text_range::Point};\n\n    #[test]\n    fn serialize_response() {\n        let expected = serde_json::json!({\n            \"data\": [\n                {\n                    \"file\": \"server/bleep/src/symbol.rs\",\n                    \"repo\": \"github.com/BloopAI/bloop\",\n                    \"data\": [{\n                        \"kind\": \"definition\",\n                        \"range\": {\n                            \"start\": { \"byte\": 2620, \"line\": 90, \"column\": 0  },\n                            \"end\": { \"byte\": 2627, \"line\": 90, \"column\": 0  },\n                        },\n                        \"snippet\": {\n                            \"highlights\": [ { \"start\": 12, \"end\": 19 } ],\n                            \"data\": \"        let indexes = Indexes::new(self.clone(), threads).await?;\\n\",\n                            \"line_range\": { \"start\": 91, \"end\": 92 },\n                            \"symbols\": []\n                        }\n                    }]\n\n                },\n                {\n                    \"file\": \"server/bleep/src/intelligence/scope_resolution.rs\",\n                    \"repo\": \"github.com/BloopAI/bloop\",\n                    \"data\": [{\n                        \"kind\": \"reference\",\n                        \"range\": {\n                            \"start\": { \"byte\": 2725, \"line\": 93, \"column\": 0  },\n                            \"end\": { \"byte\": 2732, \"line\": 93, \"column\": 0  },\n                        },\n                        \"snippet\": {\n                            \"highlights\": [ { \"start\": 12, \"end\": 19 } ],\n                            \"data\": \"            indexes.reindex().await?;\\n\",\n                            \"line_range\": { \"start\": 94, \"end\": 95 },\n                            \"symbols\": []\n                        }\n                    }]\n                }\n            ]\n        });\n\n        let observed = serde_json::to_value(TokenInfoResponse {\n            data: vec![\n                FileSymbols {\n                    file: \"server/bleep/src/symbol.rs\".into(),\n                    repo: \"github.com/BloopAI/bloop\".parse().unwrap(),\n                    data: vec![Occurrence {\n                    kind: OccurrenceKind::Definition,\n                    range: TextRange {\n                        start: Point {\n                            byte: 2620,\n                            line: 90,\n                            column: 0,\n                        },\n                        end: Point {\n                            byte: 2627,\n                            line: 90,\n                            column: 0,\n                        },\n                    },\n                    snippet: Snippet {\n                        line_range: 91..92,\n                        data: \"        let indexes = Indexes::new(self.clone(), threads).await?;\\n\"\n                            .to_owned(),\n                        highlights: vec![12..19],\n                        symbols: vec![],\n                    },\n                }],\n                },\n                FileSymbols {\n                    file: \"server/bleep/src/intelligence/scope_resolution.rs\".into(),\n                    repo: \"github.com/BloopAI/bloop\".parse().unwrap(),\n                    data: vec![Occurrence {\n                        kind: OccurrenceKind::Reference,\n                        range: TextRange {\n                            start: Point {\n                                byte: 2725,\n                                line: 93,\n                                column: 0,\n                            },\n                            end: Point {\n                                byte: 2732,\n                                line: 93,\n                                column: 0,\n                            },\n                        },\n                        snippet: Snippet {\n                            line_range: 94..95,\n                            data: \"            indexes.reindex().await?;\\n\".to_owned(),\n                            highlights: vec![12..19],\n                            symbols: vec![],\n                        },\n                    }],\n                },\n            ],\n        })\n        .unwrap();\n\n        pretty_assertions::assert_eq!(expected, observed)\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/webserver/middleware.rs",
    "content": "use super::prelude::*;\nuse crate::{llm, Application};\n\nuse anyhow::bail;\nuse axum::{\n    extract::State,\n    http::Request,\n    middleware::{from_fn_with_state, Next},\n    response::Response,\n};\n\n#[derive(Serialize, Clone)]\npub enum User {\n    Unknown,\n    Desktop {\n        access_token: String,\n        login: String,\n        #[serde(skip)]\n        crab: Arc<dyn Fn() -> anyhow::Result<octocrab::Octocrab> + Send + Sync>,\n    },\n}\n\nimpl User {\n    pub fn username(&self) -> Option<&str> {\n        match self {\n            User::Desktop { login, .. } => Some(login),\n            _ => None,\n        }\n    }\n\n    pub(crate) fn github_client(&self) -> Option<octocrab::Octocrab> {\n        let crab = match self {\n            User::Unknown => return None,\n            User::Desktop { crab, .. } => crab,\n        };\n\n        crab().ok()\n    }\n\n    pub(crate) async fn llm_gateway(\n        &self,\n        app: &Application,\n    ) -> anyhow::Result<llm::client::Client> {\n        if let User::Unknown = self {\n            bail!(\"user unauthenticated\");\n        }\n\n        Ok(llm::client::Client::new(app.clone()))\n    }\n}\n\npub fn local_user(router: Router, app: Application) -> Router {\n    router.layer(from_fn_with_state(app, local_user_mw))\n}\n\nasync fn local_user_mw<B>(\n    State(app): State<Application>,\n    mut request: Request<B>,\n    next: Next<B>,\n) -> Response {\n    request.extensions_mut().insert(app.user().await);\n    next.run(request).await\n}\n"
  },
  {
    "path": "server/bleep/src/webserver/project/doc.rs",
    "content": "use axum::{extract::Path, Extension, Json};\nuse chrono::NaiveDateTime;\n\nuse crate::{\n    webserver::{self, middleware::User, Error},\n    Application,\n};\n\n#[derive(serde::Serialize)]\npub struct Doc {\n    id: i64,\n    url: String,\n    index_status: String,\n    name: Option<String>,\n    favicon: Option<String>,\n    description: Option<String>,\n    modified_at: NaiveDateTime,\n}\n\npub async fn list(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Path(project_id): Path<i64>,\n) -> webserver::Result<Json<Vec<Doc>>> {\n    let user_id = user\n        .username()\n        .ok_or_else(webserver::no_user_id)?\n        .to_string();\n\n    let docs = sqlx::query_as! {\n        Doc,\n        \"SELECT d.id, d.url, d.index_status, d.name, d.favicon, d.description, d.modified_at\n        FROM project_docs pd\n        INNER JOIN docs d ON d.id = pd.doc_id\n        WHERE project_id = $1 AND EXISTS (\n            SELECT p.id\n            FROM projects p\n            WHERE p.id = $1 AND p.user_id = $2\n        )\",\n        project_id,\n        user_id,\n    }\n    .fetch_all(&*app.sql)\n    .await?;\n\n    Ok(Json(docs))\n}\n\n#[derive(serde::Deserialize)]\npub struct Add {\n    doc_id: i64,\n}\n\npub async fn add(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Path(project_id): Path<i64>,\n    Json(params): Json<Add>,\n) -> webserver::Result<()> {\n    let user_id = user\n        .username()\n        .ok_or_else(webserver::no_user_id)?\n        .to_string();\n\n    sqlx::query! {\n        \"SELECT id FROM projects WHERE id = ? AND user_id = ?\",\n        project_id,\n        user_id,\n    }\n    .fetch_optional(&*app.sql)\n    .await?\n    .ok_or_else(|| Error::not_found(\"project not found\"))?;\n\n    sqlx::query! {\n        \"INSERT INTO project_docs (project_id, doc_id) VALUES ($1, $2)\",\n        project_id,\n        params.doc_id,\n    }\n    .execute(&*app.sql)\n    .await\n    .map(|_| ())\n    .map_err(Error::internal)\n}\n\npub async fn delete(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Path((project_id, doc_id)): Path<(i64, i64)>,\n) -> webserver::Result<()> {\n    let user_id = user\n        .username()\n        .ok_or_else(webserver::no_user_id)?\n        .to_string();\n\n    sqlx::query! {\n        \"DELETE FROM project_docs\n        WHERE project_id = $1 AND doc_id = $2 AND EXISTS (\n            SELECT id\n            FROM projects\n            WHERE id = $1 AND user_id = $3\n        )\n        RETURNING id\",\n        project_id,\n        doc_id,\n        user_id,\n    }\n    .fetch_optional(&*app.sql)\n    .await?\n    .map(|_| ())\n    .ok_or_else(|| Error::not_found(\"project doc not found\"))\n}\n"
  },
  {
    "path": "server/bleep/src/webserver/project/repo.rs",
    "content": "use axum::{\n    extract::{Path, Query},\n    Extension, Json,\n};\n\nuse crate::{\n    repo::RepoRef,\n    webserver::{self, middleware::User, repos::Repo, Error},\n    Application,\n};\n\n#[derive(serde::Serialize)]\npub struct ListItem {\n    // TODO: Can we remove this in favour of just the `repo_ref`?\n    repo: Repo,\n    branch: Option<String>,\n}\n\npub async fn list(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Path(project_id): Path<i64>,\n) -> webserver::Result<Json<Vec<ListItem>>> {\n    let user_id = user\n        .username()\n        .ok_or_else(webserver::no_user_id)?\n        .to_string();\n\n    let list = sqlx::query! {\n        \"SELECT repo_ref, branch\n        FROM project_repos\n        WHERE project_id = $1 AND EXISTS (\n            SELECT p.id\n            FROM projects p\n            WHERE p.id = $1 AND p.user_id = $2\n        )\",\n        project_id,\n        user_id,\n    }\n    .fetch_all(&*app.sql)\n    .await?\n    .into_iter()\n    .filter_map(|row| {\n        let repo_ref = row.repo_ref.parse().ok()?;\n        let entry = app.repo_pool.get(&repo_ref)?;\n        let repo = Repo::from((entry.key(), entry.get()));\n\n        Some(ListItem {\n            repo,\n            branch: row.branch,\n        })\n    })\n    .collect();\n\n    Ok(Json(list))\n}\n\n#[derive(serde::Deserialize)]\npub struct Add {\n    #[serde(rename = \"ref\")]\n    repo_ref: RepoRef,\n    branch: Option<String>,\n}\n\npub async fn add(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Path(project_id): Path<i64>,\n    Json(params): Json<Add>,\n) -> webserver::Result<()> {\n    let user_id = user\n        .username()\n        .ok_or_else(webserver::no_user_id)?\n        .to_string();\n\n    sqlx::query! {\n        \"SELECT id FROM projects WHERE id = ? AND user_id = ?\",\n        project_id,\n        user_id,\n    }\n    .fetch_optional(&*app.sql)\n    .await?\n    .ok_or_else(|| Error::not_found(\"project not found\"))?;\n\n    let repo_ref = params.repo_ref.to_string();\n\n    sqlx::query! {\n        \"INSERT INTO project_repos (project_id, repo_ref, branch) VALUES ($1, $2, $3)\",\n        project_id,\n        repo_ref,\n        params.branch,\n    }\n    .execute(&*app.sql)\n    .await\n    .map(|_| ())\n    .map_err(Error::internal)\n}\n\n#[derive(serde::Deserialize)]\npub struct Delete {\n    #[serde(rename = \"ref\")]\n    repo_ref: String,\n}\n\npub async fn delete(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Path(project_id): Path<i64>,\n    Query(Delete { repo_ref }): Query<Delete>,\n) -> webserver::Result<()> {\n    let user_id = user\n        .username()\n        .ok_or_else(webserver::no_user_id)?\n        .to_string();\n\n    sqlx::query! {\n        \"DELETE FROM project_repos\n        WHERE project_id = $1 AND repo_ref = $2 AND EXISTS (\n            SELECT id\n            FROM projects\n            WHERE id = $1 AND user_id = $3\n        )\n        RETURNING id\",\n        project_id,\n        repo_ref,\n        user_id,\n    }\n    .fetch_optional(&*app.sql)\n    .await?\n    .map(|_| ())\n    .ok_or_else(|| Error::not_found(\"project repo not found\"))\n}\n\n#[derive(serde::Deserialize)]\npub struct Put {\n    #[serde(rename = \"ref\")]\n    repo_ref: String,\n    branch: Option<String>,\n}\n\npub async fn put(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Path(project_id): Path<i64>,\n    Json(params): Json<Put>,\n) -> webserver::Result<()> {\n    let user_id = user\n        .username()\n        .ok_or_else(webserver::no_user_id)?\n        .to_string();\n\n    sqlx::query! {\n        \"SELECT id FROM projects WHERE id = ? AND user_id = ?\",\n        project_id,\n        user_id,\n    }\n    .fetch_optional(&*app.sql)\n    .await?\n    .ok_or_else(|| Error::not_found(\"project not found\"))?;\n\n    sqlx::query! {\n        \"UPDATE project_repos\n        SET branch = ?\n        WHERE project_id = ? AND repo_ref = ?\n        RETURNING id\",\n        params.branch,\n        project_id,\n        params.repo_ref,\n    }\n    .fetch_optional(&*app.sql)\n    .await?\n    .map(|_| ())\n    .ok_or_else(|| Error::not_found(\"association between project ID and repo ref not found\"))\n}\n"
  },
  {
    "path": "server/bleep/src/webserver/project.rs",
    "content": "use std::collections::HashMap;\n\nuse crate::{webserver, Application};\nuse axum::{extract::Path, Extension, Json};\nuse chrono::NaiveDateTime;\n\nuse super::{middleware::User, repos::Repo, Error};\n\npub mod doc;\npub mod repo;\n\nfn default_name() -> String {\n    \"New Project\".into()\n}\n\n#[derive(serde::Serialize)]\npub struct ListItem {\n    id: i64,\n    name: String,\n    modified_at: Option<NaiveDateTime>,\n    most_common_langs: Vec<String>,\n}\n\npub async fn list(\n    app: Extension<Application>,\n    user: Extension<User>,\n) -> webserver::Result<Json<Vec<ListItem>>> {\n    let user_id = user.username().ok_or_else(super::no_user_id)?.to_string();\n\n    let projects = sqlx::query! {\n        \"SELECT p.id, p.name, (\n            SELECT ss.modified_at\n            FROM studio_snapshots ss\n            JOIN studios s ON s.project_id = p.id AND ss.studio_id = s.id\n            ORDER BY ss.modified_at DESC\n            LIMIT 1\n        ) AS modified_at\n        FROM projects p\n        WHERE user_id = ?\",\n        user_id,\n    }\n    .fetch_all(&*app.sql)\n    .await?;\n\n    let most_common_langs = sqlx::query! {\n        \"SELECT pr.project_id, pr.repo_ref\n        FROM project_repos pr\n        JOIN projects p ON p.id = pr.project_id AND p.user_id = ?\",\n        user_id,\n    }\n    .fetch_all(&*app.sql)\n    .await?\n    .into_iter()\n    .filter_map(|row| {\n        let repo_ref = row.repo_ref.parse().ok()?;\n        let pool_entry = app.repo_pool.get(&repo_ref)?;\n        let repo = Repo::from((&repo_ref, pool_entry.get()));\n        Some((row.project_id, repo.most_common_lang?))\n    })\n    .fold(\n        HashMap::<_, Vec<_>>::new(),\n        |mut a, (project_id, most_common_lang)| {\n            a.entry(project_id).or_default().push(most_common_lang);\n            a\n        },\n    );\n\n    let list = projects\n        .into_iter()\n        .map(|row| ListItem {\n            id: row.id,\n            name: row.name.unwrap_or_else(default_name),\n            modified_at: row.modified_at,\n            most_common_langs: most_common_langs\n                .get(&row.id)\n                .map(Vec::clone)\n                .unwrap_or_default(),\n        })\n        .collect();\n\n    Ok(Json(list))\n}\n\n#[derive(serde::Deserialize)]\npub struct Create {\n    name: Option<String>,\n}\n\npub async fn create(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Json(params): Json<Create>,\n) -> webserver::Result<String> {\n    let user_id = user.username().ok_or_else(super::no_user_id)?.to_string();\n\n    let project_id = sqlx::query! {\n        \"INSERT INTO projects (user_id, name) VALUES (?, ?) RETURNING id\",\n        user_id,\n        params.name,\n    }\n    .fetch_one(&*app.sql)\n    .await?\n    .id;\n\n    Ok(project_id.to_string())\n}\n\n#[derive(serde::Serialize)]\npub struct Get {\n    id: i64,\n    name: String,\n    modified_at: Option<NaiveDateTime>,\n    most_common_langs: Vec<String>,\n}\n\npub async fn get(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Path(id): Path<i64>,\n) -> webserver::Result<Json<Get>> {\n    let user_id = user.username().ok_or_else(super::no_user_id)?.to_string();\n\n    let row = sqlx::query! {\n        \"SELECT name, (\n            SELECT ss.modified_at\n            FROM studio_snapshots ss\n            JOIN studios s ON s.project_id = $1 AND ss.studio_id = s.id\n            ORDER BY ss.modified_at DESC\n            LIMIT 1\n        ) AS modified_at\n        FROM projects\n        WHERE id = $1 AND user_id = $2\n        LIMIT 1\",\n        id,\n        user_id,\n    }\n    .fetch_one(&*app.sql)\n    .await\n    .map_err(Error::not_found)?;\n\n    let most_common_langs = sqlx::query! {\n        \"SELECT repo_ref\n        FROM project_repos\n        WHERE project_id = ?\",\n        id,\n    }\n    .fetch_all(&*app.sql)\n    .await?\n    .into_iter()\n    .filter_map(|row| row.repo_ref.parse().ok())\n    .filter_map(|repo_ref| app.repo_pool.get(&repo_ref))\n    .map(|entry| Repo::from((entry.key(), entry.get())))\n    .filter_map(|repo| repo.most_common_lang)\n    .collect();\n\n    Ok(Json(Get {\n        id,\n        name: row.name.unwrap_or_else(default_name),\n        modified_at: row.modified_at,\n        most_common_langs,\n    }))\n}\n\n#[derive(serde::Deserialize)]\npub struct Update {\n    name: String,\n}\n\npub async fn update(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Path(id): Path<i64>,\n    Json(update): Json<Update>,\n) -> webserver::Result<()> {\n    let user_id = user.username().ok_or_else(super::no_user_id)?.to_string();\n\n    sqlx::query! {\n        \"UPDATE projects SET name = ? WHERE id = ? AND user_id = ? RETURNING id\",\n        update.name,\n        id,\n        user_id,\n    }\n    .fetch_one(&*app.sql)\n    .await\n    .map(|_id| ())\n    .map_err(Error::internal)\n}\n\npub async fn delete(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Path(id): Path<i64>,\n) -> webserver::Result<()> {\n    let user_id = user.username().ok_or_else(super::no_user_id)?;\n\n    sqlx::query! {\n        \"DELETE FROM projects WHERE id = ? AND user_id = ? RETURNING id\",\n        id,\n        user_id,\n    }\n    .fetch_optional(&*app.sql)\n    .await?\n    .map(|_id| ())\n    .ok_or_else(|| Error::not_found(\"could not find project\"))\n}\n"
  },
  {
    "path": "server/bleep/src/webserver/query.rs",
    "content": "use axum::extract::Path;\n\nuse super::prelude::*;\nuse crate::{db::QueryLog, query::execute::ApiQuery, Application};\n\npub(super) async fn handle(\n    Path(project_id): Path<i64>,\n    Query(mut api_params): Query<ApiQuery>,\n    Extension(app): Extension<Application>,\n) -> impl IntoResponse {\n    QueryLog::new(&app.sql).insert(&api_params.q).await?;\n\n    api_params.project_id = project_id;\n\n    Arc::new(api_params)\n        .query(&app)\n        .await\n        .map(json)\n        .map_err(Error::from)\n}\n"
  },
  {
    "path": "server/bleep/src/webserver/repos.rs",
    "content": "use std::{collections::HashSet, hash::Hash, time::Duration};\n\nuse crate::{\n    background::{QueuedRepoStatus, SyncConfig},\n    repo::{Backend, BranchFilterConfig, FileFilterConfig, RepoRef, Repository, SyncStatus},\n    state::RepositoryPool,\n    Application,\n};\nuse axum::{\n    extract::{Query, State},\n    http::StatusCode,\n    response::{sse, IntoResponse, Sse},\n    Extension, Json,\n};\nuse chrono::{DateTime, NaiveDateTime, Utc};\nuse serde::{Deserialize, Serialize};\n\nuse super::prelude::*;\n\n#[derive(Serialize, Debug, PartialEq, Eq)]\npub(crate) struct Branch {\n    last_commit_unix_secs: i64,\n    name: String,\n}\n\n#[derive(Serialize, Debug, Eq)]\npub struct Repo {\n    pub(super) provider: Backend,\n    pub(super) name: String,\n    #[serde(rename = \"ref\")]\n    pub(super) repo_ref: RepoRef,\n    pub(super) local_duplicates: Vec<RepoRef>,\n    pub(super) sync_status: SyncStatus,\n    pub(super) last_update: DateTime<Utc>,\n    pub(super) last_index: Option<DateTime<Utc>>,\n    pub(super) most_common_lang: Option<String>,\n    pub(super) branch_filter: BranchFilterConfig,\n    pub(super) file_filter: FileFilterConfig,\n    pub(super) branches: Vec<Branch>,\n}\n\nimpl From<(&RepoRef, &Repository)> for Repo {\n    fn from((key, repo): (&RepoRef, &Repository)) -> Self {\n        let (head, branches) = 'branch_list: {\n            let default = (\"HEAD\".to_string(), vec![]);\n            let Ok(git) = gix::open(&repo.disk_path) else {\n                break 'branch_list default;\n            };\n\n            let head = git\n                .head()\n                .ok()\n                .and_then(|head| head.try_into_referent())\n                .map(|r| {\n                    if key.is_local() {\n                        r.name().shorten().to_string()\n                    } else {\n                        format!(\"origin/{}\", r.name().shorten())\n                    }\n                })\n                .unwrap_or_else(|| default.0.clone());\n\n            let Ok(refs) = git.references() else {\n                break 'branch_list default;\n            };\n\n            let Ok(refs) = refs.all() else {\n                break 'branch_list default;\n            };\n\n            let branches = if key.is_local() {\n                vec![]\n            } else {\n                use gix::bstr::ByteSlice;\n                let mut branches = refs\n                    .filter_map(Result::ok)\n                    .filter_map(|mut r| {\n                        let name = r.name().shorten().to_str_lossy().to_string();\n                        let last_commit_unix_secs = r\n                            .peel_to_id_in_place()\n                            .ok()?\n                            .object()\n                            .ok()?\n                            .try_into_commit()\n                            .ok()?\n                            .time()\n                            .ok()?\n                            .seconds;\n\n                        Some(Branch {\n                            name,\n                            last_commit_unix_secs,\n                        })\n                    })\n                    .filter(|b| {\n                        if key.is_remote() {\n                            b.name != \"origin/HEAD\" && b.name.starts_with(\"origin/\")\n                        } else {\n                            b.name != \"HEAD\" && !b.name.starts_with(\"origin/\")\n                        }\n                    })\n                    .collect::<Vec<_>>();\n\n                branches.sort_by_key(|b| b.last_commit_unix_secs);\n                branches\n            };\n\n            (head, branches)\n        };\n\n        tracing::trace!(?branches, \"branches\");\n\n        let branch_filter = {\n            use BranchFilterConfig::*;\n            match repo.branch_filter.clone() {\n                Some(All) => Select(vec![\".*\".to_string()]),\n                Some(Head) => Select(vec![head]),\n                Some(Select(mut list)) => {\n                    if let Some(pos) = list.iter().position(|i| i == &head) {\n                        list.remove(pos);\n                    }\n                    list.insert(0, head);\n                    Select(list)\n                }\n                None => Select(vec![head]),\n            }\n        };\n\n        Repo {\n            provider: key.backend(),\n            name: key.indexed_name(),\n            repo_ref: key.clone(),\n            sync_status: repo.pub_sync_status.clone(),\n            local_duplicates: vec![],\n            last_update: NaiveDateTime::from_timestamp_opt(repo.last_commit_unix_secs, 0)\n                .unwrap()\n                .and_local_timezone(Utc)\n                .unwrap(),\n            last_index: match repo.last_index_unix_secs {\n                0 => None,\n                other => Some(\n                    NaiveDateTime::from_timestamp_opt(other as i64, 0)\n                        .unwrap()\n                        .and_local_timezone(Utc)\n                        .unwrap(),\n                ),\n            },\n            most_common_lang: repo.most_common_lang.clone(),\n            file_filter: repo.file_filter.clone(),\n            branch_filter,\n            branches,\n        }\n    }\n}\n\nimpl Repo {\n    pub(crate) fn from_github(\n        local_duplicates: Vec<RepoRef>,\n        origin: &octocrab::models::Repository,\n    ) -> Self {\n        let name = origin.full_name.clone().unwrap();\n        Repo {\n            provider: Backend::Github,\n            repo_ref: RepoRef::new(Backend::Github, &name).unwrap(),\n            sync_status: SyncStatus::Uninitialized,\n            local_duplicates,\n            name,\n            last_update: origin.pushed_at.unwrap(),\n            last_index: None,\n            most_common_lang: None,\n            branch_filter: BranchFilterConfig::Select(vec![]),\n            file_filter: Default::default(),\n            branches: vec![],\n        }\n    }\n}\n\nimpl Hash for Repo {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.repo_ref.hash(state)\n    }\n}\n\nimpl PartialEq for Repo {\n    fn eq(&self, other: &Self) -> bool {\n        self.repo_ref == other.repo_ref\n    }\n}\n\n// since it's an output type, there's no downside to having\n// excessively large variants\n#[allow(clippy::large_enum_variant)]\n#[derive(Serialize)]\n#[serde(rename_all = \"snake_case\")]\npub(crate) enum ReposResponse {\n    List(Vec<Repo>),\n    Item(Repo),\n    SyncQueue(Vec<QueuedRepoStatus>),\n    SyncQueued,\n    Deleted,\n}\n\nimpl super::ApiResponse for ReposResponse {}\n\n#[allow(unused_mut)]\npub(super) fn router() -> Router {\n    use axum::routing::*;\n\n    let mut indexed = get(indexed).put(set_indexed).delete(delete_by_id);\n\n    Router::new()\n        .route(\"/\", get(available))\n        .route(\"/queue\", get(queue))\n        .route(\"/status\", get(index_status))\n        .route(\"/indexed\", indexed)\n        .route(\"/sync\", get(sync).delete(delete_sync))\n}\n\n/// Get a stream of status notifications about the indexing of each repository\n/// This endpoint opens an SSE stream\n//\npub(super) async fn index_status(Extension(app): Extension<Application>) -> impl IntoResponse {\n    let mut receiver = app.sync_queue.subscribe();\n\n    Sse::new(async_stream::stream! {\n        loop {\n            if let Ok(event) = receiver.recv().await {\n                yield sse::Event::default().json_data(event).map_err(Box::new);\n            }\n        }\n    })\n    .keep_alive(\n        sse::KeepAlive::new()\n            .interval(Duration::from_secs(5))\n            .event(sse::Event::default().event(\"heartbeat\")),\n    )\n}\n\n#[derive(Deserialize)]\npub(super) struct IndexedParams {\n    repo: Option<RepoRef>,\n}\n\n#[derive(Deserialize)]\npub(crate) struct RepoParams {\n    pub(crate) repo: RepoRef,\n    #[serde(default)]\n    pub(crate) shallow: bool,\n}\n\n/// Live report of the state of the sync queue\n//\npub(super) async fn queue(State(app): State<Application>) -> impl IntoResponse {\n    json(ReposResponse::SyncQueue(app.sync_queue.read_queue().await))\n}\n\n/// Retrieve all indexed repositories\n//\npub(super) async fn indexed(\n    Query(IndexedParams { repo }): Query<IndexedParams>,\n    app: State<Application>,\n) -> Result<impl IntoResponse> {\n    if let Some(repo) = repo {\n        return get_by_id(\n            Query(RepoParams {\n                repo,\n                shallow: false,\n            }),\n            app,\n        )\n        .await;\n    }\n\n    let mut repos = vec![];\n    app.0\n        .repo_pool\n        .scan_async(|k, v| repos.push(Repo::from((k, v))))\n        .await;\n\n    Ok(json(ReposResponse::List(repos)))\n}\n\n/// Get details of an indexed repository based on their id\npub(super) async fn get_by_id(\n    Query(RepoParams { repo, .. }): Query<RepoParams>,\n    State(app): State<Application>,\n) -> Result<Json<super::Response<'static>>> {\n    match app\n        .repo_pool\n        .read_async(&repo, |k, v| ReposResponse::Item(Repo::from((k, v))))\n        .await\n    {\n        Some(result) => Ok(json(result)),\n        None => Err(Error::new(ErrorKind::NotFound, \"Can't find repository\")),\n    }\n}\n\n/// Delete a repository from the disk and any indexes\n//\npub(super) async fn delete_by_id(\n    Query(RepoParams { repo, .. }): Query<RepoParams>,\n    State(app): State<Application>,\n) -> Result<impl IntoResponse> {\n    // TODO: We can refactor `repo_pool` to also hold queued repos, instead of doing a calculation\n    // like this which is prone to timing issues.\n    let found = app.write_index().remove(repo).await.is_some();\n\n    if found {\n        Ok(json(ReposResponse::Deleted))\n    } else {\n        Err(Error::new(ErrorKind::NotFound, \"Repo not found\"))\n    }\n}\n\n/// Synchronize a repo by its id\npub(super) async fn sync(\n    Query(RepoParams { repo, shallow }): Query<RepoParams>,\n    State(app): State<Application>,\n) -> Result<impl IntoResponse> {\n    // TODO: We can refactor `repo_pool` to also hold queued repos, instead of doing a calculation\n    // like this which is prone to timing issues.\n    app.write_index()\n        .enqueue(SyncConfig::new(app.clone(), repo).shallow(shallow))\n        .await;\n\n    Ok(json(ReposResponse::SyncQueued))\n}\n\n/// Synchronize a repo by its id\npub(super) async fn delete_sync(\n    Query(RepoParams { repo, .. }): Query<RepoParams>,\n    State(app): State<Application>,\n) -> Result<impl IntoResponse> {\n    app.write_index().cancel(repo).await;\n    Ok(json(ReposResponse::SyncQueued))\n}\n\n/// List all repositories that are either indexed, or available for indexing\n//\npub(super) async fn available(State(app): State<Application>) -> impl IntoResponse {\n    let unknown_github = app\n        .credentials\n        .github()\n        .map(|gh| gh.repositories)\n        .unwrap_or_default()\n        .iter()\n        .map(|repo| {\n            let mut local_duplicates = vec![];\n            app.repo_pool.scan(|k, v| {\n                // either `ssh_url` or `clone_url` should match what we generate.\n                //\n                // also note that this is quite possibly not the\n                // most efficient way of doing this, but the\n                // number of repos should be small, so even n^2\n                // should be fast.\n                //\n                // most of the time is spent in the network.\n                if [\n                    repo.ssh_url.as_deref().unwrap_or_default().to_lowercase(),\n                    repo.clone_url\n                        .as_ref()\n                        .map(|url| url.as_str())\n                        .unwrap_or_default()\n                        .to_lowercase(),\n                ]\n                .contains(&v.remote.to_string().to_lowercase())\n                {\n                    local_duplicates.push(k.clone())\n                }\n            });\n\n            Repo::from_github(local_duplicates, repo)\n        })\n        .collect::<HashSet<_>>();\n\n    let repos = list_unique_repos(app.repo_pool.clone(), unknown_github).await;\n    (StatusCode::OK, Json(ReposResponse::List(repos)))\n}\n\n#[derive(Serialize, Deserialize, Debug)]\npub(super) struct SetIndexed {\n    indexed: Vec<RepoRef>,\n}\n\n/// Update the list of repositories that are currently being indexed.\n/// This will automatically trigger a sync of currently un-indexed repositories.\n//\npub(super) async fn set_indexed(\n    State(app): State<Application>,\n    Json(new_list): Json<SetIndexed>,\n) -> impl IntoResponse {\n    let mut repo_list = new_list.indexed.into_iter().collect::<HashSet<_>>();\n\n    app.repo_pool\n        .for_each_async(|k, existing| {\n            if !repo_list.contains(k) {\n                existing.mark_removed();\n                repo_list.insert(k.to_owned());\n            }\n        })\n        .await;\n\n    app.write_index()\n        .enqueue_all(repo_list.into_iter().collect())\n        .await;\n\n    json(ReposResponse::SyncQueued)\n}\n\n#[derive(Deserialize)]\npub(super) struct ScanRequest {\n    /// The path to scan\n    path: String,\n}\n\n/// Gather recognized repository types from the filesystem\n///\npub(super) async fn scan_local(\n    Query(scan_request): Query<ScanRequest>,\n    State(app): State<Application>,\n) -> impl IntoResponse {\n    let root = std::path::Path::new(&scan_request.path);\n\n    if app.allow_path(root) {\n        Ok(json(ReposResponse::List(\n            crate::remotes::gather_repo_roots(root, app.config.source.repo_dir())\n                .into_iter()\n                .map(|reporef| {\n                    let mut repo = Repository::local_from(&reporef);\n                    repo.sync_status = SyncStatus::Uninitialized;\n\n                    (&reporef, &repo).into()\n                })\n                .collect(),\n        )))\n    } else {\n        Err(Error::user(\"scanning not allowed\").with_status(StatusCode::UNAUTHORIZED))\n    }\n}\n\nasync fn list_unique_repos(repo_pool: RepositoryPool, other: HashSet<Repo>) -> Vec<Repo> {\n    let mut repos = HashSet::new();\n    repo_pool\n        .scan_async(|k, v| {\n            // this will hash to the same thing as another object due\n            // to `Hash` proxying to `repo_ref`, so stay on the safe\n            // side and check like good citizens\n            let repo = Repo::from((k, v));\n            repos.insert(repo);\n        })\n        .await;\n\n    repos.extend(other);\n    repos.into_iter().collect()\n}\n\n#[cfg(test)]\nmod test {\n    use std::collections::HashSet;\n\n    use crate::repo::{GitProtocol, GitRemote, RepoRef, RepoRemote::Git, Repository, SyncStatus};\n\n    use super::{list_unique_repos, Repo, RepositoryPool};\n\n    #[tokio::test]\n    async fn unique_repos_only() {\n        let repo_pool = RepositoryPool::default();\n        repo_pool\n            .insert(\n                RepoRef::try_from(\"github.com/test/test\").unwrap(),\n                Repository {\n                    disk_path: \"/repo\".into(),\n                    remote: Git(GitRemote {\n                        protocol: GitProtocol::Https,\n                        host: \"github.com\".into(),\n                        address: \"test/test\".into(),\n                    }),\n                    sync_status: SyncStatus::Done,\n                    last_commit_unix_secs: 123456,\n                    last_index_unix_secs: 123456,\n                    most_common_lang: Default::default(),\n                    branch_filter: Default::default(),\n                    file_filter: Default::default(),\n                    pub_sync_status: Default::default(),\n                    locked: Default::default(),\n                    shallow: Default::default(),\n                },\n            )\n            .unwrap();\n        repo_pool\n            .insert(\n                RepoRef::try_from(\"local//code/test2\").unwrap(),\n                Repository {\n                    disk_path: \"/repo2\".into(),\n                    remote: Git(GitRemote {\n                        protocol: GitProtocol::Https,\n                        host: \"github.com\".into(),\n                        address: \"test/test2\".into(),\n                    }),\n                    sync_status: SyncStatus::Done,\n                    last_commit_unix_secs: 123456,\n                    last_index_unix_secs: 123456,\n                    most_common_lang: Default::default(),\n                    branch_filter: Default::default(),\n                    file_filter: Default::default(),\n                    pub_sync_status: Default::default(),\n                    locked: Default::default(),\n                    shallow: Default::default(),\n                },\n            )\n            .unwrap();\n\n        let mut gh_list = HashSet::new();\n        gh_list.insert(\n            (\n                &RepoRef::try_from(\"github.com/test/test\").unwrap(),\n                &Repository {\n                    disk_path: \"/unused\".into(),\n                    remote: Git(GitRemote {\n                        protocol: GitProtocol::Https,\n                        host: \"github.com\".into(),\n                        address: \"test/test\".into(),\n                    }),\n                    sync_status: SyncStatus::Uninitialized,\n                    last_commit_unix_secs: 123456,\n                    last_index_unix_secs: 0,\n                    most_common_lang: Default::default(),\n                    branch_filter: Default::default(),\n                    file_filter: Default::default(),\n                    pub_sync_status: Default::default(),\n                    locked: Default::default(),\n                    shallow: Default::default(),\n                },\n            )\n                .into(),\n        );\n\n        let mut ghrepo_2: Repo = (\n            &RepoRef::try_from(\"github.com/test/test2\").unwrap(),\n            &Repository {\n                disk_path: \"/unused\".into(),\n                remote: Git(GitRemote {\n                    protocol: GitProtocol::Https,\n                    host: \"github.com\".into(),\n                    address: \"test/test2\".into(),\n                }),\n                sync_status: SyncStatus::Uninitialized,\n                last_commit_unix_secs: 123456,\n                last_index_unix_secs: 0,\n                most_common_lang: Default::default(),\n                branch_filter: Default::default(),\n                file_filter: Default::default(),\n                pub_sync_status: Default::default(),\n                locked: Default::default(),\n                shallow: Default::default(),\n            },\n        )\n            .into();\n\n        ghrepo_2.local_duplicates = vec![RepoRef::try_from(\"local//code/test2\").unwrap()];\n        gh_list.insert(ghrepo_2);\n\n        let unique = list_unique_repos(repo_pool, gh_list)\n            .await\n            .into_iter()\n            .map(|repo| repo.repo_ref)\n            .collect::<HashSet<_>>();\n        assert_eq!(\n            HashSet::from([\n                RepoRef::try_from(\"local//code/test2\").unwrap(),\n                RepoRef::try_from(\"github.com/test/test\").unwrap(),\n                RepoRef::try_from(\"github.com/test/test2\").unwrap(),\n            ]),\n            unique\n        );\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/webserver/search.rs",
    "content": "use super::prelude::*;\nuse crate::{\n    query::{\n        execute::{\n            ApiQuery, FileResultData, PagingMetadata, QueryResponse, QueryResult, ResultStats,\n        },\n        parser::{self},\n    },\n    semantic::{self, Semantic},\n    Application,\n};\nuse axum::extract::Path;\nuse tracing::error;\n\npub(super) async fn semantic_code(\n    Query(args): Query<ApiQuery>,\n    Extension(semantic): Extension<Semantic>,\n) -> impl IntoResponse {\n    match parser::parse_nl(&args.q.clone()) {\n        Ok(q) => semantic::execute::execute(semantic, q, args)\n            .await\n            .map(json)\n            .map_err(Error::from),\n        Err(err) => {\n            error!(?err, \"Couldn't parse query\");\n            Err(Error::new(ErrorKind::UpstreamService, \"error\"))\n        }\n    }\n}\n\n#[axum::debug_handler]\npub(super) async fn fuzzy_path(\n    Path(project_id): Path<i64>,\n    Query(args): Query<ApiQuery>,\n    Extension(app): Extension<Application>,\n    Extension(indexes): Extension<Arc<Indexes>>,\n) -> Result<impl IntoResponse> {\n    let q = parser::parse_nl(&args.q).map_err(|err| {\n        error!(?err, \"Couldn't parse query\");\n        Error::new(ErrorKind::UpstreamService, \"parse error\")\n    })?;\n\n    let target = q.target();\n    let target = target.as_deref().ok_or_else(|| {\n        error!(?q, \"Query has no target\");\n        Error::new(ErrorKind::UpstreamService, \"Query has no target\")\n    })?;\n\n    let repo_refs = sqlx::query! {\n        \"SELECT repo_ref\n        FROM project_repos\n        WHERE project_id = ?\",\n        project_id,\n    }\n    .fetch_all(&*app.sql)\n    .await?\n    .into_iter()\n    .map(|row| row.repo_ref)\n    .filter_map(|rr| rr.parse().ok());\n\n    let data = indexes\n        .file\n        .skim_fuzzy_path_match(\n            repo_refs,\n            target,\n            q.first_branch().as_deref(),\n            std::iter::empty(),\n            args.page_size,\n        )\n        .await\n        .map(|c: crate::indexes::reader::FileDocument| {\n            QueryResult::FileResult(FileResultData::new(\n                c.repo_name,\n                c.relative_path,\n                c.repo_ref,\n                c.lang,\n                c.branches,\n                c.indexed,\n                c.is_dir,\n            ))\n        })\n        .collect::<Vec<QueryResult>>();\n\n    Ok(json(QueryResponse {\n        count: data.len(),\n        data,\n        metadata: PagingMetadata::new(args.page, args.page_size, None),\n        stats: ResultStats::default(),\n    }))\n}\n"
  },
  {
    "path": "server/bleep/src/webserver/studio/diff.rs",
    "content": "use std::fmt;\n\nuse anyhow::{bail, Result};\nuse lazy_regex::regex;\n\npub fn extract(chat_response: &str) -> Result<impl Iterator<Item = DiffChunk>> {\n    Ok(relaxed_parse(&extract_diff(chat_response)?)\n        // We eagerly collect the iterator, and re-create it, as our input string is created in and\n        // can't escape this function. This also allows us to catch parse errors earlier.\n        .collect::<Vec<_>>()\n        .into_iter())\n}\n\nfn extract_diff(chat_response: &str) -> Result<String> {\n    let fragments = regex!(r#\"^```diff.*?^(.*?)^```.*?($|\\z)\"#sm)\n        .captures_iter(chat_response)\n        .map(|c| c.get(1).unwrap().as_str())\n        .collect::<String>();\n\n    if fragments.is_empty() {\n        bail!(\"chat response didn't contain any diff blocks\");\n    } else {\n        Ok(fragments)\n    }\n}\n\n/// Parse a diff, allowing for some formatting errors.\npub fn relaxed_parse(diff: &str) -> impl Iterator<Item = DiffChunk> + '_ {\n    split_chunks(diff).map(|mut chunk| {\n        chunk.fixup_hunks();\n        chunk\n    })\n}\n\nfn split_chunks(diff: &str) -> impl Iterator<Item = DiffChunk> + '_ {\n    let chunk_regex = regex!(r#\"(?: (.*)$\\n^\\+\\+\\+ (.*)$)\\n((?:^$\\n?|^[-+@ ].*\\n?)+)\"#m);\n\n    regex!(\"^---\"m).split(diff).filter_map(|chunk| {\n        let caps = chunk_regex.captures(chunk)?;\n        Some(DiffChunk {\n            src: match caps.get(1).unwrap().as_str() {\n                \"/dev/null\" => None,\n                s => Some(s.to_owned()),\n            },\n            dst: match caps.get(2).unwrap().as_str() {\n                \"/dev/null\" => None,\n                s => Some(s.to_owned()),\n            },\n            hunks: split_hunks(caps.get(3).unwrap().as_str()).collect(),\n        })\n    })\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct DiffChunk {\n    pub src: Option<String>,\n    pub dst: Option<String>,\n    pub hunks: Vec<DiffHunk>,\n}\n\nimpl DiffChunk {\n    pub fn fixup_hunks(&mut self) {\n        self.hunks.retain_mut(|h| {\n            if !h.fixup() {\n                false\n            } else {\n                h.lines.iter().any(|l| !matches!(l, Line::Context(_)))\n            }\n        });\n    }\n}\n\nimpl fmt::Display for DiffChunk {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let hunks_str = self.hunks.iter().map(|h| h.to_string()).collect::<String>();\n\n        let src = if let Some(s) = self.src.as_deref() {\n            s\n        } else {\n            \"/dev/null\"\n        };\n\n        let dst = if let Some(s) = self.dst.as_deref() {\n            s\n        } else {\n            \"/dev/null\"\n        };\n\n        write!(f, \"--- {src}\\n+++ {dst}\\n{hunks_str}\")\n    }\n}\n\nfn split_hunks(hunks: &str) -> impl Iterator<Item = DiffHunk> + '_ {\n    let hunk_regex =\n        regex!(r#\"@@ -(\\d+)(?:,(\\d+))? \\+(\\d+)(?:,(\\d+))? @@.*\\n((?:^\\n|^[-+ ].*\\n?)*)\"#m);\n\n    hunk_regex.captures_iter(hunks).map(|caps| DiffHunk {\n        src_line: caps.get(1).unwrap().as_str().parse().unwrap(),\n        src_count: caps\n            .get(2)\n            .and_then(|m| m.as_str().parse().ok())\n            .unwrap_or(0),\n        dst_line: caps.get(3).unwrap().as_str().parse().unwrap(),\n        dst_count: caps\n            .get(4)\n            .and_then(|m| m.as_str().parse().ok())\n            .unwrap_or(0),\n        lines: {\n            caps.get(5)\n                .unwrap()\n                .as_str()\n                .lines()\n                .map(|l| {\n                    if !l.is_empty() {\n                        l.split_at(1)\n                    } else {\n                        (\" \", \"\")\n                    }\n                })\n                .map(|(type_, line)| match type_ {\n                    \" \" => Line::Context(line.into()),\n                    \"+\" => Line::Add(line.into()),\n                    \"-\" => Line::Del(line.into()),\n                    _ => unreachable!(\"unknown character slipped through regex\"),\n                })\n                .collect()\n        },\n    })\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct DiffHunk {\n    pub src_line: usize,\n    pub dst_line: usize,\n    pub src_count: usize,\n    pub dst_count: usize,\n\n    pub lines: Vec<Line>,\n}\n\nimpl DiffHunk {\n    fn fixup(&mut self) -> bool {\n        let src = self\n            .lines\n            .iter()\n            .filter_map(|line| match line {\n                Line::Context(l) => Some(format!(\"{l}\\n\")),\n                Line::Add(_) => None,\n                Line::Del(l) => Some(format!(\"{l}\\n\")),\n            })\n            .collect::<String>();\n\n        let dst = self\n            .lines\n            .iter()\n            .filter_map(|line| match line {\n                Line::Context(l) => Some(format!(\"{l}\\n\")),\n                Line::Add(l) => Some(format!(\"{l}\\n\")),\n                Line::Del(_) => None,\n            })\n            .collect::<String>();\n\n        let patch = diffy::DiffOptions::default()\n            .set_context_len(usize::MAX)\n            .create_patch(&src, &dst);\n        let patch = patch.to_string();\n\n        let mut new_hunks = split_hunks(&patch).collect::<Vec<_>>();\n\n        if new_hunks.is_empty() {\n            return false;\n        }\n\n        assert_eq!(\n            new_hunks.len(),\n            1,\n            \"regenerated hunk's patch was malformed:\\n\\n{patch}\"\n        );\n        self.lines = new_hunks.pop().unwrap().lines.into_iter().collect();\n\n        self.src_count = self\n            .lines\n            .iter()\n            .map(|l| match l {\n                Line::Context(_) | Line::Del(_) => 1,\n                Line::Add(_) => 0,\n            })\n            .sum();\n\n        self.dst_count = self\n            .lines\n            .iter()\n            .map(|l| match l {\n                Line::Context(_) | Line::Add(_) => 1,\n                Line::Del(_) => 0,\n            })\n            .sum();\n\n        true\n    }\n}\n\nimpl fmt::Display for DiffHunk {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        writeln!(\n            f,\n            \"@@ -{},{} +{},{} @@\",\n            self.src_line, self.src_count, self.dst_line, self.dst_count\n        )?;\n\n        for line in &self.lines {\n            <Line as fmt::Display>::fmt(line, f)?;\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum Line {\n    Context(String),\n    Add(String),\n    Del(String),\n}\n\nimpl fmt::Display for Line {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            Line::Context(line) => writeln!(f, \" {line}\"),\n            Line::Add(line) => writeln!(f, \"+{line}\"),\n            Line::Del(line) => writeln!(f, \"-{line}\"),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    use pretty_assertions::assert_eq;\n\n    #[test]\n    fn test_extract_diff() {\n        let s = \"```diff\nfoo bar\n```\";\n\n        assert_eq!(extract_diff(s).unwrap(), \"foo bar\\n\");\n    }\n\n    #[test]\n    fn test_extract_diff_complex() {\n        let s = \"```diff\nx\n\n    ```diff\nfoo bar\n ```\n```\";\n\n        assert_eq!(\n            extract_diff(s).unwrap(),\n            \"x\\n\\n    ```diff\\nfoo bar\\n ```\\n\"\n        );\n    }\n\n    #[test]\n    fn test_relaxed_parse() {\n        let s = \"\\\n--- foo.rs\n+++ foo.rs\n@@ -1,1 +1,1 @@\n-foo\n+bar\n@@ -10,1 +10,1 @@\n quux\n quux2\n+quux3\n quux4\n--- bar.rs\n+++ bar.rs\n@@ -10 +10 @@\n-bar\n+fred\n@@ -100,1 +100,1 @@\n baz\n-bar\n+thud\n baz\n+thud\n baz\";\n\n        let expected = \"\\\n--- foo.rs\n+++ foo.rs\n@@ -1,1 +1,1 @@\n-foo\n+bar\n@@ -10,3 +10,4 @@\n quux\n quux2\n+quux3\n quux4\n--- bar.rs\n+++ bar.rs\n@@ -10,1 +10,1 @@\n-bar\n+fred\n@@ -100,4 +100,5 @@\n baz\n-bar\n+thud\n baz\n+thud\n baz\n\";\n        let output = relaxed_parse(s)\n            .map(|chunk| chunk.to_string())\n            .collect::<String>();\n        assert_eq!(expected, output);\n    }\n\n    #[test]\n    fn test_split_hunks() {\n        let hunks = \"@@ -1,1 +1,1 @@\n context\n the line right below this one is intentionally empty\n\n-foo\n+bar\n@@ -10,1 +10,2 @@\n-bar\n+quux\n+quux2\";\n\n        let expected = vec![\n            DiffHunk {\n                src_line: 1,\n                src_count: 1,\n                dst_line: 1,\n                dst_count: 1,\n                lines: vec![\n                    Line::Context(\"context\".to_owned()),\n                    Line::Context(\n                        \"the line right below this one is intentionally empty\".to_owned(),\n                    ),\n                    Line::Context(\"\".to_owned()),\n                    Line::Del(\"foo\".to_owned()),\n                    Line::Add(\"bar\".to_owned()),\n                ],\n            },\n            DiffHunk {\n                src_line: 10,\n                src_count: 1,\n                dst_line: 10,\n                dst_count: 2,\n                lines: vec![\n                    Line::Del(\"bar\".to_owned()),\n                    Line::Add(\"quux\".to_owned()),\n                    Line::Add(\"quux2\".to_owned()),\n                ],\n            },\n        ];\n\n        let output = split_hunks(hunks).collect::<Vec<_>>();\n\n        assert_eq!(expected, output);\n    }\n\n    #[test]\n    fn test_split_chunks() {\n        let diff = \"    A simple diff description.\n\n--- foo.rs\n+++ foo.rs\n@@ -1,1 +1,1 @@\n context\n the line right below this one is intentionally empty\n\n-foo\n+bar\n--- bar.rs\n+++ bar.rs\n@@ -10,1 +10,2 @@\n-bar\n+quux\n+quux2\";\n\n        let expected = vec![\n            DiffChunk {\n                src: Some(\"foo.rs\".to_owned()),\n                dst: Some(\"foo.rs\".to_owned()),\n                hunks: vec![DiffHunk {\n                    src_line: 1,\n                    src_count: 1,\n                    dst_line: 1,\n                    dst_count: 1,\n                    lines: vec![\n                        Line::Context(\"context\".to_owned()),\n                        Line::Context(\n                            \"the line right below this one is intentionally empty\".to_owned(),\n                        ),\n                        Line::Context(\"\".to_owned()),\n                        Line::Del(\"foo\".to_owned()),\n                        Line::Add(\"bar\".to_owned()),\n                    ],\n                }],\n            },\n            DiffChunk {\n                src: Some(\"bar.rs\".to_owned()),\n                dst: Some(\"bar.rs\".to_owned()),\n                hunks: vec![DiffHunk {\n                    src_line: 10,\n                    src_count: 1,\n                    dst_line: 10,\n                    dst_count: 2,\n                    lines: vec![\n                        Line::Del(\"bar\".to_owned()),\n                        Line::Add(\"quux\".to_owned()),\n                        Line::Add(\"quux2\".to_owned()),\n                    ],\n                }],\n            },\n        ];\n\n        let output = split_chunks(diff).collect::<Vec<_>>();\n\n        assert_eq!(expected, output);\n    }\n\n    #[test]\n    fn test_bug_split() {\n        let chat_response = r#\"```diff\n--- server/bleep/src/analytics.rs\n+++ server/bleep/src/analytics.rs\n@@ -215,6 +215,22 @@ impl RudderHub {\n             }));\n         }\n     }\n+    \n+    pub fn track_index_repo(&self, user: &crate::webserver::middleware::User, repo_ref: RepoRef) {\n+        if let Some(options) = &self.options {\n+            self.send(Message::Track(Track {\n+                user_id: Some(self.tracking_id(user.username())),\n+                event: \"index repo\".to_owned(),\n+                properties: Some(json!({\n+                    \"device_id\": self.device_id(),\n+                    \"repo_ref\": repo_ref.to_string(),\n+                    \"package_metadata\": options.package_metadata,\n+                })),\n+                ..Default::default()\n+            }));\n+        }\n+    }\n }\n \n impl From<Option<String>> for DeviceId {\n\n--- server/bleep/src/indexes.rs\n+++ server/bleep/src/indexes.rs\n@@ -61,7 +61,9 @@ impl<'a> GlobalWriteHandle<'a> {\n     }\n \n     pub(crate) async fn index(\n-        &self,\n+        &self,\n+        analytics: &RudderHub,  // Pass in the RudderHub instance\n+        user: &crate::webserver::middleware::User,  // Pass in the current user\n         sync_handle: &SyncHandle,\n         repo: &Repository,\n     ) -> Result<Arc<RepoMetadata>, RepoError> {\n@@ -70,6 +72,9 @@ impl<'a> GlobalWriteHandle<'a> {\n \n         for h in &self.handles {\n             h.index(sync_handle, repo, &metadata).await?;\n+            \n+            // Track the repo indexing event\n+            analytics.track_index_repo(user, repo.repo_ref.clone());\n         }\n \n         Ok(metadata)\n```\"#;\n        let expected = vec![\n            DiffChunk {\n                src: Some(\"server/bleep/src/analytics.rs\".to_owned()),\n                dst: Some(\"server/bleep/src/analytics.rs\".to_owned()),\n                hunks: vec![DiffHunk {\n                    src_line: 215,\n                    src_count: 7,\n                    dst_line: 215,\n                    dst_count: 22,\n                    lines: vec![\n                        Line::Context(\"            }));\".to_owned()),\n                        Line::Context(\"        }\".to_owned()),\n                        Line::Context(\"    }\".to_owned()),\n                        Line::Add(\"    \".to_owned()),\n                        Line::Add(\"    pub fn track_index_repo(&self, user: &crate::webserver::middleware::User, repo_ref: RepoRef) {\".to_owned()),\n                        Line::Add(r#\"        if let Some(options) = &self.options {\"#.to_owned()),\n                        Line::Add(r#\"            self.send(Message::Track(Track {\"#.to_owned()),\n                        Line::Add(r#\"                user_id: Some(self.tracking_id(user.username())),\"#.to_owned()),\n                        Line::Add(r#\"                event: \"index repo\".to_owned(),\"#.to_owned()),\n                        Line::Add(r#\"                properties: Some(json!({\"#.to_owned()),\n                        Line::Add(r#\"                    \"device_id\": self.device_id(),\"#.to_owned()),\n                        Line::Add(r#\"                    \"repo_ref\": repo_ref.to_string(),\"#.to_owned()),\n                        Line::Add(r#\"                    \"package_metadata\": options.package_metadata,\"#.to_owned()),\n                        Line::Add(r#\"                })),\"#.to_owned()),\n                        Line::Add(r#\"                ..Default::default()\"#.to_owned()),\n                        Line::Add(r#\"            }));\"#.to_owned()),\n                        Line::Add(r#\"        }\"#.to_owned()),\n                        Line::Add(r#\"    }\"#.to_owned()),\n                        Line::Context(\"}\".to_owned()),\n                        Line::Context(\"\".to_owned()),\n                        Line::Context(\"impl From<Option<String>> for DeviceId {\".to_owned()),\n                        Line::Context(\"\".to_owned()),\n                    ],\n                }],\n            },\n            DiffChunk {\n                src: Some(\"server/bleep/src/indexes.rs\".to_owned()),\n                dst: Some(\"server/bleep/src/indexes.rs\".to_owned()),\n                hunks: vec![\n                    DiffHunk {\n                        src_line: 61,\n                        src_count: 7,\n                        dst_line: 61,\n                        dst_count: 9,\n                        lines: vec![\n                            Line::Context(r#\"    }\"#.to_owned()),\n                            Line::Context(r#\"\"#.to_owned()),\n                            Line::Context(r#\"    pub(crate) async fn index(\"#.to_owned()),\n                            Line::Context(r#\"        &self,\"#.to_owned()),\n                            Line::Add(r#\"        analytics: &RudderHub,  // Pass in the RudderHub instance\"#.to_owned()),\n                            Line::Add(r#\"        user: &crate::webserver::middleware::User,  // Pass in the current user\"#.to_owned()),\n                            Line::Context(r#\"        sync_handle: &SyncHandle,\"#.to_owned()),\n                            Line::Context(r#\"        repo: &Repository,\"#.to_owned()),\n                            Line::Context(r#\"    ) -> Result<Arc<RepoMetadata>, RepoError> {\"#.to_owned()),\n                        ],\n                    },\n                    DiffHunk {\n                        src_line: 70,\n                        src_count: 6,\n                        dst_line: 72,\n                        dst_count: 9,\n                        lines: vec![\n                            Line::Context(r#\"\"#.to_owned()),\n                            Line::Context(r#\"        for h in &self.handles {\"#.to_owned()),\n                            Line::Context(r#\"            h.index(sync_handle, repo, &metadata).await?;\"#.to_owned()),\n                            Line::Add(r#\"            \"#.to_owned()),\n                            Line::Add(r#\"            // Track the repo indexing event\"#.to_owned()),\n                            Line::Add(r#\"            analytics.track_index_repo(user, repo.repo_ref.clone());\"#.to_owned()),\n                            Line::Context(r#\"        }\"#.to_owned()),\n                            Line::Context(r#\"\"#.to_owned()),\n                            Line::Context(r#\"        Ok(metadata)\"#.to_owned()),\n                        ],\n                    },\n                ],\n            },\n        ];\n\n        let output = extract(chat_response).unwrap().collect::<Vec<_>>();\n\n        assert_eq!(expected, output);\n    }\n\n    #[test]\n    fn test_split_chunks_no_count() {}\n\n    #[test]\n    fn test_fixup_remove_redundancy() {\n        let mut hunk = DiffHunk {\n            src_line: 10,\n            src_count: 5,\n            dst_line: 10,\n            dst_count: 5,\n            lines: vec![\n                Line::Del(\"fn main() {\".to_owned()),\n                Line::Add(\"fn main() {\".to_owned()),\n                Line::Context(\"    let a = 123;\".to_owned()),\n                Line::Del(\"    println!(\\\"the value of `a` is {a:?}\\\");\".to_owned()),\n                Line::Add(\"    dbg!(&a);\".to_owned()),\n                Line::Context(\"    drop(a);\".to_owned()),\n                Line::Context(\"}\".to_owned()),\n            ],\n        };\n\n        hunk.fixup();\n\n        let expected = DiffHunk {\n            src_line: 10,\n            src_count: 5,\n            dst_line: 10,\n            dst_count: 5,\n            lines: vec![\n                Line::Context(\"fn main() {\".to_owned()),\n                Line::Context(\"    let a = 123;\".to_owned()),\n                Line::Del(\"    println!(\\\"the value of `a` is {a:?}\\\");\".to_owned()),\n                Line::Add(\"    dbg!(&a);\".to_owned()),\n                Line::Context(\"    drop(a);\".to_owned()),\n                Line::Context(\"}\".to_owned()),\n            ],\n        };\n\n        assert_eq!(expected, hunk);\n    }\n\n    #[test]\n    fn test_extract_redundant() {\n        let chat_response = \"```diff\n--- server/bleep/src/query/parser.rs\n+++ server/bleep/src/query/parser.rs\n@@ -64,7 +64,7 @@\n     }\n \n     pub fn from_str(query: String, repo_ref: String) -> Self {\n-        Self {\n+        Self {\n             target: Some(Literal::Plain(Cow::Owned(query))),\n             repos: [Literal::Plain(Cow::Owned(repo_ref))].into(),\n             ..Default::default()\n```\";\n\n        for _ in extract(&chat_response).unwrap() {}\n    }\n\n    #[test]\n    fn test_multiple_diff_blocks() {\n        let chat_response = r#\"```diff\n--- server/bleep/src/analytics.rs\n+++ server/bleep/src/analytics.rs\n@@ -215,6 +215,22 @@ impl RudderHub {\n             }));\n         }\n     }\n+    \n+    pub fn track_index_repo(&self, user: &crate::webserver::middleware::User, repo_ref: RepoRef) {\n+        if let Some(options) = &self.options {\n+            self.send(Message::Track(Track {\n+                user_id: Some(self.tracking_id(user.username())),\n+                event: \"index repo\".to_owned(),\n+                properties: Some(json!({\n+                    \"device_id\": self.device_id(),\n+                    \"repo_ref\": repo_ref.to_string(),\n+                    \"package_metadata\": options.package_metadata,\n+                })),\n+                ..Default::default()\n+            }));\n+        }\n+    }\n }\n \n impl From<Option<String>> for DeviceId {\n```\n\n```diff\n--- server/bleep/src/indexes.rs\n+++ server/bleep/src/indexes.rs\n@@ -61,7 +61,9 @@ impl<'a> GlobalWriteHandle<'a> {\n     }\n \n     pub(crate) async fn index(\n-        &self,\n+        &self,\n+        analytics: &RudderHub,  // Pass in the RudderHub instance\n+        user: &crate::webserver::middleware::User,  // Pass in the current user\n         sync_handle: &SyncHandle,\n         repo: &Repository,\n     ) -> Result<Arc<RepoMetadata>, RepoError> {\n@@ -70,6 +72,9 @@ impl<'a> GlobalWriteHandle<'a> {\n \n         for h in &self.handles {\n             h.index(sync_handle, repo, &metadata).await?;\n+            \n+            // Track the repo indexing event\n+            analytics.track_index_repo(user, repo.repo_ref.clone());\n         }\n \n         Ok(metadata)\n```\"#;\n\n        let expected = r#\"--- server/bleep/src/analytics.rs\n+++ server/bleep/src/analytics.rs\n@@ -215,6 +215,22 @@ impl RudderHub {\n             }));\n         }\n     }\n+    \n+    pub fn track_index_repo(&self, user: &crate::webserver::middleware::User, repo_ref: RepoRef) {\n+        if let Some(options) = &self.options {\n+            self.send(Message::Track(Track {\n+                user_id: Some(self.tracking_id(user.username())),\n+                event: \"index repo\".to_owned(),\n+                properties: Some(json!({\n+                    \"device_id\": self.device_id(),\n+                    \"repo_ref\": repo_ref.to_string(),\n+                    \"package_metadata\": options.package_metadata,\n+                })),\n+                ..Default::default()\n+            }));\n+        }\n+    }\n }\n \n impl From<Option<String>> for DeviceId {\n--- server/bleep/src/indexes.rs\n+++ server/bleep/src/indexes.rs\n@@ -61,7 +61,9 @@ impl<'a> GlobalWriteHandle<'a> {\n     }\n \n     pub(crate) async fn index(\n-        &self,\n+        &self,\n+        analytics: &RudderHub,  // Pass in the RudderHub instance\n+        user: &crate::webserver::middleware::User,  // Pass in the current user\n         sync_handle: &SyncHandle,\n         repo: &Repository,\n     ) -> Result<Arc<RepoMetadata>, RepoError> {\n@@ -70,6 +72,9 @@ impl<'a> GlobalWriteHandle<'a> {\n \n         for h in &self.handles {\n             h.index(sync_handle, repo, &metadata).await?;\n+            \n+            // Track the repo indexing event\n+            analytics.track_index_repo(user, repo.repo_ref.clone());\n         }\n \n         Ok(metadata)\n\"#;\n\n        let output = extract_diff(chat_response).unwrap();\n\n        assert_eq!(expected, output);\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/webserver/studio.rs",
    "content": "use std::{\n    borrow::Cow,\n    collections::{HashMap, HashSet},\n    iter, mem,\n    ops::Range,\n    pin::Pin,\n};\n\nuse anyhow::{Context, Result};\nuse axum::{\n    extract::{Path, Query, State},\n    response::{sse, Sse},\n    Extension, Json,\n};\nuse chrono::NaiveDateTime;\nuse futures::{pin_mut, stream, StreamExt, TryStreamExt};\nuse rayon::prelude::*;\nuse reqwest::StatusCode;\nuse tracing::{debug, error, warn};\nuse uuid::Uuid;\n\nuse self::diff::{DiffChunk, DiffHunk};\n\nuse super::{middleware::User, Error};\nuse crate::{\n    agent::{exchange::Exchange, prompts},\n    llm,\n    repo::RepoRef,\n    webserver, Application,\n};\n\nmod diff;\n\nconst LLM_GATEWAY_MODEL: &str = \"gpt-4-turbo\";\n\nfn studio_not_found() -> Error {\n    Error::not_found(\"unknown code studio ID\")\n}\n\nfn default_studio_name() -> String {\n    \"New Studio\".to_owned()\n}\n\nasync fn latest_snapshot_id<'a, E>(studio_id: i64, exec: E, user_id: &str) -> webserver::Result<i64>\nwhere\n    E: sqlx::Executor<'a, Database = sqlx::Sqlite>,\n{\n    sqlx::query! {\n        \"SELECT ss.id\n        FROM studio_snapshots ss\n        JOIN studios s ON s.id = ss.studio_id\n        JOIN projects p ON s.project_id = p.id\n        WHERE ss.studio_id = ? AND p.user_id = ?\n        ORDER BY ss.modified_at DESC\n        LIMIT 1\",\n        studio_id,\n        user_id,\n    }\n    .fetch_optional(exec)\n    .await?\n    .and_then(|r| r.id)\n    .ok_or_else(|| Error::not_found(\"no snapshots with given studio ID\"))\n}\n\n#[derive(serde::Deserialize)]\npub struct Create {\n    name: Option<String>,\n}\n\npub async fn create(\n    app: Extension<Application>,\n    Path(project_id): Path<i64>,\n    Json(params): Json<Create>,\n) -> webserver::Result<String> {\n    let mut transaction = app.sql.begin().await?;\n\n    let studio_id: i64 = sqlx::query! {\n        \"INSERT INTO studios (project_id, name) VALUES (?, ?) RETURNING id\",\n        project_id,\n        params.name,\n    }\n    .fetch_one(&mut transaction)\n    .await?\n    .id;\n\n    sqlx::query! {\n        \"INSERT INTO studio_snapshots (studio_id, context, doc_context, messages)\n         VALUES (?, ?, ?, ?)\",\n        studio_id,\n        \"[]\",\n        \"[]\",\n        \"[]\",\n    }\n    .execute(&mut transaction)\n    .await\n    .unwrap();\n\n    transaction.commit().await?;\n\n    Ok(studio_id.to_string())\n}\n\n#[derive(serde::Serialize)]\npub struct Studio {\n    name: String,\n    modified_at: NaiveDateTime,\n    context: Vec<ContextFile>,\n    doc_context: Vec<DocContextFile>,\n    messages: Vec<Message>,\n    token_counts: TokenCounts,\n}\n\n#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq)]\nstruct ContextFile {\n    path: String,\n    hidden: bool,\n    repo: RepoRef,\n    branch: Option<String>,\n    ranges: Vec<Range<usize>>,\n}\n\nimpl ContextFile {\n    /// Merge two files.\n    ///\n    /// This just joins the two ranges. All other fields come from `self`.\n    fn merge(mut self, rhs: Self) -> Self {\n        self.ranges.extend(rhs.ranges);\n        self\n    }\n}\n\n#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq)]\nstruct DocContextFile {\n    doc_id: i64,\n    doc_source: url::Url,\n    doc_icon: Option<String>,\n    doc_title: Option<String>,\n    relative_url: String,\n    absolute_url: String,\n    hidden: bool,\n    ranges: Vec<Uuid>,\n}\n\n#[derive(Clone, serde::Serialize, serde::Deserialize)]\nenum Message {\n    User(String),\n    Assistant(String),\n}\n\nimpl From<&Message> for llm::client::api::Message {\n    fn from(value: &Message) -> Self {\n        match value {\n            Message::User(s) => llm::client::api::Message::user(s),\n            Message::Assistant(s) => llm::client::api::Message::assistant(s),\n        }\n    }\n}\n\n#[derive(serde::Deserialize)]\npub struct Get {\n    pub snapshot_id: Option<i64>,\n}\n\npub async fn get(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Path((project_id, studio_id)): Path<(i64, i64)>,\n    Query(params): Query<Get>,\n) -> webserver::Result<Json<Studio>> {\n    let user_id = user.username().ok_or_else(super::no_user_id)?.to_string();\n\n    let snapshot_id = match params.snapshot_id {\n        Some(id) => id,\n        None => latest_snapshot_id(studio_id, &*app.sql, &user_id).await?,\n    };\n\n    let row = sqlx::query! {\n        \"SELECT s.id, s.name, ss.context, ss.doc_context, ss.messages, ss.modified_at\n        FROM studios s\n        INNER JOIN studio_snapshots ss ON ss.id = ?\n        WHERE s.id = ? AND s.project_id = ?\",\n        snapshot_id,\n        studio_id,\n        project_id,\n    }\n    .fetch_optional(&*app.sql)\n    .await?\n    .ok_or_else(studio_not_found)?;\n\n    let context: Vec<ContextFile> =\n        serde_json::from_str(&row.context).context(\"failed to deserialize context\")?;\n    let doc_context: Vec<DocContextFile> =\n        serde_json::from_str(&row.doc_context).context(\"failed to deserialize doc context\")?;\n    let messages: Vec<Message> =\n        serde_json::from_str(&row.messages).context(\"failed to deserialize message list\")?;\n\n    Ok(Json(Studio {\n        modified_at: row.modified_at,\n        name: row.name.unwrap_or_else(default_studio_name),\n        token_counts: token_counts((*app).clone(), &messages, &context, &doc_context).await?,\n        context,\n        doc_context,\n        messages,\n    }))\n}\n\n#[derive(serde::Deserialize)]\npub struct Patch {\n    name: Option<String>,\n    modified_at: Option<NaiveDateTime>,\n    context: Option<Vec<ContextFile>>,\n    doc_context: Option<Vec<DocContextFile>>,\n    messages: Option<Vec<Message>>,\n    snapshot_id: Option<i64>,\n}\n\npub async fn patch(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Path((project_id, studio_id)): Path<(i64, i64)>,\n    Json(patch): Json<Patch>,\n) -> webserver::Result<Json<TokenCounts>> {\n    let user_id = user.username().ok_or_else(super::no_user_id)?.to_string();\n\n    let mut transaction = app.sql.begin().await?;\n\n    let snapshot_id = match patch.snapshot_id {\n        Some(id) => id,\n        None => latest_snapshot_id(studio_id, &mut transaction, &user_id).await?,\n    };\n\n    // Ensure the ID is valid first.\n    sqlx::query!(\n        \"SELECT s.id FROM studios s\n        JOIN projects p ON p.id = s.project_id\n        WHERE s.id = ? AND p.id = ? AND p.user_id = ?\",\n        studio_id,\n        project_id,\n        user_id,\n    )\n    .fetch_optional(&mut transaction)\n    .await?\n    .ok_or_else(studio_not_found)?;\n\n    if let Some(name) = patch.name {\n        sqlx::query!(\"UPDATE studios SET name = ? WHERE id = ?\", name, studio_id)\n            .execute(&mut transaction)\n            .await?;\n    }\n\n    if let Some(modified_at) = patch.modified_at {\n        sqlx::query!(\n            \"UPDATE studio_snapshots SET modified_at = ? WHERE id = ?\",\n            modified_at,\n            snapshot_id\n        )\n        .execute(&mut transaction)\n        .await?;\n    }\n\n    if let Some(context) = patch.context {\n        let json = serde_json::to_string(&context).unwrap();\n        sqlx::query!(\n            \"UPDATE studio_snapshots SET context = ? WHERE id = ?\",\n            json,\n            snapshot_id\n        )\n        .execute(&mut transaction)\n        .await?;\n    }\n\n    if let Some(doc_context) = patch.doc_context {\n        let json = serde_json::to_string(&doc_context).unwrap();\n        sqlx::query!(\n            \"UPDATE studio_snapshots SET doc_context = ? WHERE id = ?\",\n            json,\n            snapshot_id\n        )\n        .execute(&mut transaction)\n        .await?;\n    }\n\n    if let Some(messages) = patch.messages {\n        let json = serde_json::to_string(&messages).unwrap();\n        sqlx::query!(\n            \"UPDATE studio_snapshots SET messages = ? WHERE id = ?\",\n            json,\n            snapshot_id\n        )\n        .execute(&mut transaction)\n        .await?;\n    }\n\n    sqlx::query! {\n        \"UPDATE studio_snapshots SET modified_at = datetime('now') WHERE id = ?\",\n        snapshot_id,\n    }\n    .execute(&mut transaction)\n    .await?;\n\n    // Re-fetch the context and messages in case we didn't change them. If we did, this will now\n    // contain the updated values.\n    let (messages_json, context_json, doc_context_json) = sqlx::query!(\n        \"SELECT messages, context, doc_context FROM studio_snapshots WHERE id = ?\",\n        snapshot_id\n    )\n    .fetch_optional(&mut transaction)\n    .await?\n    .map(|r| (r.messages, r.context, r.doc_context))\n    .unwrap_or_default();\n\n    let context: Vec<ContextFile> =\n        serde_json::from_str(&context_json).context(\"invalid context JSON\")?;\n\n    let doc_context: Vec<DocContextFile> =\n        serde_json::from_str(&doc_context_json).context(\"invalid context JSON\")?;\n\n    let messages: Vec<Message> =\n        serde_json::from_str(&messages_json).context(\"invalid messages JSON\")?;\n\n    let counts = token_counts((*app).clone(), &messages, &context, &doc_context).await?;\n\n    transaction.commit().await?;\n\n    Ok(Json(counts))\n}\n\npub async fn delete(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Path((project_id, studio_id)): Path<(i64, i64)>,\n) -> webserver::Result<()> {\n    let user_id = user.username().ok_or_else(super::no_user_id)?.to_string();\n\n    sqlx::query!(\n        \"DELETE FROM studios\n        WHERE id = $1 AND project_id = $2 AND EXISTS (\n            SELECT p.id FROM projects p WHERE p.id = $2 AND p.user_id = $3\n        )\n        RETURNING id\",\n        studio_id,\n        project_id,\n        user_id,\n    )\n    .fetch_optional(&*app.sql)\n    .await?\n    .ok_or_else(studio_not_found)\n    .map(|_| ())\n}\n\n#[derive(serde::Serialize)]\npub struct ListItem {\n    id: i64,\n    name: String,\n    modified_at: NaiveDateTime,\n    repos: Vec<String>,\n    most_common_ext: String,\n    context: Vec<ContextFile>,\n    doc_context: Vec<DocContextFile>,\n    token_counts: TokenCounts,\n}\n\npub async fn list(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Path(project_id): Path<i64>,\n) -> webserver::Result<Json<Vec<ListItem>>> {\n    let user_id = user.username().ok_or_else(super::no_user_id)?.to_string();\n\n    let studios = sqlx::query!(\n        \"SELECT\n            s.id,\n            s.name,\n            ss.modified_at as \\\"modified_at!\\\",\n            ss.context,\n            ss.doc_context,\n            ss.messages\n        FROM studios s\n        INNER JOIN studio_snapshots ss ON s.id = ss.studio_id\n        INNER JOIN projects p ON p.id = s.project_id\n        WHERE s.project_id = ? AND p.user_id = ? AND (ss.studio_id, ss.modified_at) IN (\n            SELECT studio_id, MAX(modified_at)\n            FROM studio_snapshots\n            GROUP BY studio_id\n        )\",\n        project_id,\n        user_id,\n    )\n    .fetch_all(&*app.sql)\n    .await?;\n\n    let mut list_items = Vec::new();\n\n    for studio in studios {\n        let context: Vec<ContextFile> =\n            serde_json::from_str(&studio.context).map_err(Error::internal)?;\n        let doc_context: Vec<DocContextFile> =\n            serde_json::from_str(&studio.doc_context).map_err(Error::internal)?;\n        let messages: Vec<Message> =\n            serde_json::from_str(&studio.messages).map_err(Error::internal)?;\n\n        let repos: HashSet<String> = context.iter().map(|file| file.repo.name.clone()).collect();\n\n        let ext_tokens = token_counts((*app).clone(), &[], &context, &[])\n            .await?\n            .per_file\n            .iter()\n            .zip(\n                context\n                    .iter()\n                    .map(|file| file.path.split('.').last().unwrap_or_default()),\n            )\n            .fold(HashMap::new(), |mut tokens_by_ext, (count, extension)| {\n                *tokens_by_ext.entry(extension).or_insert(0) += count.unwrap_or(0);\n                tokens_by_ext\n            });\n\n        let most_common_ext = ext_tokens\n            .into_iter()\n            .max_by_key(|(_, tokens)| *tokens)\n            .map(|(ext, _)| ext)\n            .unwrap_or_default()\n            .to_owned();\n\n        let token_counts = token_counts((*app).clone(), &messages, &context, &doc_context).await?;\n\n        let list_item = ListItem {\n            id: studio.id,\n            name: studio.name.unwrap_or_else(default_studio_name),\n            modified_at: studio.modified_at,\n            repos: repos.into_iter().collect::<Vec<_>>(),\n            most_common_ext,\n            context,\n            doc_context,\n            token_counts,\n        };\n\n        list_items.push(list_item);\n    }\n\n    Ok(Json(list_items))\n}\n\n#[derive(serde::Serialize)]\npub struct TokenCounts {\n    total: usize,\n    messages: usize,\n    per_file: Vec<Option<usize>>,\n    baseline: usize,\n    per_doc_file: Vec<Option<usize>>,\n}\n\nasync fn token_counts(\n    app: Application,\n    messages: &[Message],\n    context: &[ContextFile],\n    doc_context: &[DocContextFile],\n) -> webserver::Result<TokenCounts> {\n    let per_file = stream::iter(context)\n        .map(|file| {\n            let app = app.clone();\n\n            async move {\n                if file.hidden {\n                    return Ok::<_, Error>(None);\n                }\n\n                let content = app\n                    .indexes\n                    .file\n                    .by_path(&file.repo, &file.path, file.branch.as_deref())\n                    .await?\n                    .map(|doc| doc.content);\n\n                Ok(Some((file, content)))\n            }\n        })\n        // We need to box here, to avoid a higher-ranked lifetime error.\n        .boxed()\n        .buffered(16)\n        .try_collect::<Vec<_>>()\n        .await?\n        .into_par_iter()\n        .map(|data: Option<(&ContextFile, Option<String>)>| {\n            let (file, body) = match data {\n                Some(t) => t,\n                None => return Some(0),\n            };\n\n            body.map(|b| count_tokens_for_file(&file.path, &b, &file.ranges))\n        })\n        .collect::<Vec<_>>();\n\n    let core_bpe = tiktoken_rs::get_bpe_from_model(\"gpt-4-turbo\").unwrap();\n    let per_doc_file = stream::iter(doc_context)\n        .map(|file| async {\n            if file.hidden {\n                return Ok::<_, Error>(None);\n            }\n\n            let content = app\n                .indexes\n                .doc\n                .fetch(file.doc_id, &file.relative_url)\n                .await\n                .map(|search_results| {\n                    search_results\n                        .into_iter()\n                        .filter(|search_result| {\n                            if file.ranges.is_empty() {\n                                true\n                            } else {\n                                file.ranges.contains(&search_result.point_id)\n                            }\n                        })\n                        .map(|sr| sr.text)\n                        .collect::<String>()\n                })\n                .ok();\n\n            Ok(content)\n        })\n        .boxed()\n        .buffered(16)\n        .try_collect::<Vec<_>>()\n        .await?\n        .into_par_iter()\n        .map(|data| data.map(|d| core_bpe.encode_ordinary(&d).len()))\n        .collect::<Vec<_>>();\n\n    let empty_context = generate_llm_context(app.clone(), &[], &[]).await?;\n    let empty_system_message = tiktoken_rs::ChatCompletionRequestMessage {\n        role: \"system\".to_owned(),\n        content: Some(prompts::studio_article_prompt(&empty_context)),\n        name: None,\n        function_call: None,\n    };\n\n    let baseline =\n        tiktoken_rs::num_tokens_from_messages(LLM_GATEWAY_MODEL, &[empty_system_message.clone()])\n            .unwrap();\n\n    let tiktoken_messages = messages.iter().cloned().map(|message| match message {\n        Message::User(content) => tiktoken_rs::ChatCompletionRequestMessage {\n            role: \"user\".to_owned(),\n            content: Some(content),\n            name: None,\n            function_call: None,\n        },\n        Message::Assistant(content) => tiktoken_rs::ChatCompletionRequestMessage {\n            role: \"assistant\".to_owned(),\n            content: Some(content),\n            name: None,\n            function_call: None,\n        },\n    });\n\n    let messages = tiktoken_rs::num_tokens_from_messages(\n        LLM_GATEWAY_MODEL,\n        &iter::once(empty_system_message)\n            .chain(tiktoken_messages)\n            .collect::<Vec<_>>(),\n    )\n    .unwrap();\n\n    // We calculate `total` here as a summation of other calculated values here, because OpenAI's\n    // tokenization in general is contextual. Summing token counts from subsections of a string\n    // will often result in a different (and slightly larger) token count compared to counting\n    // tokens in the same string as a whole.\n    //\n    // We accept that here, and opt to always use the slightly less accurate (but larger) number\n    // for consistency.\n    let total = (messages\n        + per_file\n            .iter()\n            .flatten()\n            .chain(per_doc_file.iter().flatten())\n            .sum::<usize>())\n    .saturating_sub(baseline);\n\n    Ok(TokenCounts {\n        total,\n        messages,\n        per_file,\n        baseline,\n        per_doc_file,\n    })\n}\n\n#[derive(serde::Deserialize)]\npub struct GetFileTokenCount {\n    pub path: String,\n    pub repo: RepoRef,\n    pub branch: Option<String>,\n    pub ranges: Option<Vec<Range<usize>>>,\n}\n\npub async fn get_file_token_count(\n    app: Extension<Application>,\n    Json(params): Json<GetFileTokenCount>,\n) -> webserver::Result<Json<usize>> {\n    let file = ContextFile {\n        path: params.path,\n        hidden: false,\n        repo: params.repo,\n        branch: params.branch,\n        ranges: params.ranges.unwrap_or_default(),\n    };\n\n    let doc = app\n        .indexes\n        .file\n        .by_path(&file.repo, &file.path, file.branch.as_deref())\n        .await?\n        .with_context(|| {\n            format!(\n                \"file `{}` did not exist in repo `{}`, branch `{:?}`\",\n                file.path, file.repo, file.branch\n            )\n        })?;\n\n    let token_count = count_tokens_for_file(&file.path, &doc.content, &file.ranges);\n\n    Ok(Json(token_count))\n}\n\n#[derive(serde::Deserialize)]\npub struct GetDocFileTokenCount {\n    pub doc_id: i64,\n    pub relative_url: String,\n    pub ranges: Vec<Uuid>,\n}\n\npub async fn get_doc_file_token_count(\n    app: Extension<Application>,\n    Json(params): Json<GetDocFileTokenCount>,\n) -> webserver::Result<Json<usize>> {\n    let content = app\n        .indexes\n        .doc\n        .fetch(params.doc_id, &params.relative_url)\n        .await\n        .map_err(Error::internal)?\n        .into_iter()\n        .filter(|search_result| {\n            if params.ranges.is_empty() {\n                true\n            } else {\n                params.ranges.contains(&search_result.point_id)\n            }\n        })\n        .map(|sr| sr.text)\n        .collect::<String>();\n\n    let core_bpe = tiktoken_rs::get_bpe_from_model(\"gpt-4-turbo\").unwrap();\n    let token_count = core_bpe.encode_ordinary(&content).len();\n\n    Ok(Json(token_count))\n}\n\nfn count_tokens_for_file(path: &str, body: &str, ranges: &[Range<usize>]) -> usize {\n    let core_bpe = tiktoken_rs::get_bpe_from_model(\"gpt-4-turbo\").unwrap();\n\n    let mut chunks = Vec::new();\n\n    if ranges.is_empty() {\n        let numbered_body = body\n            .lines()\n            .enumerate()\n            .map(|(i, line)| format!(\"{} {line}\\n\", i + 1))\n            .collect::<Vec<_>>()\n            .join(\"\\n\");\n\n        chunks.push(numbered_body);\n    } else {\n        let lines = body.lines().collect::<Vec<_>>();\n        for range in ranges {\n            let chunk = lines\n                .iter()\n                .copied()\n                .enumerate()\n                .skip(range.start)\n                .take(range.end - range.start)\n                .map(|(i, line)| format!(\"{} {line}\\n\", range.start + i + 1))\n                .collect::<Vec<_>>()\n                .join(\"\\n\");\n\n            chunks.push(chunk);\n        }\n    }\n\n    // Here, we build up a pseudo context in order to count tokens more accurately. This includes\n    // the path twice; once for the full path list under the `##### PATHS #####` section, and\n    // another time for the path when it is re-printed above the code chunk.\n\n    let mut pseudo_context = format!(\"{path}\\n\");\n\n    for chunk in chunks {\n        pseudo_context += &format!(\"### {path} ###\\n{chunk}\\n\");\n    }\n\n    core_bpe.encode_ordinary(&pseudo_context).len()\n}\n\npub async fn generate(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Path((_project_id, studio_id)): Path<(i64, i64)>,\n) -> webserver::Result<Sse<Pin<Box<dyn tokio_stream::Stream<Item = Result<sse::Event>> + Send>>>> {\n    let user_id = user.username().ok_or_else(super::no_user_id)?.to_string();\n\n    let snapshot_id = latest_snapshot_id(studio_id, &*app.sql, &user_id).await?;\n\n    let llm_gateway = user\n        .llm_gateway(&app)\n        .await\n        .map_err(|e| Error::user(e).with_status(StatusCode::UNAUTHORIZED))?\n        .model(LLM_GATEWAY_MODEL)\n        .temperature(0.0);\n\n    let (messages_json, context_json, doc_context_json) = sqlx::query!(\n        \"SELECT messages, context, doc_context FROM studio_snapshots WHERE id = ?\",\n        snapshot_id,\n    )\n    .fetch_optional(&*app.sql)\n    .await?\n    .map(|row| (row.messages, row.context, row.doc_context))\n    .ok_or_else(studio_not_found)?;\n\n    let mut messages =\n        serde_json::from_str::<Vec<Message>>(&messages_json).map_err(Error::internal)?;\n\n    let context =\n        serde_json::from_str::<Vec<ContextFile>>(&context_json).map_err(Error::internal)?;\n\n    let doc_context =\n        serde_json::from_str::<Vec<DocContextFile>>(&doc_context_json).map_err(Error::internal)?;\n\n    let llm_context = generate_llm_context((*app).clone(), &context, &doc_context).await?;\n    let system_prompt = prompts::studio_article_prompt(&llm_context);\n    let llm_messages = iter::once(llm::client::api::Message::system(&system_prompt))\n        .chain(messages.iter().map(llm::client::api::Message::from))\n        .collect::<Vec<_>>();\n\n    let tokens = llm_gateway.chat_stream(&llm_messages, None).await?;\n\n    let stream = async_stream::try_stream! {\n        pin_mut!(tokens);\n\n        let mut response = String::new();\n\n        while let Some(fragment) = tokens.next().await {\n            let fragment = fragment?;\n            response += &fragment;\n            yield response.clone();\n        }\n\n        messages.push(Message::Assistant(response));\n        let messages_json = serde_json::to_string(&messages).unwrap();\n\n        sqlx::query! {\n            \"INSERT INTO studio_snapshots(studio_id, context, doc_context, messages)\n            SELECT studio_id, context, doc_context, ?\n            FROM studio_snapshots\n            WHERE id = ?\",\n            messages_json,\n            snapshot_id,\n        }\n        .execute(&*app.sql)\n        .await?;\n\n        populate_studio_name(app.clone(), user.clone(), studio_id).await?;\n    };\n\n    let mut errored = false;\n    let stream = stream.take_while(move |r| {\n        let ok = !errored;\n        if let Err(e) = &r {\n            error!(?e, \"stream error\");\n            errored = true;\n        }\n        async move { ok }\n    });\n\n    let event_stream = stream.map(|result| {\n        sse::Event::default()\n            .json_data(result.map_err(|e: Error| e.to_string()))\n            .map_err(anyhow::Error::new)\n    });\n    let done_stream = stream::once(async { Ok(sse::Event::default().data(\"[DONE]\")) });\n\n    let stream = event_stream.chain(done_stream);\n\n    Ok(Sse::new(Box::pin(stream)))\n}\n\nasync fn generate_llm_context(\n    app: Application,\n    context: &[ContextFile],\n    doc_context: &[DocContextFile],\n) -> Result<String> {\n    let mut s = String::new();\n\n    s += \"##### PATHS #####\\n\";\n\n    for file in context.iter().filter(|f| !f.hidden) {\n        s += &format!(\"{}:{}\\n\", file.repo, file.path);\n    }\n\n    s += \"\\n##### CODE CHUNKS #####\\n\\n\";\n\n    for file in context.iter().filter(|f| !f.hidden) {\n        let doc = app\n            .indexes\n            .file\n            .by_path(&file.repo, &file.path, file.branch.as_deref())\n            .await?\n            .with_context(|| {\n                format!(\n                    \"file `{}` did not exist in repo `{}`, branch `{:?}`\",\n                    file.path, file.repo, file.branch\n                )\n            })?;\n\n        let lines = doc\n            .content\n            .lines()\n            .enumerate()\n            .map(|(i, s)| format!(\"{} {s}\\n\", i + 1))\n            .collect::<Vec<_>>();\n\n        #[allow(clippy::single_range_in_vec_init)]\n        let ranges = if file.ranges.is_empty() {\n            vec![0..lines.len()]\n        } else {\n            file.ranges.clone()\n        };\n\n        for range in ranges {\n            let snippet = lines\n                .iter()\n                .skip(range.start)\n                .take(range.end - range.start)\n                .map(String::as_str)\n                .collect::<String>();\n\n            s += &format!(\"### {}:{} ###\\n{snippet}\\n\", file.repo, file.path);\n        }\n    }\n\n    s += \"\\n##### DOCUMENTATION #####\\n\\n\";\n\n    for file in doc_context.iter().filter(|f| !f.hidden) {\n        let sections = app\n            .indexes\n            .doc\n            .fetch(file.doc_id, &file.relative_url)\n            .await?\n            .into_iter()\n            .filter(|search_result| {\n                if file.ranges.is_empty() {\n                    true\n                } else {\n                    file.ranges.contains(&search_result.point_id)\n                }\n            })\n            .map(|sr| sr.text)\n            .collect::<Vec<_>>()\n            .join(\"\\n\");\n        s += &format!(\n            \"### {}{} ###\\n{sections}\\n\",\n            &file.doc_source.as_str(),\n            &file.relative_url\n        );\n    }\n\n    Ok(s)\n}\n\n/// A set of structured diff definitions consumed by the front-end.\nmod structured_diff {\n    use std::fmt;\n\n    use crate::repo::RepoRef;\n\n    #[derive(serde::Serialize, serde::Deserialize)]\n    pub struct Diff {\n        pub chunks: Vec<Chunk>,\n    }\n\n    impl fmt::Display for Diff {\n        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n            use std::fmt::Write;\n\n            let mut s = String::new();\n\n            for c in &self.chunks {\n                writeln!(s, \"--- {}\\n+++ {}\", c.file, c.file)?;\n\n                for h in &c.hunks {\n                    writeln!(s, \"@@ -{},0 +{},0 @@\", h.line_start, h.line_start)?;\n                    write!(s, \"{}\", h.patch)?;\n                }\n            }\n\n            for c in super::diff::relaxed_parse(&s) {\n                write!(f, \"{}\", c)?;\n            }\n\n            Ok(())\n        }\n    }\n\n    #[derive(serde::Serialize, serde::Deserialize)]\n    pub struct Chunk {\n        pub file: String,\n        pub repo: RepoRef,\n        pub branch: Option<String>,\n        pub lang: Option<String>,\n        pub hunks: Vec<Hunk>,\n\n        /// This field is additionally included for simplicity on the front-end.\n        pub raw_patch: String,\n    }\n\n    #[derive(serde::Serialize, serde::Deserialize)]\n    pub struct Hunk {\n        pub line_start: usize,\n        pub patch: String,\n    }\n}\n\npub async fn diff(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Path((_project_id, studio_id)): Path<(i64, i64)>,\n) -> webserver::Result<Json<structured_diff::Diff>> {\n    let user_id = user.username().ok_or_else(super::no_user_id)?.to_string();\n\n    let snapshot_id = latest_snapshot_id(studio_id, &*app.sql, &user_id).await?;\n\n    let llm_gateway = user\n        .llm_gateway(&app)\n        .await\n        .map_err(|e| Error::user(e).with_status(StatusCode::UNAUTHORIZED))?\n        .model(LLM_GATEWAY_MODEL)\n        .temperature(0.0);\n\n    let (messages_json, context_json) = sqlx::query!(\n        \"SELECT messages, context FROM studio_snapshots WHERE id = ?\",\n        snapshot_id,\n    )\n    .fetch_optional(&*app.sql)\n    .await?\n    .map(|row| (row.messages, row.context))\n    .ok_or_else(studio_not_found)?;\n\n    let messages = serde_json::from_str::<Vec<Message>>(&messages_json).map_err(Error::internal)?;\n\n    let context =\n        serde_json::from_str::<Vec<ContextFile>>(&context_json).map_err(Error::internal)?;\n\n    let user_message = messages\n        .iter()\n        .rev()\n        .find_map(|msg| match msg {\n            Message::User(m) => Some(m),\n            Message::Assistant(..) => None,\n        })\n        .context(\"studio did not contain a user message\")?;\n\n    let assistant_message = messages\n        .iter()\n        .rev()\n        .find_map(|msg| match msg {\n            Message::User(..) => None,\n            Message::Assistant(m) => Some(m),\n        })\n        .context(\"studio did not contain an assistant message\")?;\n\n    let llm_context = generate_llm_context((*app).clone(), &context, &[]).await?;\n\n    let system_prompt = prompts::studio_diff_prompt(&llm_context);\n    let user_message = format!(\"Create a patch for the task \\\"{user_message}\\\".\\n\\n\\nHere is the solution:\\n\\n{assistant_message}\");\n\n    let messages = vec![\n        llm::client::api::Message::system(&system_prompt),\n        llm::client::api::Message::user(&user_message),\n    ];\n\n    let response = llm_gateway.chat(&messages, None).await?;\n    let diff_chunks = diff::extract(&response)?.collect::<Vec<_>>();\n\n    let valid_chunks = stream::iter(diff_chunks)\n        .map(|mut chunk| {\n            let app = (*app).clone();\n            let llm_context = llm_context.clone();\n            let llm_gateway = llm_gateway.clone();\n\n            async move {\n                match (&chunk.src, &chunk.dst) {\n                    (Some(src), Some(dst)) => {\n                        if src != dst {\n                            error!(\n                                \"patch source and destination file were different: \\\n                                got `{src}` and `{dst}`\"\n                            );\n\n                            return Ok(None);\n                        }\n\n                        let (repo, path) = parse_diff_path(src)?;\n\n                        chunk.hunks = rectify_hunks(\n                            &app,\n                            &llm_context,\n                            &llm_gateway,\n                            chunk.hunks.iter(),\n                            path,\n                            &repo,\n                            None,\n                        )\n                        .await?;\n\n                        Ok(Some(chunk))\n                    }\n\n                    (Some(src), None) => {\n                        let (repo, path) = parse_diff_path(src)?;\n                        if validate_delete_file(&app, path, &repo, None).await? {\n                            Ok(Some(chunk))\n                        } else {\n                            Ok(None)\n                        }\n                    }\n\n                    (None, Some(dst)) => {\n                        let (repo, path) = parse_diff_path(dst)?;\n                        if validate_add_file(&app, &chunk, path, &repo, None).await? {\n                            Ok(Some(chunk))\n                        } else {\n                            Ok(None)\n                        }\n                    }\n\n                    (None, None) => {\n                        error!(\"patch chunk had no file source or destination\");\n                        Ok(None)\n                    }\n                }\n            }\n        })\n        .buffered(10)\n        .try_filter_map(|c: Option<_>| async move { Ok::<_, anyhow::Error>(c) })\n        .try_collect::<Vec<_>>()\n        .await\n        .context(\"failed to interpret diff chunks\")?;\n\n    let mut file_langs = HashMap::<String, String>::new();\n    let mut out = structured_diff::Diff { chunks: vec![] };\n\n    for chunk in valid_chunks {\n        let path = chunk.src.as_deref().or(chunk.dst.as_deref()).unwrap();\n        let (repo, path) = parse_diff_path(path)?;\n        let lang = if let Some(l) = file_langs.get(path) {\n            Some(l.clone())\n        } else {\n            let detected_lang = if chunk.src.is_some() {\n                let doc = app\n                    .indexes\n                    .file\n                    .by_path(&repo, path, None)\n                    .await?\n                    .context(\"path did not exist in the index\")?;\n\n                doc.lang\n            } else {\n                hyperpolyglot::detect(std::path::Path::new(&path))\n                    .ok()\n                    .flatten()\n                    .map(|detection| detection.language().to_owned())\n            };\n\n            if let Some(l) = detected_lang.clone() {\n                file_langs.insert(path.to_owned(), l);\n            }\n\n            detected_lang\n        };\n\n        out.chunks.push(structured_diff::Chunk {\n            raw_patch: chunk.to_string(),\n\n            lang: lang.clone(),\n            repo: repo.clone(),\n            branch: None,\n            file: path.to_owned(),\n            hunks: chunk\n                .hunks\n                .into_iter()\n                .map(|hunk| structured_diff::Hunk {\n                    line_start: hunk.src_line,\n                    patch: hunk\n                        .lines\n                        .into_iter()\n                        .map(|line| line.to_string())\n                        .collect::<String>(),\n                })\n                .collect(),\n        });\n    }\n\n    Ok(Json(out))\n}\n\nfn parse_diff_path(p: &str) -> Result<(RepoRef, &str)> {\n    let (repo, path) = p\n        .split_once(':')\n        .context(\"diff path did not conform to repo:path syntax\")?;\n\n    let repo = repo.parse().context(\"repo ref was invalid\")?;\n\n    Ok((repo, path))\n}\n\nasync fn rectify_hunks(\n    app: &Application,\n    llm_context: &str,\n    llm_gateway: &llm::client::Client,\n    hunks: impl Iterator<Item = &DiffHunk>,\n    path: &str,\n    repo: &RepoRef,\n    branch: Option<&str>,\n) -> Result<Vec<DiffHunk>> {\n    let file = app\n        .indexes\n        .file\n        .by_path(repo, path, branch)\n        .await?\n        .context(\"path did not exist in the index\")?;\n\n    let mut file_content = file.content;\n\n    let mut out = Vec::new();\n\n    for (i, hunk) in hunks.enumerate() {\n        let mut singular_chunk = DiffChunk {\n            src: Some(path.to_owned()),\n            dst: Some(path.to_owned()),\n            hunks: vec![hunk.clone()],\n        };\n\n        let diff = singular_chunk.to_string();\n        let patch = diffy::Patch::from_str(&diff).context(\"invalid patch\")?;\n\n        if let Ok(t) = diffy::apply(&file_content, &patch) {\n            file_content = t;\n            out.extend(singular_chunk.hunks);\n        } else {\n            debug!(\"fixing up patch:\\n\\n{hunk:?}\\n\\n{diff}\");\n\n            singular_chunk.hunks[0].lines.retain(|line| match line {\n                diff::Line::Add(..) | diff::Line::Del(..) => true,\n                diff::Line::Context(..) => false,\n            });\n            singular_chunk.fixup_hunks();\n\n            let diff = if singular_chunk.hunks[0]\n                .lines\n                .iter()\n                .all(|l| matches!(l, diff::Line::Add(..)))\n            {\n                let system_prompt = prompts::studio_diff_regen_hunk_prompt(llm_context);\n                let messages = vec![\n                    llm::client::api::Message::system(&system_prompt),\n                    llm::client::api::Message::user(&singular_chunk.to_string()),\n                ];\n                llm_gateway.chat(&messages, None).await?\n            } else {\n                singular_chunk.to_string()\n            };\n\n            let patch = diffy::Patch::from_str(&diff).context(\"redacted patch was invalid\")?;\n\n            if let Ok(t) = diffy::apply(&file_content, &patch) {\n                file_content = t;\n                out.extend(singular_chunk.hunks);\n            } else {\n                warn!(\"hunk {path}#{i} failed: {diff}\");\n            }\n        }\n    }\n\n    Ok(out)\n}\n\nasync fn validate_delete_file(\n    app: &Application,\n    path: &str,\n    repo: &RepoRef,\n    branch: Option<&str>,\n) -> Result<bool> {\n    if app\n        .indexes\n        .file\n        .by_path(repo, path, branch)\n        .await?\n        .is_none()\n    {\n        error!(\"diff tried to delete a file that doesn't exist: {path}\");\n        Ok(false)\n    } else {\n        Ok(true)\n    }\n}\n\nasync fn validate_add_file(\n    app: &Application,\n    chunk: &DiffChunk,\n    path: &str,\n    repo: &RepoRef,\n    branch: Option<&str>,\n) -> Result<bool> {\n    if app\n        .indexes\n        .file\n        .by_path(repo, path, branch)\n        .await?\n        .is_some()\n    {\n        error!(\"diff tried to create a file that already exists: {path}\");\n        return Ok(false);\n    };\n\n    if chunk\n        .hunks\n        .iter()\n        .any(|h| h.lines.iter().any(|l| !matches!(l, diff::Line::Add(..))))\n    {\n        error!(\"diff to create a new file had non-addition lines\");\n        Ok(false)\n    } else {\n        Ok(true)\n    }\n}\n\npub async fn diff_apply(\n    app: State<Application>,\n    user: Extension<User>,\n    Path((_project_id, studio_id)): Path<(i64, i64)>,\n    diff: String,\n) -> webserver::Result<()> {\n    let user_id = user.username().ok_or_else(super::no_user_id)?.to_string();\n\n    let snapshot_id = latest_snapshot_id(studio_id, &*app.sql, &user_id).await?;\n\n    let context_json = sqlx::query!(\n        \"SELECT context FROM studio_snapshots WHERE id = ?\",\n        snapshot_id,\n    )\n    .fetch_optional(&*app.sql)\n    .await?\n    .map(|row| row.context)\n    .ok_or_else(studio_not_found)?;\n\n    let _context =\n        serde_json::from_str::<Vec<ContextFile>>(&context_json).map_err(Error::internal)?;\n\n    let diff_chunks = diff::relaxed_parse(&diff);\n\n    let mut dirty_repos = HashSet::new();\n\n    for (i, chunk) in diff_chunks.enumerate() {\n        let (repo, path) = chunk\n            .src\n            .as_ref()\n            .or(chunk.dst.as_ref())\n            .context(\"diff was missing src and dst\")\n            .and_then(|p| parse_diff_path(p))?;\n\n        let Some(repo_path) = repo.local_path() else {\n            error!(\"cannot apply patch to remote repo\");\n            continue;\n        };\n\n        dirty_repos.insert(repo.clone());\n\n        let mut file_content = if chunk.src.is_some() {\n            app.indexes\n                .file\n                .by_path(&repo, path, None)\n                .await?\n                .context(\"path did not exist in the index\")?\n                .content\n        } else {\n            String::new()\n        };\n\n        for (j, hunk) in chunk.hunks.iter().enumerate() {\n            let mut singular_chunk = chunk.clone();\n            singular_chunk.hunks = vec![hunk.clone()];\n\n            let diff = singular_chunk.to_string();\n            let patch = diffy::Patch::from_str(&diff).context(\"invalid patch\")?;\n\n            match diffy::apply(&file_content, &patch) {\n                Ok(t) => file_content = t,\n                Err(e) => {\n                    return Err(\n                        Error::user(format!(\"chunk {i}, hunk {j} failed to apply: {e}\"))\n                            .with_status(StatusCode::BAD_REQUEST),\n                    )\n                }\n            }\n        }\n\n        let file_path = repo_path.join(path);\n\n        if chunk.dst.is_some() {\n            std::fs::write(file_path, file_content).context(\"failed to patch file on disk\")?;\n        } else {\n            if !file_content.trim().is_empty() {\n                return Err(Error::user(\n                    \"diff deletes a file but not all contents were removed\",\n                ));\n            }\n\n            std::fs::remove_file(file_path).context(\"failed to delete file on disk\")?;\n        }\n    }\n\n    // Force a re-sync.\n    for repo in dirty_repos {\n        let _ = webserver::repos::sync(\n            Query(webserver::repos::RepoParams {\n                repo,\n                shallow: false,\n            }),\n            app.clone(),\n        )\n        .await?;\n    }\n\n    Ok(())\n}\n\n/// If a given studio's name is `NULL`, try to auto-generate a name.\n///\n/// If the requested studio already has a name, this is a no-op.\nasync fn populate_studio_name(\n    app: Extension<Application>,\n    user: Extension<User>,\n    studio_id: i64,\n) -> webserver::Result<()> {\n    let user_id = user.username().ok_or_else(super::no_user_id)?.to_string();\n\n    let snapshot_id = latest_snapshot_id(studio_id, &*app.sql, &user_id).await?;\n    let needs_name = sqlx::query! {\n        \"SELECT id FROM studios WHERE id = ? AND name IS NULL\",\n        studio_id,\n    }\n    .fetch_optional(&*app.sql)\n    .await?\n    .is_some();\n\n    if !needs_name {\n        return Ok(());\n    }\n\n    let (context_json, messages_json) = sqlx::query! {\n        \"SELECT context, messages FROM studio_snapshots WHERE id = ?\",\n        snapshot_id,\n    }\n    .fetch_one(&*app.sql)\n    .await\n    .map(|r| (r.context, r.messages))?;\n\n    let llm_gateway = user\n        .llm_gateway(&app)\n        .await\n        .map_err(|e| Error::user(e).with_status(StatusCode::UNAUTHORIZED))?\n        .model(\"gpt-3.5-turbo-16k-0613\")\n        .temperature(0.0);\n\n    let messages = &[llm::client::api::Message::system(\n        &prompts::studio_name_prompt(&context_json, &messages_json),\n    )];\n\n    let name = llm_gateway.chat(messages, None).await?;\n\n    // Normalize studio name by removing:\n    // - surrounding whitespace\n    // - enclosing quotation marks\n    // - the prefix phrase \"Understanding \"\n    let name = name.trim();\n    let name = name.trim_matches('\"');\n    let name = name.trim_start_matches(\"Understanding \");\n\n    debug!(\"populate studio `{studio_id}` with LLM-generated name: `{name}`\");\n\n    sqlx::query!(\"UPDATE studios SET name = ? WHERE id = ?\", name, studio_id)\n        .execute(&*app.sql)\n        .await?;\n\n    Ok(())\n}\n\n#[derive(serde::Deserialize)]\npub struct Import {\n    pub thread_id: Uuid,\n    /// An optional studio ID to import into.\n    pub studio_id: Option<i64>,\n}\n\n/// Returns a new studio ID, or the `?studio_id=...` query param if present.\npub async fn import(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Path(project_id): Path<i64>,\n    Query(params): Query<Import>,\n) -> webserver::Result<String> {\n    let mut transaction = app.sql.begin().await?;\n\n    let user_id = user.username().ok_or_else(super::no_user_id)?.to_string();\n\n    let thread_id = params.thread_id.to_string();\n\n    let conversation = sqlx::query! {\n        \"SELECT c.title, c.exchanges\n        FROM conversations c\n        JOIN projects p ON p.id = c.project_id AND p.user_id = ?\n        WHERE thread_id = ?\",\n        user_id,\n        thread_id,\n    }\n    .fetch_optional(&mut transaction)\n    .await?\n    .ok_or_else(|| Error::not_found(\"conversation not found\"))?;\n\n    let exchanges = serde_json::from_str::<Vec<Exchange>>(&conversation.exchanges)\n        .context(\"couldn't deserialize exchange list\")?;\n\n    let snapshot_id = match params.studio_id {\n        None => None,\n        Some(studio_id) => Some(latest_snapshot_id(studio_id, &mut transaction, &user_id).await?),\n    };\n\n    let old_context: Vec<ContextFile> = if let Some(snapshot_id) = snapshot_id {\n        sqlx::query! {\n            \"SELECT context FROM studio_snapshots WHERE id = ?\",\n            snapshot_id,\n        }\n        .fetch_optional(&mut transaction)\n        .await?\n        .ok_or_else(studio_not_found)\n        .and_then(|r| serde_json::from_str(&r.context).map_err(Error::internal))?\n    } else {\n        Vec::new()\n    };\n\n    let imported_context = canonicalize_context(exchanges.iter().flat_map(|e| {\n        #[allow(clippy::single_range_in_vec_init)]\n        e.code_chunks.iter().map(|c| ContextFile {\n            repo: c.repo_path.repo.clone(),\n            path: c.repo_path.path.clone(),\n            hidden: false,\n            branch: e.query.branch().next().map(Cow::into_owned),\n            ranges: vec![c.start_line..c.end_line + 1],\n        })\n    }))\n    .collect::<Vec<_>>();\n\n    let new_context = extract_relevant_chunks(&user, &app, &exchanges, &imported_context).await?;\n\n    let context = canonicalize_context(new_context.clone().into_iter().chain(old_context.clone()))\n        .collect::<Vec<_>>();\n\n    let context_json = serde_json::to_string(&context).unwrap();\n\n    let studio_id = match params.studio_id {\n        Some(id) => id,\n        None => {\n            sqlx::query!(\n                \"INSERT INTO studios(name, project_id) VALUES (?, ?) RETURNING id\",\n                conversation.title,\n                project_id,\n            )\n            .fetch_one(&mut transaction)\n            .await?\n            .id\n        }\n    };\n\n    let messages_json = sqlx::query! {\n        \"SELECT messages FROM studio_snapshots WHERE id = ?\",\n        snapshot_id\n    }\n    .fetch_optional(&mut transaction)\n    .await?\n    .map(|r| r.messages)\n    .unwrap_or_else(|| \"[]\".to_owned());\n\n    sqlx::query! {\n        \"INSERT INTO studio_snapshots(studio_id, context, messages) VALUES (?, ?, ?)\",\n        studio_id,\n        context_json,\n        messages_json,\n    }\n    .execute(&mut transaction)\n    .await?;\n\n    transaction.commit().await?;\n\n    Ok(studio_id.to_string())\n}\n\nasync fn extract_relevant_chunks(\n    user: &User,\n    app: &Application,\n    exchanges: &[Exchange],\n    context: &[ContextFile],\n) -> webserver::Result<Vec<ContextFile>> {\n    let context_json = serde_json::to_string(&context).unwrap();\n\n    let llm_gateway = user\n        .llm_gateway(app)\n        .await\n        .map_err(|e| Error::user(e).with_status(StatusCode::UNAUTHORIZED))?\n        .model(LLM_GATEWAY_MODEL)\n        .temperature(0.0);\n\n    // Get last message\n    let mut last_message = String::new();\n    for exchange in exchanges.iter().rev() {\n        if let Some(answer) = &exchange.answer {\n            last_message = answer.clone();\n            break;\n        }\n    }\n\n    // Construct LLM messages\n    let llm_messages = [\n        llm::client::api::Message::system(\n            \"Your job is to output which file paths are used in the answer. Output ONLY a JSON list of \\\n            paths. For example ['path/to/file1', 'path/to/file2']\",\n        ),\n        llm::client::api::Message::assistant(&last_message),\n        llm::client::api::Message::assistant(&context_json)\n    ];\n\n    // Call the LLM gateway\n    let result = llm_gateway\n        .chat(&llm_messages, None)\n        .await\n        .and_then(|json: String| serde_json::from_str(&json).map_err(anyhow::Error::new));\n\n    // Parse the response into a JSON list of paths\n    let paths: Vec<String> = match result {\n        Ok(paths) => paths,\n        Err(e) => {\n            error!(?e, \"failed to parse response from LLM\");\n            return Ok(context.to_owned());\n        }\n    };\n\n    // Create a new context with only the files that were in the list\n    let mut filtered_context = Vec::new();\n    for file in context {\n        if paths.contains(&file.path) {\n            filtered_context.push(file.clone());\n        }\n    }\n\n    Ok(filtered_context)\n}\n\nfn canonicalize_context(\n    context: impl Iterator<Item = ContextFile>,\n) -> impl Iterator<Item = ContextFile> {\n    context\n        .fold(HashMap::<_, Vec<_>>::new(), |mut map, file| {\n            let key = (file.path.clone(), file.branch.clone());\n            map.entry(key).or_default().push(file);\n            map\n        })\n        .into_values()\n        .filter_map(|files| files.into_iter().reduce(ContextFile::merge))\n        .map(|mut c| {\n            fold_ranges(&mut c.ranges);\n            c\n        })\n}\n\nfn fold_ranges(ranges: &mut Vec<Range<usize>>) {\n    ranges.sort_by_key(|range| range.start);\n    *ranges = mem::take(ranges).into_iter().fold(Vec::new(), |mut a, e| {\n        if let Some(cur) = a.last_mut() {\n            if let Some(next) = merge_ranges(cur, e) {\n                a.push(next);\n            }\n        } else {\n            a.push(e);\n        }\n\n        a\n    });\n}\n\n/// Try to merge overlapping or nearby ranges.\n///\n/// This function assumes the input ranges are sorted, such that `a` starts before or at the same\n/// position as `b`.\n///\n/// If `b` is merged with `a`, this will return `None` and modify `a` directly. If this function\n/// determines that no merge needs to happen, then `a` will not be modified, and this function will\n/// return `Some(b)` back.\nfn merge_ranges(a: &mut Range<usize>, b: Range<usize>) -> Option<Range<usize>> {\n    const NEARBY_THRESHOLD: usize = 3;\n\n    if b.start <= a.end + NEARBY_THRESHOLD {\n        a.end = a.end.max(b.end);\n        a.start = a.start.min(b.start);\n        None\n    } else {\n        Some(b)\n    }\n}\n\n#[derive(serde::Serialize)]\npub struct Snapshot {\n    id: i64,\n    modified_at: NaiveDateTime,\n    context: Vec<ContextFile>,\n    doc_context: Vec<DocContextFile>,\n    messages: Vec<Message>,\n    token_counts: TokenCounts,\n}\n\npub async fn list_snapshots(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Path((project_id, studio_id)): Path<(i64, i64)>,\n) -> webserver::Result<Json<Vec<Snapshot>>> {\n    let user_id = user.username().ok_or_else(super::no_user_id)?.to_string();\n\n    sqlx::query! {\n        \"SELECT ss.id as 'id!', ss.modified_at, ss.context, ss.doc_context, ss.messages\n        FROM studio_snapshots ss\n        JOIN studios s ON s.id = ss.studio_id AND s.project_id = ?\n        JOIN projects p ON p.id = s.project_id\n        WHERE ss.studio_id = ? AND p.user_id = ?\n        ORDER BY modified_at DESC\",\n        project_id,\n        studio_id,\n        user_id,\n    }\n    .fetch(&*app.sql)\n    .map_err(Error::internal)\n    .and_then(|r| {\n        let app = (*app).clone();\n        async move {\n            let context: Vec<ContextFile> =\n                serde_json::from_str(&r.context).context(\"failed to deserialize context\")?;\n            let doc_context: Vec<DocContextFile> = serde_json::from_str(&r.doc_context)\n                .context(\"failed to deserialize doc context\")?;\n            let messages: Vec<Message> =\n                serde_json::from_str(&r.messages).context(\"failed to deserialize messages\")?;\n\n            let token_counts = token_counts(app, &messages, &context, &doc_context).await?;\n\n            Ok(Snapshot {\n                id: r.id,\n                modified_at: r.modified_at,\n                context,\n                doc_context,\n                messages,\n                token_counts,\n            })\n        }\n    })\n    .try_collect::<Vec<_>>()\n    .await\n    .map(Json)\n}\n\npub async fn delete_snapshot(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Path((project_id, studio_id, snapshot_id)): Path<(i64, i64, i64)>,\n) -> webserver::Result<()> {\n    let user_id = user.username().ok_or_else(super::no_user_id)?.to_string();\n\n    sqlx::query! {\n        \"DELETE FROM studio_snapshots\n        WHERE id IN (\n            SELECT ss.id\n            FROM studio_snapshots ss\n            JOIN studios s ON s.id = ss.studio_id\n            JOIN projects p ON p.id = s.project_id\n            WHERE ss.id = ? AND ss.studio_id = ? AND s.project_id = ? AND p.user_id = ?\n        )\n        RETURNING id\",\n        snapshot_id,\n        studio_id,\n        project_id,\n        user_id,\n    }\n    .fetch_optional(&*app.sql)\n    .await?\n    .map(|_id| ())\n    .ok_or_else(|| Error::not_found(\"snapshot not found\"))\n}\n\n#[cfg(test)]\nmod test {\n    use pretty_assertions::assert_eq;\n    use rand::seq::SliceRandom;\n\n    use super::*;\n\n    #[test]\n    fn test_merge_ranges_nearby() {\n        let mut r = 1..10;\n        assert_eq!(merge_ranges(&mut r, 15..20), Some(15..20));\n        assert_eq!(r, 1..10);\n\n        assert_eq!(merge_ranges(&mut r, 14..20), Some(14..20));\n        assert_eq!(r, 1..10);\n\n        assert_eq!(merge_ranges(&mut r, 13..20), None);\n        assert_eq!(r, 1..20);\n    }\n\n    #[test]\n    fn test_merge_ranges_overlap() {\n        let mut r = 1..10;\n        assert_eq!(merge_ranges(&mut r, 5..20), None);\n        assert_eq!(r, 1..20);\n    }\n\n    #[test]\n    fn test_merge_weird_ranges() {\n        let mut r = 1..10;\n        assert_eq!(merge_ranges(&mut r, 1..20), None);\n        assert_eq!(r, 1..20);\n\n        // This shouldn't happen as we expect sorted input, but we test anyway.\n        let mut r = 5..20;\n        assert_eq!(merge_ranges(&mut r, 1..10), None);\n        assert_eq!(r, 1..20);\n    }\n\n    #[test]\n    fn test_fold_ranges() {\n        let mut ranges = vec![24..35, 5..12, 15..20, 24..30, 1..10];\n\n        ranges.shuffle(&mut rand::thread_rng());\n\n        fold_ranges(&mut ranges);\n\n        assert_eq!(ranges, [1..20, 24..35]);\n    }\n\n    #[test]\n    fn test_canonicalize_context() {\n        let context = [\n            ContextFile {\n                path: \"README.md\".to_owned(),\n                hidden: false,\n                repo: \"github.com/BloopAI/bloop\".parse().unwrap(),\n                branch: None,\n                ranges: vec![5..12, 40..50],\n            },\n            ContextFile {\n                path: \"README.md\".to_owned(),\n                hidden: false,\n                repo: \"github.com/BloopAI/bloop\".parse().unwrap(),\n                branch: None,\n                ranges: vec![0..10, 20..25],\n            },\n            ContextFile {\n                path: \"server/bleep/src/main.rs\".to_owned(),\n                hidden: true,\n                repo: \"github.com/BloopAI/bloop\".parse().unwrap(),\n                branch: None,\n                ranges: vec![50..60, 30..35, 25..32],\n            },\n        ];\n\n        let expected = [\n            ContextFile {\n                path: \"README.md\".to_owned(),\n                hidden: false,\n                repo: \"github.com/BloopAI/bloop\".parse().unwrap(),\n                branch: None,\n                ranges: vec![0..12, 20..25, 40..50],\n            },\n            ContextFile {\n                path: \"server/bleep/src/main.rs\".to_owned(),\n                hidden: true,\n                repo: \"github.com/BloopAI/bloop\".parse().unwrap(),\n                branch: None,\n                ranges: vec![25..35, 50..60],\n            },\n        ];\n\n        let mut output = canonicalize_context(context.into_iter()).collect::<Vec<_>>();\n        output.sort_by_key(|cf| cf.path.clone());\n\n        assert_eq!(&expected[..], &output);\n    }\n}\n"
  },
  {
    "path": "server/bleep/src/webserver/template.rs",
    "content": "use super::{middleware::User, Error, ErrorKind};\nuse crate::{webserver, Application};\nuse anyhow::Context;\nuse axum::extract::{Extension, Json, Path};\nuse chrono::NaiveDateTime;\nuse serde::Deserialize;\n\n#[derive(Deserialize)]\npub struct Create {\n    name: String,\n    content: String,\n}\n\npub async fn create(\n    app: Extension<Application>,\n    user: Extension<User>,\n    params: Json<Create>,\n) -> webserver::Result<String> {\n    let user_id = user\n        .username()\n        .ok_or_else(|| Error::user(\"didn't have user ID\"))?\n        .to_string();\n\n    let id = sqlx::query!(\n        \"INSERT INTO templates (name, content, user_id) VALUES (?, ?, ?)\",\n        params.name,\n        params.content,\n        user_id,\n    )\n    .execute(&*app.sql)\n    .await?\n    .last_insert_rowid();\n\n    Ok(id.to_string())\n}\n\n#[derive(serde::Serialize)]\npub struct Template {\n    id: i64,\n    name: String,\n    modified_at: NaiveDateTime,\n    content: String,\n    is_default: bool,\n}\n\npub async fn list(\n    app: Extension<Application>,\n    user: Extension<User>,\n) -> webserver::Result<Json<Vec<Template>>> {\n    let user_id = user\n        .username()\n        .ok_or_else(|| Error::user(\"didn't have user ID\"))?\n        .to_string();\n\n    let templates = sqlx::query_as!(\n        Template,\n        \"SELECT id, name, modified_at, content, user_id IS NULL as \\\"is_default: bool\\\"\n        FROM templates\n        WHERE user_id = ? OR user_id IS NULL\",\n        user_id,\n    )\n    .fetch_all(&*app.sql)\n    .await?;\n\n    Ok(Json(templates))\n}\n\npub async fn get(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Path(id): Path<String>,\n) -> webserver::Result<Json<Template>> {\n    let user_id = user\n        .username()\n        .ok_or_else(|| Error::user(\"didn't have user ID\"))?\n        .to_string();\n\n    let template = sqlx::query_as!(\n        Template,\n        \"SELECT id, name, modified_at, content, user_id IS NULL as \\\"is_default: bool\\\"\n        FROM templates\n        WHERE id = ? AND (user_id = ? OR user_id IS NULL)\",\n        id,\n        user_id,\n    )\n    .fetch_optional(&*app.sql)\n    .await?\n    .ok_or_else(|| Error::new(ErrorKind::NotFound, \"Template not found\"))?;\n\n    Ok(Json(template))\n}\n\n#[derive(Deserialize)]\npub struct Patch {\n    name: Option<String>,\n    content: Option<String>,\n}\n\npub async fn patch(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Path(mut id): Path<i64>,\n    Json(patch): Json<Patch>,\n) -> webserver::Result<String> {\n    let user_id = user\n        .username()\n        .ok_or_else(|| Error::user(\"didn't have user ID\"))?\n        .to_string();\n\n    let mut transaction = app.sql.begin().await?;\n\n    // Ensure the ID is valid first.\n    let template_user_id = sqlx::query!(\n        \"SELECT user_id FROM templates WHERE id = ? AND (user_id = ? OR user_id IS NULL)\",\n        id,\n        user_id,\n    )\n    .fetch_optional(&mut transaction)\n    .await?\n    .map(|row| row.user_id)\n    .ok_or_else(|| Error::new(ErrorKind::NotFound, \"unknown template ID\"))?;\n\n    if template_user_id.is_none() {\n        id = sqlx::query! {\n            \"INSERT INTO templates(name, content, user_id)\n            SELECT name, content, ?\n            FROM templates\n            WHERE id = ?\n            RETURNING id\",\n            user_id,\n            id,\n        }\n        .fetch_one(&mut transaction)\n        .await\n        .map(|row| row.id)?\n        .context(\"query didn't return new ID\")?;\n    }\n\n    if let Some(name) = patch.name {\n        sqlx::query!(\"UPDATE templates SET name = ? WHERE id = ?\", name, id)\n            .execute(&mut transaction)\n            .await?;\n    }\n\n    if let Some(content) = patch.content {\n        sqlx::query!(\"UPDATE templates SET content = ? WHERE id = ?\", content, id)\n            .execute(&mut transaction)\n            .await?;\n    }\n\n    sqlx::query!(\n        \"UPDATE templates SET modified_at = datetime('now') WHERE id = ?\",\n        id\n    )\n    .execute(&mut transaction)\n    .await?;\n\n    transaction.commit().await?;\n\n    Ok(id.to_string())\n}\n\npub async fn delete(\n    app: Extension<Application>,\n    user: Extension<User>,\n    Path(id): Path<i64>,\n) -> webserver::Result<()> {\n    let user_id = user\n        .username()\n        .ok_or_else(|| Error::user(\"didn't have user ID\"))?\n        .to_string();\n\n    sqlx::query!(\n        \"DELETE FROM templates WHERE id = ? AND user_id = ? RETURNING id\",\n        id,\n        user_id\n    )\n    .fetch_optional(&*app.sql)\n    .await?\n    .ok_or_else(|| Error::new(ErrorKind::NotFound, \"unknown template ID\"))\n    .map(|_| ())\n}\n"
  },
  {
    "path": "server/bleep/src/webserver.rs",
    "content": "use crate::{env::Feature, Application};\n\nuse axum::{\n    extract::State,\n    http::StatusCode,\n    response::IntoResponse,\n    routing::{delete, get, post},\n    Extension, Json,\n};\nuse std::{borrow::Cow, fmt, net::SocketAddr};\nuse tower::Service;\nuse tower_http::services::{ServeDir, ServeFile};\nuse tower_http::{catch_panic::CatchPanicLayer, cors::CorsLayer};\nuse tracing::info;\n\npub mod answer;\nmod autocomplete;\nmod commits;\nmod config;\npub mod conversation;\nmod docs;\nmod file;\npub mod hoverable;\nmod index;\npub mod intelligence;\npub mod middleware;\nmod project;\nmod query;\npub mod repos;\nmod search;\nmod studio;\nmod template;\n\npub type Router<S = Application> = axum::Router<S>;\n\n#[allow(unused)]\npub(crate) mod prelude {\n    pub(crate) use super::{json, EndpointError, Error, ErrorKind, Result, Router};\n    pub(crate) use crate::indexes::Indexes;\n    pub(crate) use axum::{extract::Query, http::StatusCode, response::IntoResponse, Extension};\n    pub(crate) use serde::{Deserialize, Serialize};\n    pub(crate) use std::sync::Arc;\n}\n\npub async fn start(app: Application) -> anyhow::Result<()> {\n    let bind = SocketAddr::new(app.config.host.parse()?, app.config.port);\n\n    let mut api = Router::new()\n        .route(\"/config\", get(config::get).put(config::put))\n        // indexing\n        .route(\"/index\", get(index::handle))\n        // repo management\n        .nest(\"/repos\", repos::router())\n        // docs management\n        .nest(\n            \"/docs\",\n            Router::new()\n                .route(\"/\", get(docs::list)) // list all doc providers\n                .route(\"/search\", get(docs::search)) // text search over doc providers\n                .route(\"/enqueue\", get(docs::enqueue)) // enqueue a new url to begin syncing\n                .route(\"/verify\", get(docs::verify)) // verify if a doc url is valid\n                .route(\"/:id\", get(docs::list_one)) // list a doc provider by id\n                .route(\"/:id\", delete(docs::delete)) // delete a doc provider by id\n                .route(\"/:id/resync\", get(docs::resync)) // resync a doc provider by id\n                .route(\"/:id/status\", get(docs::status)) // query sync status of an existing doc source\n                .route(\"/:id/cancel\", get(docs::cancel)) // cancel an index job\n                .route(\"/:id/search\", get(docs::search_with_id)) // search/list sections of a doc provider\n                .route(\"/:id/list\", get(docs::list_with_id)) // list pages of a doc provider\n                .route(\"/:id/fetch\", get(docs::fetch)), // fetch all sections of a page of a doc provider\n        )\n        // intelligence\n        .route(\"/tutorial-questions\", get(commits::tutorial_questions))\n        .route(\"/hoverable\", get(hoverable::handle))\n        .route(\"/token-info\", get(intelligence::handle))\n        .route(\"/related-files\", get(intelligence::related_files))\n        .route(\n            \"/related-files-with-ranges\",\n            get(intelligence::related_file_with_ranges),\n        )\n        .route(\"/token-value\", get(intelligence::token_value))\n        // misc\n        .route(\"/search/code\", get(search::semantic_code))\n        .route(\"/file\", get(file::handle))\n        .route(\"/folder\", get(file::folder))\n        .route(\"/projects\", get(project::list).post(project::create))\n        .route(\n            \"/projects/:project_id\",\n            get(project::get)\n                .put(project::update)\n                .delete(project::delete),\n        )\n        .route(\n            \"/projects/:project_id/repos\",\n            get(project::repo::list).post(project::repo::add),\n        )\n        .route(\n            \"/projects/:project_id/repos/\",\n            delete(project::repo::delete).put(project::repo::put),\n        )\n        .route(\n            \"/projects/:project_id/docs\",\n            get(project::doc::list).post(project::doc::add),\n        )\n        .route(\n            \"/projects/:project_id/docs/:doc_id\",\n            delete(project::doc::delete),\n        )\n        .route(\n            \"/projects/:project_id/conversations\",\n            get(conversation::list),\n        )\n        .route(\n            \"/projects/:project_id/conversations/:conversation_id\",\n            get(conversation::get).delete(conversation::delete),\n        )\n        .route(\"/projects/:project_id/q\", get(query::handle))\n        .route(\n            \"/projects/:project_id/autocomplete\",\n            get(autocomplete::handle),\n        )\n        .route(\"/projects/:project_id/search/path\", get(search::fuzzy_path))\n        .route(\"/projects/:project_id/answer\", get(answer::answer))\n        .route(\"/projects/:project_id/answer/explain\", get(answer::explain))\n        .route(\"/projects/:project_id/studios\", post(studio::create))\n        .route(\"/projects/:project_id/studios\", get(studio::list))\n        .route(\n            \"/projects/:project_id/studios/:studio_id\",\n            get(studio::get).patch(studio::patch).delete(studio::delete),\n        )\n        .route(\"/projects/:project_id/studios/import\", post(studio::import))\n        .route(\n            \"/projects/:project_id/studios/:studio_id/generate\",\n            get(studio::generate),\n        )\n        .route(\n            \"/projects/:project_id/studios/:studio_id/diff\",\n            get(studio::diff),\n        )\n        .route(\n            \"/projects/:project_id/studios/:studio_id/diff/apply\",\n            post(studio::diff_apply),\n        )\n        .route(\n            \"/projects/:project_id/studios/:studio_id/snapshots\",\n            get(studio::list_snapshots),\n        )\n        .route(\n            \"/projects/:project_id/studios/:studio_id/snapshots/:snapshot_id\",\n            delete(studio::delete_snapshot),\n        )\n        .route(\n            \"/projects/:project_id/studios/file-token-count\",\n            post(studio::get_file_token_count),\n        )\n        .route(\n            \"/projects/:project_id/studios/doc-file-token-count\",\n            post(studio::get_doc_file_token_count),\n        )\n        .route(\"/template\", post(template::create))\n        .route(\"/template\", get(template::list))\n        .route(\n            \"/template/:id\",\n            get(template::get)\n                .patch(template::patch)\n                .delete(template::delete),\n        );\n\n    if app.env.allow(Feature::AnyPathScan) {\n        api = api.route(\"/repos/scan\", get(repos::scan_local));\n    }\n\n    api = api.route(\"/panic\", get(|| async { panic!(\"dead\") }));\n\n    api = middleware::local_user(api, app.clone());\n    api = api.route(\"/health\", get(health));\n\n    let api = api\n        .layer(Extension(app.indexes.clone()))\n        .layer(Extension(app.semantic.clone()))\n        .layer(Extension(app.clone()))\n        .with_state(app.clone())\n        .layer(CorsLayer::permissive())\n        .layer(CatchPanicLayer::new());\n\n    let mut router = Router::new().nest(\"/api\", api);\n\n    if let Some(frontend_dist) = app.config.frontend_dist.clone() {\n        router = router.nest_service(\n            \"/\",\n            tower::service_fn(move |req| {\n                let frontend_dist = frontend_dist.clone();\n                async move {\n                    Ok(ServeDir::new(&frontend_dist)\n                        .fallback(ServeFile::new(frontend_dist.join(\"index.html\")))\n                        .call(req)\n                        .await\n                        .unwrap())\n                }\n            }),\n        );\n    }\n\n    info!(%bind, \"starting webserver\");\n    axum::Server::bind(&bind)\n        .serve(router.into_make_service())\n        .await?;\n\n    Ok(())\n}\n\npub(crate) fn json<'a, T>(val: T) -> Json<Response<'a>>\nwhere\n    Response<'a>: From<T>,\n{\n    Json(Response::from(val))\n}\n\npub type Result<T, E = Error> = std::result::Result<T, E>;\n\n#[derive(Debug)]\npub struct Error {\n    status: StatusCode,\n    body: EndpointError<'static>,\n}\n\nimpl fmt::Display for Error {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}\", self.body.message)\n    }\n}\n\nimpl Error {\n    fn new(kind: ErrorKind, message: impl Into<Cow<'static, str>>) -> Error {\n        let status = match kind {\n            ErrorKind::Configuration\n            | ErrorKind::Unknown\n            | ErrorKind::UpstreamService\n            | ErrorKind::Internal\n            | ErrorKind::Custom => StatusCode::INTERNAL_SERVER_ERROR,\n            ErrorKind::User => StatusCode::BAD_REQUEST,\n            ErrorKind::NotFound => StatusCode::NOT_FOUND,\n        };\n\n        let body = EndpointError {\n            kind,\n            message: message.into(),\n        };\n\n        Error { status, body }\n    }\n\n    fn with_status(mut self, status_code: StatusCode) -> Self {\n        self.status = status_code;\n        self\n    }\n\n    fn internal<S: fmt::Display>(message: S) -> Self {\n        Error {\n            status: StatusCode::INTERNAL_SERVER_ERROR,\n            body: EndpointError {\n                kind: ErrorKind::Internal,\n                message: message.to_string().into(),\n            },\n        }\n    }\n\n    fn user<S: fmt::Display>(message: S) -> Self {\n        Error {\n            status: StatusCode::BAD_REQUEST,\n            body: EndpointError {\n                kind: ErrorKind::User,\n                message: message.to_string().into(),\n            },\n        }\n    }\n\n    fn not_found<S: fmt::Display>(message: S) -> Self {\n        Error {\n            status: StatusCode::NOT_FOUND,\n            body: EndpointError {\n                kind: ErrorKind::NotFound,\n                message: message.to_string().into(),\n            },\n        }\n    }\n}\n\nimpl From<anyhow::Error> for Error {\n    fn from(value: anyhow::Error) -> Self {\n        Error::internal(value)\n    }\n}\n\nimpl From<sqlx::Error> for Error {\n    fn from(value: sqlx::Error) -> Self {\n        Error::internal(value)\n    }\n}\n\nimpl IntoResponse for Error {\n    fn into_response(self) -> axum::response::Response {\n        (self.status, Json(Response::from(self.body))).into_response()\n    }\n}\n\n/// The response upon encountering an error\n#[derive(serde::Serialize, PartialEq, Eq, Debug)]\npub struct EndpointError<'a> {\n    /// The kind of this error\n    kind: ErrorKind,\n\n    /// A context aware message describing the error\n    message: Cow<'a, str>,\n}\n\n/// The kind of an error\n#[allow(unused)]\n#[derive(serde::Serialize, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\n#[non_exhaustive]\npub enum ErrorKind {\n    User,\n    Unknown,\n    NotFound,\n    Configuration,\n    UpstreamService,\n    Internal,\n\n    // TODO: allow construction of detailed custom kinds\n    #[doc(hidden)]\n    Custom,\n}\n\npub(crate) trait ApiResponse: erased_serde::Serialize {}\nerased_serde::serialize_trait_object!(ApiResponse);\n\n/// Every endpoint exposes a Response type\n#[derive(serde::Serialize)]\n#[serde(untagged)]\n#[non_exhaustive]\npub(crate) enum Response<'a> {\n    Ok(Box<dyn erased_serde::Serialize + Send + Sync + 'static>),\n    Error(EndpointError<'a>),\n}\n\nimpl<T: ApiResponse + Send + Sync + 'static> From<T> for Response<'static> {\n    fn from(value: T) -> Self {\n        Self::Ok(Box::new(value))\n    }\n}\n\nimpl<'a> From<EndpointError<'a>> for Response<'a> {\n    fn from(value: EndpointError<'a>) -> Self {\n        Self::Error(value)\n    }\n}\n\nasync fn health(State(app): State<Application>) {\n    // panic is fine here, we don't need exact reporting of\n    // subsystem checks at this stage\n    app.semantic.health_check().await.unwrap()\n}\n\nfn no_user_id() -> Error {\n    Error::user(\"didn't have user ID\")\n}\n"
  },
  {
    "path": "server/languages.yml",
    "content": "# Copied from https://github.com/github/linguist/blob/master/lib/linguist/languages.yml\n#\n# Copyright (c) 2017 GitHub, Inc.\n#\n# Permission is hereby granted, free of charge, to any person\n# obtaining a copy of this software and associated documentation\n# files (the \"Software\"), to deal in the Software without\n# restriction, including without limitation the rights to use,\n# copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the\n# Software is furnished to do so, subject to the following\n# conditions:\n# \n# The above copyright notice and this permission notice shall be\n# included in all copies or substantial portions of the Software.\n# \n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n# OTHER DEALINGS IN THE SOFTWARE.\n\n# Defines all Languages known to GitHub.\n#\n# fs_name               - Optional field. Only necessary as a replacement for the sample directory name if the\n#                         language name is not a valid filename under the Windows filesystem (e.g., if it\n#                         contains an asterisk).\n# type                  - Either data, programming, markup, prose, or nil\n# aliases               - An Array of additional aliases (implicitly\n#                         includes name.downcase)\n# ace_mode              - A String name of the Ace Mode used for highlighting whenever\n#                         a file is edited. This must match one of the filenames in http://git.io/3XO_Cg.\n#                         Use \"text\" if a mode does not exist.\n# codemirror_mode       - A String name of the CodeMirror Mode used for highlighting whenever a file is edited.\n#                         This must match a mode from https://git.io/vi9Fx\n# codemirror_mime_type  - A String name of the file mime type used for highlighting whenever a file is edited.\n#                         This should match the `mime` associated with the mode from https://git.io/f4SoQ\n# wrap                  - Boolean wrap to enable line wrapping (default: false)\n# extensions            - An Array of associated extensions (the first one is\n#                         considered the primary extension, the others should be\n#                         listed alphabetically)\n# filenames             - An Array of filenames commonly associated with the language\n# interpreters          - An Array of associated interpreters\n# language_id           - Integer used as a language-name-independent indexed field so that we can rename\n#                         languages in Linguist without reindexing all the code on GitHub. Must not be\n#                         changed for existing languages without the explicit permission of GitHub staff.\n# color                 - CSS hex color to represent the language. Only used if type is \"programming\" or \"markup\".\n# tm_scope              - The TextMate scope that represents this programming\n#                         language. This should match one of the scopes listed in\n#                         the grammars.yml file. Use \"none\" if there is no grammar\n#                         for this language.\n# group                 - Name of the parent language. Languages in a group are counted\n#                         in the statistics as the parent language.\n#\n# Any additions or modifications (even trivial) should have corresponding\n# test changes in `test/test_blob.rb`.\n#\n# Please keep this list alphabetized. Capitalization comes before lowercase.\n---\n1C Enterprise:\n  type: programming\n  color: \"#814CCC\"\n  extensions:\n  - \".bsl\"\n  - \".os\"\n  tm_scope: source.bsl\n  ace_mode: text\n  language_id: 0\n2-Dimensional Array:\n  type: data\n  color: \"#38761D\"\n  extensions:\n  - \".2da\"\n  tm_scope: source.2da\n  ace_mode: text\n  language_id: 387204628\n4D:\n  type: programming\n  color: \"#004289\"\n  extensions:\n  - \".4dm\"\n  tm_scope: source.4dm\n  ace_mode: text\n  language_id: 577529595\nABAP:\n  type: programming\n  color: \"#E8274B\"\n  extensions:\n  - \".abap\"\n  tm_scope: source.abap\n  ace_mode: abap\n  language_id: 1\nABAP CDS:\n  type: programming\n  color: \"#555e25\"\n  extensions:\n  - \".asddls\"\n  tm_scope: source.abapcds\n  language_id: 452681853\n  ace_mode: text\nABNF:\n  type: data\n  ace_mode: text\n  extensions:\n  - \".abnf\"\n  tm_scope: source.abnf\n  language_id: 429\nAGS Script:\n  type: programming\n  color: \"#B9D9FF\"\n  aliases:\n  - ags\n  extensions:\n  - \".asc\"\n  - \".ash\"\n  tm_scope: source.c++\n  ace_mode: c_cpp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-c++src\n  language_id: 2\nAIDL:\n  type: programming\n  color: \"#34EB6B\"\n  tm_scope: source.aidl\n  extensions:\n  - \".aidl\"\n  ace_mode: text\n  interpreters:\n  - aidl\n  language_id: 451700185\nAL:\n  type: programming\n  color: \"#3AA2B5\"\n  extensions:\n  - \".al\"\n  tm_scope: source.al\n  ace_mode: text\n  language_id: 658971832\nAMPL:\n  type: programming\n  color: \"#E6EFBB\"\n  extensions:\n  - \".ampl\"\n  - \".mod\"\n  tm_scope: source.ampl\n  ace_mode: text\n  language_id: 3\nANTLR:\n  type: programming\n  color: \"#9DC3FF\"\n  extensions:\n  - \".g4\"\n  tm_scope: source.antlr\n  ace_mode: text\n  language_id: 4\nAPI Blueprint:\n  type: markup\n  color: \"#2ACCA8\"\n  ace_mode: markdown\n  extensions:\n  - \".apib\"\n  tm_scope: text.html.markdown.source.gfm.apib\n  language_id: 5\nAPL:\n  type: programming\n  color: \"#5A8164\"\n  extensions:\n  - \".apl\"\n  - \".dyalog\"\n  interpreters:\n  - apl\n  - aplx\n  - dyalog\n  tm_scope: source.apl\n  ace_mode: text\n  codemirror_mode: apl\n  codemirror_mime_type: text/apl\n  language_id: 6\nASL:\n  type: programming\n  ace_mode: text\n  extensions:\n  - \".asl\"\n  - \".dsl\"\n  tm_scope: source.asl\n  language_id: 124996147\nASN.1:\n  type: data\n  extensions:\n  - \".asn\"\n  - \".asn1\"\n  tm_scope: source.asn\n  ace_mode: text\n  codemirror_mode: asn.1\n  codemirror_mime_type: text/x-ttcn-asn\n  language_id: 7\nASP.NET:\n  type: programming\n  tm_scope: text.html.asp\n  color: \"#9400ff\"\n  aliases:\n  - aspx\n  - aspx-vb\n  extensions:\n  - \".asax\"\n  - \".ascx\"\n  - \".ashx\"\n  - \".asmx\"\n  - \".aspx\"\n  - \".axd\"\n  ace_mode: text\n  codemirror_mode: htmlembedded\n  codemirror_mime_type: application/x-aspx\n  language_id: 564186416\nATS:\n  type: programming\n  color: \"#1ac620\"\n  aliases:\n  - ats2\n  extensions:\n  - \".dats\"\n  - \".hats\"\n  - \".sats\"\n  tm_scope: source.ats\n  ace_mode: ocaml\n  language_id: 9\nActionScript:\n  type: programming\n  tm_scope: source.actionscript.3\n  color: \"#882B0F\"\n  aliases:\n  - actionscript 3\n  - actionscript3\n  - as3\n  extensions:\n  - \".as\"\n  ace_mode: actionscript\n  language_id: 10\nAda:\n  type: programming\n  color: \"#02f88c\"\n  extensions:\n  - \".adb\"\n  - \".ada\"\n  - \".ads\"\n  aliases:\n  - ada95\n  - ada2005\n  tm_scope: source.ada\n  ace_mode: ada\n  language_id: 11\nAdblock Filter List:\n  type: data\n  color: \"#800000\"\n  ace_mode: text\n  extensions:\n  - \".txt\"\n  aliases:\n  - ad block filters\n  - ad block\n  - adb\n  - adblock\n  tm_scope: text.adblock\n  language_id: 884614762\nAdobe Font Metrics:\n  type: data\n  color: \"#fa0f00\"\n  tm_scope: source.afm\n  extensions:\n  - \".afm\"\n  aliases:\n  - acfm\n  - adobe composite font metrics\n  - adobe multiple font metrics\n  - amfm\n  ace_mode: text\n  language_id: 147198098\nAgda:\n  type: programming\n  color: \"#315665\"\n  extensions:\n  - \".agda\"\n  tm_scope: source.agda\n  ace_mode: text\n  language_id: 12\nAlloy:\n  type: programming\n  color: \"#64C800\"\n  extensions:\n  - \".als\"\n  tm_scope: source.alloy\n  ace_mode: text\n  language_id: 13\nAlpine Abuild:\n  type: programming\n  color: \"#0D597F\"\n  group: Shell\n  aliases:\n  - abuild\n  - apkbuild\n  filenames:\n  - APKBUILD\n  tm_scope: source.shell\n  ace_mode: sh\n  codemirror_mode: shell\n  codemirror_mime_type: text/x-sh\n  language_id: 14\nAltium Designer:\n  type: data\n  color: \"#A89663\"\n  aliases:\n  - altium\n  extensions:\n  - \".OutJob\"\n  - \".PcbDoc\"\n  - \".PrjPCB\"\n  - \".SchDoc\"\n  tm_scope: source.ini\n  ace_mode: ini\n  language_id: 187772328\nAngelScript:\n  type: programming\n  color: \"#C7D7DC\"\n  extensions:\n  - \".as\"\n  - \".angelscript\"\n  tm_scope: source.angelscript\n  ace_mode: text\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-c++src\n  language_id: 389477596\nAnt Build System:\n  type: data\n  color: \"#A9157E\"\n  tm_scope: text.xml.ant\n  filenames:\n  - ant.xml\n  - build.xml\n  ace_mode: xml\n  codemirror_mode: xml\n  codemirror_mime_type: application/xml\n  language_id: 15\nAntlers:\n  type: markup\n  color: \"#ff269e\"\n  extensions:\n  - \".antlers.html\"\n  - \".antlers.php\"\n  - \".antlers.xml\"\n  tm_scope: text.html.statamic\n  ace_mode: text\n  language_id: 1067292663\nApacheConf:\n  type: data\n  color: \"#d12127\"\n  aliases:\n  - aconf\n  - apache\n  extensions:\n  - \".apacheconf\"\n  - \".vhost\"\n  filenames:\n  - \".htaccess\"\n  - apache2.conf\n  - httpd.conf\n  tm_scope: source.apache-config\n  ace_mode: apache_conf\n  language_id: 16\nApex:\n  type: programming\n  color: \"#1797c0\"\n  extensions:\n  - \".cls\"\n  tm_scope: source.java\n  ace_mode: java\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-java\n  language_id: 17\nApollo Guidance Computer:\n  type: programming\n  color: \"#0B3D91\"\n  group: Assembly\n  extensions:\n  - \".agc\"\n  tm_scope: source.agc\n  ace_mode: assembly_x86\n  language_id: 18\nAppleScript:\n  type: programming\n  aliases:\n  - osascript\n  extensions:\n  - \".applescript\"\n  - \".scpt\"\n  interpreters:\n  - osascript\n  tm_scope: source.applescript\n  ace_mode: applescript\n  color: \"#101F1F\"\n  language_id: 19\nArc:\n  type: programming\n  color: \"#aa2afe\"\n  extensions:\n  - \".arc\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 20\nAsciiDoc:\n  type: prose\n  color: \"#73a0c5\"\n  ace_mode: asciidoc\n  wrap: true\n  extensions:\n  - \".asciidoc\"\n  - \".adoc\"\n  - \".asc\"\n  tm_scope: text.html.asciidoc\n  language_id: 22\nAspectJ:\n  type: programming\n  color: \"#a957b0\"\n  extensions:\n  - \".aj\"\n  tm_scope: source.aspectj\n  ace_mode: text\n  language_id: 23\nAssembly:\n  type: programming\n  color: \"#6E4C13\"\n  aliases:\n  - asm\n  - nasm\n  extensions:\n  - \".asm\"\n  - \".a51\"\n  - \".i\"\n  - \".inc\"\n  - \".nas\"\n  - \".nasm\"\n  tm_scope: source.assembly\n  ace_mode: assembly_x86\n  language_id: 24\nAstro:\n  type: markup\n  color: \"#ff5a03\"\n  extensions:\n  - \".astro\"\n  tm_scope: source.astro\n  ace_mode: html\n  codemirror_mode: jsx\n  codemirror_mime_type: text/jsx\n  language_id: 578209015\nAsymptote:\n  type: programming\n  color: \"#ff0000\"\n  extensions:\n  - \".asy\"\n  interpreters:\n  - asy\n  tm_scope: source.c++\n  ace_mode: c_cpp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-kotlin\n  language_id: 591605007\nAugeas:\n  type: programming\n  color: \"#9CC134\"\n  extensions:\n  - \".aug\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 25\nAutoHotkey:\n  type: programming\n  color: \"#6594b9\"\n  aliases:\n  - ahk\n  extensions:\n  - \".ahk\"\n  - \".ahkl\"\n  tm_scope: source.ahk\n  ace_mode: autohotkey\n  language_id: 26\nAutoIt:\n  type: programming\n  color: \"#1C3552\"\n  aliases:\n  - au3\n  - AutoIt3\n  - AutoItScript\n  extensions:\n  - \".au3\"\n  tm_scope: source.autoit\n  ace_mode: autohotkey\n  language_id: 27\nAvro IDL:\n  type: data\n  color: \"#0040FF\"\n  extensions:\n  - \".avdl\"\n  tm_scope: source.avro\n  ace_mode: text\n  language_id: 785497837\nAwk:\n  type: programming\n  color: \"#c30e9b\"\n  extensions:\n  - \".awk\"\n  - \".auk\"\n  - \".gawk\"\n  - \".mawk\"\n  - \".nawk\"\n  interpreters:\n  - awk\n  - gawk\n  - mawk\n  - nawk\n  tm_scope: source.awk\n  ace_mode: text\n  language_id: 28\nBASIC:\n  type: programming\n  extensions:\n  - \".bas\"\n  tm_scope: source.basic\n  ace_mode: text\n  color: \"#ff0000\"\n  language_id: 28923963\nBallerina:\n  type: programming\n  extensions:\n  - \".bal\"\n  tm_scope: source.ballerina\n  ace_mode: text\n  color: \"#FF5000\"\n  language_id: 720859680\nBatchfile:\n  type: programming\n  aliases:\n  - bat\n  - batch\n  - dosbatch\n  - winbatch\n  extensions:\n  - \".bat\"\n  - \".cmd\"\n  tm_scope: source.batchfile\n  ace_mode: batchfile\n  color: \"#C1F12E\"\n  language_id: 29\nBeef:\n  type: programming\n  color: \"#a52f4e\"\n  extensions:\n  - \".bf\"\n  tm_scope: source.cs\n  ace_mode: csharp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-csharp\n  language_id: 545626333\nBefunge:\n  type: programming\n  extensions:\n  - \".befunge\"\n  - \".bf\"\n  tm_scope: source.befunge\n  ace_mode: text\n  language_id: 30\nBerry:\n  type: programming\n  extensions:\n  - \".be\"\n  tm_scope: source.berry\n  ace_mode: text\n  color: \"#15A13C\"\n  aliases:\n  - be\n  language_id: 121855308\nBibTeX:\n  type: markup\n  color: \"#778899\"\n  group: TeX\n  extensions:\n  - \".bib\"\n  - \".bibtex\"\n  tm_scope: text.bibtex\n  ace_mode: tex\n  codemirror_mode: stex\n  codemirror_mime_type: text/x-stex\n  language_id: 982188347\nBicep:\n  type: programming\n  color: \"#519aba\"\n  extensions:\n  - \".bicep\"\n  tm_scope: source.bicep\n  ace_mode: text\n  language_id: 321200902\nBikeshed:\n  type: markup\n  color: \"#5562ac\"\n  extensions:\n  - \".bs\"\n  tm_scope: source.csswg\n  ace_mode: html\n  codemirror_mode: htmlmixed\n  codemirror_mime_type: text/html\n  language_id: 1055528081\nBison:\n  type: programming\n  color: \"#6A463F\"\n  group: Yacc\n  tm_scope: source.yacc\n  extensions:\n  - \".bison\"\n  ace_mode: text\n  language_id: 31\nBitBake:\n  type: programming\n  color: \"#00bce4\"\n  tm_scope: none\n  extensions:\n  - \".bb\"\n  ace_mode: text\n  language_id: 32\nBlade:\n  type: markup\n  color: \"#f7523f\"\n  extensions:\n  - \".blade\"\n  - \".blade.php\"\n  tm_scope: text.html.php.blade\n  ace_mode: text\n  language_id: 33\nBlitzBasic:\n  type: programming\n  color: \"#00FFAE\"\n  aliases:\n  - b3d\n  - blitz3d\n  - blitzplus\n  - bplus\n  extensions:\n  - \".bb\"\n  - \".decls\"\n  tm_scope: source.blitzmax\n  ace_mode: text\n  language_id: 34\nBlitzMax:\n  type: programming\n  color: \"#cd6400\"\n  extensions:\n  - \".bmx\"\n  aliases:\n  - bmax\n  tm_scope: source.blitzmax\n  ace_mode: text\n  language_id: 35\nBluespec:\n  type: programming\n  color: \"#12223c\"\n  extensions:\n  - \".bsv\"\n  tm_scope: source.bsv\n  ace_mode: verilog\n  language_id: 36\nBoo:\n  type: programming\n  color: \"#d4bec1\"\n  extensions:\n  - \".boo\"\n  ace_mode: text\n  tm_scope: source.boo\n  language_id: 37\nBoogie:\n  type: programming\n  color: \"#c80fa0\"\n  extensions:\n  - \".bpl\"\n  interpreters:\n  - boogie\n  tm_scope: source.boogie\n  ace_mode: text\n  language_id: 955017407\nBrainfuck:\n  type: programming\n  color: \"#2F2530\"\n  extensions:\n  - \".b\"\n  - \".bf\"\n  tm_scope: source.bf\n  ace_mode: text\n  codemirror_mode: brainfuck\n  codemirror_mime_type: text/x-brainfuck\n  language_id: 38\nBrighterScript:\n  type: programming\n  color: \"#66AABB\"\n  extensions:\n  - \".bs\"\n  tm_scope: source.brs\n  ace_mode: text\n  language_id: 943571030\nBrightscript:\n  type: programming\n  color: \"#662D91\"\n  extensions:\n  - \".brs\"\n  tm_scope: source.brs\n  ace_mode: text\n  language_id: 39\nBrowserslist:\n  type: data\n  color: \"#ffd539\"\n  filenames:\n  - \".browserslistrc\"\n  - browserslist\n  tm_scope: text.browserslist\n  ace_mode: text\n  language_id: 153503348\nC:\n  type: programming\n  color: \"#555555\"\n  extensions:\n  - \".c\"\n  - \".cats\"\n  - \".h\"\n  - \".idc\"\n  interpreters:\n  - tcc\n  tm_scope: source.c\n  ace_mode: c_cpp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-csrc\n  language_id: 41\nC#:\n  type: programming\n  ace_mode: csharp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-csharp\n  tm_scope: source.cs\n  color: \"#178600\"\n  aliases:\n  - csharp\n  - cake\n  - cakescript\n  extensions:\n  - \".cs\"\n  - \".cake\"\n  - \".csx\"\n  - \".linq\"\n  language_id: 42\nC++:\n  type: programming\n  tm_scope: source.c++\n  ace_mode: c_cpp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-c++src\n  color: \"#f34b7d\"\n  aliases:\n  - cpp\n  extensions:\n  - \".cpp\"\n  - \".c++\"\n  - \".cc\"\n  - \".cp\"\n  - \".cxx\"\n  - \".h\"\n  - \".h++\"\n  - \".hh\"\n  - \".hpp\"\n  - \".hxx\"\n  - \".inc\"\n  - \".inl\"\n  - \".ino\"\n  - \".ipp\"\n  - \".ixx\"\n  - \".re\"\n  - \".tcc\"\n  - \".tpp\"\n  language_id: 43\nC-ObjDump:\n  type: data\n  extensions:\n  - \".c-objdump\"\n  tm_scope: objdump.x86asm\n  ace_mode: assembly_x86\n  language_id: 44\nC2hs Haskell:\n  type: programming\n  group: Haskell\n  aliases:\n  - c2hs\n  extensions:\n  - \".chs\"\n  tm_scope: source.haskell\n  ace_mode: haskell\n  codemirror_mode: haskell\n  codemirror_mime_type: text/x-haskell\n  language_id: 45\nCAP CDS:\n  type: programming\n  tm_scope: source.cds\n  color: \"#0092d1\"\n  aliases:\n  - cds\n  extensions:\n  - \".cds\"\n  ace_mode: text\n  language_id: 390788699\nCIL:\n  type: data\n  tm_scope: source.cil\n  extensions:\n  - \".cil\"\n  ace_mode: text\n  language_id: 29176339\nCLIPS:\n  type: programming\n  color: \"#00A300\"\n  extensions:\n  - \".clp\"\n  tm_scope: source.clips\n  ace_mode: text\n  language_id: 46\nCMake:\n  type: programming\n  color: \"#DA3434\"\n  extensions:\n  - \".cmake\"\n  - \".cmake.in\"\n  filenames:\n  - CMakeLists.txt\n  tm_scope: source.cmake\n  ace_mode: text\n  codemirror_mode: cmake\n  codemirror_mime_type: text/x-cmake\n  language_id: 47\nCOBOL:\n  type: programming\n  extensions:\n  - \".cob\"\n  - \".cbl\"\n  - \".ccp\"\n  - \".cobol\"\n  - \".cpy\"\n  tm_scope: source.cobol\n  ace_mode: cobol\n  codemirror_mode: cobol\n  codemirror_mime_type: text/x-cobol\n  language_id: 48\nCODEOWNERS:\n  type: data\n  filenames:\n  - CODEOWNERS\n  tm_scope: text.codeowners\n  ace_mode: gitignore\n  language_id: 321684729\nCOLLADA:\n  type: data\n  color: \"#F1A42B\"\n  extensions:\n  - \".dae\"\n  tm_scope: text.xml\n  ace_mode: xml\n  codemirror_mode: xml\n  codemirror_mime_type: text/xml\n  language_id: 49\nCSON:\n  type: data\n  color: \"#244776\"\n  tm_scope: source.coffee\n  ace_mode: coffee\n  codemirror_mode: coffeescript\n  codemirror_mime_type: text/x-coffeescript\n  extensions:\n  - \".cson\"\n  language_id: 424\nCSS:\n  type: markup\n  tm_scope: source.css\n  ace_mode: css\n  codemirror_mode: css\n  codemirror_mime_type: text/css\n  color: \"#563d7c\"\n  extensions:\n  - \".css\"\n  language_id: 50\nCSV:\n  type: data\n  color: \"#237346\"\n  ace_mode: text\n  tm_scope: none\n  extensions:\n  - \".csv\"\n  language_id: 51\nCUE:\n  type: programming\n  extensions:\n  - \".cue\"\n  tm_scope: source.cue\n  ace_mode: text\n  color: \"#5886E1\"\n  language_id: 356063509\nCWeb:\n  type: programming\n  color: \"#00007a\"\n  extensions:\n  - \".w\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 657332628\nCabal Config:\n  type: data\n  color: \"#483465\"\n  aliases:\n  - Cabal\n  extensions:\n  - \".cabal\"\n  filenames:\n  - cabal.config\n  - cabal.project\n  ace_mode: haskell\n  codemirror_mode: haskell\n  codemirror_mime_type: text/x-haskell\n  tm_scope: source.cabal\n  language_id: 677095381\nCadence:\n  type: programming\n  color: \"#00ef8b\"\n  ace_mode: text\n  tm_scope: source.cadence\n  extensions:\n  - \".cdc\"\n  language_id: 270184138\nCairo:\n  type: programming\n  color: \"#ff4a48\"\n  ace_mode: text\n  tm_scope: source.cairo\n  extensions:\n  - \".cairo\"\n  language_id: 620599567\nCameLIGO:\n  type: programming\n  color: \"#3be133\"\n  extensions:\n  - \".mligo\"\n  tm_scope: source.mligo\n  ace_mode: ocaml\n  codemirror_mode: mllike\n  codemirror_mime_type: text/x-ocaml\n  group: LigoLANG\n  language_id: 829207807\nCap'n Proto:\n  type: programming\n  color: \"#c42727\"\n  tm_scope: source.capnp\n  extensions:\n  - \".capnp\"\n  ace_mode: text\n  language_id: 52\nCartoCSS:\n  type: programming\n  aliases:\n  - Carto\n  extensions:\n  - \".mss\"\n  ace_mode: text\n  tm_scope: source.css.mss\n  language_id: 53\nCeylon:\n  type: programming\n  color: \"#dfa535\"\n  extensions:\n  - \".ceylon\"\n  tm_scope: source.ceylon\n  ace_mode: text\n  language_id: 54\nChapel:\n  type: programming\n  color: \"#8dc63f\"\n  aliases:\n  - chpl\n  extensions:\n  - \".chpl\"\n  tm_scope: source.chapel\n  ace_mode: text\n  language_id: 55\nCharity:\n  type: programming\n  extensions:\n  - \".ch\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 56\nChecksums:\n  type: data\n  tm_scope: text.checksums\n  aliases:\n  - checksum\n  - hash\n  - hashes\n  - sum\n  - sums\n  filenames:\n  - MD5SUMS\n  - SHA1SUMS\n  - SHA256SUMS\n  - SHA256SUMS.txt\n  - SHA512SUMS\n  - checksums.txt\n  - cksums\n  - md5sum.txt\n  extensions:\n  - \".crc32\"\n  - \".md2\"\n  - \".md4\"\n  - \".md5\"\n  - \".sha1\"\n  - \".sha2\"\n  - \".sha224\"\n  - \".sha256\"\n  - \".sha256sum\"\n  - \".sha3\"\n  - \".sha384\"\n  - \".sha512\"\n  ace_mode: text\n  language_id: 372063053\nChucK:\n  type: programming\n  color: \"#3f8000\"\n  extensions:\n  - \".ck\"\n  tm_scope: source.java\n  ace_mode: java\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-java\n  language_id: 57\nCirru:\n  type: programming\n  color: \"#ccccff\"\n  tm_scope: source.cirru\n  ace_mode: cirru\n  extensions:\n  - \".cirru\"\n  language_id: 58\nClarion:\n  type: programming\n  color: \"#db901e\"\n  ace_mode: text\n  extensions:\n  - \".clw\"\n  tm_scope: source.clarion\n  language_id: 59\nClarity:\n  type: programming\n  color: \"#5546ff\"\n  ace_mode: lisp\n  extensions:\n  - \".clar\"\n  tm_scope: source.clar\n  language_id: 91493841\nClassic ASP:\n  type: programming\n  color: \"#6a40fd\"\n  tm_scope: text.html.asp\n  aliases:\n  - asp\n  extensions:\n  - \".asp\"\n  ace_mode: text\n  language_id: 8\nClean:\n  type: programming\n  color: \"#3F85AF\"\n  extensions:\n  - \".icl\"\n  - \".dcl\"\n  tm_scope: source.clean\n  ace_mode: text\n  language_id: 60\nClick:\n  type: programming\n  color: \"#E4E6F3\"\n  extensions:\n  - \".click\"\n  tm_scope: source.click\n  ace_mode: text\n  language_id: 61\nClojure:\n  type: programming\n  tm_scope: source.clojure\n  ace_mode: clojure\n  codemirror_mode: clojure\n  codemirror_mime_type: text/x-clojure\n  color: \"#db5855\"\n  extensions:\n  - \".clj\"\n  - \".bb\"\n  - \".boot\"\n  - \".cl2\"\n  - \".cljc\"\n  - \".cljs\"\n  - \".cljs.hl\"\n  - \".cljscm\"\n  - \".cljx\"\n  - \".hic\"\n  filenames:\n  - riemann.config\n  interpreters:\n  - bb\n  language_id: 62\nClosure Templates:\n  type: markup\n  color: \"#0d948f\"\n  ace_mode: soy_template\n  codemirror_mode: soy\n  codemirror_mime_type: text/x-soy\n  aliases:\n  - soy\n  extensions:\n  - \".soy\"\n  tm_scope: text.html.soy\n  language_id: 357046146\nCloud Firestore Security Rules:\n  type: data\n  color: \"#FFA000\"\n  ace_mode: less\n  codemirror_mode: css\n  codemirror_mime_type: text/css\n  tm_scope: source.firestore\n  filenames:\n  - firestore.rules\n  language_id: 407996372\nCoNLL-U:\n  type: data\n  extensions:\n  - \".conllu\"\n  - \".conll\"\n  tm_scope: text.conllu\n  ace_mode: text\n  aliases:\n  - CoNLL\n  - CoNLL-X\n  language_id: 421026389\nCodeQL:\n  type: programming\n  color: \"#140f46\"\n  extensions:\n  - \".ql\"\n  - \".qll\"\n  tm_scope: source.ql\n  ace_mode: text\n  language_id: 424259634\n  aliases:\n  - ql\nCoffeeScript:\n  type: programming\n  tm_scope: source.coffee\n  ace_mode: coffee\n  codemirror_mode: coffeescript\n  codemirror_mime_type: text/x-coffeescript\n  color: \"#244776\"\n  aliases:\n  - coffee\n  - coffee-script\n  extensions:\n  - \".coffee\"\n  - \"._coffee\"\n  - \".cake\"\n  - \".cjsx\"\n  - \".iced\"\n  filenames:\n  - Cakefile\n  interpreters:\n  - coffee\n  language_id: 63\nColdFusion:\n  type: programming\n  ace_mode: coldfusion\n  color: \"#ed2cd6\"\n  aliases:\n  - cfm\n  - cfml\n  - coldfusion html\n  extensions:\n  - \".cfm\"\n  - \".cfml\"\n  tm_scope: text.html.cfm\n  language_id: 64\nColdFusion CFC:\n  type: programming\n  color: \"#ed2cd6\"\n  group: ColdFusion\n  ace_mode: coldfusion\n  aliases:\n  - cfc\n  extensions:\n  - \".cfc\"\n  tm_scope: source.cfscript\n  language_id: 65\nCommon Lisp:\n  type: programming\n  tm_scope: source.lisp\n  color: \"#3fb68b\"\n  aliases:\n  - lisp\n  extensions:\n  - \".lisp\"\n  - \".asd\"\n  - \".cl\"\n  - \".l\"\n  - \".lsp\"\n  - \".ny\"\n  - \".podsl\"\n  - \".sexp\"\n  interpreters:\n  - lisp\n  - sbcl\n  - ccl\n  - clisp\n  - ecl\n  ace_mode: lisp\n  codemirror_mode: commonlisp\n  codemirror_mime_type: text/x-common-lisp\n  language_id: 66\nCommon Workflow Language:\n  aliases:\n  - cwl\n  type: programming\n  ace_mode: yaml\n  codemirror_mode: yaml\n  codemirror_mime_type: text/x-yaml\n  extensions:\n  - \".cwl\"\n  interpreters:\n  - cwl-runner\n  color: \"#B5314C\"\n  tm_scope: source.cwl\n  language_id: 988547172\nComponent Pascal:\n  type: programming\n  color: \"#B0CE4E\"\n  extensions:\n  - \".cp\"\n  - \".cps\"\n  tm_scope: source.pascal\n  ace_mode: pascal\n  codemirror_mode: pascal\n  codemirror_mime_type: text/x-pascal\n  language_id: 67\nCool:\n  type: programming\n  extensions:\n  - \".cl\"\n  tm_scope: source.cool\n  ace_mode: text\n  language_id: 68\nCoq:\n  type: programming\n  color: \"#d0b68c\"\n  extensions:\n  - \".coq\"\n  - \".v\"\n  tm_scope: source.coq\n  ace_mode: text\n  language_id: 69\nCpp-ObjDump:\n  type: data\n  extensions:\n  - \".cppobjdump\"\n  - \".c++-objdump\"\n  - \".c++objdump\"\n  - \".cpp-objdump\"\n  - \".cxx-objdump\"\n  tm_scope: objdump.x86asm\n  aliases:\n  - c++-objdump\n  ace_mode: assembly_x86\n  language_id: 70\nCreole:\n  type: prose\n  wrap: true\n  extensions:\n  - \".creole\"\n  tm_scope: text.html.creole\n  ace_mode: text\n  language_id: 71\nCrystal:\n  type: programming\n  color: \"#000100\"\n  extensions:\n  - \".cr\"\n  ace_mode: ruby\n  codemirror_mode: crystal\n  codemirror_mime_type: text/x-crystal\n  tm_scope: source.crystal\n  interpreters:\n  - crystal\n  language_id: 72\nCsound:\n  type: programming\n  color: \"#1a1a1a\"\n  aliases:\n  - csound-orc\n  extensions:\n  - \".orc\"\n  - \".udo\"\n  tm_scope: source.csound\n  ace_mode: csound_orchestra\n  language_id: 73\nCsound Document:\n  type: programming\n  color: \"#1a1a1a\"\n  aliases:\n  - csound-csd\n  extensions:\n  - \".csd\"\n  tm_scope: source.csound-document\n  ace_mode: csound_document\n  language_id: 74\nCsound Score:\n  type: programming\n  color: \"#1a1a1a\"\n  aliases:\n  - csound-sco\n  extensions:\n  - \".sco\"\n  tm_scope: source.csound-score\n  ace_mode: csound_score\n  language_id: 75\nCuda:\n  type: programming\n  extensions:\n  - \".cu\"\n  - \".cuh\"\n  tm_scope: source.cuda-c++\n  ace_mode: c_cpp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-c++src\n  color: \"#3A4E3A\"\n  language_id: 77\nCue Sheet:\n  type: data\n  extensions:\n  - \".cue\"\n  tm_scope: source.cuesheet\n  ace_mode: text\n  language_id: 942714150\nCurry:\n  type: programming\n  color: \"#531242\"\n  extensions:\n  - \".curry\"\n  tm_scope: source.curry\n  ace_mode: haskell\n  language_id: 439829048\nCycript:\n  type: programming\n  extensions:\n  - \".cy\"\n  tm_scope: source.js\n  ace_mode: javascript\n  codemirror_mode: javascript\n  codemirror_mime_type: text/javascript\n  language_id: 78\nCypher:\n  type: programming\n  color: \"#34c0eb\"\n  extensions:\n  - \".cyp\"\n  - \".cypher\"\n  tm_scope: source.cypher\n  ace_mode: text\n  language_id: 850806976\nCython:\n  type: programming\n  color: \"#fedf5b\"\n  extensions:\n  - \".pyx\"\n  - \".pxd\"\n  - \".pxi\"\n  aliases:\n  - pyrex\n  tm_scope: source.cython\n  ace_mode: text\n  codemirror_mode: python\n  codemirror_mime_type: text/x-cython\n  language_id: 79\nD:\n  type: programming\n  color: \"#ba595e\"\n  aliases:\n  - Dlang\n  extensions:\n  - \".d\"\n  - \".di\"\n  tm_scope: source.d\n  ace_mode: d\n  codemirror_mode: d\n  codemirror_mime_type: text/x-d\n  language_id: 80\nD-ObjDump:\n  type: data\n  extensions:\n  - \".d-objdump\"\n  tm_scope: objdump.x86asm\n  ace_mode: assembly_x86\n  language_id: 81\nDIGITAL Command Language:\n  type: programming\n  aliases:\n  - dcl\n  extensions:\n  - \".com\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 82\nDM:\n  type: programming\n  color: \"#447265\"\n  extensions:\n  - \".dm\"\n  aliases:\n  - byond\n  tm_scope: source.dm\n  ace_mode: c_cpp\n  language_id: 83\nDNS Zone:\n  type: data\n  extensions:\n  - \".zone\"\n  - \".arpa\"\n  tm_scope: text.zone_file\n  ace_mode: text\n  language_id: 84\nDTrace:\n  type: programming\n  aliases:\n  - dtrace-script\n  extensions:\n  - \".d\"\n  interpreters:\n  - dtrace\n  tm_scope: source.c\n  ace_mode: c_cpp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-csrc\n  language_id: 85\nDafny:\n  type: programming\n  color: \"#FFEC25\"\n  extensions:\n  - \".dfy\"\n  interpreters:\n  - dafny\n  tm_scope: text.dfy.dafny\n  ace_mode: text\n  language_id: 969323346\nDarcs Patch:\n  type: data\n  color: \"#8eff23\"\n  aliases:\n  - dpatch\n  extensions:\n  - \".darcspatch\"\n  - \".dpatch\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 86\nDart:\n  type: programming\n  color: \"#00B4AB\"\n  extensions:\n  - \".dart\"\n  interpreters:\n  - dart\n  tm_scope: source.dart\n  ace_mode: dart\n  codemirror_mode: dart\n  codemirror_mime_type: application/dart\n  language_id: 87\nDataWeave:\n  type: programming\n  color: \"#003a52\"\n  extensions:\n  - \".dwl\"\n  ace_mode: text\n  tm_scope: source.data-weave\n  language_id: 974514097\nDebian Package Control File:\n  type: data\n  color: \"#D70751\"\n  extensions:\n  - \".dsc\"\n  tm_scope: source.deb-control\n  ace_mode: text\n  language_id: 527438264\nDenizenScript:\n  type: programming\n  color: \"#FBEE96\"\n  ace_mode: yaml\n  codemirror_mode: yaml\n  codemirror_mime_type: text/x-yaml\n  extensions:\n  - \".dsc\"\n  tm_scope: source.denizenscript\n  language_id: 435000929\nDhall:\n  type: programming\n  color: \"#dfafff\"\n  extensions:\n  - \".dhall\"\n  tm_scope: source.haskell\n  ace_mode: haskell\n  codemirror_mode: haskell\n  codemirror_mime_type: text/x-haskell\n  language_id: 793969321\nDiff:\n  type: data\n  extensions:\n  - \".diff\"\n  - \".patch\"\n  aliases:\n  - udiff\n  tm_scope: source.diff\n  ace_mode: diff\n  codemirror_mode: diff\n  codemirror_mime_type: text/x-diff\n  language_id: 88\nDirectX 3D File:\n  type: data\n  color: \"#aace60\"\n  extensions:\n  - \".x\"\n  ace_mode: text\n  tm_scope: none\n  language_id: 201049282\nDockerfile:\n  type: programming\n  aliases:\n  - Containerfile\n  color: \"#384d54\"\n  tm_scope: source.dockerfile\n  extensions:\n  - \".dockerfile\"\n  filenames:\n  - Containerfile\n  - Dockerfile\n  ace_mode: dockerfile\n  codemirror_mode: dockerfile\n  codemirror_mime_type: text/x-dockerfile\n  language_id: 89\nDogescript:\n  type: programming\n  color: \"#cca760\"\n  extensions:\n  - \".djs\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 90\nDylan:\n  type: programming\n  color: \"#6c616e\"\n  extensions:\n  - \".dylan\"\n  - \".dyl\"\n  - \".intr\"\n  - \".lid\"\n  tm_scope: source.dylan\n  ace_mode: text\n  codemirror_mode: dylan\n  codemirror_mime_type: text/x-dylan\n  language_id: 91\nE:\n  type: programming\n  color: \"#ccce35\"\n  extensions:\n  - \".e\"\n  interpreters:\n  - rune\n  tm_scope: none\n  ace_mode: text\n  language_id: 92\nE-mail:\n  type: data\n  aliases:\n  - email\n  - eml\n  - mail\n  - mbox\n  extensions:\n  - \".eml\"\n  - \".mbox\"\n  tm_scope: text.eml.basic\n  ace_mode: text\n  codemirror_mode: mbox\n  codemirror_mime_type: application/mbox\n  language_id: 529653389\nEBNF:\n  type: data\n  extensions:\n  - \".ebnf\"\n  tm_scope: source.ebnf\n  ace_mode: text\n  codemirror_mode: ebnf\n  codemirror_mime_type: text/x-ebnf\n  language_id: 430\nECL:\n  type: programming\n  color: \"#8a1267\"\n  extensions:\n  - \".ecl\"\n  - \".eclxml\"\n  tm_scope: source.ecl\n  ace_mode: text\n  codemirror_mode: ecl\n  codemirror_mime_type: text/x-ecl\n  language_id: 93\nECLiPSe:\n  type: programming\n  color: \"#001d9d\"\n  group: prolog\n  extensions:\n  - \".ecl\"\n  tm_scope: source.prolog.eclipse\n  ace_mode: prolog\n  language_id: 94\nEJS:\n  type: markup\n  color: \"#a91e50\"\n  extensions:\n  - \".ejs\"\n  - \".ect\"\n  - \".ejs.t\"\n  - \".jst\"\n  tm_scope: text.html.js\n  ace_mode: ejs\n  language_id: 95\nEQ:\n  type: programming\n  color: \"#a78649\"\n  extensions:\n  - \".eq\"\n  tm_scope: source.cs\n  ace_mode: csharp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-csharp\n  language_id: 96\nEagle:\n  type: data\n  extensions:\n  - \".sch\"\n  - \".brd\"\n  tm_scope: text.xml\n  ace_mode: xml\n  codemirror_mode: xml\n  codemirror_mime_type: text/xml\n  language_id: 97\nEarthly:\n  type: programming\n  aliases:\n  - Earthfile\n  color: \"#2af0ff\"\n  tm_scope: source.earthfile\n  ace_mode: text\n  filenames:\n  - Earthfile\n  language_id: 963512632\nEasybuild:\n  type: data\n  color: \"#069406\"\n  group: Python\n  ace_mode: python\n  codemirror_mode: python\n  codemirror_mime_type: text/x-python\n  tm_scope: source.python\n  extensions:\n  - \".eb\"\n  language_id: 342840477\nEcere Projects:\n  type: data\n  color: \"#913960\"\n  group: JavaScript\n  extensions:\n  - \".epj\"\n  tm_scope: source.json\n  ace_mode: json\n  codemirror_mode: javascript\n  codemirror_mime_type: application/json\n  language_id: 98\nEditorConfig:\n  type: data\n  color: \"#fff1f2\"\n  group: INI\n  filenames:\n  - \".editorconfig\"\n  aliases:\n  - editor-config\n  ace_mode: ini\n  codemirror_mode: properties\n  codemirror_mime_type: text/x-properties\n  tm_scope: source.editorconfig\n  language_id: 96139566\nEdje Data Collection:\n  type: data\n  extensions:\n  - \".edc\"\n  tm_scope: source.c++\n  ace_mode: c_cpp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-c++src\n  language_id: 342840478\nEiffel:\n  type: programming\n  color: \"#4d6977\"\n  extensions:\n  - \".e\"\n  tm_scope: source.eiffel\n  ace_mode: eiffel\n  codemirror_mode: eiffel\n  codemirror_mime_type: text/x-eiffel\n  language_id: 99\nElixir:\n  type: programming\n  color: \"#6e4a7e\"\n  extensions:\n  - \".ex\"\n  - \".exs\"\n  tm_scope: source.elixir\n  ace_mode: elixir\n  filenames:\n  - mix.lock\n  interpreters:\n  - elixir\n  language_id: 100\nElm:\n  type: programming\n  color: \"#60B5CC\"\n  extensions:\n  - \".elm\"\n  tm_scope: source.elm\n  ace_mode: elm\n  codemirror_mode: elm\n  codemirror_mime_type: text/x-elm\n  language_id: 101\nElvish:\n  type: programming\n  ace_mode: text\n  extensions:\n  - \".elv\"\n  interpreters:\n  - elvish\n  tm_scope: source.elvish\n  color: \"#55BB55\"\n  language_id: 570996448\nEmacs Lisp:\n  type: programming\n  tm_scope: source.emacs.lisp\n  color: \"#c065db\"\n  aliases:\n  - elisp\n  - emacs\n  filenames:\n  - \".abbrev_defs\"\n  - \".emacs\"\n  - \".emacs.desktop\"\n  - \".gnus\"\n  - \".spacemacs\"\n  - \".viper\"\n  - Cask\n  - Project.ede\n  - _emacs\n  - abbrev_defs\n  extensions:\n  - \".el\"\n  - \".emacs\"\n  - \".emacs.desktop\"\n  ace_mode: lisp\n  codemirror_mode: commonlisp\n  codemirror_mime_type: text/x-common-lisp\n  language_id: 102\nEmberScript:\n  type: programming\n  color: \"#FFF4F3\"\n  extensions:\n  - \".em\"\n  - \".emberscript\"\n  tm_scope: source.coffee\n  ace_mode: coffee\n  codemirror_mode: coffeescript\n  codemirror_mime_type: text/x-coffeescript\n  language_id: 103\nErlang:\n  type: programming\n  color: \"#B83998\"\n  extensions:\n  - \".erl\"\n  - \".app.src\"\n  - \".es\"\n  - \".escript\"\n  - \".hrl\"\n  - \".xrl\"\n  - \".yrl\"\n  filenames:\n  - Emakefile\n  - rebar.config\n  - rebar.config.lock\n  - rebar.lock\n  tm_scope: source.erlang\n  ace_mode: erlang\n  codemirror_mode: erlang\n  codemirror_mime_type: text/x-erlang\n  interpreters:\n  - escript\n  language_id: 104\nEuphoria:\n  type: programming\n  color: \"#FF790B\"\n  extensions:\n  - \".e\"\n  - \".ex\"\n  interpreters:\n  - eui\n  - euiw\n  ace_mode: text\n  tm_scope: source.euphoria\n  language_id: 880693982\nF#:\n  type: programming\n  color: \"#b845fc\"\n  aliases:\n  - fsharp\n  extensions:\n  - \".fs\"\n  - \".fsi\"\n  - \".fsx\"\n  tm_scope: source.fsharp\n  ace_mode: text\n  codemirror_mode: mllike\n  codemirror_mime_type: text/x-fsharp\n  language_id: 105\nF*:\n  fs_name: Fstar\n  type: programming\n  color: \"#572e30\"\n  aliases:\n  - fstar\n  extensions:\n  - \".fst\"\n  - \".fsti\"\n  tm_scope: source.fstar\n  ace_mode: text\n  language_id: 336943375\nFIGlet Font:\n  type: data\n  color: \"#FFDDBB\"\n  aliases:\n  - FIGfont\n  extensions:\n  - \".flf\"\n  tm_scope: source.figfont\n  ace_mode: text\n  language_id: 686129783\nFLUX:\n  type: programming\n  color: \"#88ccff\"\n  extensions:\n  - \".fx\"\n  - \".flux\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 106\nFactor:\n  type: programming\n  color: \"#636746\"\n  extensions:\n  - \".factor\"\n  filenames:\n  - \".factor-boot-rc\"\n  - \".factor-rc\"\n  tm_scope: source.factor\n  ace_mode: text\n  codemirror_mode: factor\n  codemirror_mime_type: text/x-factor\n  language_id: 108\nFancy:\n  type: programming\n  color: \"#7b9db4\"\n  extensions:\n  - \".fy\"\n  - \".fancypack\"\n  filenames:\n  - Fakefile\n  tm_scope: source.fancy\n  ace_mode: text\n  language_id: 109\nFantom:\n  type: programming\n  color: \"#14253c\"\n  extensions:\n  - \".fan\"\n  tm_scope: source.fan\n  ace_mode: text\n  language_id: 110\nFaust:\n  type: programming\n  color: \"#c37240\"\n  extensions:\n  - \".dsp\"\n  tm_scope: source.faust\n  ace_mode: text\n  language_id: 622529198\nFennel:\n  type: programming\n  tm_scope: source.fnl\n  ace_mode: text\n  color: \"#fff3d7\"\n  interpreters:\n  - fennel\n  extensions:\n  - \".fnl\"\n  language_id: 239946126\nFilebench WML:\n  type: programming\n  color: \"#F6B900\"\n  extensions:\n  - \".f\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 111\nFilterscript:\n  type: programming\n  group: RenderScript\n  extensions:\n  - \".fs\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 112\nFluent:\n  type: programming\n  color: \"#ffcc33\"\n  extensions:\n  - \".ftl\"\n  tm_scope: source.ftl\n  ace_mode: text\n  language_id: 206353404\nFormatted:\n  type: data\n  extensions:\n  - \".for\"\n  - \".eam.fs\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 113\nForth:\n  type: programming\n  color: \"#341708\"\n  extensions:\n  - \".fth\"\n  - \".4th\"\n  - \".f\"\n  - \".for\"\n  - \".forth\"\n  - \".fr\"\n  - \".frt\"\n  - \".fs\"\n  tm_scope: source.forth\n  ace_mode: forth\n  codemirror_mode: forth\n  codemirror_mime_type: text/x-forth\n  language_id: 114\nFortran:\n  group: Fortran\n  type: programming\n  color: \"#4d41b1\"\n  extensions:\n  - \".f\"\n  - \".f77\"\n  - \".for\"\n  - \".fpp\"\n  tm_scope: source.fortran\n  ace_mode: text\n  codemirror_mode: fortran\n  codemirror_mime_type: text/x-fortran\n  language_id: 107\nFortran Free Form:\n  group: Fortran\n  color: \"#4d41b1\"\n  type: programming\n  extensions:\n  - \".f90\"\n  - \".f03\"\n  - \".f08\"\n  - \".f95\"\n  tm_scope: source.fortran.modern\n  ace_mode: text\n  codemirror_mode: fortran\n  codemirror_mime_type: text/x-fortran\n  language_id: 761352333\nFreeBasic:\n  type: programming\n  color: \"#867db1\"\n  extensions:\n  - \".bi\"\n  - \".bas\"\n  tm_scope: source.vbnet\n  aliases:\n  - fb\n  ace_mode: text\n  codemirror_mode: vb\n  codemirror_mime_type: text/x-vb\n  language_id: 472896659\nFreeMarker:\n  type: programming\n  color: \"#0050b2\"\n  aliases:\n  - ftl\n  extensions:\n  - \".ftl\"\n  tm_scope: text.html.ftl\n  ace_mode: ftl\n  language_id: 115\nFrege:\n  type: programming\n  color: \"#00cafe\"\n  extensions:\n  - \".fr\"\n  tm_scope: source.haskell\n  ace_mode: haskell\n  language_id: 116\nFuthark:\n  type: programming\n  color: \"#5f021f\"\n  extensions:\n  - \".fut\"\n  tm_scope: source.futhark\n  ace_mode: text\n  language_id: 97358117\nG-code:\n  type: programming\n  color: \"#D08CF2\"\n  extensions:\n  - \".g\"\n  - \".cnc\"\n  - \".gco\"\n  - \".gcode\"\n  tm_scope: source.gcode\n  ace_mode: gcode\n  language_id: 117\nGAML:\n  type: programming\n  color: \"#FFC766\"\n  extensions:\n  - \".gaml\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 290345951\nGAMS:\n  type: programming\n  color: \"#f49a22\"\n  extensions:\n  - \".gms\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 118\nGAP:\n  type: programming\n  color: \"#0000cc\"\n  extensions:\n  - \".g\"\n  - \".gap\"\n  - \".gd\"\n  - \".gi\"\n  - \".tst\"\n  tm_scope: source.gap\n  ace_mode: text\n  language_id: 119\nGCC Machine Description:\n  type: programming\n  color: \"#FFCFAB\"\n  extensions:\n  - \".md\"\n  tm_scope: source.lisp\n  ace_mode: lisp\n  codemirror_mode: commonlisp\n  codemirror_mime_type: text/x-common-lisp\n  language_id: 121\nGDB:\n  type: programming\n  extensions:\n  - \".gdb\"\n  - \".gdbinit\"\n  tm_scope: source.gdb\n  ace_mode: text\n  language_id: 122\nGDScript:\n  type: programming\n  color: \"#355570\"\n  extensions:\n  - \".gd\"\n  tm_scope: source.gdscript\n  ace_mode: text\n  language_id: 123\nGEDCOM:\n  type: data\n  color: \"#003058\"\n  ace_mode: text\n  extensions:\n  - \".ged\"\n  tm_scope: source.gedcom\n  language_id: 459577965\nGLSL:\n  type: programming\n  color: \"#5686a5\"\n  extensions:\n  - \".glsl\"\n  - \".fp\"\n  - \".frag\"\n  - \".frg\"\n  - \".fs\"\n  - \".fsh\"\n  - \".fshader\"\n  - \".geo\"\n  - \".geom\"\n  - \".glslf\"\n  - \".glslv\"\n  - \".gs\"\n  - \".gshader\"\n  - \".rchit\"\n  - \".rmiss\"\n  - \".shader\"\n  - \".tesc\"\n  - \".tese\"\n  - \".vert\"\n  - \".vrx\"\n  - \".vsh\"\n  - \".vshader\"\n  tm_scope: source.glsl\n  ace_mode: glsl\n  language_id: 124\nGN:\n  type: data\n  extensions:\n  - \".gn\"\n  - \".gni\"\n  interpreters:\n  - gn\n  filenames:\n  - \".gn\"\n  tm_scope: source.gn\n  ace_mode: python\n  codemirror_mode: python\n  codemirror_mime_type: text/x-python\n  language_id: 302957008\nGSC:\n  type: programming\n  color: \"#FF6800\"\n  extensions:\n  - \".gsc\"\n  - \".csc\"\n  - \".gsh\"\n  tm_scope: source.gsc\n  ace_mode: c_cpp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-csrc\n  language_id: 257856279\nGame Maker Language:\n  type: programming\n  color: \"#71b417\"\n  extensions:\n  - \".gml\"\n  tm_scope: source.c++\n  ace_mode: c_cpp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-c++src\n  language_id: 125\nGemfile.lock:\n  type: data\n  color: \"#701516\"\n  searchable: false\n  tm_scope: source.gemfile-lock\n  ace_mode: text\n  filenames:\n  - Gemfile.lock\n  language_id: 907065713\nGemini:\n  type: prose\n  color: \"#ff6900\"\n  ace_mode: text\n  extensions:\n  - \".gmi\"\n  aliases:\n  - gemtext\n  wrap: true\n  tm_scope: source.gemini\n  language_id: 310828396\nGenero:\n  type: programming\n  color: \"#63408e\"\n  extensions:\n  - \".4gl\"\n  tm_scope: source.genero\n  ace_mode: text\n  language_id: 986054050\nGenero Forms:\n  type: markup\n  color: \"#d8df39\"\n  extensions:\n  - \".per\"\n  tm_scope: source.genero-forms\n  ace_mode: text\n  language_id: 902995658\nGenie:\n  type: programming\n  ace_mode: text\n  extensions:\n  - \".gs\"\n  color: \"#fb855d\"\n  tm_scope: none\n  language_id: 792408528\nGenshi:\n  type: programming\n  color: \"#951531\"\n  extensions:\n  - \".kid\"\n  tm_scope: text.xml.genshi\n  aliases:\n  - xml+genshi\n  - xml+kid\n  ace_mode: xml\n  codemirror_mode: xml\n  codemirror_mime_type: text/xml\n  language_id: 126\nGentoo Ebuild:\n  type: programming\n  color: \"#9400ff\"\n  group: Shell\n  extensions:\n  - \".ebuild\"\n  tm_scope: source.shell\n  ace_mode: sh\n  codemirror_mode: shell\n  codemirror_mime_type: text/x-sh\n  language_id: 127\nGentoo Eclass:\n  type: programming\n  color: \"#9400ff\"\n  group: Shell\n  extensions:\n  - \".eclass\"\n  tm_scope: source.shell\n  ace_mode: sh\n  codemirror_mode: shell\n  codemirror_mime_type: text/x-sh\n  language_id: 128\nGerber Image:\n  type: data\n  color: \"#d20b00\"\n  aliases:\n  - rs-274x\n  extensions:\n  - \".gbr\"\n  - \".cmp\"\n  - \".gbl\"\n  - \".gbo\"\n  - \".gbp\"\n  - \".gbs\"\n  - \".gko\"\n  - \".gml\"\n  - \".gpb\"\n  - \".gpt\"\n  - \".gtl\"\n  - \".gto\"\n  - \".gtp\"\n  - \".gts\"\n  - \".ncl\"\n  - \".sol\"\n  interpreters:\n  - gerbv\n  - gerbview\n  tm_scope: source.gerber\n  ace_mode: text\n  language_id: 404627610\nGettext Catalog:\n  type: prose\n  aliases:\n  - pot\n  extensions:\n  - \".po\"\n  - \".pot\"\n  tm_scope: source.po\n  ace_mode: text\n  language_id: 129\nGherkin:\n  type: programming\n  extensions:\n  - \".feature\"\n  - \".story\"\n  tm_scope: text.gherkin.feature\n  aliases:\n  - cucumber\n  ace_mode: text\n  color: \"#5B2063\"\n  language_id: 76\nGit Attributes:\n  type: data\n  color: \"#F44D27\"\n  group: INI\n  aliases:\n  - gitattributes\n  filenames:\n  - \".gitattributes\"\n  tm_scope: source.gitattributes\n  ace_mode: gitignore\n  codemirror_mode: shell\n  codemirror_mime_type: text/x-sh\n  language_id: 956324166\nGit Config:\n  type: data\n  color: \"#F44D27\"\n  group: INI\n  aliases:\n  - gitconfig\n  - gitmodules\n  extensions:\n  - \".gitconfig\"\n  filenames:\n  - \".gitconfig\"\n  - \".gitmodules\"\n  ace_mode: ini\n  codemirror_mode: properties\n  codemirror_mime_type: text/x-properties\n  tm_scope: source.gitconfig\n  language_id: 807968997\nGit Revision List:\n  type: data\n  color: \"#F44D27\"\n  aliases:\n  - Git Blame Ignore Revs\n  filenames:\n  - \".git-blame-ignore-revs\"\n  tm_scope: source.git-revlist\n  ace_mode: text\n  language_id: 461881235\nGleam:\n  type: programming\n  color: \"#ffaff3\"\n  ace_mode: text\n  extensions:\n  - \".gleam\"\n  tm_scope: source.gleam\n  language_id: 1054258749\nGlyph:\n  type: programming\n  color: \"#c1ac7f\"\n  extensions:\n  - \".glf\"\n  tm_scope: source.tcl\n  ace_mode: tcl\n  codemirror_mode: tcl\n  codemirror_mime_type: text/x-tcl\n  language_id: 130\nGlyph Bitmap Distribution Format:\n  type: data\n  extensions:\n  - \".bdf\"\n  tm_scope: source.bdf\n  ace_mode: text\n  language_id: 997665271\nGnuplot:\n  type: programming\n  color: \"#f0a9f0\"\n  extensions:\n  - \".gp\"\n  - \".gnu\"\n  - \".gnuplot\"\n  - \".p\"\n  - \".plot\"\n  - \".plt\"\n  interpreters:\n  - gnuplot\n  tm_scope: source.gnuplot\n  ace_mode: text\n  language_id: 131\nGo:\n  type: programming\n  color: \"#00ADD8\"\n  aliases:\n  - golang\n  extensions:\n  - \".go\"\n  tm_scope: source.go\n  ace_mode: golang\n  codemirror_mode: go\n  codemirror_mime_type: text/x-go\n  language_id: 132\nGo Checksums:\n  type: data\n  color: \"#00ADD8\"\n  aliases:\n  - go.sum\n  - go sum\n  filenames:\n  - go.sum\n  tm_scope: go.sum\n  ace_mode: text\n  language_id: 1054391671\nGo Module:\n  type: data\n  color: \"#00ADD8\"\n  aliases:\n  - go.mod\n  - go mod\n  filenames:\n  - go.mod\n  tm_scope: go.mod\n  ace_mode: text\n  language_id: 947461016\nGolo:\n  type: programming\n  color: \"#88562A\"\n  extensions:\n  - \".golo\"\n  tm_scope: source.golo\n  ace_mode: text\n  language_id: 133\nGosu:\n  type: programming\n  color: \"#82937f\"\n  extensions:\n  - \".gs\"\n  - \".gst\"\n  - \".gsx\"\n  - \".vark\"\n  tm_scope: source.gosu.2\n  ace_mode: text\n  language_id: 134\nGrace:\n  type: programming\n  color: \"#615f8b\"\n  extensions:\n  - \".grace\"\n  tm_scope: source.grace\n  ace_mode: text\n  language_id: 135\nGradle:\n  type: data\n  color: \"#02303a\"\n  extensions:\n  - \".gradle\"\n  tm_scope: source.groovy.gradle\n  ace_mode: text\n  language_id: 136\nGrammatical Framework:\n  type: programming\n  aliases:\n  - gf\n  extensions:\n  - \".gf\"\n  color: \"#ff0000\"\n  tm_scope: source.gf\n  ace_mode: haskell\n  codemirror_mode: haskell\n  codemirror_mime_type: text/x-haskell\n  language_id: 137\nGraph Modeling Language:\n  type: data\n  extensions:\n  - \".gml\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 138\nGraphQL:\n  type: data\n  color: \"#e10098\"\n  extensions:\n  - \".graphql\"\n  - \".gql\"\n  - \".graphqls\"\n  tm_scope: source.graphql\n  ace_mode: text\n  language_id: 139\nGraphviz (DOT):\n  type: data\n  color: \"#2596be\"\n  tm_scope: source.dot\n  extensions:\n  - \".dot\"\n  - \".gv\"\n  ace_mode: text\n  language_id: 140\nGroovy:\n  type: programming\n  tm_scope: source.groovy\n  ace_mode: groovy\n  codemirror_mode: groovy\n  codemirror_mime_type: text/x-groovy\n  color: \"#4298b8\"\n  extensions:\n  - \".groovy\"\n  - \".grt\"\n  - \".gtpl\"\n  - \".gvy\"\n  interpreters:\n  - groovy\n  filenames:\n  - Jenkinsfile\n  language_id: 142\nGroovy Server Pages:\n  type: programming\n  color: \"#4298b8\"\n  group: Groovy\n  aliases:\n  - gsp\n  - java server page\n  extensions:\n  - \".gsp\"\n  tm_scope: text.html.jsp\n  ace_mode: jsp\n  codemirror_mode: htmlembedded\n  codemirror_mime_type: application/x-jsp\n  language_id: 143\nHAProxy:\n  type: data\n  color: \"#106da9\"\n  extensions:\n  - \".cfg\"\n  filenames:\n  - haproxy.cfg\n  tm_scope: source.haproxy-config\n  ace_mode: text\n  language_id: 366607477\nHCL:\n  type: programming\n  extensions:\n  - \".hcl\"\n  - \".nomad\"\n  - \".tf\"\n  - \".tfvars\"\n  - \".workflow\"\n  aliases:\n  - HashiCorp Configuration Language\n  - terraform\n  ace_mode: ruby\n  codemirror_mode: ruby\n  codemirror_mime_type: text/x-ruby\n  tm_scope: source.terraform\n  language_id: 144\nHLSL:\n  type: programming\n  color: \"#aace60\"\n  extensions:\n  - \".hlsl\"\n  - \".cginc\"\n  - \".fx\"\n  - \".fxh\"\n  - \".hlsli\"\n  ace_mode: text\n  tm_scope: source.hlsl\n  language_id: 145\nHOCON:\n  type: data\n  color: \"#9ff8ee\"\n  extensions:\n  - \".hocon\"\n  tm_scope: source.hocon\n  ace_mode: text\n  language_id: 679725279\nHTML:\n  type: markup\n  tm_scope: text.html.basic\n  ace_mode: html\n  codemirror_mode: htmlmixed\n  codemirror_mime_type: text/html\n  color: \"#e34c26\"\n  aliases:\n  - xhtml\n  extensions:\n  - \".html\"\n  - \".hta\"\n  - \".htm\"\n  - \".html.hl\"\n  - \".inc\"\n  - \".xht\"\n  - \".xhtml\"\n  language_id: 146\nHTML+ECR:\n  type: markup\n  color: \"#2e1052\"\n  tm_scope: text.html.ecr\n  group: HTML\n  aliases:\n  - ecr\n  extensions:\n  - \".ecr\"\n  ace_mode: text\n  codemirror_mode: htmlmixed\n  codemirror_mime_type: text/html\n  language_id: 148\nHTML+EEX:\n  type: markup\n  color: \"#6e4a7e\"\n  tm_scope: text.html.elixir\n  group: HTML\n  aliases:\n  - eex\n  - heex\n  - leex\n  extensions:\n  - \".eex\"\n  - \".html.heex\"\n  - \".html.leex\"\n  ace_mode: text\n  codemirror_mode: htmlmixed\n  codemirror_mime_type: text/html\n  language_id: 149\nHTML+ERB:\n  type: markup\n  color: \"#701516\"\n  tm_scope: text.html.erb\n  group: HTML\n  aliases:\n  - erb\n  - rhtml\n  - html+ruby\n  extensions:\n  - \".erb\"\n  - \".erb.deface\"\n  - \".rhtml\"\n  ace_mode: text\n  codemirror_mode: htmlembedded\n  codemirror_mime_type: application/x-erb\n  language_id: 150\nHTML+PHP:\n  type: markup\n  color: \"#4f5d95\"\n  tm_scope: text.html.php\n  group: HTML\n  extensions:\n  - \".phtml\"\n  ace_mode: php\n  codemirror_mode: php\n  codemirror_mime_type: application/x-httpd-php\n  language_id: 151\nHTML+Razor:\n  type: markup\n  color: \"#512be4\"\n  tm_scope: text.html.cshtml\n  group: HTML\n  aliases:\n  - razor\n  extensions:\n  - \".cshtml\"\n  - \".razor\"\n  ace_mode: razor\n  codemirror_mode: htmlmixed\n  codemirror_mime_type: text/html\n  language_id: 479039817\nHTTP:\n  type: data\n  color: \"#005C9C\"\n  extensions:\n  - \".http\"\n  tm_scope: source.httpspec\n  ace_mode: text\n  codemirror_mode: http\n  codemirror_mime_type: message/http\n  language_id: 152\nHXML:\n  type: data\n  color: \"#f68712\"\n  ace_mode: text\n  extensions:\n  - \".hxml\"\n  tm_scope: source.hxml\n  language_id: 786683730\nHack:\n  type: programming\n  ace_mode: php\n  codemirror_mode: php\n  codemirror_mime_type: application/x-httpd-php\n  extensions:\n  - \".hack\"\n  - \".hh\"\n  - \".hhi\"\n  - \".php\"\n  tm_scope: source.hack\n  color: \"#878787\"\n  language_id: 153\nHaml:\n  type: markup\n  color: \"#ece2a9\"\n  extensions:\n  - \".haml\"\n  - \".haml.deface\"\n  tm_scope: text.haml\n  ace_mode: haml\n  codemirror_mode: haml\n  codemirror_mime_type: text/x-haml\n  language_id: 154\nHandlebars:\n  type: markup\n  color: \"#f7931e\"\n  aliases:\n  - hbs\n  - htmlbars\n  extensions:\n  - \".handlebars\"\n  - \".hbs\"\n  tm_scope: text.html.handlebars\n  ace_mode: handlebars\n  language_id: 155\nHarbour:\n  type: programming\n  color: \"#0e60e3\"\n  extensions:\n  - \".hb\"\n  tm_scope: source.harbour\n  ace_mode: text\n  language_id: 156\nHaskell:\n  type: programming\n  color: \"#5e5086\"\n  extensions:\n  - \".hs\"\n  - \".hs-boot\"\n  - \".hsc\"\n  interpreters:\n  - runghc\n  - runhaskell\n  - runhugs\n  tm_scope: source.haskell\n  ace_mode: haskell\n  codemirror_mode: haskell\n  codemirror_mime_type: text/x-haskell\n  language_id: 157\nHaxe:\n  type: programming\n  ace_mode: haxe\n  codemirror_mode: haxe\n  codemirror_mime_type: text/x-haxe\n  color: \"#df7900\"\n  extensions:\n  - \".hx\"\n  - \".hxsl\"\n  tm_scope: source.hx\n  language_id: 158\nHiveQL:\n  type: programming\n  extensions:\n  - \".q\"\n  - \".hql\"\n  color: \"#dce200\"\n  tm_scope: source.hql\n  ace_mode: sql\n  language_id: 931814087\nHolyC:\n  type: programming\n  color: \"#ffefaf\"\n  extensions:\n  - \".hc\"\n  tm_scope: source.hc\n  ace_mode: c_cpp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-csrc\n  language_id: 928121743\nHy:\n  type: programming\n  ace_mode: text\n  color: \"#7790B2\"\n  extensions:\n  - \".hy\"\n  interpreters:\n  - hy\n  aliases:\n  - hylang\n  tm_scope: source.hy\n  language_id: 159\nHyPhy:\n  type: programming\n  ace_mode: text\n  extensions:\n  - \".bf\"\n  tm_scope: none\n  language_id: 160\nIDL:\n  type: programming\n  color: \"#a3522f\"\n  extensions:\n  - \".pro\"\n  - \".dlm\"\n  tm_scope: source.idl\n  ace_mode: text\n  codemirror_mode: idl\n  codemirror_mime_type: text/x-idl\n  language_id: 161\nIGOR Pro:\n  type: programming\n  color: \"#0000cc\"\n  extensions:\n  - \".ipf\"\n  aliases:\n  - igor\n  - igorpro\n  tm_scope: source.igor\n  ace_mode: text\n  language_id: 162\nINI:\n  type: data\n  color: \"#d1dbe0\"\n  extensions:\n  - \".ini\"\n  - \".cfg\"\n  - \".dof\"\n  - \".lektorproject\"\n  - \".prefs\"\n  - \".pro\"\n  - \".properties\"\n  - \".url\"\n  filenames:\n  - \".coveragerc\"\n  - \".flake8\"\n  - \".pylintrc\"\n  - buildozer.spec\n  - pylintrc\n  tm_scope: source.ini\n  aliases:\n  - dosini\n  ace_mode: ini\n  codemirror_mode: properties\n  codemirror_mime_type: text/x-properties\n  language_id: 163\nIRC log:\n  type: data\n  aliases:\n  - irc\n  - irc logs\n  extensions:\n  - \".irclog\"\n  - \".weechatlog\"\n  tm_scope: none\n  ace_mode: text\n  codemirror_mode: mirc\n  codemirror_mime_type: text/mirc\n  language_id: 164\nIdris:\n  type: programming\n  color: \"#b30000\"\n  extensions:\n  - \".idr\"\n  - \".lidr\"\n  ace_mode: text\n  tm_scope: source.idris\n  language_id: 165\nIgnore List:\n  type: data\n  color: \"#000000\"\n  group: INI\n  aliases:\n  - ignore\n  - gitignore\n  - git-ignore\n  extensions:\n  - \".gitignore\"\n  filenames:\n  - \".atomignore\"\n  - \".babelignore\"\n  - \".bzrignore\"\n  - \".coffeelintignore\"\n  - \".cvsignore\"\n  - \".dockerignore\"\n  - \".eleventyignore\"\n  - \".eslintignore\"\n  - \".gitignore\"\n  - \".markdownlintignore\"\n  - \".nodemonignore\"\n  - \".npmignore\"\n  - \".prettierignore\"\n  - \".stylelintignore\"\n  - \".vercelignore\"\n  - \".vscodeignore\"\n  - gitignore-global\n  - gitignore_global\n  ace_mode: gitignore\n  tm_scope: source.gitignore\n  codemirror_mode: shell\n  codemirror_mime_type: text/x-sh\n  language_id: 74444240\nImageJ Macro:\n  type: programming\n  color: \"#99AAFF\"\n  aliases:\n  - ijm\n  extensions:\n  - \".ijm\"\n  ace_mode: text\n  tm_scope: none\n  language_id: 575143428\nImba:\n  type: programming\n  color: \"#16cec6\"\n  extensions:\n  - \".imba\"\n  ace_mode: text\n  tm_scope: source.imba\n  language_id: 1057618448\nInform 7:\n  type: programming\n  wrap: true\n  extensions:\n  - \".ni\"\n  - \".i7x\"\n  tm_scope: source.inform7\n  aliases:\n  - i7\n  - inform7\n  ace_mode: text\n  language_id: 166\nInno Setup:\n  type: programming\n  color: \"#264b99\"\n  extensions:\n  - \".iss\"\n  - \".isl\"\n  tm_scope: source.inno\n  ace_mode: text\n  language_id: 167\nIo:\n  type: programming\n  color: \"#a9188d\"\n  extensions:\n  - \".io\"\n  interpreters:\n  - io\n  tm_scope: source.io\n  ace_mode: io\n  language_id: 168\nIoke:\n  type: programming\n  color: \"#078193\"\n  extensions:\n  - \".ik\"\n  interpreters:\n  - ioke\n  tm_scope: source.ioke\n  ace_mode: text\n  language_id: 169\nIsabelle:\n  type: programming\n  color: \"#FEFE00\"\n  extensions:\n  - \".thy\"\n  tm_scope: source.isabelle.theory\n  ace_mode: text\n  language_id: 170\nIsabelle ROOT:\n  type: programming\n  color: \"#FEFE00\"\n  group: Isabelle\n  filenames:\n  - ROOT\n  tm_scope: source.isabelle.root\n  ace_mode: text\n  language_id: 171\nJ:\n  type: programming\n  color: \"#9EEDFF\"\n  extensions:\n  - \".ijs\"\n  interpreters:\n  - jconsole\n  tm_scope: source.j\n  ace_mode: text\n  language_id: 172\nJAR Manifest:\n  type: data\n  color: \"#b07219\"\n  filenames:\n  - MANIFEST.MF\n  tm_scope: source.yaml\n  ace_mode: text\n  language_id: 447261135\nJFlex:\n  type: programming\n  color: \"#DBCA00\"\n  group: Lex\n  extensions:\n  - \".flex\"\n  - \".jflex\"\n  tm_scope: source.jflex\n  ace_mode: text\n  language_id: 173\nJSON:\n  type: data\n  color: \"#292929\"\n  tm_scope: source.json\n  ace_mode: json\n  codemirror_mode: javascript\n  codemirror_mime_type: application/json\n  aliases:\n  - geojson\n  - jsonl\n  - topojson\n  extensions:\n  - \".json\"\n  - \".4DForm\"\n  - \".4DProject\"\n  - \".avsc\"\n  - \".geojson\"\n  - \".gltf\"\n  - \".har\"\n  - \".ice\"\n  - \".JSON-tmLanguage\"\n  - \".jsonl\"\n  - \".mcmeta\"\n  - \".tfstate\"\n  - \".tfstate.backup\"\n  - \".topojson\"\n  - \".webapp\"\n  - \".webmanifest\"\n  - \".yy\"\n  - \".yyp\"\n  filenames:\n  - \".all-contributorsrc\"\n  - \".arcconfig\"\n  - \".auto-changelog\"\n  - \".c8rc\"\n  - \".htmlhintrc\"\n  - \".imgbotconfig\"\n  - \".nycrc\"\n  - \".tern-config\"\n  - \".tern-project\"\n  - \".watchmanconfig\"\n  - Pipfile.lock\n  - composer.lock\n  - mcmod.info\n  language_id: 174\nJSON with Comments:\n  type: data\n  color: \"#292929\"\n  group: JSON\n  tm_scope: source.js\n  ace_mode: javascript\n  codemirror_mode: javascript\n  codemirror_mime_type: text/javascript\n  aliases:\n  - jsonc\n  extensions:\n  - \".jsonc\"\n  - \".code-snippets\"\n  - \".sublime-build\"\n  - \".sublime-commands\"\n  - \".sublime-completions\"\n  - \".sublime-keymap\"\n  - \".sublime-macro\"\n  - \".sublime-menu\"\n  - \".sublime-mousemap\"\n  - \".sublime-project\"\n  - \".sublime-settings\"\n  - \".sublime-theme\"\n  - \".sublime-workspace\"\n  - \".sublime_metrics\"\n  - \".sublime_session\"\n  filenames:\n  - \".babelrc\"\n  - \".devcontainer.json\"\n  - \".eslintrc.json\"\n  - \".jscsrc\"\n  - \".jshintrc\"\n  - \".jslintrc\"\n  - api-extractor.json\n  - devcontainer.json\n  - jsconfig.json\n  - language-configuration.json\n  - tsconfig.json\n  - tslint.json\n  language_id: 423\nJSON5:\n  type: data\n  color: \"#267CB9\"\n  extensions:\n  - \".json5\"\n  tm_scope: source.js\n  ace_mode: javascript\n  codemirror_mode: javascript\n  codemirror_mime_type: application/json\n  language_id: 175\nJSONLD:\n  type: data\n  color: \"#0c479c\"\n  extensions:\n  - \".jsonld\"\n  tm_scope: source.js\n  ace_mode: javascript\n  codemirror_mode: javascript\n  codemirror_mime_type: application/json\n  language_id: 176\nJSONiq:\n  color: \"#40d47e\"\n  type: programming\n  ace_mode: jsoniq\n  codemirror_mode: javascript\n  codemirror_mime_type: application/json\n  extensions:\n  - \".jq\"\n  tm_scope: source.jsoniq\n  language_id: 177\nJanet:\n  type: programming\n  color: \"#0886a5\"\n  extensions:\n  - \".janet\"\n  tm_scope: source.janet\n  ace_mode: scheme\n  codemirror_mode: scheme\n  codemirror_mime_type: text/x-scheme\n  interpreters:\n  - janet\n  language_id: 1028705371\nJasmin:\n  type: programming\n  color: \"#d03600\"\n  ace_mode: java\n  extensions:\n  - \".j\"\n  tm_scope: source.jasmin\n  language_id: 180\nJava:\n  type: programming\n  tm_scope: source.java\n  ace_mode: java\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-java\n  color: \"#b07219\"\n  extensions:\n  - \".java\"\n  - \".jav\"\n  - \".jsh\"\n  language_id: 181\nJava Properties:\n  type: data\n  color: \"#2A6277\"\n  extensions:\n  - \".properties\"\n  tm_scope: source.java-properties\n  ace_mode: properties\n  codemirror_mode: properties\n  codemirror_mime_type: text/x-properties\n  language_id: 519377561\nJava Server Pages:\n  type: programming\n  color: \"#2A6277\"\n  group: Java\n  aliases:\n  - jsp\n  extensions:\n  - \".jsp\"\n  - \".tag\"\n  tm_scope: text.html.jsp\n  ace_mode: jsp\n  codemirror_mode: htmlembedded\n  codemirror_mime_type: application/x-jsp\n  language_id: 182\nJavaScript:\n  type: programming\n  tm_scope: source.js\n  ace_mode: javascript\n  codemirror_mode: javascript\n  codemirror_mime_type: text/javascript\n  color: \"#f1e05a\"\n  aliases:\n  - js\n  - node\n  extensions:\n  - \".js\"\n  - \"._js\"\n  - \".bones\"\n  - \".cjs\"\n  - \".es\"\n  - \".es6\"\n  - \".frag\"\n  - \".gs\"\n  - \".jake\"\n  - \".javascript\"\n  - \".jsb\"\n  - \".jscad\"\n  - \".jsfl\"\n  - \".jslib\"\n  - \".jsm\"\n  - \".jspre\"\n  - \".jss\"\n  - \".jsx\"\n  - \".mjs\"\n  - \".njs\"\n  - \".pac\"\n  - \".sjs\"\n  - \".ssjs\"\n  - \".xsjs\"\n  - \".xsjslib\"\n  filenames:\n  - Jakefile\n  interpreters:\n  - chakra\n  - d8\n  - gjs\n  - js\n  - node\n  - nodejs\n  - qjs\n  - rhino\n  - v8\n  - v8-shell\n  language_id: 183\nJavaScript+ERB:\n  type: programming\n  color: \"#f1e05a\"\n  tm_scope: source.js\n  group: JavaScript\n  extensions:\n  - \".js.erb\"\n  ace_mode: javascript\n  codemirror_mode: javascript\n  codemirror_mime_type: application/javascript\n  language_id: 914318960\nJest Snapshot:\n  type: data\n  color: \"#15c213\"\n  tm_scope: source.jest.snap\n  extensions:\n  - \".snap\"\n  ace_mode: javascript\n  codemirror_mode: javascript\n  codemirror_mime_type: application/javascript\n  language_id: 774635084\nJetBrains MPS:\n  type: programming\n  aliases:\n  - mps\n  color: \"#21D789\"\n  extensions:\n  - \".mps\"\n  - \".mpl\"\n  - \".msd\"\n  ace_mode: xml\n  codemirror_mode: xml\n  codemirror_mime_type: text/xml\n  tm_scope: none\n  language_id: 465165328\nJinja:\n  type: markup\n  color: \"#a52a22\"\n  aliases:\n  - django\n  - html+django\n  - html+jinja\n  - htmldjango\n  extensions:\n  - \".jinja\"\n  - \".j2\"\n  - \".jinja2\"\n  tm_scope: text.html.django\n  ace_mode: django\n  codemirror_mode: django\n  codemirror_mime_type: text/x-django\n  language_id: 147\nJison:\n  type: programming\n  color: \"#56b3cb\"\n  group: Yacc\n  extensions:\n  - \".jison\"\n  tm_scope: source.jison\n  ace_mode: text\n  language_id: 284531423\nJison Lex:\n  type: programming\n  color: \"#56b3cb\"\n  group: Lex\n  extensions:\n  - \".jisonlex\"\n  tm_scope: source.jisonlex\n  ace_mode: text\n  language_id: 406395330\nJolie:\n  type: programming\n  extensions:\n  - \".ol\"\n  - \".iol\"\n  interpreters:\n  - jolie\n  color: \"#843179\"\n  ace_mode: text\n  tm_scope: source.jolie\n  language_id: 998078858\nJsonnet:\n  color: \"#0064bd\"\n  type: programming\n  ace_mode: text\n  extensions:\n  - \".jsonnet\"\n  - \".libsonnet\"\n  tm_scope: source.jsonnet\n  language_id: 664885656\nJulia:\n  type: programming\n  extensions:\n  - \".jl\"\n  interpreters:\n  - julia\n  color: \"#a270ba\"\n  tm_scope: source.julia\n  ace_mode: julia\n  codemirror_mode: julia\n  codemirror_mime_type: text/x-julia\n  language_id: 184\nJupyter Notebook:\n  type: markup\n  ace_mode: json\n  codemirror_mode: javascript\n  codemirror_mime_type: application/json\n  tm_scope: source.json\n  color: \"#DA5B0B\"\n  extensions:\n  - \".ipynb\"\n  filenames:\n  - Notebook\n  aliases:\n  - IPython Notebook\n  language_id: 185\nKRL:\n  type: programming\n  color: \"#28430A\"\n  extensions:\n  - \".krl\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 186\nKaitai Struct:\n  type: programming\n  aliases:\n  - ksy\n  ace_mode: yaml\n  codemirror_mode: yaml\n  codemirror_mime_type: text/x-yaml\n  color: \"#773b37\"\n  extensions:\n  - \".ksy\"\n  tm_scope: source.yaml\n  language_id: 818804755\nKakouneScript:\n  type: programming\n  color: \"#6f8042\"\n  tm_scope: source.kakscript\n  aliases:\n  - kak\n  - kakscript\n  extensions:\n  - \".kak\"\n  filenames:\n  - kakrc\n  ace_mode: text\n  language_id: 603336474\nKiCad Layout:\n  type: data\n  color: \"#2f4aab\"\n  aliases:\n  - pcbnew\n  extensions:\n  - \".kicad_pcb\"\n  - \".kicad_mod\"\n  - \".kicad_wks\"\n  filenames:\n  - fp-lib-table\n  tm_scope: source.pcb.sexp\n  ace_mode: lisp\n  codemirror_mode: commonlisp\n  codemirror_mime_type: text/x-common-lisp\n  language_id: 187\nKiCad Legacy Layout:\n  type: data\n  color: \"#2f4aab\"\n  extensions:\n  - \".brd\"\n  tm_scope: source.pcb.board\n  ace_mode: text\n  language_id: 140848857\nKiCad Schematic:\n  type: data\n  color: \"#2f4aab\"\n  aliases:\n  - eeschema schematic\n  extensions:\n  - \".kicad_sch\"\n  - \".sch\"\n  tm_scope: source.pcb.schematic\n  ace_mode: text\n  language_id: 622447435\nKit:\n  type: markup\n  ace_mode: html\n  codemirror_mode: htmlmixed\n  codemirror_mime_type: text/html\n  extensions:\n  - \".kit\"\n  tm_scope: text.html.basic\n  language_id: 188\nKotlin:\n  type: programming\n  color: \"#A97BFF\"\n  extensions:\n  - \".kt\"\n  - \".ktm\"\n  - \".kts\"\n  tm_scope: source.kotlin\n  ace_mode: text\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-kotlin\n  language_id: 189\nKusto:\n  type: data\n  extensions:\n  - \".csl\"\n  - \".kql\"\n  tm_scope: source.kusto\n  ace_mode: text\n  language_id: 225697190\nLFE:\n  type: programming\n  color: \"#4C3023\"\n  extensions:\n  - \".lfe\"\n  tm_scope: source.lisp\n  ace_mode: lisp\n  codemirror_mode: commonlisp\n  codemirror_mime_type: text/x-common-lisp\n  language_id: 190\nLLVM:\n  type: programming\n  extensions:\n  - \".ll\"\n  tm_scope: source.llvm\n  ace_mode: text\n  color: \"#185619\"\n  language_id: 191\nLOLCODE:\n  type: programming\n  extensions:\n  - \".lol\"\n  color: \"#cc9900\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 192\nLSL:\n  type: programming\n  tm_scope: source.lsl\n  ace_mode: lsl\n  extensions:\n  - \".lsl\"\n  - \".lslp\"\n  interpreters:\n  - lsl\n  color: \"#3d9970\"\n  language_id: 193\nLTspice Symbol:\n  type: data\n  extensions:\n  - \".asy\"\n  tm_scope: source.ltspice.symbol\n  ace_mode: text\n  codemirror_mode: spreadsheet\n  codemirror_mime_type: text/x-spreadsheet\n  language_id: 1013566805\nLabVIEW:\n  type: programming\n  color: \"#fede06\"\n  extensions:\n  - \".lvproj\"\n  - \".lvclass\"\n  - \".lvlib\"\n  tm_scope: text.xml\n  ace_mode: xml\n  codemirror_mode: xml\n  codemirror_mime_type: text/xml\n  language_id: 194\nLark:\n  type: data\n  color: \"#2980B9\"\n  extensions:\n  - \".lark\"\n  tm_scope: source.lark\n  ace_mode: text\n  codemirror_mode: ebnf\n  codemirror_mime_type: text/x-ebnf\n  language_id: 758480799\nLasso:\n  type: programming\n  color: \"#999999\"\n  extensions:\n  - \".lasso\"\n  - \".las\"\n  - \".lasso8\"\n  - \".lasso9\"\n  tm_scope: file.lasso\n  aliases:\n  - lassoscript\n  ace_mode: text\n  language_id: 195\nLatte:\n  type: markup\n  color: \"#f2a542\"\n  extensions:\n  - \".latte\"\n  tm_scope: text.html.smarty\n  ace_mode: smarty\n  codemirror_mode: smarty\n  codemirror_mime_type: text/x-smarty\n  language_id: 196\nLean:\n  type: programming\n  extensions:\n  - \".lean\"\n  - \".hlean\"\n  tm_scope: source.lean\n  ace_mode: text\n  language_id: 197\nLess:\n  type: markup\n  color: \"#1d365d\"\n  aliases:\n  - less-css\n  extensions:\n  - \".less\"\n  tm_scope: source.css.less\n  ace_mode: less\n  codemirror_mode: css\n  codemirror_mime_type: text/css\n  language_id: 198\nLex:\n  type: programming\n  color: \"#DBCA00\"\n  aliases:\n  - flex\n  extensions:\n  - \".l\"\n  - \".lex\"\n  filenames:\n  - Lexer.x\n  - lexer.x\n  tm_scope: source.lex\n  ace_mode: text\n  language_id: 199\nLigoLANG:\n  type: programming\n  color: \"#0e74ff\"\n  extensions:\n  - \".ligo\"\n  tm_scope: source.ligo\n  ace_mode: pascal\n  codemirror_mode: pascal\n  codemirror_mime_type: text/x-pascal\n  group: LigoLANG\n  language_id: 1040646257\nLilyPond:\n  type: programming\n  color: \"#9ccc7c\"\n  extensions:\n  - \".ly\"\n  - \".ily\"\n  tm_scope: source.lilypond\n  ace_mode: text\n  language_id: 200\nLimbo:\n  type: programming\n  extensions:\n  - \".b\"\n  - \".m\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 201\nLinker Script:\n  type: data\n  extensions:\n  - \".ld\"\n  - \".lds\"\n  - \".x\"\n  filenames:\n  - ld.script\n  tm_scope: none\n  ace_mode: text\n  language_id: 202\nLinux Kernel Module:\n  type: data\n  extensions:\n  - \".mod\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 203\nLiquid:\n  type: markup\n  color: \"#67b8de\"\n  extensions:\n  - \".liquid\"\n  tm_scope: text.html.liquid\n  ace_mode: liquid\n  language_id: 204\nLiterate Agda:\n  type: programming\n  color: \"#315665\"\n  group: Agda\n  extensions:\n  - \".lagda\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 205\nLiterate CoffeeScript:\n  type: programming\n  color: \"#244776\"\n  tm_scope: source.litcoffee\n  group: CoffeeScript\n  ace_mode: text\n  wrap: true\n  aliases:\n  - litcoffee\n  extensions:\n  - \".litcoffee\"\n  - \".coffee.md\"\n  language_id: 206\nLiterate Haskell:\n  type: programming\n  color: \"#5e5086\"\n  group: Haskell\n  aliases:\n  - lhaskell\n  - lhs\n  extensions:\n  - \".lhs\"\n  tm_scope: text.tex.latex.haskell\n  ace_mode: text\n  codemirror_mode: haskell-literate\n  codemirror_mime_type: text/x-literate-haskell\n  language_id: 207\nLiveScript:\n  type: programming\n  color: \"#499886\"\n  aliases:\n  - live-script\n  - ls\n  extensions:\n  - \".ls\"\n  - \"._ls\"\n  filenames:\n  - Slakefile\n  tm_scope: source.livescript\n  ace_mode: livescript\n  codemirror_mode: livescript\n  codemirror_mime_type: text/x-livescript\n  language_id: 208\nLogos:\n  type: programming\n  extensions:\n  - \".xm\"\n  - \".x\"\n  - \".xi\"\n  ace_mode: text\n  tm_scope: source.logos\n  language_id: 209\nLogtalk:\n  type: programming\n  color: \"#295b9a\"\n  extensions:\n  - \".lgt\"\n  - \".logtalk\"\n  tm_scope: source.logtalk\n  ace_mode: text\n  language_id: 210\nLookML:\n  type: programming\n  ace_mode: yaml\n  codemirror_mode: yaml\n  codemirror_mime_type: text/x-yaml\n  color: \"#652B81\"\n  extensions:\n  - \".lookml\"\n  - \".model.lkml\"\n  - \".view.lkml\"\n  tm_scope: source.yaml\n  language_id: 211\nLoomScript:\n  type: programming\n  extensions:\n  - \".ls\"\n  tm_scope: source.loomscript\n  ace_mode: text\n  language_id: 212\nLua:\n  type: programming\n  tm_scope: source.lua\n  ace_mode: lua\n  codemirror_mode: lua\n  codemirror_mime_type: text/x-lua\n  color: \"#000080\"\n  extensions:\n  - \".lua\"\n  - \".fcgi\"\n  - \".nse\"\n  - \".p8\"\n  - \".pd_lua\"\n  - \".rbxs\"\n  - \".rockspec\"\n  - \".wlua\"\n  filenames:\n  - \".luacheckrc\"\n  interpreters:\n  - lua\n  language_id: 213\nM:\n  type: programming\n  aliases:\n  - mumps\n  extensions:\n  - \".mumps\"\n  - \".m\"\n  ace_mode: text\n  codemirror_mode: mumps\n  codemirror_mime_type: text/x-mumps\n  language_id: 214\n  tm_scope: none\nM4:\n  type: programming\n  extensions:\n  - \".m4\"\n  - \".mc\"\n  tm_scope: source.m4\n  ace_mode: text\n  language_id: 215\nM4Sugar:\n  type: programming\n  group: M4\n  aliases:\n  - autoconf\n  extensions:\n  - \".m4\"\n  filenames:\n  - configure.ac\n  tm_scope: source.m4\n  ace_mode: text\n  language_id: 216\nMATLAB:\n  type: programming\n  color: \"#e16737\"\n  aliases:\n  - octave\n  extensions:\n  - \".matlab\"\n  - \".m\"\n  tm_scope: source.matlab\n  ace_mode: matlab\n  codemirror_mode: octave\n  codemirror_mime_type: text/x-octave\n  language_id: 225\nMAXScript:\n  type: programming\n  color: \"#00a6a6\"\n  extensions:\n  - \".ms\"\n  - \".mcr\"\n  tm_scope: source.maxscript\n  ace_mode: text\n  language_id: 217\nMLIR:\n  type: programming\n  color: \"#5EC8DB\"\n  extensions:\n  - \".mlir\"\n  tm_scope: source.mlir\n  ace_mode: text\n  language_id: 448253929\nMQL4:\n  type: programming\n  color: \"#62A8D6\"\n  extensions:\n  - \".mq4\"\n  - \".mqh\"\n  tm_scope: source.mql5\n  ace_mode: c_cpp\n  language_id: 426\nMQL5:\n  type: programming\n  color: \"#4A76B8\"\n  extensions:\n  - \".mq5\"\n  - \".mqh\"\n  tm_scope: source.mql5\n  ace_mode: c_cpp\n  language_id: 427\nMTML:\n  type: markup\n  color: \"#b7e1f4\"\n  extensions:\n  - \".mtml\"\n  tm_scope: text.html.basic\n  ace_mode: html\n  codemirror_mode: htmlmixed\n  codemirror_mime_type: text/html\n  language_id: 218\nMUF:\n  type: programming\n  group: Forth\n  extensions:\n  - \".muf\"\n  - \".m\"\n  tm_scope: none\n  ace_mode: forth\n  codemirror_mode: forth\n  codemirror_mime_type: text/x-forth\n  language_id: 219\nMacaulay2:\n  type: programming\n  extensions:\n  - \".m2\"\n  aliases:\n  - m2\n  interpreters:\n  - M2\n  ace_mode: text\n  tm_scope: source.m2\n  color: \"#d8ffff\"\n  language_id: 34167825\nMakefile:\n  type: programming\n  color: \"#427819\"\n  aliases:\n  - bsdmake\n  - make\n  - mf\n  extensions:\n  - \".mak\"\n  - \".d\"\n  - \".make\"\n  - \".makefile\"\n  - \".mk\"\n  - \".mkfile\"\n  filenames:\n  - BSDmakefile\n  - GNUmakefile\n  - Kbuild\n  - Makefile\n  - Makefile.am\n  - Makefile.boot\n  - Makefile.frag\n  - Makefile.in\n  - Makefile.inc\n  - Makefile.wat\n  - makefile\n  - makefile.sco\n  - mkfile\n  interpreters:\n  - make\n  tm_scope: source.makefile\n  ace_mode: makefile\n  codemirror_mode: cmake\n  codemirror_mime_type: text/x-cmake\n  language_id: 220\nMako:\n  type: programming\n  color: \"#7e858d\"\n  extensions:\n  - \".mako\"\n  - \".mao\"\n  tm_scope: text.html.mako\n  ace_mode: text\n  language_id: 221\nMarkdown:\n  type: prose\n  color: \"#083fa1\"\n  aliases:\n  - pandoc\n  - md\n  ace_mode: markdown\n  codemirror_mode: gfm\n  codemirror_mime_type: text/x-gfm\n  wrap: true\n  extensions:\n  - \".md\"\n  - \".livemd\"\n  - \".markdown\"\n  - \".mdown\"\n  - \".mdwn\"\n  - \".mdx\"\n  - \".mkd\"\n  - \".mkdn\"\n  - \".mkdown\"\n  - \".ronn\"\n  - \".scd\"\n  - \".workbook\"\n  filenames:\n  - contents.lr\n  tm_scope: source.gfm\n  language_id: 222\nMarko:\n  type: markup\n  color: \"#42bff2\"\n  tm_scope: text.marko\n  extensions:\n  - \".marko\"\n  aliases:\n  - markojs\n  ace_mode: text\n  codemirror_mode: htmlmixed\n  codemirror_mime_type: text/html\n  language_id: 932782397\nMask:\n  type: markup\n  color: \"#f97732\"\n  ace_mode: mask\n  extensions:\n  - \".mask\"\n  tm_scope: source.mask\n  language_id: 223\nMathematica:\n  type: programming\n  color: \"#dd1100\"\n  extensions:\n  - \".mathematica\"\n  - \".cdf\"\n  - \".m\"\n  - \".ma\"\n  - \".mt\"\n  - \".nb\"\n  - \".nbp\"\n  - \".wl\"\n  - \".wlt\"\n  aliases:\n  - mma\n  - wolfram\n  - wolfram language\n  - wolfram lang\n  - wl\n  tm_scope: source.mathematica\n  ace_mode: text\n  codemirror_mode: mathematica\n  codemirror_mime_type: text/x-mathematica\n  language_id: 224\nMaven POM:\n  type: data\n  group: XML\n  tm_scope: text.xml.pom\n  filenames:\n  - pom.xml\n  ace_mode: xml\n  codemirror_mode: xml\n  codemirror_mime_type: text/xml\n  language_id: 226\nMax:\n  type: programming\n  color: \"#c4a79c\"\n  aliases:\n  - max/msp\n  - maxmsp\n  extensions:\n  - \".maxpat\"\n  - \".maxhelp\"\n  - \".maxproj\"\n  - \".mxt\"\n  - \".pat\"\n  tm_scope: source.json\n  ace_mode: json\n  codemirror_mode: javascript\n  codemirror_mime_type: application/json\n  language_id: 227\nMercury:\n  type: programming\n  color: \"#ff2b2b\"\n  ace_mode: prolog\n  interpreters:\n  - mmi\n  extensions:\n  - \".m\"\n  - \".moo\"\n  tm_scope: source.mercury\n  language_id: 229\nMermaid:\n  type: markup\n  color: \"#ff3670\"\n  aliases:\n  - mermaid example\n  extensions:\n  - \".mmd\"\n  - \".mermaid\"\n  tm_scope: source.mermaid\n  ace_mode: text\n  language_id: 385992043\nMeson:\n  type: programming\n  color: \"#007800\"\n  filenames:\n  - meson.build\n  - meson_options.txt\n  tm_scope: source.meson\n  ace_mode: text\n  language_id: 799141244\nMetal:\n  type: programming\n  color: \"#8f14e9\"\n  extensions:\n  - \".metal\"\n  tm_scope: source.c++\n  ace_mode: c_cpp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-c++src\n  language_id: 230\nMicrosoft Developer Studio Project:\n  type: data\n  extensions:\n  - \".dsp\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 800983837\nMicrosoft Visual Studio Solution:\n  type: data\n  extensions:\n  - \".sln\"\n  tm_scope: source.solution\n  ace_mode: text\n  language_id: 849523096\nMiniD:\n  type: programming\n  extensions:\n  - \".minid\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 231\nMiniYAML:\n  type: data\n  color: \"#ff1111\"\n  tm_scope: source.miniyaml\n  extensions:\n  - \".yaml\"\n  - \".yml\"\n  ace_mode: yaml\n  codemirror_mode: yaml\n  codemirror_mime_type: text/x-yaml\n  language_id: 4896465\nMint:\n  type: programming\n  extensions:\n  - \".mint\"\n  ace_mode: text\n  color: \"#02b046\"\n  tm_scope: source.mint\n  language_id: 968740319\nMirah:\n  type: programming\n  color: \"#c7a938\"\n  extensions:\n  - \".druby\"\n  - \".duby\"\n  - \".mirah\"\n  tm_scope: source.ruby\n  ace_mode: ruby\n  codemirror_mode: ruby\n  codemirror_mime_type: text/x-ruby\n  language_id: 232\nModelica:\n  type: programming\n  color: \"#de1d31\"\n  extensions:\n  - \".mo\"\n  tm_scope: source.modelica\n  ace_mode: text\n  codemirror_mode: modelica\n  codemirror_mime_type: text/x-modelica\n  language_id: 233\nModula-2:\n  type: programming\n  color: \"#10253f\"\n  extensions:\n  - \".mod\"\n  tm_scope: source.modula2\n  ace_mode: text\n  language_id: 234\nModula-3:\n  type: programming\n  extensions:\n  - \".i3\"\n  - \".ig\"\n  - \".m3\"\n  - \".mg\"\n  color: \"#223388\"\n  ace_mode: text\n  tm_scope: source.modula-3\n  language_id: 564743864\nModule Management System:\n  type: programming\n  extensions:\n  - \".mms\"\n  - \".mmk\"\n  filenames:\n  - descrip.mmk\n  - descrip.mms\n  tm_scope: none\n  ace_mode: text\n  language_id: 235\nMonkey:\n  type: programming\n  extensions:\n  - \".monkey\"\n  - \".monkey2\"\n  ace_mode: text\n  tm_scope: source.monkey\n  language_id: 236\nMonkey C:\n  type: programming\n  color: \"#8D6747\"\n  extensions:\n  - \".mc\"\n  tm_scope: source.mc\n  ace_mode: c_cpp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-csrc\n  language_id: 231751931\nMoocode:\n  type: programming\n  extensions:\n  - \".moo\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 237\nMoonScript:\n  type: programming\n  color: \"#ff4585\"\n  extensions:\n  - \".moon\"\n  interpreters:\n  - moon\n  tm_scope: source.moonscript\n  ace_mode: text\n  language_id: 238\nMotoko:\n  type: programming\n  color: \"#fbb03b\"\n  extensions:\n  - \".mo\"\n  tm_scope: source.mo\n  ace_mode: text\n  language_id: 202937027\nMotorola 68K Assembly:\n  type: programming\n  color: \"#005daa\"\n  group: Assembly\n  aliases:\n  - m68k\n  extensions:\n  - \".asm\"\n  - \".i\"\n  - \".inc\"\n  - \".s\"\n  - \".x68\"\n  tm_scope: source.m68k\n  ace_mode: assembly_x86\n  language_id: 477582706\nMove:\n  type: programming\n  color: \"#4a137a\"\n  extensions:\n  - \".move\"\n  tm_scope: source.move\n  ace_mode: text\n  language_id: 638334599\nMuse:\n  type: prose\n  extensions:\n  - \".muse\"\n  tm_scope: text.muse\n  ace_mode: text\n  wrap: true\n  language_id: 474864066\n  aliases:\n  - amusewiki\n  - emacs muse\nMustache:\n  type: markup\n  color: \"#724b3b\"\n  extensions:\n  - \".mustache\"\n  tm_scope: text.html.smarty\n  ace_mode: smarty\n  codemirror_mode: smarty\n  codemirror_mime_type: text/x-smarty\n  language_id: 638334590\nMyghty:\n  type: programming\n  extensions:\n  - \".myt\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 239\nNASL:\n  type: programming\n  extensions:\n  - \".nasl\"\n  - \".inc\"\n  tm_scope: source.nasl\n  ace_mode: text\n  language_id: 171666519\nNCL:\n  type: programming\n  color: \"#28431f\"\n  extensions:\n  - \".ncl\"\n  tm_scope: source.ncl\n  ace_mode: text\n  language_id: 240\nNEON:\n  type: data\n  extensions:\n  - \".neon\"\n  tm_scope: source.neon\n  ace_mode: text\n  aliases:\n  - nette object notation\n  - ne-on\n  language_id: 481192983\nNL:\n  type: data\n  extensions:\n  - \".nl\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 241\nNPM Config:\n  type: data\n  color: \"#cb3837\"\n  group: INI\n  aliases:\n  - npmrc\n  filenames:\n  - \".npmrc\"\n  tm_scope: source.ini.npmrc\n  ace_mode: text\n  language_id: 685022663\nNSIS:\n  type: programming\n  extensions:\n  - \".nsi\"\n  - \".nsh\"\n  tm_scope: source.nsis\n  ace_mode: text\n  codemirror_mode: nsis\n  codemirror_mime_type: text/x-nsis\n  language_id: 242\nNWScript:\n  type: programming\n  color: \"#111522\"\n  extensions:\n  - \".nss\"\n  tm_scope: source.c.nwscript\n  ace_mode: c_cpp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-csrc\n  language_id: 731233819\nNasal:\n  type: programming\n  color: \"#1d2c4e\"\n  extensions:\n  - \".nas\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 178322513\nNearley:\n  type: programming\n  ace_mode: text\n  color: \"#990000\"\n  extensions:\n  - \".ne\"\n  - \".nearley\"\n  tm_scope: source.ne\n  language_id: 521429430\nNemerle:\n  type: programming\n  color: \"#3d3c6e\"\n  extensions:\n  - \".n\"\n  tm_scope: source.nemerle\n  ace_mode: text\n  language_id: 243\nNetLinx:\n  type: programming\n  color: \"#0aa0ff\"\n  extensions:\n  - \".axs\"\n  - \".axi\"\n  tm_scope: source.netlinx\n  ace_mode: text\n  language_id: 244\nNetLinx+ERB:\n  type: programming\n  color: \"#747faa\"\n  extensions:\n  - \".axs.erb\"\n  - \".axi.erb\"\n  tm_scope: source.netlinx.erb\n  ace_mode: text\n  language_id: 245\nNetLogo:\n  type: programming\n  color: \"#ff6375\"\n  extensions:\n  - \".nlogo\"\n  tm_scope: source.lisp\n  ace_mode: lisp\n  codemirror_mode: commonlisp\n  codemirror_mime_type: text/x-common-lisp\n  language_id: 246\nNewLisp:\n  type: programming\n  color: \"#87AED7\"\n  extensions:\n  - \".nl\"\n  - \".lisp\"\n  - \".lsp\"\n  interpreters:\n  - newlisp\n  tm_scope: source.lisp\n  ace_mode: lisp\n  codemirror_mode: commonlisp\n  codemirror_mime_type: text/x-common-lisp\n  language_id: 247\nNextflow:\n  type: programming\n  ace_mode: groovy\n  tm_scope: source.nextflow\n  color: \"#3ac486\"\n  extensions:\n  - \".nf\"\n  filenames:\n  - nextflow.config\n  interpreters:\n  - nextflow\n  language_id: 506780613\nNginx:\n  type: data\n  color: \"#009639\"\n  extensions:\n  - \".nginx\"\n  - \".nginxconf\"\n  - \".vhost\"\n  filenames:\n  - nginx.conf\n  tm_scope: source.nginx\n  aliases:\n  - nginx configuration file\n  ace_mode: text\n  codemirror_mode: nginx\n  codemirror_mime_type: text/x-nginx-conf\n  language_id: 248\nNim:\n  type: programming\n  color: \"#ffc200\"\n  extensions:\n  - \".nim\"\n  - \".nim.cfg\"\n  - \".nimble\"\n  - \".nimrod\"\n  - \".nims\"\n  filenames:\n  - nim.cfg\n  ace_mode: text\n  tm_scope: source.nim\n  language_id: 249\nNinja:\n  type: data\n  tm_scope: source.ninja\n  extensions:\n  - \".ninja\"\n  ace_mode: text\n  language_id: 250\nNit:\n  type: programming\n  color: \"#009917\"\n  extensions:\n  - \".nit\"\n  tm_scope: source.nit\n  ace_mode: text\n  language_id: 251\nNix:\n  type: programming\n  color: \"#7e7eff\"\n  extensions:\n  - \".nix\"\n  aliases:\n  - nixos\n  tm_scope: source.nix\n  ace_mode: nix\n  language_id: 252\nNu:\n  type: programming\n  color: \"#c9df40\"\n  aliases:\n  - nush\n  extensions:\n  - \".nu\"\n  filenames:\n  - Nukefile\n  tm_scope: source.nu\n  ace_mode: scheme\n  codemirror_mode: scheme\n  codemirror_mime_type: text/x-scheme\n  interpreters:\n  - nush\n  language_id: 253\nNumPy:\n  type: programming\n  color: \"#9C8AF9\"\n  group: Python\n  extensions:\n  - \".numpy\"\n  - \".numpyw\"\n  - \".numsc\"\n  tm_scope: none\n  ace_mode: text\n  codemirror_mode: python\n  codemirror_mime_type: text/x-python\n  language_id: 254\nNunjucks:\n  type: markup\n  color: \"#3d8137\"\n  extensions:\n  - \".njk\"\n  aliases:\n  - njk\n  tm_scope: text.html.nunjucks\n  ace_mode: nunjucks\n  language_id: 461856962\nOASv2-json:\n  type: data\n  color: \"#85ea2d\"\n  extensions:\n  - \".json\"\n  group: OpenAPI Specification v2\n  tm_scope: source.json\n  ace_mode: json\n  codemirror_mode: javascript\n  codemirror_mime_type: application/json\n  language_id: 834374816\nOASv2-yaml:\n  type: data\n  color: \"#85ea2d\"\n  extensions:\n  - \".yaml\"\n  - \".yml\"\n  group: OpenAPI Specification v2\n  tm_scope: source.yaml\n  ace_mode: yaml\n  codemirror_mode: yaml\n  codemirror_mime_type: text/x-yaml\n  language_id: 105187618\nOASv3-json:\n  type: data\n  color: \"#85ea2d\"\n  extensions:\n  - \".json\"\n  group: OpenAPI Specification v3\n  tm_scope: source.json\n  ace_mode: json\n  codemirror_mode: javascript\n  codemirror_mime_type: application/json\n  language_id: 980062566\nOASv3-yaml:\n  type: data\n  color: \"#85ea2d\"\n  extensions:\n  - \".yaml\"\n  - \".yml\"\n  group: OpenAPI Specification v3\n  tm_scope: source.yaml\n  ace_mode: yaml\n  codemirror_mode: yaml\n  codemirror_mime_type: text/x-yaml\n  language_id: 51239111\nOCaml:\n  type: programming\n  ace_mode: ocaml\n  codemirror_mode: mllike\n  codemirror_mime_type: text/x-ocaml\n  color: \"#3be133\"\n  extensions:\n  - \".ml\"\n  - \".eliom\"\n  - \".eliomi\"\n  - \".ml4\"\n  - \".mli\"\n  - \".mll\"\n  - \".mly\"\n  interpreters:\n  - ocaml\n  - ocamlrun\n  - ocamlscript\n  tm_scope: source.ocaml\n  language_id: 255\nObjDump:\n  type: data\n  extensions:\n  - \".objdump\"\n  tm_scope: objdump.x86asm\n  ace_mode: assembly_x86\n  language_id: 256\nObject Data Instance Notation:\n  type: data\n  extensions:\n  - \".odin\"\n  tm_scope: source.odin-ehr\n  ace_mode: text\n  language_id: 985227236\nObjectScript:\n  type: programming\n  extensions:\n  - \".cls\"\n  language_id: 202735509\n  tm_scope: source.objectscript\n  color: \"#424893\"\n  ace_mode: text\nObjective-C:\n  type: programming\n  tm_scope: source.objc\n  color: \"#438eff\"\n  aliases:\n  - obj-c\n  - objc\n  - objectivec\n  extensions:\n  - \".m\"\n  - \".h\"\n  ace_mode: objectivec\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-objectivec\n  language_id: 257\nObjective-C++:\n  type: programming\n  tm_scope: source.objc++\n  color: \"#6866fb\"\n  aliases:\n  - obj-c++\n  - objc++\n  - objectivec++\n  extensions:\n  - \".mm\"\n  ace_mode: objectivec\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-objectivec\n  language_id: 258\nObjective-J:\n  type: programming\n  color: \"#ff0c5a\"\n  aliases:\n  - obj-j\n  - objectivej\n  - objj\n  extensions:\n  - \".j\"\n  - \".sj\"\n  tm_scope: source.js.objj\n  ace_mode: text\n  language_id: 259\nOdin:\n  type: programming\n  color: \"#60AFFE\"\n  aliases:\n  - odinlang\n  - odin-lang\n  extensions:\n  - \".odin\"\n  tm_scope: source.odin\n  ace_mode: text\n  language_id: 889244082\nOmgrofl:\n  type: programming\n  extensions:\n  - \".omgrofl\"\n  color: \"#cabbff\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 260\nOpa:\n  type: programming\n  extensions:\n  - \".opa\"\n  tm_scope: source.opa\n  ace_mode: text\n  language_id: 261\nOpal:\n  type: programming\n  color: \"#f7ede0\"\n  extensions:\n  - \".opal\"\n  tm_scope: source.opal\n  ace_mode: text\n  language_id: 262\nOpen Policy Agent:\n  type: programming\n  color: \"#7d9199\"\n  ace_mode: text\n  extensions:\n  - \".rego\"\n  language_id: 840483232\n  tm_scope: source.rego\nOpenAPI Specification v2:\n  aliases:\n  - oasv2\n  type: data\n  color: \"#85ea2d\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 848295328\nOpenAPI Specification v3:\n  aliases:\n  - oasv3\n  type: data\n  color: \"#85ea2d\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 557959099\nOpenCL:\n  type: programming\n  color: \"#ed2e2d\"\n  group: C\n  extensions:\n  - \".cl\"\n  - \".opencl\"\n  tm_scope: source.c\n  ace_mode: c_cpp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-csrc\n  language_id: 263\nOpenEdge ABL:\n  type: programming\n  color: \"#5ce600\"\n  aliases:\n  - progress\n  - openedge\n  - abl\n  extensions:\n  - \".p\"\n  - \".cls\"\n  - \".w\"\n  tm_scope: source.abl\n  ace_mode: text\n  language_id: 264\nOpenQASM:\n  type: programming\n  extensions:\n  - \".qasm\"\n  color: \"#AA70FF\"\n  tm_scope: source.qasm\n  ace_mode: text\n  language_id: 153739399\nOpenRC runscript:\n  type: programming\n  group: Shell\n  aliases:\n  - openrc\n  interpreters:\n  - openrc-run\n  tm_scope: source.shell\n  ace_mode: sh\n  codemirror_mode: shell\n  codemirror_mime_type: text/x-sh\n  language_id: 265\nOpenSCAD:\n  type: programming\n  color: \"#e5cd45\"\n  extensions:\n  - \".scad\"\n  tm_scope: source.scad\n  ace_mode: scad\n  language_id: 266\nOpenStep Property List:\n  type: data\n  extensions:\n  - \".plist\"\n  - \".glyphs\"\n  tm_scope: source.plist\n  ace_mode: text\n  language_id: 598917541\nOpenType Feature File:\n  type: data\n  aliases:\n  - AFDKO\n  extensions:\n  - \".fea\"\n  tm_scope: source.opentype\n  ace_mode: text\n  language_id: 374317347\nOption List:\n  type: data\n  color: \"#476732\"\n  aliases:\n  - opts\n  - ackrc\n  filenames:\n  - \".ackrc\"\n  - \"ackrc\"\n  - \"mocha.opts\"\n  tm_scope: source.opts\n  ace_mode: sh\n  codemirror_mode: shell\n  codemirror_mime_type: text/x-sh\n  language_id: 723589315\nOrg:\n  type: prose\n  color: \"#77aa99\"\n  wrap: true\n  extensions:\n  - \".org\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 267\nOx:\n  type: programming\n  extensions:\n  - \".ox\"\n  - \".oxh\"\n  - \".oxo\"\n  tm_scope: source.ox\n  ace_mode: text\n  language_id: 268\nOxygene:\n  type: programming\n  color: \"#cdd0e3\"\n  extensions:\n  - \".oxygene\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 269\nOz:\n  type: programming\n  color: \"#fab738\"\n  extensions:\n  - \".oz\"\n  tm_scope: source.oz\n  ace_mode: text\n  codemirror_mode: oz\n  codemirror_mime_type: text/x-oz\n  language_id: 270\nP4:\n  type: programming\n  color: \"#7055b5\"\n  extensions:\n  - \".p4\"\n  tm_scope: source.p4\n  ace_mode: text\n  language_id: 348895984\nPDDL:\n  type: programming\n  color: \"#0d00ff\"\n  extensions:\n  - \".pddl\"\n  tm_scope: source.pddl\n  ace_mode: text\n  language_id: 736235603\nPEG.js:\n  type: programming\n  color: \"#234d6b\"\n  extensions:\n  - \".pegjs\"\n  tm_scope: source.pegjs\n  ace_mode: javascript\n  codemirror_mode: javascript\n  codemirror_mime_type: text/javascript\n  language_id: 81442128\nPHP:\n  type: programming\n  tm_scope: text.html.php\n  ace_mode: php\n  codemirror_mode: php\n  codemirror_mime_type: application/x-httpd-php\n  color: \"#4F5D95\"\n  extensions:\n  - \".php\"\n  - \".aw\"\n  - \".ctp\"\n  - \".fcgi\"\n  - \".inc\"\n  - \".php3\"\n  - \".php4\"\n  - \".php5\"\n  - \".phps\"\n  - \".phpt\"\n  filenames:\n  - \".php\"\n  - \".php_cs\"\n  - \".php_cs.dist\"\n  - Phakefile\n  interpreters:\n  - php\n  aliases:\n  - inc\n  language_id: 272\nPLSQL:\n  type: programming\n  ace_mode: sql\n  codemirror_mode: sql\n  codemirror_mime_type: text/x-plsql\n  tm_scope: none\n  color: \"#dad8d8\"\n  extensions:\n  - \".pls\"\n  - \".bdy\"\n  - \".ddl\"\n  - \".fnc\"\n  - \".pck\"\n  - \".pkb\"\n  - \".pks\"\n  - \".plb\"\n  - \".plsql\"\n  - \".prc\"\n  - \".spc\"\n  - \".sql\"\n  - \".tpb\"\n  - \".tps\"\n  - \".trg\"\n  - \".vw\"\n  language_id: 273\nPLpgSQL:\n  type: programming\n  color: \"#336790\"\n  ace_mode: pgsql\n  codemirror_mode: sql\n  codemirror_mime_type: text/x-sql\n  tm_scope: source.sql\n  extensions:\n  - \".pgsql\"\n  - \".sql\"\n  language_id: 274\nPOV-Ray SDL:\n  type: programming\n  color: \"#6bac65\"\n  aliases:\n  - pov-ray\n  - povray\n  extensions:\n  - \".pov\"\n  - \".inc\"\n  tm_scope: source.pov-ray sdl\n  ace_mode: text\n  language_id: 275\nPan:\n  type: programming\n  color: \"#cc0000\"\n  extensions:\n  - \".pan\"\n  tm_scope: source.pan\n  ace_mode: text\n  language_id: 276\nPapyrus:\n  type: programming\n  color: \"#6600cc\"\n  extensions:\n  - \".psc\"\n  tm_scope: source.papyrus.skyrim\n  ace_mode: text\n  language_id: 277\nParrot:\n  type: programming\n  color: \"#f3ca0a\"\n  extensions:\n  - \".parrot\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 278\nParrot Assembly:\n  group: Parrot\n  type: programming\n  aliases:\n  - pasm\n  extensions:\n  - \".pasm\"\n  interpreters:\n  - parrot\n  tm_scope: none\n  ace_mode: text\n  language_id: 279\nParrot Internal Representation:\n  group: Parrot\n  tm_scope: source.parrot.pir\n  type: programming\n  aliases:\n  - pir\n  extensions:\n  - \".pir\"\n  interpreters:\n  - parrot\n  ace_mode: text\n  language_id: 280\nPascal:\n  type: programming\n  color: \"#E3F171\"\n  aliases:\n  - delphi\n  - objectpascal\n  extensions:\n  - \".pas\"\n  - \".dfm\"\n  - \".dpr\"\n  - \".inc\"\n  - \".lpr\"\n  - \".pascal\"\n  - \".pp\"\n  interpreters:\n  - instantfpc\n  tm_scope: source.pascal\n  ace_mode: pascal\n  codemirror_mode: pascal\n  codemirror_mime_type: text/x-pascal\n  language_id: 281\nPawn:\n  type: programming\n  color: \"#dbb284\"\n  extensions:\n  - \".pwn\"\n  - \".inc\"\n  - \".sma\"\n  tm_scope: source.pawn\n  ace_mode: text\n  language_id: 271\nPep8:\n  type: programming\n  color: \"#C76F5B\"\n  extensions:\n  - \".pep\"\n  ace_mode: text\n  tm_scope: source.pep8\n  language_id: 840372442\nPerl:\n  type: programming\n  tm_scope: source.perl\n  ace_mode: perl\n  codemirror_mode: perl\n  codemirror_mime_type: text/x-perl\n  color: \"#0298c3\"\n  extensions:\n  - \".pl\"\n  - \".al\"\n  - \".cgi\"\n  - \".fcgi\"\n  - \".perl\"\n  - \".ph\"\n  - \".plx\"\n  - \".pm\"\n  - \".psgi\"\n  - \".t\"\n  filenames:\n  - \".latexmkrc\"\n  - Makefile.PL\n  - Rexfile\n  - ack\n  - cpanfile\n  - latexmkrc\n  interpreters:\n  - cperl\n  - perl\n  aliases:\n  - cperl\n  language_id: 282\nPic:\n  type: markup\n  group: Roff\n  tm_scope: source.pic\n  extensions:\n  - \".pic\"\n  - \".chem\"\n  ace_mode: text\n  codemirror_mode: troff\n  codemirror_mime_type: text/troff\n  language_id: 425\nPickle:\n  type: data\n  extensions:\n  - \".pkl\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 284\nPicoLisp:\n  type: programming\n  color: \"#6067af\"\n  extensions:\n  - \".l\"\n  interpreters:\n  - picolisp\n  - pil\n  tm_scope: source.lisp\n  ace_mode: lisp\n  language_id: 285\nPigLatin:\n  type: programming\n  color: \"#fcd7de\"\n  extensions:\n  - \".pig\"\n  tm_scope: source.pig_latin\n  ace_mode: text\n  language_id: 286\nPike:\n  type: programming\n  color: \"#005390\"\n  extensions:\n  - \".pike\"\n  - \".pmod\"\n  interpreters:\n  - pike\n  tm_scope: source.pike\n  ace_mode: text\n  language_id: 287\nPlantUML:\n  type: data\n  extensions:\n  - \".puml\"\n  - \".iuml\"\n  - \".plantuml\"\n  tm_scope: source.wsd\n  ace_mode: text\n  language_id: 833504686\nPod:\n  type: prose\n  ace_mode: perl\n  codemirror_mode: perl\n  codemirror_mime_type: text/x-perl\n  wrap: true\n  extensions:\n  - \".pod\"\n  interpreters:\n  - perl\n  tm_scope: none\n  language_id: 288\nPod 6:\n  type: prose\n  ace_mode: perl\n  tm_scope: source.raku\n  wrap: true\n  extensions:\n  - \".pod\"\n  - \".pod6\"\n  interpreters:\n  - perl6\n  language_id: 155357471\nPogoScript:\n  type: programming\n  color: \"#d80074\"\n  extensions:\n  - \".pogo\"\n  tm_scope: source.pogoscript\n  ace_mode: text\n  language_id: 289\nPolar:\n  type: programming\n  color: \"#ae81ff\"\n  extensions:\n  - \".polar\"\n  tm_scope: source.polar\n  ace_mode: text\n  language_id: 839112914\nPony:\n  type: programming\n  extensions:\n  - \".pony\"\n  tm_scope: source.pony\n  ace_mode: text\n  language_id: 290\nPortugol:\n  type: programming\n  color: \"#f8bd00\"\n  extensions:\n  - \".por\"\n  tm_scope: source.portugol\n  ace_mode: text\n  language_id: 832391833\nPostCSS:\n  type: markup\n  color: \"#dc3a0c\"\n  tm_scope: source.postcss\n  group: CSS\n  extensions:\n  - \".pcss\"\n  - \".postcss\"\n  ace_mode: text\n  language_id: 262764437\nPostScript:\n  type: markup\n  color: \"#da291c\"\n  extensions:\n  - \".ps\"\n  - \".eps\"\n  - \".epsi\"\n  - \".pfa\"\n  tm_scope: source.postscript\n  aliases:\n  - postscr\n  ace_mode: text\n  language_id: 291\nPowerBuilder:\n  type: programming\n  color: \"#8f0f8d\"\n  extensions:\n  - \".pbt\"\n  - \".sra\"\n  - \".sru\"\n  - \".srw\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 292\nPowerShell:\n  type: programming\n  color: \"#012456\"\n  tm_scope: source.powershell\n  ace_mode: powershell\n  codemirror_mode: powershell\n  codemirror_mime_type: application/x-powershell\n  aliases:\n  - posh\n  - pwsh\n  extensions:\n  - \".ps1\"\n  - \".psd1\"\n  - \".psm1\"\n  interpreters:\n  - pwsh\n  language_id: 293\nPrisma:\n  type: data\n  color: \"#0c344b\"\n  extensions:\n  - \".prisma\"\n  tm_scope: source.prisma\n  ace_mode: text\n  language_id: 499933428\nProcessing:\n  type: programming\n  color: \"#0096D8\"\n  extensions:\n  - \".pde\"\n  tm_scope: source.processing\n  ace_mode: text\n  language_id: 294\nProcfile:\n  type: programming\n  color: \"#3B2F63\"\n  filenames:\n  - Procfile\n  tm_scope: source.procfile\n  ace_mode: batchfile\n  language_id: 305313959\nProguard:\n  type: data\n  extensions:\n  - \".pro\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 716513858\nProlog:\n  type: programming\n  color: \"#74283c\"\n  extensions:\n  - \".pl\"\n  - \".pro\"\n  - \".prolog\"\n  - \".yap\"\n  interpreters:\n  - swipl\n  - yap\n  tm_scope: source.prolog\n  ace_mode: prolog\n  language_id: 295\nPromela:\n  type: programming\n  color: \"#de0000\"\n  tm_scope: source.promela\n  ace_mode: text\n  extensions:\n  - \".pml\"\n  language_id: 441858312\nPropeller Spin:\n  type: programming\n  color: \"#7fa2a7\"\n  extensions:\n  - \".spin\"\n  tm_scope: source.spin\n  ace_mode: text\n  language_id: 296\nProtocol Buffer:\n  type: data\n  aliases:\n  - protobuf\n  - Protocol Buffers\n  extensions:\n  - \".proto\"\n  tm_scope: source.proto\n  ace_mode: protobuf\n  codemirror_mode: protobuf\n  codemirror_mime_type: text/x-protobuf\n  language_id: 297\nProtocol Buffer Text Format:\n  type: data\n  aliases:\n  - text proto\n  - protobuf text format\n  extensions:\n  - \".textproto\"\n  - \".pbt\"\n  - \".pbtxt\"\n  tm_scope: source.textproto\n  ace_mode: text\n  language_id: 436568854\nPublic Key:\n  type: data\n  extensions:\n  - \".asc\"\n  - \".pub\"\n  tm_scope: none\n  ace_mode: text\n  codemirror_mode: asciiarmor\n  codemirror_mime_type: application/pgp\n  language_id: 298\nPug:\n  type: markup\n  color: \"#a86454\"\n  extensions:\n  - \".jade\"\n  - \".pug\"\n  tm_scope: text.jade\n  ace_mode: jade\n  codemirror_mode: pug\n  codemirror_mime_type: text/x-pug\n  language_id: 179\nPuppet:\n  type: programming\n  color: \"#302B6D\"\n  extensions:\n  - \".pp\"\n  filenames:\n  - Modulefile\n  ace_mode: text\n  codemirror_mode: puppet\n  codemirror_mime_type: text/x-puppet\n  tm_scope: source.puppet\n  language_id: 299\nPure Data:\n  type: data\n  extensions:\n  - \".pd\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 300\nPureBasic:\n  type: programming\n  color: \"#5a6986\"\n  extensions:\n  - \".pb\"\n  - \".pbi\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 301\nPureScript:\n  type: programming\n  color: \"#1D222D\"\n  extensions:\n  - \".purs\"\n  tm_scope: source.purescript\n  ace_mode: haskell\n  codemirror_mode: haskell\n  codemirror_mime_type: text/x-haskell\n  language_id: 302\nPython:\n  type: programming\n  tm_scope: source.python\n  ace_mode: python\n  codemirror_mode: python\n  codemirror_mime_type: text/x-python\n  color: \"#3572A5\"\n  extensions:\n  - \".py\"\n  - \".cgi\"\n  - \".fcgi\"\n  - \".gyp\"\n  - \".gypi\"\n  - \".lmi\"\n  - \".py3\"\n  - \".pyde\"\n  - \".pyi\"\n  - \".pyp\"\n  - \".pyt\"\n  - \".pyw\"\n  - \".rpy\"\n  - \".smk\"\n  - \".spec\"\n  - \".tac\"\n  - \".wsgi\"\n  - \".xpy\"\n  filenames:\n  - \".gclient\"\n  - DEPS\n  - SConscript\n  - SConstruct\n  - Snakefile\n  - wscript\n  interpreters:\n  - python\n  - python2\n  - python3\n  aliases:\n  - python3\n  - rusthon\n  language_id: 303\nPython console:\n  type: programming\n  color: \"#3572A5\"\n  group: Python\n  aliases:\n  - pycon\n  tm_scope: text.python.console\n  ace_mode: text\n  language_id: 428\nPython traceback:\n  type: data\n  color: \"#3572A5\"\n  group: Python\n  extensions:\n  - \".pytb\"\n  tm_scope: text.python.traceback\n  ace_mode: text\n  language_id: 304\nQ#:\n  type: programming\n  extensions:\n  - \".qs\"\n  aliases:\n  - qsharp\n  color: \"#fed659\"\n  ace_mode: text\n  tm_scope: source.qsharp\n  language_id: 697448245\nQML:\n  type: programming\n  color: \"#44a51c\"\n  extensions:\n  - \".qml\"\n  - \".qbs\"\n  tm_scope: source.qml\n  ace_mode: text\n  language_id: 305\nQMake:\n  type: programming\n  extensions:\n  - \".pro\"\n  - \".pri\"\n  interpreters:\n  - qmake\n  tm_scope: source.qmake\n  ace_mode: text\n  language_id: 306\nQt Script:\n  type: programming\n  ace_mode: javascript\n  codemirror_mode: javascript\n  codemirror_mime_type: text/javascript\n  extensions:\n  - \".qs\"\n  filenames:\n  - installscript.qs\n  - toolchain_installscript.qs\n  color: \"#00b841\"\n  tm_scope: source.js\n  language_id: 558193693\nQuake:\n  type: programming\n  filenames:\n  - m3makefile\n  - m3overrides\n  color: \"#882233\"\n  ace_mode: text\n  tm_scope: source.quake\n  language_id: 375265331\nR:\n  type: programming\n  color: \"#198CE7\"\n  aliases:\n  - R\n  - Rscript\n  - splus\n  extensions:\n  - \".r\"\n  - \".rd\"\n  - \".rsx\"\n  filenames:\n  - \".Rprofile\"\n  - expr-dist\n  interpreters:\n  - Rscript\n  tm_scope: source.r\n  ace_mode: r\n  codemirror_mode: r\n  codemirror_mime_type: text/x-rsrc\n  language_id: 307\nRAML:\n  type: markup\n  ace_mode: yaml\n  codemirror_mode: yaml\n  codemirror_mime_type: text/x-yaml\n  tm_scope: source.yaml\n  color: \"#77d9fb\"\n  extensions:\n  - \".raml\"\n  language_id: 308\nRDoc:\n  type: prose\n  color: \"#701516\"\n  ace_mode: rdoc\n  wrap: true\n  extensions:\n  - \".rdoc\"\n  tm_scope: text.rdoc\n  language_id: 309\nREALbasic:\n  type: programming\n  extensions:\n  - \".rbbas\"\n  - \".rbfrm\"\n  - \".rbmnu\"\n  - \".rbres\"\n  - \".rbtbar\"\n  - \".rbuistate\"\n  tm_scope: source.vbnet\n  ace_mode: text\n  language_id: 310\nREXX:\n  type: programming\n  color: \"#d90e09\"\n  aliases:\n  - arexx\n  extensions:\n  - \".rexx\"\n  - \".pprx\"\n  - \".rex\"\n  interpreters:\n  - regina\n  - rexx\n  tm_scope: source.rexx\n  ace_mode: text\n  language_id: 311\nRMarkdown:\n  type: prose\n  color: \"#198ce7\"\n  wrap: true\n  ace_mode: markdown\n  codemirror_mode: gfm\n  codemirror_mime_type: text/x-gfm\n  extensions:\n  - \".qmd\"\n  - \".rmd\"\n  tm_scope: source.gfm\n  language_id: 313\nRPC:\n  type: programming\n  aliases:\n  - rpcgen\n  - oncrpc\n  - xdr\n  ace_mode: c_cpp\n  extensions:\n  - \".x\"\n  tm_scope: source.c\n  language_id: 1031374237\nRPGLE:\n  type: programming\n  ace_mode: text\n  color: \"#2BDE21\"\n  aliases:\n  - ile rpg\n  - sqlrpgle\n  extensions:\n  - \".rpgle\"\n  - \".sqlrpgle\"\n  tm_scope: source.rpgle\n  language_id: 609977990\nRPM Spec:\n  type: data\n  tm_scope: source.rpm-spec\n  extensions:\n  - \".spec\"\n  aliases:\n  - specfile\n  ace_mode: text\n  codemirror_mode: rpm\n  codemirror_mime_type: text/x-rpm-spec\n  language_id: 314\nRUNOFF:\n  type: markup\n  color: \"#665a4e\"\n  extensions:\n  - \".rnh\"\n  - \".rno\"\n  wrap: true\n  tm_scope: text.runoff\n  ace_mode: text\n  language_id: 315\nRacket:\n  type: programming\n  color: \"#3c5caa\"\n  extensions:\n  - \".rkt\"\n  - \".rktd\"\n  - \".rktl\"\n  - \".scrbl\"\n  interpreters:\n  - racket\n  tm_scope: source.racket\n  ace_mode: lisp\n  language_id: 316\nRagel:\n  type: programming\n  color: \"#9d5200\"\n  extensions:\n  - \".rl\"\n  aliases:\n  - ragel-rb\n  - ragel-ruby\n  tm_scope: none\n  ace_mode: text\n  language_id: 317\nRaku:\n  type: programming\n  color: \"#0000fb\"\n  extensions:\n  - \".6pl\"\n  - \".6pm\"\n  - \".nqp\"\n  - \".p6\"\n  - \".p6l\"\n  - \".p6m\"\n  - \".pl\"\n  - \".pl6\"\n  - \".pm\"\n  - \".pm6\"\n  - \".raku\"\n  - \".rakumod\"\n  - \".t\"\n  interpreters:\n  - perl6\n  - raku\n  - rakudo\n  aliases:\n  - perl6\n  - perl-6\n  tm_scope: source.raku\n  ace_mode: perl\n  codemirror_mode: perl\n  codemirror_mime_type: text/x-perl\n  language_id: 283\nRascal:\n  type: programming\n  color: \"#fffaa0\"\n  extensions:\n  - \".rsc\"\n  tm_scope: source.rascal\n  ace_mode: text\n  language_id: 173616037\nRaw token data:\n  type: data\n  aliases:\n  - raw\n  extensions:\n  - \".raw\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 318\nReScript:\n  type: programming\n  color: \"#ed5051\"\n  ace_mode: rust\n  codemirror_mode: rust\n  codemirror_mime_type: text/x-rustsrc\n  extensions:\n  - \".res\"\n  interpreters:\n  - ocaml\n  tm_scope: source.rescript\n  language_id: 501875647\nReadline Config:\n  type: data\n  group: INI\n  aliases:\n  - inputrc\n  - readline\n  filenames:\n  - \".inputrc\"\n  - inputrc\n  tm_scope: source.inputrc\n  ace_mode: text\n  language_id: 538732839\nReason:\n  type: programming\n  color: \"#ff5847\"\n  ace_mode: rust\n  codemirror_mode: rust\n  codemirror_mime_type: text/x-rustsrc\n  extensions:\n  - \".re\"\n  - \".rei\"\n  tm_scope: source.reason\n  language_id: 869538413\nReasonLIGO:\n  type: programming\n  color: \"#ff5847\"\n  ace_mode: rust\n  codemirror_mode: rust\n  codemirror_mime_type: text/x-rustsrc\n  group: LigoLANG\n  extensions:\n  - \".religo\"\n  tm_scope: source.religo\n  language_id: 319002153\nRebol:\n  type: programming\n  color: \"#358a5b\"\n  extensions:\n  - \".reb\"\n  - \".r\"\n  - \".r2\"\n  - \".r3\"\n  - \".rebol\"\n  ace_mode: text\n  tm_scope: source.rebol\n  language_id: 319\nRecord Jar:\n  type: data\n  filenames:\n  - language-subtag-registry.txt\n  tm_scope: source.record-jar\n  codemirror_mode: properties\n  codemirror_mime_type: text/x-properties\n  ace_mode: text\n  color: \"#0673ba\"\n  language_id: 865765202\nRed:\n  type: programming\n  color: \"#f50000\"\n  extensions:\n  - \".red\"\n  - \".reds\"\n  aliases:\n  - red/system\n  tm_scope: source.red\n  ace_mode: text\n  language_id: 320\nRedcode:\n  type: programming\n  extensions:\n  - \".cw\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 321\nRedirect Rules:\n  type: data\n  aliases:\n  - redirects\n  filenames:\n  - _redirects\n  tm_scope: source.redirects\n  ace_mode: text\n  language_id: 1020148948\nRegular Expression:\n  type: data\n  color: \"#009a00\"\n  extensions:\n  - \".regexp\"\n  - \".regex\"\n  aliases:\n  - regexp\n  - regex\n  ace_mode: text\n  tm_scope: source.regexp\n  language_id: 363378884\nRen'Py:\n  type: programming\n  aliases:\n  - renpy\n  color: \"#ff7f7f\"\n  extensions:\n  - \".rpy\"\n  tm_scope: source.renpy\n  ace_mode: python\n  language_id: 322\nRenderScript:\n  type: programming\n  extensions:\n  - \".rs\"\n  - \".rsh\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 323\nRich Text Format:\n  type: markup\n  extensions:\n  - \".rtf\"\n  tm_scope: text.rtf\n  ace_mode: text\n  language_id: 51601661\nRing:\n  type: programming\n  color: \"#2D54CB\"\n  extensions:\n  - \".ring\"\n  tm_scope: source.ring\n  ace_mode: text\n  language_id: 431\nRiot:\n  type: markup\n  color: \"#A71E49\"\n  ace_mode: html\n  extensions:\n  - \".riot\"\n  tm_scope: text.html.riot\n  language_id: 878396783\nRobotFramework:\n  type: programming\n  color: \"#00c0b5\"\n  extensions:\n  - \".robot\"\n  tm_scope: text.robot\n  ace_mode: text\n  language_id: 324\nRoff:\n  type: markup\n  color: \"#ecdebe\"\n  extensions:\n  - \".roff\"\n  - \".1\"\n  - \".1in\"\n  - \".1m\"\n  - \".1x\"\n  - \".2\"\n  - \".3\"\n  - \".3in\"\n  - \".3m\"\n  - \".3p\"\n  - \".3pm\"\n  - \".3qt\"\n  - \".3x\"\n  - \".4\"\n  - \".5\"\n  - \".6\"\n  - \".7\"\n  - \".8\"\n  - \".9\"\n  - \".l\"\n  - \".man\"\n  - \".mdoc\"\n  - \".me\"\n  - \".ms\"\n  - \".n\"\n  - \".nr\"\n  - \".rno\"\n  - \".tmac\"\n  filenames:\n  - eqnrc\n  - mmn\n  - mmt\n  - troffrc\n  - troffrc-end\n  tm_scope: text.roff\n  aliases:\n  - groff\n  - man\n  - manpage\n  - man page\n  - man-page\n  - mdoc\n  - nroff\n  - troff\n  wrap: true\n  ace_mode: text\n  codemirror_mode: troff\n  codemirror_mime_type: text/troff\n  language_id: 141\nRoff Manpage:\n  type: markup\n  color: \"#ecdebe\"\n  group: Roff\n  extensions:\n  - \".1\"\n  - \".1in\"\n  - \".1m\"\n  - \".1x\"\n  - \".2\"\n  - \".3\"\n  - \".3in\"\n  - \".3m\"\n  - \".3p\"\n  - \".3pm\"\n  - \".3qt\"\n  - \".3x\"\n  - \".4\"\n  - \".5\"\n  - \".6\"\n  - \".7\"\n  - \".8\"\n  - \".9\"\n  - \".man\"\n  - \".mdoc\"\n  wrap: true\n  tm_scope: text.roff\n  ace_mode: text\n  codemirror_mode: troff\n  codemirror_mime_type: text/troff\n  language_id: 612669833\nRouge:\n  type: programming\n  ace_mode: clojure\n  codemirror_mode: clojure\n  codemirror_mime_type: text/x-clojure\n  color: \"#cc0088\"\n  extensions:\n  - \".rg\"\n  tm_scope: source.clojure\n  language_id: 325\nRouterOS Script:\n  type: programming\n  ace_mode: text\n  extensions:\n  - \".rsc\"\n  interpreters:\n  - RouterOS\n  color: \"#DE3941\"\n  tm_scope: none\n  language_id: 592853203\nRuby:\n  type: programming\n  tm_scope: source.ruby\n  ace_mode: ruby\n  codemirror_mode: ruby\n  codemirror_mime_type: text/x-ruby\n  color: \"#701516\"\n  aliases:\n  - jruby\n  - macruby\n  - rake\n  - rb\n  - rbx\n  extensions:\n  - \".rb\"\n  - \".builder\"\n  - \".eye\"\n  - \".fcgi\"\n  - \".gemspec\"\n  - \".god\"\n  - \".jbuilder\"\n  - \".mspec\"\n  - \".pluginspec\"\n  - \".podspec\"\n  - \".prawn\"\n  - \".rabl\"\n  - \".rake\"\n  - \".rbi\"\n  - \".rbuild\"\n  - \".rbw\"\n  - \".rbx\"\n  - \".ru\"\n  - \".ruby\"\n  - \".spec\"\n  - \".thor\"\n  - \".watchr\"\n  interpreters:\n  - ruby\n  - macruby\n  - rake\n  - jruby\n  - rbx\n  filenames:\n  - \".irbrc\"\n  - \".pryrc\"\n  - \".simplecov\"\n  - Appraisals\n  - Berksfile\n  - Brewfile\n  - Buildfile\n  - Capfile\n  - Dangerfile\n  - Deliverfile\n  - Fastfile\n  - Gemfile\n  - Guardfile\n  - Jarfile\n  - Mavenfile\n  - Podfile\n  - Puppetfile\n  - Rakefile\n  - Snapfile\n  - Steepfile\n  - Thorfile\n  - Vagrantfile\n  - buildfile\n  language_id: 326\nRust:\n  type: programming\n  aliases:\n  - rs\n  color: \"#dea584\"\n  extensions:\n  - \".rs\"\n  - \".rs.in\"\n  tm_scope: source.rust\n  ace_mode: rust\n  codemirror_mode: rust\n  codemirror_mime_type: text/x-rustsrc\n  language_id: 327\nSAS:\n  type: programming\n  color: \"#B34936\"\n  extensions:\n  - \".sas\"\n  tm_scope: source.sas\n  ace_mode: text\n  codemirror_mode: sas\n  codemirror_mime_type: text/x-sas\n  language_id: 328\nSCSS:\n  type: markup\n  color: \"#c6538c\"\n  tm_scope: source.css.scss\n  ace_mode: scss\n  codemirror_mode: css\n  codemirror_mime_type: text/x-scss\n  extensions:\n  - \".scss\"\n  language_id: 329\nSELinux Policy:\n  aliases:\n  - SELinux Kernel Policy Language\n  - sepolicy\n  type: data\n  tm_scope: source.sepolicy\n  extensions:\n  - \".te\"\n  filenames:\n  - file_contexts\n  - genfs_contexts\n  - initial_sids\n  - port_contexts\n  - security_classes\n  ace_mode: text\n  language_id: 880010326\nSMT:\n  type: programming\n  extensions:\n  - \".smt2\"\n  - \".smt\"\n  interpreters:\n  - boolector\n  - cvc4\n  - mathsat5\n  - opensmt\n  - smtinterpol\n  - smt-rat\n  - stp\n  - verit\n  - yices2\n  - z3\n  tm_scope: source.smt\n  ace_mode: text\n  language_id: 330\nSPARQL:\n  type: data\n  color: \"#0C4597\"\n  tm_scope: source.sparql\n  ace_mode: text\n  codemirror_mode: sparql\n  codemirror_mime_type: application/sparql-query\n  extensions:\n  - \".sparql\"\n  - \".rq\"\n  language_id: 331\nSQF:\n  type: programming\n  color: \"#3F3F3F\"\n  extensions:\n  - \".sqf\"\n  - \".hqf\"\n  tm_scope: source.sqf\n  ace_mode: text\n  language_id: 332\nSQL:\n  type: data\n  color: \"#e38c00\"\n  tm_scope: source.sql\n  ace_mode: sql\n  codemirror_mode: sql\n  codemirror_mime_type: text/x-sql\n  extensions:\n  - \".sql\"\n  - \".cql\"\n  - \".ddl\"\n  - \".inc\"\n  - \".mysql\"\n  - \".prc\"\n  - \".tab\"\n  - \".udf\"\n  - \".viw\"\n  language_id: 333\nSQLPL:\n  type: programming\n  color: \"#e38c00\"\n  ace_mode: sql\n  codemirror_mode: sql\n  codemirror_mime_type: text/x-sql\n  tm_scope: source.sql\n  extensions:\n  - \".sql\"\n  - \".db2\"\n  language_id: 334\nSRecode Template:\n  type: markup\n  color: \"#348a34\"\n  tm_scope: source.lisp\n  ace_mode: lisp\n  codemirror_mode: commonlisp\n  codemirror_mime_type: text/x-common-lisp\n  extensions:\n  - \".srt\"\n  language_id: 335\nSSH Config:\n  type: data\n  group: INI\n  filenames:\n  - ssh-config\n  - ssh_config\n  - sshconfig\n  - sshconfig.snip\n  - sshd-config\n  - sshd_config\n  ace_mode: text\n  tm_scope: source.ssh-config\n  language_id: 554920715\nSTAR:\n  type: data\n  extensions:\n  - \".star\"\n  tm_scope: source.star\n  ace_mode: text\n  language_id: 424510560\nSTL:\n  type: data\n  color: \"#373b5e\"\n  aliases:\n  - ascii stl\n  - stla\n  extensions:\n  - \".stl\"\n  tm_scope: source.stl\n  ace_mode: text\n  language_id: 455361735\nSTON:\n  type: data\n  group: Smalltalk\n  extensions:\n  - \".ston\"\n  tm_scope: source.smalltalk\n  ace_mode: text\n  language_id: 336\nSVG:\n  type: data\n  color: \"#ff9900\"\n  extensions:\n  - \".svg\"\n  tm_scope: text.xml.svg\n  ace_mode: xml\n  codemirror_mode: xml\n  codemirror_mime_type: text/xml\n  language_id: 337\nSWIG:\n  type: programming\n  extensions:\n  - \".i\"\n  tm_scope: source.c++\n  ace_mode: c_cpp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-c++src\n  language_id: 1066250075\nSage:\n  type: programming\n  extensions:\n  - \".sage\"\n  - \".sagews\"\n  tm_scope: source.python\n  ace_mode: python\n  codemirror_mode: python\n  codemirror_mime_type: text/x-python\n  language_id: 338\nSaltStack:\n  type: programming\n  color: \"#646464\"\n  aliases:\n  - saltstate\n  - salt\n  extensions:\n  - \".sls\"\n  tm_scope: source.yaml.salt\n  ace_mode: yaml\n  codemirror_mode: yaml\n  codemirror_mime_type: text/x-yaml\n  language_id: 339\nSass:\n  type: markup\n  color: \"#a53b70\"\n  tm_scope: source.sass\n  extensions:\n  - \".sass\"\n  ace_mode: sass\n  codemirror_mode: sass\n  codemirror_mime_type: text/x-sass\n  language_id: 340\nScala:\n  type: programming\n  tm_scope: source.scala\n  ace_mode: scala\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-scala\n  color: \"#c22d40\"\n  extensions:\n  - \".scala\"\n  - \".kojo\"\n  - \".sbt\"\n  - \".sc\"\n  interpreters:\n  - scala\n  language_id: 341\nScaml:\n  type: markup\n  color: \"#bd181a\"\n  extensions:\n  - \".scaml\"\n  tm_scope: source.scaml\n  ace_mode: text\n  language_id: 342\nScenic:\n  type: programming\n  color: \"#fdc700\"\n  extensions:\n  - \".scenic\"\n  tm_scope: source.scenic\n  ace_mode: text\n  interpreters:\n  - scenic\n  language_id: 619814037\nScheme:\n  type: programming\n  color: \"#1e4aec\"\n  extensions:\n  - \".scm\"\n  - \".sch\"\n  - \".sld\"\n  - \".sls\"\n  - \".sps\"\n  - \".ss\"\n  interpreters:\n  - scheme\n  - guile\n  - bigloo\n  - chicken\n  - csi\n  - gosh\n  - r6rs\n  tm_scope: source.scheme\n  ace_mode: scheme\n  codemirror_mode: scheme\n  codemirror_mime_type: text/x-scheme\n  language_id: 343\nScilab:\n  type: programming\n  color: \"#ca0f21\"\n  extensions:\n  - \".sci\"\n  - \".sce\"\n  - \".tst\"\n  tm_scope: source.scilab\n  ace_mode: text\n  language_id: 344\nSelf:\n  type: programming\n  color: \"#0579aa\"\n  extensions:\n  - \".self\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 345\nShaderLab:\n  type: programming\n  color: \"#222c37\"\n  extensions:\n  - \".shader\"\n  ace_mode: text\n  tm_scope: source.shaderlab\n  language_id: 664257356\nShell:\n  type: programming\n  color: \"#89e051\"\n  aliases:\n  - sh\n  - shell-script\n  - bash\n  - zsh\n  extensions:\n  - \".sh\"\n  - \".bash\"\n  - \".bats\"\n  - \".cgi\"\n  - \".command\"\n  - \".env\"\n  - \".fcgi\"\n  - \".ksh\"\n  - \".sh.in\"\n  - \".tmux\"\n  - \".tool\"\n  - \".zsh\"\n  - \".zsh-theme\"\n  filenames:\n  - \".bash_aliases\"\n  - \".bash_history\"\n  - \".bash_logout\"\n  - \".bash_profile\"\n  - \".bashrc\"\n  - \".cshrc\"\n  - \".env\"\n  - \".env.example\"\n  - \".flaskenv\"\n  - \".kshrc\"\n  - \".login\"\n  - \".profile\"\n  - \".zlogin\"\n  - \".zlogout\"\n  - \".zprofile\"\n  - \".zshenv\"\n  - \".zshrc\"\n  - 9fs\n  - PKGBUILD\n  - bash_aliases\n  - bash_logout\n  - bash_profile\n  - bashrc\n  - cshrc\n  - gradlew\n  - kshrc\n  - login\n  - man\n  - profile\n  - zlogin\n  - zlogout\n  - zprofile\n  - zshenv\n  - zshrc\n  interpreters:\n  - ash\n  - bash\n  - dash\n  - ksh\n  - mksh\n  - pdksh\n  - rc\n  - sh\n  - zsh\n  tm_scope: source.shell\n  ace_mode: sh\n  codemirror_mode: shell\n  codemirror_mime_type: text/x-sh\n  language_id: 346\nShellCheck Config:\n  type: data\n  color: \"#cecfcb\"\n  filenames:\n  - \".shellcheckrc\"\n  aliases:\n  - shellcheckrc\n  tm_scope: source.shellcheckrc\n  ace_mode: ini\n  codemirror_mode: properties\n  codemirror_mime_type: text/x-properties\n  language_id: 687511714\nShellSession:\n  type: programming\n  extensions:\n  - \".sh-session\"\n  aliases:\n  - bash session\n  - console\n  tm_scope: text.shell-session\n  ace_mode: sh\n  codemirror_mode: shell\n  codemirror_mime_type: text/x-sh\n  language_id: 347\nShen:\n  type: programming\n  color: \"#120F14\"\n  extensions:\n  - \".shen\"\n  tm_scope: source.shen\n  ace_mode: text\n  language_id: 348\nSieve:\n  type: programming\n  tm_scope: source.sieve\n  ace_mode: text\n  extensions:\n  - \".sieve\"\n  codemirror_mode: sieve\n  codemirror_mime_type: application/sieve\n  language_id: 208976687\nSimple File Verification:\n  type: data\n  group: Checksums\n  color: \"#C9BFED\"\n  extensions:\n  - \".sfv\"\n  aliases:\n  - sfv\n  tm_scope: source.sfv\n  ace_mode: ini\n  codemirror_mode: properties\n  codemirror_mime_type: text/x-properties\n  language_id: 735623761\nSingularity:\n  type: programming\n  color: \"#64E6AD\"\n  tm_scope: source.singularity\n  filenames:\n  - Singularity\n  ace_mode: text\n  language_id: 987024632\nSlash:\n  type: programming\n  color: \"#007eff\"\n  extensions:\n  - \".sl\"\n  tm_scope: text.html.slash\n  ace_mode: text\n  language_id: 349\nSlice:\n  type: programming\n  color: \"#003fa2\"\n  tm_scope: source.slice\n  ace_mode: text\n  extensions:\n  - \".ice\"\n  language_id: 894641667\nSlim:\n  type: markup\n  color: \"#2b2b2b\"\n  extensions:\n  - \".slim\"\n  tm_scope: text.slim\n  ace_mode: text\n  codemirror_mode: slim\n  codemirror_mime_type: text/x-slim\n  language_id: 350\nSmPL:\n  type: programming\n  extensions:\n  - \".cocci\"\n  aliases:\n  - coccinelle\n  ace_mode: text\n  tm_scope: source.smpl\n  color: \"#c94949\"\n  language_id: 164123055\nSmali:\n  type: programming\n  extensions:\n  - \".smali\"\n  ace_mode: text\n  tm_scope: source.smali\n  language_id: 351\nSmalltalk:\n  type: programming\n  color: \"#596706\"\n  extensions:\n  - \".st\"\n  - \".cs\"\n  aliases:\n  - squeak\n  tm_scope: source.smalltalk\n  ace_mode: text\n  codemirror_mode: smalltalk\n  codemirror_mime_type: text/x-stsrc\n  language_id: 352\nSmarty:\n  type: programming\n  color: \"#f0c040\"\n  extensions:\n  - \".tpl\"\n  ace_mode: smarty\n  codemirror_mode: smarty\n  codemirror_mime_type: text/x-smarty\n  tm_scope: text.html.smarty\n  language_id: 353\nSolidity:\n  type: programming\n  color: \"#AA6746\"\n  ace_mode: text\n  tm_scope: source.solidity\n  extensions:\n  - \".sol\"\n  language_id: 237469032\nSoong:\n  type: data\n  tm_scope: source.bp\n  ace_mode: text\n  filenames:\n  - Android.bp\n  language_id: 222900098\nSourcePawn:\n  type: programming\n  color: \"#f69e1d\"\n  aliases:\n  - sourcemod\n  extensions:\n  - \".sp\"\n  - \".inc\"\n  tm_scope: source.sourcepawn\n  ace_mode: text\n  language_id: 354\nSpline Font Database:\n  type: data\n  extensions:\n  - \".sfd\"\n  tm_scope: text.sfd\n  ace_mode: yaml\n  language_id: 767169629\nSquirrel:\n  type: programming\n  color: \"#800000\"\n  extensions:\n  - \".nut\"\n  tm_scope: source.nut\n  ace_mode: c_cpp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-c++src\n  language_id: 355\nStan:\n  type: programming\n  color: \"#b2011d\"\n  extensions:\n  - \".stan\"\n  ace_mode: text\n  tm_scope: source.stan\n  language_id: 356\nStandard ML:\n  type: programming\n  color: \"#dc566d\"\n  aliases:\n  - sml\n  extensions:\n  - \".ml\"\n  - \".fun\"\n  - \".sig\"\n  - \".sml\"\n  tm_scope: source.ml\n  ace_mode: text\n  codemirror_mode: mllike\n  codemirror_mime_type: text/x-ocaml\n  language_id: 357\nStarlark:\n  type: programming\n  tm_scope: source.python\n  ace_mode: python\n  codemirror_mode: python\n  codemirror_mime_type: text/x-python\n  color: \"#76d275\"\n  extensions:\n  - \".bzl\"\n  - \".star\"\n  filenames:\n  - BUCK\n  - BUILD\n  - BUILD.bazel\n  - Tiltfile\n  - WORKSPACE\n  - WORKSPACE.bazel\n  aliases:\n  - bazel\n  - bzl\n  language_id: 960266174\nStata:\n  type: programming\n  color: \"#1a5f91\"\n  extensions:\n  - \".do\"\n  - \".ado\"\n  - \".doh\"\n  - \".ihlp\"\n  - \".mata\"\n  - \".matah\"\n  - \".sthlp\"\n  tm_scope: source.stata\n  ace_mode: text\n  language_id: 358\nStringTemplate:\n  type: markup\n  color: \"#3fb34f\"\n  extensions:\n  - \".st\"\n  tm_scope: source.string-template\n  ace_mode: html\n  codemirror_mode: htmlmixed\n  codemirror_mime_type: text/html\n  language_id: 89855901\nStylus:\n  type: markup\n  color: \"#ff6347\"\n  extensions:\n  - \".styl\"\n  tm_scope: source.stylus\n  ace_mode: stylus\n  language_id: 359\nSubRip Text:\n  type: data\n  color: \"#9e0101\"\n  extensions:\n  - \".srt\"\n  ace_mode: text\n  tm_scope: text.srt\n  language_id: 360\nSugarSS:\n  type: markup\n  color: \"#2fcc9f\"\n  tm_scope: source.css.postcss.sugarss\n  extensions:\n  - \".sss\"\n  ace_mode: text\n  language_id: 826404698\nSuperCollider:\n  type: programming\n  color: \"#46390b\"\n  extensions:\n  - \".sc\"\n  - \".scd\"\n  interpreters:\n  - sclang\n  - scsynth\n  tm_scope: source.supercollider\n  ace_mode: text\n  language_id: 361\nSvelte:\n  type: markup\n  color: \"#ff3e00\"\n  tm_scope: source.svelte\n  ace_mode: html\n  codemirror_mode: htmlmixed\n  codemirror_mime_type: text/html\n  extensions:\n  - \".svelte\"\n  language_id: 928734530\nSwift:\n  type: programming\n  color: \"#F05138\"\n  extensions:\n  - \".swift\"\n  tm_scope: source.swift\n  ace_mode: text\n  codemirror_mode: swift\n  codemirror_mime_type: text/x-swift\n  language_id: 362\nSystemVerilog:\n  type: programming\n  color: \"#DAE1C2\"\n  extensions:\n  - \".sv\"\n  - \".svh\"\n  - \".vh\"\n  tm_scope: source.systemverilog\n  ace_mode: verilog\n  codemirror_mode: verilog\n  codemirror_mime_type: text/x-systemverilog\n  language_id: 363\nTI Program:\n  type: programming\n  ace_mode: text\n  color: \"#A0AA87\"\n  extensions:\n  - \".8xp\"\n  - \".8xk\"\n  - \".8xk.txt\"\n  - \".8xp.txt\"\n  language_id: 422\n  tm_scope: none\nTLA:\n  type: programming\n  color: \"#4b0079\"\n  extensions:\n  - \".tla\"\n  tm_scope: source.tla\n  ace_mode: text\n  language_id: 364\nTOML:\n  type: data\n  color: \"#9c4221\"\n  extensions:\n  - \".toml\"\n  filenames:\n  - Cargo.lock\n  - Gopkg.lock\n  - Pipfile\n  - poetry.lock\n  tm_scope: source.toml\n  ace_mode: toml\n  codemirror_mode: toml\n  codemirror_mime_type: text/x-toml\n  language_id: 365\nTSQL:\n  type: programming\n  color: \"#e38c00\"\n  extensions:\n  - \".sql\"\n  ace_mode: sql\n  tm_scope: source.tsql\n  language_id: 918334941\nTSV:\n  type: data\n  color: \"#237346\"\n  ace_mode: text\n  tm_scope: source.generic-db\n  extensions:\n  - \".tsv\"\n  language_id: 1035892117\nTSX:\n  type: programming\n  color: \"#3178c6\"\n  group: TypeScript\n  extensions:\n  - \".tsx\"\n  tm_scope: source.tsx\n  ace_mode: javascript\n  codemirror_mode: jsx\n  codemirror_mime_type: text/jsx\n  language_id: 94901924\nTXL:\n  type: programming\n  color: \"#0178b8\"\n  extensions:\n  - \".txl\"\n  tm_scope: source.txl\n  ace_mode: text\n  language_id: 366\nTalon:\n  type: programming\n  ace_mode: text\n  color: \"#333333\"\n  extensions:\n  - \".talon\"\n  tm_scope: source.talon\n  language_id: 959889508\nTcl:\n  type: programming\n  color: \"#e4cc98\"\n  extensions:\n  - \".tcl\"\n  - \".adp\"\n  - \".sdc\"\n  - \".tcl.in\"\n  - \".tm\"\n  - \".xdc\"\n  aliases:\n  - sdc\n  - xdc\n  filenames:\n  - owh\n  - starfield\n  interpreters:\n  - tclsh\n  - wish\n  tm_scope: source.tcl\n  ace_mode: tcl\n  codemirror_mode: tcl\n  codemirror_mime_type: text/x-tcl\n  language_id: 367\nTcsh:\n  type: programming\n  group: Shell\n  extensions:\n  - \".tcsh\"\n  - \".csh\"\n  interpreters:\n  - tcsh\n  - csh\n  tm_scope: source.shell\n  ace_mode: sh\n  codemirror_mode: shell\n  codemirror_mime_type: text/x-sh\n  language_id: 368\nTeX:\n  type: markup\n  color: \"#3D6117\"\n  ace_mode: tex\n  codemirror_mode: stex\n  codemirror_mime_type: text/x-stex\n  tm_scope: text.tex.latex\n  wrap: true\n  aliases:\n  - latex\n  extensions:\n  - \".tex\"\n  - \".aux\"\n  - \".bbx\"\n  - \".cbx\"\n  - \".cls\"\n  - \".dtx\"\n  - \".ins\"\n  - \".lbx\"\n  - \".ltx\"\n  - \".mkii\"\n  - \".mkiv\"\n  - \".mkvi\"\n  - \".sty\"\n  - \".toc\"\n  language_id: 369\nTea:\n  type: markup\n  extensions:\n  - \".tea\"\n  tm_scope: source.tea\n  ace_mode: text\n  language_id: 370\nTerra:\n  type: programming\n  extensions:\n  - \".t\"\n  color: \"#00004c\"\n  tm_scope: source.terra\n  ace_mode: lua\n  codemirror_mode: lua\n  codemirror_mime_type: text/x-lua\n  interpreters:\n  - lua\n  language_id: 371\nTexinfo:\n  type: prose\n  wrap: true\n  extensions:\n  - \".texinfo\"\n  - \".texi\"\n  - \".txi\"\n  ace_mode: text\n  tm_scope: text.texinfo\n  interpreters:\n  - makeinfo\n  language_id: 988020015\nText:\n  type: prose\n  wrap: true\n  aliases:\n  - fundamental\n  - plain text\n  extensions:\n  - \".txt\"\n  - \".fr\"\n  - \".nb\"\n  - \".ncl\"\n  - \".no\"\n  filenames:\n  - CITATION\n  - CITATIONS\n  - COPYING\n  - COPYING.regex\n  - COPYRIGHT.regex\n  - FONTLOG\n  - INSTALL\n  - INSTALL.mysql\n  - LICENSE\n  - LICENSE.mysql\n  - NEWS\n  - README.me\n  - README.mysql\n  - README.nss\n  - click.me\n  - delete.me\n  - keep.me\n  - package.mask\n  - package.use.mask\n  - package.use.stable.mask\n  - read.me\n  - readme.1st\n  - test.me\n  - use.mask\n  - use.stable.mask\n  tm_scope: none\n  ace_mode: text\n  language_id: 372\nTextMate Properties:\n  type: data\n  color: \"#df66e4\"\n  aliases:\n  - tm-properties\n  filenames:\n  - \".tm_properties\"\n  ace_mode: properties\n  codemirror_mode: properties\n  codemirror_mime_type: text/x-properties\n  tm_scope: source.tm-properties\n  language_id: 981795023\nTextile:\n  type: prose\n  color: \"#ffe7ac\"\n  ace_mode: textile\n  codemirror_mode: textile\n  codemirror_mime_type: text/x-textile\n  wrap: true\n  extensions:\n  - \".textile\"\n  tm_scope: none\n  language_id: 373\nThrift:\n  type: programming\n  color: \"#D12127\"\n  tm_scope: source.thrift\n  extensions:\n  - \".thrift\"\n  ace_mode: text\n  language_id: 374\nTuring:\n  type: programming\n  color: \"#cf142b\"\n  extensions:\n  - \".t\"\n  - \".tu\"\n  tm_scope: source.turing\n  ace_mode: text\n  language_id: 375\nTurtle:\n  type: data\n  extensions:\n  - \".ttl\"\n  tm_scope: source.turtle\n  ace_mode: text\n  codemirror_mode: turtle\n  codemirror_mime_type: text/turtle\n  language_id: 376\nTwig:\n  type: markup\n  color: \"#c1d026\"\n  extensions:\n  - \".twig\"\n  tm_scope: text.html.twig\n  ace_mode: twig\n  codemirror_mode: twig\n  codemirror_mime_type: text/x-twig\n  language_id: 377\nType Language:\n  type: data\n  aliases:\n  - tl\n  extensions:\n  - \".tl\"\n  tm_scope: source.tl\n  ace_mode: text\n  language_id: 632765617\nTypeScript:\n  type: programming\n  color: \"#3178c6\"\n  aliases:\n  - ts\n  interpreters:\n  - deno\n  - ts-node\n  extensions:\n  - \".ts\"\n  - \".cts\"\n  - \".mts\"\n  tm_scope: source.ts\n  ace_mode: typescript\n  codemirror_mode: javascript\n  codemirror_mime_type: application/typescript\n  language_id: 378\nUnified Parallel C:\n  type: programming\n  color: \"#4e3617\"\n  group: C\n  ace_mode: c_cpp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-csrc\n  extensions:\n  - \".upc\"\n  tm_scope: source.c\n  language_id: 379\nUnity3D Asset:\n  type: data\n  color: \"#222c37\"\n  ace_mode: yaml\n  codemirror_mode: yaml\n  codemirror_mime_type: text/x-yaml\n  extensions:\n  - \".anim\"\n  - \".asset\"\n  - \".mask\"\n  - \".mat\"\n  - \".meta\"\n  - \".prefab\"\n  - \".unity\"\n  tm_scope: source.yaml\n  language_id: 380\nUnix Assembly:\n  type: programming\n  group: Assembly\n  extensions:\n  - \".s\"\n  - \".ms\"\n  aliases:\n  - gas\n  - gnu asm\n  - unix asm\n  tm_scope: source.x86\n  ace_mode: assembly_x86\n  language_id: 120\nUno:\n  type: programming\n  color: \"#9933cc\"\n  extensions:\n  - \".uno\"\n  ace_mode: csharp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-csharp\n  tm_scope: source.cs\n  language_id: 381\nUnrealScript:\n  type: programming\n  color: \"#a54c4d\"\n  extensions:\n  - \".uc\"\n  tm_scope: source.java\n  ace_mode: java\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-java\n  language_id: 382\nUrWeb:\n  type: programming\n  color: \"#ccccee\"\n  aliases:\n  - Ur/Web\n  - Ur\n  extensions:\n  - \".ur\"\n  - \".urs\"\n  tm_scope: source.ur\n  ace_mode: text\n  language_id: 383\nV:\n  type: programming\n  color: \"#4f87c4\"\n  aliases:\n  - vlang\n  extensions:\n  - \".v\"\n  tm_scope: source.v\n  ace_mode: golang\n  codemirror_mode: go\n  codemirror_mime_type: text/x-go\n  language_id: 603371597\nVBA:\n  type: programming\n  color: \"#867db1\"\n  extensions:\n  - \".bas\"\n  - \".cls\"\n  - \".frm\"\n  - \".frx\"\n  - \".vba\"\n  tm_scope: source.vbnet\n  aliases:\n  - visual basic for applications\n  ace_mode: text\n  codemirror_mode: vb\n  codemirror_mime_type: text/x-vb\n  language_id: 399230729\nVBScript:\n  type: programming\n  color: \"#15dcdc\"\n  extensions:\n  - \".vbs\"\n  tm_scope: source.vbnet\n  ace_mode: text\n  codemirror_mode: vbscript\n  codemirror_mime_type: text/vbscript\n  language_id: 408016005\nVCL:\n  type: programming\n  color: \"#148AA8\"\n  extensions:\n  - \".vcl\"\n  tm_scope: source.varnish.vcl\n  ace_mode: text\n  language_id: 384\nVHDL:\n  type: programming\n  color: \"#adb2cb\"\n  extensions:\n  - \".vhdl\"\n  - \".vhd\"\n  - \".vhf\"\n  - \".vhi\"\n  - \".vho\"\n  - \".vhs\"\n  - \".vht\"\n  - \".vhw\"\n  tm_scope: source.vhdl\n  ace_mode: vhdl\n  codemirror_mode: vhdl\n  codemirror_mime_type: text/x-vhdl\n  language_id: 385\nVala:\n  type: programming\n  color: \"#a56de2\"\n  extensions:\n  - \".vala\"\n  - \".vapi\"\n  tm_scope: source.vala\n  ace_mode: vala\n  language_id: 386\nValve Data Format:\n  type: data\n  color: \"#f26025\"\n  aliases:\n  - keyvalues\n  - vdf\n  extensions:\n  - \".vdf\"\n  ace_mode: text\n  tm_scope: source.keyvalues\n  language_id: 544060961\nVelocity Template Language:\n  type: markup\n  color: \"#507cff\"\n  aliases:\n  - vtl\n  - velocity\n  extensions:\n  - \".vtl\"\n  ace_mode: velocity\n  tm_scope: source.velocity\n  codemirror_mode: velocity\n  codemirror_mime_type: text/velocity\n  language_id: 292377326\nVerilog:\n  type: programming\n  color: \"#b2b7f8\"\n  extensions:\n  - \".v\"\n  - \".veo\"\n  tm_scope: source.verilog\n  ace_mode: verilog\n  codemirror_mode: verilog\n  codemirror_mime_type: text/x-verilog\n  language_id: 387\nVim Help File:\n  type: prose\n  color: \"#199f4b\"\n  aliases:\n  - help\n  - vimhelp\n  extensions:\n  - \".txt\"\n  tm_scope: text.vim-help\n  ace_mode: text\n  language_id: 508563686\nVim Script:\n  type: programming\n  color: \"#199f4b\"\n  tm_scope: source.viml\n  aliases:\n  - vim\n  - viml\n  - nvim\n  extensions:\n  - \".vim\"\n  - \".vba\"\n  - \".vimrc\"\n  - \".vmb\"\n  filenames:\n  - \".exrc\"\n  - \".gvimrc\"\n  - \".nvimrc\"\n  - \".vimrc\"\n  - _vimrc\n  - gvimrc\n  - nvimrc\n  - vimrc\n  ace_mode: text\n  language_id: 388\nVim Snippet:\n  type: markup\n  color: \"#199f4b\"\n  aliases:\n  - SnipMate\n  - UltiSnip\n  - UltiSnips\n  - NeoSnippet\n  extensions:\n  - \".snip\"\n  - \".snippet\"\n  - \".snippets\"\n  tm_scope: source.vim-snippet\n  ace_mode: text\n  language_id: 81265970\nVisual Basic .NET:\n  type: programming\n  color: \"#945db7\"\n  extensions:\n  - \".vb\"\n  - \".vbhtml\"\n  aliases:\n  - visual basic\n  - vbnet\n  - vb .net\n  - vb.net\n  tm_scope: source.vbnet\n  ace_mode: text\n  codemirror_mode: vb\n  codemirror_mime_type: text/x-vb\n  language_id: 389\nVisual Basic 6.0:\n  type: programming\n  color: \"#2c6353\"\n  extensions:\n  - \".ctl\"\n  - \".Dsr\"\n  tm_scope: source.vbnet\n  aliases:\n  - vb6\n  - vb 6\n  - visual basic 6\n  - visual basic classic\n  - classic visual basic\n  ace_mode: text\n  codemirror_mode: vb\n  codemirror_mime_type: text/x-vb\n  language_id: 679594952\nVolt:\n  type: programming\n  color: \"#1F1F1F\"\n  extensions:\n  - \".volt\"\n  tm_scope: source.d\n  ace_mode: d\n  codemirror_mode: d\n  codemirror_mime_type: text/x-d\n  language_id: 390\nVue:\n  type: markup\n  color: \"#41b883\"\n  extensions:\n  - \".vue\"\n  tm_scope: text.html.vue\n  ace_mode: html\n  language_id: 391\nVyper:\n  type: programming\n  extensions:\n  - \".vy\"\n  color: \"#2980b9\"\n  ace_mode: text\n  tm_scope: source.vyper\n  language_id: 1055641948\nWavefront Material:\n  type: data\n  extensions:\n  - \".mtl\"\n  tm_scope: source.wavefront.mtl\n  ace_mode: text\n  language_id: 392\nWavefront Object:\n  type: data\n  extensions:\n  - \".obj\"\n  tm_scope: source.wavefront.obj\n  ace_mode: text\n  language_id: 393\nWeb Ontology Language:\n  type: data\n  color: \"#5b70bd\"\n  extensions:\n  - \".owl\"\n  tm_scope: text.xml\n  ace_mode: xml\n  language_id: 394\nWebAssembly:\n  type: programming\n  color: \"#04133b\"\n  extensions:\n  - \".wast\"\n  - \".wat\"\n  aliases:\n  - wast\n  - wasm\n  tm_scope: source.webassembly\n  ace_mode: lisp\n  codemirror_mode: commonlisp\n  codemirror_mime_type: text/x-common-lisp\n  language_id: 956556503\nWebIDL:\n  type: programming\n  extensions:\n  - \".webidl\"\n  tm_scope: source.webidl\n  ace_mode: text\n  codemirror_mode: webidl\n  codemirror_mime_type: text/x-webidl\n  language_id: 395\nWebVTT:\n  type: data\n  wrap: true\n  aliases:\n  - vtt\n  extensions:\n  - \".vtt\"\n  tm_scope: text.vtt\n  ace_mode: text\n  language_id: 658679714\nWget Config:\n  type: data\n  group: INI\n  aliases:\n  - wgetrc\n  filenames:\n  - \".wgetrc\"\n  tm_scope: source.wgetrc\n  ace_mode: text\n  language_id: 668457123\nWhiley:\n  type: programming\n  color: \"#d5c397\"\n  extensions:\n  - \".whiley\"\n  tm_scope: source.whiley\n  ace_mode: text\n  language_id: 888779559\nWikitext:\n  type: prose\n  color: \"#fc5757\"\n  wrap: true\n  aliases:\n  - mediawiki\n  - wiki\n  extensions:\n  - \".mediawiki\"\n  - \".wiki\"\n  - \".wikitext\"\n  tm_scope: text.html.mediawiki\n  ace_mode: text\n  language_id: 228\nWin32 Message File:\n  type: data\n  extensions:\n  - \".mc\"\n  tm_scope: source.win32-messages\n  ace_mode: ini\n  codemirror_mode: properties\n  codemirror_mime_type: text/x-properties\n  language_id: 950967261\nWindows Registry Entries:\n  type: data\n  color: \"#52d5ff\"\n  extensions:\n  - \".reg\"\n  tm_scope: source.reg\n  ace_mode: ini\n  codemirror_mode: properties\n  codemirror_mime_type: text/x-properties\n  language_id: 969674868\nWitcher Script:\n  type: programming\n  color: \"#ff0000\"\n  extensions:\n  - \".ws\"\n  ace_mode: text\n  tm_scope: source.witcherscript\n  language_id: 686821385\nWollok:\n  type: programming\n  color: \"#a23738\"\n  extensions:\n  - \".wlk\"\n  ace_mode: text\n  tm_scope: source.wollok\n  language_id: 632745969\nWorld of Warcraft Addon Data:\n  type: data\n  color: \"#f7e43f\"\n  extensions:\n  - \".toc\"\n  tm_scope: source.toc\n  ace_mode: text\n  language_id: 396\nWren:\n  type: programming\n  color: \"#383838\"\n  aliases:\n  - wrenlang\n  extensions:\n  - \".wren\"\n  tm_scope: source.wren\n  ace_mode: text\n  language_id: 713580619\nX BitMap:\n  type: data\n  group: C\n  aliases:\n  - xbm\n  extensions:\n  - \".xbm\"\n  ace_mode: c_cpp\n  tm_scope: source.c\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-csrc\n  language_id: 782911107\nX Font Directory Index:\n  type: data\n  filenames:\n  - encodings.dir\n  - fonts.alias\n  - fonts.dir\n  - fonts.scale\n  tm_scope: source.fontdir\n  ace_mode: text\n  language_id: 208700028\nX PixMap:\n  type: data\n  group: C\n  aliases:\n  - xpm\n  extensions:\n  - \".xpm\"\n  - \".pm\"\n  ace_mode: c_cpp\n  tm_scope: source.c\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-csrc\n  language_id: 781846279\nX10:\n  type: programming\n  aliases:\n  - xten\n  ace_mode: text\n  extensions:\n  - \".x10\"\n  color: \"#4B6BEF\"\n  tm_scope: source.x10\n  language_id: 397\nXC:\n  type: programming\n  color: \"#99DA07\"\n  extensions:\n  - \".xc\"\n  tm_scope: source.xc\n  ace_mode: c_cpp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-csrc\n  language_id: 398\nXCompose:\n  type: data\n  filenames:\n  - \".XCompose\"\n  - XCompose\n  - xcompose\n  tm_scope: config.xcompose\n  ace_mode: text\n  language_id: 225167241\nXML:\n  type: data\n  color: \"#0060ac\"\n  tm_scope: text.xml\n  ace_mode: xml\n  codemirror_mode: xml\n  codemirror_mime_type: text/xml\n  aliases:\n  - rss\n  - xsd\n  - wsdl\n  extensions:\n  - \".xml\"\n  - \".adml\"\n  - \".admx\"\n  - \".ant\"\n  - \".axaml\"\n  - \".axml\"\n  - \".builds\"\n  - \".ccproj\"\n  - \".ccxml\"\n  - \".clixml\"\n  - \".cproject\"\n  - \".cscfg\"\n  - \".csdef\"\n  - \".csl\"\n  - \".csproj\"\n  - \".ct\"\n  - \".depproj\"\n  - \".dita\"\n  - \".ditamap\"\n  - \".ditaval\"\n  - \".dll.config\"\n  - \".dotsettings\"\n  - \".filters\"\n  - \".fsproj\"\n  - \".fxml\"\n  - \".glade\"\n  - \".gml\"\n  - \".gmx\"\n  - \".grxml\"\n  - \".gst\"\n  - \".hzp\"\n  - \".iml\"\n  - \".ivy\"\n  - \".jelly\"\n  - \".jsproj\"\n  - \".kml\"\n  - \".launch\"\n  - \".mdpolicy\"\n  - \".mjml\"\n  - \".mm\"\n  - \".mod\"\n  - \".mxml\"\n  - \".natvis\"\n  - \".ncl\"\n  - \".ndproj\"\n  - \".nproj\"\n  - \".nuspec\"\n  - \".odd\"\n  - \".osm\"\n  - \".pkgproj\"\n  - \".pluginspec\"\n  - \".proj\"\n  - \".props\"\n  - \".ps1xml\"\n  - \".psc1\"\n  - \".pt\"\n  - \".qhelp\"\n  - \".rdf\"\n  - \".res\"\n  - \".resx\"\n  - \".rs\"\n  - \".rss\"\n  - \".sch\"\n  - \".scxml\"\n  - \".sfproj\"\n  - \".shproj\"\n  - \".srdf\"\n  - \".storyboard\"\n  - \".sublime-snippet\"\n  - \".targets\"\n  - \".tml\"\n  - \".ts\"\n  - \".tsx\"\n  - \".ui\"\n  - \".urdf\"\n  - \".ux\"\n  - \".vbproj\"\n  - \".vcxproj\"\n  - \".vsixmanifest\"\n  - \".vssettings\"\n  - \".vstemplate\"\n  - \".vxml\"\n  - \".wixproj\"\n  - \".workflow\"\n  - \".wsdl\"\n  - \".wsf\"\n  - \".wxi\"\n  - \".wxl\"\n  - \".wxs\"\n  - \".x3d\"\n  - \".xacro\"\n  - \".xaml\"\n  - \".xib\"\n  - \".xlf\"\n  - \".xliff\"\n  - \".xmi\"\n  - \".xml.dist\"\n  - \".xmp\"\n  - \".xproj\"\n  - \".xsd\"\n  - \".xspec\"\n  - \".xul\"\n  - \".zcml\"\n  filenames:\n  - \".classpath\"\n  - \".cproject\"\n  - \".project\"\n  - App.config\n  - NuGet.config\n  - Settings.StyleCop\n  - Web.Debug.config\n  - Web.Release.config\n  - Web.config\n  - packages.config\n  language_id: 399\nXML Property List:\n  type: data\n  color: \"#0060ac\"\n  group: XML\n  extensions:\n  - \".plist\"\n  - \".stTheme\"\n  - \".tmCommand\"\n  - \".tmLanguage\"\n  - \".tmPreferences\"\n  - \".tmSnippet\"\n  - \".tmTheme\"\n  tm_scope: text.xml.plist\n  ace_mode: xml\n  codemirror_mode: xml\n  codemirror_mime_type: text/xml\n  language_id: 75622871\nXPages:\n  type: data\n  extensions:\n  - \".xsp-config\"\n  - \".xsp.metadata\"\n  tm_scope: text.xml\n  ace_mode: xml\n  codemirror_mode: xml\n  codemirror_mime_type: text/xml\n  language_id: 400\nXProc:\n  type: programming\n  extensions:\n  - \".xpl\"\n  - \".xproc\"\n  tm_scope: text.xml\n  ace_mode: xml\n  codemirror_mode: xml\n  codemirror_mime_type: text/xml\n  language_id: 401\nXQuery:\n  type: programming\n  color: \"#5232e7\"\n  extensions:\n  - \".xquery\"\n  - \".xq\"\n  - \".xql\"\n  - \".xqm\"\n  - \".xqy\"\n  ace_mode: xquery\n  codemirror_mode: xquery\n  codemirror_mime_type: application/xquery\n  tm_scope: source.xq\n  language_id: 402\nXS:\n  type: programming\n  extensions:\n  - \".xs\"\n  tm_scope: source.c\n  ace_mode: c_cpp\n  codemirror_mode: clike\n  codemirror_mime_type: text/x-csrc\n  language_id: 403\nXSLT:\n  type: programming\n  aliases:\n  - xsl\n  extensions:\n  - \".xslt\"\n  - \".xsl\"\n  tm_scope: text.xml.xsl\n  ace_mode: xml\n  codemirror_mode: xml\n  codemirror_mime_type: text/xml\n  color: \"#EB8CEB\"\n  language_id: 404\nXojo:\n  type: programming\n  color: \"#81bd41\"\n  extensions:\n  - \".xojo_code\"\n  - \".xojo_menu\"\n  - \".xojo_report\"\n  - \".xojo_script\"\n  - \".xojo_toolbar\"\n  - \".xojo_window\"\n  tm_scope: source.xojo\n  ace_mode: text\n  language_id: 405\nXonsh:\n  type: programming\n  color: \"#285EEF\"\n  extensions:\n  - \".xsh\"\n  tm_scope: source.python\n  ace_mode: text\n  codemirror_mode: python\n  codemirror_mime_type: text/x-python\n  language_id: 614078284\nXtend:\n  type: programming\n  color: \"#24255d\"\n  extensions:\n  - \".xtend\"\n  tm_scope: source.xtend\n  ace_mode: text\n  language_id: 406\nYAML:\n  type: data\n  color: \"#cb171e\"\n  tm_scope: source.yaml\n  aliases:\n  - yml\n  extensions:\n  - \".yml\"\n  - \".mir\"\n  - \".reek\"\n  - \".rviz\"\n  - \".sublime-syntax\"\n  - \".syntax\"\n  - \".yaml\"\n  - \".yaml-tmlanguage\"\n  - \".yaml.sed\"\n  - \".yml.mysql\"\n  filenames:\n  - \".clang-format\"\n  - \".clang-tidy\"\n  - \".gemrc\"\n  - CITATION.cff\n  - glide.lock\n  - yarn.lock\n  ace_mode: yaml\n  codemirror_mode: yaml\n  codemirror_mime_type: text/x-yaml\n  language_id: 407\nYANG:\n  type: data\n  extensions:\n  - \".yang\"\n  tm_scope: source.yang\n  ace_mode: text\n  language_id: 408\nYARA:\n  type: programming\n  color: \"#220000\"\n  ace_mode: text\n  extensions:\n  - \".yar\"\n  - \".yara\"\n  tm_scope: source.yara\n  language_id: 805122868\nYASnippet:\n  type: markup\n  aliases:\n  - snippet\n  - yas\n  color: \"#32AB90\"\n  extensions:\n  - \".yasnippet\"\n  tm_scope: source.yasnippet\n  ace_mode: text\n  language_id: 378760102\nYacc:\n  type: programming\n  extensions:\n  - \".y\"\n  - \".yacc\"\n  - \".yy\"\n  tm_scope: source.yacc\n  ace_mode: text\n  color: \"#4B6C4B\"\n  language_id: 409\nYul:\n  type: programming\n  color: \"#794932\"\n  ace_mode: text\n  tm_scope: source.yul\n  extensions:\n  - \".yul\"\n  language_id: 237469033\nZAP:\n  type: programming\n  color: \"#0d665e\"\n  extensions:\n  - \".zap\"\n  - \".xzap\"\n  tm_scope: source.zap\n  ace_mode: text\n  language_id: 952972794\nZIL:\n  type: programming\n  color: \"#dc75e5\"\n  extensions:\n  - \".zil\"\n  - \".mud\"\n  tm_scope: source.zil\n  ace_mode: text\n  language_id: 973483626\nZeek:\n  type: programming\n  aliases:\n  - bro\n  extensions:\n  - \".zeek\"\n  - \".bro\"\n  tm_scope: source.zeek\n  ace_mode: text\n  language_id: 40\nZenScript:\n  type: programming\n  color: \"#00BCD1\"\n  extensions:\n  - \".zs\"\n  tm_scope: source.zenscript\n  ace_mode: text\n  language_id: 494938890\nZephir:\n  type: programming\n  color: \"#118f9e\"\n  extensions:\n  - \".zep\"\n  tm_scope: source.php.zephir\n  ace_mode: php\n  language_id: 410\nZig:\n  type: programming\n  color: \"#ec915c\"\n  extensions:\n  - \".zig\"\n  tm_scope: source.zig\n  ace_mode: text\n  language_id: 646424281\nZimpl:\n  type: programming\n  color: \"#d67711\"\n  extensions:\n  - \".zimpl\"\n  - \".zmpl\"\n  - \".zpl\"\n  tm_scope: none\n  ace_mode: text\n  language_id: 411\ncURL Config:\n  type: data\n  group: INI\n  aliases:\n  - curlrc\n  filenames:\n  - \".curlrc\"\n  - _curlrc\n  tm_scope: source.curlrc\n  ace_mode: text\n  language_id: 992375436\ndesktop:\n  type: data\n  extensions:\n  - \".desktop\"\n  - \".desktop.in\"\n  - \".service\"\n  tm_scope: source.desktop\n  ace_mode: text\n  language_id: 412\ndircolors:\n  type: data\n  extensions:\n  - \".dircolors\"\n  filenames:\n  - \".dir_colors\"\n  - \".dircolors\"\n  - DIR_COLORS\n  - _dir_colors\n  - _dircolors\n  - dir_colors\n  tm_scope: source.dircolors\n  ace_mode: text\n  language_id: 691605112\neC:\n  type: programming\n  color: \"#913960\"\n  extensions:\n  - \".ec\"\n  - \".eh\"\n  tm_scope: source.c.ec\n  ace_mode: text\n  language_id: 413\nedn:\n  type: data\n  ace_mode: clojure\n  codemirror_mode: clojure\n  codemirror_mime_type: text/x-clojure\n  extensions:\n  - \".edn\"\n  tm_scope: source.clojure\n  language_id: 414\nfish:\n  type: programming\n  color: \"#4aae47\"\n  group: Shell\n  interpreters:\n  - fish\n  extensions:\n  - \".fish\"\n  tm_scope: source.fish\n  ace_mode: text\n  language_id: 415\nhoon:\n  type: programming\n  color: \"#00b171\"\n  tm_scope: source.hoon\n  ace_mode: text\n  extensions:\n  - \".hoon\"\n  language_id: 560883276\njq:\n  color: \"#c7254e\"\n  ace_mode: text\n  type: programming\n  extensions:\n  - \".jq\"\n  tm_scope: source.jq\n  language_id: 905371884\njust:\n  type: programming\n  aliases:\n  - Justfile\n  color: \"#384d54\"\n  tm_scope: source.just\n  filenames:\n  - Justfile\n  ace_mode: text\n  language_id: 128447695\nkvlang:\n  type: markup\n  ace_mode: text\n  extensions:\n  - \".kv\"\n  color: \"#1da6e0\"\n  tm_scope: source.python.kivy\n  language_id: 970675279\nmIRC Script:\n  type: programming\n  color: \"#3d57c3\"\n  extensions:\n  - \".mrc\"\n  tm_scope: source.msl\n  ace_mode: text\n  language_id: 517654727\nmcfunction:\n  type: programming\n  color: \"#E22837\"\n  extensions:\n  - \".mcfunction\"\n  tm_scope: source.mcfunction\n  ace_mode: text\n  language_id: 462488745\nmupad:\n  type: programming\n  color: \"#244963\"\n  extensions:\n  - \".mu\"\n  tm_scope: source.mupad\n  ace_mode: text\n  language_id: 416\nnanorc:\n  type: data\n  color: \"#2d004d\"\n  group: INI\n  extensions:\n  - \".nanorc\"\n  filenames:\n  - \".nanorc\"\n  - nanorc\n  tm_scope: source.nanorc\n  ace_mode: text\n  language_id: 775996197\nnesC:\n  type: programming\n  color: \"#94B0C7\"\n  extensions:\n  - \".nc\"\n  ace_mode: text\n  tm_scope: source.nesc\n  language_id: 417\nooc:\n  type: programming\n  color: \"#b0b77e\"\n  extensions:\n  - \".ooc\"\n  tm_scope: source.ooc\n  ace_mode: text\n  language_id: 418\nq:\n  type: programming\n  extensions:\n  - \".q\"\n  tm_scope: source.q\n  ace_mode: text\n  color: \"#0040cd\"\n  language_id: 970539067\nreStructuredText:\n  type: prose\n  color: \"#141414\"\n  wrap: true\n  aliases:\n  - rst\n  extensions:\n  - \".rst\"\n  - \".rest\"\n  - \".rest.txt\"\n  - \".rst.txt\"\n  tm_scope: text.restructuredtext\n  ace_mode: text\n  codemirror_mode: rst\n  codemirror_mime_type: text/x-rst\n  language_id: 419\nrobots.txt:\n  type: data\n  aliases:\n  - robots\n  - robots txt\n  filenames:\n  - robots.txt\n  ace_mode: text\n  tm_scope: text.robots-txt\n  language_id: 674736065\nsed:\n  type: programming\n  color: \"#64b970\"\n  extensions:\n  - \".sed\"\n  interpreters:\n  - gsed\n  - minised\n  - sed\n  - ssed\n  ace_mode: text\n  tm_scope: source.sed\n  language_id: 847830017\nwdl:\n  type: programming\n  color: \"#42f1f4\"\n  extensions:\n  - \".wdl\"\n  tm_scope: source.wdl\n  ace_mode: text\n  language_id: 374521672\nwisp:\n  type: programming\n  ace_mode: clojure\n  codemirror_mode: clojure\n  codemirror_mime_type: text/x-clojure\n  color: \"#7582D1\"\n  extensions:\n  - \".wisp\"\n  tm_scope: source.clojure\n  language_id: 420\nxBase:\n  type: programming\n  color: \"#403a40\"\n  aliases:\n  - advpl\n  - clipper\n  - foxpro\n  extensions:\n  - \".prg\"\n  - \".ch\"\n  - \".prw\"\n  tm_scope: source.harbour\n  ace_mode: text\n  language_id: 421\n"
  },
  {
    "path": "tests/.example.env",
    "content": "SCAN_FOLDER=/Users/...\nGITHUB_USER=test\nGITHUB_PASSWORD=123\n"
  },
  {
    "path": "tests/.gitignore",
    "content": ".env\n"
  },
  {
    "path": "tests/all_onboarding.spec.js_",
    "content": "import { test, expect } from '@playwright/test';\n\nconst REPOS_TO_SYNC = 1;\n\nif (!process.env.SCAN_FOLDER) {\n  throw new Error('SCAN_FOLDER env not set');\n}\n\ntest.describe.serial('Main test group', () => {\n  let page; // Share page context between tests\n\n  test.beforeAll(async ({ browser }) => {\n    const context = await browser.newContext();\n    page = await context.newPage();\n    await page.goto(\n      'http://localhost:5173/?chosen_scan_folder=${process.env.SCAN_FOLDER}',\n    );\n  });\n\n  const repoNames = [];\n\n  test.afterAll(async ({ browser }) => {\n    await browser.close();\n  });\n\n  test('Local and GitHub onboarding', async () => {\n    await page.goto(\n      `http://localhost:5173/?chosen_scan_folder=${process.env.SCAN_FOLDER}`,\n    );\n    await page.getByRole('button', { name: \"Don't share\" }).click();\n    await page.getByPlaceholder('First name').click();\n    await page.getByPlaceholder('First name').fill('Steve');\n    await page.getByPlaceholder('First name').press('Tab');\n    await page.getByRole('button').first().press('Tab');\n    await page.getByPlaceholder('Last name').fill('Wozniak');\n    await page.getByPlaceholder('Email address').click();\n    await page.getByPlaceholder('Email address').fill('steve@bloop.ai');\n    await page.locator('form').getByRole('button').nth(2).click();\n    await page.getByPlaceholder('Email address').fill('steve.w@bloop.ai');\n    await page.getByRole('button', { name: 'Submit' }).click();\n\n    // Local\n\n    await page.getByRole('button', { name: 'Choose a folder' }).click();\n    await page.getByRole('button', { name: 'Sync selected repos' }).click();\n\n    await page.waitForSelector('.bg-skeleton', {\n      state: 'detached',\n      timeout: 60 * 1000,\n    });\n\n    await page\n      .locator('label')\n      .filter({ hasText: 'Select all' })\n      .getByRole('checkbox')\n      .click();\n\n    for (let i = 1; i <= REPOS_TO_SYNC; i++) {\n      const repo = page.locator(`ul > :nth-match(li, ${i})`);\n      repoNames.push(await repo.locator('span').innerText());\n      await repo.click();\n    }\n\n    await page.getByRole('button', { name: 'Sync repositories' }).click();\n\n    // GitHub login\n\n    await expect(page.locator('.subhead-l > span')).toBeVisible();\n    await page.getByRole('button').first().click();\n    const [page1] = await Promise.all([\n      page.waitForEvent('popup'),\n      page.getByRole('button', { name: 'Connect GitHub' }).click(),\n    ]);\n    await page1\n      .getByLabel('Username or email address')\n      .fill(process.env.GITHUB_USER);\n    await page1.getByLabel('Password').click();\n    await page1.getByLabel('Password').fill(process.env.GITHUB_PASSWORD);\n    await page1.getByRole('button', { name: 'Sign in' }).click();\n\n    const githubAuthCode = await page\n      .locator('.subhead-l > span')\n      .first()\n      .innerText();\n\n    for (var i = 0; i < githubAuthCode.length; i++) {\n      if (i === 4) continue;\n      await page1.locator(`#user-code-${i}`).fill(githubAuthCode[i]);\n    }\n\n    await page1.getByRole('button', { name: 'Continue' }).click();\n    await page1.getByRole('button', { name: 'Authorize BloopAI' }).click();\n    await page1.close();\n\n    await page.getByRole('button', { name: 'Sync selected repos' }).click();\n\n    await page.waitForSelector('.bg-skeleton', {\n      state: 'detached',\n      timeout: 60 * 1000,\n    });\n\n    await page\n      .locator('label')\n      .filter({ hasText: 'Select all' })\n      .getByRole('checkbox')\n      .click();\n\n    for (let i = 1; i <= REPOS_TO_SYNC; i++) {\n      const repo = page.locator(`ul > :nth-match(li, ${i})`);\n      repoNames.push(await repo.locator('span').innerText());\n      await repo.click();\n    }\n\n    await page.getByRole('button', { name: 'Sync repositories' }).click();\n\n    await Promise.all(\n      repoNames.map((repoName) =>\n        page.waitForSelector(`p:has-text(\"${repoName}\")`, {\n          state: 'attached',\n          timeout: 60 * 1000,\n        }),\n      ),\n    );\n\n    await Promise.all(\n      repoNames.map((repoName, i) =>\n        page\n          .locator('.bg-green-500')\n          .nth(i)\n          .waitFor({ timeout: 60 * 1000 }),\n      ),\n    );\n\n    await page.evaluate(() => {\n      console.log('local storage');\n      for (i = 0; i < localStorage.length; i++) {\n        console.log(\n          localStorage.key(i) +\n            '=[' +\n            localStorage.getItem(localStorage.key(i)) +\n            ']',\n        );\n      }\n    });\n  });\n\n  test('Code search', async () => {\n    test.setTimeout(5 * 1000);\n    await page.locator('a').first().click();\n    await page.getByPlaceholder('My search').click();\n    await page.getByPlaceholder('My search').fill('test');\n    await page.getByPlaceholder('My search').press('Enter');\n    await expect(page.getByText(/Showing \\d+ results?/)).toBeVisible();\n  });\n\n  test('Symbol search', async () => {\n    test.setTimeout(5 * 1000);\n    await page.locator('a').first().click();\n    await page.getByPlaceholder('My search').click();\n    await page.getByPlaceholder('My search').fill('symbol:test');\n    await page.getByPlaceholder('My search').press('Enter');\n    await expect(page.getByText(/Showing \\d+ results?/)).toBeVisible();\n  });\n\n  test('Repo search', async () => {\n    test.setTimeout(repoNames.length * 5 * 1000);\n    for (let repoName of repoNames) {\n      await page.locator('a').first().click();\n      await page.getByPlaceholder('My search').click();\n      await page.getByPlaceholder('My search').fill(`repo:${repoName}`);\n      await page.getByPlaceholder('My search').press('Enter');\n      await expect(page.getByText(/Showing \\d+ results?/)).toBeVisible();\n    }\n  });\n\n  test('Path search', async () => {\n    test.setTimeout(5 * 1000);\n    await page.locator('a').first().click();\n    await page.getByPlaceholder('My search').click();\n    await page.getByPlaceholder('My search').fill('path:src');\n    await page.getByPlaceholder('My search').press('Enter');\n    await expect(page.getByText(/Showing \\d+ results?/)).toBeVisible();\n  });\n});\n"
  },
  {
    "path": "tests/github_onboarding.spec.js_",
    "content": "import { test, expect } from '@playwright/test';\n\nconst REPOS_TO_SYNC = 3;\n\ntest('test', async ({ page }) => {\n  await page.goto(`http://localhost:5173`);\n  await page.getByRole('button', { name: \"Don't share\" }).click();\n  await page.getByRole('button', { name: 'Skip this step' }).click();\n  await page.getByRole('button', { name: 'Skip this step' }).click();\n  await expect(page.locator('.subhead-l > span')).toBeVisible();\n  await page.getByRole('button').first().click();\n  const [page1] = await Promise.all([\n    page.waitForEvent('popup'),\n    page.getByRole('button', { name: 'Connect GitHub' }).click(),\n  ]);\n  await page1\n    .getByLabel('Username or email address')\n    .fill(process.env.GITHUB_USER);\n  await page1.getByLabel('Username or email address').press('Tab');\n  await page1.getByLabel('Password').fill(process.env.GITHUB_PASSWORD);\n  await page1.getByRole('button', { name: 'Sign in' }).click();\n\n  const githubAuthCode = await page\n    .locator('.subhead-l > span')\n    .first()\n    .innerText();\n\n  for (var i = 0; i < githubAuthCode.length; i++) {\n    if (i === 4) continue;\n    await page1.locator(`#user-code-${i}`).fill(githubAuthCode[i]);\n  }\n\n  await page1.getByRole('button', { name: 'Continue' }).click();\n  await page1.getByRole('button', { name: 'Authorize BloopAI' }).click();\n  await page1.close();\n\n  await page.getByRole('button', { name: 'Sync selected repos' }).click();\n\n  await page.waitForSelector('.bg-skeleton', {\n    state: 'detached',\n    timeout: 60 * 1000,\n  });\n\n  await page\n    .locator('label')\n    .filter({ hasText: 'Select all' })\n    .getByRole('checkbox')\n    .click();\n\n  // Store repo names to check status later\n  const repoNames = [];\n\n  for (let i = 1; i <= REPOS_TO_SYNC; i++) {\n    const repo = page.locator(`ul > :nth-match(li, ${i})`);\n    repoNames.push(await repo.locator('span').innerText());\n    await repo.click();\n  }\n\n  await page.getByRole('button', { name: 'Sync repositories' }).click();\n\n  await Promise.all(\n    repoNames.map((repoName) =>\n      page.waitForSelector(`p:has-text(\"${repoName}\")`, {\n        state: 'attached',\n        timeout: 60 * 1000,\n      }),\n    ),\n  );\n\n  await Promise.all(\n    repoNames.map((repoName, i) =>\n      page\n        .locator('.bg-green-500')\n        .nth(i)\n        .waitFor({ timeout: 60 * 1000 }),\n    ),\n  );\n});\n"
  },
  {
    "path": "tests/local_onboarding.spec.js_",
    "content": "import { test, expect } from '@playwright/test';\n\nconst REPOS_TO_SYNC = 3;\n\ntest.skip('test', async ({ page }) => {\n  if (!process.env.SCAN_FOLDER) {\n    throw new Error('SCAN_FOLDER env not set');\n  }\n\n  await page.goto(\n    `http://localhost:5173/?chosen_scan_folder=${process.env.SCAN_FOLDER}`,\n  );\n  await page.getByRole('button', { name: \"Don't share\" }).click();\n  await page.getByPlaceholder('First name').click();\n  await page.getByPlaceholder('First name').fill('Steve');\n  await page.getByPlaceholder('First name').press('Tab');\n  await page.getByRole('button').first().press('Tab');\n  await page.getByPlaceholder('Last name').fill('Wozniak');\n  await page.getByPlaceholder('Email address').click();\n  await page.getByPlaceholder('Email address').fill('steve@bloop.ai');\n  await page.locator('form').getByRole('button').nth(2).click();\n  await page.getByPlaceholder('Email address').fill('steve.w@bloop.ai');\n  await page.getByRole('button', { name: 'Submit' }).click();\n  await page.getByRole('button', { name: 'Choose a folder' }).click();\n  await page.getByRole('button', { name: 'Sync selected repos' }).click();\n\n  await page.waitForSelector('.bg-skeleton', {\n    state: 'detached',\n    timeout: 60 * 1000,\n  });\n\n  await page\n    .locator('label')\n    .filter({ hasText: 'Select all' })\n    .getByRole('checkbox')\n    .click();\n\n  // Store repo names to check status later\n  const repoNames = [];\n\n  for (let i = 1; i <= REPOS_TO_SYNC; i++) {\n    const repo = page.locator(`ul > :nth-match(li, ${i})`);\n    repoNames.push(await repo.locator('span').innerText());\n    await repo.click();\n  }\n\n  await page.getByRole('button', { name: 'Sync repositories' }).click();\n  await page.getByRole('button', { name: 'Setup later' }).click();\n\n  await Promise.all(\n    repoNames.map((repoName) =>\n      page.waitForSelector(`p:has-text(\"${repoName}\")`, {\n        state: 'attached',\n        timeout: 60 * 1000,\n      }),\n    ),\n  );\n\n  await Promise.all(\n    repoNames.map((repoName, i) =>\n      page\n        .locator('.bg-green-500')\n        .nth(i)\n        .waitFor({ timeout: 60 * 1000 }),\n    ),\n  );\n});\n"
  },
  {
    "path": "tests/onboarding.spec.ts",
    "content": "import { test } from '@playwright/test';\nimport { runOnboarding } from './onboarding';\n\ntest.describe.serial('OnBoarding', () => {\n  let page; // Share page context between tests\n  let context;\n\n  test.beforeAll(async ({ browser }) => {\n    context = await browser.newContext();\n    page = await context.newPage();\n    await page.goto(\n      `http://localhost:5173/?chosen_scan_folder=${process.env.SCAN_FOLDER}`,\n    );\n  });\n\n  test.afterAll(async ({ browser }) => {\n    await browser.close();\n  });\n\n  test('Local and GitHub', async () => {\n    await runOnboarding(page, context);\n  });\n});\n"
  },
  {
    "path": "tests/onboarding.ts",
    "content": "import { expect } from '@playwright/test';\n\nconst REPOS_TO_SYNC = 1;\n\nexport const runOnboarding = async (page, context) => {\n  const repoNames = [];\n\n  await page.getByPlaceholder('First name').click();\n  await page.getByPlaceholder('First name').fill('Steve');\n  await page.getByPlaceholder('First name').press('Tab');\n  await page.getByRole('button').first().press('Tab');\n  await page.getByPlaceholder('Last name', { exact: true }).fill('Wozniak');\n  await page.getByPlaceholder('Email address', { exact: true }).click();\n  await page\n    .getByPlaceholder('Email address', { exact: true })\n    .fill('steve@bloop.ai');\n  await page.locator('form').getByRole('button').nth(2).click();\n  await page\n    .getByPlaceholder('Email address', { exact: true })\n    .fill('steve.w@bloop.ai');\n  await page.getByRole('button', { name: 'Submit' }).click();\n\n  await page.getByRole('button', { name: 'Get Started' }).click();\n\n  await page.getByText('Opt-out of remote services').click();\n  await page.getByRole('button', { name: 'Confirm' }).click();\n  await page.getByRole('button', { name: 'Change selection' }).click();\n  await page.getByText('Opt-in to remote services').click();\n  await page.getByRole('button', { name: 'Confirm' }).click();\n\n  // GitHub login\n\n  await new Promise((res) => setTimeout(() => res(1), 1500));\n  if (\n    (await page.isVisible(\"text='Connect GitHub'\")) &&\n    !(await page.isVisible(\"text='Sync GitHub repositories'\"))\n  ) {\n    await expect(page.locator('.subhead-l > span')).toBeVisible();\n    // await page.getByRole('button').first().click();\n    const page1 = await context.newPage();\n    await page1.goto('https://github.com/login/device');\n    await page1\n      .getByLabel('Username or email address')\n      .fill(process.env.GITHUB_USER);\n    await page1.getByLabel('Password').click();\n    await page1.getByLabel('Password').fill(process.env.GITHUB_PASSWORD);\n    await page1.getByRole('button', { name: 'Sign in' }).click();\n\n    const githubAuthCode = await page\n      .locator('.subhead-l > span')\n      .first()\n      .innerText();\n\n    for (var i = 0; i < githubAuthCode.length; i++) {\n      if (i === 4) continue;\n      await page1.locator(`#user-code-${i}`).fill(githubAuthCode[i]);\n    }\n\n    await page1.getByRole('button', { name: 'Continue' }).click();\n    await page1.getByRole('button', { name: 'Authorize BloopAI' }).click();\n    await page1.close();\n  }\n\n  await page.getByRole('button', { name: 'Sync selected repos' }).click();\n\n  await page.waitForSelector('.bg-skeleton', {\n    state: 'detached',\n    timeout: 60 * 1000,\n  });\n\n  for (let i = 1; i <= REPOS_TO_SYNC; i++) {\n    const repo = page.locator(`ul > :nth-match(li, ${i})`);\n    repoNames.push(await repo.locator('span').innerText());\n    await repo.click();\n  }\n\n  await page.getByRole('button', { name: 'Sync repositories' }).click();\n\n  // Local\n\n  await page.getByRole('button', { name: 'Choose a folder' }).click();\n  await page.getByRole('button', { name: 'Sync selected repos' }).click();\n\n  await page.waitForSelector('.bg-skeleton', {\n    state: 'detached',\n    timeout: 60 * 1000,\n  });\n\n  for (let i = 1; i <= REPOS_TO_SYNC; i++) {\n    const repo = page.locator(`ul > :nth-match(li, ${i})`);\n    repoNames.push(await repo.locator('span').innerText());\n    await repo.click();\n  }\n\n  await page.getByRole('button', { name: 'Sync repositories' }).click();\n\n  await Promise.all(\n    repoNames.map((repoName) =>\n      page.waitForSelector(`p:has-text(\"${repoName}\")`, {\n        state: 'attached',\n        timeout: 60 * 1000,\n      }),\n    ),\n  );\n\n  await Promise.all(\n    repoNames.map((repoName, i) =>\n      page\n        .locator('.bg-green-500')\n        .nth(i)\n        .waitFor({ timeout: 60 * 1000 }),\n    ),\n  );\n};\n"
  },
  {
    "path": "tests/repository.spec.ts",
    "content": "import * as console from 'console';\nimport { test, expect, Page } from '@playwright/test';\n\ntest.describe('Repository navigation', () => {\n  let page: Page;\n\n  test.beforeAll(async ({ browser }) => {\n    const context = await browser.newContext();\n    page = await context.newPage();\n    await page.goto(\n      `http://localhost:5173/?chosen_scan_folder=${process.env.SCAN_FOLDER}`,\n    );\n    await page.getByRole('button', { name: \"Don't share\" }).click();\n    await page.getByRole('button', { name: 'Skip this step' }).click();\n    await page.getByRole('button', { name: 'Choose a folder' }).click();\n    await page.waitForSelector('.bg-skeleton', {\n      state: 'detached',\n      timeout: 60 * 1000,\n    });\n\n    await page\n      .locator('label')\n      .filter({ hasText: 'Select all' })\n      .getByRole('checkbox')\n      .click();\n\n    await page.getByRole('button', { name: 'Sync repositories' }).click();\n    await page.getByRole('button', { name: 'Setup later' }).click();\n  });\n\n  test.beforeEach(async () => {\n    await page.goto('http://localhost:5173/');\n  });\n\n  test('Navigation folder', async () => {\n    const repoName = await page\n      .locator('div.flex.items-start.gap-4 > p')\n      .first()\n      .innerText();\n    await page.locator('p.cursor-pointer.break-all').first().click();\n    await expect(\n      page.locator('div.flex.flex-col.gap-4 > div > h4').first(),\n    ).toHaveText(`Files in ${repoName}`);\n\n    const folderName = await page\n      .locator('span.flex.flex-row.justify-between.px-4.py-4.bg-gray-900')\n      .first()\n      .innerText();\n\n    await page\n      .locator('span.flex.flex-row.justify-between.px-4.py-4.bg-gray-900')\n      .first()\n      .click();\n\n    const lastBreadcrumbs = await page\n      .locator('span.flex.items-center.gap-1.flex-shrink-0 > a > span')\n      .last()\n      .innerText();\n\n    await expect(folderName.trim()).toEqual(lastBreadcrumbs.trim());\n  });\n  test('Navigation file', async () => {\n    const repoName = await page\n      .locator('div.flex.items-start.gap-4 > p')\n      .first()\n      .innerText();\n    await page.locator('p.cursor-pointer.break-all').first().click();\n    await expect(\n      page.locator('div.flex.flex-col.gap-4 > div > h4').first(),\n    ).toHaveText(`Files in ${repoName}`);\n\n    const fileName = await page\n      .locator('span.flex.flex-row.justify-between.px-4.py-4.bg-gray-900')\n      .first()\n      .innerText();\n    await page\n      .locator('span.flex.flex-row.justify-between.px-4.py-4.bg-gray-900')\n      .first()\n      .click();\n\n    const openedFile = await page\n      .locator('span.flex.items-center.gap-1.flex-shrink-0 > a > span')\n      .nth(1)\n      .innerText();\n\n    await expect(openedFile.trim()).toEqual(fileName.trim());\n  });\n});\n"
  },
  {
    "path": "tests/search.spec.ts",
    "content": "import { expect, Page, test } from '@playwright/test';\nimport { runOnboarding } from './onboarding';\n\ntest.describe.serial('Search', () => {\n  let page: Page;\n\n  test.beforeAll(async ({ browser }) => {\n    const context = await browser.newContext();\n    page = await context.newPage();\n    await page.goto(\n      `http://localhost:5173/?chosen_scan_folder=${process.env.SCAN_FOLDER}`,\n    );\n    await runOnboarding(page, context);\n  });\n\n  test.beforeEach(async () => {\n    await page.goto('http://localhost:5173/');\n  });\n\n  test('NL search', async () => {\n    await page.getByPlaceholder('My search').click();\n    await page.getByPlaceholder('My search').fill('hello');\n    await page.getByPlaceholder('My search').press('Enter');\n\n    await expect(\n      page.locator('li > div > div:nth-child(2)').first(),\n    ).toBeVisible({ timeout: 15000 });\n  });\n\n  test('Regex search snippet more matches', async () => {\n    await page.getByTitle('Search type').click();\n    await page.getByText('Regex').click();\n    await page.getByPlaceholder('My search').click();\n    await page.getByPlaceholder('My search').fill('const');\n    await page.getByPlaceholder('My search').press('Enter');\n    const bbCollapsed = await (\n      await page.locator('li > div > div:nth-child(2)').first()\n    ).boundingBox();\n    const bbCollapsedHeight = bbCollapsed.height;\n\n    await page\n      .getByRole('button', { name: /Show\\s\\d*\\smore\\smatch/ })\n      .first()\n      .click();\n    const bb = await (\n      await page.locator('li > div > div:nth-child(2)').first()\n    ).boundingBox();\n    expect(bbCollapsedHeight).not.toEqual(bb.height);\n  });\n\n  test('Regex search pagination', async () => {\n    await page.getByTitle('Search type').click();\n    await page.getByText('Regex').click();\n    await page.getByPlaceholder('My search').click();\n    await page.getByPlaceholder('My search').fill('a');\n    await page.getByPlaceholder('My search').press('Enter');\n    const firstItem = await page\n      .locator('.flex.items-center.body-s.flex-shrink-0.gap-1')\n      .first()\n      .innerText();\n\n    await page.locator('.mt-8 > div > div > button:nth-child(3)').click();\n\n    await new Promise((res) => setTimeout(() => res(1), 1000));\n\n    const secondPageItem = await page\n      .locator('.flex.items-center.body-s.flex-shrink-0.gap-1')\n      .first()\n      .innerText();\n\n    await expect(firstItem).not.toMatch(secondPageItem);\n  });\n\n  test('Code search result navigation', async () => {\n    await page.getByTitle('Search type').click();\n    await page.getByText('Regex').click();\n    await page.getByPlaceholder('My search').click();\n    await page.getByPlaceholder('My search').fill('a');\n    await page.getByPlaceholder('My search').press('Enter');\n\n    const firstItem = await page.locator('li > div > div > div').first();\n\n    await page.locator('li > div > div:nth-child(2)').first().click();\n\n    await expect(\n      page.getByRole('button', { name: 'Open in modal' }),\n    ).toBeVisible();\n\n    await expect(\n      page\n        .locator('div:nth-child(4) > div:nth-child(2) > div > div > div')\n        .first()\n        .innerText(),\n    ).toEqual(firstItem.innerText());\n\n    await page.getByRole('button', { name: 'Open in modal' }).click();\n\n    await expect(\n      page.getByRole('button', { name: 'Open in modal' }),\n    ).toHaveClass(/text-sky-500/);\n\n    await page.getByRole('button', { name: 'Open in full view' }).click();\n    await expect(\n      page\n        .locator(\n          '.text-gray-200 > div:nth-child(2) > div:nth-child(2) > div > div',\n        )\n        .first()\n        .innerText(),\n    ).toEqual(firstItem.innerText());\n    await expect(page.locator('.overflow-scroll > div')).toBeVisible();\n  });\n\n  test('Autocomplete', async () => {\n    await page.getByTitle('Search type').click();\n    await page.getByText('Regex').click();\n    await page.getByPlaceholder('My search').click();\n    await page.getByPlaceholder('My search').fill('a');\n\n    const itemsCount = await page.locator('#downshift-1-menu').count();\n    await page.getByRole('button', { name: 'View all results' }).click();\n    await expect(page.locator('#downshift-1-menu').count()).not.toEqual(\n      itemsCount,\n    );\n    const path = await page\n      .locator('#downshift-1-item-0 div > div > div')\n      .first()\n      .innerText();\n    await page.locator('#downshift-1-item-0').click();\n\n    await page.waitForSelector(\n      '.text-gray-200 > div:nth-child(2) > div:nth-child(2) > div > div > div',\n      {\n        state: 'attached',\n        timeout: 60 * 1000,\n      },\n    );\n    const currPath = await page\n      .locator(\n        '.text-gray-200 > div:nth-child(2) > div:nth-child(2) > div > div > div',\n      )\n      .first()\n      .innerText();\n\n    await expect(currPath).toEqual(path);\n  });\n\n  test('Code filters search', async () => {\n    await page.getByTitle('Search type').click();\n    await page.getByText('Regex').click();\n    await page.getByPlaceholder('My search').click();\n    await page.getByPlaceholder('My search').fill('a');\n    await page.getByPlaceholder('My search').press('Enter');\n\n    await expect(\n      page.locator('li > div > div:nth-child(2)').first(),\n    ).toBeVisible();\n\n    const filterCheckBox = await page\n      .locator('label')\n      .nth(1)\n      .getByRole('checkbox');\n\n    await filterCheckBox.click();\n    await page.getByRole('button', { name: 'Apply filters' }).click();\n\n    await expect(\n      page.locator('li > div > div:nth-child(2)').first(),\n    ).toBeVisible();\n\n    const inputValue = await page.getByPlaceholder('My search').innerText();\n    await expect(\n      inputValue.includes(await filterCheckBox.innerText()),\n    ).toBeTruthy();\n  });\n\n  test('Symbol search', async () => {\n    await page.getByTitle('Search type').click();\n    await page.getByText('Regex').click();\n    await page.getByPlaceholder('My search').click();\n    await page.getByPlaceholder('My search').fill('symbol:a');\n    await page.getByPlaceholder('My search').press('Enter');\n\n    await expect(\n      page.locator('li > div > div:nth-child(2)').first(),\n    ).toBeVisible();\n    await expect(page.locator('div > .w-4 > svg').first()).toBeVisible();\n    await page.locator('.text-gray-400 > button').first().click();\n\n    await expect(\n      page.locator('li > div > div:nth-child(2)').first(),\n    ).toHaveClass(\n      'bg-gray-900 text-gray-600 text-xs  border-gray-700 py-4 cursor-pointer w-full overflow-auto',\n    );\n  });\n\n  test('Repo search', async () => {\n    await page.getByTitle('Search type').click();\n    await page.getByText('Regex').click();\n    await page.getByPlaceholder('My search').click();\n    await page.getByPlaceholder('My search').fill('repo:a');\n    await page.getByPlaceholder('My search').press('Enter');\n\n    const repoItem = await page\n      .getByRole('list')\n      .locator('div > span > span')\n      .nth(1);\n    const repoName = await repoItem.innerText();\n\n    await expect(repoItem).toBeVisible();\n    await page.getByRole('list').locator('div > span > span').first().click();\n\n    await expect(page.getByRole('heading').first()).toHaveText(\n      `Files in ${repoName}`,\n    );\n  });\n\n  // TODO: Fix path search\n  test.skip('Path search', async () => {\n    await page.getByTitle('Search type').click();\n    await page.getByText('Regex').click();\n    await page.getByPlaceholder('My search').click();\n    await page.getByPlaceholder('My search').fill('path:index');\n    await page.getByPlaceholder('My search').press('Enter');\n    // check path results\n    await page.locator('span:nth-child(6) > .flex').first().click();\n    // check opened file\n  });\n\n  test('Repo navigate from search', async () => {\n    const repoName = await page\n      .locator('div.flex.items-start.gap-4 > p')\n      .first()\n      .innerText();\n    await page.locator('div.flex.items-start.gap-4').first().click();\n\n    await expect(\n      page.locator('div.flex.flex-col.gap-4 > div > h4').first(),\n    ).toHaveText(new RegExp(`Files in (github.com/)?${repoName}`));\n  });\n\n  test('File navigate from search', async () => {\n    let repoName = await page\n      .locator('div.flex.items-start.gap-4 > p')\n      .first()\n      .innerText();\n\n    await page.getByText(repoName).first().click();\n\n    repoName = (\n      await page\n        .locator('div.flex.flex-col.gap-4 > div > h4')\n        .first()\n        .innerText()\n    ).slice(9);\n\n    const fileName = await page\n      .locator(\n        '.flex.flex-row.justify-between.px-4.py-4.bg-gray-900.group.cursor-pointer',\n      )\n      .last()\n      .innerText();\n\n    await page.getByTitle('Search type').click();\n    await page.getByText('Regex').click();\n    await page.getByPlaceholder('My search').click();\n    await page\n      .getByPlaceholder('My search')\n      .fill(`open:true repo:${repoName} path:${fileName}`);\n    await page.getByPlaceholder('My search').press('Enter');\n\n    const currName = await page\n      .locator('span > button > span.whitespace-nowrap')\n      .nth(1)\n      .innerText();\n\n    await expect(currName.trim()).toMatch(fileName.trim());\n  });\n});\n"
  },
  {
    "path": "tests/settings.spec.ts",
    "content": "import { test, expect } from '@playwright/test';\n\ntest.describe('Settings panel', () => {\n  let page;\n\n  test.beforeAll(async ({ browser }) => {\n    const context = await browser.newContext();\n    page = await context.newPage();\n    await page.goto(\n      `http://localhost:5173/?chosen_scan_folder=${process.env.SCAN_FOLDER}`,\n    );\n  });\n\n  test('Settings panel', async ({ page }) => {\n    await page.getByRole('button', { name: \"Don't share\" }).click();\n    await page.getByRole('button', { name: 'Skip this step' }).click();\n    await page.getByRole('button', { name: 'Skip this step' }).click();\n    await page.getByRole('button', { name: 'Setup later' }).click();\n    await page.locator('#dropdownDefault').nth(1).click();\n    await page.getByText('Settings').click();\n    await page.locator('input[name=\"firstName\"]').click();\n    await page.locator('input[name=\"firstName\"]').fill('John');\n    await page.getByPlaceholder('Knight-Webb').click();\n    await page.getByPlaceholder('Knight-Webb').fill('Doe');\n    await page\n      .locator('form div')\n      .filter({\n        hasText: 'EmailUsed to sign in, syncing and product updatesEmail',\n      })\n      .locator('div')\n      .nth(4)\n      .click();\n    await page.getByPlaceholder('louis@bloop.ai').fill('test@test.com');\n    await page.getByRole('button', { name: 'Save changes' }).click();\n    await page.getByText('Preferences').click();\n    await page\n      .locator('span')\n      .filter({ hasText: 'Repositories' })\n      .nth(1)\n      .click();\n    await page\n      .locator('div')\n      .filter({\n        hasText:\n          'GeneralPreferencesRepositoriesRepositoriesAdd repositoriesConnect a GitHub accou',\n      })\n      .nth(1)\n      .click();\n    await page.locator('#dropdown span').first().click();\n    await page.getByRole('button', { name: 'Choose a folder' }).click();\n    await page\n      .locator('label')\n      .filter({ hasText: 'Select all' })\n      .getByRole('checkbox')\n      .click();\n    await page\n      .locator('label')\n      .filter({ hasText: 'Select all' })\n      .getByRole('checkbox')\n      .click();\n    await page.getByRole('button', { name: 'Sync repositories' }).click();\n  });\n});\n"
  },
  {
    "path": "tests/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\", // This must be specified if \"paths\" is.\n    \"lib\": [ \"es2015\" ],\n    \"types\": [\"node\"],\n    \"include\": [\n      \"./\"\n    ]\n  }\n}\n"
  }
]