[
  {
    "path": ".cargo/config.toml",
    "content": "[env]\nMACOSX_DEPLOYMENT_TARGET  = \"10.12\"\nJEMALLOC_SYS_WITH_LG_PAGE = \"16\"\n\n# environment variable for tikv-jemalloc-sys\n#\n# https://jemalloc.net/jemalloc.3.html#opt.narenas\n# narenas is the maximum number of arenas to use for automatic multiplexing\n# of threads and arenas. The default is four times the number of CPUs,\n# or one if there is a single CPU.\n#\n# Improve memory efficiency by reducing fragmentation and ensuring all threads allocate from the same pool\nJEMALLOC_SYS_WITH_MALLOC_CONF = \"narenas:1\"\n\n[target.aarch64-apple-darwin]\nrustflags = [ \"-Ctarget-cpu=apple-m1\" ]\n"
  },
  {
    "path": ".envrc",
    "content": "use flake"
  },
  {
    "path": ".github/DISCUSSION_TEMPLATE/1-q-a.yml",
    "content": "body:\n  - type: dropdown\n    id: os\n    attributes:\n      label: What system are you running Yazi on?\n      options:\n        - Linux X11\n        - Linux Wayland\n        - macOS\n        - Windows\n        - Windows WSL\n        - FreeBSD X11\n        - FreeBSD Wayland\n        - Android\n    validations:\n      required: true\n  - type: input\n    id: terminal\n    attributes:\n      label: What terminal are you running Yazi in?\n      placeholder: \"ex: kitty v0.32.2\"\n    validations:\n      required: true\n  - type: textarea\n    id: debug\n    attributes:\n      label: \"`yazi --debug` output\"\n      description: Please run `yazi --debug` and paste the debug information here.\n      render: Shell\n    validations:\n      required: true\n  - type: textarea\n    id: description\n    attributes:\n      label: Describe the question\n      description: A clear and concise description of what the question is\n      placeholder: Tell us what you want to know\n    validations:\n      required: true\n  - type: textarea\n    id: other\n    attributes:\n      label: Anything else?\n      description: |\n        Add any other context about the problem here.  You can attach screenshots by clicking\n        this area to highlight it and then drag the files in.\n  - type: checkboxes\n    id: checklist\n    attributes:\n      label: Checklist\n      description: Before submitting the post, please make sure you have completed the following\n      options:\n        - label: I have read all the documentation\n          required: true\n        - label: I have searched the existing discussions/issues\n          required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug.yml",
    "content": "name: 🐞 Bug Report\ndescription: Create a report to help us improve\nlabels: [bug]\nassignees: []\nbody:\n  - type: dropdown\n    id: os\n    attributes:\n      label: What system are you running Yazi on?\n      options:\n        - Linux X11\n        - Linux Wayland\n        - macOS\n        - Windows\n        - Windows WSL\n        - FreeBSD X11\n        - FreeBSD Wayland\n        - Android\n    validations:\n      required: true\n  - type: input\n    id: terminal\n    attributes:\n      label: What terminal are you running Yazi in?\n      placeholder: \"ex: kitty v0.32.2\"\n    validations:\n      required: true\n  - type: textarea\n    id: debug\n    attributes:\n      label: \"`yazi --debug` output\"\n      description: Please run `yazi --debug` and paste the debug information here.\n      render: Shell\n    validations:\n      required: true\n  - type: textarea\n    id: description\n    attributes:\n      label: Describe the bug\n      description: A clear and concise description of what the bug is\n      placeholder: Tell us what happened\n    validations:\n      required: true\n  - type: textarea\n    id: reproducer\n    attributes:\n      label: Minimal reproducer\n      description: A [minimal reproducer](https://stackoverflow.com/help/minimal-reproducible-example) is required, otherwise the issue might be closed without further notice.\n      placeholder: |\n        Please include as much information as possible that can help to reproduce and understand the issue.\n    validations:\n      required: true\n  - type: textarea\n    id: other\n    attributes:\n      label: Anything else?\n      description: |\n        Add any other context about the problem here.  You can attach screenshots by clicking\n        this area to highlight it and then drag the files in.\n  - type: checkboxes\n    id: checklist\n    attributes:\n      label: Checklist\n      description: Before submitting the issue, please make sure you have completed the following\n      options:\n        - label: I tried the [latest nightly build](https://yazi-rs.github.io/docs/installation#binaries), and the issue is still reproducible\n          required: true\n        - label: I updated the debug information (`yazi --debug`) input box to the nightly that I tried\n          required: true\n        - label: I can reproduce it after disabling all custom configs/plugins (`mv ~/.config/yazi ~/.config/yazi-backup`)\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: 🚧 Build Issues\n    url: https://github.com/sxyazi/yazi/discussions/new?category=3-build-issues\n    about: If you have issues building Yazi from source code\n  - name: 📝 Documentation Improvement\n    url: https://github.com/yazi-rs/yazi-rs.github.io\n    about: If you'd like to help improve the documentation\n  - name: 💬 GitHub Discussions\n    url: https://github.com/sxyazi/yazi/discussions/new?category=1-q-a\n    about: When you have questions that are not bug reports or feature requests\n  - name: 🌐 Discord Server / Telegram Group\n    url: https://github.com/sxyazi/yazi#discussion\n    about: If you'd prefer more realtime conversation with the community\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature.yml",
    "content": "name: 💡 Feature Request\ndescription: Suggest an idea for this project\nlabels: [feature]\nassignees: []\nbody:\n  - type: textarea\n    id: debug\n    attributes:\n      label: \"`yazi --debug` output\"\n      description: Please run `yazi --debug` and paste the debug information here.\n      render: Shell\n    validations:\n      required: true\n  - type: textarea\n    id: problem\n    attributes:\n      label: Please describe the problem you're trying to solve\n      description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n    validations:\n      required: true\n  - type: checkboxes\n    id: contribute\n    attributes:\n      label: Would you be willing to contribute this feature?\n      description: The feature has a much higher chance of completion if you are willing to get involved!\n      options:\n        - label: Yes, I'll give it a shot\n  - type: textarea\n    id: solution\n    attributes:\n      label: Describe the solution you'd like\n      description: A clear and concise description of what you want to happen.\n    validations:\n      required: true\n  - type: textarea\n    id: context\n    attributes:\n      label: Additional context\n      description: Add any other context or screenshots about the feature request here.\n  - type: checkboxes\n    id: checklist\n    attributes:\n      label: Checklist\n      description: Before submitting the issue, please make sure you have completed the following\n      options:\n        - label: I have searched the existing issues/discussions\n          required: true\n        - label: The [latest nightly build](https://yazi-rs.github.io/docs/installation/#binaries) doesn't already have this feature\n          required: true\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    commit-message:\n      prefix: \"ci\"\n\n  - package-ecosystem: \"npm\"\n    directory: \"/scripts/validate-form\"\n    schedule:\n      interval: \"weekly\"\n    commit-message:\n      prefix: \"ci\"\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "## Which issue does this PR resolve?\n\n<!--\nFor any fixes and enhancements, we usually require an associated issue to be filed where clearly detailed your proposed changes and why they are necessary, which ensures we are aligned and reduces the risk of re-work.\n\nYou can use GitHub syntax to link an issue to this PR, such as `Resolves #1000`, which indicates this PR will resolve issue #1000.\n-->\n\nResolves #\n\n## Rationale of this PR\n\n<!--\nA clear and concise description of the rationale of the changes, to help our reviewers understand your intent and why it is necessary.\n\nIf it has already been detailed in the associated issue, please skip this section.\n-->\n"
  },
  {
    "path": ".github/workflows/cachix.yml",
    "content": "name: Cachix\n\non:\n  push:\n    branches: [main]\n\npermissions:\n  contents: read\n\njobs:\n  publish:\n    name: Publish Flake\n    strategy:\n      matrix:\n        os: [ubuntu-latest, macos-latest]\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Install Nix\n        uses: cachix/install-nix-action@v31\n\n      - name: Authenticate with Cachix\n        uses: cachix/cachix-action@v16\n        with:\n          name: yazi\n          authToken: \"${{ secrets.CACHIX_AUTH_TOKEN }}\"\n\n      - name: Build Flake\n        run: nix build -L\n"
  },
  {
    "path": ".github/workflows/check.yml",
    "content": "name: Check\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\nenv:\n  SCCACHE_GHA_ENABLED: true\n  RUSTC_WRAPPER: sccache\n\npermissions:\n  contents: read\n\njobs:\n  clippy:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Setup Rust toolchain\n        run: |\n          rustup toolchain install stable --profile minimal\n          rustup component add clippy\n\n      - name: Setup sccache\n        uses: mozilla-actions/sccache-action@v0.0.9\n\n      - name: Clippy\n        run: cargo clippy --all\n\n  rustfmt:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Setup Rust toolchain\n        run: |\n          rustup toolchain install nightly --profile minimal\n          rustup component add rustfmt --toolchain nightly\n\n      - name: Setup sccache\n        uses: mozilla-actions/sccache-action@v0.0.9\n\n      - name: Rustfmt\n        run: rustfmt +nightly --check **/*.rs\n\n  stylua:\n    runs-on: ubuntu-22.04\n    steps:\n      - uses: actions/checkout@v6\n      - uses: JohnnyMorganz/stylua-action@v4\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          version: latest\n          args: --color always --check .\n"
  },
  {
    "path": ".github/workflows/draft.yml",
    "content": "name: Draft\n\non:\n  push:\n    branches: [main]\n    tags: [\"v[0-9]+.[0-9]+.[0-9]+\"]\n  workflow_dispatch:\n\nenv:\n  SCCACHE_GHA_ENABLED: true\n\npermissions:\n  contents: read\n\njobs:\n  build-unix:\n    strategy:\n      matrix:\n        include:\n          - os: ubuntu-latest\n            target: x86_64-unknown-linux-gnu\n          - os: ubuntu-latest\n            target: aarch64-unknown-linux-gnu\n            gcc: gcc-aarch64-linux-gnu\n          - os: ubuntu-latest\n            target: i686-unknown-linux-gnu\n            gcc: gcc-i686-linux-gnu\n          - os: ubuntu-latest\n            target: riscv64gc-unknown-linux-gnu\n            gcc: gcc-riscv64-linux-gnu\n          - os: ubuntu-latest\n            target: sparc64-unknown-linux-gnu\n            gcc: gcc-sparc64-linux-gnu\n          - os: macos-latest\n            target: x86_64-apple-darwin\n          - os: macos-latest\n            target: aarch64-apple-darwin\n    runs-on: ${{ matrix.os }}\n    env:\n      CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc\n      CARGO_TARGET_I686_UNKNOWN_LINUX_GNU_LINKER: i686-linux-gnu-gcc\n      CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER: riscv64-linux-gnu-gcc\n      CARGO_TARGET_SPARC64_UNKNOWN_LINUX_GNU_LINKER: sparc64-linux-gnu-gcc\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Install gcc\n        if: matrix.gcc != ''\n        run: sudo apt update && sudo apt install -yq ${{ matrix.gcc }}\n\n      - uses: dtolnay/rust-toolchain@stable\n        with:\n          targets: ${{ matrix.target }}\n\n      - name: Setup sccache\n        uses: mozilla-actions/sccache-action@v0.0.9\n\n      - name: Build\n        run: ./scripts/build.sh ${{ matrix.target }}\n\n      - name: Upload artifact\n        uses: actions/upload-artifact@v7\n        with:\n          name: ${{ matrix.target }}\n          path: |\n            yazi-${{ matrix.target }}.zip\n            yazi-${{ matrix.target }}.deb\n\n  build-windows:\n    strategy:\n      matrix:\n        include:\n          - os: windows-latest\n            target: x86_64-pc-windows-msvc\n          - os: windows-latest\n            target: aarch64-pc-windows-msvc\n    runs-on: ${{ matrix.os }}\n    env:\n      RUSTC_WRAPPER: sccache\n      YAZI_GEN_COMPLETIONS: true\n      CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER: lld-link.exe\n      CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER: lld-link.exe\n    steps:\n      - uses: actions/checkout@v6\n\n      - uses: dtolnay/rust-toolchain@stable\n        with:\n          targets: ${{ matrix.target }}\n\n      - name: Setup sccache\n        uses: mozilla-actions/sccache-action@v0.0.9\n\n      - name: Build\n        run: cargo build --profile release-windows --locked --target ${{ matrix.target }}\n\n      - name: Pack artifact\n        env:\n          TARGET_NAME: yazi-${{ matrix.target }}\n        run: |\n          New-Item -ItemType Directory -Path ${env:TARGET_NAME}\n          Copy-Item -Path \"target\\${{ matrix.target }}\\release-windows\\ya.exe\" -Destination ${env:TARGET_NAME}\n          Copy-Item -Path \"target\\${{ matrix.target }}\\release-windows\\yazi.exe\" -Destination ${env:TARGET_NAME}\n          Copy-Item -Path \"yazi-boot\\completions\" -Destination ${env:TARGET_NAME} -Recurse\n          Copy-Item -Path \"README.md\", \"LICENSE\" -Destination ${env:TARGET_NAME}\n          Compress-Archive -Path ${env:TARGET_NAME} -DestinationPath \"${env:TARGET_NAME}.zip\"\n\n      - name: Upload artifact\n        uses: actions/upload-artifact@v7\n        with:\n          name: ${{ matrix.target }}\n          path: yazi-${{ matrix.target }}.zip\n\n  build-musl:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        include:\n          - target: x86_64-unknown-linux-musl\n          - target: aarch64-unknown-linux-musl\n    container:\n      image: docker://ghcr.io/cross-rs/${{ matrix.target }}:edge\n    steps:\n      - uses: actions/checkout@v6\n\n      - uses: dtolnay/rust-toolchain@stable\n        with:\n          targets: ${{ matrix.target }}\n\n      - name: Setup sccache\n        uses: mozilla-actions/sccache-action@v0.0.9\n\n      - name: Build\n        run: ./scripts/build.sh ${{ matrix.target }}\n\n      - name: Upload artifact\n        uses: actions/upload-artifact@v7\n        with:\n          name: ${{ matrix.target }}\n          path: |\n            yazi-${{ matrix.target }}.zip\n            yazi-${{ matrix.target }}.deb\n\n  build-snap:\n    strategy:\n      matrix:\n        include:\n          - os: ubuntu-latest\n            arch: amd64\n          - os: ubuntu-24.04-arm\n            arch: arm64\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Setup LXD\n        uses: canonical/setup-lxd@v1\n\n      - name: Setup snapcraft\n        run: sudo snap install --classic snapcraft\n\n      - name: Build snap\n        run: snapcraft --verbose\n\n      - name: Rename snap\n        run: mv yazi_*.snap yazi-${{ matrix.arch }}.snap\n\n      - name: Upload artifact\n        uses: actions/upload-artifact@v7\n        with:\n          name: snap-${{ matrix.arch }}\n          path: yazi-${{ matrix.arch }}.snap\n\n  snap:\n    runs-on: ubuntu-latest\n    needs: [build-snap]\n    steps:\n      - uses: actions/download-artifact@v8\n        with:\n          pattern: snap-*\n          merge-multiple: true\n\n      - name: Setup snapcraft\n        run: sudo snap install --classic snapcraft\n\n      - name: Push snap to candidate channel\n        if: startsWith(github.ref, 'refs/tags/')\n        env:\n          SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_TOKEN }}\n        run: |\n          parallel 'snapcraft upload -v --release latest/candidate {}' ::: yazi-*.snap || true\n\n      - name: Push snap to edge channel\n        if: ${{ !startsWith(github.ref, 'refs/tags/') }}\n        env:\n          SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_TOKEN }}\n        run: |\n          parallel 'snapcraft upload -v --release latest/edge {}' ::: yazi-*.snap || true\n\n  draft:\n    if: startsWith(github.ref, 'refs/tags/')\n    permissions:\n      contents: write\n    runs-on: ubuntu-latest\n    needs: [build-unix, build-windows, build-musl, build-snap]\n    steps:\n      - uses: actions/download-artifact@v8\n        with:\n          merge-multiple: true\n\n      - name: Draft\n        uses: softprops/action-gh-release@v2\n        with:\n          draft: true\n          files: |\n            yazi-*.zip\n            yazi-*.deb\n            yazi-*.snap\n          generate_release_notes: true\n\n  nightly:\n    if: ${{ !startsWith(github.ref, 'refs/tags/') }}\n    permissions:\n      contents: write\n    runs-on: ubuntu-latest\n    needs: [build-unix, build-windows, build-musl, build-snap]\n    steps:\n      - run: |\n          echo 'NIGHTLY_BODY<<EOF' >> $GITHUB_ENV\n          echo \"From commit: ${GITHUB_SHA:0:8}\" >> $GITHUB_ENV\n          echo \"Generated on: $(date -u +\"%Y-%m-%d %H:%M\") UTC\" >> $GITHUB_ENV\n          echo \"Nightly changelog: https://github.com/sxyazi/yazi/blob/main/CHANGELOG.md#unreleased\" >> $GITHUB_ENV\n          echo \"EOF\" >> $GITHUB_ENV\n\n      - uses: actions/checkout@v6\n\n      - uses: actions/download-artifact@v8\n        with:\n          merge-multiple: true\n\n      - name: Update the tag\n        run: |\n          git config user.name \"github-actions[bot]\"\n          git config user.email \"41898282+github-actions[bot]@users.noreply.github.com\"\n          git tag --force nightly && git push --force origin tag nightly\n\n      - name: Nightly\n        uses: softprops/action-gh-release@v2\n        with:\n          tag_name: nightly\n          prerelease: true\n          files: |\n            yazi-*.zip\n            yazi-*.deb\n            yazi-*.snap\n          name: Nightly Build\n          body: ${{ env.NIGHTLY_BODY }}\n          target_commitish: ${{ github.sha }}\n"
  },
  {
    "path": ".github/workflows/lock.yml",
    "content": "name: Lock Threads\n\non:\n  schedule:\n    - cron: \"5 3 * * *\"\n  workflow_dispatch:\n\nconcurrency:\n  group: lock\n\njobs:\n  lock:\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n      pull-requests: write\n      discussions: write\n    steps:\n      - uses: dessant/lock-threads@v6\n        with:\n          issue-inactive-days: \"30\"\n          issue-comment: >\n            I'm going to lock this issue because it has been closed for _30 days_. ⏳\n\n            This helps our maintainers find and focus on the active issues.\n            If you have found a problem that seems similar to this, please file a new\n            issue and complete the issue template so we can capture all the details\n            necessary to investigate further.\n          pr-inactive-days: \"30\"\n          discussion-inactive-days: \"30\"\n          process-only: \"issues,prs,discussions\"\n"
  },
  {
    "path": ".github/workflows/no-response.yml",
    "content": "name: No Response\n\non:\n  issue_comment:\n    types: [created]\n  schedule:\n    - cron: \"10 * * * *\"\n\njobs:\n  no-response:\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n    steps:\n      - uses: lee-dohm/no-response@v0.5.0\n        with:\n          token: ${{ github.token }}\n          daysUntilClose: 7\n          responseRequiredLabel: waiting on op\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish\n\non:\n  release:\n    types: [published]\n\npermissions:\n  contents: read\n\njobs:\n  winget:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/download-artifact@v8\n        with:\n          merge-multiple: true\n\n      - name: Publish to Winget\n        uses: vedantmgoyal9/winget-releaser@main\n        with:\n          identifier: sxyazi.yazi\n          installers-regex: 'yazi-(x86_64|aarch64)-pc-windows-msvc\\.zip$'\n          token: ${{ secrets.WINGET_TOKEN }}\n\n  snapcraft:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/download-artifact@v8\n        with:\n          merge-multiple: true\n\n      - name: Promote snap to stable\n        env:\n          SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_TOKEN }}\n        run: |\n          sudo snap install --classic snapcraft\n          snapcraft promote yazi --from-channel latest/candidate --to-channel latest/stable --yes\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\nenv:\n  SCCACHE_GHA_ENABLED: true\n  RUSTC_WRAPPER: sccache\n  CARGO_TERM_COLOR: always\n\npermissions:\n  contents: read\n\njobs:\n  test:\n    strategy:\n      matrix:\n        os: [ubuntu-latest, windows-latest, macos-latest]\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Setup Rust toolchain\n        run: rustup toolchain install stable --profile minimal\n\n      - name: Setup sccache\n        uses: mozilla-actions/sccache-action@v0.0.9\n\n      - name: Build\n        run: cargo build --verbose\n\n      - name: Test\n        run: cargo test --workspace --verbose\n"
  },
  {
    "path": ".github/workflows/validate-form.yml",
    "content": "name: Validate Form\n\non:\n  issues:\n    types: [opened, edited]\n  schedule:\n    - cron: \"20 * * * *\"\n\njobs:\n  check-version:\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: 20\n\n      - name: Install Dependencies\n        run: |\n          cd scripts/validate-form\n          npm ci\n\n      - name: Validate Form\n        uses: actions/github-script@v8\n        with:\n          script: |\n            const script = require('./scripts/validate-form/main.js')\n            await script({github, context, core})\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "target/\nyazi-*/completions\nnode_modules/\n\n.DS_Store\n\nresult\nresult-*\n.direnv\n\n.idea/\n.vscode/\n\n*.snap\n"
  },
  {
    "path": ".luarc.json",
    "content": "{\n\t\"$schema\": \"https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json\",\n\t\"runtime.version\": \"Lua 5.5\",\n\t\"runtime.special\": {\n\t\t\"fail\": \"error\"\n\t},\n\t\"workspace.library\": [\"~/.config/yazi/plugins/types.yazi/\"],\n\t\"diagnostics.disable\": [\"redefined-local\"]\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):\n\n- `Added` for new features.\n- `Changed` for changes in existing functionality.\n- `Deprecated` for soon-to-be removed features.\n- `Fixed` for any bug fixes.\n- `Improved` for performance improvements.\n\n## [Unreleased]\n\n### Added\n\n- Custom tab name ([#3666])\n- New `--in` for `search` action to set search directory ([#3696])\n- Multi-file spotter ([#3733])\n- Certificate authentication for SFTP VFS provider ([#3716])\n- New `hovered` condition specifying different icons for hovered files ([#3728])\n- Allow using `ps.sub()` in `init.lua` directly without a plugin ([#3638])\n- New `ya.exec()` API and `ya exec` subcommand to execute an action and await its result ([#3780])\n- New `sort_fallback` option to control fallback sorting behavior ([#3077])\n- New `fs.access()` API to access the filesystem ([#3668])\n- New `relay-notify-push` DDS event to customize the notification handler ([#3642])\n- New `ind-app-title` DDS event to customize the app title ([#3684])\n- New `ind-hidden` and `key-hidden` DDS events to change hidden status in Lua ([#3748])\n- New `marker_symbol` option to specify the symbol used for marking files ([#3689])\n- New `fs.unique()` creates a unique file or directory ([#3677])\n- New `download` DDS event fires when remote files are downloaded ([#3687])\n- New `ind-which-activate` DDS event to change the which component behavior ([#3608])\n- New `hey` DDS event fires when static messages are restored from persistence ([#3725])\n- New `cx.which` API to access the which component state ([#3617])\n- New experimental `ya.co()` API that creates a coroutine ([#3757])\n\n### Changed\n\n- Upgrade Lua to 5.5 ([#3633])\n- Change preset <kbd>t</kbd> for creating tabs to <kbd>t</kbd> ⇒ <kbd>t</kbd> to avoid conflict with new <kbd>t</kbd> ⇒ <kbd>r</kbd> for renaming tabs ([#3666])\n- Remove `title_format` in favor of new `ind-app-title` DDS event for flexible title customization ([#3684])\n- Remove `micro_workers` and `macro_workers` in favor of finer control over concurrent workers ([#3661])\n\n### Deprecated\n\n- Deprecate `fs.unique_name()` in favor of `fs.unique()` to fix a TOCTOU race condition ([#3677])\n\n### Fixed\n\n- Chafa v1.18.1 causes random ghost keypresses when previewing images ([#3678])\n- Be a little defensive while parsing the output of `7zz -ba` ([#3744])\n- Make `ya pkg` ignore default remote name in user Git config ([#3648])\n- Archive extraction fails for target paths with non-ASCII characters on Windows ([#3607])\n- Escape backslashes in ImageMagick font path parameter ([#3708])\n\n### Improved\n\n- Reduce memory allocations by using Lua 5.5 external strings ([#3634])\n- Reuse previewed and spotted widgets when possible ([#3765])\n\n## [v26.1.22]\n\n### Added\n\n- Tree view for the preset archive previewer ([#3525])\n- Support compressed tarballs (`.tar.gz`, `.tar.bz2`, etc.) in the preset archive previewer ([#3518])\n- Check and refresh the file list when the terminal gains focus ([#3561])\n- Experimental module-level async support ([#3594])\n- Disable ANSI escape sequences in `ya pkg` when stdout is not a TTY ([#3566])\n- New `Path.os()` API creates an OS-native `Path` ([#3541])\n\n### Fixed\n\n- Smart-case in interactive `cd` broken due to a typo ([#3540])\n- Fix shell formatting for non-spread opener rules ([#3532])\n- `sort extension` excludes directories since only files have extensions ([#3582])\n- Account for URL covariance in `Url:join()` ([#3514])\n\n## [v26.1.4]\n\n### Added\n\n- Support VFS for preset previewers that rely on external commands ([#3477])\n- Support 8-bit images in RGB, CIELAB, and GRAY color spaces ([#3358])\n\n### Fixed\n\n- `ya pkg` fails to write `package.toml` when the config directory does not exist ([#3482])\n- A race condition generating unique filenames for concurrent file operations ([#3494])\n\n## [v25.12.29]\n\n### Added\n\n- Remote file management ([#3396])\n- Virtual file system ([#3034], [#3035], [#3094], [#3108], [#3187], [#3203])\n- Shell formatting ([#3232])\n- Multi-entry support for plugin system ([#3154])\n- Zoom in or out of the preview image ([#2864])\n- Improve the UX of the pick and input components ([#2906], [#2935])\n- Show progress of each task in task manager ([#3121], [#3131], [#3134])\n- New `fs.copy()` and `fs.rename()` APIs ([#3467])\n- New experimental `ya.async()` API ([#3422])\n- New `overall` option to set the overall background color ([#3317])\n- Rounded corners for indicator bar ([#3419])\n- New `bulk_rename` action always renames files with the editor ([#2984])\n- `key-*` DDS events to allow changing or canceling user key events ([#3005], [#3037])\n- New `--bg` specifying image background color in the preset SVG and ImageMagick previewers ([#3189])\n- `filter` by full path (prefix + filename) in search view instead of just filename ([#2915])\n- New `casefy` action for case conversion of the input content ([#3235])\n- Allow dynamic adjustment of layout ratio via `rt.mgr.ratio` ([#2964])\n- Support `.deb` packages ([#2807], [#3128], [#3209])\n- Port several widespread GUI keys to the input component ([#2849])\n- Support invalid UTF-8 paths throughout the codebase ([#2884], [#2889], [#2890], [#2895], [#3023], [#3290], [#3369])\n- Allow upgrading only specific packages with `ya pkg` ([#2841])\n- Respect the user's `image_filter` setting in the preset ImageMagick previewer ([#3286])\n- New `duplicate` DDS event for copying files ([#3456])\n- New `ind-sort` and `key-sort` DDS events to change sorting in Lua ([#3391])\n- Allow custom mouse click behavior for individual files ([#2925])\n- Display newlines in input as spaces to improve readability ([#2932])\n- Fill in error messages if preview fails ([#2917], [#3383], [#3387])\n- Search view shares file selection and yank state ([#2855])\n- Offload mimetype fetching on opening files to the task scheduler ([#3141])\n- Increase terminal response timeout to better tolerate slow SSH network environments ([#2843])\n\n### Changed\n\n- Rename `name` to `url` for open, fetchers, spotters, preloaders, previewers, filetype, and `globs` icon rules to support virtual file system ([#3034])\n- Rename `mime` fetcher to `mime.local`, and introduce `mime.dir` fetcher to support folder MIME types ([#3222])\n- Reclassify `hovered` and `preview_hovered` under `[mgr]` of `theme.toml` into `[indicator]` as `current` and `preview`, respectively ([#3419])\n- Remove `$0` parameter in opener rules to make the `open` action work under empty directories ([#3226])\n- Return `Path` instead of `Url` from `Url:strip_prefix()` and `File.link_to` to enforce type safety ([#3361], [#3385])\n- Use `body` instead of the term `content` in confirmations ([#2921])\n- Use `u16` instead of `u32` as the type of `max_width` and `max_height` options to avoid memory exhaustion ([#3313])\n- Implement `__pairs` metamethod instead of `__index` for the callback argument of the `@yank` DDS event ([#2997])\n\n### Deprecated\n\n- Deprecate `$n`, `$@` (\\*nix) and `%n`, `%*` (Windows) in `shell` action and opener rules in favor of new shell formatting ([#3232])\n- Deprecate `ya.hide`, `ya.render`, and `ya.truncate` in favor of `ui.hide`, `ui.render`, and `ui.truncate` ([#2939])\n- Deprecate `position` property of `ya.input()` in favor of `pos` to align with `ya.confirm()` and its type `ui.Pos` ([#2921])\n- Deprecate `cx.tasks.progress` in favor of `cx.tasks.summary` ([#3131])\n- Deprecate `frag` properly of `Url` in favor of `domain` ([#3034])\n- Deprecate `ui.Rect.default` in favor of `ui.Rect {}` ([#2927])\n\n### Fixed\n\n- User-prepended open rules do not override presets ([#3360])\n- Respect user's system media opener instead of hardcoding `mpv` ([#2959])\n- Incorrect `$0` and `$@` parameters in `shell` action under empty directories ([#3225])\n- Avoid appending a newline when reading clipboard contents ([#3059])\n- Renew package `rev` only when it's empty ([#3200])\n- Suspend only when there is a parent process ([#3008])\n- Preserve open order for files with post-resolved MIME types ([#2931])\n- A race condition in concurrent directory loading on a slow device ([#3271])\n- Erase overlapping image portions when previewing errors ([#3067])\n- Force Git checkout for plugin cache repositories ([#3169])\n- Check compatibility when reusing previewer bytecode cache ([#3190])\n- Disable kitty keyboard protocol on Windows due to `crossterm` inability to handle it ([#3250])\n- Prevent quotes in file(1) arguments from being stripped under MSYS2 ([#3364])\n- Expose `ya` CLI in the Snap build ([#2904])\n- Fallback to `PollWatcher` for file changes watching on NetBSD ([#2941])\n- Generate unique image IDs for Kgp to tolerate tmux ([#3038])\n\n### Improved\n\n- Make copy, cut, delete, link, hardlink, download, and upload tasks immediately cancellable ([#3429])\n- Make preload tasks discardable ([#2875])\n- Reduce file change event frequency ([#2820])\n- Upload and download of a single file over SFTP in chunks concurrently ([#3393])\n- Do not listen for file changes in inactive tabs ([#2958])\n- Switch to a higher-performance hash algorithm ([#3083])\n- Sequence-based rendering merge strategy ([#2861])\n- Store only `Urn` instead of full `Url` in find results ([#2914])\n- Zero-copy `UrlBuf` to `Url` conversion ([#3117])\n- String interning to reduce memory usage of mimetype and URL domain ([#3084], [#3091])\n- Do not pre-allocate memory for Lua tables ([#2879])\n- Copy-on-write on command data, and avoid converting primitive types to strings thereby allocating memory ([#2862])\n- Use `AnyUserData::type_id()` to reduce stack pushes ([#2834])\n- App data instead of Lua registry to reduce stack pushes ([#2880])\n\n## [v25.5.31]\n\n### Fixed\n\n- Expose `ui.Wrap` ([#2810])\n- `forward --end-of-word` of the input should consider the mode's delta ([#2811])\n- Make every effort to carry hidden states for dummy files ([#2814])\n\n## [v25.5.28]\n\n### Added\n\n- Redesign tabs ([#2745])\n- Support embedded cover for video preview ([#2640])\n- Calculate real-time directory size in spotter ([#2695])\n- Truncate long items in the file list ([#2754], [#2759], [#2778])\n- Obscure input component for inputting passwords ([#2675])\n- Improve path auto-completion results ([#2765])\n- New `ya pkg` subcommand ([#2770])\n- New `ya.emit()` API ([#2653])\n- New `fs.calc_size()` API ([#2691])\n- Allow custom exit code with `quit --code` ([#2609])\n- New `--hovered` for the `copy` action ([#2709])\n- `s` and `S` keybinds in the input component ([#2678])\n- Limit memory usage for previewing large images ([#2602])\n- Show error when image preview fails ([#2706])\n- New `ui.Align`, `ui.Wrap`, and `ui.Edge` ([#2802])\n- Make `ui.Line` renderable ([#2743])\n- Checks in `ya pub` and `ya emit` subcommands to verify receiver exists and has necessary abilities ([#2696])\n- Make the hover state for `reveal`, `sort`, and `hidden` actions stable ([#2657])\n- New `--no-dummy` option for `reveal` action ([#2664])\n- Fall back to `CSI 16 t` when PowerShell OpenSSH returns a fake terminal size ([#2636])\n\n### Changed\n\n- Deprecate `[manager]` in favor of `[mgr]` to make it consistent with other APIs ([#2803])\n- Remove `tab_width` as it no longer needs to be set manually ([#2745])\n- Move `tab_active` and `tab_inactive` to a dedicated `[tabs]` section ([#2745])\n- Remove `sixel_fraction` as it's no longer needed ([#2707])\n\n### Deprecated\n\n- Deprecate `ya.mgr_emit()`, `ya.app_emit()` and `ya.input_emit()` ([#2653])\n- Deprecate `ya.preview_widgets()` ([#2706])\n- Deprecate the `Command:args()` method ([#2752])\n- Deprecate the `ya pack` subcommand in favor of `ya pkg` ([#2770])\n- Deprecate `LEFT`, `CENTER`, and `RIGHT` on `ui.Line` and `ui.Text` in favor of `ui.Align` ([#2802])\n- Deprecate `NONE`, `TOP`, `RIGHT`, `BOTTOM`, `LEFT`, and `ALL` on `ui.Bar` and `ui.Border` in favor of `ui.Edge` ([#2802])\n- Deprecate `WRAP_NO`, `WRAP` and `WRAP_TRIM` on `ui.Text` in favor of `ui.Wrap` ([#2802])\n\n### Fixed\n\n- Respect the user's `max_width` setting in the preset video previewer ([#2560])\n- Reverse the mixing order of theme and flavor configuration ([#2594])\n- No title is set when starts the first time ([#2700])\n- `ya pub-to 0` checks if any peer is able to receive the message ([#2697])\n- Detach background and orphan processes from the controlling terminal with `setsid()` ([#2723])\n- Always try to create state directory before draining DDS data ([#2769])\n- Avoid tmux interfering with kitty graphical sequences ([#2734])\n\n### Improved\n\n- Double directory size calculation speed ([#2683])\n- 9x faster Sixel image preview ([#2707])\n- Remove intermediate variables in natural sorting algorithm to avoid unnecessary allocation ([#2764])\n- Avoid unnecessary memory allocation in `ya.truncate()` ([#2753])\n\n## [v25.4.8]\n\n### Added\n\n- Enhance `fzf` integration ([#2553])\n- Platform-specific key binding ([#2526])\n- Custom search engine Lua API ([#2452])\n- New `follow` action to follow files pointed to by symlinks ([#2543])\n- Allow `tab_swap` to cycle tabs ([#2456])\n- Show error message when directory fails to load ([#2527])\n- New `symlink_target` to style the target of symbolic links ([#2522])\n- Use Yazi in Helix directly without Zellij or tmux ([#2461])\n- New `<C-A>` and `<C-E>` keybindings to select entire line for the input component ([#2439])\n- New `fs.expand_url()` API ([#2476])\n- New `ui.Text:scroll()` API for setting text to scroll horizontally or vertically ([#2589])\n- Allow initializing input when opening it with actions like `rename`, `create`, `find`, `filter`, etc. ([#2578])\n- New `@sync peek` annotation for sync previewers ([#2487])\n- New `ya.id(\"app\")` to get `YAZI_ID` in plugins ([#2503])\n- New `base` field for the `Url` ([#2492])\n- New `rt.term` exports terminal emulator information ([#2442])\n- Allow bulk renaming to include trailing content in addition to the required new names ([#2494])\n- Log `tmux` call execution time to logs ([#2444])\n\n### Changed\n\n- Navigation wraparound with new `arrow prev` and `arrow next` actions ([#2485], [#2540])\n- Swap default key bindings for fzf and zoxide ([#2546])\n- Switch to `resvg` as the SVG renderer ([#2581])\n- Make `frag`, `name`, `stem`, `ext`, and `parent` on `Url`, `name` on `tab::Tab`, and `is_hovered` on `fs::File` properties ([#2572])\n- Replace `tasks_show` and `close_input` with `tasks:show` and `input:close` ([#2530])\n- Replace `sync = true` with the `@sync peek` annotation ([#2487])\n\n### Deprecated\n\n- Deprecate `ui.Padding` and `ui.Rect:padding()` ([#2574])\n\n### Fixed\n\n- Always show the size in the status bar even in empty directories ([#2449])\n- Remove the temporary extraction directory forcefully ([#2458])\n- Align the behavior of the end-of-options marker (`--`) with that of the shell ([#2431])\n- Respect hidden status of directory junctions and symlinks themselves on Windows ([#2471])\n\n### Improved\n\n- Rewrite config parser to double the startup speed ([#2508])\n- Lazy compile and cache lua plugins as binary bytecode ([#2490])\n- Faster image preview with optimized `magick` arguments ([#2533])\n- Cache UserData fields ([#2572])\n\n## [v25.3.2]\n\n### Added\n\n- Expose all theme fields in Lua ([#2405])\n- Expose almost the entirety of the user's configuration in Lua ([#2413])\n\n### Fixed\n\n- `STDIN_FILENO` poll always returns 0 under SSH ([#2427])\n- Ignore stdin redirection to ensure always accessing the real tty ([#2425])\n- Incorrect deprecation warning when the plugin doesn't exist ([#2418])\n\n## [v25.2.26]\n\n### Added\n\n- Allow to specify layer for keymap actions ([#2399])\n- New `rt` and `th` allow to access user configuration and theme scheme in sync/async plugins consistently ([#2389], [#2392], [#2393], [#2397])\n- New `tbl_col` and `tbl_cell` in theme system for spotter table styling ([#2391])\n- Allow different separators to be applied individually to the left and right sides of the status bar ([#2313])\n- `ripgrep-all` support for the `search` action ([#2383])\n- Respect the user's `max_width` setting in the preset PDF preloader ([#2331])\n- Respect the user's `wrap` setting in the preset JSON previewer ([#2337])\n- Respect the user's `image_alloc` setting in the preset ImageMagick previewer ([#2403])\n- New `external` and `removable` fields in the `fs.partitions()` API ([#2343])\n- CSI-based Vim and Neovim built-in terminal detection for better accuracy ([#2327])\n\n### Changed\n\n- Replace `separator_open` and `separator_close` with `sep_left` and `sep_right` ([#2313])\n- Rename the `[completion]` component to `[cmp]` ([#2399])\n\n### Deprecated\n\n- Deprecate `MANAGER`, `PREVIEW`, `PLUGIN`, and `THEME` in favor of `rt` and `th` ([#2389])\n- Deprecate `ya.manager_emit()` in favor of `ya.mgr_emit()` ([#2397])\n\n### Fixed\n\n- Didn't reset previous `Cha` when loading directories in chunks ([#2366])\n- Load mount points with the best effort even if the `/dev/disk/by-label` directory does not exist ([#2326])\n- Add maximum preview limit under `/proc` virtual file system ([#2355])\n\n## [v25.2.11]\n\n### Added\n\n- New `overall` option under `[status]` to allow specifying the overall style of the status bar ([#2321])\n- Reduce terminal response wait timeout ([#2314])\n\n### Fixed\n\n- Unable to delete sealed files on Windows due to platform differences ([#2319])\n- Reverse the order of CSI-based and environment-based terminal detection ([#2310])\n\n## [v25.2.7]\n\n### Added\n\n- Mount manager ([#2199])\n- New `ya.confirm()` API ([#2095])\n- New `arrow top` and `arrow bot` actions to jump to the top and bottom ([#2294])\n- Support end of options (`--`) marker for all actions ([#2298])\n- Replace mode and Vim motions (`W`, `E`, `B`, `^`, `_`) for inputs ([#2143])\n- New `ya pack -d` subcommand to delete packages ([#2181])\n- `ya pack` supports adding and deleting multiple packages at once ([#2257])\n- Theme support for the spotter border and title ([#2002])\n- Use positional argument instead of `--args` for the `plugin` action ([#2299])\n- Support and hide Windows system files by default ([#2149])\n- New `--no-cwd-file` option for the `close` action ([#2185])\n- Prompt users missing fzf in the zoxide plugin ([#2122])\n- More decent package locking mechanism ([#2168])\n- Custom modal component API ([#2205])\n- Support local `tmux` image preview over SSH\n- New `@since` plugin annotation to specify the minimum supported Yazi version ([#2290])\n- Allow preloaders to return an optional `Error` to describe the failure ([#2253])\n- ARM64 Snap package ([#2188])\n- Support `package.toml` as a symlink ([#2245])\n- New `cx.layer` API to determine the current UI layer ([#2247])\n- Channel and multi-concurrent task join support for the plugin system ([#2210])\n- Support `application/mbox` mimetype ([#2173])\n- `cbr` and `cbz` as valid archive extensions ([#2077])\n\n### Deprecated\n\n- Deprecate `--args` in the `plugin` action in favor of a 2nd positional parameter ([#2299])\n- Deprecate plugin entry file `init.lua` in favor of `main.lua` ([#2168])\n- Deprecate `arrow -99999999` and `arrow 99999999` in favor of `arrow top` and `arrow bot` ([#2294])\n- Deprecate the numeric return value of preloaders in favor of a boolean return value ([#2253])\n- Deprecate `ya.md5()` in favor of `ya.hash()` ([#2168])\n\n### Fixed\n\n- `before_ext` excludes directories since only files have extensions ([#2132])\n- Element style of `ui.Text` was not applied to the entire area ([#2093])\n- Incorrect monorepo sub-plugin path resolution ([#2186])\n- Use `u32` for parsing Linux partition blocks ([#2234])\n- Unmangle the hexadecimal space strings (`\"\\x20\"`) in Linux partition labels ([#2233])\n- JSON value `null` should be deserialized as Lua `nil`, not lightweight userdata `null` ([#2242])\n- Don't check if has a hovered file in advance, only do so when `--hovered` is explicitly specified ([#2105])\n- Handle broken pipe errors gracefully ([#2110])\n\n### Improved\n\n- Detach the watch registration from the main thread ([#2224])\n\n## [v0.4.2]\n\n### Added\n\n- More supported archive formats to the preset config ([#1926])\n- New `fs.create()` Lua API ([#2068])\n- New `--cwd` parameter for the `shell` action and `fs.cwd()` API ([#2060])\n- Allow `noop` for single-key chords by removing the mixing length limit ([#2064])\n- Support for Android platform in the `for` qualifier of opener ([#2041])\n\n### Fixed\n\n- Set the current working directory in a thread-safe way ([#2043])\n- Interactive `cd` autocomplete doesn't follow the latest `CWD` changes ([#2025])\n- Offset cursor shift when deleting multiple files in bulk ([#2030])\n- Missing a render after resuming from an external blocking process ([#2071])\n- Missing a hover after reordering from an external plugin ([#2072])\n- Use a less intrusive `DSR` instead of `DA1` workaround to forward terminal responses twice in tmux ([#2058])\n- `allow-passthrough` must be set to `on` to prevent `tmux` from forwarding the real terminal's response to the inactive pane ([#2052])\n\n## [v0.4.1]\n\n### Fixed\n\n- Correctly handle CRLF on Windows in preset archive and JSON plugins ([#2017])\n- Failed to parse certain image dimensions for Überzug++ backend ([#2020])\n- Disable passthrough when the user launches Yazi in Neovim inside tmux ([#2014])\n\n## [v0.4.0]\n\n### Added\n\n- Spotter ([#1802])\n- Support transparent image preview ([#1556])\n- Auto switch between dark and light icons/flavors based on terminal backgrounds ([#1946])\n- Allow disabling certain preset keybinds with the new `noop` virtual action ([#1882])\n- New `ya emit` and `ya emit-to` subcommands to emit actions to a specified instance for execution ([#1979])\n- Custom styles for the `confirm` component ([#1789])\n- Make the builtin `extract` plugin support compressed tarballs (`*.tar.gz`, `*.tar.bz2`, etc.) ([#1583])\n- Launch from preset settings if the user's config cannot be parsed ([#1832])\n- Prioritize paths that need to be processed first during bulk renaming ([#1801])\n- New `copy --separator` option to allow specifying the path separator ([#1877])\n- Set a different input title for `create --dir` ([#1650])\n- Include package revision hash in `ya pack --list` ([#1884])\n- New `load` DDS event ([#1980])\n- New log system ([#1945])\n- New `ui.Text` and `ui.Table` layout elements ([#1776])\n- Support passing arguments to previewer/preloader/spotter/fetcher ([#1966])\n- Move notification from top-right to bottom-right corner to avoid covering content as much as possible ([#1984])\n- Append the suffix to the end instead of start when generating unique filenames for directories ([#1784])\n- Allow overriding and rewriting the sync methods of built-in plugins ([#1695])\n- Fallback to `CSI 16 t` for certain terminals that do not support `TIOCGWINSZ` ([#2004])\n- Support calling methods in built-in plugins with arbitrary types of arguments (`self` can now be omitted) ([#1666])\n- Support `assets` installation for the `ya pack` subcommand ([#1973])\n- Complete and consistent support for the `ui.Style()` API\n- Image ICC profiles for better color accuracy ([#1808])\n- Support reading non-UTF8 data with `Child:read_line()` API ([#1816])\n- New `area()` method for renderable elements ([#1667])\n- `yazi --debug` supports detecting whether `tmux` is built with `--enable-sixel` ([#1762])\n\n### Changed\n\n- Eliminate the `x-` prefix in MIME types ([#1927])\n- Remove the `vnd.` prefix from mimetype to solve differences introduced in the newest `file(1)` v5.46 ([#1995])\n- Rename the term `select` to `toggle` to reserve `select` for future use ([#1773])\n- Correct the misuse of the term `ctime` and unify others ([#1761])\n- Replace `ffmpegthumbnailer` with `ffmpeg` as the video preview backend to support spotter ([#1928])\n- Use an `Error` userdata instead of a plain error code for I/O errors ([#1939])\n- Remove `ui.ListItem` since it's no longer necessary ([#1772])\n- Decouple coordinates from `ui.List`, `ui.Bar`, `ui.Border`, and `ui.Gauge` ([#1782])\n- Make `backspace` action not close the input even when value is empty ([#1680])\n- Remove the meaningless `--confirm` option to simplify the `shell` action ([#1982])\n- Use `dark` and `light` instead of `use` under `[flavor]` to support auto-switching between light and dark modes ([#1946])\n- Unify the `fg_dark` and `fg_light` into one `fg` since `fg_light` is redundant and never used ([#1946])\n- Extend the available styles for `mode` by separating `mode` from the `separator` styles ([#1953])\n\n### Deprecated\n\n- Deprecate `--sync` option for the `plugin` action ([#1891])\n- Deprecate `ui.Paragraph` in favor of `ui.Text` ([#1776])\n- Deprecate the task info of `peek()`, `seek()`, and `preload()` from `self` in favor of a `job` parameter ([#1966])\n- Deprecate parameter list of `entry()` from its first argument in favor of a `job` parameter ([#1966])\n- Deprecate the number of units of `seek()` from its first argument in favor of a `job` parameter ([#1966])\n\n### Fixed\n\n- Introduce a new `btime` term to align `ctime` with Unix ([#1761])\n- Match icon by extension case-insensitively ([#1614])\n- Copy the CWD path with `c => d` regardless even if the directory is empty ([#1849])\n- Respect the `image_quality` setting in preset PDF previewer ([#2006])\n- Images were not cleared when closing a tab in front of the current tab ([#1792])\n- Replace control characters to printable characters in plain text preview ([#1704])\n- One file's MIME type changed multiple times without triggering a preview again ([#1682])\n- Reset image rendering and skip peeking if the TUI in the background ([#1833])\n- File upserting should handle deleting in edge cases where the source and target URNs are different ([#1737])\n- Revise `revision` if the new file list is empty but the previous one was not ([#2003])\n- Update `rustix` to fix the `enable_raw_mode()` error on WSL/Android\n\n### Improved\n\n- Merge multiple file operations into one to greatly speed up updates in large directories ([#1745])\n- Eliminate all memory reallocations during sorting ([#1846])\n- Introduce URN to speed up large directory sorting, updating, locating ([#1622], [#1652], [#1648])\n- Improve jemalloc memory allocator efficiency ([#1689])\n- Lazy load `ui`, `ya`, `fs`, and `ps` ([#1903])\n- Avoid unnecessary allocations in base64 encoding of inline image protocol ([#1639])\n- Introduce copy-on-write for event system to eliminate all memory reallocations ([#1962])\n- Apply rotate in place to images with orientation ([#1807])\n- Introduce reflow for the rendering engine ([#1863])\n\n## [v0.3.3]\n\n### Added\n\n- `size` linemode supports showing the file count for directories ([#1591])\n- Support image preview in Windows Terminal ([#1588])\n- Add `is_absolute`, `has_root`, `starts_with`, `ends_with`, `strip_prefix` to `Url` ([#1605])\n\n### Fixed\n\n- Keybindings disappear when mixing presets with a wrong filter condition ([#1568])\n- Squeeze `offset` of the file list after resizing window ([#1500])\n- Check compositor support status before using ueberzug wayland output ([#1566])\n- Fallback to `PollWatcher` for file changes watching on WSL ([#1574])\n\n### Improved\n\n- Truncate long lists in confirm dialogs ([#1590])\n\n## [v0.3.2]\n\n### Added\n\n- New confirm component ([#1167])\n- Word wrapping in `code` previewer ([#1159])\n- New `--dir` option for `create` action ([#1505])\n- New `ext()` method for `Url` ([#1528])\n- Make the builtin `code` previewer handle invalid carriage return chars and binary streams better ([#1550])\n\n### Fixed\n\n- Wait till mimetype is resolved to avoid preview flickering ([#1542])\n- Use a different cache directory for each user to avoid permission issues ([#1541])\n- Filter out candidates that overlap with longer key chords from the which component ([#1562])\n- Overlong single-line text preview containing escape sequences was not being properly escaped ([#1497])\n\n### Improved\n\n- New `image_delay` option debounces image previews to avoid lag caused by terminal image decoding during fast scrolling ([#1512])\n- Only scan the first 1024 bytes to detect if it's binary, apply `\\r` fixes only to content within the visible range, avoid unnecessary allocations during natural sorting ([#1551])\n\n## [v0.3.1]\n\n### Added\n\n- Start with multiple tabs with different paths ([#1443])\n- Key notion shorthands such as `<C-S-x>` as `<C-X>` ([#1448])\n- Support `F13` - `F19` keys ([#1446])\n- New `--cursor` for the `shell` action ([#1422])\n- New `search_do` action to make it easier to achieve a flat view ([#1431])\n- Portrait orientation preview for EXIF image ([#1412])\n- Keybinding for the `hardlink` action ([#1461])\n- New `empty` previewer for empty and `/proc/*` files ([#1482])\n- Note about filtering in the help menu ([#1361])\n- New `tab` DDS event on tab switch ([#1474])\n- New `status()` method for `Command` ([#1473])\n\n### Fixed\n\n- Directory loading status ([#1439])\n- Resolve relative path when expanding path ([#1428])\n- DDS static messages only work when at least two instances are running ([#1467])\n- Escape files containing special `\\x1b` characters and render it as plain text ([#1395])\n- 7zip shows different error messages for wrong password ([#1451])\n- 7zip shows different error messages for RAR and ZIP files ([#1468])\n- Newly created directories with the same name causing a false positive in directory loading optimization due to having the same modification time ([#1434])\n- Close stdin before waiting for child process ([#1464])\n\n## [v0.3.0]\n\n### Added\n\n- Package manager ([#985], [#1110])\n- Support mouse event ([#1038], [#1139], [#1232])\n- New `extract` built-in plugin for archive extracting ([#1321])\n- Redesign icons ([#1086])\n- Font preview ([#1048])\n- SVG, HEIC, AVIF, and JPEG XL preview support ([#1050], [#1249])\n- Simplify keybindings ([#1241])\n- New `hardlink` action to create hard links ([#1268])\n- Keep file creation time on macOS and Windows ([#1169])\n- Sort randomly ([#1291])\n- New linemode to show file ownership ([#1238])\n- New linemode to show file ctime ([#1295])\n- New `--hovered` option for the `rename` and `remove` actions ([#1227])\n- Support Super/Command/Windows key with `D-` notation ([#1069])\n- Interactive `cd` path auto-completion supports `~` expansion ([#1081])\n- Preview files containing non-UTF-8 characters ([#958])\n- Expand Windows paths like \"D:\" that only have a drive letter but no root ([#948])\n- Close confirmation dialogs and exit automatically when the ongoing task gone ([#997])\n- Case-insensitive special keys in keymappings ([#1082])\n- Transliteration for natural sorting ([#1053])\n- New `ya.clipboard()` API ([#980])\n- New `debounce` option for the `ya.input()` API ([#1025])\n- Support `yazi-cli` for Nix flake ([#944])\n- Support `stdin` and pipe for `Child` API ([#1033])\n- New `ya sub` subcommand to subscribe to DDS events ([#1004])\n- Allow specifying `$YAZI_ID` with a command-line argument ([#1305])\n- DDS client-server version check ([#1111])\n- New `bulk` DDS event ([#937])\n- Support `cargo binstall yazi-fm` and `cargo binstall yazi-cli` ([#1003])\n- Show `ya` CLI version in the `yazi --debug` output ([#1005])\n- Detect terminal type in tmux with CSI sequence in passthrough mode ([#977])\n\n### Changed\n\n- Use Ctrl+c instead of Ctrl+q as the universal close key to follow the conventions\n- Replace Alt+k/Alt+j with K/J as the `seek` keybindings to avoid Alt key not working in certain terminals\n- Replace Ctrl+Enter with Shift+Enter as the alternative key for Shift+o so that it corresponds to Enter being `o` (without Shift)\n- keep original state of `sort` action in favor of specifying `yes` or `no` to explicitly apply a new state to its `--reverse`, `--dir-first`, and `--translit`\n- Move `mime` plugin from `[plugin.preloaders]` to `[plugin.fetchers]` of yazi.toml\n- Turn `success` and `code` into properties of `Status` instead of methods\n- Remove `fs.cha_follow(url)` in favor of `fs.cha(url, true)`\n- Rename `is_block_device`, `is_char_device`, and `is_socket` of `Cha` to `is_block`, `is_char`, and `is_sock` for simplicity\n\n### Fixed\n\n- Different filenames should be treated as the same file on case-insensitive file systems ([#1151])\n- Suppress warnings for different name representations of the same file in the case-insensitive file system when renaming ([#1185])\n- Avoid duplicate candidates in the `which` component ([#975])\n- Sixel support from certain `st` forks cannot be detected ([#1094])\n- Move the DDS socket file out of the cache directory to avoid being affected by `yazi --clear-cache`\n- Build `jemalloc` with 64KB pagesize on linux/arm64 ([#1270])\n- Cursor gets out of sync occasionally at image previewing through IIP under tmux ([#1070])\n\n### Improved\n\n- Reimplement and significantly speed up archive previewing ([#1220])\n\n## [v0.2.5]\n\n### Added\n\n- Data distribution service ([#826], [#855], [#861], [#867], [#868], [#871], [#880], [#895], [#913], [#928], [#933], [#940])\n- Re-implement fzf and zoxide as built-in plugins ([#884], [#881])\n- Preserve files' modified at timestamp while copying ([#926])\n- New `--orphan` option for `shell` action ([#887])\n- Smart-case for completion of interactive `cd` paths ([#910])\n- Allow creating a tab with the startup directory through `tab_create` without specifying a path ([#917])\n- Bunch of new debugging information to `yazi --debug` ([#824])\n- Time-based selection order preservation ([#843])\n- Placeholder message when there are no files in the list ([#900])\n- Enhance `ya.dbg()` and `ya.err()` by support arbitrary types ([#835])\n- Trigger path completion with both `/` and `\\` on Windows ([#909])\n- Allow opening interactively with the `--chosen-file` flag ([#920])\n- Support `YAZI_FILE_ONE` in the preset `file` previewer ([#846])\n\n### Deprecated\n\n- Deprecate the `jump` action in favor of `plugin fzf` and `plugin zoxide` ([#884], [#881])\n\n### Fixed\n\n- Kill all spawned processes on exit ([#812])\n- Prevent pasting a directory into itself ([#925])\n- Use `BTreeSet` for selected files to maintain order ([#799])\n- CJK text rendering issue where the input popup component overlaps with images ([#879])\n\n### Improved\n\n- Accelerate kitty graphics protocol encoding by avoiding string reallocation ([#837])\n- Wrap stderr with `BufWriter` to avoid frequent system calls thereby increase rendering frame rate ([#849])\n- Switch to `globset` to reduce CPU time spent on matching icons ([#908])\n- Re-implement file watcher in an async way ([#877])\n- Cache each file's icon to avoid redundant calculations at rendering ([#931])\n- Port `require()` and `ya.sync()` to Rust to avoid plugin information initialization ([#853])\n\n## [v0.2.4]\n\n### Added\n\n- Vim-like notification with new `ya.notify()` API ([#659], [#749], [#780])\n- New `ya.input()` API to request user input ([#762])\n- Cross-directory selection ([#693])\n- Colorize the icons ([#683])\n- Flavors ([#753])\n- New counter component shows the number of yanked/selected items ([#646])\n- New `scrolloff` option to keep a margin when scrolling ([#679])\n- New `<Home>`, `<End>`, and `<Delete>` keybindings for inputs ([#665])\n- New `<C-p>` and `<C-n>` for the select component to move the cursor up/down ([#779])\n- New `Ctrl+[` as an alternative to the escape key ([#763])\n- New option `--hovered` for the `open` action allows only to open the currently hovered file ([#687])\n- Support musl builds for Linux ([#759])\n- New `--debug` flag to print debug information ([#794])\n- Send a foreground notification to the user when the process fails to run ([#775])\n- Nested conflict detection for cross-directory selections ([#689])\n- New `prepend_rules` and `append_rules` for `[open]` and `[icon]` ([#754], [#670])\n- Call sync functions within an async plugin ([#649])\n- Allow access to complete app data from all tabs ([#644])\n- Ability to sort candidates in the which component ([#662])\n- Expose selected/yanked files as Lua API ([#674])\n- New `cx.yanked` API to access yanked files ([#788])\n- New `$0` (Unix) / `%0` (Windows) to access the hovered file in `shell` action ([#738])\n- New `ya.hide()` API to hide the UI temporarily ([#792])\n- Allow both `/` and `\\` for folder creation on Windows ([#751])\n- New `parse()` method for the line elements to parse ANSI sequences\n- New `ui.Clear` component to clear areas ([#786])\n- Support `YAZI_FILE_ONE` environment variable for `file(1)` path ([#752])\n- Merge wildcard preloader and previewer rules via `append_preloaders` and `append_previewers`\n\n### Deprecated\n\n- Deprecate the `exec` property in yazi.toml, keymap.toml, and theme.toml in favor of `run`\n\n### Fixed\n\n- Rendering fails when no file type style is matched ([#721])\n\n### Improved\n\n- Cache loaded plugins ([#710])\n- Cheaper sync context initialization ([#643])\n- Prefer `raw_get()` and `raw_set()`\n\n## [v0.2.3]\n\n### Added\n\n- Preview image over SSH ([#585])\n- New `unyank` action ([#313])\n- Customize number of columns of the which component ([#571])\n- Support passing arguments to plugin ([#587])\n- New `image_quality` and `sixel_fraction` options to configure the image preview quality ([#576])\n- New `ya.which()` API for custom key events ([#617])\n- New `ya.quote()` API to quote strings safely\n- `plugin` action for each layer\n- Plugin-specific state persistence ([#590])\n- Allow to configure image filter ([#586])\n- Shorten unit names and add more units to `ya.readable_size()`\n- Support char device in `[filetype]` ([#628])\n- File hidden attributes on Windows ([#632])\n- Make `trash` crate optional on Android ([#600])\n\n### Fixed\n\n- Parent folder not tracking CWD ([#581])\n- Input offset is not reset when renaming with `--cursor=start` and the filename is too long ([#575])\n\n### Improved\n\n- Read directory in bulk in the background at startup ([#599])\n- Lazy sorting when loading large directories to reduce CPU consumption ([#607])\n\n## [v0.2.2]\n\n### Added\n\n- `prepend_keymap` and `append_keymap` for configuration mixing ([#546])\n- `file(1)` as the file fallback previewer ([#543])\n- Submit both completion and input with a single press of enter ([#565])\n- Allow the spawned child processes to suspend ([#556])\n- New `ya.host_name()` API ([#550])\n- Desktop entry and logo ([#534])\n- Snap package ([#531])\n- Support Windows ARM64 ([#558])\n- Image preview in Tabby terminal ([#569])\n\n### Fixed\n\n- Can't display file name with invalid UTF-8 ([#529])\n\n### Improved\n\n- New event system allows multiple actions to reuse a single render ([#561])\n\n## [v0.2.1]\n\n### Fixed\n\n- Renaming may cause a crash when encountering Unicode characters ([#519])\n\n## [v0.2.0]\n\n### Added\n\n- New `filter` action to filter files on the fly ([#454])\n- Sort by file extension ([#405])\n- Custom preloader and previewer ([#401])\n- New `plugin` action to run Lua plugins\n- Auto-completion for input component ([#324], [#353], [#352])\n- Start with the specified file hovers over ([#358])\n- Emacs readline keybindings for inputs ([#345], [#382])\n- New `--empty` and `--cursor` options for the `rename` action ([#513])\n- New `--follow` option for `paste` action ([#436])\n- Make `copy` action work over SSH with OSC 52 ([#447])\n- New `reveal` action ([#341])\n- Support colored icons ([#503])\n- Support highlighting specific file types ([#510])\n- Make the position of input and select components customizable ([#361])\n- New `prepend_preloaders`, `append_preloaders`, `prepend_previewers`, `append_previewers` options for configuration mixing\n- Cursor and page key navigation parity with Vim bindings ([#386])\n- Use terminal ANSI colors for code highlighting by default\n- New `image_alloc` and `image_bound` options to control image preview memory usage ([#376])\n- New `suppress_preload` option to hide preload tasks ([#430])\n- New kitty graphics protocol implementation for better compatibility with `tmux` through Unicode placeholders ([#365])\n- New `ya.user_name()` and `ya.group_name()` API ([#469])\n- New `ya.render()` to trigger a UI render\n- Image orientation support ([#488])\n- Raise open file descriptors limit at startup ([#342])\n- Support image preview on WSL ([#315])\n- Fine-grained scheduling priority ([#462])\n- New `YAZI_LEVEL` environment variable to indicate the nested level ([#514])\n- New `QuadrantInside` and `QuadrantOutside` border type\n\n### Changed\n\n- Rename the option `layout` to `ratio` to make it more self-explanatory\n- Rename the `peek` action to `seek` to better convey the action of \"seeking for\" content to preview\n- Rename the `--dir_first` option of `sort` action to `--dir-first` to make it consistent with the style of other actions\n- Replace `[plugins.preload]` with the `init.lua` entry file\n\n### Fixed\n\n- `jq` previews empty when the user sets `tab_size=8` ([#320])\n- Precache n-1 and n+1 pages ([#349])\n- Popup components being covered by previewed images ([#360])\n- Rust panics instead of returning an error when file times are invalid ([#357])\n- Clear Sixel image with empty characters instead of `\\x2B[K` to be compatible with GNOME VTE ([#309])\n- Use `WAYLAND_DISPLAY` and `DISPLAY` to detect Wayland/X11 when `XDG_SESSION_TYPE` is not set ([#312])\n\n### Improved\n\n- Chunk loading for MIME types ([#467])\n- Fallback to plain highlighter for long text ([#329])\n- Reduce peak memory footprint during decoding large images ([#375])\n- Clear only necessary cells when hiding images ([#369])\n- New UI rendering architecture ([#468])\n- Partial rendering progress and composite into a complete UI to reduce CPU consumption caused by frequent progress updates ([#509])\n\n## [v0.1.5]\n\n### Added\n\n- New `find` action to find files ([#104])\n- Linemode to show extra file info ([#291])\n- New `sort_sensitive` option ([#155])\n- Cross-platform opener rules ([#289])\n- Multiple openers for a single open rule ([#154])\n- Vim-like `gg`, `G` in the preset key mappings for boundary jumps\n- Theme system ([#161])\n- New `--force` option for `remove`, `create`, `rename` actions ([#179], [#208])\n- Image preview within tmux ([#147])\n- New `link` action creates symlinks to the yanked files ([#167])\n- New `orphan` option for opener rules to detach processes from the task scheduler ([#216])\n- New `backward` and `forward` actions\n- New `--smart` option for the `find` action to support smart case ([#240])\n- Sorting for each tab individually ([#131])\n- Suspend process with `Ctrl+z` ([#120])\n- Percentage values for the `arrow` action to scroll half/full page (with newly added Vi-like `<C-u>`, `<C-d>`, `<C-b>`, and `<C-f>` keybindings) ([#213])\n- Show keywords when in search mode ([#152])\n- Tab switch wraparound ([#160])\n- Highlight matched keywords in find mode ([#211])\n- Customizable main UI border styles ([#278])\n- `<BackTab>` key notion ([#209])\n- Use of environment variables in `cd` paths ([#241])\n- Nix Flakes package ([#205])\n- New `V`, `D`, `C` Vim-like keybindings for the input component\n- New `--no-cwd-file` option for the `quit` action to exit without writing the CWD file ([#245])\n- Fallback to built-in code highlighting if `jq` is not installed ([#151])\n- New `realtime` option for the input to support real-time input feedback ([#127])\n- RGBA-16 image preview ([#250])\n- FreeBSD and NetBSD support ([#169], [#178])\n- Trash files on NetBSD ([#251])\n- Image preview support on Mintty (Git Bash) terminal\n\n### Changed\n\n- Make glob expressions case-insensitive by default (with new `\\s` for sensitive) ([#156])\n- Make help items filtering case-insensitive\n\n### Fixed\n\n- `show_hidden` not properly applied to hovered folder ([#124])\n- Notification of file changes in linked directories ([#121])\n- Restore default cursor style when closing input from insert mode\n- Task manager cursor position not reset after task cancellation\n- Redirect clipboard process' stderr to /dev/null\n- Delegate the `SIGINT` signal of processes with `orphan=true` to their parent ([#290])\n- Inconsistent `Shift` key behavior on Unix and Windows ([#174])\n\n### Improved\n\n- Load large folders in chunks ([#117])\n- Reimplemented natural sorting algorithm for ~6x faster case-insensitive sorting\n- Kill process immediately after getting enough JSON or archive preview content to avoid wasting CPU resources ([#128])\n\n## [v0.1.4]\n\n### Added\n\n- Help menu ([#93])\n- Scrollable preview ([#86])\n- Natural sorting ([#82])\n- Windows support\n- New `copy` action to copy file paths to clipboard ([#72])\n- File chooser mode ([#69])\n- Show symlink path ([#67])\n- Respect `$EDITOR` environment variable when opening text files ([#91])\n- Customizable main UI layout ratio ([#76])\n- Allow accessing selected files when running shell commands ([#73])\n- Update MIME type when file changes are detected ([#78])\n- More clipboard backend: `xclip` and `xsel`, and Windows ([#74], [#75])\n- New `cache_dir` option ([#96])\n- New `YAZI_CONFIG_HOME` to specify the configuration directory ([#97])\n- Black Box terminal image preview support ([#99])\n\n### Deprecated\n\n- Deprecate `--cwd` in favor of the positional argument ([#100])\n\n### Fixed\n\n- Make file(1) follow symbolic links when fetching file MIME type ([#77])\n- Wrong height of the select component ([#65])\n- Regression causing UI tearing when previewing images\n- Specify `doNotMoveCursor` to make WezTerm render images sensibly\n\n## [v0.1.3]\n\n### Added\n\n- Bulk rename ([#50])\n- PDF preview and precache ([#18])\n- New `sort_dir_first` option ([#49])\n- Code highlighting supports more languages ([#22])\n- Change the shell CWD on exit with the shell wrapper ([#40])\n- Allow customizing the display name of openers ([#31])\n- New `shell` action ([#24])\n- Command template support for the `shell` action ([#48])\n- Interactive `cd` ([#43])\n- Show the output of running tasks in real time ([#17])\n- Allow using the current directory name as tab name ([#41])\n- Custom status bar separator ([#30])\n- Fallback for opening files when no openers are available\n- Preview files with `inode/empty` and `application/json` MIME types\n- Transparent image support for the Sixel backend ([#14])\n- Refresh image preview after terminal restoration\n- New `micro_workers`, `macro_workers`, and `bizarre_retry` options to control task concurrency ([#53])\n\n### Fixed\n\n- PDF cache cannot be generated with a large `max_width` value ([#28])\n- `show_hidden` option not working ([#47])\n- Wrong task name when `shell` action has no arguments\n\n### Improved\n\n- Make code highlighting discardable ([#20])\n- Improved performance of highlighting large JSON files ([#23])\n- Wrap `stdout` with `BufWriter` to improve image preview performance ([#55])\n- Improved bulk rename performance ([#54])\n\n## [v0.1.2]\n\n### Added\n\n- New `sort` action to change sorting method on the fly ([#7])\n- Which-key component to support multi-key chords ([#4])\n- Hover the cursor over newly created files automatically ([#10])\n- Make folders openable ([#9])\n- Several default goto key mappings\n- Support Überzug++ as the image preview backend for X11/Wayland ([#12])\n- Cut input content to the system clipboard ([#6])\n- Input component supports `undo` for cursor position\n- Support for bracketed paste ([#5])\n\n### Improved\n\n- Cache directory size to avoid redundant calculations ([#11])\n\n## [v0.1.1]\n\n### Added\n\n- Arrow keys are now bound for navigation by default (along with existing Vim-style bindings)\n- Horizontal scrolling support for the `input` component\n- Visual mode for the input component\n- New `yank` and `paste` actions for the input component\n- New `undo` and `redo` actions for the `input` component\n\n### Fixed\n\n- Cannot delete the last character of the input if at the end of the word\n\n### Improved\n\n- Decode images in a dedicated blocking thread to avoid blocking the UI\n\n## [v0.1.0]\n\n### Added\n\n- Preset configurations\n- New `open` action\n- Select component for interactive `open`\n- Plain text and archive preview\n- Search files with `fd` and `rg`\n- Jump around with `fzf` and `zoxide`\n- Flat view for search results\n- Precache images and videos\n- Return to its parents if the CWD no longer exists\n- Confirm when deleting files or exiting\n- Custom status bar colors\n\n### Fixed\n\n- Build errors on Linux\n- Number of remaining tasks cannot be updated\n\n<!-- Link definitions -->\n\n[Unreleased]: https://github.com/sxyazi/yazi/compare/shipped...HEAD\n[v0.1.0]: https://github.com/sxyazi/yazi/releases/tag/v0.1.0\n[v0.1.1]: https://github.com/sxyazi/yazi/compare/v0.1.0...v0.1.1\n[v0.1.2]: https://github.com/sxyazi/yazi/compare/v0.1.1...v0.1.2\n[v0.1.3]: https://github.com/sxyazi/yazi/compare/v0.1.2...v0.1.3\n[v0.1.4]: https://github.com/sxyazi/yazi/compare/v0.1.3...v0.1.4\n[v0.1.5]: https://github.com/sxyazi/yazi/compare/v0.1.4...v0.1.5\n[v0.2.0]: https://github.com/sxyazi/yazi/compare/v0.1.5...v0.2.0\n[v0.2.1]: https://github.com/sxyazi/yazi/compare/v0.2.0...v0.2.1\n[v0.2.2]: https://github.com/sxyazi/yazi/compare/v0.2.1...v0.2.2\n[v0.2.3]: https://github.com/sxyazi/yazi/compare/v0.2.2...v0.2.3\n[v0.2.4]: https://github.com/sxyazi/yazi/compare/v0.2.3...v0.2.4\n[v0.2.5]: https://github.com/sxyazi/yazi/compare/v0.2.4...v0.2.5\n[v0.3.0]: https://github.com/sxyazi/yazi/compare/v0.2.5...v0.3.0\n[v0.3.1]: https://github.com/sxyazi/yazi/compare/v0.3.0...v0.3.1\n[v0.3.2]: https://github.com/sxyazi/yazi/compare/v0.3.1...v0.3.2\n[v0.3.3]: https://github.com/sxyazi/yazi/compare/v0.3.2...v0.3.3\n[v0.4.0]: https://github.com/sxyazi/yazi/compare/v0.3.3...v0.4.0\n[v0.4.1]: https://github.com/sxyazi/yazi/compare/v0.4.0...v0.4.1\n[v0.4.2]: https://github.com/sxyazi/yazi/compare/v0.4.1...v0.4.2\n[v25.2.7]: https://github.com/sxyazi/yazi/compare/v0.4.2...v25.2.7\n[v25.2.11]: https://github.com/sxyazi/yazi/compare/v25.2.7...v25.2.11\n[v25.2.26]: https://github.com/sxyazi/yazi/compare/v25.2.11...v25.2.26\n[v25.3.2]: https://github.com/sxyazi/yazi/compare/v25.2.26...v25.3.2\n[v25.4.8]: https://github.com/sxyazi/yazi/compare/v25.3.2...v25.4.8\n[v25.5.28]: https://github.com/sxyazi/yazi/compare/v25.4.8...v25.5.28\n[v25.5.31]: https://github.com/sxyazi/yazi/compare/v25.5.28...v25.5.31\n[v25.12.29]: https://github.com/sxyazi/yazi/compare/v25.5.31...v25.12.29\n[v26.1.4]: https://github.com/sxyazi/yazi/compare/v25.12.29...v26.1.4\n[v26.1.22]: https://github.com/sxyazi/yazi/compare/v26.1.4...v26.1.22\n[#4]: https://github.com/sxyazi/yazi/pull/4\n[#5]: https://github.com/sxyazi/yazi/pull/5\n[#6]: https://github.com/sxyazi/yazi/pull/6\n[#7]: https://github.com/sxyazi/yazi/pull/7\n[#9]: https://github.com/sxyazi/yazi/pull/9\n[#10]: https://github.com/sxyazi/yazi/pull/10\n[#11]: https://github.com/sxyazi/yazi/pull/11\n[#12]: https://github.com/sxyazi/yazi/pull/12\n[#14]: https://github.com/sxyazi/yazi/pull/14\n[#17]: https://github.com/sxyazi/yazi/pull/17\n[#18]: https://github.com/sxyazi/yazi/pull/18\n[#20]: https://github.com/sxyazi/yazi/pull/20\n[#22]: https://github.com/sxyazi/yazi/pull/22\n[#23]: https://github.com/sxyazi/yazi/pull/23\n[#24]: https://github.com/sxyazi/yazi/pull/24\n[#28]: https://github.com/sxyazi/yazi/pull/28\n[#30]: https://github.com/sxyazi/yazi/pull/30\n[#31]: https://github.com/sxyazi/yazi/pull/31\n[#40]: https://github.com/sxyazi/yazi/pull/40\n[#41]: https://github.com/sxyazi/yazi/pull/41\n[#43]: https://github.com/sxyazi/yazi/pull/43\n[#47]: https://github.com/sxyazi/yazi/pull/47\n[#48]: https://github.com/sxyazi/yazi/pull/48\n[#49]: https://github.com/sxyazi/yazi/pull/49\n[#50]: https://github.com/sxyazi/yazi/pull/50\n[#53]: https://github.com/sxyazi/yazi/pull/53\n[#54]: https://github.com/sxyazi/yazi/pull/54\n[#55]: https://github.com/sxyazi/yazi/pull/55\n[#65]: https://github.com/sxyazi/yazi/pull/65\n[#67]: https://github.com/sxyazi/yazi/pull/67\n[#69]: https://github.com/sxyazi/yazi/pull/69\n[#72]: https://github.com/sxyazi/yazi/pull/72\n[#73]: https://github.com/sxyazi/yazi/pull/73\n[#74]: https://github.com/sxyazi/yazi/pull/74\n[#75]: https://github.com/sxyazi/yazi/pull/75\n[#76]: https://github.com/sxyazi/yazi/pull/76\n[#77]: https://github.com/sxyazi/yazi/pull/77\n[#78]: https://github.com/sxyazi/yazi/pull/78\n[#82]: https://github.com/sxyazi/yazi/pull/82\n[#86]: https://github.com/sxyazi/yazi/pull/86\n[#91]: https://github.com/sxyazi/yazi/pull/91\n[#93]: https://github.com/sxyazi/yazi/pull/93\n[#96]: https://github.com/sxyazi/yazi/pull/96\n[#97]: https://github.com/sxyazi/yazi/pull/97\n[#99]: https://github.com/sxyazi/yazi/pull/99\n[#100]: https://github.com/sxyazi/yazi/pull/100\n[#104]: https://github.com/sxyazi/yazi/pull/104\n[#117]: https://github.com/sxyazi/yazi/pull/117\n[#120]: https://github.com/sxyazi/yazi/pull/120\n[#121]: https://github.com/sxyazi/yazi/pull/121\n[#124]: https://github.com/sxyazi/yazi/pull/124\n[#127]: https://github.com/sxyazi/yazi/pull/127\n[#128]: https://github.com/sxyazi/yazi/pull/128\n[#131]: https://github.com/sxyazi/yazi/pull/131\n[#147]: https://github.com/sxyazi/yazi/pull/147\n[#151]: https://github.com/sxyazi/yazi/pull/151\n[#152]: https://github.com/sxyazi/yazi/pull/152\n[#154]: https://github.com/sxyazi/yazi/pull/154\n[#155]: https://github.com/sxyazi/yazi/pull/155\n[#156]: https://github.com/sxyazi/yazi/pull/156\n[#160]: https://github.com/sxyazi/yazi/pull/160\n[#161]: https://github.com/sxyazi/yazi/pull/161\n[#167]: https://github.com/sxyazi/yazi/pull/167\n[#169]: https://github.com/sxyazi/yazi/pull/169\n[#174]: https://github.com/sxyazi/yazi/pull/174\n[#178]: https://github.com/sxyazi/yazi/pull/178\n[#179]: https://github.com/sxyazi/yazi/pull/179\n[#205]: https://github.com/sxyazi/yazi/pull/205\n[#208]: https://github.com/sxyazi/yazi/pull/208\n[#209]: https://github.com/sxyazi/yazi/pull/209\n[#211]: https://github.com/sxyazi/yazi/pull/211\n[#213]: https://github.com/sxyazi/yazi/pull/213\n[#216]: https://github.com/sxyazi/yazi/pull/216\n[#240]: https://github.com/sxyazi/yazi/pull/240\n[#241]: https://github.com/sxyazi/yazi/pull/241\n[#245]: https://github.com/sxyazi/yazi/pull/245\n[#250]: https://github.com/sxyazi/yazi/pull/250\n[#251]: https://github.com/sxyazi/yazi/pull/251\n[#278]: https://github.com/sxyazi/yazi/pull/278\n[#289]: https://github.com/sxyazi/yazi/pull/289\n[#290]: https://github.com/sxyazi/yazi/pull/290\n[#291]: https://github.com/sxyazi/yazi/pull/291\n[#309]: https://github.com/sxyazi/yazi/pull/309\n[#312]: https://github.com/sxyazi/yazi/pull/312\n[#313]: https://github.com/sxyazi/yazi/pull/313\n[#315]: https://github.com/sxyazi/yazi/pull/315\n[#320]: https://github.com/sxyazi/yazi/pull/320\n[#324]: https://github.com/sxyazi/yazi/pull/324\n[#329]: https://github.com/sxyazi/yazi/pull/329\n[#341]: https://github.com/sxyazi/yazi/pull/341\n[#342]: https://github.com/sxyazi/yazi/pull/342\n[#345]: https://github.com/sxyazi/yazi/pull/345\n[#349]: https://github.com/sxyazi/yazi/pull/349\n[#352]: https://github.com/sxyazi/yazi/pull/352\n[#353]: https://github.com/sxyazi/yazi/pull/353\n[#357]: https://github.com/sxyazi/yazi/pull/357\n[#358]: https://github.com/sxyazi/yazi/pull/358\n[#360]: https://github.com/sxyazi/yazi/pull/360\n[#361]: https://github.com/sxyazi/yazi/pull/361\n[#365]: https://github.com/sxyazi/yazi/pull/365\n[#369]: https://github.com/sxyazi/yazi/pull/369\n[#375]: https://github.com/sxyazi/yazi/pull/375\n[#376]: https://github.com/sxyazi/yazi/pull/376\n[#382]: https://github.com/sxyazi/yazi/pull/382\n[#386]: https://github.com/sxyazi/yazi/pull/386\n[#401]: https://github.com/sxyazi/yazi/pull/401\n[#405]: https://github.com/sxyazi/yazi/pull/405\n[#430]: https://github.com/sxyazi/yazi/pull/430\n[#436]: https://github.com/sxyazi/yazi/pull/436\n[#447]: https://github.com/sxyazi/yazi/pull/447\n[#454]: https://github.com/sxyazi/yazi/pull/454\n[#462]: https://github.com/sxyazi/yazi/pull/462\n[#467]: https://github.com/sxyazi/yazi/pull/467\n[#468]: https://github.com/sxyazi/yazi/pull/468\n[#469]: https://github.com/sxyazi/yazi/pull/469\n[#488]: https://github.com/sxyazi/yazi/pull/488\n[#503]: https://github.com/sxyazi/yazi/pull/503\n[#509]: https://github.com/sxyazi/yazi/pull/509\n[#510]: https://github.com/sxyazi/yazi/pull/510\n[#513]: https://github.com/sxyazi/yazi/pull/513\n[#514]: https://github.com/sxyazi/yazi/pull/514\n[#519]: https://github.com/sxyazi/yazi/pull/519\n[#529]: https://github.com/sxyazi/yazi/pull/529\n[#531]: https://github.com/sxyazi/yazi/pull/531\n[#534]: https://github.com/sxyazi/yazi/pull/534\n[#543]: https://github.com/sxyazi/yazi/pull/543\n[#546]: https://github.com/sxyazi/yazi/pull/546\n[#550]: https://github.com/sxyazi/yazi/pull/550\n[#556]: https://github.com/sxyazi/yazi/pull/556\n[#558]: https://github.com/sxyazi/yazi/pull/558\n[#561]: https://github.com/sxyazi/yazi/pull/561\n[#565]: https://github.com/sxyazi/yazi/pull/565\n[#569]: https://github.com/sxyazi/yazi/pull/569\n[#571]: https://github.com/sxyazi/yazi/pull/571\n[#575]: https://github.com/sxyazi/yazi/pull/575\n[#576]: https://github.com/sxyazi/yazi/pull/576\n[#581]: https://github.com/sxyazi/yazi/pull/581\n[#585]: https://github.com/sxyazi/yazi/pull/585\n[#586]: https://github.com/sxyazi/yazi/pull/586\n[#587]: https://github.com/sxyazi/yazi/pull/587\n[#590]: https://github.com/sxyazi/yazi/pull/590\n[#599]: https://github.com/sxyazi/yazi/pull/599\n[#600]: https://github.com/sxyazi/yazi/pull/600\n[#607]: https://github.com/sxyazi/yazi/pull/607\n[#617]: https://github.com/sxyazi/yazi/pull/617\n[#628]: https://github.com/sxyazi/yazi/pull/628\n[#632]: https://github.com/sxyazi/yazi/pull/632\n[#643]: https://github.com/sxyazi/yazi/pull/643\n[#644]: https://github.com/sxyazi/yazi/pull/644\n[#646]: https://github.com/sxyazi/yazi/pull/646\n[#649]: https://github.com/sxyazi/yazi/pull/649\n[#659]: https://github.com/sxyazi/yazi/pull/659\n[#662]: https://github.com/sxyazi/yazi/pull/662\n[#665]: https://github.com/sxyazi/yazi/pull/665\n[#670]: https://github.com/sxyazi/yazi/pull/670\n[#674]: https://github.com/sxyazi/yazi/pull/674\n[#679]: https://github.com/sxyazi/yazi/pull/679\n[#683]: https://github.com/sxyazi/yazi/pull/683\n[#687]: https://github.com/sxyazi/yazi/pull/687\n[#689]: https://github.com/sxyazi/yazi/pull/689\n[#693]: https://github.com/sxyazi/yazi/pull/693\n[#710]: https://github.com/sxyazi/yazi/pull/710\n[#721]: https://github.com/sxyazi/yazi/pull/721\n[#738]: https://github.com/sxyazi/yazi/pull/738\n[#749]: https://github.com/sxyazi/yazi/pull/749\n[#751]: https://github.com/sxyazi/yazi/pull/751\n[#752]: https://github.com/sxyazi/yazi/pull/752\n[#753]: https://github.com/sxyazi/yazi/pull/753\n[#754]: https://github.com/sxyazi/yazi/pull/754\n[#759]: https://github.com/sxyazi/yazi/pull/759\n[#762]: https://github.com/sxyazi/yazi/pull/762\n[#763]: https://github.com/sxyazi/yazi/pull/763\n[#775]: https://github.com/sxyazi/yazi/pull/775\n[#779]: https://github.com/sxyazi/yazi/pull/779\n[#780]: https://github.com/sxyazi/yazi/pull/780\n[#786]: https://github.com/sxyazi/yazi/pull/786\n[#788]: https://github.com/sxyazi/yazi/pull/788\n[#792]: https://github.com/sxyazi/yazi/pull/792\n[#794]: https://github.com/sxyazi/yazi/pull/794\n[#799]: https://github.com/sxyazi/yazi/pull/799\n[#812]: https://github.com/sxyazi/yazi/pull/812\n[#824]: https://github.com/sxyazi/yazi/pull/824\n[#826]: https://github.com/sxyazi/yazi/pull/826\n[#835]: https://github.com/sxyazi/yazi/pull/835\n[#837]: https://github.com/sxyazi/yazi/pull/837\n[#843]: https://github.com/sxyazi/yazi/pull/843\n[#846]: https://github.com/sxyazi/yazi/pull/846\n[#849]: https://github.com/sxyazi/yazi/pull/849\n[#853]: https://github.com/sxyazi/yazi/pull/853\n[#855]: https://github.com/sxyazi/yazi/pull/855\n[#861]: https://github.com/sxyazi/yazi/pull/861\n[#867]: https://github.com/sxyazi/yazi/pull/867\n[#868]: https://github.com/sxyazi/yazi/pull/868\n[#871]: https://github.com/sxyazi/yazi/pull/871\n[#877]: https://github.com/sxyazi/yazi/pull/877\n[#879]: https://github.com/sxyazi/yazi/pull/879\n[#880]: https://github.com/sxyazi/yazi/pull/880\n[#881]: https://github.com/sxyazi/yazi/pull/881\n[#884]: https://github.com/sxyazi/yazi/pull/884\n[#887]: https://github.com/sxyazi/yazi/pull/887\n[#895]: https://github.com/sxyazi/yazi/pull/895\n[#900]: https://github.com/sxyazi/yazi/pull/900\n[#908]: https://github.com/sxyazi/yazi/pull/908\n[#909]: https://github.com/sxyazi/yazi/pull/909\n[#910]: https://github.com/sxyazi/yazi/pull/910\n[#913]: https://github.com/sxyazi/yazi/pull/913\n[#917]: https://github.com/sxyazi/yazi/pull/917\n[#920]: https://github.com/sxyazi/yazi/pull/920\n[#925]: https://github.com/sxyazi/yazi/pull/925\n[#926]: https://github.com/sxyazi/yazi/pull/926\n[#928]: https://github.com/sxyazi/yazi/pull/928\n[#931]: https://github.com/sxyazi/yazi/pull/931\n[#933]: https://github.com/sxyazi/yazi/pull/933\n[#937]: https://github.com/sxyazi/yazi/pull/937\n[#940]: https://github.com/sxyazi/yazi/pull/940\n[#944]: https://github.com/sxyazi/yazi/pull/944\n[#948]: https://github.com/sxyazi/yazi/pull/948\n[#958]: https://github.com/sxyazi/yazi/pull/958\n[#975]: https://github.com/sxyazi/yazi/pull/975\n[#977]: https://github.com/sxyazi/yazi/pull/977\n[#980]: https://github.com/sxyazi/yazi/pull/980\n[#985]: https://github.com/sxyazi/yazi/pull/985\n[#997]: https://github.com/sxyazi/yazi/pull/997\n[#1003]: https://github.com/sxyazi/yazi/pull/1003\n[#1004]: https://github.com/sxyazi/yazi/pull/1004\n[#1005]: https://github.com/sxyazi/yazi/pull/1005\n[#1025]: https://github.com/sxyazi/yazi/pull/1025\n[#1033]: https://github.com/sxyazi/yazi/pull/1033\n[#1038]: https://github.com/sxyazi/yazi/pull/1038\n[#1048]: https://github.com/sxyazi/yazi/pull/1048\n[#1050]: https://github.com/sxyazi/yazi/pull/1050\n[#1053]: https://github.com/sxyazi/yazi/pull/1053\n[#1069]: https://github.com/sxyazi/yazi/pull/1069\n[#1070]: https://github.com/sxyazi/yazi/pull/1070\n[#1081]: https://github.com/sxyazi/yazi/pull/1081\n[#1082]: https://github.com/sxyazi/yazi/pull/1082\n[#1086]: https://github.com/sxyazi/yazi/pull/1086\n[#1094]: https://github.com/sxyazi/yazi/pull/1094\n[#1110]: https://github.com/sxyazi/yazi/pull/1110\n[#1111]: https://github.com/sxyazi/yazi/pull/1111\n[#1139]: https://github.com/sxyazi/yazi/pull/1139\n[#1151]: https://github.com/sxyazi/yazi/pull/1151\n[#1159]: https://github.com/sxyazi/yazi/pull/1159\n[#1167]: https://github.com/sxyazi/yazi/pull/1167\n[#1169]: https://github.com/sxyazi/yazi/pull/1169\n[#1185]: https://github.com/sxyazi/yazi/pull/1185\n[#1220]: https://github.com/sxyazi/yazi/pull/1220\n[#1227]: https://github.com/sxyazi/yazi/pull/1227\n[#1232]: https://github.com/sxyazi/yazi/pull/1232\n[#1238]: https://github.com/sxyazi/yazi/pull/1238\n[#1241]: https://github.com/sxyazi/yazi/pull/1241\n[#1249]: https://github.com/sxyazi/yazi/pull/1249\n[#1268]: https://github.com/sxyazi/yazi/pull/1268\n[#1270]: https://github.com/sxyazi/yazi/pull/1270\n[#1291]: https://github.com/sxyazi/yazi/pull/1291\n[#1295]: https://github.com/sxyazi/yazi/pull/1295\n[#1305]: https://github.com/sxyazi/yazi/pull/1305\n[#1321]: https://github.com/sxyazi/yazi/pull/1321\n[#1361]: https://github.com/sxyazi/yazi/pull/1361\n[#1395]: https://github.com/sxyazi/yazi/pull/1395\n[#1412]: https://github.com/sxyazi/yazi/pull/1412\n[#1422]: https://github.com/sxyazi/yazi/pull/1422\n[#1428]: https://github.com/sxyazi/yazi/pull/1428\n[#1431]: https://github.com/sxyazi/yazi/pull/1431\n[#1434]: https://github.com/sxyazi/yazi/pull/1434\n[#1439]: https://github.com/sxyazi/yazi/pull/1439\n[#1443]: https://github.com/sxyazi/yazi/pull/1443\n[#1446]: https://github.com/sxyazi/yazi/pull/1446\n[#1448]: https://github.com/sxyazi/yazi/pull/1448\n[#1451]: https://github.com/sxyazi/yazi/pull/1451\n[#1461]: https://github.com/sxyazi/yazi/pull/1461\n[#1464]: https://github.com/sxyazi/yazi/pull/1464\n[#1467]: https://github.com/sxyazi/yazi/pull/1467\n[#1468]: https://github.com/sxyazi/yazi/pull/1468\n[#1473]: https://github.com/sxyazi/yazi/pull/1473\n[#1474]: https://github.com/sxyazi/yazi/pull/1474\n[#1482]: https://github.com/sxyazi/yazi/pull/1482\n[#1497]: https://github.com/sxyazi/yazi/pull/1497\n[#1500]: https://github.com/sxyazi/yazi/pull/1500\n[#1505]: https://github.com/sxyazi/yazi/pull/1505\n[#1512]: https://github.com/sxyazi/yazi/pull/1512\n[#1528]: https://github.com/sxyazi/yazi/pull/1528\n[#1541]: https://github.com/sxyazi/yazi/pull/1541\n[#1542]: https://github.com/sxyazi/yazi/pull/1542\n[#1550]: https://github.com/sxyazi/yazi/pull/1550\n[#1551]: https://github.com/sxyazi/yazi/pull/1551\n[#1556]: https://github.com/sxyazi/yazi/pull/1556\n[#1562]: https://github.com/sxyazi/yazi/pull/1562\n[#1566]: https://github.com/sxyazi/yazi/pull/1566\n[#1568]: https://github.com/sxyazi/yazi/pull/1568\n[#1574]: https://github.com/sxyazi/yazi/pull/1574\n[#1583]: https://github.com/sxyazi/yazi/pull/1583\n[#1588]: https://github.com/sxyazi/yazi/pull/1588\n[#1590]: https://github.com/sxyazi/yazi/pull/1590\n[#1591]: https://github.com/sxyazi/yazi/pull/1591\n[#1605]: https://github.com/sxyazi/yazi/pull/1605\n[#1614]: https://github.com/sxyazi/yazi/pull/1614\n[#1622]: https://github.com/sxyazi/yazi/pull/1622\n[#1639]: https://github.com/sxyazi/yazi/pull/1639\n[#1648]: https://github.com/sxyazi/yazi/pull/1648\n[#1650]: https://github.com/sxyazi/yazi/pull/1650\n[#1652]: https://github.com/sxyazi/yazi/pull/1652\n[#1666]: https://github.com/sxyazi/yazi/pull/1666\n[#1667]: https://github.com/sxyazi/yazi/pull/1667\n[#1680]: https://github.com/sxyazi/yazi/pull/1680\n[#1682]: https://github.com/sxyazi/yazi/pull/1682\n[#1689]: https://github.com/sxyazi/yazi/pull/1689\n[#1695]: https://github.com/sxyazi/yazi/pull/1695\n[#1704]: https://github.com/sxyazi/yazi/pull/1704\n[#1737]: https://github.com/sxyazi/yazi/pull/1737\n[#1745]: https://github.com/sxyazi/yazi/pull/1745\n[#1761]: https://github.com/sxyazi/yazi/pull/1761\n[#1762]: https://github.com/sxyazi/yazi/pull/1762\n[#1772]: https://github.com/sxyazi/yazi/pull/1772\n[#1773]: https://github.com/sxyazi/yazi/pull/1773\n[#1776]: https://github.com/sxyazi/yazi/pull/1776\n[#1782]: https://github.com/sxyazi/yazi/pull/1782\n[#1784]: https://github.com/sxyazi/yazi/pull/1784\n[#1789]: https://github.com/sxyazi/yazi/pull/1789\n[#1792]: https://github.com/sxyazi/yazi/pull/1792\n[#1801]: https://github.com/sxyazi/yazi/pull/1801\n[#1802]: https://github.com/sxyazi/yazi/pull/1802\n[#1807]: https://github.com/sxyazi/yazi/pull/1807\n[#1808]: https://github.com/sxyazi/yazi/pull/1808\n[#1816]: https://github.com/sxyazi/yazi/pull/1816\n[#1832]: https://github.com/sxyazi/yazi/pull/1832\n[#1833]: https://github.com/sxyazi/yazi/pull/1833\n[#1846]: https://github.com/sxyazi/yazi/pull/1846\n[#1849]: https://github.com/sxyazi/yazi/pull/1849\n[#1863]: https://github.com/sxyazi/yazi/pull/1863\n[#1877]: https://github.com/sxyazi/yazi/pull/1877\n[#1882]: https://github.com/sxyazi/yazi/pull/1882\n[#1884]: https://github.com/sxyazi/yazi/pull/1884\n[#1891]: https://github.com/sxyazi/yazi/pull/1891\n[#1903]: https://github.com/sxyazi/yazi/pull/1903\n[#1926]: https://github.com/sxyazi/yazi/pull/1926\n[#1927]: https://github.com/sxyazi/yazi/pull/1927\n[#1928]: https://github.com/sxyazi/yazi/pull/1928\n[#1939]: https://github.com/sxyazi/yazi/pull/1939\n[#1945]: https://github.com/sxyazi/yazi/pull/1945\n[#1946]: https://github.com/sxyazi/yazi/pull/1946\n[#1953]: https://github.com/sxyazi/yazi/pull/1953\n[#1962]: https://github.com/sxyazi/yazi/pull/1962\n[#1966]: https://github.com/sxyazi/yazi/pull/1966\n[#1973]: https://github.com/sxyazi/yazi/pull/1973\n[#1979]: https://github.com/sxyazi/yazi/pull/1979\n[#1980]: https://github.com/sxyazi/yazi/pull/1980\n[#1982]: https://github.com/sxyazi/yazi/pull/1982\n[#1984]: https://github.com/sxyazi/yazi/pull/1984\n[#1995]: https://github.com/sxyazi/yazi/pull/1995\n[#2002]: https://github.com/sxyazi/yazi/pull/2002\n[#2003]: https://github.com/sxyazi/yazi/pull/2003\n[#2004]: https://github.com/sxyazi/yazi/pull/2004\n[#2006]: https://github.com/sxyazi/yazi/pull/2006\n[#2014]: https://github.com/sxyazi/yazi/pull/2014\n[#2017]: https://github.com/sxyazi/yazi/pull/2017\n[#2020]: https://github.com/sxyazi/yazi/pull/2020\n[#2025]: https://github.com/sxyazi/yazi/pull/2025\n[#2030]: https://github.com/sxyazi/yazi/pull/2030\n[#2041]: https://github.com/sxyazi/yazi/pull/2041\n[#2043]: https://github.com/sxyazi/yazi/pull/2043\n[#2052]: https://github.com/sxyazi/yazi/pull/2052\n[#2058]: https://github.com/sxyazi/yazi/pull/2058\n[#2060]: https://github.com/sxyazi/yazi/pull/2060\n[#2064]: https://github.com/sxyazi/yazi/pull/2064\n[#2068]: https://github.com/sxyazi/yazi/pull/2068\n[#2071]: https://github.com/sxyazi/yazi/pull/2071\n[#2072]: https://github.com/sxyazi/yazi/pull/2072\n[#2077]: https://github.com/sxyazi/yazi/pull/2077\n[#2093]: https://github.com/sxyazi/yazi/pull/2093\n[#2095]: https://github.com/sxyazi/yazi/pull/2095\n[#2105]: https://github.com/sxyazi/yazi/pull/2105\n[#2110]: https://github.com/sxyazi/yazi/pull/2110\n[#2122]: https://github.com/sxyazi/yazi/pull/2122\n[#2132]: https://github.com/sxyazi/yazi/pull/2132\n[#2143]: https://github.com/sxyazi/yazi/pull/2143\n[#2149]: https://github.com/sxyazi/yazi/pull/2149\n[#2168]: https://github.com/sxyazi/yazi/pull/2168\n[#2173]: https://github.com/sxyazi/yazi/pull/2173\n[#2181]: https://github.com/sxyazi/yazi/pull/2181\n[#2185]: https://github.com/sxyazi/yazi/pull/2185\n[#2186]: https://github.com/sxyazi/yazi/pull/2186\n[#2188]: https://github.com/sxyazi/yazi/pull/2188\n[#2199]: https://github.com/sxyazi/yazi/pull/2199\n[#2205]: https://github.com/sxyazi/yazi/pull/2205\n[#2210]: https://github.com/sxyazi/yazi/pull/2210\n[#2224]: https://github.com/sxyazi/yazi/pull/2224\n[#2233]: https://github.com/sxyazi/yazi/pull/2233\n[#2234]: https://github.com/sxyazi/yazi/pull/2234\n[#2242]: https://github.com/sxyazi/yazi/pull/2242\n[#2245]: https://github.com/sxyazi/yazi/pull/2245\n[#2247]: https://github.com/sxyazi/yazi/pull/2247\n[#2253]: https://github.com/sxyazi/yazi/pull/2253\n[#2257]: https://github.com/sxyazi/yazi/pull/2257\n[#2290]: https://github.com/sxyazi/yazi/pull/2290\n[#2294]: https://github.com/sxyazi/yazi/pull/2294\n[#2298]: https://github.com/sxyazi/yazi/pull/2298\n[#2299]: https://github.com/sxyazi/yazi/pull/2299\n[#2310]: https://github.com/sxyazi/yazi/pull/2310\n[#2313]: https://github.com/sxyazi/yazi/pull/2313\n[#2314]: https://github.com/sxyazi/yazi/pull/2314\n[#2319]: https://github.com/sxyazi/yazi/pull/2319\n[#2321]: https://github.com/sxyazi/yazi/pull/2321\n[#2326]: https://github.com/sxyazi/yazi/pull/2326\n[#2327]: https://github.com/sxyazi/yazi/pull/2327\n[#2331]: https://github.com/sxyazi/yazi/pull/2331\n[#2337]: https://github.com/sxyazi/yazi/pull/2337\n[#2343]: https://github.com/sxyazi/yazi/pull/2343\n[#2355]: https://github.com/sxyazi/yazi/pull/2355\n[#2366]: https://github.com/sxyazi/yazi/pull/2366\n[#2383]: https://github.com/sxyazi/yazi/pull/2383\n[#2389]: https://github.com/sxyazi/yazi/pull/2389\n[#2391]: https://github.com/sxyazi/yazi/pull/2391\n[#2392]: https://github.com/sxyazi/yazi/pull/2392\n[#2393]: https://github.com/sxyazi/yazi/pull/2393\n[#2397]: https://github.com/sxyazi/yazi/pull/2397\n[#2399]: https://github.com/sxyazi/yazi/pull/2399\n[#2403]: https://github.com/sxyazi/yazi/pull/2403\n[#2405]: https://github.com/sxyazi/yazi/pull/2405\n[#2413]: https://github.com/sxyazi/yazi/pull/2413\n[#2418]: https://github.com/sxyazi/yazi/pull/2418\n[#2425]: https://github.com/sxyazi/yazi/pull/2425\n[#2427]: https://github.com/sxyazi/yazi/pull/2427\n[#2431]: https://github.com/sxyazi/yazi/pull/2431\n[#2439]: https://github.com/sxyazi/yazi/pull/2439\n[#2442]: https://github.com/sxyazi/yazi/pull/2442\n[#2444]: https://github.com/sxyazi/yazi/pull/2444\n[#2449]: https://github.com/sxyazi/yazi/pull/2449\n[#2452]: https://github.com/sxyazi/yazi/pull/2452\n[#2456]: https://github.com/sxyazi/yazi/pull/2456\n[#2458]: https://github.com/sxyazi/yazi/pull/2458\n[#2461]: https://github.com/sxyazi/yazi/pull/2461\n[#2471]: https://github.com/sxyazi/yazi/pull/2471\n[#2476]: https://github.com/sxyazi/yazi/pull/2476\n[#2485]: https://github.com/sxyazi/yazi/pull/2485\n[#2487]: https://github.com/sxyazi/yazi/pull/2487\n[#2490]: https://github.com/sxyazi/yazi/pull/2490\n[#2492]: https://github.com/sxyazi/yazi/pull/2492\n[#2494]: https://github.com/sxyazi/yazi/pull/2494\n[#2503]: https://github.com/sxyazi/yazi/pull/2503\n[#2508]: https://github.com/sxyazi/yazi/pull/2508\n[#2522]: https://github.com/sxyazi/yazi/pull/2522\n[#2526]: https://github.com/sxyazi/yazi/pull/2526\n[#2527]: https://github.com/sxyazi/yazi/pull/2527\n[#2530]: https://github.com/sxyazi/yazi/pull/2530\n[#2533]: https://github.com/sxyazi/yazi/pull/2533\n[#2540]: https://github.com/sxyazi/yazi/pull/2540\n[#2543]: https://github.com/sxyazi/yazi/pull/2543\n[#2546]: https://github.com/sxyazi/yazi/pull/2546\n[#2553]: https://github.com/sxyazi/yazi/pull/2553\n[#2560]: https://github.com/sxyazi/yazi/pull/2560\n[#2572]: https://github.com/sxyazi/yazi/pull/2572\n[#2574]: https://github.com/sxyazi/yazi/pull/2574\n[#2578]: https://github.com/sxyazi/yazi/pull/2578\n[#2581]: https://github.com/sxyazi/yazi/pull/2581\n[#2589]: https://github.com/sxyazi/yazi/pull/2589\n[#2594]: https://github.com/sxyazi/yazi/pull/2594\n[#2602]: https://github.com/sxyazi/yazi/pull/2602\n[#2609]: https://github.com/sxyazi/yazi/pull/2609\n[#2636]: https://github.com/sxyazi/yazi/pull/2636\n[#2640]: https://github.com/sxyazi/yazi/pull/2640\n[#2653]: https://github.com/sxyazi/yazi/pull/2653\n[#2657]: https://github.com/sxyazi/yazi/pull/2657\n[#2664]: https://github.com/sxyazi/yazi/pull/2664\n[#2675]: https://github.com/sxyazi/yazi/pull/2675\n[#2678]: https://github.com/sxyazi/yazi/pull/2678\n[#2683]: https://github.com/sxyazi/yazi/pull/2683\n[#2691]: https://github.com/sxyazi/yazi/pull/2691\n[#2695]: https://github.com/sxyazi/yazi/pull/2695\n[#2696]: https://github.com/sxyazi/yazi/pull/2696\n[#2697]: https://github.com/sxyazi/yazi/pull/2697\n[#2700]: https://github.com/sxyazi/yazi/pull/2700\n[#2706]: https://github.com/sxyazi/yazi/pull/2706\n[#2707]: https://github.com/sxyazi/yazi/pull/2707\n[#2709]: https://github.com/sxyazi/yazi/pull/2709\n[#2723]: https://github.com/sxyazi/yazi/pull/2723\n[#2734]: https://github.com/sxyazi/yazi/pull/2734\n[#2743]: https://github.com/sxyazi/yazi/pull/2743\n[#2745]: https://github.com/sxyazi/yazi/pull/2745\n[#2752]: https://github.com/sxyazi/yazi/pull/2752\n[#2753]: https://github.com/sxyazi/yazi/pull/2753\n[#2754]: https://github.com/sxyazi/yazi/pull/2754\n[#2759]: https://github.com/sxyazi/yazi/pull/2759\n[#2764]: https://github.com/sxyazi/yazi/pull/2764\n[#2765]: https://github.com/sxyazi/yazi/pull/2765\n[#2769]: https://github.com/sxyazi/yazi/pull/2769\n[#2770]: https://github.com/sxyazi/yazi/pull/2770\n[#2778]: https://github.com/sxyazi/yazi/pull/2778\n[#2802]: https://github.com/sxyazi/yazi/pull/2802\n[#2803]: https://github.com/sxyazi/yazi/pull/2803\n[#2807]: https://github.com/sxyazi/yazi/pull/2807\n[#2810]: https://github.com/sxyazi/yazi/pull/2810\n[#2811]: https://github.com/sxyazi/yazi/pull/2811\n[#2814]: https://github.com/sxyazi/yazi/pull/2814\n[#2820]: https://github.com/sxyazi/yazi/pull/2820\n[#2834]: https://github.com/sxyazi/yazi/pull/2834\n[#2841]: https://github.com/sxyazi/yazi/pull/2841\n[#2843]: https://github.com/sxyazi/yazi/pull/2843\n[#2849]: https://github.com/sxyazi/yazi/pull/2849\n[#2855]: https://github.com/sxyazi/yazi/pull/2855\n[#2861]: https://github.com/sxyazi/yazi/pull/2861\n[#2862]: https://github.com/sxyazi/yazi/pull/2862\n[#2864]: https://github.com/sxyazi/yazi/pull/2864\n[#2875]: https://github.com/sxyazi/yazi/pull/2875\n[#2879]: https://github.com/sxyazi/yazi/pull/2879\n[#2880]: https://github.com/sxyazi/yazi/pull/2880\n[#2884]: https://github.com/sxyazi/yazi/pull/2884\n[#2889]: https://github.com/sxyazi/yazi/pull/2889\n[#2890]: https://github.com/sxyazi/yazi/pull/2890\n[#2895]: https://github.com/sxyazi/yazi/pull/2895\n[#2904]: https://github.com/sxyazi/yazi/pull/2904\n[#2906]: https://github.com/sxyazi/yazi/pull/2906\n[#2914]: https://github.com/sxyazi/yazi/pull/2914\n[#2915]: https://github.com/sxyazi/yazi/pull/2915\n[#2917]: https://github.com/sxyazi/yazi/pull/2917\n[#2921]: https://github.com/sxyazi/yazi/pull/2921\n[#2925]: https://github.com/sxyazi/yazi/pull/2925\n[#2927]: https://github.com/sxyazi/yazi/pull/2927\n[#2931]: https://github.com/sxyazi/yazi/pull/2931\n[#2932]: https://github.com/sxyazi/yazi/pull/2932\n[#2935]: https://github.com/sxyazi/yazi/pull/2935\n[#2939]: https://github.com/sxyazi/yazi/pull/2939\n[#2941]: https://github.com/sxyazi/yazi/pull/2941\n[#2958]: https://github.com/sxyazi/yazi/pull/2958\n[#2959]: https://github.com/sxyazi/yazi/pull/2959\n[#2964]: https://github.com/sxyazi/yazi/pull/2964\n[#2984]: https://github.com/sxyazi/yazi/pull/2984\n[#2997]: https://github.com/sxyazi/yazi/pull/2997\n[#3005]: https://github.com/sxyazi/yazi/pull/3005\n[#3008]: https://github.com/sxyazi/yazi/pull/3008\n[#3023]: https://github.com/sxyazi/yazi/pull/3023\n[#3034]: https://github.com/sxyazi/yazi/pull/3034\n[#3035]: https://github.com/sxyazi/yazi/pull/3035\n[#3037]: https://github.com/sxyazi/yazi/pull/3037\n[#3038]: https://github.com/sxyazi/yazi/pull/3038\n[#3059]: https://github.com/sxyazi/yazi/pull/3059\n[#3067]: https://github.com/sxyazi/yazi/pull/3067\n[#3077]: https://github.com/sxyazi/yazi/pull/3077\n[#3083]: https://github.com/sxyazi/yazi/pull/3083\n[#3084]: https://github.com/sxyazi/yazi/pull/3084\n[#3091]: https://github.com/sxyazi/yazi/pull/3091\n[#3094]: https://github.com/sxyazi/yazi/pull/3094\n[#3108]: https://github.com/sxyazi/yazi/pull/3108\n[#3117]: https://github.com/sxyazi/yazi/pull/3117\n[#3121]: https://github.com/sxyazi/yazi/pull/3121\n[#3128]: https://github.com/sxyazi/yazi/pull/3128\n[#3131]: https://github.com/sxyazi/yazi/pull/3131\n[#3134]: https://github.com/sxyazi/yazi/pull/3134\n[#3141]: https://github.com/sxyazi/yazi/pull/3141\n[#3154]: https://github.com/sxyazi/yazi/pull/3154\n[#3166]: https://github.com/sxyazi/yazi/pull/3166\n[#3169]: https://github.com/sxyazi/yazi/pull/3169\n[#3170]: https://github.com/sxyazi/yazi/pull/3170\n[#3172]: https://github.com/sxyazi/yazi/pull/3172\n[#3187]: https://github.com/sxyazi/yazi/pull/3187\n[#3189]: https://github.com/sxyazi/yazi/pull/3189\n[#3190]: https://github.com/sxyazi/yazi/pull/3190\n[#3198]: https://github.com/sxyazi/yazi/pull/3198\n[#3200]: https://github.com/sxyazi/yazi/pull/3200\n[#3201]: https://github.com/sxyazi/yazi/pull/3201\n[#3203]: https://github.com/sxyazi/yazi/pull/3203\n[#3209]: https://github.com/sxyazi/yazi/pull/3209\n[#3222]: https://github.com/sxyazi/yazi/pull/3222\n[#3225]: https://github.com/sxyazi/yazi/pull/3225\n[#3226]: https://github.com/sxyazi/yazi/pull/3226\n[#3232]: https://github.com/sxyazi/yazi/pull/3232\n[#3235]: https://github.com/sxyazi/yazi/pull/3235\n[#3243]: https://github.com/sxyazi/yazi/pull/3243\n[#3250]: https://github.com/sxyazi/yazi/pull/3250\n[#3264]: https://github.com/sxyazi/yazi/pull/3264\n[#3268]: https://github.com/sxyazi/yazi/pull/3268\n[#3271]: https://github.com/sxyazi/yazi/pull/3271\n[#3286]: https://github.com/sxyazi/yazi/pull/3286\n[#3290]: https://github.com/sxyazi/yazi/pull/3290\n[#3313]: https://github.com/sxyazi/yazi/pull/3313\n[#3317]: https://github.com/sxyazi/yazi/pull/3317\n[#3358]: https://github.com/sxyazi/yazi/pull/3358\n[#3360]: https://github.com/sxyazi/yazi/pull/3360\n[#3361]: https://github.com/sxyazi/yazi/pull/3361\n[#3364]: https://github.com/sxyazi/yazi/pull/3364\n[#3369]: https://github.com/sxyazi/yazi/pull/3369\n[#3383]: https://github.com/sxyazi/yazi/pull/3383\n[#3385]: https://github.com/sxyazi/yazi/pull/3385\n[#3387]: https://github.com/sxyazi/yazi/pull/3387\n[#3391]: https://github.com/sxyazi/yazi/pull/3391\n[#3393]: https://github.com/sxyazi/yazi/pull/3393\n[#3396]: https://github.com/sxyazi/yazi/pull/3396\n[#3419]: https://github.com/sxyazi/yazi/pull/3419\n[#3422]: https://github.com/sxyazi/yazi/pull/3422\n[#3429]: https://github.com/sxyazi/yazi/pull/3429\n[#3456]: https://github.com/sxyazi/yazi/pull/3456\n[#3467]: https://github.com/sxyazi/yazi/pull/3467\n[#3477]: https://github.com/sxyazi/yazi/pull/3477\n[#3482]: https://github.com/sxyazi/yazi/pull/3482\n[#3494]: https://github.com/sxyazi/yazi/pull/3494\n[#3514]: https://github.com/sxyazi/yazi/pull/3514\n[#3518]: https://github.com/sxyazi/yazi/pull/3518\n[#3525]: https://github.com/sxyazi/yazi/pull/3525\n[#3532]: https://github.com/sxyazi/yazi/pull/3532\n[#3540]: https://github.com/sxyazi/yazi/pull/3540\n[#3541]: https://github.com/sxyazi/yazi/pull/3541\n[#3561]: https://github.com/sxyazi/yazi/pull/3561\n[#3566]: https://github.com/sxyazi/yazi/pull/3566\n[#3582]: https://github.com/sxyazi/yazi/pull/3582\n[#3594]: https://github.com/sxyazi/yazi/pull/3594\n[#3607]: https://github.com/sxyazi/yazi/pull/3607\n[#3608]: https://github.com/sxyazi/yazi/pull/3608\n[#3617]: https://github.com/sxyazi/yazi/pull/3617\n[#3633]: https://github.com/sxyazi/yazi/pull/3633\n[#3634]: https://github.com/sxyazi/yazi/pull/3634\n[#3638]: https://github.com/sxyazi/yazi/pull/3638\n[#3642]: https://github.com/sxyazi/yazi/pull/3642\n[#3648]: https://github.com/sxyazi/yazi/pull/3648\n[#3661]: https://github.com/sxyazi/yazi/pull/3661\n[#3666]: https://github.com/sxyazi/yazi/pull/3666\n[#3668]: https://github.com/sxyazi/yazi/pull/3668\n[#3677]: https://github.com/sxyazi/yazi/pull/3677\n[#3678]: https://github.com/sxyazi/yazi/pull/3678\n[#3684]: https://github.com/sxyazi/yazi/pull/3684\n[#3687]: https://github.com/sxyazi/yazi/pull/3687\n[#3689]: https://github.com/sxyazi/yazi/pull/3689\n[#3696]: https://github.com/sxyazi/yazi/pull/3696\n[#3708]: https://github.com/sxyazi/yazi/pull/3708\n[#3716]: https://github.com/sxyazi/yazi/pull/3716\n[#3725]: https://github.com/sxyazi/yazi/pull/3725\n[#3728]: https://github.com/sxyazi/yazi/pull/3728\n[#3733]: https://github.com/sxyazi/yazi/pull/3733\n[#3744]: https://github.com/sxyazi/yazi/pull/3744\n[#3748]: https://github.com/sxyazi/yazi/pull/3748\n[#3757]: https://github.com/sxyazi/yazi/pull/3757\n[#3765]: https://github.com/sxyazi/yazi/pull/3765\n[#3780]: https://github.com/sxyazi/yazi/pull/3780\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\n\n## Goal\n\nOur goal is to create a welcoming and safe space where anyone can contribute and seek help for this project in a respectful, collaborative\nand harassment-free way.\n\nAll contributions are welcome and encourage everyone to participate regardless of age, body size, disability, ethnicity, sex characteristics,\ngender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion,\nor sexual identity and orientation.\n\n## Standards\n\nOur standards are as follows:\n\n- Respect different viewpoints and opinions\n- Do not harass, attack, or discriminate against others for any reason. We have zero tolerance for harassment\n- Communicate professionally and constructively. Do not post spam or go off-topic\n- Assume goodwill in conversations. Misunderstandings happen, so give others the benefit of the doubt before jumping to conclusions\n\nExamples of unacceptable behavior include:\n\n- Trolling, insulting or derogatory comments, and personal attacks\n- Harassing or bullying another person\n- Publishing others' private information without their explicit permission\n- Posting things unrelated to the topic being discussed\n- Other conduct that could reasonably be considered inappropriate in a professional setting\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported here: https://discord.com/users/638378964804698112\nAll reporters are guaranteed privacy, and your reports will always be kept private and secure.\n\nAll complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate.\nThe following is a list of consequences that you may face if you are found to be breaking the terms of the CoC.\n\n### 1. Correction\n\nA private warning with an explanation as to why the behavior was not appropriate.\n\n### 2. Warning\n\nA private or public warning with consequences for continued behavior.\n\n### 3. Temporary Ban\n\nA temporary ban from any sort of interaction or public communication with the community for a specified period of time.\n\n### 4. Permanent Ban\n\nA permanent ban from any sort of public interaction within the community.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the\ncommunity in public spaces. Examples of representing our community include using an official e-mail address, posting via an official\nsocial media account, or acting as an appointed representative at an online or offline event.\n\n## Attribution\n\nThis Code of Conduct is adapted 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\nCommunity Impact Guidelines were inspired by\n[Mozilla's code of conduct enforcement ladder][Mozilla CoC].\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[homepage]: https://www.contributor-covenant.org\n[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html\n[Mozilla CoC]: https://github.com/mozilla/diversity\n[FAQ]: https://www.contributor-covenant.org/faq\n[translations]: https://www.contributor-covenant.org/translations\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Yazi\n\nThank you for your interest in contributing to Yazi! We welcome contributions in the form of bug reports, feature requests, documentation improvements, and code changes.\n\nThis guide will help you understand how to contribute to the project.\n\n## Table of Contents\n\n1. [Getting Started](#getting-started)\n2. [Project Structure](#project-structure)\n3. [Development Setup](#development-setup)\n4. [How to Contribute](#how-to-contribute)\n5. [Pull Requests](#pull-requests)\n\n## Getting Started\n\n### Prerequisites\n\nBefore you begin, ensure you have met the following requirements:\n\n- Rust installed on your machine. You can download it from [rustup.rs](https://rustup.rs).\n- Familiarity with Git and GitHub.\n\n### Fork the Repository\n\n1. Fork the [Yazi repository](https://github.com/sxyazi/yazi) to your GitHub account.\n2. Clone your fork to your local machine:\n\n   ```sh\n   git clone https://github.com/<your-username>/yazi.git\n   ```\n\n3. Set up the upstream remote:\n   ```sh\n   git remote add upstream https://github.com/sxyazi/yazi.git\n   ```\n\n## Project Structure\n\nA brief overview of the project's structure:\n\n```sh\n.\n├── assets/             # Assets like images and fonts\n├── nix/                # Nix-related configurations\n├── scripts/            # Helper scripts used by CI/CD\n├── snap/               # Snapcraft configuration\n├── yazi-adapter/       # Yazi image adapter\n├── yazi-binding/       # Yazi Lua bindings\n├── yazi-boot/          # Yazi bootstrapper\n├── yazi-cli/           # Yazi command-line interface\n├── yazi-codegen/       # Yazi code generator\n├── yazi-config/        # Yazi configuration file parser\n├── yazi-core/          # Yazi core logic\n├── yazi-dds/           # Yazi data distribution service\n├── yazi-ffi/           # Yazi foreign function interface\n├── yazi-fm/            # Yazi file manager\n├── yazi-fs/            # Yazi file system\n├── yazi-macro/         # Yazi macros\n├── yazi-plugin/        # Yazi plugin system\n├── yazi-proxy/         # Yazi event proxy\n├── yazi-scheduler/     # Yazi task scheduler\n├── yazi-shared/        # Yazi shared library\n├── yazi-term/          # Yazi terminal extensions\n├── yazi-widgets/       # Yazi user interface widgets\n├── .github/            # GitHub-specific files and workflows\n├── Cargo.toml          # Rust workflow configuration\n└── README.md           # Project overview\n```\n\n## Development Setup\n\n1. Ensure the latest stable Rust is installed:\n\n   ```sh\n   rustc --version\n   cargo --version\n   ```\n\n2. Build the project:\n\n   ```sh\n   cargo build\n   ```\n\n3. Run the tests:\n\n   ```sh\n   cargo test --workspace --verbose\n   ```\n\n4. Format the code (requires `rustfmt` nightly):\n\n   ```sh\n   rustup component add rustfmt --toolchain nightly\n   rustfmt +nightly **/*.rs\n   ```\n\n## How to Contribute\n\n### Reporting Bugs\n\nIf you encounter a bug and have found a way to reliably reproduce it on the latest `main` branch, please file a [bug report](https://github.com/sxyazi/yazi/issues/new?template=bug.yml) with a [minimal reproducer](https://stackoverflow.com/help/minimal-reproducible-example).\n\n### Suggesting Features\n\nIf you want to request a feature, please file a [feature request](https://github.com/sxyazi/yazi/issues/new?template=feature.yml). Please make sure to search for existing issues and discussions before submitting.\n\n### Improving Documentation\n\nYazi's documentation placed at [yazi-rs/yazi-rs.github.io](https://github.com/yazi-rs/yazi-rs.github.io), contributions related to documentation need to be made there.\n\n### Improving Icons\n\nYazi's icon originates from [`nvim-web-devicons`](https://github.com/nvim-tree/nvim-web-devicons), and it is periodically grabbed and updated with the latest changes from upstream via [`generate.lua`](https://github.com/sxyazi/yazi/blob/main/scripts/icons/generate.lua).\n\nContributions related to the icon should be made upstream to facilitate easier automation of this process.\n\n### Submitting Code Changes\n\n1. Create a new branch for your changes:\n\n   ```sh\n   git checkout -b your-branch-name\n   ```\n\n2. Make your changes. Ensure that your code follows the project's [coding style](https://github.com/sxyazi/yazi/blob/main/rustfmt.toml) and passes all tests.\n3. Commit your changes with a descriptive commit message:\n\n   ```sh\n   git commit -m \"feat: an awesome feature\"\n   ```\n\n4. Push your changes to your fork:\n\n   ```sh\n   git push origin your-branch-name\n   ```\n\n## Pull Requests\n\nIf you have an idea, before raising a pull request, we encourage you to file an issue to propose it, ensuring that we are aligned and reducing the risk of re-work.\n\nWe want you to succeed, and it can be discouraging to find that a lot of re-work is needed.\n\n### Process\n\n1. Ensure your fork is up-to-date with the upstream repository:\n\n   ```sh\n   git fetch upstream\n   git checkout main\n   git merge upstream/main\n   ```\n\n2. Rebase your feature branch onto the `main` branch:\n\n   ```sh\n   git checkout your-branch-name\n   git rebase main\n   ```\n\n3. Create a pull request to the `main` branch of the upstream repository. Follow the pull request template and ensure that:\n   - Your code passes all tests and lints.\n   - Your pull request description clearly explains the changes and why they are needed.\n4. Address any review comments. Make sure to push updates to the same branch on your fork.\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nresolver        = \"3\"\nmembers         = [ \"yazi-*\" ]\ndefault-members = [ \"yazi-fm\", \"yazi-cli\" ]\n\n[workspace.package]\nedition      = \"2024\"\nversion      = \"26.2.2\"\nlicense      = \"MIT\"\nauthors      = [ \"sxyazi <sxyazi@gmail.com>\" ]\nhomepage     = \"https://yazi-rs.github.io\"\nrepository   = \"https://github.com/sxyazi/yazi\"\nrust-version = \"1.92.0\"\n\n[profile.dev]\ndebug = \"line-tables-only\"\n\n[profile.release]\ncodegen-units = 1\nlto           = true\npanic         = \"abort\"\nstrip         = true\n\n[profile.release-windows]\ninherits = \"release\"\npanic    = \"unwind\"\n\n[profile.dev-opt]\ninherits      = \"release\"\ncodegen-units = 256\nincremental   = true\nlto           = false\n\n[profile.dev.package.\"*\"]\ndebug = false\n\n[workspace.dependencies]\nansi-to-tui         = \"8.0.1\"\nanyhow              = \"1.0.102\"\nbase64              = \"0.22.1\"\nbitflags            = { version = \"2.11.0\", features = [ \"serde\" ] }\nchrono              = \"0.4.44\"\nclap                = { version = \"4.6.0\", features = [ \"derive\" ] }\ncore-foundation-sys = \"0.8.7\"\ncrossterm           = { version = \"0.29.0\", features = [ \"event-stream\" ] }\ndirs                = \"6.0.0\"\ndyn-clone           = \"1.0.20\"\neither              = { version = \"1.15.0\" }\nfoldhash            = \"0.2.0\"\nfutures             = \"0.3.32\"\nglobset             = \"0.4.18\"\nhashbrown           = { version = \"0.16.1\", features = [ \"serde\" ] }\nindexmap            = { version = \"2.13.0\", features = [ \"serde\" ] }\nlibc                = \"0.2.183\"\nlru                 = \"0.16.3\"\nmlua                = { version = \"0.11.6\", features = [ \"anyhow\", \"async\", \"error-send\", \"lua55\", \"macros\", \"serde\" ] }\nobjc2               = \"0.6.4\"\nordered-float       = { version = \"5.1.0\", features = [ \"serde\" ] }\nparking_lot         = \"0.12.5\"\npaste               = \"1.0.15\"\npercent-encoding    = \"2.3.2\"\nrand                = { version = \"0.9.2\", default-features = false, features = [ \"os_rng\", \"small_rng\", \"std\" ] }\nratatui             = { version = \"0.30.0\", features = [ \"serde\", \"unstable-rendered-line-info\", \"unstable-widget-ref\" ] }\nregex               = \"1.12.3\"\nrussh               = { version = \"0.57.1\", default-features = false, features = [ \"ring\", \"rsa\" ] }\nscopeguard          = \"1.2.0\"\nserde               = { version = \"1.0.228\", features = [ \"derive\" ] }\nserde_json          = \"1.0.149\"\nserde_with          = \"3.18.0\"\nsyntect             = { version = \"5.3.0\", default-features = false, features = [ \"parsing\", \"plist-load\", \"regex-onig\" ] }\nthiserror           = \"2.0.18\"\ntokio               = { version = \"1.50.0\", features = [ \"full\" ] }\ntokio-stream        = \"0.1.18\"\ntokio-util          = \"0.7.18\"\ntoml                = { version = \"1.0.7\" }\ntracing             = { version = \"0.1.44\", features = [ \"max_level_debug\", \"release_max_level_debug\" ] }\ntwox-hash           = { version = \"2.1.2\", default-features = false, features = [ \"std\", \"random\", \"xxhash3_128\" ] }\ntyped-path          = \"0.12.3\"\nunicode-width       = { version = \"0.2.2\", default-features = false }\nuzers               = \"0.12.2\"\n\n[workspace.lints.clippy]\nformat_push_string   = \"warn\"\nif_same_then_else    = \"allow\"\nimplicit_clone       = \"warn\"\nlen_without_is_empty = \"allow\"\nmissing_safety_doc   = \"allow\"\nmodule_inception     = \"allow\"\noption_map_unit_fn   = \"allow\"\nunit_arg             = \"allow\"\nuse_self             = \"warn\"\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 - sxyazi\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "LICENSE-ICONS",
    "content": "MIT License\n\nCopyright (c) 2023 nvim-tree\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n\t<sup>Special thanks to:</sup><br>\n\n|<a href=\"https://go.warp.dev/yazi\" target=\"_blank\"><img alt=\"Warp sponsorship\" width=350 src=\"https://github.com/warpdotdev/brand-assets/blob/main/Github/Sponsor/Warp-Github-LG-02.png\"><br><b>Warp, built for coding with multiple AI agents</b><br><sup>Available for macOS, Linux and Windows</sup></a>|<a href=\"https://git-tower.com/?utm_source=yazi&utm_medium=referral\" target=\"_blank\"><img alt=\"Tower sponsorship\" width=350 src=\"https://github.com/user-attachments/assets/c561a30f-2c5e-4f33-bbec-2bf9df26431a\"><br><b>The most powerful Git client for Mac and Windows</b></a>|\n|-|-|\n\n</div>\n\n## Yazi - ⚡️ Blazing Fast Terminal File Manager\n\nYazi (means \"duck\") is a terminal file manager written in Rust, based on non-blocking async I/O. It aims to provide an efficient, user-friendly, and customizable file management experience.\n\n💡 A new article explaining its internal workings: [Why is Yazi Fast?](https://yazi-rs.github.io/blog/why-is-yazi-fast)\n\n- 🚀 **Full Asynchronous Support**: All I/O operations are asynchronous, CPU tasks are spread across multiple threads, making the most of available resources.\n- 💪 **Powerful Async Task Scheduling and Management**: Provides real-time progress updates, task cancellation, and internal task priority assignment.\n- 🖼️ **Built-in Support for Multiple Image Protocols**: Also integrated with Überzug++ and Chafa, covering almost all terminals.\n- 🌟 **Built-in Code Highlighting and Image Decoding**: Combined with the pre-loading mechanism, greatly accelerates image and normal file loading.\n- 🔌 **Concurrent Plugin System**: UI plugins (rewriting most of the UI), functional plugins, custom previewer/preloader/spotter/fetcher; Just some pieces of Lua.\n- ☁️ **Virtual Filesystem**: Remote file management, custom search engines.\n- 📡 **Data Distribution Service**: Built on a client-server architecture (no additional server process required), integrated with a Lua-based publish-subscribe model, achieving cross-instance communication and state persistence.\n- 📦 **Package Manager**: Install plugins and themes with one command, keeping them up-to-date, or pin them to a specific version.\n- 🧰 Integration with ripgrep, fd, fzf, zoxide\n- 💫 Vim-like input/pick/confirm/which/notify component, auto-completion for cd paths\n- 🏷️ Multi-Tab Support, Cross-directory selection, Scrollable Preview (for videos, PDFs, archives, code, directories, etc.)\n- 🔄 Bulk Renaming, Archive Extraction, Visual Mode, File Chooser, [Git Integration](https://github.com/yazi-rs/plugins/tree/main/git.yazi), [Mount Manager](https://github.com/yazi-rs/plugins/tree/main/mount.yazi)\n- 🎨 Theme System, Mouse Support, Trash Bin, Custom Layouts, CSI u, OSC 52\n- ... and more!\n\nhttps://github.com/sxyazi/yazi/assets/17523360/92ff23fa-0cd5-4f04-b387-894c12265cc7\n\n## Project status\n\nPublic beta, can be used as a daily driver.\n\nYazi is currently in heavy development, expect breaking changes.\n\n## Documentation\n\n- Usage: https://yazi-rs.github.io/docs/installation\n- Features: https://yazi-rs.github.io/features\n\n## Discussion\n\n- Discord Server (English mainly): https://discord.gg/qfADduSdJu\n- Telegram Group (Chinese mainly): https://t.me/yazi_rs\n\n## Image Preview\n\n| Platform                                                                     | Protocol                               | Support                                  |\n| ---------------------------------------------------------------------------- | -------------------------------------- | ---------------------------------------- |\n| [kitty](https://github.com/kovidgoyal/kitty) (>= 0.28.0)                     | [Kitty unicode placeholders][kgp]      | ✅ Built-in                              |\n| [iTerm2](https://iterm2.com)                                                 | [Inline images protocol][iip]          | ✅ Built-in                              |\n| [WezTerm](https://github.com/wez/wezterm)                                    | [Inline images protocol][iip]          | ✅ Built-in                              |\n| [Konsole](https://invent.kde.org/utilities/konsole)                          | [Kitty old protocol][kgp-old]          | ✅ Built-in                              |\n| [foot](https://codeberg.org/dnkl/foot)                                       | [Sixel graphics format][sixel]         | ✅ Built-in                              |\n| [Ghostty](https://github.com/ghostty-org/ghostty)                            | [Kitty unicode placeholders][kgp]      | ✅ Built-in                              |\n| [Windows Terminal](https://github.com/microsoft/terminal) (>= v1.22.10352.0) | [Sixel graphics format][sixel]         | ✅ Built-in                              |\n| [st with Sixel patch](https://github.com/bakkeby/st-flexipatch)              | [Sixel graphics format][sixel]         | ✅ Built-in                              |\n| [Warp](https://www.warp.dev) (macOS/Linux only)                              | [Inline images protocol][iip]          | ✅ Built-in                              |\n| [Tabby](https://github.com/Eugeny/tabby)                                     | [Inline images protocol][iip]          | ✅ Built-in                              |\n| [VSCode](https://github.com/microsoft/vscode)                                | [Inline images protocol][iip]          | ✅ Built-in                              |\n| [Rio](https://github.com/raphamorim/rio)                                     | [Inline images protocol][iip]          | ❌ Rio renders images at incorrect sizes |\n| [Black Box](https://gitlab.gnome.org/raggesilver/blackbox)                   | [Sixel graphics format][sixel]         | ✅ Built-in                              |\n| [Bobcat](https://github.com/ismail-yilmaz/Bobcat)                            | [Inline images protocol][iip]          | ✅ Built-in                              |\n| X11 / Wayland                                                                | Window system protocol                 | ☑️ [Überzug++][ueberzug] required        |\n| Fallback                                                                     | [ASCII art (Unicode block)][ascii-art] | ☑️ [Chafa][chafa] required (>= 1.16.0)   |\n\nSee https://yazi-rs.github.io/docs/image-preview for details.\n\n<!-- Protocols -->\n\n[kgp]: https://sw.kovidgoyal.net/kitty/graphics-protocol/#unicode-placeholders\n[kgp-old]: https://github.com/sxyazi/yazi/blob/main/yazi-adapter/src/drivers/kgp_old.rs\n[iip]: https://iterm2.com/documentation-images.html\n[sixel]: https://www.vt100.net/docs/vt3xx-gp/chapter14.html\n[ascii-art]: https://en.wikipedia.org/wiki/ASCII_art\n\n<!-- Dependencies -->\n\n[ueberzug]: https://github.com/jstkdng/ueberzugpp\n[chafa]: https://hpjansson.org/chafa/\n\n## Special Thanks\n\n<img alt=\"RustRover logo\" align=\"right\" width=\"200\" src=\"https://resources.jetbrains.com/storage/products/company/brand/logos/RustRover.svg\">\n\nThanks to RustRover team for providing open-source licenses to support the maintenance of Yazi.\n\nActive code contributors can contact @sxyazi to get a license (if any are still available).\n\n## License\n\nYazi is MIT-licensed. For more information check the [LICENSE](LICENSE) file.\n"
  },
  {
    "path": "assets/yazi.desktop",
    "content": "[Desktop Entry]\nName=Yazi File Manager\nIcon=yazi\nComment=Blazing fast terminal file manager written in Rust, based on async I/O\nTerminal=true\nTryExec=yazi\nExec=yazi %u\nType=Application\nMimeType=inode/directory\nCategories=System;FileManager;FileTools;ConsoleOnly\nKeywords=File;Manager;Explorer;Browser;Launcher\n"
  },
  {
    "path": "cspell.json",
    "content": "{\"version\":\"0.2\",\"flagWords\":[],\"words\":[\"Punct\",\"KEYMAP\",\"splitn\",\"crossterm\",\"YAZI\",\"peekable\",\"ratatui\",\"syntect\",\"pbpaste\",\"pbcopy\",\"oneshot\",\"Posix\",\"Lsar\",\"XADDOS\",\"zoxide\",\"cands\",\"Deque\",\"precache\",\"imageops\",\"IFBLK\",\"IFCHR\",\"IFDIR\",\"IFIFO\",\"IFLNK\",\"IFMT\",\"IFSOCK\",\"IRGRP\",\"IROTH\",\"IRUSR\",\"ISGID\",\"ISUID\",\"ISVTX\",\"IWGRP\",\"IWOTH\",\"IWUSR\",\"IXGRP\",\"IXOTH\",\"IXUSR\",\"libc\",\"winsize\",\"TIOCGWINSZ\",\"xpixel\",\"ypixel\",\"ioerr\",\"appender\",\"Catppuccin\",\"macchiato\",\"gitmodules\",\"Dotfiles\",\"bashprofile\",\"vimrc\",\"flac\",\"webp\",\"exiftool\",\"mediainfo\",\"ripgrep\",\"indexmap\",\"indexmap\",\"unwatch\",\"canonicalize\",\"serde\",\"fsevent\",\"Ueberzug\",\"iterm\",\"wezterm\",\"sixel\",\"chafa\",\"ueberzugpp\",\"Konsole\",\"Überzug\",\"pkgs\",\"pdftoppm\",\"poppler\",\"singlefile\",\"jpegopt\",\"EXIF\",\"rustfmt\",\"mktemp\",\"nanos\",\"xclip\",\"xsel\",\"natord\",\"Mintty\",\"nixos\",\"nixpkgs\",\"SIGTSTP\",\"SIGCONT\",\"SIGCONT\",\"mlua\",\"nonstatic\",\"userdata\",\"metatable\",\"natsort\",\"backstack\",\"luajit\",\"Succ\",\"Succ\",\"cand\",\"fileencoding\",\"foldmethod\",\"lightgreen\",\"darkgray\",\"lightred\",\"lightyellow\",\"lightcyan\",\"nushell\",\"msvc\",\"aarch\",\"linemode\",\"sxyazi\",\"rsplit\",\"ZELLIJ\",\"bitflags\",\"bitflags\",\"USERPROFILE\",\"Neovim\",\"vergen\",\"gitcl\",\"Renderable\",\"preloaders\",\"prec\",\"Upserting\",\"prio\",\"Ghostty\",\"Catmull\",\"Lanczos\",\"cmds\",\"unyank\",\"scrolloff\",\"headsup\",\"unsub\",\"uzers\",\"scopeguard\",\"SPDLOG\",\"globset\",\"filetime\",\"magick\",\"magick\",\"prefetcher\",\"Prework\",\"prefetchers\",\"PREWORKERS\",\"conds\",\"translit\",\"rxvt\",\"Urxvt\",\"realpath\",\"realname\",\"REPARSE\",\"hardlink\",\"hardlinking\",\"nlink\",\"nlink\",\"linemodes\",\"SIGSTOP\",\"sevenzip\",\"rsplitn\",\"replacen\",\"DECSET\",\"DECRQM\",\"repeek\",\"cwds\",\"tcsi\",\"Hyprland\",\"Wayfire\",\"SWAYSOCK\",\"btime\",\"nsec\",\"codegen\",\"gethostname\",\"fchmod\",\"fdfind\",\"Rustc\",\"rustc\",\"ffprobe\",\"vframes\",\"luma\",\"obase\",\"outln\",\"errln\",\"tmtheme\",\"twox\",\"cfgs\",\"fstype\",\"objc\",\"rdev\",\"runloop\",\"exfat\",\"rclone\",\"DECRQSS\",\"DECSCUSR\",\"libvterm\",\"Uninit\",\"lockin\",\"rposition\",\"resvg\",\"foldhash\",\"tilded\",\"futs\",\"chdir\",\"hashbrown\",\"JEMALLOC\",\"RUSTFLAGS\",\"RDONLY\",\"GETPATH\",\"fcntl\",\"casefold\",\"inodes\",\"Splatable\",\"casefied\",\"thiserror\",\"memchr\",\"memmem\",\"russh\",\"deadpool\",\"keepalive\",\"nodelay\",\"publickey\",\"deadpool\",\"initing\",\"treelize\",\"TOCTOU\",\"fellback\",\"watchee\"],\"language\":\"en\"}"
  },
  {
    "path": "flake.nix",
    "content": "{\n  inputs = {\n    nixpkgs.url = \"github:NixOS/nixpkgs/nixpkgs-unstable\";\n    flake-utils.url = \"github:numtide/flake-utils\";\n    rust-overlay = {\n      url = \"github:oxalica/rust-overlay\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n    };\n  };\n\n  outputs =\n    {\n      self,\n      nixpkgs,\n      rust-overlay,\n      flake-utils,\n      ...\n    }:\n    flake-utils.lib.eachDefaultSystem (\n      system:\n      let\n        pkgs = import nixpkgs {\n          inherit system;\n          overlays = [ rust-overlay.overlays.default ];\n        };\n        toolchain = pkgs.rust-bin.stable.latest.default;\n        rustPlatform = pkgs.makeRustPlatform {\n          cargo = toolchain;\n          rustc = toolchain;\n        };\n\n        rev = self.shortRev or self.dirtyShortRev or \"dirty\";\n        date = self.lastModifiedDate or self.lastModified or \"19700101\";\n        version =\n          (builtins.fromTOML (builtins.readFile ./Cargo.toml)).workspace.package.version\n          + \"pre${builtins.substring 0 8 date}_${rev}\";\n      in\n      {\n        packages = {\n          yazi-unwrapped = pkgs.callPackage ./nix/yazi-unwrapped.nix {\n            inherit\n              version\n              rev\n              date\n              rustPlatform\n              ;\n          };\n          yazi = pkgs.callPackage ./nix/yazi.nix {\n            inherit (self.packages.${system}) yazi-unwrapped;\n          };\n          default = self.packages.${system}.yazi;\n        };\n\n        devShells = {\n          default = pkgs.callPackage ./nix/shell.nix {\n            inherit toolchain;\n            inherit (self.packages.${system}) yazi yazi-unwrapped;\n          };\n        };\n\n        formatter = pkgs.nixfmt-tree;\n      }\n    )\n    // {\n      overlays = {\n        default = self.overlays.yazi;\n        yazi = _: prev: {\n          inherit (self.packages.${prev.stdenv.system}) yazi yazi-unwrapped;\n        };\n      };\n    };\n}\n"
  },
  {
    "path": "nix/shell.nix",
    "content": "{\n  mkShell,\n  yazi,\n  toolchain,\n  nodePackages,\n  yazi-unwrapped,\n}:\n\nmkShell {\n  packages = yazi.passthru.runtimePaths ++ [\n    (toolchain.override {\n      extensions = [\n        \"rust-src\"\n        \"rustfmt\"\n        \"rust-analyzer\"\n        \"clippy\"\n      ];\n    })\n    nodePackages.cspell\n  ];\n\n  inputsFrom = [ yazi-unwrapped ];\n\n  env.RUST_BACKTRACE = \"1\";\n}\n"
  },
  {
    "path": "nix/yazi-unwrapped.nix",
    "content": "{\n  rustPlatform,\n  version ? \"git\",\n  rev ? \"unknown\",\n  date ? \"19700101\",\n  lib,\n\n  installShellFiles,\n  fetchFromGitHub,\n  rust-jemalloc-sys,\n\n  imagemagick,\n}:\nlet\n  src = lib.fileset.toSource {\n    root = ../.;\n    fileset = lib.fileset.unions [\n      ../assets\n      ../Cargo.toml\n      ../Cargo.lock\n      (lib.fileset.fromSource (lib.sources.sourceByRegex ../. [ \"^yazi-.*\" ]))\n    ];\n  };\nin\nrustPlatform.buildRustPackage (finalAttrs: {\n  pname = \"yazi\";\n  inherit version src;\n\n  cargoLock = {\n    lockFile = \"${src}/Cargo.lock\";\n    #outputHashes = {\n    #  \"mlua-0.10.0\" = \"sha256-Xg6/jc+UP8tbJJ6x1sbAgt8ZHt051xEBBcjmikQqYlw=\";\n    #};\n  };\n\n  env = {\n    YAZI_GEN_COMPLETIONS = true;\n    VERGEN_GIT_SHA = rev;\n    VERGEN_BUILD_DATE = builtins.concatStringsSep \"-\" (builtins.match \"(.{4})(.{2})(.{2}).*\" date);\n  };\n\n  nativeBuildInputs = [\n    installShellFiles\n    imagemagick\n  ];\n\n  buildInputs = [\n    rust-jemalloc-sys\n  ];\n\n  postInstall = ''\n    installShellCompletion --cmd yazi \\\n      --bash ./yazi-boot/completions/yazi.bash \\\n      --fish ./yazi-boot/completions/yazi.fish \\\n      --zsh  ./yazi-boot/completions/_yazi\n\n    # Resize logo\n    for RES in 16 24 32 48 64 128 256; do\n      mkdir -p $out/share/icons/hicolor/\"$RES\"x\"$RES\"/apps\n      magick assets/logo.png -resize \"$RES\"x\"$RES\" $out/share/icons/hicolor/\"$RES\"x\"$RES\"/apps/yazi.png\n    done\n\n    installManPage ${finalAttrs.passthru.srcs.man_src}/yazi{.1,-config.5}\n\n    mkdir -p $out/share/applications\n    install -m644 assets/yazi.desktop $out/share/applications/\n  '';\n\n  passthru.srcs = {\n    man_src = fetchFromGitHub {\n      name = \"manpages\"; # needed to ensure name is unique\n      owner = \"yazi-rs\";\n      repo = \"manpages\";\n      rev = \"8950e968f4a1ad0b83d5836ec54a070855068dbf\";\n      hash = \"sha256-kEVXejDg4ChFoMNBvKlwdFEyUuTcY2VuK9j0PdafKus=\";\n    };\n  };\n  \n\n  meta = {\n    description = \"Blazing fast terminal file manager written in Rust, based on async I/O\";\n    homepage = \"https://github.com/sxyazi/yazi\";\n    license = lib.licenses.mit;\n    mainProgram = \"yazi\";\n  };\n})\n"
  },
  {
    "path": "nix/yazi.nix",
    "content": "{\n  lib,\n  formats,\n  runCommand,\n  makeWrapper,\n\n  runtimeDeps ? (ps: ps),\n\n  # deps\n  file,\n  yazi-unwrapped,\n\n  # default optional deps\n  jq,\n  poppler-utils,\n  _7zz,\n  ffmpeg,\n  fd,\n  ripgrep,\n  resvg,\n  fzf,\n  zoxide,\n  imagemagick,\n  chafa,\n\n  settings ? { },\n  plugins ? { },\n  flavors ? { },\n  initLua ? null,\n}:\nlet\n  inherit (lib)\n    concatStringsSep\n    concatMapStringsSep\n    optionalString\n    makeBinPath\n    mapAttrsToList\n    ;\n\n  defaultDeps = [\n    jq\n    poppler-utils\n    _7zz\n    ffmpeg\n    fd\n    ripgrep\n    resvg\n    fzf\n    zoxide\n    imagemagick\n    chafa\n  ];\n  runtimePaths = [ file ] ++ (runtimeDeps defaultDeps);\n\n  settingsFormat = formats.toml { };\n\n  files = [\n    \"yazi\"\n    \"theme\"\n    \"keymap\"\n  ];\n\n  configHome =\n    if (settings == { } && initLua == null && plugins == { } && flavors == { }) then\n      null\n    else\n      runCommand \"YAZI_CONFIG_HOME\" { } ''\n        mkdir -p $out\n        ${concatMapStringsSep \"\\n\" (\n          name:\n          optionalString (settings ? ${name} && settings.${name} != { }) ''\n            ln -s ${settingsFormat.generate \"${name}.toml\" settings.${name}} $out/${name}.toml\n          ''\n        ) files}\n\n        mkdir $out/plugins\n        ${optionalString (plugins != { }) ''\n          ${concatStringsSep \"\\n\" (\n            mapAttrsToList (name: value: \"ln -s ${value} $out/plugins/${name}\") plugins\n          )}\n        ''}\n\n        mkdir $out/flavors\n        ${optionalString (flavors != { }) ''\n          ${concatStringsSep \"\\n\" (\n            mapAttrsToList (name: value: \"ln -s ${value} $out/flavors/${name}\") flavors\n          )}\n        ''}\n\n\n        ${optionalString (initLua != null) \"ln -s ${initLua} $out/init.lua\"}\n      '';\nin\nrunCommand yazi-unwrapped.name\n  {\n    inherit (yazi-unwrapped) pname version meta;\n\n    nativeBuildInputs = [ makeWrapper ];\n\n    passthru.runtimePaths = runtimePaths;\n  }\n  ''\n    mkdir -p $out/bin\n    ln -s ${yazi-unwrapped}/share $out/share\n    ln -s ${yazi-unwrapped}/bin/ya $out/bin/ya\n    makeWrapper ${yazi-unwrapped}/bin/yazi $out/bin/yazi \\\n      --prefix PATH : \"${makeBinPath runtimePaths}\" \\\n      ${optionalString (configHome != null) \"--set YAZI_CONFIG_HOME ${configHome}\"}\n  ''\n"
  },
  {
    "path": "rustfmt.toml",
    "content": "color                        = \"Never\"\ncondense_wildcard_suffixes   = true\nedition                      = \"2024\"\nenum_discrim_align_threshold = 99\nfn_single_line               = true\nformat_code_in_doc_comments  = false\nformat_generated_files       = false\nformat_macro_matchers        = true\nformat_macro_bodies          = true\nformat_strings               = false\nhard_tabs                    = true\nhex_literal_case             = \"Lower\"\nshow_parse_errors            = false\nimports_indent               = \"Visual\"\nimports_layout               = \"Horizontal\"\nimports_granularity          = \"Crate\"\nnewline_style                = \"Unix\"\nnormalize_comments           = true\nnormalize_doc_attributes     = false\noverflow_delimited_expr      = true\nreorder_impl_items           = true\ngroup_imports                = \"StdExternalCrate\"\nreorder_modules              = true\nstruct_field_align_threshold = 99\ntab_spaces                   = 2\nunstable_features            = true\nuse_field_init_shorthand     = true\nuse_small_heuristics         = \"Max\"\nuse_try_shorthand            = true\nstyle_edition                = \"2024\"\nwrap_comments                = true\n"
  },
  {
    "path": "scripts/build.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nexport ARTIFACT_NAME=\"yazi-$1\"\nexport YAZI_GEN_COMPLETIONS=1\n\n# Build the target\ngit config --global --add safe.directory \"*\"\ncargo build --release --locked --target \"$1\"\n\n# Copy the binaries to a known location\nmkdir -p \"target/release\"\ncp \"target/$1/release/ya\" \"target/release/ya\"\ncp \"target/$1/release/yazi\" \"target/release/yazi\"\n\n# Package deb\nif [[ \"$ARTIFACT_NAME\" == *-linux-* ]] && { [[ \"$ARTIFACT_NAME\" == *-aarch64-* ]] || [[ \"$ARTIFACT_NAME\" == *-x86_64-* ]]; }; then\n\tcargo install cargo-deb\n\tcargo deb -p yazi-packing --no-build --target \"$1\" -o \"$ARTIFACT_NAME.deb\"\nfi\n\n# Create the artifact\nmkdir -p \"$ARTIFACT_NAME/completions\"\ncp \"target/release/ya\" \"$ARTIFACT_NAME\"\ncp \"target/release/yazi\" \"$ARTIFACT_NAME\"\ncp yazi-cli/completions/* \"$ARTIFACT_NAME/completions\"\ncp yazi-boot/completions/* \"$ARTIFACT_NAME/completions\"\ncp README.md LICENSE \"$ARTIFACT_NAME\"\n\n# Zip the artifact\nif ! command -v zip &> /dev/null; then\n\tapt-get update && apt-get install -yq zip\nfi\nzip -r \"$ARTIFACT_NAME.zip\" \"$ARTIFACT_NAME\"\n"
  },
  {
    "path": "scripts/bump.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\ncd \"$SCRIPT_DIR/..\"\n\necho \"Bumping version: $1\"\n\nTOML_FILES=\"$(git ls-files '*Cargo.toml')\"\nperl -pi -e 's/^version .*= .*$/version = \"'\"$1\"'\"/' -- $TOML_FILES\nperl -pi -e 's/^(yazi-[a-z]+)\\s*=\\s*{.*$/\\1 = { path = \"..\\/\\1\", version = \"'\"$1\"'\" }/' -- $TOML_FILES\n\n# Insert \"## [v$1]\" after \"## [Unreleased]\"\nperl -0777 -pe \"s/^(## \\[Unreleased\\]\\s*)/\\\\1## [v$1]\\n\\n/m\" -i CHANGELOG.md\n# Determine previous version and append compare link\nprev_ver=$(grep -oE '^\\[v[0-9][^]]+\\]' CHANGELOG.md | tail -n1 | tr -d '[]')\nlink=\"\\[v$1\\]: https://github.com/sxyazi/yazi/compare/$prev_ver...v$1\"\nperl -pi -e 's{(\\['\"$prev_ver\"'\\]:[^\\n]+\\n)}{$1'\"$link\"'\\n}s' CHANGELOG.md\n\nESLINT_USE_FLAT_CONFIG=true eslint -c ~/.config/rules/eslint/eslint.config.cjs --fix -- $TOML_FILES\n"
  },
  {
    "path": "scripts/icons/generate.lua",
    "content": "local dark = {\n\ticons_by_filename = require(\"default.icons_by_filename\"),\n\ticons_by_file_extension = require(\"default.icons_by_file_extension\"),\n}\nlocal light = {\n\ticons_by_filename = require(\"light.icons_by_filename\"),\n\ticons_by_file_extension = require(\"light.icons_by_file_extension\"),\n}\n\nfunction rearrange(by)\n\tlocal map = {}\n\tlocal source = by == \"exts\" and \"icons_by_file_extension\" or \"icons_by_filename\"\n\tfor k, v in pairs(dark[source]) do\n\t\tmap[k] = map[k] or {}\n\t\tmap[k].icon = v.icon\n\t\tmap[k].fg_dark = v.color:lower()\n\tend\n\tfor k, v in pairs(light[source]) do\n\t\tmap[k].fg_light = v.color:lower()\n\tend\n\treturn map\nend\n\nfunction dump(map)\n\tlocal list = {}\n\tfor k, v in pairs(map) do\n\t\tlist[#list + 1] = { name = k, text = v.icon, fg_dark = v.fg_dark, fg_light = v.fg_light }\n\tend\n\ttable.sort(list, function(a, b) return a.name:lower() < b.name:lower() end)\n\tlocal dark, light = \"\", \"\"\n\tfor _, v in ipairs(list) do\n\t\t-- stylua: ignore\n\t\tdark = dark .. string.format('\\t{ name = \"%s\", text = \"%s\", fg = \"%s\" },\\n', v.name, v.text, v.fg_dark)\n\t\tlight = light .. string.format('\\t{ name = \"%s\", text = \"%s\", fg = \"%s\" },\\n', v.name, v.text, v.fg_light)\n\tend\n\treturn dark, light\nend\n\nfunction save(typ, files, exts)\n\tlocal p = string.format(\"../../yazi-config/preset/theme-%s.toml\", typ)\n\tlocal s = io.open(p, \"r\"):read(\"*a\")\n\ts = s:gsub(\"files = %[\\n(.-)\\n%]\", string.format(\"files = [\\n%s]\", files))\n\ts = s:gsub(\"exts = %[\\n(.-)\\n%]\", string.format(\"exts = [\\n%s]\", exts))\n\tio.open(p, \"w\"):write(s)\nend\n\nlocal dark_files, light_files = dump(rearrange(\"files\"))\nlocal dark_exts, light_exts = dump(rearrange(\"exts\"))\n\nsave(\"dark\", dark_files, dark_exts)\nsave(\"light\", light_files, light_exts)\n"
  },
  {
    "path": "scripts/validate-form/main.js",
    "content": "const LABEL_NAME = \"needs info\"\nconst RE_VERSION = /Yazi\\s+Version\\s*:\\s\\d+\\.\\d+\\.\\d+\\s\\(/gm\nconst RE_DEPENDENCIES = /Dependencies\\s+[/a-z]+\\s*:\\s/gm\nconst RE_CHECKLIST = /#{3}\\s+Checklist\\s+(?:^-\\s+\\[x]\\s+.+?(?:\\n|\\r\\n|$)){2}/gm\n\nfunction bugReportBody(creator, content, hash) {\n\tif (RE_DEPENDENCIES.test(content) && RE_CHECKLIST.test(content) && new RegExp(` \\\\(${hash}[a-f0-9]? `).test(content)) {\n\t\treturn null\n\t}\n\n\treturn `Hey @${creator}, thank you for opening the issue to help improve Yazi, appreciate it!\n\nI noticed that you did not correctly follow the issue template. Please ensure that:\n\n- The bug can still be reproduced on the [newest nightly build](https://yazi-rs.github.io/docs/installation/#binaries).\n- The debug information (\\`yazi --debug\\`) is updated for the newest nightly.\n- The non-optional items in the checklist are checked.\n\nIssues with \\`${LABEL_NAME}\\` will be marked ready once edited with the proper content, or closed after 2 days of inactivity.\n\nOur maintainers work on Yazi in their free time, this helps them work efficiently, understand your setup quickly, and find a more appropriate solution. Thanks for your understanding! 🙏\n`\n}\n\nfunction featureRequestBody(creator, content) {\n\tif (RE_VERSION.test(content) && RE_DEPENDENCIES.test(content) && RE_CHECKLIST.test(content)) {\n\t\treturn null\n\t}\n\n\treturn `Hey @${creator}, thank you for opening the issue to help improve Yazi, appreciate it!\n\nI noticed that you did not correctly follow the issue template. Please ensure that:\n\n- The requested feature does not exist in the [newest nightly build](https://yazi-rs.github.io/docs/installation/#binaries).\n- The debug information (\\`yazi --debug\\`) is updated for the newest nightly.\n- The non-optional items in the checklist are checked.\n\nIssues with \\`${LABEL_NAME}\\` will be marked ready once edited with the proper content, or closed after 2 days of inactivity.\n\nOur maintainers work on Yazi in their free time, this helps them work efficiently, understand your setup quickly, and find a more appropriate solution. Thanks for your understanding! 🙏\n`\n}\n\nmodule.exports = async ({ github, context, core }) => {\n\tasync function nightlyHash() {\n\t\ttry {\n\t\t\tconst { data: tagRef } = await github.rest.git.getRef({ owner: \"sxyazi\", repo: \"yazi\", ref: \"tags/nightly\" })\n\t\t\treturn tagRef.object.sha.slice(0, 7)\n\t\t} catch (e) {\n\t\t\tif (e.status === 404) {\n\t\t\t\tcore.error(\"Nightly tag not found\")\n\t\t\t} else {\n\t\t\t\tcore.error(`Error fetching nightly version: ${e.message}`)\n\t\t\t}\n\t\t\treturn null\n\t\t}\n\t}\n\n\tasync function hasLabel(id, label) {\n\t\ttry {\n\t\t\tconst { data: labels } = await github.rest.issues.listLabelsOnIssue({\n\t\t\t\t...context.repo,\n\t\t\t\tissue_number: id,\n\t\t\t})\n\t\t\treturn labels.some(l => l.name === label)\n\t\t} catch (e) {\n\t\t\tcore.error(`Error checking labels: ${e.message}`)\n\t\t\treturn false\n\t\t}\n\t}\n\n\tasync function lastLabeledAt(id) {\n\t\ttry {\n\t\t\tconst { data: events } = await github.rest.issues.listEvents({\n\t\t\t\t...context.repo,\n\t\t\t\tissue_number: id,\n\t\t\t\tper_page: 100,\n\t\t\t})\n\n\t\t\tconst all = events.filter(v => v.event === \"labeled\" && v.label?.name === LABEL_NAME)\n\t\t\treturn all.at(-1)?.created_at\n\t\t} catch (e) {\n\t\t\tcore.error(`Error getting label timestamp: ${e.message}`)\n\t\t\treturn null\n\t\t}\n\t}\n\n\tasync function removedLabelManually(id) {\n\t\ttry {\n\t\t\tconst { data: events } = await github.rest.issues.listEvents({\n\t\t\t\t...context.repo,\n\t\t\t\tissue_number: id,\n\t\t\t\tper_page: 100,\n\t\t\t})\n\n\t\t\tconst all = events.filter(v => v.event === \"unlabeled\" && v.label?.name === LABEL_NAME)\n\t\t\treturn all.length === 0 ? false : !all.at(-1).actor.login.endsWith(\"[bot]\")\n\t\t} catch (e) {\n\t\t\tcore.error(`Error checking label removal history: ${e.message}`)\n\t\t\treturn false\n\t\t}\n\t}\n\n\tasync function updateLabels(id, mark, body) {\n\t\ttry {\n\t\t\tconst marked = await hasLabel(id, LABEL_NAME)\n\n\t\t\tif (!mark && marked) {\n\t\t\t\tawait github.rest.issues.removeLabel({\n\t\t\t\t\t...context.repo,\n\t\t\t\t\tissue_number: id,\n\t\t\t\t\tname: LABEL_NAME,\n\t\t\t\t})\n\t\t\t\tawait hideOldComments(id)\n\t\t\t} else if (mark && !marked && !await removedLabelManually(id)) {\n\t\t\t\tawait github.rest.issues.addLabels({\n\t\t\t\t\t...context.repo,\n\t\t\t\t\tissue_number: id,\n\t\t\t\t\tlabels: [LABEL_NAME],\n\t\t\t\t})\n\t\t\t\tawait hideOldComments(id)\n\t\t\t\tawait github.rest.issues.createComment({\n\t\t\t\t\t...context.repo,\n\t\t\t\t\tissue_number: id,\n\t\t\t\t\tbody,\n\t\t\t\t})\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tcore.error(`Error updating labels: ${e.message}`)\n\t\t}\n\t}\n\n\tasync function hideOldComments(id) {\n\t\ttry {\n\t\t\tconst comments = await github.paginate(github.rest.issues.listComments, {\n\t\t\t\t...context.repo,\n\t\t\t\tissue_number: id,\n\t\t\t\tper_page: 100,\n\t\t\t})\n\n\t\t\tfor (const c of comments) {\n\t\t\t\tconst byBot = c.user?.login?.endsWith(\"[bot]\") || c.user?.type === \"Bot\"\n\t\t\t\tconst contains = c?.body?.includes(\"or closed after 2 days of inactivity\")\n\t\t\t\tif (!byBot || !contains || !c.node_id) continue\n\n\t\t\t\ttry {\n\t\t\t\t\tawait github.graphql(\n\t\t\t\t\t\t`mutation($subjectId: ID!, $classifier: ReportedContentClassifiers!) {\n\t\t\t\t\t\t\tminimizeComment(input: {subjectId: $subjectId, classifier: $classifier}) {\n\t\t\t\t\t\t\t\tminimizedComment { isMinimized }\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}`,\n\t\t\t\t\t\t{ subjectId: c.node_id, classifier: \"OUTDATED\" },\n\t\t\t\t\t)\n\t\t\t\t} catch (e) {\n\t\t\t\t\tcore.error(`Error minimizing comment ${c.id}: ${e.message}`)\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tcore.error(`Error listing comments: ${e.message}`)\n\t\t}\n\t}\n\n\tasync function closeOldIssues() {\n\t\ttry {\n\t\t\tconst { data: issues } = await github.rest.issues.listForRepo({\n\t\t\t\t...context.repo,\n\t\t\t\tstate: \"open\",\n\t\t\t\tlabels: LABEL_NAME,\n\t\t\t})\n\n\t\t\tconst now = new Date()\n\t\t\tconst twoDaysAgo = new Date(now - 2 * 24 * 60 * 60 * 1000)\n\n\t\t\tfor (const issue of issues) {\n\t\t\t\tconst markedAt = new Date(await lastLabeledAt(issue.number) || issue.created_at)\n\t\t\t\tif (markedAt < twoDaysAgo) {\n\t\t\t\t\tawait github.rest.issues.update({\n\t\t\t\t\t\t...context.repo,\n\t\t\t\t\t\tissue_number: issue.number,\n\t\t\t\t\t\tstate: \"closed\",\n\t\t\t\t\t\tstate_reason: \"not_planned\",\n\t\t\t\t\t})\n\t\t\t\t\tawait github.rest.issues.createComment({\n\t\t\t\t\t\t...context.repo,\n\t\t\t\t\t\tissue_number: issue.number,\n\t\t\t\t\t\tbody: `This issue has been automatically closed because it was marked as \\`${LABEL_NAME}\\` for more than 2 days without updates.\nIf the problem persists, please file a new issue and complete the issue template so we can capture all the details necessary to investigate further.`,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tcore.error(`Error checking old issues: ${e.message}`)\n\t\t}\n\t}\n\n\tasync function closeUnsupportedIssue(id) {\n\t\ttry {\n\t\t\tawait github.rest.issues.update({\n\t\t\t\t...context.repo,\n\t\t\t\tissue_number: id,\n\t\t\t\tstate: \"closed\",\n\t\t\t\tstate_reason: \"not_planned\",\n\t\t\t})\n\t\t\tawait github.rest.issues.createComment({\n\t\t\t\t...context.repo,\n\t\t\t\tissue_number: id,\n\t\t\t\tbody: `Unsupported issue template.\nEither the [Bug Report](https://github.com/sxyazi/yazi/issues/new?template=bug.yml) or [Feature Request](https://github.com/sxyazi/yazi/issues/new?template=feature.yml) template should be used.`,\n\t\t\t})\n\t\t} catch (e) {\n\t\t\tcore.error(`Error closing unsupported issue: ${e.message}`)\n\t\t}\n\t}\n\n\tasync function main() {\n\t\tconst hash = await nightlyHash()\n\t\tif (!hash) return\n\n\t\tif (context.eventName === \"schedule\") {\n\t\t\tawait closeOldIssues()\n\t\t\treturn\n\t\t}\n\n\t\tif (context.eventName === \"issues\") {\n\t\t\tconst id = context.payload.issue.number\n\t\t\tconst content = context.payload.issue.body || \"\"\n\t\t\tconst creator = context.payload.issue.user.login\n\n\t\t\tif (await hasLabel(id, \"bug\")) {\n\t\t\t\tconst body = bugReportBody(creator, content, hash)\n\t\t\t\tawait updateLabels(id, !!body, body)\n\t\t\t} else if (await hasLabel(id, \"feature\")) {\n\t\t\t\tconst body = featureRequestBody(creator, content)\n\t\t\t\tawait updateLabels(id, !!body, body)\n\t\t\t} else if (context.payload.action === \"opened\") {\n\t\t\t\tawait closeUnsupportedIssue(id)\n\t\t\t}\n\t\t}\n\t}\n\n\tawait main()\n}\n"
  },
  {
    "path": "scripts/validate-form/package.json",
    "content": "{\n\t\"name\": \"validate-form\",\n\t\"version\": \"1.0.0\",\n\t\"scripts\": {\n\t\t\"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n\t},\n\t\"private\": true,\n\t\"dependencies\": {\n\t\t\"@actions/core\": \"^3.0.0\",\n\t\t\"@actions/github\": \"^9.0.0\"\n\t}\n}\n"
  },
  {
    "path": "snap/snapcraft.yaml",
    "content": "name: yazi\nbase: core24\nadopt-info: yazi\nsummary: Blazing fast terminal file manager written in Rust, based on async I/O.\ndescription: |\n  Yazi is a terminal file manager written in Rust, based on non-blocking async I/O.\n  It aims to provide an efficient, user-friendly, and customizable file management experience.\nlicense: MIT\ngrade: stable\nconfinement: classic\n\nplatforms:\n  amd64:\n  arm64:\n\napps:\n  yazi:\n    command: yazi\n    environment:\n      PATH: $SNAP/bin:$SNAP/usr/bin:$PATH\n  ya:\n    command: ya\n    environment:\n      PATH: $SNAP/bin:$SNAP/usr/bin:$PATH\n\nparts:\n  yazi:\n    plugin: rust\n    source: https://github.com/sxyazi/yazi.git\n    stage-snaps:\n      - jq\n    stage-packages:\n      - 7zip-standalone\n      - chafa\n      - fd-find\n      - ffmpeg\n      # - fzf\n      - libglu1-mesa\n      - libglut3.12\n      - poppler-utils\n      - ripgrep\n      - wl-clipboard\n      - xclip\n      - zoxide\n    override-build: |\n      craftctl default\n      craftctl set version=$(git describe --tags --abbrev=0)\n    build-attributes:\n      - enable-patchelf\n    organize:\n      # Ubuntu's `fd` package installs a binary named `fdfind`. Rename it in the snap.\n      usr/bin/fdfind: usr/bin/fd\n    prime:\n      # Remove unused items bought in by dependency packages\n      - -usr/bin/fc-*\n      - -usr/bin/ffplay\n      - -usr/bin/pdfattach\n      - -usr/bin/pdfdetach\n      - -usr/bin/pdffonts\n      - -usr/bin/pdfimages\n      - -usr/bin/pdfinfo\n      - -usr/bin/pdfseparate\n      - -usr/bin/pdfsig\n      - -usr/bin/pdftocairo\n      - -usr/bin/pdftohtml\n      - -usr/bin/pdftops\n      - -usr/bin/pdftotext\n      - -usr/bin/pdfunite\n      # Remove unused libraries identified by snapcraft's linter\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libabsl_bad_*\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libabsl_civil_time.so.*\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libabsl_cord*\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libabsl_examine_stack.so.*\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libabsl_exponential_biased.so.*\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libabsl_failure_signal_handler.so.*\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libabsl_flags*\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libabsl_hash*\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libabsl_log_severity.so.*\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libabsl_periodic_sampler.so.*\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libabsl_random*\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libabsl_raw_hash_set.so.*\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libabsl_scoped_set_env.so.*\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libabsl_statusor.so.*\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libabsl_status.so.*\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libabsl_strerror.so.*\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libabsl_str_format_internal.so.*\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libcaca++.so.0.99.20\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libcjson_utils.so.1.7.17\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libfftw3_omp.so.3.6.10\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libfftw3_threads.so.3.6.10\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libflite_cmu_grapheme_lang.so.2.2\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libflite_cmu_grapheme_lex.so.2.2\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libflite_cmu_indic_lang.so.2.2\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libflite_cmu_indic_lex.so.2.2\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libflite_cmu_time_awb.so.2.2\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libfreebl3.so\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libfreeblpriv3.so\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libGLX_mesa.so.0.0.0\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libhwy_contrib.so.1.0.7\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libhwy_test.so.1.0.7\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libicui18n.so.74.2\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libicuio.so.74.2\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libicutest.so.74.2\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libicutu.so.74.2\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libjacknet.so.0.1.0\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libjackserver.so.0.1.0\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libnssckbi.so\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libnssdbm3.so\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libpulse-simple.so.0.1.1\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libsoftokn3.so\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libsphinxad.so.3.0.0\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libssl3.so\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libtheora.so.0.3.10\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libxcb-dri2.so.0.0.0\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libxcb-glx.so.0.0.0\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libxcb-present.so.0.0.0\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libxcb-sync.so.1.0.0\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libxshmfence.so.1.0.0\n      - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libzvbi-chains.so.0.0.0\n\n  # The Ubuntu `imagemagick` package does not ship a `magick` binary,\n  # which `yazi` looks for. Building the package oursleves yields the\n  # expected binary without much overhead, and works across platforms.\n  magick:\n    plugin: autotools\n    source: https://github.com/ImageMagick/ImageMagick.git\n    source-type: git\n    source-tag: 7.1.2-0\n    source-depth: 1\n    stage-packages:\n      - libgomp1\n    autotools-configure-parameters:\n      - --prefix=/usr\n    build-attributes:\n      - enable-patchelf\n    prime:\n      - usr/bin/magick\n      - usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libgomp.so.1\n      - usr/lib/libMagickCore-7.Q16HDRI.so*\n      - usr/lib/libMagickWand-7.Q16HDRI.so*\n\n  cleanup:\n    after: [yazi, magick]\n    plugin: nil\n    build-packages:\n      - patchelf\n    build-snaps:\n      - core24\n    override-prime: |\n      # Ubuntu's /usr/bin/7zz is a simple bash wrapper that just exec's\n      # /usr/lib/7zip/7zz - this just shortcuts that and places the actual\n      # executable at $SNAP/usr/bin/7zz.\n      mv $CRAFT_PRIME/usr/lib/7zip/7zz $CRAFT_PRIME/usr/bin/7zz\n\n      # Ensure we don't ship duplicates of files that exist in the core24\n      # snap.\n      cd /snap/core24/current\n      find . -type f,l -exec rm -rf \"${CRAFT_PRIME}/{}\" \\;\n"
  },
  {
    "path": "stylua.toml",
    "content": "syntax                    = \"Lua54\"\nindent_width              = 2\ncall_parentheses          = \"NoSingleTable\"\ncollapse_simple_statement = \"FunctionOnly\"\n\n[sort_requires]\nenabled = true\n"
  },
  {
    "path": "yazi-actor/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-actor\"\ndescription            = \"Yazi actor model\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[features]\ndefault      = [ \"vendored-lua\" ]\nvendored-lua = [ \"mlua/vendored\" ]\n\n[dependencies]\nyazi-binding   = { path = \"../yazi-binding\", version = \"26.2.2\" }\nyazi-boot      = { path = \"../yazi-boot\", version = \"26.2.2\" }\nyazi-config    = { path = \"../yazi-config\", version = \"26.2.2\" }\nyazi-core      = { path = \"../yazi-core\", version = \"26.2.2\" }\nyazi-dds       = { path = \"../yazi-dds\", version = \"26.2.2\" }\nyazi-emulator  = { path = \"../yazi-emulator\", version = \"26.2.2\" }\nyazi-fs        = { path = \"../yazi-fs\", version = \"26.2.2\" }\nyazi-macro     = { path = \"../yazi-macro\", version = \"26.2.2\" }\nyazi-parser    = { path = \"../yazi-parser\", version = \"26.2.2\" }\nyazi-plugin    = { path = \"../yazi-plugin\", version = \"26.2.2\" }\nyazi-proxy     = { path = \"../yazi-proxy\", version = \"26.2.2\" }\nyazi-scheduler = { path = \"../yazi-scheduler\", version = \"26.2.2\" }\nyazi-shared    = { path = \"../yazi-shared\", version = \"26.2.2\" }\nyazi-term      = { path = \"../yazi-term\", version = \"26.2.2\" }\nyazi-tty       = { path = \"../yazi-tty\", version = \"26.2.2\" }\nyazi-vfs       = { path = \"../yazi-vfs\", version = \"26.2.2\" }\nyazi-watcher   = { path = \"../yazi-watcher\", version = \"26.2.2\" }\nyazi-widgets   = { path = \"../yazi-widgets\", version = \"26.2.2\" }\n\n# External dependencies\nanyhow       = { workspace = true }\ncrossterm    = { workspace = true }\neither       = { workspace = true }\nfutures      = { workspace = true }\nhashbrown    = { workspace = true }\nmlua         = { workspace = true }\npaste        = { workspace = true }\nratatui      = { workspace = true }\nscopeguard   = { workspace = true }\ntokio        = { workspace = true }\ntokio-stream = { workspace = true }\ntracing      = { workspace = true }\n\n[target.\"cfg(unix)\".dependencies]\nlibc = { workspace = true }\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\ncrossterm = { workspace = true, features = [ \"use-dev-tty\", \"libc\" ] }\n"
  },
  {
    "path": "yazi-actor/README.md",
    "content": "# yazi-actor\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-actor/src/actor.rs",
    "content": "use anyhow::Result;\nuse yazi_dds::spark::SparkKind;\nuse yazi_shared::data::Data;\n\nuse crate::Ctx;\n\npub trait Actor {\n\ttype Options;\n\n\tconst NAME: &str;\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data>;\n\n\tfn hook(_cx: &Ctx, _opt: &Self::Options) -> Option<SparkKind> { None }\n}\n"
  },
  {
    "path": "yazi-actor/src/app/accept_payload.rs",
    "content": "use anyhow::Result;\nuse mlua::IntoLua;\nuse tracing::error;\nuse yazi_actor::lives::Lives;\nuse yazi_binding::runtime_scope;\nuse yazi_dds::{LOCAL, Payload, REMOTE};\nuse yazi_macro::succ;\nuse yazi_plugin::LUA;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct AcceptPayload;\n\nimpl Actor for AcceptPayload {\n\ttype Options = Payload<'static>;\n\n\tconst NAME: &str = \"accept_payload\";\n\n\tfn act(cx: &mut Ctx, payload: Payload) -> Result<Data> {\n\t\tlet kind = payload.body.kind();\n\t\tlet lock = if payload.receiver == 0 || payload.receiver != payload.sender {\n\t\t\tREMOTE.read()\n\t\t} else {\n\t\t\tLOCAL.read()\n\t\t};\n\n\t\tlet Some(handlers) = lock.get(kind).filter(|&m| !m.is_empty()).cloned() else { succ!() };\n\t\tdrop(lock);\n\n\t\tlet kind = kind.to_owned();\n\t\tsucc!(Lives::scope(cx.core, || {\n\t\t\tlet body = payload.body.into_lua(&LUA)?;\n\t\t\tfor (id, cb) in handlers {\n\t\t\t\tif let Err(e) = runtime_scope!(LUA, &id, cb.call::<()>(body.clone())) {\n\t\t\t\t\terror!(\"Failed to run `{kind}` event handler in your `{id}` plugin: {e}\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tOk(())\n\t\t})?);\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/app/bootstrap.rs",
    "content": "use anyhow::Result;\nuse yazi_actor::Ctx;\nuse yazi_boot::BOOT;\nuse yazi_macro::{act, succ};\nuse yazi_parser::{VoidOpt, mgr::CdSource};\nuse yazi_shared::{data::Data, strand::StrandLike, url::UrlLike};\n\nuse crate::Actor;\n\npub struct Bootstrap;\n\nimpl Actor for Bootstrap {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"bootstrap\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tcx.mgr.tabs.resize_with(BOOT.files.len(), Default::default);\n\n\t\tfor (i, file) in BOOT.files.iter().enumerate().rev() {\n\t\t\tcx.tab = i;\n\t\t\tif file.is_empty() {\n\t\t\t\tact!(mgr:cd, cx, (BOOT.cwds[i].clone(), CdSource::Tab))?;\n\t\t\t} else if let Ok(u) = BOOT.cwds[i].try_join(file) {\n\t\t\t\tact!(mgr:reveal, cx, (u, CdSource::Tab))?;\n\t\t\t}\n\t\t}\n\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/app/deprecate.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::act;\nuse yazi_parser::{app::DeprecateOpt, notify::{PushLevel, PushOpt}};\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Deprecate;\n\nimpl Actor for Deprecate {\n\ttype Options = DeprecateOpt;\n\n\tconst NAME: &str = \"deprecate\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tact!(notify:push, cx, PushOpt {\n\t\t\ttitle:   \"Deprecated API\".to_owned(),\n\t\t\tcontent: opt.content.into_owned(),\n\t\t\tlevel:   PushLevel::Warn,\n\t\t\ttimeout: std::time::Duration::from_secs(20),\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/app/focus.rs",
    "content": "use anyhow::Result;\nuse yazi_actor::Ctx;\nuse yazi_macro::act;\nuse yazi_parser::VoidOpt;\nuse yazi_shared::data::Data;\n\nuse crate::Actor;\n\npub struct Focus;\n\nimpl Actor for Focus {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"focus\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> { act!(mgr:refresh, cx) }\n}\n"
  },
  {
    "path": "yazi-actor/src/app/mod.rs",
    "content": "yazi_macro::mod_flat!(\n\taccept_payload\n\tbootstrap\n\tdeprecate\n\tfocus\n\tmouse\n\tplugin\n\tplugin_do\n\tquit\n\treflow\n\tresize\n\tresume\n\tstop\n\ttitle\n\tupdate_progress\n);\n"
  },
  {
    "path": "yazi-actor/src/app/mouse.rs",
    "content": "use anyhow::Result;\nuse crossterm::event::MouseEventKind;\nuse mlua::{ObjectLike, Table};\nuse tracing::error;\nuse yazi_actor::lives::Lives;\nuse yazi_binding::runtime_scope;\nuse yazi_config::YAZI;\nuse yazi_macro::succ;\nuse yazi_parser::app::MouseOpt;\nuse yazi_plugin::LUA;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Mouse;\n\nimpl Actor for Mouse {\n\ttype Options = MouseOpt;\n\n\tconst NAME: &str = \"mouse\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet event = yazi_binding::MouseEvent::from(opt.event);\n\n\t\tlet Some(size) = cx.term.as_ref().and_then(|t| t.size().ok()) else { succ!() };\n\t\tlet area = yazi_binding::elements::Rect::from(size);\n\n\t\tlet result = Lives::scope(cx.core, move || {\n\t\t\tlet root = runtime_scope!(LUA, \"root\", {\n\t\t\t\tLUA.globals().raw_get::<Table>(\"Root\")?.call_method::<Table>(\"new\", area)\n\t\t\t})?;\n\n\t\t\tif matches!(event.kind, MouseEventKind::Down(_) if YAZI.mgr.mouse_events.get().draggable()) {\n\t\t\t\troot.raw_set(\"_drag_start\", event)?;\n\t\t\t}\n\n\t\t\tmatch event.kind {\n\t\t\t\tMouseEventKind::Down(_) => root.call_method(\"click\", (event, false))?,\n\t\t\t\tMouseEventKind::Up(_) => root.call_method(\"click\", (event, true))?,\n\n\t\t\t\tMouseEventKind::ScrollDown => root.call_method(\"scroll\", (event, 1))?,\n\t\t\t\tMouseEventKind::ScrollUp => root.call_method(\"scroll\", (event, -1))?,\n\n\t\t\t\tMouseEventKind::ScrollRight => root.call_method(\"touch\", (event, 1))?,\n\t\t\t\tMouseEventKind::ScrollLeft => root.call_method(\"touch\", (event, -1))?,\n\n\t\t\t\tMouseEventKind::Moved => root.call_method(\"move\", event)?,\n\t\t\t\tMouseEventKind::Drag(_) => root.call_method(\"drag\", event)?,\n\t\t\t}\n\n\t\t\tOk(())\n\t\t});\n\n\t\tif let Err(ref e) = result {\n\t\t\terror!(\"{e}\");\n\t\t}\n\t\tsucc!(result?);\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/app/plugin.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, succ};\nuse yazi_parser::app::{PluginMode, PluginOpt};\nuse yazi_plugin::loader::LOADER;\nuse yazi_proxy::{AppProxy, NotifyProxy};\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Plugin;\n\nimpl Actor for Plugin {\n\ttype Options = PluginOpt;\n\n\tconst NAME: &str = \"plugin\";\n\n\tfn act(cx: &mut Ctx, mut opt: Self::Options) -> Result<Data> {\n\t\tlet mut hits = false;\n\t\tif let Some(chunk) = LOADER.read().get(&*opt.id) {\n\t\t\thits = true;\n\t\t\topt.mode = opt.mode.auto_then(chunk.sync_entry);\n\t\t}\n\n\t\tif opt.mode == PluginMode::Async {\n\t\t\tsucc!(cx.core.tasks.scheduler.plugin_entry(opt));\n\t\t} else if opt.mode == PluginMode::Sync && hits {\n\t\t\treturn act!(app:plugin_do, cx, opt);\n\t\t}\n\n\t\ttokio::spawn(async move {\n\t\t\tmatch LOADER.ensure(&opt.id, |_| ()).await {\n\t\t\t\tOk(()) => AppProxy::plugin_do(opt),\n\t\t\t\tErr(e) => NotifyProxy::push_error(\"Plugin load failed\", e),\n\t\t\t}\n\t\t});\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/app/plugin_do.rs",
    "content": "use anyhow::Result;\nuse mlua::ObjectLike;\nuse scopeguard::defer;\nuse tracing::{error, warn};\nuse yazi_binding::runtime_mut;\nuse yazi_dds::Sendable;\nuse yazi_macro::succ;\nuse yazi_parser::app::{PluginMode, PluginOpt};\nuse yazi_plugin::{LUA, loader::{LOADER, Loader}};\nuse yazi_proxy::NotifyProxy;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx, lives::Lives};\n\npub struct PluginDo;\n\nimpl Actor for PluginDo {\n\ttype Options = PluginOpt;\n\n\tconst NAME: &str = \"plugin_do\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet loader = LOADER.read();\n\t\tlet Some(chunk) = loader.get(&*opt.id) else {\n\t\t\tsucc!(warn!(\"plugin `{}` not found\", opt.id));\n\t\t};\n\n\t\tif let Err(e) = Loader::compatible_or_error(&opt.id, chunk) {\n\t\t\tsucc!(NotifyProxy::push_error(\"Incompatible plugin\", e));\n\t\t}\n\n\t\tif opt.mode.auto_then(chunk.sync_entry) != PluginMode::Sync {\n\t\t\tsucc!(cx.core.tasks.scheduler.plugin_entry(opt));\n\t\t}\n\n\t\tlet blocking = runtime_mut!(LUA)?.critical_push(&opt.id, true);\n\t\tdefer! { _ = runtime_mut!(LUA).map(|mut r| r.critical_pop(blocking)) }\n\n\t\tlet plugin = match LOADER.load_chunk(&LUA, &opt.id, chunk) {\n\t\t\tOk(t) => t,\n\t\t\tErr(e) => succ!(warn!(\"{e}\")),\n\t\t};\n\t\tdrop(loader);\n\n\t\tlet result = Lives::scope(cx.core, || {\n\t\t\tif let Some(cb) = opt.callback {\n\t\t\t\tcb(&LUA, plugin)\n\t\t\t} else {\n\t\t\t\tlet job = LUA.create_table_from([(\"args\", Sendable::args_to_table(&LUA, opt.args)?)])?;\n\t\t\t\tplugin.call_method(\"entry\", job)\n\t\t\t}\n\t\t});\n\t\tif let Err(ref e) = result {\n\t\t\terror!(\"Sync plugin `{}` failed: {e}\", opt.id);\n\t\t}\n\t\tsucc!(result?);\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/app/quit.rs",
    "content": "use anyhow::Result;\nuse yazi_boot::ARGS;\nuse yazi_fs::provider::{Provider, local::Local};\nuse yazi_parser::app::QuitOpt;\nuse yazi_shared::{data::Data, strand::{StrandBuf, StrandLike, ToStrand}};\nuse yazi_term::Term;\n\nuse crate::{Actor, Ctx};\n\npub struct Quit;\n\nimpl Actor for Quit {\n\ttype Options = QuitOpt;\n\n\tconst NAME: &str = \"quit\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tcx.tasks.shutdown();\n\t\tcx.mgr.shutdown();\n\n\t\tfutures::executor::block_on(async {\n\t\t\t_ = futures::join!(\n\t\t\t\tyazi_dds::shutdown(),\n\t\t\t\tyazi_dds::STATE.drain(),\n\t\t\t\tSelf::cwd_to_file(cx, opt.no_cwd_file),\n\t\t\t\tSelf::selected_to_file(opt.selected)\n\t\t\t);\n\t\t});\n\n\t\tTerm::goodbye(|| opt.code);\n\t}\n}\n\nimpl Quit {\n\tasync fn cwd_to_file(cx: &Ctx<'_>, no: bool) {\n\t\tif let Some(p) = ARGS.cwd_file.as_ref().filter(|_| !no) {\n\t\t\tlet cwd = cx.mgr.cwd().to_strand();\n\t\t\tLocal::regular(p).write(cwd.encoded_bytes()).await.ok();\n\t\t}\n\t}\n\n\tasync fn selected_to_file(selected: Option<StrandBuf>) {\n\t\tif let (Some(s), Some(p)) = (selected, &ARGS.chooser_file) {\n\t\t\tLocal::regular(p).write(s.encoded_bytes()).await.ok();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/app/reflow.rs",
    "content": "use anyhow::Result;\nuse mlua::Value;\nuse ratatui::layout::Position;\nuse tracing::error;\nuse yazi_actor::lives::Lives;\nuse yazi_config::LAYOUT;\nuse yazi_macro::{render, succ};\nuse yazi_parser::app::ReflowOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Reflow;\n\nimpl Actor for Reflow {\n\ttype Options = ReflowOpt;\n\n\tconst NAME: &str = \"reflow\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet Some(size) = cx.term.as_ref().and_then(|t| t.size().ok()) else { succ!() };\n\t\tlet mut layout = LAYOUT.get();\n\n\t\tlet result = Lives::scope(cx.core, || {\n\t\t\tlet comps = (opt.reflow)((Position::ORIGIN, size).into())?;\n\n\t\t\tfor v in comps.sequence_values::<Value>() {\n\t\t\t\tlet Value::Table(t) = v? else {\n\t\t\t\t\terror!(\"`reflow()` must return a table of components\");\n\t\t\t\t\tcontinue;\n\t\t\t\t};\n\n\t\t\t\tlet id: mlua::String = t.get(\"_id\")?;\n\t\t\t\tmatch &*id.as_bytes() {\n\t\t\t\t\tb\"current\" => layout.current = *t.raw_get::<yazi_binding::elements::Rect>(\"_area\")?,\n\t\t\t\t\tb\"preview\" => layout.preview = *t.raw_get::<yazi_binding::elements::Rect>(\"_area\")?,\n\t\t\t\t\tb\"progress\" => layout.progress = *t.raw_get::<yazi_binding::elements::Rect>(\"_area\")?,\n\t\t\t\t\t_ => {}\n\t\t\t\t}\n\t\t\t}\n\t\t\tOk(())\n\t\t});\n\n\t\tif layout != LAYOUT.get() {\n\t\t\tLAYOUT.set(layout);\n\t\t\trender!();\n\t\t}\n\n\t\tif let Err(ref e) = result {\n\t\t\terror!(\"Failed to `reflow()` the `Root` component:\\n{e}\");\n\t\t}\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/app/resize.rs",
    "content": "use anyhow::Result;\nuse yazi_actor::Ctx;\nuse yazi_macro::act;\nuse yazi_parser::app::ReflowOpt;\nuse yazi_shared::data::Data;\n\nuse crate::Actor;\n\npub struct Resize;\n\nimpl Actor for Resize {\n\ttype Options = ReflowOpt;\n\n\tconst NAME: &str = \"resize\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tact!(app:reflow, cx, opt)?;\n\n\t\tcx.current_mut().arrow(0);\n\t\tcx.parent_mut().map(|f| f.arrow(0));\n\t\tcx.current_mut().sync_page(true);\n\n\t\tact!(mgr:peek, cx)\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/app/resume.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::app::ResumeOpt;\nuse yazi_shared::data::Data;\nuse yazi_term::Term;\n\nuse crate::{Actor, Ctx};\n\npub struct Resume;\n\nimpl Actor for Resume {\n\ttype Options = ResumeOpt;\n\n\tconst NAME: &str = \"resume\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tcx.active_mut().preview.reset();\n\t\t*cx.term = Some(Term::start()?);\n\n\t\t// While the app resumes, it's possible that the terminal size has changed.\n\t\t// We need to trigger a resize, and render the UI based on the resized area.\n\t\tact!(app:resize, cx, opt.reflow)?;\n\n\t\topt.tx.send((true, opt.token))?;\n\n\t\tact!(app:title, cx).ok();\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/app/stop.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::succ;\nuse yazi_parser::app::StopOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Stop;\n\nimpl Actor for Stop {\n\ttype Options = StopOpt;\n\n\tconst NAME: &str = \"stop\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tcx.active_mut().preview.reset_image();\n\n\t\t// We need to destroy the `term` first before stopping the `signals`\n\t\t// to prevent any signal from triggering the term to render again\n\t\t// while the app is being suspended.\n\t\t*cx.term = None;\n\n\t\topt.tx.send((false, opt.token))?;\n\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/app/title.rs",
    "content": "use anyhow::Result;\nuse crossterm::{execute, terminal::SetTitle};\nuse yazi_actor::Ctx;\nuse yazi_dds::spark::SparkKind;\nuse yazi_macro::succ;\nuse yazi_parser::app::TitleOpt;\nuse yazi_shared::{Source, data::Data};\nuse yazi_term::TermState;\nuse yazi_tty::TTY;\n\nuse crate::Actor;\n\npub struct Title;\n\nimpl Actor for Title {\n\ttype Options = TitleOpt;\n\n\tconst NAME: &str = \"title\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet s = opt.value.unwrap_or_else(|| format!(\"Yazi: {}\", cx.tab().name()).into());\n\t\texecute!(TTY.writer(), SetTitle(&s))?;\n\n\t\tyazi_term::STATE.set(TermState { title: !s.is_empty(), ..yazi_term::STATE.get() });\n\t\tsucc!()\n\t}\n\n\tfn hook(cx: &Ctx, _opt: &Self::Options) -> Option<SparkKind> {\n\t\tmatch cx.source() {\n\t\t\tSource::Ind => Some(SparkKind::IndAppTitle),\n\t\t\t_ => None,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/app/update_progress.rs",
    "content": "use anyhow::Result;\nuse yazi_actor::Ctx;\nuse yazi_macro::{act, render, render_partial, succ};\nuse yazi_parser::app::UpdateProgressOpt;\nuse yazi_shared::data::Data;\n\nuse crate::Actor;\n\npub struct UpdateProgress;\n\nimpl Actor for UpdateProgress {\n\ttype Options = UpdateProgressOpt;\n\n\tconst NAME: &str = \"update_progress\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\t// Update the progress of all tasks.\n\t\tlet tasks = &mut cx.tasks;\n\t\tlet progressed = tasks.summary != opt.summary;\n\t\ttasks.summary = opt.summary;\n\n\t\t// If the task manager is visible, update the snaps with a full render.\n\t\tif tasks.visible {\n\t\t\tlet new = tasks.paginate();\n\t\t\tif tasks.snaps != new {\n\t\t\t\ttasks.snaps = new;\n\t\t\t\tact!(tasks:arrow, cx)?;\n\t\t\t\tsucc!(render!());\n\t\t\t}\n\t\t}\n\n\t\tif !progressed {\n\t\t\tsucc!()\n\t\t} else if tasks.summary.total == 0 {\n\t\t\tsucc!(render!())\n\t\t} else {\n\t\t\tsucc!(render_partial!())\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/cmp/arrow.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{render, succ};\nuse yazi_parser::ArrowOpt;\nuse yazi_shared::data::Data;\nuse yazi_widgets::Scrollable;\n\nuse crate::{Actor, Ctx};\n\npub struct Arrow;\n\nimpl Actor for Arrow {\n\ttype Options = ArrowOpt;\n\n\tconst NAME: &str = \"arrow\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tsucc!(render!(cx.cmp.scroll(opt.step)));\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/cmp/close.rs",
    "content": "use std::mem;\n\nuse anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::cmp::CloseOpt;\nuse yazi_shared::data::Data;\nuse yazi_widgets::input::parser::CompleteOpt;\n\nuse crate::{Actor, Ctx};\n\npub struct Close;\n\nimpl Actor for Close {\n\ttype Options = CloseOpt;\n\n\tconst NAME: &str = \"close\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet cmp = &mut cx.cmp;\n\t\tif let Some(item) = cmp.selected().filter(|_| opt.submit).cloned() {\n\t\t\treturn act!(input:complete, cx, CompleteOpt { name: item.name, is_dir: item.is_dir, ticket: cmp.ticket });\n\t\t}\n\n\t\tcmp.caches.clear();\n\t\tcmp.ticket = Default::default();\n\t\tcmp.handle.take().map(|h| h.abort());\n\t\tsucc!(render!(mem::replace(&mut cmp.visible, false)));\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/cmp/mod.rs",
    "content": "yazi_macro::mod_flat!(arrow close show trigger);\n"
  },
  {
    "path": "yazi-actor/src/cmp/show.rs",
    "content": "use std::{mem, ops::ControlFlow};\n\nuse anyhow::Result;\nuse yazi_macro::{render, succ};\nuse yazi_parser::cmp::{CmpItem, ShowOpt};\nuse yazi_shared::{data::Data, path::{AsPath, PathDyn}, strand::StrandLike};\n\nuse crate::{Actor, Ctx};\n\nconst LIMIT: usize = 30;\n\npub struct Show;\n\nimpl Actor for Show {\n\ttype Options = ShowOpt;\n\n\tconst NAME: &str = \"show\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet cmp = &mut cx.cmp;\n\t\tif cmp.ticket != opt.ticket {\n\t\t\tsucc!();\n\t\t}\n\n\t\tif !opt.cache.is_empty() {\n\t\t\tcmp.caches.insert(opt.cache_name.clone(), opt.cache);\n\t\t}\n\t\tlet Some(cache) = cmp.caches.get(&opt.cache_name) else {\n\t\t\tsucc!();\n\t\t};\n\n\t\tcmp.matches = Self::match_candidates(opt.word.as_path(), cache);\n\t\tif cmp.matches.is_empty() {\n\t\t\tsucc!(render!(mem::replace(&mut cmp.visible, false)));\n\t\t}\n\n\t\tcmp.offset = 0;\n\t\tcmp.cursor = 0;\n\t\tcmp.visible = true;\n\t\tsucc!(render!());\n\t}\n}\n\nimpl Show {\n\tfn match_candidates(word: PathDyn, cache: &[CmpItem]) -> Vec<CmpItem> {\n\t\tlet smart = !word.encoded_bytes().iter().any(|&b| b.is_ascii_uppercase());\n\n\t\tlet flow = cache.iter().try_fold((Vec::new(), Vec::new()), |(mut exact, mut fuzzy), item| {\n\t\t\tlet starts_with = if smart {\n\t\t\t\titem.name.starts_with_ignore_ascii_case(word)\n\t\t\t} else {\n\t\t\t\titem.name.starts_with(word)\n\t\t\t};\n\n\t\t\tif starts_with {\n\t\t\t\texact.push(item);\n\t\t\t\tif exact.len() >= LIMIT {\n\t\t\t\t\treturn ControlFlow::Break((exact, fuzzy));\n\t\t\t\t}\n\t\t\t} else if fuzzy.len() < LIMIT - exact.len() && item.name.contains(word) {\n\t\t\t\t// Here we don't break the control flow, since we want more exact matching.\n\t\t\t\tfuzzy.push(item)\n\t\t\t}\n\t\t\tControlFlow::Continue((exact, fuzzy))\n\t\t});\n\n\t\tlet (exact, fuzzy) = match flow {\n\t\t\tControlFlow::Continue(v) => v,\n\t\t\tControlFlow::Break(v) => v,\n\t\t};\n\n\t\tlet it = fuzzy.into_iter().take(LIMIT - exact.len());\n\t\texact.into_iter().chain(it).cloned().collect()\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/cmp/trigger.rs",
    "content": "use std::{io, mem};\n\nuse anyhow::Result;\nuse yazi_fs::{path::clean_url, provider::{DirReader, FileHolder}};\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::cmp::{CmpItem, ShowOpt, TriggerOpt};\nuse yazi_proxy::CmpProxy;\nuse yazi_shared::{AnyAsciiChar, BytePredictor, data::Data, natsort, path::{AsPath, PathBufDyn, PathLike}, scheme::{SchemeCow, SchemeLike}, strand::{AsStrand, StrandLike}, url::{UrlBuf, UrlCow, UrlLike}};\nuse yazi_vfs::provider;\n\nuse crate::{Actor, Ctx};\n\npub struct Trigger;\n\nimpl Actor for Trigger {\n\ttype Options = TriggerOpt;\n\n\tconst NAME: &str = \"trigger\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tif opt.ticket.is_some_and(|t| t != cx.cmp.ticket) {\n\t\t\tsucc!();\n\t\t} else if opt.ticket.is_none() {\n\t\t\tcx.cmp.ticket = cx.input.ticket.current();\n\t\t}\n\n\t\tcx.cmp.handle.take().map(|h| h.abort());\n\t\tlet Some((parent, word)) = Self::split_url(&opt.word) else {\n\t\t\treturn act!(cmp:close, cx, false);\n\t\t};\n\n\t\tlet ticket = cx.cmp.ticket;\n\t\tif cx.cmp.caches.contains_key(&parent) {\n\t\t\treturn act!(cmp:show, cx, ShowOpt { cache: vec![], cache_name: parent, word, ticket });\n\t\t}\n\n\t\tcx.cmp.handle = Some(tokio::spawn(async move {\n\t\t\tlet mut dir = provider::read_dir(&parent).await?;\n\t\t\tlet mut cache = vec![];\n\n\t\t\t// \"/\" is both a directory separator and the root directory per se\n\t\t\t// As there's no parent directory for the FS root, it is a special case\n\t\t\tif parent.loc() == \"/\" {\n\t\t\t\tcache.push(CmpItem { name: Default::default(), is_dir: true });\n\t\t\t}\n\n\t\t\twhile let Ok(Some(ent)) = dir.next().await {\n\t\t\t\tif let Ok(ft) = ent.file_type().await {\n\t\t\t\t\tcache.push(CmpItem { name: ent.name().into_owned(), is_dir: ft.is_dir() });\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !cache.is_empty() {\n\t\t\t\tcache\n\t\t\t\t\t.sort_unstable_by(|a, b| natsort(a.name.encoded_bytes(), b.name.encoded_bytes(), false));\n\t\t\t\tCmpProxy::show(ShowOpt { cache, cache_name: parent, word, ticket });\n\t\t\t}\n\n\t\t\tOk::<_, io::Error>(())\n\t\t}));\n\n\t\tsucc!(render!(mem::replace(&mut cx.cmp.visible, false)));\n\t}\n}\n\nimpl Trigger {\n\tfn split_url(s: &str) -> Option<(UrlBuf, PathBufDyn)> {\n\t\tlet sep = if cfg!(windows) {\n\t\t\tAnyAsciiChar::new(b\"/\\\\\").unwrap()\n\t\t} else {\n\t\t\tAnyAsciiChar::new(b\"/\").unwrap()\n\t\t};\n\n\t\tlet (scheme, path) = SchemeCow::parse(s.as_bytes()).ok()?;\n\t\tif path.is_empty() && !sep.predicate(s.bytes().last()?) {\n\t\t\treturn None; // We don't complete a `sftp://test`, but `sftp://test/`\n\t\t}\n\n\t\t// Scheme\n\t\tlet scheme = scheme.zeroed();\n\t\tif scheme.is_local() && path.as_strand() == \"~\" {\n\t\t\treturn None; // We don't complete a `~`, but `~/`\n\t\t}\n\n\t\t// Child\n\t\tlet child = path.rsplit_pred(sep).map_or(path.as_path(), |(_, c)| c).to_owned();\n\n\t\t// Parent\n\t\tlet url = UrlCow::try_from((scheme.clone().zeroed(), path)).ok()?;\n\t\tlet abs = if let Some(u) = provider::try_absolute(&url) { u } else { url };\n\t\tlet parent = abs.loc().try_strip_suffix(&child).ok()?;\n\n\t\tSome((clean_url(UrlCow::try_from((scheme, parent)).ok()?), child))\n\t}\n}\n\n#[cfg(test)]\nmod tests {\n\tuse yazi_fs::CWD;\n\tuse yazi_shared::url::UrlLike;\n\n\tuse super::*;\n\n\tfn compare(s: &str, parent: &str, child: &str) {\n\t\tlet (mut p, c) = Trigger::split_url(s).unwrap();\n\t\tif let Ok(u) = p.try_strip_prefix(yazi_fs::CWD.load().as_ref()) {\n\t\t\tp = UrlBuf::Regular(u.as_os().unwrap().into());\n\t\t}\n\t\tassert_eq!((p, c.to_str().unwrap()), (parent.parse().unwrap(), child));\n\t}\n\n\t#[cfg(unix)]\n\t#[test]\n\tfn test_split() {\n\t\tyazi_shared::init_tests();\n\t\tyazi_fs::init();\n\n\t\tassert_eq!(Trigger::split_url(\"\"), None);\n\t\tassert_eq!(Trigger::split_url(\"sftp://test\"), None);\n\t\tcompare(\" \", \"\", \" \");\n\n\t\tcompare(\"/\", \"/\", \"\");\n\t\tcompare(\"//\", \"/\", \"\");\n\t\tcompare(\"///\", \"/\", \"\");\n\n\t\tcompare(\"/foo\", \"/\", \"foo\");\n\t\tcompare(\"//foo\", \"/\", \"foo\");\n\t\tcompare(\"///foo\", \"/\", \"foo\");\n\n\t\tcompare(\"/foo/\", \"/foo/\", \"\");\n\t\tcompare(\"//foo/\", \"/foo/\", \"\");\n\t\tcompare(\"/foo/bar\", \"/foo/\", \"bar\");\n\t\tcompare(\"///foo/bar\", \"/foo/\", \"bar\");\n\n\t\tCWD.set(&\"sftp://test\".parse::<UrlBuf>().unwrap(), || {});\n\t\tcompare(\"sftp://test/a\", \"sftp://test/.\", \"a\");\n\t\tcompare(\"sftp://test//a\", \"sftp://test//\", \"a\");\n\t\tcompare(\"sftp://test2/a\", \"sftp://test2/.\", \"a\");\n\t\tcompare(\"sftp://test2//a\", \"sftp://test2//\", \"a\");\n\t}\n\n\t#[cfg(windows)]\n\t#[test]\n\tfn test_split() {\n\t\tyazi_fs::init();\n\t\tcompare(\"foo\", \"\", \"foo\");\n\n\t\tcompare(r\"foo\\\", r\"foo\\\", \"\");\n\t\tcompare(r\"foo\\bar\", r\"foo\\\", \"bar\");\n\t\tcompare(r\"foo\\bar\\\", r\"foo\\bar\\\", \"\");\n\n\t\tcompare(r\"C:\\\", r\"C:\\\", \"\");\n\t\tcompare(r\"C:\\foo\", r\"C:\\\", \"foo\");\n\t\tcompare(r\"C:\\foo\\\", r\"C:\\foo\\\", \"\");\n\t\tcompare(r\"C:\\foo\\bar\", r\"C:\\foo\\\", \"bar\");\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/confirm/arrow.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{render, succ};\nuse yazi_parser::ArrowOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Arrow;\n\nimpl Actor for Arrow {\n\ttype Options = ArrowOpt;\n\n\tconst NAME: &str = \"arrow\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet confirm = &mut cx.core.confirm;\n\n\t\tlet area = cx.core.mgr.area(confirm.position);\n\t\tlet len = confirm.list.line_count(area.width);\n\n\t\tlet old = confirm.offset;\n\t\tconfirm.offset = opt.step.add(confirm.offset, len, area.height as _);\n\n\t\tsucc!(render!(old != confirm.offset));\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/confirm/close.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{render, succ};\nuse yazi_parser::confirm::CloseOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Close;\n\nimpl Actor for Close {\n\ttype Options = CloseOpt;\n\n\tconst NAME: &str = \"close\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tcx.confirm.token.complete(opt.submit);\n\t\tcx.confirm.visible = false;\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/confirm/mod.rs",
    "content": "yazi_macro::mod_flat!(arrow close show);\n"
  },
  {
    "path": "yazi-actor/src/confirm/show.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::confirm::ShowOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Show;\n\nimpl Actor for Show {\n\ttype Options = ShowOpt;\n\n\tconst NAME: &str = \"show\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tact!(confirm:close, cx)?;\n\n\t\tlet confirm = &mut cx.confirm;\n\t\tconfirm.title = opt.cfg.title;\n\t\tconfirm.body = opt.cfg.body;\n\t\tconfirm.list = opt.cfg.list;\n\n\t\tconfirm.position = opt.cfg.position;\n\t\tconfirm.offset = 0;\n\n\t\tconfirm.token = opt.token;\n\t\tconfirm.visible = true;\n\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/context.rs",
    "content": "use std::ops::{Deref, DerefMut};\n\nuse anyhow::{Result, anyhow};\nuse yazi_core::{Core, mgr::Tabs, tab::{Folder, Tab}, tasks::Tasks};\nuse yazi_fs::File;\nuse yazi_shared::{Id, Source, event::Action, url::UrlBuf};\nuse yazi_term::Term;\n\npub struct Ctx<'a> {\n\tpub core:      &'a mut Core,\n\tpub term:      &'a mut Option<Term>,\n\tpub tab:       usize,\n\tpub level:     usize,\n\tpub source:    Source,\n\t#[cfg(debug_assertions)]\n\tpub backtrace: Vec<&'static str>,\n}\n\nimpl Deref for Ctx<'_> {\n\ttype Target = Core;\n\n\tfn deref(&self) -> &Self::Target { self.core }\n}\n\nimpl DerefMut for Ctx<'_> {\n\tfn deref_mut(&mut self) -> &mut Self::Target { self.core }\n}\n\nimpl<'a> Ctx<'a> {\n\t#[inline]\n\tpub fn new(action: &Action, core: &'a mut Core, term: &'a mut Option<Term>) -> Result<Self> {\n\t\tlet tab = if let Ok(id) = action.get::<Id>(\"tab\") {\n\t\t\tcore\n\t\t\t\t.mgr\n\t\t\t\t.tabs\n\t\t\t\t.iter()\n\t\t\t\t.position(|t| t.id == id)\n\t\t\t\t.ok_or_else(|| anyhow!(\"Tab with id {id} not found\"))?\n\t\t} else {\n\t\t\tcore.mgr.tabs.cursor\n\t\t};\n\n\t\tOk(Self {\n\t\t\tcore,\n\t\t\tterm,\n\t\t\ttab,\n\t\t\tlevel: 0,\n\t\t\tsource: action.source,\n\t\t\t#[cfg(debug_assertions)]\n\t\t\tbacktrace: vec![],\n\t\t})\n\t}\n\n\t#[inline]\n\tpub fn renew<'b>(cx: &'a mut Ctx<'b>) -> Self {\n\t\tlet tab = cx.core.mgr.tabs.cursor;\n\t\tSelf {\n\t\t\tcore: cx.core,\n\t\t\tterm: cx.term,\n\t\t\ttab,\n\t\t\tlevel: cx.level,\n\t\t\tsource: cx.source,\n\t\t\t#[cfg(debug_assertions)]\n\t\t\tbacktrace: vec![],\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn active(core: &'a mut Core, term: &'a mut Option<Term>) -> Self {\n\t\tlet tab = core.mgr.tabs.cursor;\n\t\tSelf {\n\t\t\tcore,\n\t\t\tterm,\n\t\t\ttab,\n\t\t\tlevel: 0,\n\t\t\tsource: Source::Unknown,\n\t\t\t#[cfg(debug_assertions)]\n\t\t\tbacktrace: vec![],\n\t\t}\n\t}\n}\n\nimpl<'a> Ctx<'a> {\n\t#[inline]\n\tpub fn tabs(&self) -> &Tabs { &self.mgr.tabs }\n\n\t#[inline]\n\tpub fn tabs_mut(&mut self) -> &mut Tabs { &mut self.mgr.tabs }\n\n\t#[inline]\n\tpub fn tab(&self) -> &Tab { &self.tabs()[self.tab] }\n\n\t#[inline]\n\tpub fn tab_mut(&mut self) -> &mut Tab { &mut self.core.mgr.tabs[self.tab] }\n\n\t#[inline]\n\tpub fn cwd(&self) -> &UrlBuf { self.tab().cwd() }\n\n\t#[inline]\n\tpub fn parent(&self) -> Option<&Folder> { self.tab().parent.as_ref() }\n\n\t#[inline]\n\tpub fn parent_mut(&mut self) -> Option<&mut Folder> { self.tab_mut().parent.as_mut() }\n\n\t#[inline]\n\tpub fn current(&self) -> &Folder { &self.tab().current }\n\n\t#[inline]\n\tpub fn current_mut(&mut self) -> &mut Folder { &mut self.tab_mut().current }\n\n\t#[inline]\n\tpub fn hovered(&self) -> Option<&File> { self.tab().hovered() }\n\n\t#[inline]\n\tpub fn hovered_folder(&self) -> Option<&Folder> { self.tab().hovered_folder() }\n\n\t#[inline]\n\tpub fn hovered_folder_mut(&mut self) -> Option<&mut Folder> {\n\t\tself.tab_mut().hovered_folder_mut()\n\t}\n\n\t#[inline]\n\tpub fn tasks(&self) -> &Tasks { &self.tasks }\n\n\t#[inline]\n\tpub fn source(&self) -> Source { if self.level != 1 { Source::Ind } else { self.source } }\n}\n"
  },
  {
    "path": "yazi-actor/src/core/mod.rs",
    "content": "yazi_macro::mod_flat!(preflight);\n"
  },
  {
    "path": "yazi-actor/src/core/preflight.rs",
    "content": "use anyhow::Result;\nuse mlua::{ErrorContext, ExternalError, IntoLua, Value};\nuse yazi_binding::runtime_scope;\nuse yazi_dds::{LOCAL, spark::{Spark, SparkKind}};\nuse yazi_plugin::LUA;\n\nuse crate::{Ctx, lives::Lives};\n\npub struct Preflight;\n\nimpl Preflight {\n\tpub fn act<'a>(cx: &mut Ctx, opt: (SparkKind, Spark<'a>)) -> Result<Spark<'a>> {\n\t\tlet kind = opt.0;\n\t\tlet Some(handlers) = LOCAL.read().get(kind.as_ref()).filter(|&m| !m.is_empty()).cloned() else {\n\t\t\treturn Ok(opt.1);\n\t\t};\n\n\t\tOk(Lives::scope(cx.core, || {\n\t\t\tlet mut body = opt.1.into_lua(&LUA)?;\n\t\t\tfor (id, cb) in handlers {\n\t\t\t\tmatch runtime_scope!(LUA, &id, cb.call::<Value>(&body)) {\n\t\t\t\t\tOk(Value::Nil) => {\n\t\t\t\t\t\tErr(format!(\"`{kind}` event cancelled by `{id}` plugin on preflight\").into_lua_err())?\n\t\t\t\t\t}\n\t\t\t\t\tOk(v) => body = v,\n\t\t\t\t\tErr(e) => Err(\n\t\t\t\t\t\tformat!(\"Failed to run `{kind}` event handler in `{id}` plugin: {e}\").into_lua_err(),\n\t\t\t\t\t)?,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tSpark::from_lua(&LUA, kind, body)\n\t\t\t\t.with_context(|e| format!(\"Unexpected return type from `{kind}` event handlers: {e}\"))\n\t\t})?)\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/help/arrow.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{render, succ};\nuse yazi_parser::ArrowOpt;\nuse yazi_shared::data::Data;\nuse yazi_widgets::Scrollable;\n\nuse crate::{Actor, Ctx};\n\npub struct Arrow;\n\nimpl Actor for Arrow {\n\ttype Options = ArrowOpt;\n\n\tconst NAME: &str = \"arrow\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tsucc!(render!(cx.help.scroll(opt.step)));\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/help/escape.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::VoidOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Escape;\n\nimpl Actor for Escape {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"escape\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tif cx.help.keyword().is_none() {\n\t\t\treturn act!(help:toggle, cx, cx.help.layer);\n\t\t}\n\n\t\tlet help = &mut cx.help;\n\t\thelp.keyword = String::new();\n\t\thelp.in_filter = None;\n\t\thelp.filter_apply();\n\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/help/filter.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{render, succ};\nuse yazi_parser::VoidOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Filter;\n\nimpl Actor for Filter {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"filter\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tlet help = &mut cx.help;\n\n\t\thelp.in_filter = Some(Default::default());\n\t\thelp.filter_apply();\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/help/mod.rs",
    "content": "yazi_macro::mod_flat!(arrow escape filter toggle);\n"
  },
  {
    "path": "yazi-actor/src/help/toggle.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{render, succ};\nuse yazi_parser::help::ToggleOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Toggle;\n\nimpl Actor for Toggle {\n\ttype Options = ToggleOpt;\n\n\tconst NAME: &str = \"toggle\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet help = &mut cx.help;\n\n\t\thelp.visible = !help.visible;\n\t\thelp.layer = opt.layer;\n\n\t\thelp.keyword = String::new();\n\t\thelp.in_filter = None;\n\t\thelp.filter_apply();\n\n\t\thelp.offset = 0;\n\t\thelp.cursor = 0;\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/input/close.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::input::CloseOpt;\nuse yazi_shared::data::Data;\nuse yazi_widgets::input::InputEvent;\n\nuse crate::{Actor, Ctx};\n\npub struct Close;\n\nimpl Actor for Close {\n\ttype Options = CloseOpt;\n\n\tconst NAME: &str = \"close\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet input = &mut cx.input;\n\t\tinput.visible = false;\n\t\tinput.ticket.next();\n\n\t\tif let Some(tx) = input.tx.take() {\n\t\t\tlet value = input.snap().value.clone();\n\t\t\t_ = tx.send(if opt.submit { InputEvent::Submit(value) } else { InputEvent::Cancel(value) });\n\t\t}\n\n\t\tact!(cmp:close, cx)?;\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/input/complete.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, succ};\nuse yazi_shared::data::Data;\nuse yazi_widgets::input::parser::CompleteOpt;\n\nuse crate::{Actor, Ctx};\n\npub struct Complete;\n\nimpl Actor for Complete {\n\ttype Options = CompleteOpt;\n\n\tconst NAME: &str = \"complete\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet input = &mut cx.input;\n\t\tif !input.visible || input.ticket.current() != opt.ticket {\n\t\t\tsucc!();\n\t\t}\n\n\t\tact!(complete, input, opt)\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/input/escape.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::VoidOpt;\nuse yazi_shared::data::Data;\nuse yazi_widgets::input::InputOp;\n\nuse crate::{Actor, Ctx};\n\npub struct Escape;\n\nimpl Actor for Escape {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"escape\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tuse yazi_widgets::input::InputMode as M;\n\t\tlet input = &mut cx.input;\n\n\t\tlet mode = input.snap().mode;\n\t\tmatch mode {\n\t\t\tM::Normal if input.snap_mut().op == InputOp::None => act!(input:close, cx),\n\t\t\tM::Insert => act!(cmp:close, cx),\n\t\t\tM::Normal | M::Replace => Ok(().into()),\n\t\t}?;\n\n\t\tact!(escape, cx.input)?;\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/input/mod.rs",
    "content": "yazi_macro::mod_flat!(close complete escape show);\n"
  },
  {
    "path": "yazi-actor/src/input/show.rs",
    "content": "use std::ops::DerefMut;\n\nuse anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_shared::data::Data;\nuse yazi_widgets::input::InputOpt;\n\nuse crate::{Actor, Ctx};\n\npub struct Show;\n\nimpl Actor for Show {\n\ttype Options = InputOpt;\n\n\tconst NAME: &str = \"show\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tact!(input:close, cx)?;\n\n\t\tlet input = &mut cx.input;\n\t\tinput.visible = true;\n\t\tinput.title = opt.cfg.title.clone();\n\t\tinput.position = opt.cfg.position;\n\t\t*input.deref_mut() = yazi_widgets::input::Input::new(opt)?;\n\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/lib.rs",
    "content": "extern crate self as yazi_actor;\n\nyazi_macro::mod_pub!(app cmp confirm core help input lives mgr notify pick spot tasks which);\n\nyazi_macro::mod_flat!(actor context);\n"
  },
  {
    "path": "yazi-actor/src/lives/core.rs",
    "content": "use std::ops::Deref;\n\nuse mlua::{AnyUserData, IntoLua, MetaMethod, UserData, Value};\nuse paste::paste;\n\nuse super::{Lives, PtrCell};\n\npub(super) struct Core {\n\tinner: PtrCell<yazi_core::Core>,\n\n\tc_active: Option<Value>,\n\tc_tabs:   Option<Value>,\n\tc_tasks:  Option<Value>,\n\tc_yanked: Option<Value>,\n\tc_layer:  Option<Value>,\n\tc_which:  Option<Value>,\n}\n\nimpl Deref for Core {\n\ttype Target = yazi_core::Core;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl Core {\n\tpub(super) fn make(inner: &yazi_core::Core) -> mlua::Result<AnyUserData> {\n\t\tLives::scoped_userdata(Self {\n\t\t\tinner: inner.into(),\n\n\t\t\tc_active: None,\n\t\t\tc_tabs:   None,\n\t\t\tc_tasks:  None,\n\t\t\tc_yanked: None,\n\t\t\tc_layer:  None,\n\t\t\tc_which:  None,\n\t\t})\n\t}\n}\n\nimpl UserData for Core {\n\tfn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_meta_method_mut(MetaMethod::Index, |lua, me, key: mlua::String| {\n\t\t\tmacro_rules! reuse {\n\t\t\t\t($key:ident, $value:expr) => {\n\t\t\t\t\tmatch paste! { &me.[<c_ $key>] } {\n\t\t\t\t\t\tSome(v) => v.clone(),\n\t\t\t\t\t\tNone => {\n\t\t\t\t\t\t\tlet v = $value?.into_lua(lua)?;\n\t\t\t\t\t\t\tpaste! { me.[<c_ $key>] = Some(v.clone()); };\n\t\t\t\t\t\t\tv\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\t\t\tOk(match &*key.as_bytes() {\n\t\t\t\tb\"active\" => reuse!(active, super::Tab::make(me.active())),\n\t\t\t\tb\"tabs\" => reuse!(tabs, super::Tabs::make(&me.mgr.tabs)),\n\t\t\t\tb\"tasks\" => reuse!(tasks, super::Tasks::make(&me.tasks)),\n\t\t\t\tb\"yanked\" => reuse!(yanked, super::Yanked::make(&me.mgr.yanked)),\n\t\t\t\tb\"layer\" => {\n\t\t\t\t\treuse!(layer, Ok::<_, mlua::Error>(yazi_binding::Layer::from(me.layer())))\n\t\t\t\t}\n\t\t\t\tb\"which\" => reuse!(which, super::Which::make(&me.which)),\n\t\t\t\t_ => Value::Nil,\n\t\t\t})\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/lives/file.rs",
    "content": "use std::{ops::Deref, ptr};\n\nuse mlua::{AnyUserData, IntoLua, UserData, UserDataFields, UserDataMethods, Value};\nuse yazi_binding::{Range, Style, cached_field};\nuse yazi_config::THEME;\nuse yazi_shared::{path::AsPath, url::UrlLike};\n\nuse super::Lives;\nuse crate::lives::PtrCell;\n\npub(super) struct File {\n\tidx:    usize,\n\tfolder: PtrCell<yazi_core::tab::Folder>,\n\ttab:    PtrCell<yazi_core::tab::Tab>,\n\n\tv_cha:     Option<Value>,\n\tv_url:     Option<Value>,\n\tv_link_to: Option<Value>,\n\n\tv_name:  Option<Value>,\n\tv_path:  Option<Value>,\n\tv_cache: Option<Value>,\n\n\tv_bare: Option<Value>,\n}\n\nimpl Deref for File {\n\ttype Target = yazi_fs::File;\n\n\tfn deref(&self) -> &Self::Target { &self.folder.files[self.idx] }\n}\n\nimpl AsRef<yazi_fs::File> for File {\n\tfn as_ref(&self) -> &yazi_fs::File { self }\n}\n\nimpl File {\n\tpub(super) fn make(\n\t\tidx: usize,\n\t\tfolder: &yazi_core::tab::Folder,\n\t\ttab: &yazi_core::tab::Tab,\n\t) -> mlua::Result<AnyUserData> {\n\t\tuse hashbrown::hash_map::Entry;\n\n\t\tOk(match super::FILE_CACHE.borrow_mut().entry(PtrCell(&folder.files[idx])) {\n\t\t\tEntry::Occupied(oe) => oe.into_mut().clone(),\n\t\t\tEntry::Vacant(ve) => {\n\t\t\t\tlet ud = Lives::scoped_userdata(Self {\n\t\t\t\t\tidx,\n\t\t\t\t\tfolder: folder.into(),\n\t\t\t\t\ttab: tab.into(),\n\n\t\t\t\t\tv_cha: None,\n\t\t\t\t\tv_url: None,\n\t\t\t\t\tv_link_to: None,\n\n\t\t\t\t\tv_name: None,\n\t\t\t\t\tv_path: None,\n\t\t\t\t\tv_cache: None,\n\n\t\t\t\t\tv_bare: None,\n\t\t\t\t})?;\n\t\t\t\tve.insert(ud.clone());\n\t\t\t\tud\n\t\t\t}\n\t\t})\n\t}\n\n\t#[inline]\n\tfn is_hovered(&self) -> bool { self.idx == self.folder.cursor }\n}\n\nimpl UserData for File {\n\tfn add_fields<F: UserDataFields<Self>>(fields: &mut F) {\n\t\tyazi_binding::impl_file_fields!(fields);\n\t\tcached_field!(fields, bare, |_, me| Ok(yazi_binding::File::new(&**me)));\n\n\t\tfields.add_field_method_get(\"idx\", |_, me| Ok(me.idx + 1));\n\t\tfields.add_field_method_get(\"is_hovered\", |_, me| Ok(me.is_hovered()));\n\t\tfields.add_field_method_get(\"in_current\", |_, me| Ok(ptr::eq(&*me.folder, &me.tab.current)));\n\t\tfields.add_field_method_get(\"in_preview\", |_, me| {\n\t\t\tOk(me.idx == me.folder.cursor && me.tab.hovered().is_some_and(|f| f.url == me.folder.url))\n\t\t});\n\t}\n\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tyazi_binding::impl_file_methods!(methods);\n\n\t\tmethods.add_method(\"icon\", |_, me, ()| {\n\t\t\tuse yazi_binding::Icon;\n\t\t\t// TODO: use a cache\n\t\t\tOk(yazi_config::THEME.icon.matches(me, me.is_hovered()).map(Icon::from))\n\t\t});\n\t\tmethods.add_method(\"size\", |_, me, ()| {\n\t\t\tOk(if me.is_dir() { me.folder.files.sizes.get(&me.urn()).copied() } else { Some(me.len) })\n\t\t});\n\t\tmethods.add_method(\"mime\", |lua, me, ()| {\n\t\t\tlua.named_registry_value::<AnyUserData>(\"cx\")?.borrow_scoped(|core: &yazi_core::Core| {\n\t\t\t\tcore.mgr.mimetype.get(&me.url).map(|s| lua.create_string(s)).transpose()\n\t\t\t})?\n\t\t});\n\t\tmethods.add_method(\"prefix\", |lua, me, ()| {\n\t\t\tif !me.url.has_trail() {\n\t\t\t\treturn Ok(None);\n\t\t\t}\n\n\t\t\tlet mut comp = me.url.try_strip_prefix(me.url.trail()).unwrap_or(me.url.loc()).components();\n\t\t\tcomp.next_back();\n\t\t\tSome(lua.create_string(comp.as_path().encoded_bytes())).transpose()\n\t\t});\n\t\tmethods.add_method(\"style\", |lua, me, ()| {\n\t\t\tlua.named_registry_value::<AnyUserData>(\"cx\")?.borrow_scoped(|core: &yazi_core::Core| {\n\t\t\t\tlet mime = core.mgr.mimetype.get(&me.url).unwrap_or_default();\n\t\t\t\tTHEME.filetype.iter().find(|&x| x.matches(me, mime)).map(|x| Style::from(x.style))\n\t\t\t})\n\t\t});\n\t\tmethods.add_method(\"is_yanked\", |lua, me, ()| {\n\t\t\tlua.named_registry_value::<AnyUserData>(\"cx\")?.borrow_scoped(|core: &yazi_core::Core| {\n\t\t\t\tif !core.mgr.yanked.contains(&me.url) {\n\t\t\t\t\t0u8\n\t\t\t\t} else if core.mgr.yanked.cut {\n\t\t\t\t\t2u8\n\t\t\t\t} else {\n\t\t\t\t\t1u8\n\t\t\t\t}\n\t\t\t})\n\t\t});\n\t\tmethods.add_method(\"is_marked\", |_, me, ()| {\n\t\t\tuse yazi_core::tab::Mode::*;\n\t\t\tif !me.tab.mode.is_visual() || me.folder.url != me.tab.current.url {\n\t\t\t\treturn Ok(0u8);\n\t\t\t}\n\n\t\t\tOk(match &me.tab.mode {\n\t\t\t\tSelect(_, indices) if indices.contains(&me.idx) => 1u8,\n\t\t\t\tUnset(_, indices) if indices.contains(&me.idx) => 2u8,\n\t\t\t\t_ => 0u8,\n\t\t\t})\n\t\t});\n\t\tmethods.add_method(\"is_selected\", |_, me, ()| Ok(me.tab.selected.contains(&me.url)));\n\t\tmethods.add_method(\"found\", |lua, me, ()| {\n\t\t\tlua.named_registry_value::<AnyUserData>(\"cx\")?.borrow_scoped(|core: &yazi_core::Core| {\n\t\t\t\tlet Some(finder) = &core.active().finder else {\n\t\t\t\t\treturn Ok(None);\n\t\t\t\t};\n\n\t\t\t\tlet Some(idx) = finder.matched_idx(&me.folder, me.urn()) else {\n\t\t\t\t\treturn Ok(None);\n\t\t\t\t};\n\n\t\t\t\tSome(lua.create_sequence_from([idx.into_lua(lua)?, finder.matched.len().into_lua(lua)?]))\n\t\t\t\t\t.transpose()\n\t\t\t})\n\t\t});\n\t\tmethods.add_method(\"highlights\", |lua, me, ()| {\n\t\t\tlua.named_registry_value::<AnyUserData>(\"cx\")?.borrow_scoped(|core: &yazi_core::Core| {\n\t\t\t\tlet Some(finder) = &core.active().finder else {\n\t\t\t\t\treturn None;\n\t\t\t\t};\n\t\t\t\tif me.folder.url != me.tab.current.url {\n\t\t\t\t\treturn None;\n\t\t\t\t}\n\n\t\t\t\tlet h = finder.filter.highlighted(me.url.name()?)?;\n\t\t\t\tSome(h.into_iter().map(Range::from).collect::<Vec<_>>())\n\t\t\t})\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/lives/files.rs",
    "content": "use std::ops::{Deref, Range};\n\nuse mlua::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods, Value};\nuse yazi_binding::cached_field;\n\nuse super::{File, Filter, Lives, PtrCell};\n\npub(super) struct Files {\n\twindow: Range<usize>,\n\tfolder: PtrCell<yazi_core::tab::Folder>,\n\ttab:    PtrCell<yazi_core::tab::Tab>,\n\n\tv_filter: Option<Value>,\n}\n\nimpl Deref for Files {\n\ttype Target = yazi_fs::Files;\n\n\tfn deref(&self) -> &Self::Target { &self.folder.files }\n}\n\nimpl Files {\n\tpub(super) fn make(\n\t\twindow: Range<usize>,\n\t\tfolder: &yazi_core::tab::Folder,\n\t\ttab: &yazi_core::tab::Tab,\n\t) -> mlua::Result<AnyUserData> {\n\t\tLives::scoped_userdata(Self { window, folder: folder.into(), tab: tab.into(), v_filter: None })\n\t}\n}\n\nimpl UserData for Files {\n\tfn add_fields<F: UserDataFields<Self>>(fields: &mut F) {\n\t\tcached_field!(fields, filter, |_, me| me.filter().map(Filter::make).transpose());\n\t}\n\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.window.end - me.window.start));\n\n\t\tmethods.add_meta_method(MetaMethod::Index, |_, me, mut idx: usize| {\n\t\t\tidx += me.window.start;\n\t\t\tif idx > me.window.end || idx == 0 {\n\t\t\t\tOk(None)\n\t\t\t} else {\n\t\t\t\tSome(File::make(idx - 1, &me.folder, &me.tab)).transpose()\n\t\t\t}\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/lives/filter.rs",
    "content": "use std::ops::Deref;\n\nuse mlua::{AnyUserData, MetaMethod, UserData, UserDataMethods};\n\nuse super::{Lives, PtrCell};\n\npub(super) struct Filter {\n\tinner: PtrCell<yazi_fs::Filter>,\n}\n\nimpl Deref for Filter {\n\ttype Target = yazi_fs::Filter;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl Filter {\n\tpub(super) fn make(inner: &yazi_fs::Filter) -> mlua::Result<AnyUserData> {\n\t\tLives::scoped_userdata(Self { inner: inner.into() })\n\t}\n}\n\nimpl UserData for Filter {\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_meta_method(MetaMethod::ToString, |_, me, ()| Ok(me.to_string()));\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/lives/finder.rs",
    "content": "use std::ops::Deref;\n\nuse mlua::{AnyUserData, MetaMethod, UserData, UserDataMethods};\n\nuse super::{Lives, PtrCell};\n\npub(super) struct Finder {\n\tinner: PtrCell<yazi_core::tab::Finder>,\n}\n\nimpl Deref for Finder {\n\ttype Target = yazi_core::tab::Finder;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl Finder {\n\tpub(super) fn make(inner: &yazi_core::tab::Finder) -> mlua::Result<AnyUserData> {\n\t\tLives::scoped_userdata(Self { inner: inner.into() })\n\t}\n}\n\nimpl UserData for Finder {\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_meta_method(MetaMethod::ToString, |_, me, ()| Ok(me.filter.to_string()));\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/lives/folder.rs",
    "content": "use std::ops::{Deref, Range};\n\nuse mlua::{AnyUserData, UserData, UserDataFields, Value};\nuse yazi_binding::{FolderStage, Url, cached_field};\nuse yazi_config::LAYOUT;\n\nuse super::{File, Files, Lives, PtrCell};\n\npub(super) struct Folder {\n\twindow: Range<usize>,\n\tinner:  PtrCell<yazi_core::tab::Folder>,\n\ttab:    PtrCell<yazi_core::tab::Tab>,\n\n\tv_cwd:     Option<Value>,\n\tv_files:   Option<Value>,\n\tv_stage:   Option<Value>,\n\tv_window:  Option<Value>,\n\tv_hovered: Option<Value>,\n}\n\nimpl Deref for Folder {\n\ttype Target = yazi_core::tab::Folder;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl Folder {\n\tpub(super) fn make(\n\t\twindow: Option<Range<usize>>,\n\t\tinner: &yazi_core::tab::Folder,\n\t\ttab: &yazi_core::tab::Tab,\n\t) -> mlua::Result<AnyUserData> {\n\t\tlet window = match window {\n\t\t\tSome(w) => w,\n\t\t\tNone => {\n\t\t\t\tlet limit = LAYOUT.get().preview.height as usize;\n\t\t\t\tinner.offset..inner.files.len().min(inner.offset + limit)\n\t\t\t}\n\t\t};\n\n\t\tLives::scoped_userdata(Self {\n\t\t\twindow,\n\t\t\tinner: inner.into(),\n\t\t\ttab: tab.into(),\n\n\t\t\tv_cwd: None,\n\t\t\tv_files: None,\n\t\t\tv_stage: None,\n\t\t\tv_window: None,\n\t\t\tv_hovered: None,\n\t\t})\n\t}\n}\n\nimpl UserData for Folder {\n\tfn add_fields<F: UserDataFields<Self>>(fields: &mut F) {\n\t\tcached_field!(fields, cwd, |_, me| Ok(Url::new(&me.url)));\n\t\tcached_field!(fields, files, |_, me| Files::make(0..me.files.len(), me, &me.tab));\n\t\tcached_field!(fields, stage, |_, me| Ok(FolderStage::new(me.stage.clone())));\n\t\tcached_field!(fields, window, |_, me| Files::make(me.window.clone(), me, &me.tab));\n\n\t\tfields.add_field_method_get(\"offset\", |_, me| Ok(me.offset));\n\t\tfields.add_field_method_get(\"cursor\", |_, me| Ok(me.cursor));\n\t\tcached_field!(fields, hovered, |_, me| {\n\t\t\tme.hovered().map(|_| File::make(me.cursor, me, &me.tab)).transpose()\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/lives/lives.rs",
    "content": "use std::cell::RefCell;\n\nuse hashbrown::HashMap;\nuse mlua::{AnyUserData, UserData};\nuse scopeguard::defer;\nuse tracing::error;\nuse yazi_plugin::LUA;\nuse yazi_shared::RoCell;\n\nuse super::{Core, PtrCell};\n\nstatic TO_DESTROY: RoCell<RefCell<Vec<AnyUserData>>> = RoCell::new_const(RefCell::new(Vec::new()));\npub(super) static FILE_CACHE: RoCell<RefCell<HashMap<PtrCell<yazi_fs::File>, AnyUserData>>> =\n\tRoCell::new();\n\npub struct Lives;\n\nimpl Lives {\n\tpub fn scope<T, F>(core: &yazi_core::Core, f: F) -> mlua::Result<T>\n\twhere\n\t\tF: FnOnce() -> mlua::Result<T>,\n\t{\n\t\tFILE_CACHE.init(Default::default());\n\t\tdefer! { FILE_CACHE.drop(); }\n\n\t\tlet result = LUA.scope(|scope| {\n\t\t\tscope.add_destructor(|| {\n\t\t\t\tfor ud in TO_DESTROY.borrow_mut().drain(..) {\n\t\t\t\t\tud.destroy().expect(\"failed to destruct scoped userdata\");\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tLUA.set_named_registry_value(\"cx\", scope.create_any_userdata_ref(core)?)?;\n\t\t\tLUA.globals().raw_set(\"cx\", Core::make(core)?)?;\n\t\t\tf()\n\t\t});\n\n\t\tif let Err(ref e) = result {\n\t\t\terror!(\"{e}\");\n\t\t}\n\t\tresult\n\t}\n\n\tpub(crate) fn scoped_userdata<T>(data: T) -> mlua::Result<AnyUserData>\n\twhere\n\t\tT: UserData + 'static,\n\t{\n\t\tlet ud = LUA.create_userdata(data)?;\n\t\tTO_DESTROY.borrow_mut().push(ud.clone());\n\t\tOk(ud)\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/lives/mod.rs",
    "content": "yazi_macro::mod_flat!(core file files filter finder folder lives mode preference preview ptr selected tab tabs task tasks which yanked);\n"
  },
  {
    "path": "yazi-actor/src/lives/mode.rs",
    "content": "use std::ops::Deref;\n\nuse mlua::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods};\n\nuse super::{Lives, PtrCell};\n\npub(super) struct Mode {\n\tinner: PtrCell<yazi_core::tab::Mode>,\n}\n\nimpl Deref for Mode {\n\ttype Target = yazi_core::tab::Mode;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl Mode {\n\tpub(super) fn make(inner: &yazi_core::tab::Mode) -> mlua::Result<AnyUserData> {\n\t\tLives::scoped_userdata(Self { inner: inner.into() })\n\t}\n}\n\nimpl UserData for Mode {\n\tfn add_fields<F: UserDataFields<Self>>(fields: &mut F) {\n\t\tfields.add_field_method_get(\"is_select\", |_, me| Ok(me.is_select()));\n\t\tfields.add_field_method_get(\"is_unset\", |_, me| Ok(me.is_unset()));\n\t\tfields.add_field_method_get(\"is_visual\", |_, me| Ok(me.is_visual()));\n\t}\n\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_meta_method(MetaMethod::ToString, |_, me, ()| Ok(me.to_string()));\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/lives/preference.rs",
    "content": "use std::ops::Deref;\n\nuse mlua::{AnyUserData, UserData, UserDataFields, Value};\nuse yazi_binding::cached_field;\n\nuse super::{Lives, PtrCell};\n\npub(super) struct Preference {\n\tinner: PtrCell<yazi_core::tab::Preference>,\n\n\tv_name:     Option<Value>,\n\tv_linemode: Option<Value>,\n\n\tv_sort_by: Option<Value>,\n}\n\nimpl Deref for Preference {\n\ttype Target = yazi_core::tab::Preference;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl Preference {\n\tpub(super) fn make(inner: &yazi_core::tab::Preference) -> mlua::Result<AnyUserData> {\n\t\tLives::scoped_userdata(Self {\n\t\t\tinner: inner.into(),\n\n\t\t\tv_name:     None,\n\t\t\tv_linemode: None,\n\n\t\t\tv_sort_by: None,\n\t\t})\n\t}\n}\n\nimpl UserData for Preference {\n\tfn add_fields<F: UserDataFields<Self>>(fields: &mut F) {\n\t\t// Display\n\t\tcached_field!(fields, name, |lua, me| lua.create_string(&me.name));\n\t\tcached_field!(fields, linemode, |lua, me| lua.create_string(&me.linemode));\n\t\tfields.add_field_method_get(\"show_hidden\", |_, me| Ok(me.show_hidden));\n\n\t\t// Sorting\n\t\tcached_field!(fields, sort_by, |_, me| Ok(me.sort_by.to_string()));\n\t\tfields.add_field_method_get(\"sort_sensitive\", |_, me| Ok(me.sort_sensitive));\n\t\tfields.add_field_method_get(\"sort_reverse\", |_, me| Ok(me.sort_reverse));\n\t\tfields.add_field_method_get(\"sort_dir_first\", |_, me| Ok(me.sort_dir_first));\n\t\tfields.add_field_method_get(\"sort_translit\", |_, me| Ok(me.sort_translit));\n\t\tfields.add_field_method_get(\"sort_fallback\", |_, me| Ok(me.sort_fallback.to_string()));\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/lives/preview.rs",
    "content": "use std::ops::Deref;\n\nuse mlua::{AnyUserData, UserData, UserDataFields, Value};\nuse yazi_binding::cached_field;\nuse yazi_config::LAYOUT;\n\nuse super::{Folder, Lives, PtrCell};\n\npub(super) struct Preview {\n\ttab: PtrCell<yazi_core::tab::Tab>,\n\n\tv_folder: Option<Value>,\n}\n\nimpl Deref for Preview {\n\ttype Target = yazi_core::tab::Preview;\n\n\tfn deref(&self) -> &Self::Target { &self.tab.preview }\n}\n\nimpl Preview {\n\tpub(super) fn make(tab: &yazi_core::tab::Tab) -> mlua::Result<AnyUserData> {\n\t\tLives::scoped_userdata(Self { tab: tab.into(), v_folder: None })\n\t}\n}\n\nimpl UserData for Preview {\n\tfn add_fields<F: UserDataFields<Self>>(fields: &mut F) {\n\t\tfields.add_field_method_get(\"skip\", |_, me| Ok(me.skip));\n\t\tcached_field!(fields, folder, |_, me| {\n\t\t\tme.tab\n\t\t\t\t.hovered_folder()\n\t\t\t\t.map(|f| {\n\t\t\t\t\tlet limit = LAYOUT.get().preview.height as usize;\n\t\t\t\t\tFolder::make(Some(me.skip..f.files.len().min(me.skip + limit)), f, &me.tab)\n\t\t\t\t})\n\t\t\t\t.transpose()\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/lives/ptr.rs",
    "content": "use std::{hash::{Hash, Hasher}, ops::Deref};\n\npub(super) struct PtrCell<T>(pub(super) *const T);\n\nimpl<T> Deref for PtrCell<T> {\n\ttype Target = T;\n\n\tfn deref(&self) -> &Self::Target { unsafe { &*self.0 } }\n}\n\nimpl<T> From<&T> for PtrCell<T> {\n\tfn from(value: &T) -> Self { Self(value) }\n}\n\nimpl<T> Clone for PtrCell<T> {\n\tfn clone(&self) -> Self { *self }\n}\n\nimpl<T> Copy for PtrCell<T> {}\n\nimpl<T> PartialEq for PtrCell<T> {\n\tfn eq(&self, other: &Self) -> bool { self.0.addr() == other.0.addr() }\n}\n\nimpl<T> Eq for PtrCell<T> {}\n\nimpl<T> Hash for PtrCell<T> {\n\tfn hash<H: Hasher>(&self, state: &mut H) { state.write_usize(self.0.addr()); }\n}\n\nimpl<T> PtrCell<T> {\n\t#[inline]\n\tpub(super) fn as_static(&self) -> &'static T { unsafe { &*self.0 } }\n}\n"
  },
  {
    "path": "yazi-actor/src/lives/selected.rs",
    "content": "use mlua::AnyUserData;\n\nuse super::Lives;\nuse crate::lives::PtrCell;\n\n#[derive(Clone, Copy)]\npub(super) struct Selected;\n\nimpl Selected {\n\tpub(super) fn make(inner: &yazi_core::tab::Selected) -> mlua::Result<AnyUserData> {\n\t\tlet inner = PtrCell::from(inner);\n\n\t\tLives::scoped_userdata(yazi_binding::Iter::new(\n\t\t\tinner.as_static().values().map(yazi_binding::Url::new),\n\t\t\tSome(inner.len()),\n\t\t))\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/lives/tab.rs",
    "content": "use std::{borrow::Cow, ops::Deref};\n\nuse mlua::{AnyUserData, UserData, UserDataFields, UserDataMethods, Value};\nuse yazi_binding::{Id, UrlRef, cached_field};\n\nuse super::{Finder, Folder, Lives, Mode, Preference, Preview, PtrCell, Selected};\n\npub(super) struct Tab {\n\tinner: PtrCell<yazi_core::tab::Tab>,\n\n\tv_name:     Option<Value>,\n\tv_mode:     Option<Value>,\n\tv_pref:     Option<Value>,\n\tv_current:  Option<Value>,\n\tv_parent:   Option<Value>,\n\tv_selected: Option<Value>,\n\tv_preview:  Option<Value>,\n\tv_finder:   Option<Value>,\n}\n\nimpl Deref for Tab {\n\ttype Target = yazi_core::tab::Tab;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl Tab {\n\tpub(super) fn make(inner: &yazi_core::tab::Tab) -> mlua::Result<AnyUserData> {\n\t\tLives::scoped_userdata(Self {\n\t\t\tinner: inner.into(),\n\n\t\t\tv_name:     None,\n\t\t\tv_mode:     None,\n\t\t\tv_pref:     None,\n\t\t\tv_current:  None,\n\t\t\tv_parent:   None,\n\t\t\tv_selected: None,\n\t\t\tv_preview:  None,\n\t\t\tv_finder:   None,\n\t\t})\n\t}\n}\n\nimpl UserData for Tab {\n\tfn add_fields<F: UserDataFields<Self>>(fields: &mut F) {\n\t\tfields.add_field_method_get(\"id\", |_, me| Ok(Id(me.id)));\n\t\tcached_field!(fields, name, |lua, me| {\n\t\t\tmatch me.name() {\n\t\t\t\tCow::Borrowed(s) => lua.create_string(s),\n\t\t\t\tCow::Owned(s) => lua.create_external_string(s),\n\t\t\t}\n\t\t});\n\n\t\tcached_field!(fields, mode, |_, me| Mode::make(&me.mode));\n\t\tcached_field!(fields, pref, |_, me| Preference::make(&me.pref));\n\t\tcached_field!(fields, current, |_, me| Folder::make(None, &me.current, me));\n\t\tcached_field!(fields, parent, |_, me| {\n\t\t\tme.parent.as_ref().map(|f| Folder::make(None, f, me)).transpose()\n\t\t});\n\n\t\tcached_field!(fields, selected, |_, me| Selected::make(&me.selected));\n\n\t\tcached_field!(fields, preview, |_, me| Preview::make(me));\n\t\tcached_field!(fields, finder, |_, me| me.finder.as_ref().map(Finder::make).transpose());\n\t}\n\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_method(\"history\", |_, me, url: UrlRef| {\n\t\t\tme.history.get(url.as_ref()).map(|f| Folder::make(None, f, me)).transpose()\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/lives/tabs.rs",
    "content": "use std::ops::Deref;\n\nuse mlua::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods};\n\nuse super::{Lives, PtrCell, Tab};\n\npub(super) struct Tabs {\n\tinner: PtrCell<yazi_core::mgr::Tabs>,\n}\n\nimpl Deref for Tabs {\n\ttype Target = yazi_core::mgr::Tabs;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl Tabs {\n\tpub(super) fn make(inner: &yazi_core::mgr::Tabs) -> mlua::Result<AnyUserData> {\n\t\tLives::scoped_userdata(Self { inner: inner.into() })\n\t}\n}\n\nimpl UserData for Tabs {\n\tfn add_fields<F: UserDataFields<Self>>(fields: &mut F) {\n\t\tfields.add_field_method_get(\"idx\", |_, me| Ok(me.cursor + 1));\n\t}\n\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.len()));\n\n\t\tmethods.add_meta_method(MetaMethod::Index, |_, me, idx: usize| {\n\t\t\tif idx > me.len() || idx == 0 { Ok(None) } else { Some(Tab::make(&me[idx - 1])).transpose() }\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/lives/task.rs",
    "content": "use std::ops::Deref;\n\nuse mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value};\nuse yazi_binding::{SER_OPT, cached_field};\n\nuse super::{Lives, PtrCell};\n\npub(super) struct TaskSnap {\n\tinner: PtrCell<yazi_scheduler::TaskSnap>,\n\n\tv_name: Option<Value>,\n\tv_prog: Option<Value>,\n}\n\nimpl Deref for TaskSnap {\n\ttype Target = yazi_scheduler::TaskSnap;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl TaskSnap {\n\tpub(super) fn make(inner: &yazi_scheduler::TaskSnap) -> mlua::Result<AnyUserData> {\n\t\tLives::scoped_userdata(Self { inner: inner.into(), v_name: None, v_prog: None })\n\t}\n}\n\nimpl UserData for TaskSnap {\n\tfn add_fields<F: UserDataFields<Self>>(fields: &mut F) {\n\t\tcached_field!(fields, name, |lua, me| lua.create_string(&me.name));\n\t\tcached_field!(fields, prog, |lua, me| lua.to_value_with(&me.prog, SER_OPT));\n\n\t\tfields.add_field_method_get(\"cooked\", |_, me| Ok(me.prog.cooked()));\n\t\tfields.add_field_method_get(\"running\", |_, me| Ok(me.prog.running()));\n\t\tfields.add_field_method_get(\"success\", |_, me| Ok(me.prog.success()));\n\t\tfields.add_field_method_get(\"failed\", |_, me| Ok(me.prog.failed()));\n\t\tfields.add_field_method_get(\"percent\", |_, me| Ok(me.prog.percent()));\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/lives/tasks.rs",
    "content": "use std::ops::Deref;\n\nuse mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value};\nuse yazi_binding::{SER_OPT, cached_field};\n\nuse super::{Lives, PtrCell};\nuse crate::lives::TaskSnap;\n\npub(super) struct Tasks {\n\tinner: PtrCell<yazi_core::tasks::Tasks>,\n\n\tv_snaps:   Option<Value>,\n\tv_summary: Option<Value>,\n}\n\nimpl Deref for Tasks {\n\ttype Target = yazi_core::tasks::Tasks;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl Tasks {\n\tpub(super) fn make(inner: &yazi_core::tasks::Tasks) -> mlua::Result<AnyUserData> {\n\t\tLives::scoped_userdata(Self {\n\t\t\tinner: inner.into(),\n\n\t\t\tv_snaps:   None,\n\t\t\tv_summary: None,\n\t\t})\n\t}\n}\n\nimpl UserData for Tasks {\n\tfn add_fields<F: UserDataFields<Self>>(fields: &mut F) {\n\t\tfields.add_field_method_get(\"cursor\", |_, me| Ok(me.cursor));\n\n\t\tcached_field!(fields, snaps, |lua, me| {\n\t\t\tlet tbl = lua.create_table_with_capacity(me.snaps.len(), 0)?;\n\t\t\tfor snap in &me.snaps {\n\t\t\t\ttbl.raw_push(TaskSnap::make(snap)?)?;\n\t\t\t}\n\t\t\tOk(tbl)\n\t\t});\n\n\t\tcached_field!(fields, summary, |lua, me| lua.to_value_with(&me.summary, SER_OPT));\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/lives/which.rs",
    "content": "use std::ops::Deref;\n\nuse mlua::{AnyUserData, UserData, UserDataFields, Value};\nuse yazi_binding::cached_field;\n\nuse super::{Lives, PtrCell};\n\npub(super) struct Which {\n\tinner: PtrCell<yazi_core::which::Which>,\n\n\tv_tx:    Option<Value>,\n\tv_cands: Option<Value>,\n}\n\nimpl Deref for Which {\n\ttype Target = yazi_core::which::Which;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl Which {\n\tpub(super) fn make(inner: &yazi_core::which::Which) -> mlua::Result<AnyUserData> {\n\t\tLives::scoped_userdata(Self { inner: inner.into(), v_tx: None, v_cands: None })\n\t}\n}\n\nimpl UserData for Which {\n\tfn add_fields<F: UserDataFields<Self>>(fields: &mut F) {\n\t\tcached_field!(fields, tx, |_, me| Ok(me.tx.clone().map(yazi_binding::MpscUnboundedTx)));\n\t\tfields.add_field_method_get(\"times\", |_, me| Ok(me.inner.times));\n\t\tcached_field!(fields, cands, |lua, me| {\n\t\t\tlua.create_sequence_from(me.inner.cands.iter().cloned().map(yazi_binding::ChordCow))\n\t\t});\n\n\t\tfields.add_field_method_get(\"active\", |_, me| Ok(me.inner.active));\n\t\tfields.add_field_method_get(\"silent\", |_, me| Ok(me.inner.silent));\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/lives/yanked.rs",
    "content": "use std::ops::Deref;\n\nuse mlua::{AnyUserData, MetaMethod, MultiValue, ObjectLike, UserData, UserDataFields, UserDataMethods};\nuse yazi_binding::{Iter, get_metatable};\n\nuse super::{Lives, PtrCell};\n\npub(super) struct Yanked {\n\tinner: PtrCell<yazi_core::mgr::Yanked>,\n\titer:  AnyUserData,\n}\n\nimpl Deref for Yanked {\n\ttype Target = yazi_core::mgr::Yanked;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl Yanked {\n\tpub(super) fn make(inner: &yazi_core::mgr::Yanked) -> mlua::Result<AnyUserData> {\n\t\tlet inner = PtrCell::from(inner);\n\n\t\tLives::scoped_userdata(Self {\n\t\t\tinner,\n\t\t\titer: Lives::scoped_userdata(Iter::new(\n\t\t\t\tinner.as_static().iter().map(yazi_binding::Url::new),\n\t\t\t\tSome(inner.len()),\n\t\t\t))?,\n\t\t})\n\t}\n}\n\nimpl UserData for Yanked {\n\tfn add_fields<F: UserDataFields<Self>>(fields: &mut F) {\n\t\tfields.add_field_method_get(\"is_cut\", |_, me| Ok(me.cut));\n\t}\n\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.len()));\n\n\t\tmethods.add_meta_method(MetaMethod::Pairs, |lua, me, ()| {\n\t\t\tget_metatable(lua, &me.iter)?\n\t\t\t\t.call_function::<MultiValue>(MetaMethod::Pairs.name(), me.iter.clone())\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/arrow.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::ArrowOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Arrow;\n\nimpl Actor for Arrow {\n\ttype Options = ArrowOpt;\n\n\tconst NAME: &str = \"arrow\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet tab = cx.tab_mut();\n\t\tif !tab.current.arrow(opt.step) {\n\t\t\tsucc!();\n\t\t}\n\n\t\t// Visual selection\n\t\tif let Some((start, items)) = tab.mode.visual_mut() {\n\t\t\tlet end = tab.current.cursor;\n\t\t\t*items = (start.min(end)..=end.max(start)).collect();\n\t\t}\n\n\t\tact!(mgr:hover, cx)?;\n\t\tact!(mgr:peek, cx)?;\n\t\tact!(mgr:watch, cx)?;\n\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/back.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, succ};\nuse yazi_parser::{VoidOpt, mgr::CdSource};\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Back;\n\nimpl Actor for Back {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"back\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tif let Some(u) = cx.tab_mut().backstack.shift_backward().cloned() {\n\t\t\tact!(mgr:cd, cx, (u, CdSource::Back))?;\n\t\t}\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/bulk_rename.rs",
    "content": "use std::{hash::Hash, io::{Read, Write}, ops::Deref, path::Path};\n\nuse anyhow::{Result, anyhow};\nuse crossterm::{execute, style::Print};\nuse hashbrown::HashMap;\nuse scopeguard::defer;\nuse tokio::io::AsyncWriteExt;\nuse yazi_binding::Permit;\nuse yazi_config::{YAZI, opener::OpenerRule};\nuse yazi_dds::Pubsub;\nuse yazi_fs::{File, FilesOp, Splatter, max_common_root, path::skip_url, provider::{FileBuilder, Provider, local::{Gate, Local}}};\nuse yazi_macro::{err, succ};\nuse yazi_parser::VoidOpt;\nuse yazi_proxy::{AppProxy, NotifyProxy, TasksProxy};\nuse yazi_shared::{data::Data, path::PathDyn, strand::{AsStrand, AsStrandJoin, Strand, StrandBuf, StrandLike}, terminal_clear, url::{AsUrl, UrlBuf, UrlCow, UrlLike}};\nuse yazi_term::YIELD_TO_SUBPROCESS;\nuse yazi_tty::TTY;\nuse yazi_vfs::{VfsFile, maybe_exists, provider};\nuse yazi_watcher::WATCHER;\n\nuse crate::{Actor, Ctx};\n\npub struct BulkRename;\n\nimpl Actor for BulkRename {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"bulk_rename\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tlet Some(opener) = Self::opener() else {\n\t\t\tsucc!(NotifyProxy::push_warn(\"Bulk rename\", \"No text opener found\"));\n\t\t};\n\n\t\tlet selected: Vec<_> = cx.tab().selected_or_hovered().cloned().collect();\n\t\tif selected.is_empty() {\n\t\t\tsucc!(NotifyProxy::push_warn(\"Bulk rename\", \"No files selected\"));\n\t\t}\n\n\t\tlet root = max_common_root(&selected);\n\t\tlet old: Vec<_> =\n\t\t\tselected.iter().enumerate().map(|(i, u)| Tuple::new(i, skip_url(u, root))).collect();\n\n\t\tlet cwd = cx.cwd().clone();\n\t\ttokio::spawn(async move {\n\t\t\tlet tmp = YAZI.preview.tmpfile(\"bulk\");\n\t\t\tGate::default()\n\t\t\t\t.write(true)\n\t\t\t\t.create_new(true)\n\t\t\t\t.open(&tmp)\n\t\t\t\t.await?\n\t\t\t\t.write_all(old.join(Strand::Utf8(\"\\n\")).encoded_bytes())\n\t\t\t\t.await?;\n\n\t\t\tdefer! {\n\t\t\t\tlet tmp = tmp.clone();\n\t\t\t\ttokio::spawn(async move {\n\t\t\t\t\tLocal::regular(&tmp).remove_file().await\n\t\t\t\t});\n\t\t\t}\n\t\t\tTasksProxy::process_exec(\n\t\t\t\tcwd.into(),\n\t\t\t\tSplatter::new(&[UrlCow::default(), tmp.as_url().into()]).splat(&opener.run),\n\t\t\t\tvec![UrlCow::default(), UrlBuf::from(&tmp).into()],\n\t\t\t\topener.block,\n\t\t\t\topener.orphan,\n\t\t\t)\n\t\t\t.await;\n\n\t\t\tlet _permit = Permit::new(YIELD_TO_SUBPROCESS.acquire().await.unwrap(), AppProxy::resume());\n\t\t\tAppProxy::stop().await;\n\n\t\t\tlet new: Vec<_> = Local::regular(&tmp)\n\t\t\t\t.read_to_string()\n\t\t\t\t.await?\n\t\t\t\t.lines()\n\t\t\t\t.take(old.len())\n\t\t\t\t.enumerate()\n\t\t\t\t.map(|(i, s)| Tuple::new(i, s))\n\t\t\t\t.collect();\n\n\t\t\tSelf::r#do(root, old, new, selected).await\n\t\t});\n\t\tsucc!();\n\t}\n}\n\nimpl BulkRename {\n\tasync fn r#do(\n\t\troot: usize,\n\t\told: Vec<Tuple>,\n\t\tnew: Vec<Tuple>,\n\t\tselected: Vec<UrlBuf>,\n\t) -> Result<()> {\n\t\tterminal_clear(TTY.writer())?;\n\t\tif old.len() != new.len() {\n\t\t\t#[rustfmt::skip]\n\t\t\tlet s = format!(\"Number of new and old file names mismatch (New: {}, Old: {}).\\nPress <Enter> to exit...\", new.len(), old.len());\n\t\t\texecute!(TTY.writer(), Print(s))?;\n\n\t\t\tTTY.reader().read_exact(&mut [0])?;\n\t\t\treturn Ok(());\n\t\t}\n\n\t\tlet (old, new) = old.into_iter().zip(new).filter(|(o, n)| o != n).unzip();\n\t\tlet todo = Self::prioritized_paths(old, new);\n\t\tif todo.is_empty() {\n\t\t\treturn Ok(());\n\t\t}\n\n\t\t{\n\t\t\tlet mut w = TTY.lockout();\n\t\t\tfor (old, new) in &todo {\n\t\t\t\twriteln!(w, \"{} -> {}\", old.display(), new.display())?;\n\t\t\t}\n\t\t\twrite!(w, \"Continue to rename? (y/N): \")?;\n\t\t\tw.flush()?;\n\t\t}\n\n\t\tlet mut buf = [0; 10];\n\t\t_ = TTY.reader().read(&mut buf)?;\n\t\tif buf[0] != b'y' && buf[0] != b'Y' {\n\t\t\treturn Ok(());\n\t\t}\n\n\t\tlet permit = WATCHER.acquire().await.unwrap();\n\t\tlet (mut failed, mut succeeded) = (Vec::new(), HashMap::with_capacity(todo.len()));\n\t\tfor (o, n) in todo {\n\t\t\tlet (Ok(old), Ok(new)) =\n\t\t\t\t(Self::replace_url(&selected[o.0], root, &o), Self::replace_url(&selected[n.0], root, &n))\n\t\t\telse {\n\t\t\t\tfailed.push((o, n, anyhow!(\"Invalid new or old file name\")));\n\t\t\t\tcontinue;\n\t\t\t};\n\n\t\t\tif maybe_exists(&new).await && !provider::must_identical(&old, &new).await {\n\t\t\t\tfailed.push((o, n, anyhow!(\"Destination already exists\")));\n\t\t\t} else if let Err(e) = provider::rename(&old, &new).await {\n\t\t\t\tfailed.push((o, n, e.into()));\n\t\t\t} else if let Ok(f) = File::new(new).await {\n\t\t\t\tsucceeded.insert(old, f);\n\t\t\t} else {\n\t\t\t\tfailed.push((o, n, anyhow!(\"Failed to retrieve file info\")));\n\t\t\t}\n\t\t}\n\n\t\tif !succeeded.is_empty() {\n\t\t\tlet it = succeeded.iter().map(|(o, n)| (o.as_url(), n.url.as_url()));\n\t\t\terr!(Pubsub::pub_after_bulk(it));\n\t\t\tFilesOp::rename(succeeded);\n\t\t}\n\t\tdrop(permit);\n\n\t\tif !failed.is_empty() {\n\t\t\tSelf::output_failed(failed).await?;\n\t\t}\n\t\tOk(())\n\t}\n\n\tfn opener() -> Option<&'static OpenerRule> {\n\t\tYAZI.opener.block(YAZI.open.all(Path::new(\"bulk-rename.txt\"), \"text/plain\"))\n\t}\n\n\tfn replace_url(url: &UrlBuf, take: usize, rep: &StrandBuf) -> Result<UrlBuf> {\n\t\tOk(url.try_replace(take, PathDyn::with(url.kind(), rep)?)?.into_owned())\n\t}\n\n\tasync fn output_failed(failed: Vec<(Tuple, Tuple, anyhow::Error)>) -> Result<()> {\n\t\tlet mut stdout = TTY.lockout();\n\t\tterminal_clear(&mut *stdout)?;\n\n\t\twriteln!(stdout, \"Failed to rename:\")?;\n\t\tfor (old, new, err) in failed {\n\t\t\twriteln!(stdout, \"{} -> {}: {err}\", old.display(), new.display())?;\n\t\t}\n\t\twriteln!(stdout, \"\\nPress ENTER to exit\")?;\n\n\t\tstdout.flush()?;\n\t\tTTY.reader().read_exact(&mut [0])?;\n\t\tOk(())\n\t}\n\n\tfn prioritized_paths(old: Vec<Tuple>, new: Vec<Tuple>) -> Vec<(Tuple, Tuple)> {\n\t\tlet orders: HashMap<_, _> = old.iter().enumerate().map(|(i, t)| (t, i)).collect();\n\t\tlet mut incomes: HashMap<_, _> = old.iter().map(|t| (t, false)).collect();\n\t\tlet mut todos: HashMap<_, _> = old\n\t\t\t.iter()\n\t\t\t.zip(new)\n\t\t\t.map(|(o, n)| {\n\t\t\t\tincomes.get_mut(&n).map(|b| *b = true);\n\t\t\t\t(o, n)\n\t\t\t})\n\t\t\t.collect();\n\n\t\tlet mut sorted = Vec::with_capacity(old.len());\n\t\twhile !todos.is_empty() {\n\t\t\t// Paths that are non-incomes and don't need to be prioritized in this round\n\t\t\tlet mut outcomes: Vec<_> = incomes.iter().filter(|&(_, b)| !b).map(|(&t, _)| t).collect();\n\t\t\toutcomes.sort_unstable_by(|a, b| orders[b].cmp(&orders[a]));\n\n\t\t\t// If there're no outcomes, it means there are cycles in the renaming\n\t\t\tif outcomes.is_empty() {\n\t\t\t\tlet mut remain: Vec<_> = todos.into_iter().map(|(o, n)| (o.clone(), n)).collect();\n\t\t\t\tremain.sort_unstable_by(|(a, _), (b, _)| orders[a].cmp(&orders[b]));\n\t\t\t\tsorted.reverse();\n\t\t\t\tsorted.extend(remain);\n\t\t\t\treturn sorted;\n\t\t\t}\n\n\t\t\tfor old in outcomes {\n\t\t\t\tlet Some(new) = todos.remove(old) else { unreachable!() };\n\t\t\t\tincomes.remove(&old);\n\t\t\t\tincomes.get_mut(&new).map(|b| *b = false);\n\t\t\t\tsorted.push((old.clone(), new));\n\t\t\t}\n\t\t}\n\t\tsorted.reverse();\n\t\tsorted\n\t}\n}\n\n// --- Tuple\n#[derive(Clone, Debug)]\nstruct Tuple(usize, StrandBuf);\n\nimpl Deref for Tuple {\n\ttype Target = StrandBuf;\n\n\tfn deref(&self) -> &Self::Target { &self.1 }\n}\n\nimpl PartialEq for Tuple {\n\tfn eq(&self, other: &Self) -> bool { self.1 == other.1 }\n}\n\nimpl Eq for Tuple {}\n\nimpl Hash for Tuple {\n\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) { self.1.hash(state); }\n}\n\nimpl AsStrand for &Tuple {\n\tfn as_strand(&self) -> Strand<'_> { self.1.as_strand() }\n}\n\nimpl Tuple {\n\tfn new(index: usize, inner: impl Into<StrandBuf>) -> Self { Self(index, inner.into()) }\n}\n\n// --- Tests\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\n\t#[test]\n\tfn test_sort() {\n\t\tfn cmp(input: &[(&str, &str)], expected: &[(&str, &str)]) {\n\t\t\tlet sorted = BulkRename::prioritized_paths(\n\t\t\t\tinput.iter().map(|&(o, _)| Tuple::new(0, o)).collect(),\n\t\t\t\tinput.iter().map(|&(_, n)| Tuple::new(0, n)).collect(),\n\t\t\t);\n\t\t\tlet sorted: Vec<_> =\n\t\t\t\tsorted.iter().map(|(o, n)| (o.to_str().unwrap(), n.to_str().unwrap())).collect();\n\t\t\tassert_eq!(sorted, expected);\n\t\t}\n\n\t\t#[rustfmt::skip]\n\t\tcmp(\n\t\t\t&[(\"2\", \"3\"), (\"1\", \"2\"), (\"3\", \"4\")],\n\t\t\t&[(\"3\", \"4\"), (\"2\", \"3\"), (\"1\", \"2\")]\n\t\t);\n\n\t\t#[rustfmt::skip]\n\t\tcmp(\n\t\t\t&[(\"1\", \"3\"), (\"2\", \"3\"), (\"3\", \"4\")],\n\t\t\t&[(\"3\", \"4\"), (\"1\", \"3\"), (\"2\", \"3\")]\n\t\t);\n\n\t\t#[rustfmt::skip]\n\t\tcmp(\n\t\t\t&[(\"2\", \"1\"), (\"1\", \"2\")],\n\t\t\t&[(\"2\", \"1\"), (\"1\", \"2\")]\n\t\t);\n\n\t\t#[rustfmt::skip]\n\t\tcmp(\n\t\t\t&[(\"3\", \"2\"), (\"2\", \"1\"), (\"1\", \"3\"), (\"a\", \"b\"), (\"b\", \"c\")],\n\t\t\t&[(\"b\", \"c\"), (\"a\", \"b\"), (\"3\", \"2\"), (\"2\", \"1\"), (\"1\", \"3\")]\n\t\t);\n\n\t\t#[rustfmt::skip]\n\t\tcmp(\n\t\t\t&[(\"b\", \"b_\"), (\"a\", \"a_\"), (\"c\", \"c_\")],\n\t\t\t&[(\"b\", \"b_\"), (\"a\", \"a_\"), (\"c\", \"c_\")],\n\t\t);\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/cd.rs",
    "content": "use std::{mem, time::Duration};\n\nuse anyhow::Result;\nuse tokio::pin;\nuse tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};\nuse yazi_config::popup::InputCfg;\nuse yazi_dds::Pubsub;\nuse yazi_fs::{File, FilesOp, path::{clean_url, expand_url}};\nuse yazi_macro::{act, err, render, succ};\nuse yazi_parser::mgr::CdOpt;\nuse yazi_proxy::{CmpProxy, InputProxy, MgrProxy};\nuse yazi_shared::{Debounce, data::Data, url::{AsUrl, UrlBuf, UrlLike}};\nuse yazi_vfs::{VfsFile, provider};\nuse yazi_widgets::input::InputEvent;\n\nuse crate::{Actor, Ctx};\n\npub struct Cd;\n\nimpl Actor for Cd {\n\ttype Options = CdOpt;\n\n\tconst NAME: &str = \"cd\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tact!(mgr:escape_visual, cx)?;\n\t\tif opt.interactive {\n\t\t\treturn Self::cd_interactive(cx);\n\t\t}\n\n\t\tlet tab = cx.tab_mut();\n\t\tif opt.target == *tab.cwd() {\n\t\t\tsucc!();\n\t\t}\n\n\t\t// Take parent to history\n\t\tif let Some(t) = tab.parent.take() {\n\t\t\ttab.history.insert(t.url.clone(), t);\n\t\t}\n\n\t\t// Current\n\t\tlet rep = tab.history.remove_or(&opt.target);\n\t\tlet rep = mem::replace(&mut tab.current, rep);\n\t\ttab.history.insert(rep.url.clone(), rep);\n\n\t\t// Parent\n\t\tif let Some(parent) = opt.target.parent() {\n\t\t\ttab.parent = Some(tab.history.remove_or(parent));\n\t\t}\n\n\t\terr!(Pubsub::pub_after_cd(tab.id, tab.cwd()));\n\t\tact!(mgr:displace, cx)?;\n\t\tact!(mgr:hidden, cx).ok();\n\t\tact!(mgr:sort, cx).ok();\n\t\tact!(mgr:hover, cx)?;\n\t\tact!(mgr:refresh, cx)?;\n\t\tact!(mgr:stash, cx, opt).ok();\n\t\tact!(app:title, cx).ok();\n\t\tsucc!(render!());\n\t}\n}\n\nimpl Cd {\n\tfn cd_interactive(cx: &Ctx) -> Result<Data> {\n\t\tlet input = InputProxy::show(InputCfg::cd(cx.cwd().as_url()));\n\n\t\ttokio::spawn(async move {\n\t\t\tlet rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50));\n\t\t\tpin!(rx);\n\n\t\t\twhile let Some(result) = rx.next().await {\n\t\t\t\tmatch result {\n\t\t\t\t\tInputEvent::Submit(s) => {\n\t\t\t\t\t\tlet Ok(url) = UrlBuf::try_from(s).map(expand_url) else { return };\n\t\t\t\t\t\tlet Ok(url) = provider::absolute(&url).await else { return };\n\t\t\t\t\t\tlet url = clean_url(url);\n\n\t\t\t\t\t\tlet Ok(file) = File::new(&url).await else { return };\n\t\t\t\t\t\tif file.is_dir() {\n\t\t\t\t\t\t\treturn MgrProxy::cd(&url);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif let Some(p) = url.parent() {\n\t\t\t\t\t\t\tFilesOp::Upserting(p.into(), [(url.urn().into(), file)].into()).emit();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tMgrProxy::reveal(url);\n\t\t\t\t\t}\n\t\t\t\t\tInputEvent::Trigger(before, ticket) => {\n\t\t\t\t\t\tCmpProxy::trigger(before, ticket);\n\t\t\t\t\t}\n\t\t\t\t\t_ => break,\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/close.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::act;\nuse yazi_parser::mgr::CloseOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Close;\n\nimpl Actor for Close {\n\ttype Options = CloseOpt;\n\n\tconst NAME: &str = \"close\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tif cx.tabs().len() > 1 {\n\t\t\tact!(mgr:tab_close, cx, cx.tabs().cursor)\n\t\t} else {\n\t\t\tact!(mgr:quit, cx, opt.0)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/copy.rs",
    "content": "use anyhow::{Result, bail};\nuse yazi_macro::{act, succ};\nuse yazi_parser::mgr::CopyOpt;\nuse yazi_shared::{data::Data, strand::ToStrand, url::UrlLike};\nuse yazi_widgets::CLIPBOARD;\n\nuse crate::{Actor, Ctx};\n\npub struct Copy;\n\nimpl Actor for Copy {\n\ttype Options = CopyOpt;\n\n\tconst NAME: &str = \"copy\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tact!(mgr:escape_visual, cx)?;\n\n\t\tlet mut s = Vec::<u8>::new();\n\t\tlet mut it = if opt.hovered {\n\t\t\tBox::new(cx.hovered().map(|h| &h.url).into_iter())\n\t\t} else {\n\t\t\tcx.tab().selected_or_hovered()\n\t\t}\n\t\t.peekable();\n\n\t\twhile let Some(u) = it.next() {\n\t\t\tmatch opt.r#type.as_ref() {\n\t\t\t\t// TODO: rename to \"url\"\n\t\t\t\t\"path\" => {\n\t\t\t\t\ts.extend_from_slice(&opt.separator.transform(&u.to_strand()));\n\t\t\t\t}\n\t\t\t\t\"dirname\" => {\n\t\t\t\t\tif let Some(p) = u.parent() {\n\t\t\t\t\t\ts.extend_from_slice(&opt.separator.transform(&p.to_strand()));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\"filename\" => {\n\t\t\t\t\ts.extend_from_slice(&opt.separator.transform(&u.name().unwrap_or_default()));\n\t\t\t\t}\n\t\t\t\t\"name_without_ext\" => {\n\t\t\t\t\ts.extend_from_slice(&opt.separator.transform(&u.stem().unwrap_or_default()));\n\t\t\t\t}\n\t\t\t\t_ => bail!(\"Unknown copy type: {}\", opt.r#type),\n\t\t\t};\n\t\t\tif it.peek().is_some() {\n\t\t\t\ts.push(b'\\n');\n\t\t\t}\n\t\t}\n\n\t\t// Copy the CWD path regardless even if the directory is empty\n\t\tif s.is_empty() && opt.r#type == \"dirname\" {\n\t\t\ts.extend_from_slice(&opt.separator.transform(&cx.cwd().to_strand()));\n\t\t}\n\n\t\tfutures::executor::block_on(CLIPBOARD.set(s));\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/create.rs",
    "content": "use anyhow::{Result, bail};\nuse yazi_config::popup::{ConfirmCfg, InputCfg};\nuse yazi_fs::{File, FilesOp};\nuse yazi_macro::{ok_or_not_found, succ};\nuse yazi_parser::mgr::CreateOpt;\nuse yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy};\nuse yazi_shared::{data::Data, url::{UrlBuf, UrlLike}};\nuse yazi_vfs::{VfsFile, maybe_exists, provider};\nuse yazi_watcher::WATCHER;\nuse yazi_widgets::input::InputEvent;\n\nuse crate::{Actor, Ctx};\n\npub struct Create;\n\nimpl Actor for Create {\n\ttype Options = CreateOpt;\n\n\tconst NAME: &str = \"create\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet cwd = cx.cwd().to_owned();\n\t\tlet mut input = InputProxy::show(InputCfg::create(opt.dir));\n\n\t\ttokio::spawn(async move {\n\t\t\tlet Some(InputEvent::Submit(name)) = input.recv().await else { return };\n\t\t\tif name.is_empty() {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet Ok(new) = cwd.try_join(&name) else {\n\t\t\t\treturn;\n\t\t\t};\n\n\t\t\tif !opt.force\n\t\t\t\t&& maybe_exists(&new).await\n\t\t\t\t&& !ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t_ = Self::r#do(new, opt.dir || name.ends_with('/') || name.ends_with('\\\\')).await;\n\t\t});\n\t\tsucc!();\n\t}\n}\n\nimpl Create {\n\tasync fn r#do(new: UrlBuf, dir: bool) -> Result<()> {\n\t\tlet _permit = WATCHER.acquire().await.unwrap();\n\n\t\tif dir {\n\t\t\tprovider::create_dir_all(&new).await?;\n\t\t} else if let Ok(real) = provider::casefold(&new).await\n\t\t\t&& let Some((parent, urn)) = real.pair()\n\t\t{\n\t\t\tok_or_not_found!(provider::remove_file(&new).await);\n\t\t\tFilesOp::Deleting(parent.into(), [urn.into()].into()).emit();\n\t\t\tprovider::create(&new).await?;\n\t\t} else if let Some(parent) = new.parent() {\n\t\t\tprovider::create_dir_all(parent).await.ok();\n\t\t\tok_or_not_found!(provider::remove_file(&new).await);\n\t\t\tprovider::create(&new).await?;\n\t\t} else {\n\t\t\tbail!(\"Cannot create file at root\");\n\t\t}\n\n\t\tif let Ok(real) = provider::casefold(&new).await\n\t\t\t&& let Some((parent, urn)) = real.pair()\n\t\t{\n\t\t\tlet file = File::new(&real).await?;\n\t\t\tFilesOp::Upserting(parent.into(), [(urn.into(), file)].into()).emit();\n\t\t\tMgrProxy::reveal(&real);\n\t\t}\n\n\t\tOk(())\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/displace.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::succ;\nuse yazi_parser::{VoidOpt, mgr::DisplaceDoOpt};\nuse yazi_proxy::MgrProxy;\nuse yazi_shared::{data::Data, url::UrlLike};\nuse yazi_vfs::provider;\n\nuse crate::{Actor, Ctx};\n\npub struct Displace;\n\nimpl Actor for Displace {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"displace\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tif cx.cwd().is_absolute() {\n\t\t\tsucc!();\n\t\t}\n\n\t\tlet tab = cx.tab().id;\n\t\tlet from = cx.cwd().to_owned();\n\t\ttokio::spawn(async move {\n\t\t\tMgrProxy::displace_do(tab, DisplaceDoOpt {\n\t\t\t\tto: provider::canonicalize(&from).await.map_err(Into::into),\n\t\t\t\tfrom,\n\t\t\t});\n\t\t});\n\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/displace_do.rs",
    "content": "use anyhow::{Result, bail};\nuse yazi_fs::FilesOp;\nuse yazi_macro::{act, succ};\nuse yazi_parser::mgr::{CdSource, DisplaceDoOpt};\nuse yazi_shared::{data::Data, url::UrlLike};\n\nuse crate::{Actor, Ctx};\n\npub struct DisplaceDo;\n\nimpl Actor for DisplaceDo {\n\ttype Options = DisplaceDoOpt;\n\n\tconst NAME: &str = \"displace_do\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tif cx.cwd() != opt.from {\n\t\t\tsucc!()\n\t\t}\n\n\t\tlet to = match opt.to {\n\t\t\tOk(url) => url,\n\t\t\tErr(e) => return act!(mgr:update_files, cx, FilesOp::IOErr(opt.from, e)),\n\t\t};\n\n\t\tif !to.is_absolute() {\n\t\t\tbail!(\"Target URL must be absolute\");\n\t\t} else if let Some(hovered) = cx.hovered()\n\t\t\t&& let Ok(url) = to.try_join(hovered.urn())\n\t\t{\n\t\t\tact!(mgr:reveal, cx, (url, CdSource::Displace))\n\t\t} else {\n\t\t\tact!(mgr:cd, cx, (to, CdSource::Displace))\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/download.rs",
    "content": "use std::{mem, time::{Duration, Instant}};\n\nuse anyhow::Result;\nuse futures::{StreamExt, stream::FuturesUnordered};\nuse hashbrown::HashSet;\nuse yazi_fs::{File, FsScheme, provider::{Provider, local::Local}};\nuse yazi_macro::succ;\nuse yazi_parser::mgr::{DownloadOpt, OpenOpt};\nuse yazi_proxy::MgrProxy;\nuse yazi_shared::{data::Data, url::{UrlCow, UrlLike}};\nuse yazi_vfs::VfsFile;\n\nuse crate::{Actor, Ctx};\n\npub struct Download;\n\nimpl Actor for Download {\n\ttype Options = DownloadOpt;\n\n\tconst NAME: &str = \"download\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet cwd = cx.cwd().clone();\n\t\tlet scheduler = cx.tasks.scheduler.clone();\n\n\t\ttokio::spawn(async move {\n\t\t\tSelf::prepare(&opt.urls).await;\n\n\t\t\tlet mut wg1 = FuturesUnordered::new();\n\t\t\tfor url in opt.urls {\n\t\t\t\tlet done = scheduler.file_download(url.to_owned());\n\t\t\t\twg1.push(async move { (done.future().await, url) });\n\t\t\t}\n\n\t\t\tlet mut wg2 = vec![];\n\t\t\tlet mut urls = Vec::with_capacity(wg1.len());\n\t\t\tlet mut files = Vec::with_capacity(wg1.len());\n\t\t\tlet mut instant = Instant::now();\n\t\t\twhile let Some((success, url)) = wg1.next().await {\n\t\t\t\tif !success {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tlet Ok(f) = File::new(&url).await else { continue };\n\t\t\t\turls.push(url);\n\t\t\t\tfiles.push(f);\n\n\t\t\t\tif instant.elapsed() >= Duration::from_secs(1) {\n\t\t\t\t\twg2.push(scheduler.fetch_mimetype(mem::take(&mut files)));\n\t\t\t\t\tinstant = Instant::now();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !files.is_empty() {\n\t\t\t\twg2.push(scheduler.fetch_mimetype(files));\n\t\t\t}\n\t\t\tif futures::future::join_all(wg2).await.into_iter().any(|b| !b) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif opt.open && !urls.is_empty() {\n\t\t\t\tMgrProxy::open(OpenOpt {\n\t\t\t\t\tcwd:         Some(cwd.into()),\n\t\t\t\t\ttargets:     urls,\n\t\t\t\t\tinteractive: false,\n\t\t\t\t\thovered:     false,\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t\tsucc!();\n\t}\n}\n\nimpl Download {\n\tasync fn prepare(urls: &[UrlCow<'_>]) {\n\t\tlet roots: HashSet<_> = urls.iter().filter_map(|u| u.scheme().cache()).collect();\n\t\tfor mut root in roots {\n\t\t\troot.push(\"%lock\");\n\t\t\tLocal::regular(&root).create_dir_all().await.ok();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/enter.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, succ};\nuse yazi_parser::{VoidOpt, mgr::CdSource};\nuse yazi_shared::{data::Data, url::UrlLike};\n\nuse crate::{Actor, Ctx};\n\npub struct Enter;\n\nimpl Actor for Enter {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"enter\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tlet Some(h) = cx.hovered().filter(|h| h.is_dir()) else { succ!() };\n\n\t\tlet url = if h.url.is_search() { h.url.to_regular()? } else { h.url.clone() };\n\n\t\tact!(mgr:cd, cx, (url, CdSource::Enter))\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/escape.rs",
    "content": "use anyhow::{Result, bail};\nuse yazi_macro::{act, render, render_and, succ};\nuse yazi_parser::{VoidOpt, mgr::EscapeOpt};\nuse yazi_proxy::NotifyProxy;\nuse yazi_shared::{data::Data, url::UrlLike};\n\nuse crate::{Actor, Ctx};\n\npub struct Escape;\n\nimpl Actor for Escape {\n\ttype Options = EscapeOpt;\n\n\tconst NAME: &str = \"escape\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tif opt.is_empty() {\n\t\t\t_ = act!(mgr:escape_find, cx)? != false\n\t\t\t\t|| act!(mgr:escape_visual, cx)? != false\n\t\t\t\t|| act!(mgr:escape_filter, cx)? != false\n\t\t\t\t|| act!(mgr:escape_select, cx)? != false\n\t\t\t\t|| act!(mgr:escape_search, cx)? != false;\n\t\t\tsucc!();\n\t\t}\n\n\t\tif opt.contains(EscapeOpt::FIND) {\n\t\t\tact!(mgr:escape_find, cx)?;\n\t\t}\n\t\tif opt.contains(EscapeOpt::VISUAL) {\n\t\t\tact!(mgr:escape_visual, cx)?;\n\t\t}\n\t\tif opt.contains(EscapeOpt::FILTER) {\n\t\t\tact!(mgr:escape_filter, cx)?;\n\t\t}\n\t\tif opt.contains(EscapeOpt::SELECT) {\n\t\t\tact!(mgr:escape_select, cx)?;\n\t\t}\n\t\tif opt.contains(EscapeOpt::SEARCH) {\n\t\t\tact!(mgr:escape_search, cx)?;\n\t\t}\n\t\tsucc!();\n\t}\n}\n\n// --- Find\npub struct EscapeFind;\n\nimpl Actor for EscapeFind {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"escape_find\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tsucc!(render_and!(cx.tab_mut().finder.take().is_some()))\n\t}\n}\n\n// --- Visual\npub struct EscapeVisual;\n\nimpl Actor for EscapeVisual {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"escape_visual\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tlet tab = cx.tab_mut();\n\n\t\tlet select = tab.mode.is_select();\n\t\tlet Some((_, indices)) = tab.mode.take_visual() else { succ!(false) };\n\n\t\trender!();\n\t\tlet urls: Vec<_> =\n\t\t\tindices.into_iter().filter_map(|i| tab.current.files.get(i)).map(|f| &f.url).collect();\n\n\t\tif !select {\n\t\t\ttab.selected.remove_many(urls);\n\t\t} else if urls.len() != tab.selected.add_many(urls) {\n\t\t\tNotifyProxy::push_warn(\n\t\t\t\t\"Escape visual mode\",\n\t\t\t\t\"Some files cannot be selected, due to path nesting conflict.\",\n\t\t\t);\n\t\t\tbail!(\"Some files cannot be selected, due to path nesting conflict.\");\n\t\t}\n\n\t\tsucc!(true)\n\t}\n}\n\n// --- Filter\npub struct EscapeFilter;\n\nimpl Actor for EscapeFilter {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"escape_filter\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tif cx.current_mut().files.filter().is_none() {\n\t\t\tsucc!(false);\n\t\t}\n\n\t\tact!(mgr:filter_do, cx)?;\n\t\trender!();\n\t\tsucc!(true);\n\t}\n}\n\n// --- Select\npub struct EscapeSelect;\n\nimpl Actor for EscapeSelect {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"escape_select\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tlet tab = cx.tab_mut();\n\t\tif tab.selected.is_empty() {\n\t\t\tsucc!(false);\n\t\t}\n\n\t\ttab.selected.clear();\n\t\tif tab.hovered().is_some_and(|h| h.is_dir()) {\n\t\t\tact!(mgr:peek, cx, true)?;\n\t\t}\n\n\t\trender!();\n\t\tsucc!(true);\n\t}\n}\n\n// --- Search\npub struct EscapeSearch;\n\nimpl Actor for EscapeSearch {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"escape_search\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tlet b = cx.cwd().is_search();\n\t\tact!(mgr:search_stop, cx)?;\n\t\tsucc!(render_and!(b));\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/filter.rs",
    "content": "use std::time::Duration;\n\nuse anyhow::Result;\nuse tokio::pin;\nuse tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};\nuse yazi_config::popup::InputCfg;\nuse yazi_macro::succ;\nuse yazi_parser::mgr::FilterOpt;\nuse yazi_proxy::{InputProxy, MgrProxy};\nuse yazi_shared::{Debounce, data::Data};\nuse yazi_widgets::input::InputEvent;\n\nuse crate::{Actor, Ctx};\n\npub struct Filter;\n\nimpl Actor for Filter {\n\ttype Options = FilterOpt;\n\n\tconst NAME: &str = \"filter\";\n\n\tfn act(_: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet input = InputProxy::show(InputCfg::filter());\n\n\t\ttokio::spawn(async move {\n\t\t\tlet rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50));\n\t\t\tpin!(rx);\n\n\t\t\twhile let Some(event) = rx.next().await {\n\t\t\t\tlet done = event.is_submit();\n\t\t\t\tlet (InputEvent::Submit(s) | InputEvent::Type(s)) = event else { continue };\n\n\t\t\t\tMgrProxy::filter_do(FilterOpt { query: s.into(), case: opt.case, done });\n\t\t\t}\n\t\t});\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/filter_do.rs",
    "content": "use anyhow::Result;\nuse yazi_fs::Filter;\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::mgr::FilterOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct FilterDo;\n\nimpl Actor for FilterDo {\n\ttype Options = FilterOpt;\n\n\tconst NAME: &str = \"filter_do\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet filter = if opt.query.is_empty() { None } else { Some(Filter::new(&opt.query, opt.case)?) };\n\n\t\tlet hovered = cx.hovered().map(|f| f.urn().into());\n\t\tcx.current_mut().files.set_filter(filter);\n\n\t\tif cx.hovered().map(|f| f.urn()) != hovered.as_ref().map(Into::into) {\n\t\t\tact!(mgr:hover, cx, hovered)?;\n\t\t\tact!(mgr:peek, cx)?;\n\t\t\tact!(mgr:watch, cx)?;\n\t\t}\n\n\t\tif opt.done {\n\t\t\tact!(mgr:update_paged, cx)?;\n\t\t}\n\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/find.rs",
    "content": "use std::time::Duration;\n\nuse anyhow::Result;\nuse tokio::pin;\nuse tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};\nuse yazi_config::popup::InputCfg;\nuse yazi_macro::succ;\nuse yazi_parser::mgr::{FindDoOpt, FindOpt};\nuse yazi_proxy::{InputProxy, MgrProxy};\nuse yazi_shared::{Debounce, data::Data};\nuse yazi_widgets::input::InputEvent;\n\nuse crate::{Actor, Ctx};\n\npub struct Find;\n\nimpl Actor for Find {\n\ttype Options = FindOpt;\n\n\tconst NAME: &str = \"find\";\n\n\tfn act(_: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet input = InputProxy::show(InputCfg::find(opt.prev));\n\n\t\ttokio::spawn(async move {\n\t\t\tlet rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50));\n\t\t\tpin!(rx);\n\n\t\t\twhile let Some(InputEvent::Submit(s) | InputEvent::Type(s)) = rx.next().await {\n\t\t\t\tMgrProxy::find_do(FindDoOpt { query: s.into(), prev: opt.prev, case: opt.case });\n\t\t\t}\n\t\t});\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/find_arrow.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::mgr::FindArrowOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct FindArrow;\n\nimpl Actor for FindArrow {\n\ttype Options = FindArrowOpt;\n\n\tconst NAME: &str = \"find_arrow\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet tab = cx.tab_mut();\n\t\tlet Some(finder) = &mut tab.finder else { succ!() };\n\n\t\trender!(finder.catchup(&tab.current));\n\t\tlet offset = if opt.prev {\n\t\t\tfinder.prev(&tab.current.files, tab.current.cursor, false)\n\t\t} else {\n\t\t\tfinder.next(&tab.current.files, tab.current.cursor, false)\n\t\t};\n\n\t\tif let Some(offset) = offset {\n\t\t\tact!(mgr:arrow, cx, offset)?;\n\t\t}\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/find_do.rs",
    "content": "use anyhow::Result;\nuse yazi_core::tab::Finder;\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::mgr::FindDoOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct FindDo;\n\nimpl Actor for FindDo {\n\ttype Options = FindDoOpt;\n\n\tconst NAME: &str = \"find_do\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tif opt.query.is_empty() {\n\t\t\treturn act!(mgr:escape_find, cx);\n\t\t}\n\n\t\tlet finder = Finder::new(&opt.query, opt.case)?;\n\t\tif matches!(&cx.tab().finder, Some(f) if f.filter == finder.filter) {\n\t\t\tsucc!();\n\t\t}\n\n\t\tlet step = if opt.prev {\n\t\t\tfinder.prev(&cx.current().files, cx.current().cursor, true)\n\t\t} else {\n\t\t\tfinder.next(&cx.current().files, cx.current().cursor, true)\n\t\t};\n\n\t\tif let Some(step) = step {\n\t\t\tact!(mgr:arrow, cx, step)?;\n\t\t}\n\n\t\tcx.tab_mut().finder = Some(finder);\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/follow.rs",
    "content": "use anyhow::Result;\nuse yazi_fs::path::clean_url;\nuse yazi_macro::{act, succ};\nuse yazi_parser::{VoidOpt, mgr::CdSource};\nuse yazi_shared::{data::Data, url::UrlLike};\n\nuse crate::{Actor, Ctx};\n\npub struct Follow;\n\nimpl Actor for Follow {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"follow\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tlet Some(file) = cx.hovered() else { succ!() };\n\t\tlet Some(link_to) = &file.link_to else { succ!() };\n\t\tlet Some(parent) = file.url.parent() else { succ!() };\n\t\tlet Ok(joined) = parent.try_join(link_to) else { succ!() };\n\t\tact!(mgr:reveal, cx, (clean_url(joined), CdSource::Follow))\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/forward.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, succ};\nuse yazi_parser::{VoidOpt, mgr::CdSource};\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Forward;\n\nimpl Actor for Forward {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"forward\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tif let Some(u) = cx.tab_mut().backstack.shift_forward().cloned() {\n\t\t\tact!(mgr:cd, cx, (u, CdSource::Forward))?;\n\t\t}\n\t\tsucc!()\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/hardlink.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::succ;\nuse yazi_parser::mgr::HardlinkOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Hardlink;\n\nimpl Actor for Hardlink {\n\ttype Options = HardlinkOpt;\n\n\tconst NAME: &str = \"hardlink\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet mgr = &mut cx.core.mgr;\n\t\tlet tab = &mgr.tabs[cx.tab];\n\n\t\tif !mgr.yanked.cut {\n\t\t\tcx.core.tasks.file_hardlink(&mgr.yanked, tab.cwd(), opt.force, opt.follow);\n\t\t}\n\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/hidden.rs",
    "content": "use anyhow::Result;\nuse yazi_core::tab::Folder;\nuse yazi_dds::spark::SparkKind;\nuse yazi_fs::FolderStage;\nuse yazi_macro::{act, render, render_and, succ};\nuse yazi_parser::mgr::HiddenOpt;\nuse yazi_shared::{Source, data::Data};\n\nuse crate::{Actor, Ctx};\n\npub struct Hidden;\n\nimpl Actor for Hidden {\n\ttype Options = HiddenOpt;\n\n\tconst NAME: &str = \"hidden\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet state = opt.state.bool(cx.tab().pref.show_hidden);\n\t\tcx.tab_mut().pref.show_hidden = state;\n\n\t\tlet hovered = cx.hovered().map(|f| f.urn().to_owned());\n\t\tlet apply = |f: &mut Folder| {\n\t\t\tif f.stage == FolderStage::Loading {\n\t\t\t\trender!();\n\t\t\t\tfalse\n\t\t\t} else {\n\t\t\t\tf.files.set_show_hidden(state);\n\t\t\t\trender_and!(f.files.catchup_revision())\n\t\t\t}\n\t\t};\n\n\t\t// Apply to CWD and parent\n\t\tif let (a, Some(b)) = (apply(cx.current_mut()), cx.parent_mut().map(apply))\n\t\t\t&& (a | b)\n\t\t{\n\t\t\tact!(mgr:hover, cx)?;\n\t\t\tact!(mgr:update_paged, cx)?;\n\t\t}\n\n\t\t// Apply to hovered\n\t\tif let Some(h) = cx.hovered_folder_mut()\n\t\t\t&& apply(h)\n\t\t{\n\t\t\trender!(h.repos(None));\n\t\t\tact!(mgr:peek, cx, true)?;\n\t\t} else if cx.hovered().map(|f| f.urn()) != hovered.as_ref().map(Into::into) {\n\t\t\tact!(mgr:peek, cx)?;\n\t\t\tact!(mgr:watch, cx)?;\n\t\t}\n\n\t\tsucc!()\n\t}\n\n\tfn hook(cx: &Ctx, _: &Self::Options) -> Option<SparkKind> {\n\t\tmatch cx.source() {\n\t\t\tSource::Ind => Some(SparkKind::IndHidden),\n\t\t\tSource::Key => Some(SparkKind::KeyHidden),\n\t\t\t_ => None,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/hover.rs",
    "content": "use anyhow::Result;\nuse yazi_dds::Pubsub;\nuse yazi_macro::{err, render, succ, tab};\nuse yazi_parser::mgr::HoverOpt;\nuse yazi_shared::{data::Data, url::UrlLike};\n\nuse crate::{Actor, Ctx};\n\npub struct Hover;\n\nimpl Actor for Hover {\n\ttype Options = HoverOpt;\n\n\tconst NAME: &str = \"hover\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet tab = tab!(cx);\n\n\t\t// Parent should always track CWD\n\t\tif let Some(p) = &mut tab.parent {\n\t\t\trender!(p.repos(tab.current.url.try_strip_prefix(&p.url).ok()));\n\t\t}\n\n\t\t// Repos CWD\n\t\ttab.current.repos(opt.urn.as_ref().map(Into::into));\n\n\t\t// Turn on tracing\n\t\tif let (Some(h), Some(u)) = (tab.hovered(), opt.urn)\n\t\t\t&& h.urn() == u\n\t\t{\n\t\t\t// `hover(Some)` occurs after user actions, such as create, rename, reveal, etc.\n\t\t\t// At this point, it's intuitive to track the location of the file regardless.\n\t\t\ttab.current.trace = Some(u.clone());\n\t\t}\n\n\t\t// Publish through DDS\n\t\terr!(Pubsub::pub_after_hover(tab.id, tab.hovered().map(|h| &h.url)));\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/leave.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, succ};\nuse yazi_parser::{VoidOpt, mgr::CdSource};\nuse yazi_shared::{data::Data, url::UrlLike};\n\nuse crate::{Actor, Ctx};\n\npub struct Leave;\n\nimpl Actor for Leave {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"leave\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tlet url = cx\n\t\t\t.hovered()\n\t\t\t.and_then(|h| h.url.parent())\n\t\t\t.filter(|u| u != cx.cwd())\n\t\t\t.or_else(|| cx.cwd().parent());\n\n\t\tlet Some(mut url) = url else { succ!() };\n\t\tif url.is_search() {\n\t\t\turl = url.as_regular()?;\n\t\t}\n\n\t\tact!(mgr:cd, cx, (url, CdSource::Leave))\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/linemode.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{render, succ};\nuse yazi_parser::mgr::LinemodeOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Linemode;\n\nimpl Actor for Linemode {\n\ttype Options = LinemodeOpt;\n\n\tconst NAME: &str = \"linemode\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet tab = cx.tab_mut();\n\n\t\tif opt.new != tab.pref.linemode {\n\t\t\ttab.pref.linemode = opt.new.into_owned();\n\t\t\trender!();\n\t\t}\n\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/link.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::succ;\nuse yazi_parser::mgr::LinkOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Link;\n\nimpl Actor for Link {\n\ttype Options = LinkOpt;\n\n\tconst NAME: &str = \"link\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet mgr = &mut cx.core.mgr;\n\t\tlet tab = &mgr.tabs[cx.tab];\n\n\t\tif !mgr.yanked.cut {\n\t\t\tcx.core.tasks.file_link(&mgr.yanked, tab.cwd(), opt.relative, opt.force);\n\t\t}\n\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/mod.rs",
    "content": "yazi_macro::mod_flat!(\n\tarrow\n\tback\n\tbulk_rename\n\tcd\n\tclose\n\tcopy\n\tcreate\n\tdisplace\n\tdisplace_do\n\tdownload\n\tenter\n\tescape\n\tfilter\n\tfilter_do\n\tfind\n\tfind_arrow\n\tfind_do\n\tfollow\n\tforward\n\thardlink\n\thidden\n\thover\n\tleave\n\tlinemode\n\tlink\n\topen\n\topen_do\n\tpaste\n\tpeek\n\tquit\n\trefresh\n\tremove\n\trename\n\treveal\n\tsearch\n\tseek\n\tshell\n\tsort\n\tspot\n\tstash\n\tsuspend\n\ttab_close\n\ttab_create\n\ttab_rename\n\ttab_swap\n\ttab_switch\n\ttoggle\n\ttoggle_all\n\tunyank\n\tupdate_files\n\tupdate_mimes\n\tupdate_paged\n\tupdate_peeked\n\tupdate_spotted\n\tupdate_yanked\n\tupload\n\tvisual_mode\n\twatch\n\tyank\n);\n"
  },
  {
    "path": "yazi-actor/src/mgr/open.rs",
    "content": "use anyhow::Result;\nuse yazi_boot::ARGS;\nuse yazi_fs::File;\nuse yazi_macro::{act, succ};\nuse yazi_parser::mgr::{OpenDoOpt, OpenOpt};\nuse yazi_proxy::MgrProxy;\nuse yazi_shared::data::Data;\nuse yazi_vfs::VfsFile;\n\nuse crate::{Actor, Ctx, mgr::Quit};\n\npub struct Open;\n\nimpl Actor for Open {\n\ttype Options = OpenOpt;\n\n\tconst NAME: &str = \"open\";\n\n\tfn act(cx: &mut Ctx, mut opt: Self::Options) -> Result<Data> {\n\t\tif !opt.interactive && ARGS.chooser_file.is_some() {\n\t\t\tsucc!(if !opt.targets.is_empty() {\n\t\t\t\tQuit::with_selected(opt.targets)\n\t\t\t} else if opt.hovered {\n\t\t\t\tQuit::with_selected(cx.hovered().map(|h| &h.url))\n\t\t\t} else {\n\t\t\t\tact!(mgr:escape_visual, cx)?;\n\t\t\t\tQuit::with_selected(cx.tab().selected_or_hovered())\n\t\t\t});\n\t\t}\n\n\t\tif opt.targets.is_empty() {\n\t\t\topt.targets = if opt.hovered {\n\t\t\t\tcx.hovered().map(|h| vec![h.url.clone().into()]).unwrap_or_default()\n\t\t\t} else {\n\t\t\t\tact!(mgr:escape_visual, cx)?;\n\t\t\t\tcx.tab().selected_or_hovered().cloned().map(Into::into).collect()\n\t\t\t};\n\t\t}\n\t\tif opt.targets.is_empty() {\n\t\t\tsucc!();\n\t\t}\n\n\t\tlet todo: Vec<_> = opt\n\t\t\t.targets\n\t\t\t.iter()\n\t\t\t.enumerate()\n\t\t\t.filter(|&(_, u)| !cx.mgr.mimetype.contains(u))\n\t\t\t.map(|(i, _)| i)\n\t\t\t.collect();\n\n\t\tlet cwd = opt.cwd.unwrap_or_else(|| cx.cwd().clone().into());\n\t\tif todo.is_empty() {\n\t\t\treturn act!(mgr:open_do, cx, OpenDoOpt { cwd, targets: opt.targets, interactive: opt.interactive });\n\t\t}\n\n\t\tlet scheduler = cx.tasks.scheduler.clone();\n\t\ttokio::spawn(async move {\n\t\t\tlet mut files = Vec::with_capacity(todo.len());\n\t\t\tfor i in todo {\n\t\t\t\tif let Ok(f) = File::new(&opt.targets[i]).await {\n\t\t\t\t\tfiles.push(f);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif scheduler.fetch_mimetype(files).await {\n\t\t\t\tMgrProxy::open_do(OpenDoOpt { cwd, targets: opt.targets, interactive: opt.interactive });\n\t\t\t}\n\t\t});\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/open_do.rs",
    "content": "use anyhow::Result;\nuse hashbrown::HashMap;\nuse yazi_config::{YAZI, popup::PickCfg};\nuse yazi_macro::succ;\nuse yazi_parser::{mgr::OpenDoOpt, tasks::ProcessOpenOpt};\nuse yazi_proxy::{PickProxy, TasksProxy};\nuse yazi_shared::{data::Data, url::UrlCow};\n\nuse crate::{Actor, Ctx};\n\npub struct OpenDo;\n\nimpl Actor for OpenDo {\n\ttype Options = OpenDoOpt;\n\n\tconst NAME: &str = \"open_do\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet targets: Vec<_> = opt\n\t\t\t.targets\n\t\t\t.into_iter()\n\t\t\t.map(|u| {\n\t\t\t\tlet m = cx.mgr.mimetype.get(&u).unwrap_or_default();\n\t\t\t\t(u, m)\n\t\t\t})\n\t\t\t.filter(|(_, m)| !m.is_empty())\n\t\t\t.collect();\n\n\t\tif targets.is_empty() {\n\t\t\tsucc!();\n\t\t} else if !opt.interactive {\n\t\t\tsucc!(Self::match_and_open(cx, opt.cwd, targets));\n\t\t}\n\n\t\tlet openers: Vec<_> = YAZI.opener.all(YAZI.open.common(&targets).into_iter()).collect();\n\t\tif openers.is_empty() {\n\t\t\tsucc!();\n\t\t}\n\n\t\tlet pick = PickProxy::show(PickCfg::open(openers.iter().map(|o| o.desc()).collect()));\n\t\tlet urls: Vec<_> =\n\t\t\t[UrlCow::default()].into_iter().chain(targets.into_iter().map(|(u, _)| u)).collect();\n\t\ttokio::spawn(async move {\n\t\t\tif let Some(choice) = pick.await {\n\t\t\t\tTasksProxy::open_shell_compat(ProcessOpenOpt {\n\t\t\t\t\tcwd:    opt.cwd,\n\t\t\t\t\tcmd:    openers[choice].run.clone().into(),\n\t\t\t\t\targs:   urls,\n\t\t\t\t\tblock:  openers[choice].block,\n\t\t\t\t\torphan: openers[choice].orphan,\n\t\t\t\t\tdone:   None,\n\t\t\t\t\tspread: openers[choice].spread,\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t\tsucc!();\n\t}\n}\n\nimpl OpenDo {\n\t// TODO: remove\n\tfn match_and_open(cx: &Ctx, cwd: UrlCow<'static>, targets: Vec<(UrlCow<'static>, &str)>) {\n\t\tlet mut openers = HashMap::new();\n\t\tfor (url, mime) in targets {\n\t\t\tif let Some(opener) = YAZI.opener.first(YAZI.open.all(&url, mime)) {\n\t\t\t\topeners.entry(opener).or_insert_with(|| vec![UrlCow::default()]).push(url);\n\t\t\t}\n\t\t}\n\t\tfor (opener, args) in openers {\n\t\t\tcx.tasks.open_shell_compat(ProcessOpenOpt {\n\t\t\t\tcwd: cwd.clone(),\n\t\t\t\tcmd: opener.run.clone().into(),\n\t\t\t\targs,\n\t\t\t\tblock: opener.block,\n\t\t\t\torphan: opener.orphan,\n\t\t\t\tdone: None,\n\t\t\t\tspread: opener.spread,\n\t\t\t});\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/paste.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, succ};\nuse yazi_parser::mgr::PasteOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Paste;\n\nimpl Actor for Paste {\n\ttype Options = PasteOpt;\n\n\tconst NAME: &str = \"paste\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet mgr = &mut cx.core.mgr;\n\t\tlet tab = &mgr.tabs[cx.tab];\n\n\t\tlet dest = tab.cwd();\n\t\tif mgr.yanked.cut {\n\t\t\tcx.core.tasks.file_cut(&mgr.yanked, dest, opt.force);\n\n\t\t\tmgr.tabs.iter_mut().for_each(|t| _ = t.selected.remove_many(mgr.yanked.iter()));\n\t\t\tact!(mgr:unyank, cx)\n\t\t} else {\n\t\t\tsucc!(cx.core.tasks.file_copy(&mgr.yanked, dest, opt.force, opt.follow));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/peek.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::succ;\nuse yazi_parser::mgr::PeekOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Peek;\n\nimpl Actor for Peek {\n\ttype Options = PeekOpt;\n\n\tconst NAME: &str = \"peek\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet Some(hovered) = cx.hovered().cloned() else {\n\t\t\tsucc!(cx.tab_mut().preview.reset());\n\t\t};\n\t\tif cx.term.is_none() {\n\t\t\tsucc!(cx.tab_mut().preview.reset_image());\n\t\t}\n\n\t\tlet mime = cx.mgr.mimetype.owned(&hovered.url).unwrap_or_default();\n\t\tlet folder = cx.tab().hovered_folder().map(|f| (f.offset, f.cha));\n\n\t\tif !cx.tab().preview.same_url(&hovered.url) {\n\t\t\tcx.tab_mut().preview.skip = folder.map(|f| f.0).unwrap_or_default();\n\t\t}\n\t\tif !cx.tab().preview.same_file(&hovered, &mime) {\n\t\t\tcx.tab_mut().preview.reset();\n\t\t}\n\t\tif !cx.tab().preview.same_folder(&hovered.url) {\n\t\t\tcx.tab_mut().preview.folder_lock = None;\n\t\t}\n\n\t\tif matches!(opt.only_if, Some(u) if u != hovered.url) {\n\t\t\tsucc!();\n\t\t}\n\n\t\tif let Some(skip) = opt.skip {\n\t\t\tlet preview = &mut cx.tab_mut().preview;\n\t\t\tif opt.upper_bound {\n\t\t\t\tpreview.skip = preview.skip.min(skip);\n\t\t\t} else {\n\t\t\t\tpreview.skip = skip;\n\t\t\t}\n\t\t}\n\n\t\tif hovered.is_dir() {\n\t\t\tcx.tab_mut().preview.go_folder(hovered, folder.map(|(_, cha)| cha), mime, opt.force);\n\t\t} else {\n\t\t\tcx.tab_mut().preview.go(hovered, mime, opt.force);\n\t\t}\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/quit.rs",
    "content": "use std::time::Duration;\n\nuse anyhow::Result;\nuse tokio::{select, time};\nuse yazi_config::popup::ConfirmCfg;\nuse yazi_dds::spark::SparkKind;\nuse yazi_macro::{act, succ};\nuse yazi_parser::app::QuitOpt;\nuse yazi_proxy::{AppProxy, ConfirmProxy};\nuse yazi_shared::{data::Data, strand::{Strand, StrandLike, ToStrandJoin}, url::AsUrl};\n\nuse crate::{Actor, Ctx};\n\npub struct Quit;\n\nimpl Actor for Quit {\n\ttype Options = QuitOpt;\n\n\tconst NAME: &str = \"quit\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet ongoing = cx.tasks().ongoing().clone();\n\t\tlet (left, left_names) = {\n\t\t\tlet ongoing = ongoing.lock();\n\t\t\t(ongoing.len(), ongoing.values().take(11).map(|t| t.name.clone()).collect())\n\t\t};\n\n\t\tif left == 0 {\n\t\t\treturn act!(app:quit, cx, opt);\n\t\t}\n\n\t\ttokio::spawn(async move {\n\t\t\tlet mut i = 0;\n\t\t\tlet token = ConfirmProxy::show_sync(ConfirmCfg::quit(left, left_names));\n\t\t\tloop {\n\t\t\t\tselect! {\n\t\t\t\t\t_ = time::sleep(Duration::from_millis(50)) => {\n\t\t\t\t\t\ti += 1;\n\t\t\t\t\t\tif i > 40 { break }\n\t\t\t\t\t\telse if ongoing.lock().is_empty() {\n\t\t\t\t\t\t\tAppProxy::quit(opt);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tb = token.future() => {\n\t\t\t\t\t\tif b {\n\t\t\t\t\t\t\tAppProxy::quit(opt);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif token.future().await {\n\t\t\t\tAppProxy::quit(opt);\n\t\t\t}\n\t\t});\n\t\tsucc!();\n\t}\n\n\tfn hook(cx: &Ctx, _opt: &Self::Options) -> Option<SparkKind> {\n\t\tSome(SparkKind::KeyQuit).filter(|_| cx.source().is_key())\n\t}\n}\n\nimpl Quit {\n\tpub(super) fn with_selected<I>(selected: I)\n\twhere\n\t\tI: IntoIterator,\n\t\tI::Item: AsUrl,\n\t{\n\t\tlet paths = selected.into_iter().join(Strand::Utf8(\"\\n\"));\n\t\tif !paths.is_empty() {\n\t\t\tAppProxy::quit(QuitOpt { selected: Some(paths), ..Default::default() });\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/refresh.rs",
    "content": "use anyhow::Result;\nuse yazi_core::tab::Folder;\nuse yazi_fs::{CWD, Files, FilesOp, cha::Cha};\nuse yazi_macro::{act, succ};\nuse yazi_parser::VoidOpt;\nuse yazi_proxy::MgrProxy;\nuse yazi_shared::{data::Data, url::{UrlBuf, UrlLike}};\nuse yazi_vfs::{VfsFiles, VfsFilesOp};\n\nuse crate::{Actor, Ctx};\n\npub struct Refresh;\n\nimpl Actor for Refresh {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"refresh\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tCWD.set(cx.cwd(), Self::cwd_changed);\n\n\t\tif let Some(p) = cx.parent() {\n\t\t\tSelf::trigger_dirs(&[cx.current(), p]);\n\t\t} else {\n\t\t\tSelf::trigger_dirs(&[cx.current()]);\n\t\t}\n\n\t\tact!(mgr:peek, cx)?;\n\t\tact!(mgr:watch, cx)?;\n\t\tact!(mgr:update_paged, cx)?;\n\n\t\tcx.tasks().prework_sorted(&cx.current().files);\n\t\tsucc!();\n\t}\n}\n\nimpl Refresh {\n\tfn cwd_changed() {\n\t\tif CWD.load().kind().is_virtual() {\n\t\t\tMgrProxy::watch();\n\t\t}\n\t}\n\n\t// TODO: performance improvement\n\tfn trigger_dirs(folders: &[&Folder]) {\n\t\tasync fn go(dir: UrlBuf, cha: Cha) {\n\t\t\tlet Some(cha) = Files::assert_stale(&dir, cha).await else { return };\n\n\t\t\tmatch Files::from_dir_bulk(&dir).await {\n\t\t\t\tOk(files) => FilesOp::Full(dir, files, cha).emit(),\n\t\t\t\tErr(e) => FilesOp::issue_error(&dir, e).await,\n\t\t\t}\n\t\t}\n\n\t\tlet futs: Vec<_> = folders\n\t\t\t.iter()\n\t\t\t.filter(|&f| f.url.is_absolute() && f.url.is_internal())\n\t\t\t.map(|&f| go(f.url.clone(), f.cha))\n\t\t\t.collect();\n\n\t\tif !futs.is_empty() {\n\t\t\ttokio::spawn(futures::future::join_all(futs));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/remove.rs",
    "content": "use anyhow::Result;\nuse yazi_config::popup::ConfirmCfg;\nuse yazi_macro::{act, succ};\nuse yazi_parser::mgr::RemoveOpt;\nuse yazi_proxy::{ConfirmProxy, MgrProxy};\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Remove;\n\nimpl Actor for Remove {\n\ttype Options = RemoveOpt;\n\n\tconst NAME: &str = \"remove\";\n\n\tfn act(cx: &mut Ctx, mut opt: Self::Options) -> Result<Data> {\n\t\tact!(mgr:escape_visual, cx)?;\n\n\t\topt.targets = if opt.hovered {\n\t\t\tcx.hovered().map_or(vec![], |h| vec![h.url.clone()])\n\t\t} else {\n\t\t\tcx.tab().selected_or_hovered().cloned().collect()\n\t\t};\n\n\t\tif opt.targets.is_empty() {\n\t\t\tsucc!();\n\t\t} else if opt.force {\n\t\t\treturn act!(mgr:remove_do, cx, opt);\n\t\t}\n\n\t\tlet confirm = ConfirmProxy::show(if opt.permanently {\n\t\t\tConfirmCfg::delete(&opt.targets)\n\t\t} else {\n\t\t\tConfirmCfg::trash(&opt.targets)\n\t\t});\n\n\t\ttokio::spawn(async move {\n\t\t\tif confirm.await {\n\t\t\t\tMgrProxy::remove_do(opt.targets, opt.permanently);\n\t\t\t}\n\t\t});\n\t\tsucc!();\n\t}\n}\n\n// --- Do\npub struct RemoveDo;\n\nimpl Actor for RemoveDo {\n\ttype Options = RemoveOpt;\n\n\tconst NAME: &str = \"remove_do\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet mgr = &mut cx.mgr;\n\n\t\tmgr.tabs.iter_mut().for_each(|t| {\n\t\t\tt.selected.remove_many(&opt.targets);\n\t\t});\n\n\t\tfor u in &opt.targets {\n\t\t\tmgr.yanked.remove(u);\n\t\t}\n\n\t\tmgr.yanked.catchup_revision(false);\n\t\tcx.tasks.file_remove(opt.targets, opt.permanently);\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/rename.rs",
    "content": "use anyhow::Result;\nuse yazi_config::popup::{ConfirmCfg, InputCfg};\nuse yazi_dds::Pubsub;\nuse yazi_fs::{File, FilesOp};\nuse yazi_macro::{act, err, ok_or_not_found, succ};\nuse yazi_parser::mgr::RenameOpt;\nuse yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy};\nuse yazi_shared::{Id, data::Data, url::{UrlBuf, UrlLike}};\nuse yazi_vfs::{VfsFile, maybe_exists, provider};\nuse yazi_watcher::WATCHER;\nuse yazi_widgets::input::InputEvent;\n\nuse crate::{Actor, Ctx};\n\npub struct Rename;\n\nimpl Actor for Rename {\n\ttype Options = RenameOpt;\n\n\tconst NAME: &str = \"rename\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tact!(mgr:escape_visual, cx)?;\n\n\t\tif !opt.hovered && !cx.tab().selected.is_empty() {\n\t\t\treturn act!(mgr:bulk_rename, cx);\n\t\t}\n\n\t\tlet Some(hovered) = cx.hovered() else { succ!() };\n\n\t\tlet name = Self::empty_url_part(&hovered.url, &opt.empty);\n\t\tlet cursor = match opt.cursor.as_ref() {\n\t\t\t\"start\" => Some(0),\n\t\t\t\"before_ext\" => name\n\t\t\t\t.chars()\n\t\t\t\t.rev()\n\t\t\t\t.position(|c| c == '.')\n\t\t\t\t.filter(|_| hovered.is_file())\n\t\t\t\t.map(|i| name.chars().count() - i - 1)\n\t\t\t\t.filter(|&i| i != 0),\n\t\t\t_ => None,\n\t\t};\n\n\t\tlet (tab, old) = (cx.tab().id, hovered.url_owned());\n\t\tlet mut input = InputProxy::show(InputCfg::rename().with_value(name).with_cursor(cursor));\n\n\t\ttokio::spawn(async move {\n\t\t\tlet Some(InputEvent::Submit(name)) = input.recv().await else { return };\n\t\t\tif name.is_empty() {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet Some(Ok(new)) = old.parent().map(|u| u.try_join(name)) else {\n\t\t\t\treturn;\n\t\t\t};\n\n\t\t\tif opt.force || !maybe_exists(&new).await || provider::must_identical(&old, &new).await {\n\t\t\t\tSelf::r#do(tab, old, new).await.ok();\n\t\t\t} else if ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await {\n\t\t\t\tSelf::r#do(tab, old, new).await.ok();\n\t\t\t}\n\t\t});\n\t\tsucc!();\n\t}\n}\n\nimpl Rename {\n\tasync fn r#do(tab: Id, old: UrlBuf, new: UrlBuf) -> Result<()> {\n\t\tlet Some((old_p, old_n)) = old.pair() else { return Ok(()) };\n\t\tlet Some(_) = new.pair() else { return Ok(()) };\n\t\tlet _permit = WATCHER.acquire().await.unwrap();\n\n\t\tlet overwritten = provider::casefold(&new).await;\n\t\tprovider::rename(&old, &new).await?;\n\n\t\tif let Ok(u) = overwritten\n\t\t\t&& u != new\n\t\t\t&& let Some((parent, urn)) = u.pair()\n\t\t{\n\t\t\tok_or_not_found!(provider::rename(&u, &new).await);\n\t\t\tFilesOp::Deleting(parent.to_owned(), [urn.into()].into()).emit();\n\t\t}\n\n\t\tlet new = provider::casefold(&new).await?;\n\t\tlet Some((new_p, new_n)) = new.pair() else { return Ok(()) };\n\n\t\tlet file = File::new(&new).await?;\n\t\tif new_p == old_p {\n\t\t\tFilesOp::Upserting(old_p.into(), [(old_n.into(), file)].into()).emit();\n\t\t} else {\n\t\t\tFilesOp::Deleting(old_p.into(), [old_n.into()].into()).emit();\n\t\t\tFilesOp::Upserting(new_p.into(), [(new_n.into(), file)].into()).emit();\n\t\t}\n\n\t\tMgrProxy::reveal(&new);\n\t\terr!(Pubsub::pub_after_rename(tab, &old, &new));\n\t\tOk(())\n\t}\n\n\tfn empty_url_part(url: &UrlBuf, by: &str) -> String {\n\t\tif by == \"all\" {\n\t\t\treturn String::new();\n\t\t}\n\n\t\tlet ext = url.ext();\n\t\tmatch by {\n\t\t\t\"stem\" => ext.map_or_else(String::new, |s| format!(\".{}\", s.to_string_lossy())),\n\t\t\t\"ext\" if ext.is_some() => format!(\"{}.\", url.stem().unwrap().to_string_lossy()),\n\t\t\t\"dot_ext\" if ext.is_some() => url.stem().unwrap().to_string_lossy().into_owned(),\n\t\t\t_ => url.name().unwrap_or_default().to_string_lossy().into_owned(),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/reveal.rs",
    "content": "use anyhow::Result;\nuse yazi_fs::{File, FilesOp};\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::mgr::RevealOpt;\nuse yazi_shared::{data::Data, url::UrlLike};\n\nuse crate::{Actor, Ctx};\n\npub struct Reveal;\n\nimpl Actor for Reveal {\n\ttype Options = RevealOpt;\n\n\tconst NAME: &str = \"reveal\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet Some((parent, child)) = opt.target.pair() else { succ!() };\n\n\t\t// Cd to the parent directory\n\t\tact!(mgr:cd, cx, (parent, opt.source))?;\n\n\t\t// Try to hover over the child file\n\t\tlet tab = cx.tab_mut();\n\t\trender!(tab.current.hover(child));\n\n\t\t// If the child is not hovered, which means it doesn't exist,\n\t\t// create a dummy file\n\t\tif !opt.no_dummy && tab.hovered().is_none_or(|f| child != f.urn()) {\n\t\t\tlet op = FilesOp::Creating(parent.into(), vec![File::from_dummy(&opt.target, None)]);\n\t\t\ttab.current.update_pub(tab.id, op);\n\t\t}\n\n\t\t// Now, we can safely hover over the target\n\t\tact!(mgr:hover, cx, Some(child.into()))?;\n\n\t\tact!(mgr:peek, cx)?;\n\t\tact!(mgr:watch, cx)?;\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/search.rs",
    "content": "use std::{borrow::Cow, time::Duration};\n\nuse anyhow::Result;\nuse tokio::pin;\nuse tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};\nuse yazi_config::popup::InputCfg;\nuse yazi_fs::{FilesOp, cha::Cha};\nuse yazi_macro::{act, succ};\nuse yazi_parser::{VoidOpt, mgr::{CdSource, SearchOpt, SearchOptVia}};\nuse yazi_plugin::external;\nuse yazi_proxy::{InputProxy, MgrProxy, NotifyProxy};\nuse yazi_shared::{data::Data, url::{AsUrl, UrlLike}};\nuse yazi_widgets::input::InputEvent;\n\nuse crate::{Actor, Ctx};\n\npub struct Search;\n\nimpl Actor for Search {\n\ttype Options = SearchOpt;\n\n\tconst NAME: &str = \"search\";\n\n\tfn act(cx: &mut Ctx, mut opt: Self::Options) -> Result<Data> {\n\t\tif let Some(handle) = cx.tab_mut().search.take() {\n\t\t\thandle.abort();\n\t\t}\n\n\t\tlet mut input =\n\t\t\tInputProxy::show(InputCfg::search(opt.via.into_str()).with_value(&*opt.subject));\n\n\t\ttokio::spawn(async move {\n\t\t\tif let Some(InputEvent::Submit(subject)) = input.recv().await {\n\t\t\t\topt.subject = Cow::Owned(subject);\n\t\t\t\tMgrProxy::search_do(opt);\n\t\t\t}\n\t\t});\n\t\tsucc!();\n\t}\n}\n\n// --- Do\npub struct SearchDo;\n\nimpl Actor for SearchDo {\n\ttype Options = SearchOpt;\n\n\tconst NAME: &str = \"search_do\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet tab = cx.tab_mut();\n\t\tif let Some(handle) = tab.search.take() {\n\t\t\thandle.abort();\n\t\t}\n\n\t\tlet hidden = tab.pref.show_hidden;\n\t\tlet r#in = opt.r#in.as_ref().map_or_else(|| tab.cwd().as_url(), |u| u.as_url());\n\t\tlet Ok(cwd) = r#in.to_search(&opt.subject) else {\n\t\t\tsucc!(NotifyProxy::push_warn(\"Search\", \"Only local filesystem searches are supported\"));\n\t\t};\n\n\t\ttab.search = Some(tokio::spawn(async move {\n\t\t\tlet rx = match opt.via {\n\t\t\t\tSearchOptVia::Rg => external::rg(external::RgOpt {\n\t\t\t\t\tcwd: cwd.clone(),\n\t\t\t\t\thidden,\n\t\t\t\t\tsubject: opt.subject.into_owned(),\n\t\t\t\t\targs: opt.args,\n\t\t\t\t}),\n\t\t\t\tSearchOptVia::Rga => external::rga(external::RgaOpt {\n\t\t\t\t\tcwd: cwd.clone(),\n\t\t\t\t\thidden,\n\t\t\t\t\tsubject: opt.subject.into_owned(),\n\t\t\t\t\targs: opt.args,\n\t\t\t\t}),\n\t\t\t\tSearchOptVia::Fd => external::fd(external::FdOpt {\n\t\t\t\t\tcwd: cwd.clone(),\n\t\t\t\t\thidden,\n\t\t\t\t\tsubject: opt.subject.into_owned(),\n\t\t\t\t\targs: opt.args,\n\t\t\t\t}),\n\t\t\t}?;\n\n\t\t\tlet rx = UnboundedReceiverStream::new(rx).chunks_timeout(5000, Duration::from_millis(500));\n\t\t\tpin!(rx);\n\n\t\t\tlet ((), ticket) = (MgrProxy::cd(&cwd), FilesOp::prepare(&cwd));\n\t\t\twhile let Some(chunk) = rx.next().await {\n\t\t\t\tFilesOp::Part(cwd.clone(), chunk, ticket).emit();\n\t\t\t}\n\t\t\tFilesOp::Done(cwd, Cha::default(), ticket).emit();\n\n\t\t\tOk(())\n\t\t}));\n\n\t\tsucc!();\n\t}\n}\n\n// --- Stop\npub struct SearchStop;\n\nimpl Actor for SearchStop {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"search_stop\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tlet tab = cx.tab_mut();\n\t\tif let Some(handle) = tab.search.take() {\n\t\t\thandle.abort();\n\t\t}\n\n\t\tif !tab.cwd().is_search() {\n\t\t\tsucc!();\n\t\t}\n\n\t\tif let Some(u) = tab.backstack.current().cloned() {\n\t\t\tact!(mgr:cd, cx, (u, CdSource::Escape))\n\t\t} else {\n\t\t\tact!(mgr:cd, cx, (tab.cwd().to_regular()?, CdSource::Escape))\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/seek.rs",
    "content": "use anyhow::Result;\nuse yazi_config::YAZI;\nuse yazi_macro::succ;\nuse yazi_parser::mgr::SeekOpt;\nuse yazi_plugin::isolate;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Seek;\n\nimpl Actor for Seek {\n\ttype Options = SeekOpt;\n\n\tconst NAME: &str = \"seek\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet Some(hovered) = cx.hovered() else {\n\t\t\tsucc!(cx.tab_mut().preview.reset());\n\t\t};\n\n\t\tlet Some(mime) = cx.mgr.mimetype.get(&hovered.url) else {\n\t\t\tsucc!(cx.tab_mut().preview.reset());\n\t\t};\n\n\t\tlet Some(previewer) = YAZI.plugin.previewer(hovered, mime) else {\n\t\t\tsucc!(cx.tab_mut().preview.reset());\n\t\t};\n\n\t\tisolate::seek_sync(&previewer.run, hovered.clone(), opt.units);\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/shell.rs",
    "content": "use std::borrow::Cow;\n\nuse anyhow::Result;\nuse yazi_config::popup::InputCfg;\nuse yazi_macro::{act, succ};\nuse yazi_parser::{mgr::ShellOpt, tasks::ProcessOpenOpt};\nuse yazi_proxy::{InputProxy, TasksProxy};\nuse yazi_shared::data::Data;\nuse yazi_widgets::input::InputEvent;\n\nuse crate::{Actor, Ctx};\n\npub struct Shell;\n\nimpl Actor for Shell {\n\ttype Options = ShellOpt;\n\n\tconst NAME: &str = \"shell\";\n\n\tfn act(cx: &mut Ctx, mut opt: Self::Options) -> Result<Data> {\n\t\tact!(mgr:escape_visual, cx)?;\n\n\t\tlet cwd = opt.cwd.take().unwrap_or(cx.cwd().into()).into_owned();\n\t\tlet selected: Vec<_> = cx.tab().hovered_and_selected().cloned().map(Into::into).collect();\n\n\t\tlet input = opt.interactive.then(|| {\n\t\t\tInputProxy::show(InputCfg::shell(opt.block).with_value(&*opt.run).with_cursor(opt.cursor))\n\t\t});\n\n\t\ttokio::spawn(async move {\n\t\t\tif let Some(mut rx) = input {\n\t\t\t\tmatch rx.recv().await {\n\t\t\t\t\tSome(InputEvent::Submit(e)) => opt.run = Cow::Owned(e),\n\t\t\t\t\t_ => return,\n\t\t\t\t}\n\t\t\t}\n\t\t\tif opt.run.is_empty() {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tTasksProxy::open_shell_compat(ProcessOpenOpt {\n\t\t\t\tcwd:    cwd.into(),\n\t\t\t\tcmd:    opt.run.to_string().into(),\n\t\t\t\targs:   selected,\n\t\t\t\tblock:  opt.block,\n\t\t\t\torphan: opt.orphan,\n\t\t\t\tdone:   None,\n\t\t\t\tspread: true,\n\t\t\t});\n\t\t});\n\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/sort.rs",
    "content": "use anyhow::Result;\nuse yazi_core::tab::Folder;\nuse yazi_dds::spark::SparkKind;\nuse yazi_fs::{FilesSorter, FolderStage};\nuse yazi_macro::{act, render, render_and, succ};\nuse yazi_parser::mgr::SortOpt;\nuse yazi_shared::{Source, data::Data};\n\nuse crate::{Actor, Ctx};\n\npub struct Sort;\n\nimpl Actor for Sort {\n\ttype Options = SortOpt;\n\n\tconst NAME: &str = \"sort\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet pref = &mut cx.tab_mut().pref;\n\t\tpref.sort_by = opt.by.unwrap_or(pref.sort_by);\n\t\tpref.sort_reverse = opt.reverse.unwrap_or(pref.sort_reverse);\n\t\tpref.sort_dir_first = opt.dir_first.unwrap_or(pref.sort_dir_first);\n\t\tpref.sort_sensitive = opt.sensitive.unwrap_or(pref.sort_sensitive);\n\t\tpref.sort_translit = opt.translit.unwrap_or(pref.sort_translit);\n\t\tpref.sort_fallback = opt.fallback.unwrap_or(pref.sort_fallback);\n\n\t\tlet sorter = FilesSorter::from(&*pref);\n\t\tlet hovered = cx.hovered().map(|f| f.urn().to_owned());\n\t\tlet apply = |f: &mut Folder| {\n\t\t\tif f.stage == FolderStage::Loading {\n\t\t\t\trender!();\n\t\t\t\tfalse\n\t\t\t} else {\n\t\t\t\tf.files.set_sorter(sorter);\n\t\t\t\trender_and!(f.files.catchup_revision())\n\t\t\t}\n\t\t};\n\n\t\t// Apply to CWD and parent\n\t\tif let (a, Some(b)) = (apply(cx.current_mut()), cx.parent_mut().map(apply))\n\t\t\t&& (a | b)\n\t\t{\n\t\t\tact!(mgr:hover, cx)?;\n\t\t\tact!(mgr:update_paged, cx)?;\n\t\t\tcx.tasks.prework_sorted(&cx.mgr.tabs[cx.tab].current.files);\n\t\t}\n\n\t\t// Apply to hovered\n\t\tif let Some(h) = cx.hovered_folder_mut()\n\t\t\t&& apply(h)\n\t\t{\n\t\t\trender!(h.repos(None));\n\t\t\tact!(mgr:peek, cx, true)?;\n\t\t} else if cx.hovered().map(|f| f.urn()) != hovered.as_ref().map(Into::into) {\n\t\t\tact!(mgr:peek, cx)?;\n\t\t\tact!(mgr:watch, cx)?;\n\t\t}\n\n\t\tsucc!();\n\t}\n\n\tfn hook(cx: &Ctx, _: &Self::Options) -> Option<SparkKind> {\n\t\tmatch cx.source() {\n\t\t\tSource::Ind => Some(SparkKind::IndSort),\n\t\t\tSource::Key => Some(SparkKind::KeySort),\n\t\t\t_ => None,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/spot.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, succ};\nuse yazi_parser::mgr::SpotOpt;\nuse yazi_shared::{data::Data, pool::InternStr};\n\nuse crate::{Actor, Ctx};\n\npub struct Spot;\n\nimpl Actor for Spot {\n\ttype Options = SpotOpt;\n\n\tconst NAME: &str = \"spot\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tact!(mgr:escape_visual, cx)?;\n\t\tlet Some(hovered) = cx.hovered().cloned() else { succ!() };\n\n\t\tlet mime = if cx.tab().selected.is_empty() {\n\t\t\tcx.mgr.mimetype.owned(&hovered.url).unwrap_or_default()\n\t\t} else {\n\t\t\t\"multi/unknown\".intern()\n\t\t};\n\n\t\t// if !self.active().spot.same_file(&hovered, &mime) {\n\t\t// self.active_mut().spot.reset();\n\t\t// }\n\n\t\tif let Some(skip) = opt.skip {\n\t\t\tcx.tab_mut().spot.skip = skip;\n\t\t} else if !cx.tab().spot.same_url(&hovered.url) {\n\t\t\tcx.tab_mut().spot.skip = 0;\n\t\t}\n\n\t\tcx.tab_mut().spot.go(hovered, mime);\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/stash.rs",
    "content": "use anyhow::Result;\nuse yazi_dds::spark::SparkKind;\nuse yazi_macro::succ;\nuse yazi_parser::mgr::StashOpt;\nuse yazi_shared::{Source, data::Data, url::{AsUrl, UrlLike}};\n\nuse crate::{Actor, Ctx};\n\npub struct Stash;\n\nimpl Actor for Stash {\n\ttype Options = StashOpt;\n\n\tconst NAME: &str = \"stash\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tif opt.target.is_absolute() && opt.target.is_internal() {\n\t\t\tcx.tab_mut().backstack.push(opt.target.as_url());\n\t\t}\n\n\t\tsucc!()\n\t}\n\n\tfn hook(cx: &Ctx, _opt: &Self::Options) -> Option<SparkKind> {\n\t\tmatch cx.source() {\n\t\t\tSource::Ind => Some(SparkKind::IndStash),\n\t\t\tSource::Relay => Some(SparkKind::RelayStash),\n\t\t\t_ => None,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/suspend.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::succ;\nuse yazi_parser::VoidOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Suspend;\n\nimpl Actor for Suspend {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"suspend\";\n\n\tfn act(_: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\t#[cfg(unix)]\n\t\tif !yazi_shared::session_leader() {\n\t\t\tunsafe {\n\t\t\t\tlibc::raise(libc::SIGTSTP);\n\t\t\t}\n\t\t}\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/tab_close.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::mgr::TabCloseOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct TabClose;\n\nimpl Actor for TabClose {\n\ttype Options = TabCloseOpt;\n\n\tconst NAME: &str = \"tab_close\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet len = cx.tabs().len();\n\t\tif len < 2 || opt.idx >= len {\n\t\t\tsucc!();\n\t\t}\n\n\t\tlet tabs = cx.tabs_mut();\n\t\ttabs.remove(opt.idx).shutdown();\n\n\t\tif opt.idx > tabs.cursor {\n\t\t\ttabs.set_idx(tabs.cursor);\n\t\t} else {\n\t\t\ttabs.set_idx(usize::min(tabs.cursor + 1, tabs.len() - 1));\n\t\t}\n\n\t\tlet cx = &mut Ctx::renew(cx);\n\t\tact!(mgr:refresh, cx)?;\n\t\tact!(mgr:peek, cx, true)?;\n\t\tact!(app:title, cx).ok();\n\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/tab_create.rs",
    "content": "use anyhow::Result;\nuse yazi_core::tab::Tab;\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::mgr::{CdSource, TabCreateOpt};\nuse yazi_proxy::NotifyProxy;\nuse yazi_shared::{data::Data, url::UrlLike};\n\nuse crate::{Actor, Ctx};\n\nconst MAX_TABS: usize = 9;\n\npub struct TabCreate;\n\nimpl Actor for TabCreate {\n\ttype Options = TabCreateOpt;\n\n\tconst NAME: &str = \"tab_create\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tif cx.tabs().len() >= MAX_TABS {\n\t\t\tsucc!(NotifyProxy::push_warn(\n\t\t\t\t\"Too many tabs\",\n\t\t\t\t\"You can only open up to 9 tabs at the same time.\"\n\t\t\t));\n\t\t}\n\n\t\tlet mut tab = Tab::default();\n\t\tlet (cd, url) = if let Some(wd) = opt.url {\n\t\t\t(true, wd.into_owned())\n\t\t} else if let Some(h) = cx.hovered() {\n\t\t\ttab.pref = cx.tab().pref.clone();\n\t\t\t(false, h.url.clone())\n\t\t} else if !cx.cwd().is_search() {\n\t\t\ttab.pref = cx.tab().pref.clone();\n\t\t\t(true, cx.cwd().clone())\n\t\t} else if let Some(u) = tab.backstack.current().cloned() {\n\t\t\ttab.pref = cx.tab().pref.clone();\n\t\t\t(true, u)\n\t\t} else {\n\t\t\ttab.pref = cx.tab().pref.clone();\n\t\t\t(true, tab.cwd().to_regular()?)\n\t\t};\n\n\t\tlet tabs = &mut cx.mgr.tabs;\n\t\ttabs.items.insert(tabs.cursor + 1, tab);\n\t\ttabs.set_idx(tabs.cursor + 1);\n\n\t\tlet cx = &mut Ctx::renew(cx);\n\t\tif cd {\n\t\t\tact!(mgr:cd, cx, (url, CdSource::Tab))?;\n\t\t} else {\n\t\t\tact!(mgr:reveal, cx, (url, CdSource::Tab))?;\n\t\t}\n\n\t\tact!(mgr:refresh, cx)?;\n\t\tact!(mgr:peek, cx, true)?;\n\t\tact!(app:title, cx).ok();\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/tab_rename.rs",
    "content": "use std::borrow::Cow;\n\nuse anyhow::Result;\nuse yazi_config::popup::InputCfg;\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::mgr::TabRenameOpt;\nuse yazi_proxy::{InputProxy, MgrProxy};\nuse yazi_shared::data::Data;\nuse yazi_widgets::input::InputEvent;\n\nuse crate::{Actor, Ctx};\n\npub struct TabRename;\n\nimpl Actor for TabRename {\n\ttype Options = TabRenameOpt;\n\n\tconst NAME: &str = \"tab_rename\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet tab = cx.tab().id;\n\t\tlet pref = &mut cx.tab_mut().pref;\n\n\t\tif !opt.interactive {\n\t\t\tpref.name = opt.name.unwrap_or_default().into_owned();\n\t\t\tact!(app:title, cx).ok();\n\t\t\tsucc!(render!());\n\t\t}\n\n\t\tlet mut input = InputProxy::show(\n\t\t\tInputCfg::tab_rename().with_value(opt.name.unwrap_or(Cow::Borrowed(&pref.name))),\n\t\t);\n\t\ttokio::spawn(async move {\n\t\t\tif let Some(InputEvent::Submit(name)) = input.recv().await {\n\t\t\t\tMgrProxy::tab_rename(tab, name);\n\t\t\t}\n\t\t});\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/tab_swap.rs",
    "content": "use anyhow::Result;\nuse yazi_dds::Pubsub;\nuse yazi_macro::{err, render, succ};\nuse yazi_parser::ArrowOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct TabSwap;\n\nimpl Actor for TabSwap {\n\ttype Options = ArrowOpt;\n\n\tconst NAME: &str = \"tab_swap\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet tabs = cx.tabs_mut();\n\n\t\tlet new = opt.step.add(tabs.cursor, tabs.len(), 0);\n\t\tif new == tabs.cursor {\n\t\t\tsucc!();\n\t\t}\n\n\t\ttabs.items.swap(tabs.cursor, new);\n\t\ttabs.cursor = new;\n\n\t\terr!(Pubsub::pub_after_tab(cx.active().id));\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/tab_switch.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::mgr::TabSwitchOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct TabSwitch;\n\nimpl Actor for TabSwitch {\n\ttype Options = TabSwitchOpt;\n\n\tconst NAME: &str = \"tab_switch\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet tabs = cx.tabs_mut();\n\t\tlet idx = if opt.relative {\n\t\t\topt.step.saturating_add_unsigned(tabs.cursor).rem_euclid(tabs.len() as _) as _\n\t\t} else {\n\t\t\topt.step as usize\n\t\t};\n\n\t\tif idx == tabs.cursor || idx >= tabs.len() {\n\t\t\tsucc!();\n\t\t}\n\n\t\ttabs.set_idx(idx);\n\t\tlet cx = &mut Ctx::renew(cx);\n\n\t\tact!(mgr:refresh, cx)?;\n\t\tact!(mgr:peek, cx, true)?;\n\t\tact!(app:title, cx).ok();\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/toggle.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{render_and, succ};\nuse yazi_parser::mgr::ToggleOpt;\nuse yazi_proxy::NotifyProxy;\nuse yazi_shared::{data::Data, url::UrlCow};\n\nuse crate::{Actor, Ctx};\n\npub struct Toggle;\n\nimpl Actor for Toggle {\n\ttype Options = ToggleOpt;\n\n\tconst NAME: &str = \"toggle\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet tab = cx.tab_mut();\n\t\tlet Some(url) = opt.url.or(tab.current.hovered().map(|h| UrlCow::from(&h.url))) else {\n\t\t\tsucc!();\n\t\t};\n\n\t\tlet b = match opt.state {\n\t\t\tSome(true) => render_and!(tab.selected.add(&url)),\n\t\t\tSome(false) => render_and!(tab.selected.remove(&url)) | true,\n\t\t\tNone => render_and!(tab.selected.remove(&url) || tab.selected.add(&url)),\n\t\t};\n\n\t\tif !b {\n\t\t\tNotifyProxy::push_warn(\n\t\t\t\t\"Toggle\",\n\t\t\t\t\"This file cannot be selected, due to path nesting conflict.\",\n\t\t\t);\n\t\t}\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/toggle_all.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{render, succ};\nuse yazi_parser::mgr::ToggleAllOpt;\nuse yazi_proxy::NotifyProxy;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct ToggleAll;\n\nimpl Actor for ToggleAll {\n\ttype Options = ToggleAllOpt;\n\n\tconst NAME: &str = \"toggle_all\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tuse either::Either::*;\n\t\tlet tab = cx.tab_mut();\n\n\t\tlet it = tab.current.files.iter().map(|f| &f.url);\n\t\tlet either = match opt.state {\n\t\t\tSome(true) if opt.urls.is_empty() => Left((vec![], it.collect())),\n\t\t\tSome(true) => Right((vec![], opt.urls)),\n\t\t\tSome(false) if opt.urls.is_empty() => Left((it.collect(), vec![])),\n\t\t\tSome(false) => Right((opt.urls, vec![])),\n\t\t\tNone if opt.urls.is_empty() => Left(it.partition(|&u| tab.selected.contains(u))),\n\t\t\tNone => Right(opt.urls.into_iter().partition(|u| tab.selected.contains(u))),\n\t\t};\n\n\t\tlet warn = match either {\n\t\t\tLeft((removal, addition)) => {\n\t\t\t\trender!(tab.selected.remove_many(removal) > 0);\n\t\t\t\taddition.len() != render!(tab.selected.add_many(addition), > 0)\n\t\t\t}\n\t\t\tRight((removal, addition)) => {\n\t\t\t\trender!(tab.selected.remove_many(&removal) > 0);\n\t\t\t\trender!(tab.selected.add_many(&addition), > 0) != addition.len()\n\t\t\t}\n\t\t};\n\n\t\tif warn {\n\t\t\tNotifyProxy::push_warn(\n\t\t\t\t\"Toggle all\",\n\t\t\t\t\"Some files cannot be selected, due to path nesting conflict.\",\n\t\t\t);\n\t\t}\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/unyank.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::VoidOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Unyank;\n\nimpl Actor for Unyank {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"unyank\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tlet repeek = cx.hovered().is_some_and(|f| f.is_dir() && cx.mgr.yanked.contains_in(&f.url));\n\t\tcx.mgr.yanked.clear();\n\n\t\trender!(cx.mgr.yanked.catchup_revision(false));\n\t\tif repeek {\n\t\t\tact!(mgr:peek, cx, true)?;\n\t\t}\n\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/update_files.rs",
    "content": "use anyhow::Result;\nuse yazi_core::tab::Folder;\nuse yazi_fs::FilesOp;\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::mgr::UpdateFilesOpt;\nuse yazi_shared::{data::Data, url::UrlLike};\nuse yazi_watcher::local::LINKED;\n\nuse crate::{Actor, Ctx};\n\npub struct UpdateFiles;\n\nimpl Actor for UpdateFiles {\n\ttype Options = UpdateFilesOpt;\n\n\tconst NAME: &str = \"update_files\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet revision = cx.current().files.revision;\n\t\tlet linked: Vec<_> = LINKED.read().from_dir(opt.op.cwd()).map(|u| opt.op.chdir(u)).collect();\n\n\t\tfor op in [opt.op].into_iter().chain(linked) {\n\t\t\tcx.mgr.yanked.apply_op(&op);\n\t\t\tSelf::update_tab(cx, op).ok();\n\t\t}\n\n\t\trender!(cx.mgr.yanked.catchup_revision(false));\n\t\tact!(mgr:hidden, cx).ok();\n\t\tact!(mgr:sort, cx).ok();\n\n\t\tif revision != cx.current().files.revision {\n\t\t\tact!(mgr:hover, cx)?;\n\t\t\tact!(mgr:peek, cx)?;\n\t\t\tact!(mgr:watch, cx)?;\n\t\t\tact!(mgr:update_paged, cx)?;\n\t\t}\n\t\tsucc!();\n\t}\n}\n\nimpl UpdateFiles {\n\tfn update_tab(cx: &mut Ctx, op: FilesOp) -> Result<Data> {\n\t\tlet url = op.cwd();\n\t\tcx.tab_mut().selected.apply_op(&op);\n\n\t\tif url == cx.cwd() {\n\t\t\tSelf::update_current(cx, op)\n\t\t} else if matches!(cx.parent(), Some(p) if *url == p.url) {\n\t\t\tSelf::update_parent(cx, op)\n\t\t} else if matches!(cx.hovered(), Some(h) if *url == h.url) {\n\t\t\tSelf::update_hovered(cx, op)\n\t\t} else {\n\t\t\tSelf::update_history(cx, op)\n\t\t}\n\t}\n\n\tfn update_parent(cx: &mut Ctx, op: FilesOp) -> Result<Data> {\n\t\tlet tab = cx.tab_mut();\n\n\t\tlet urn = tab.current.url.urn();\n\t\tlet leave = matches!(op, FilesOp::Deleting(_, ref urns) if urns.contains(&urn));\n\n\t\tif let Some(f) = tab.parent.as_mut() {\n\t\t\trender!(f.update_pub(tab.id, op));\n\t\t\trender!(f.hover(urn));\n\t\t}\n\n\t\tif leave {\n\t\t\tact!(mgr:leave, cx)?;\n\t\t}\n\t\tsucc!();\n\t}\n\n\tfn update_current(cx: &mut Ctx, op: FilesOp) -> Result<Data> {\n\t\tlet calc = !matches!(op, FilesOp::Size(..) | FilesOp::Deleting(..));\n\n\t\tlet id = cx.tab().id;\n\t\tif !cx.current_mut().update_pub(id, op) {\n\t\t\tsucc!();\n\t\t}\n\n\t\tif calc {\n\t\t\tcx.tasks.prework_sorted(&cx.current().files);\n\t\t}\n\t\tsucc!();\n\t}\n\n\tfn update_hovered(cx: &mut Ctx, op: FilesOp) -> Result<Data> {\n\t\tlet (id, url) = (cx.tab().id, op.cwd());\n\t\tlet folder = cx.tab_mut().history.entry_ref(url).or_insert_with(|| Folder::from(url));\n\n\t\tif folder.update_pub(id, op) {\n\t\t\tact!(mgr:peek, cx, true)?;\n\t\t}\n\t\tsucc!();\n\t}\n\n\tfn update_history(cx: &mut Ctx, op: FilesOp) -> Result<Data> {\n\t\tlet tab = &mut cx.tab_mut();\n\t\tlet leave = tab.parent.as_ref().and_then(|f| f.url.parent().map(|p| (p, f.url.urn()))).is_some_and(\n\t\t\t|(p, n)| matches!(op, FilesOp::Deleting(ref parent, ref urns) if *parent == p && urns.contains(&n)),\n\t\t);\n\n\t\ttab\n\t\t\t.history\n\t\t\t.entry_ref(op.cwd())\n\t\t\t.or_insert_with(|| Folder::from(op.cwd()))\n\t\t\t.update_pub(tab.id, op);\n\n\t\tif leave {\n\t\t\tact!(mgr:leave, cx)?;\n\t\t}\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/update_mimes.rs",
    "content": "use anyhow::Result;\nuse hashbrown::HashMap;\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::mgr::UpdateMimesOpt;\nuse yazi_shared::{data::Data, pool::InternStr, url::{AsUrl, UrlCov}};\nuse yazi_watcher::local::LINKED;\n\nuse crate::{Actor, Ctx};\n\npub struct UpdateMimes;\n\nimpl Actor for UpdateMimes {\n\ttype Options = UpdateMimesOpt;\n\n\tconst NAME: &str = \"update_mimes\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet linked = LINKED.read();\n\t\tlet updates = opt\n\t\t\t.updates\n\t\t\t.into_iter()\n\t\t\t.flat_map(|(key, value)| key.into_url().zip(value.into_string()))\n\t\t\t.filter(|(url, mime)| cx.mgr.mimetype.get(url) != Some(mime))\n\t\t\t.fold(HashMap::new(), |mut map, (u, m)| {\n\t\t\t\tfor u in linked.from_file(u.as_url()) {\n\t\t\t\t\tmap.insert(u.into(), m.intern());\n\t\t\t\t}\n\t\t\t\tmap.insert(u.into(), m.intern());\n\t\t\t\tmap\n\t\t\t});\n\n\t\tdrop(linked);\n\t\tif updates.is_empty() {\n\t\t\tsucc!();\n\t\t}\n\n\t\tlet affected: Vec<_> = cx\n\t\t\t.current()\n\t\t\t.paginate(cx.current().page)\n\t\t\t.iter()\n\t\t\t.filter(|&f| updates.contains_key(&UrlCov::new(&f.url)))\n\t\t\t.cloned()\n\t\t\t.collect();\n\n\t\tlet repeek = cx.hovered().is_some_and(|f| updates.contains_key(&UrlCov::new(&f.url)));\n\t\tcx.mgr.mimetype.extend(updates);\n\n\t\tif repeek {\n\t\t\tact!(mgr:peek, cx)?;\n\t\t}\n\t\tcx.tasks.fetch_paged(&affected, &cx.mgr.mimetype);\n\t\tcx.tasks.preload_paged(&affected, &cx.mgr.mimetype);\n\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/update_paged.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::succ;\nuse yazi_parser::mgr::UpdatePagedOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct UpdatePaged;\n\nimpl Actor for UpdatePaged {\n\ttype Options = UpdatePagedOpt;\n\n\tconst NAME: &str = \"update_paged\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tif opt.only_if.is_some_and(|u| u != *cx.cwd()) {\n\t\t\tsucc!();\n\t\t}\n\n\t\tlet targets = cx.current().paginate(opt.page.unwrap_or(cx.current().page));\n\t\tif !targets.is_empty() {\n\t\t\tcx.tasks().fetch_paged(targets, &cx.mgr.mimetype);\n\t\t\tcx.tasks().preload_paged(targets, &cx.mgr.mimetype);\n\t\t}\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/update_peeked.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{render, succ};\nuse yazi_parser::mgr::UpdatePeekedOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct UpdatePeeked;\n\nimpl Actor for UpdatePeeked {\n\ttype Options = UpdatePeekedOpt;\n\n\tconst NAME: &str = \"update_peeked\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet Some(hovered) = cx.hovered().map(|h| &h.url) else {\n\t\t\tsucc!(cx.tab_mut().preview.reset());\n\t\t};\n\n\t\tif opt.lock.url == *hovered {\n\t\t\tcx.tab_mut().preview.lock = Some(opt.lock);\n\t\t\trender!();\n\t\t}\n\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/update_spotted.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{render, succ};\nuse yazi_parser::mgr::UpdateSpottedOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct UpdateSpotted;\n\nimpl Actor for UpdateSpotted {\n\ttype Options = UpdateSpottedOpt;\n\n\tconst NAME: &str = \"update_spotted\";\n\n\tfn act(cx: &mut Ctx, mut opt: Self::Options) -> Result<Data> {\n\t\tlet tab = cx.tab_mut();\n\t\tlet Some(hovered) = tab.hovered().map(|h| &h.url) else {\n\t\t\tsucc!(tab.spot.reset());\n\t\t};\n\n\t\tif opt.lock.url != *hovered {\n\t\t\tsucc!();\n\t\t}\n\n\t\tif tab.spot.lock.as_ref().is_none_or(|l| l.id != opt.lock.id) {\n\t\t\ttab.spot.skip = opt.lock.selected().unwrap_or_default();\n\t\t} else if let Some(s) = opt.lock.selected() {\n\t\t\ttab.spot.skip = s;\n\t\t} else {\n\t\t\topt.lock.select(Some(tab.spot.skip));\n\t\t}\n\n\t\ttab.spot.lock = Some(opt.lock);\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/update_yanked.rs",
    "content": "use anyhow::Result;\nuse yazi_core::mgr::Yanked;\nuse yazi_macro::{render, succ};\nuse yazi_parser::mgr::UpdateYankedOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct UpdateYanked;\n\nimpl Actor for UpdateYanked {\n\ttype Options = UpdateYankedOpt<'static>;\n\n\tconst NAME: &str = \"update_yanked\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tif opt.urls.is_empty() && cx.mgr.yanked.is_empty() {\n\t\t\tsucc!();\n\t\t}\n\n\t\tcx.mgr.yanked = Yanked::new(opt.cut, opt.urls.into_owned());\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/upload.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::succ;\nuse yazi_parser::mgr::UploadOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Upload;\n\nimpl Actor for Upload {\n\ttype Options = UploadOpt;\n\n\tconst NAME: &str = \"upload\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tfor url in opt.urls {\n\t\t\tcx.tasks.scheduler.file_upload(url.into_owned());\n\t\t}\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/visual_mode.rs",
    "content": "use std::collections::BTreeSet;\n\nuse anyhow::Result;\nuse yazi_core::tab::Mode;\nuse yazi_macro::{render, succ};\nuse yazi_parser::mgr::VisualModeOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct VisualMode;\n\nimpl Actor for VisualMode {\n\ttype Options = VisualModeOpt;\n\n\tconst NAME: &str = \"visual_mode\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet tab = cx.tab_mut();\n\n\t\tlet idx = tab.current.cursor;\n\t\tif opt.unset {\n\t\t\ttab.mode = Mode::Unset(idx, BTreeSet::from([idx]));\n\t\t} else {\n\t\t\ttab.mode = Mode::Select(idx, BTreeSet::from([idx]));\n\t\t};\n\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/watch.rs",
    "content": "use std::iter;\n\nuse anyhow::Result;\nuse yazi_macro::succ;\nuse yazi_parser::VoidOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Watch;\n\nimpl Actor for Watch {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"watch\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tlet it = iter::once(cx.core.mgr.tabs.active().cwd())\n\t\t\t.chain(cx.core.mgr.tabs.parent().map(|p| &p.url))\n\t\t\t.chain(cx.core.mgr.tabs.hovered().filter(|h| h.is_dir()).map(|h| &h.url));\n\n\t\tcx.core.mgr.watcher.watch(it);\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/mgr/yank.rs",
    "content": "use anyhow::Result;\nuse yazi_core::mgr::Yanked;\nuse yazi_macro::{act, render};\nuse yazi_parser::mgr::YankOpt;\nuse yazi_shared::{data::Data, url::UrlBufCov};\n\nuse crate::{Actor, Ctx};\n\npub struct Yank;\n\nimpl Actor for Yank {\n\ttype Options = YankOpt;\n\n\tconst NAME: &str = \"yank\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tact!(mgr:escape_visual, cx)?;\n\n\t\tcx.mgr.yanked =\n\t\t\tYanked::new(opt.cut, cx.tab().selected_or_hovered().cloned().map(UrlBufCov).collect());\n\t\trender!(cx.mgr.yanked.catchup_revision(true));\n\n\t\tact!(mgr:escape_select, cx)\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/notify/mod.rs",
    "content": "yazi_macro::mod_flat!(push tick);\n"
  },
  {
    "path": "yazi-actor/src/notify/push.rs",
    "content": "use std::time::Instant;\n\nuse anyhow::Result;\nuse yazi_core::notify::Message;\nuse yazi_dds::spark::SparkKind;\nuse yazi_macro::{act, succ};\nuse yazi_parser::notify::PushOpt;\nuse yazi_shared::{Source, data::Data};\n\nuse crate::{Actor, Ctx};\n\npub struct Push;\n\nimpl Actor for Push {\n\ttype Options = PushOpt;\n\n\tconst NAME: &str = \"push\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet instant = Instant::now();\n\n\t\tlet mut msg = Message::from(opt);\n\t\tmsg.timeout += instant - cx.notify.messages.first().map_or(instant, |m| m.instant);\n\n\t\tif cx.notify.messages.iter().all(|m| m != &msg) {\n\t\t\tcx.notify.messages.push(msg);\n\t\t\tact!(notify:tick, cx)?;\n\t\t}\n\t\tsucc!();\n\t}\n\n\tfn hook(cx: &Ctx, _: &Self::Options) -> Option<SparkKind> {\n\t\tmatch cx.source() {\n\t\t\tSource::Relay => Some(SparkKind::RelayNotifyPush),\n\t\t\t_ => None,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/notify/tick.rs",
    "content": "use std::time::Duration;\n\nuse anyhow::Result;\nuse ratatui::layout::Rect;\nuse yazi_core::notify::Notify;\nuse yazi_emulator::Dimension;\nuse yazi_macro::{render, render_partial, succ};\nuse yazi_parser::notify::TickOpt;\nuse yazi_proxy::NotifyProxy;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Tick;\n\nimpl Actor for Tick {\n\ttype Options = TickOpt;\n\n\tconst NAME: &str = \"tick\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tcx.notify.tick_handle.take().map(|h| h.abort());\n\n\t\tlet Dimension { rows, columns, .. } = Dimension::available();\n\t\tlet area = Notify::available(Rect { x: 0, y: 0, width: columns, height: rows });\n\n\t\tlet limit = cx.notify.limit(area);\n\t\tif limit == 0 {\n\t\t\tsucc!();\n\t\t}\n\n\t\tfor m in &mut cx.notify.messages[..limit] {\n\t\t\tif m.timeout.is_zero() {\n\t\t\t\tm.percent = m.percent.saturating_sub(20);\n\t\t\t} else if m.percent < 100 {\n\t\t\t\tm.percent += 20;\n\t\t\t} else {\n\t\t\t\tm.timeout = m.timeout.saturating_sub(opt.interval);\n\t\t\t}\n\t\t}\n\n\t\tcx.notify.messages.retain(|m| m.percent > 0 || !m.timeout.is_zero());\n\t\tlet limit = cx.notify.limit(area);\n\t\tlet timeouts: Vec<_> = cx.notify.messages[..limit]\n\t\t\t.iter()\n\t\t\t.filter(|&m| m.percent == 100 && !m.timeout.is_zero())\n\t\t\t.map(|m| m.timeout)\n\t\t\t.collect();\n\n\t\tlet interval = if timeouts.len() != limit {\n\t\t\tDuration::from_millis(50)\n\t\t} else if let Some(min) = timeouts.iter().min() {\n\t\t\t*min\n\t\t} else {\n\t\t\tsucc!(render!());\n\t\t};\n\n\t\tcx.notify.tick_handle = Some(tokio::spawn(async move {\n\t\t\ttokio::time::sleep(interval).await;\n\t\t\tNotifyProxy::tick(interval);\n\t\t}));\n\t\tsucc!(render_partial!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/pick/arrow.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{render, succ};\nuse yazi_parser::ArrowOpt;\nuse yazi_shared::data::Data;\nuse yazi_widgets::Scrollable;\n\nuse crate::{Actor, Ctx};\n\npub struct Arrow;\n\nimpl Actor for Arrow {\n\ttype Options = ArrowOpt;\n\n\tconst NAME: &str = \"arrow\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tsucc!(render!(cx.pick.scroll(opt.step)));\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/pick/close.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{render, succ};\nuse yazi_parser::pick::CloseOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Close;\n\nimpl Actor for Close {\n\ttype Options = CloseOpt;\n\n\tconst NAME: &str = \"close\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet pick = &mut cx.pick;\n\t\tif let Some(cb) = pick.callback.take() {\n\t\t\t_ = cb.send(if opt.submit { Some(pick.cursor) } else { None });\n\t\t}\n\n\t\tpick.cursor = 0;\n\t\tpick.offset = 0;\n\t\tpick.visible = false;\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/pick/mod.rs",
    "content": "yazi_macro::mod_flat!(arrow close show);\n"
  },
  {
    "path": "yazi-actor/src/pick/show.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::pick::ShowOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Show;\n\nimpl Actor for Show {\n\ttype Options = ShowOpt;\n\n\tconst NAME: &str = \"show\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tact!(pick:close, cx)?;\n\n\t\tlet pick = &mut cx.pick;\n\t\tpick.title = opt.cfg.title;\n\t\tpick.items = opt.cfg.items;\n\t\tpick.position = opt.cfg.position;\n\n\t\tpick.callback = Some(opt.tx);\n\t\tpick.visible = true;\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/spot/arrow.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::ArrowOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Arrow;\n\nimpl Actor for Arrow {\n\ttype Options = ArrowOpt;\n\n\tconst NAME: &str = \"arrow\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet spot = &mut cx.tab_mut().spot;\n\t\tlet Some(lock) = &mut spot.lock else { succ!() };\n\n\t\tlet new = opt.step.add(spot.skip, lock.len().unwrap_or(u16::MAX as _), 0);\n\t\tlet Some(old) = lock.selected() else {\n\t\t\treturn act!(mgr:spot, cx, new);\n\t\t};\n\n\t\tlock.select(Some(new));\n\t\tlet new = lock.selected().unwrap();\n\n\t\tspot.skip = new;\n\t\tsucc!(render!(new != old));\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/spot/close.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::succ;\nuse yazi_parser::VoidOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Close;\n\nimpl Actor for Close {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"close\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tsucc!(cx.tab_mut().spot.reset());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/spot/copy.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::succ;\nuse yazi_parser::spot::CopyOpt;\nuse yazi_shared::data::Data;\nuse yazi_widgets::CLIPBOARD;\n\nuse crate::{Actor, Ctx};\n\npub struct Copy;\n\nimpl Actor for Copy {\n\ttype Options = CopyOpt;\n\n\tconst NAME: &str = \"copy\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet spot = &cx.tab().spot;\n\t\tlet Some(lock) = &spot.lock else { succ!() };\n\t\tlet Some(table) = lock.table() else { succ!() };\n\n\t\tlet mut s = String::new();\n\t\tmatch opt.r#type.as_ref() {\n\t\t\t\"cell\" => {\n\t\t\t\tlet Some(cell) = table.selected_cell() else { succ!() };\n\t\t\t\ts = cell.to_string();\n\t\t\t}\n\t\t\t\"line\" => {\n\t\t\t\t// TODO\n\t\t\t}\n\t\t\t_ => {}\n\t\t}\n\n\t\tfutures::executor::block_on(CLIPBOARD.set(s));\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/spot/mod.rs",
    "content": "yazi_macro::mod_flat!(arrow close copy swipe);\n"
  },
  {
    "path": "yazi-actor/src/spot/swipe.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::act;\nuse yazi_parser::ArrowOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Swipe;\n\nimpl Actor for Swipe {\n\ttype Options = ArrowOpt;\n\n\tconst NAME: &str = \"swipe\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tact!(mgr:arrow, cx, opt)?;\n\t\tact!(mgr:spot, cx)\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/tasks/arrow.rs",
    "content": "use anyhow::Result;\nuse yazi_core::tasks::Tasks;\nuse yazi_macro::{render, succ};\nuse yazi_parser::ArrowOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Arrow;\n\nimpl Actor for Arrow {\n\ttype Options = ArrowOpt;\n\n\tconst NAME: &str = \"arrow\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tlet tasks = &mut cx.tasks;\n\n\t\tlet old = tasks.cursor;\n\t\ttasks.cursor = opt.step.add(tasks.cursor, tasks.snaps.len(), Tasks::limit());\n\n\t\tsucc!(render!(tasks.cursor != old));\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/tasks/cancel.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::VoidOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Cancel;\n\nimpl Actor for Cancel {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"cancel\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tlet tasks = &mut cx.tasks;\n\n\t\tlet id = tasks.ongoing().lock().get_id(tasks.cursor);\n\t\tif id.map(|id| tasks.scheduler.cancel(id)) != Some(true) {\n\t\t\tsucc!();\n\t\t}\n\n\t\ttasks.snaps = tasks.paginate();\n\t\tact!(tasks:arrow, cx)?;\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/tasks/close.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::VoidOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Close;\n\nimpl Actor for Close {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"close\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tlet tasks = &mut cx.tasks;\n\t\tif !tasks.visible {\n\t\t\tsucc!();\n\t\t}\n\n\t\ttasks.visible = false;\n\t\ttasks.snaps = Vec::new();\n\n\t\tact!(tasks:arrow, cx)?;\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/tasks/inspect.rs",
    "content": "use std::io::Write;\n\nuse anyhow::Result;\nuse crossterm::{execute, terminal::{disable_raw_mode, enable_raw_mode}};\nuse scopeguard::defer;\nuse tokio::{io::{AsyncReadExt, stdin}, select, sync::mpsc, time};\nuse yazi_binding::Permit;\nuse yazi_macro::succ;\nuse yazi_parser::VoidOpt;\nuse yazi_proxy::AppProxy;\nuse yazi_shared::{data::Data, terminal_clear};\nuse yazi_term::YIELD_TO_SUBPROCESS;\nuse yazi_tty::TTY;\n\nuse crate::{Actor, Ctx};\n\npub struct Inspect;\n\nimpl Actor for Inspect {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"inspect\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tlet ongoing = cx.tasks.ongoing().clone();\n\t\tlet Some(id) = ongoing.lock().get_id(cx.tasks.cursor) else {\n\t\t\tsucc!();\n\t\t};\n\n\t\ttokio::spawn(async move {\n\t\t\tlet _permit = Permit::new(YIELD_TO_SUBPROCESS.acquire().await.unwrap(), AppProxy::resume());\n\t\t\tlet (tx, mut rx) = mpsc::unbounded_channel();\n\n\t\t\tlet buffered = {\n\t\t\t\tlet mut ongoing = ongoing.lock();\n\t\t\t\tlet Some(task) = ongoing.get_mut(id) else { return };\n\n\t\t\t\ttask.logger = Some(tx);\n\t\t\t\ttask.logs.clone()\n\t\t\t};\n\n\t\t\tAppProxy::stop().await;\n\t\t\tterminal_clear(TTY.writer()).ok();\n\t\t\tTTY.writer().write_all(buffered.as_bytes()).ok();\n\t\t\tTTY.writer().flush().ok();\n\n\t\t\tdefer! { disable_raw_mode().ok(); }\n\t\t\tenable_raw_mode().ok();\n\n\t\t\tlet mut stdin = stdin(); // TODO: stdin\n\t\t\tlet mut answer = 0;\n\t\t\tloop {\n\t\t\t\tselect! {\n\t\t\t\t\tSome(line) = rx.recv() => {\n\t\t\t\t\t\texecute!(TTY.writer(), crossterm::style::Print(line), crossterm::style::Print(\"\\r\\n\")).ok();\n\t\t\t\t\t}\n\t\t\t\t\t_ = time::sleep(time::Duration::from_millis(500)) => {\n\t\t\t\t\t\tif !ongoing.lock().exists(id) {\n\t\t\t\t\t\t\texecute!(TTY.writer(), crossterm::style::Print(\"Task finished, press `q` to quit\\r\\n\")).ok();\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tresult = stdin.read_u8() => {\n\t\t\t\t\t\tanswer = result.unwrap_or(b'q');\n\t\t\t\t\t\tif answer == b'q' {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif let Some(task) = ongoing.lock().get_mut(id) {\n\t\t\t\ttask.logger = None;\n\t\t\t}\n\t\t\twhile answer != b'q' {\n\t\t\t\tanswer = stdin.read_u8().await.unwrap_or(b'q');\n\t\t\t}\n\t\t});\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/tasks/mod.rs",
    "content": "yazi_macro::mod_flat!(arrow cancel close open_shell_compat inspect process_open show update_succeed);\n"
  },
  {
    "path": "yazi-actor/src/tasks/open_shell_compat.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::succ;\nuse yazi_parser::tasks::ProcessOpenOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct OpenShellCompat;\n\n// TODO: remove\nimpl Actor for OpenShellCompat {\n\ttype Options = ProcessOpenOpt;\n\n\tconst NAME: &str = \"open_shell_compat\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tsucc!(cx.tasks.open_shell_compat(opt));\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/tasks/process_open.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::succ;\nuse yazi_parser::tasks::ProcessOpenOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct ProcessOpen;\n\nimpl Actor for ProcessOpen {\n\ttype Options = ProcessOpenOpt;\n\n\tconst NAME: &str = \"process_open\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tsucc!(cx.tasks.scheduler.process_open(opt));\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/tasks/show.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_parser::VoidOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Show;\n\nimpl Actor for Show {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"show\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tlet tasks = &mut cx.tasks;\n\t\tif tasks.visible {\n\t\t\tsucc!();\n\t\t}\n\n\t\ttasks.visible = true;\n\t\ttasks.snaps = tasks.paginate();\n\n\t\tact!(tasks:arrow, cx)?;\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/tasks/update_succeed.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::succ;\nuse yazi_parser::tasks::UpdateSucceedOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct UpdateSucceed;\n\nimpl Actor for UpdateSucceed {\n\ttype Options = UpdateSucceedOpt;\n\n\tconst NAME: &str = \"update_succeed\";\n\n\tfn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {\n\t\tcx.mgr.watcher.report(opt.urls);\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/which/activate.rs",
    "content": "use anyhow::Result;\nuse yazi_core::which::WhichSorter;\nuse yazi_dds::spark::SparkKind;\nuse yazi_macro::{render, succ};\nuse yazi_parser::which::ActivateOpt;\nuse yazi_shared::{Source, data::Data};\n\nuse crate::{Actor, Ctx};\n\npub struct Activate;\n\nimpl Actor for Activate {\n\ttype Options = ActivateOpt;\n\n\tconst NAME: &str = \"activate\";\n\n\tfn act(cx: &mut Ctx, mut opt: Self::Options) -> Result<Data> {\n\t\topt.cands.retain(|c| c.on.len() > opt.times);\n\t\tWhichSorter::default().sort(&mut opt.cands);\n\n\t\tif opt.cands.is_empty() {\n\t\t\tsucc!();\n\t\t}\n\n\t\tlet which = &mut cx.which;\n\t\twhich.tx = opt.tx;\n\t\twhich.times = opt.times;\n\t\twhich.cands = opt.cands;\n\n\t\twhich.active = true;\n\t\twhich.silent = opt.silent;\n\t\tsucc!(render!());\n\t}\n\n\tfn hook(cx: &Ctx, _opt: &Self::Options) -> Option<SparkKind> {\n\t\tmatch cx.source() {\n\t\t\tSource::Unknown => Some(SparkKind::IndWhichActivate),\n\t\t\t_ => None,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/which/dismiss.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::succ;\nuse yazi_parser::VoidOpt;\nuse yazi_shared::data::Data;\n\nuse crate::{Actor, Ctx};\n\npub struct Dismiss;\n\nimpl Actor for Dismiss {\n\ttype Options = VoidOpt;\n\n\tconst NAME: &str = \"dismiss\";\n\n\tfn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {\n\t\tsucc!(cx.which.dismiss(None));\n\t}\n}\n"
  },
  {
    "path": "yazi-actor/src/which/mod.rs",
    "content": "yazi_macro::mod_flat!(activate dismiss);\n"
  },
  {
    "path": "yazi-adapter/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-adapter\"\ndescription            = \"Yazi image adapter\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[dependencies]\nyazi-config   = { path = \"../yazi-config\", version = \"26.2.2\" }\nyazi-emulator = { path = \"../yazi-emulator\", version = \"26.2.2\" }\nyazi-fs       = { path = \"../yazi-fs\", version = \"26.2.2\" }\nyazi-macro    = { path = \"../yazi-macro\", version = \"26.2.2\" }\nyazi-shared   = { path = \"../yazi-shared\", version = \"26.2.2\" }\nyazi-tty      = { path = \"../yazi-tty\", version = \"26.2.2\" }\n\n# External dependencies\nansi-to-tui = { workspace = true }\nanyhow      = { workspace = true }\nbase64      = { workspace = true }\ncrossterm   = { workspace = true }\nimage       = { version = \"0.25.10\", default-features = false, features = [ \"avif\", \"bmp\", \"dds\", \"exr\", \"ff\", \"gif\", \"hdr\", \"ico\", \"jpeg\", \"png\", \"pnm\", \"qoi\", \"tga\", \"tiff\", \"webp\" ] }\nmoxcms      = \"0.8.1\"\npalette     = { version = \"0.7.6\", default-features = false }\nquantette   = { version = \"0.5.1\", default-features = false }\nratatui     = { workspace = true }\ntokio       = { workspace = true }\ntracing     = { workspace = true }\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\ncrossterm = { workspace = true, features = [ \"use-dev-tty\", \"libc\" ] }\n"
  },
  {
    "path": "yazi-adapter/README.md",
    "content": "# yazi-adapter\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-adapter/src/adapter.rs",
    "content": "use std::{env, fmt::Display, path::PathBuf};\n\nuse anyhow::Result;\nuse ratatui::layout::Rect;\nuse tracing::warn;\nuse yazi_emulator::{Emulator, TMUX};\nuse yazi_shared::env_exists;\n\nuse crate::{Adapters, SHOWN, drivers};\n\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]\npub enum Adapter {\n\tKgp,\n\tKgpOld,\n\tIip,\n\tSixel,\n\n\t// Supported by Überzug++\n\tX11,\n\tWayland,\n\tChafa,\n}\n\nimpl Display for Adapter {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tmatch self {\n\t\t\tSelf::Kgp => write!(f, \"kgp\"),\n\t\t\tSelf::KgpOld => write!(f, \"kgp-old\"),\n\t\t\tSelf::Iip => write!(f, \"iip\"),\n\t\t\tSelf::Sixel => write!(f, \"sixel\"),\n\t\t\tSelf::X11 => write!(f, \"x11\"),\n\t\t\tSelf::Wayland => write!(f, \"wayland\"),\n\t\t\tSelf::Chafa => write!(f, \"chafa\"),\n\t\t}\n\t}\n}\n\nimpl Adapter {\n\tpub async fn image_show<P>(self, path: P, max: Rect) -> Result<Rect>\n\twhere\n\t\tP: Into<PathBuf>,\n\t{\n\t\tif max.is_empty() {\n\t\t\treturn Ok(Rect::default());\n\t\t}\n\n\t\tlet path = path.into();\n\t\tmatch self {\n\t\t\tSelf::Kgp => drivers::Kgp::image_show(path, max).await,\n\t\t\tSelf::KgpOld => drivers::KgpOld::image_show(path, max).await,\n\t\t\tSelf::Iip => drivers::Iip::image_show(path, max).await,\n\t\t\tSelf::Sixel => drivers::Sixel::image_show(path, max).await,\n\t\t\tSelf::X11 | Self::Wayland => drivers::Ueberzug::image_show(path, max).await,\n\t\t\tSelf::Chafa => drivers::Chafa::image_show(path, max).await,\n\t\t}\n\t}\n\n\tpub fn image_hide(self) -> Result<()> {\n\t\tif let Some(area) = SHOWN.replace(None) { self.image_erase(area) } else { Ok(()) }\n\t}\n\n\tpub fn image_erase(self, area: Rect) -> Result<()> {\n\t\tmatch self {\n\t\t\tSelf::Kgp => drivers::Kgp::image_erase(area),\n\t\t\tSelf::KgpOld => drivers::KgpOld::image_erase(area),\n\t\t\tSelf::Iip => drivers::Iip::image_erase(area),\n\t\t\tSelf::Sixel => drivers::Sixel::image_erase(area),\n\t\t\tSelf::X11 | Self::Wayland => drivers::Ueberzug::image_erase(area),\n\t\t\tSelf::Chafa => drivers::Chafa::image_erase(area),\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn shown_load(self) -> Option<Rect> { SHOWN.get() }\n\n\t#[inline]\n\tpub(super) fn shown_store(area: Rect) { SHOWN.set(Some(area)); }\n\n\tpub(super) fn start(self) { drivers::Ueberzug::start(self); }\n\n\t#[inline]\n\tpub(super) fn needs_ueberzug(self) -> bool {\n\t\t!matches!(self, Self::Kgp | Self::KgpOld | Self::Iip | Self::Sixel)\n\t}\n}\n\nimpl Adapter {\n\tpub fn matches(emulator: &Emulator) -> Self {\n\t\tlet mut adapters: Adapters = emulator.into();\n\t\tif env_exists(\"ZELLIJ_SESSION_NAME\") {\n\t\t\tadapters.retain(|p| *p == Self::Sixel);\n\t\t} else if TMUX.get() {\n\t\t\tadapters.retain(|p| *p != Self::KgpOld);\n\t\t}\n\t\tif let Some(p) = adapters.first() {\n\t\t\treturn *p;\n\t\t}\n\n\t\tlet supported_compositor = drivers::Ueberzug::supported_compositor();\n\t\tmatch env::var(\"XDG_SESSION_TYPE\").unwrap_or_default().as_str() {\n\t\t\t\"x11\" => return Self::X11,\n\t\t\t\"wayland\" if supported_compositor => return Self::Wayland,\n\t\t\t\"wayland\" if !supported_compositor => return Self::Chafa,\n\t\t\t_ => warn!(\"[Adapter] Could not identify XDG_SESSION_TYPE\"),\n\t\t}\n\t\tif env_exists(\"WAYLAND_DISPLAY\") {\n\t\t\treturn if supported_compositor { Self::Wayland } else { Self::Chafa };\n\t\t}\n\t\tmatch env::var(\"DISPLAY\").unwrap_or_default().as_str() {\n\t\t\ts if !s.is_empty() && !s.contains(\"/org.xquartz\") => return Self::X11,\n\t\t\t_ => {}\n\t\t}\n\n\t\twarn!(\"[Adapter] Falling back to chafa\");\n\t\tSelf::Chafa\n\t}\n}\n"
  },
  {
    "path": "yazi-adapter/src/adapters.rs",
    "content": "use std::ops::{Deref, DerefMut};\n\nuse crate::Adapter;\n\npub(super) struct Adapters(Vec<Adapter>);\n\nimpl Deref for Adapters {\n\ttype Target = Vec<Adapter>;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl DerefMut for Adapters {\n\tfn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }\n}\n\nimpl From<&yazi_emulator::Emulator> for Adapters {\n\tfn from(value: &yazi_emulator::Emulator) -> Self { value.kind.either_into() }\n}\n\nimpl From<yazi_emulator::Brand> for Adapters {\n\tfn from(value: yazi_emulator::Brand) -> Self {\n\t\tuse yazi_emulator::Brand as B;\n\n\t\tuse crate::Adapter as A;\n\n\t\tSelf(match value {\n\t\t\tB::Kitty => vec![A::Kgp],\n\t\t\tB::Konsole => vec![A::KgpOld],\n\t\t\tB::Iterm2 => vec![A::Iip, A::Sixel],\n\t\t\tB::WezTerm => vec![A::Iip, A::Sixel],\n\t\t\tB::Foot => vec![A::Sixel],\n\t\t\tB::Ghostty => vec![A::Kgp],\n\t\t\tB::Microsoft => vec![A::Sixel],\n\t\t\tB::Warp => vec![A::Iip, A::KgpOld],\n\t\t\tB::Rio => vec![A::Iip, A::Sixel],\n\t\t\tB::BlackBox => vec![A::Sixel],\n\t\t\tB::VSCode => vec![A::Iip, A::Sixel],\n\t\t\tB::Tabby => vec![A::Iip, A::Sixel],\n\t\t\tB::Hyper => vec![A::Iip, A::Sixel],\n\t\t\tB::Mintty => vec![A::Iip],\n\t\t\tB::Tmux => vec![],\n\t\t\tB::VTerm => vec![],\n\t\t\tB::Apple => vec![],\n\t\t\tB::Urxvt => vec![],\n\t\t\tB::Bobcat => vec![A::Iip, A::Sixel],\n\t\t})\n\t}\n}\n\nimpl From<yazi_emulator::Unknown> for Adapters {\n\tfn from(value: yazi_emulator::Unknown) -> Self {\n\t\tuse Adapter as A;\n\n\t\tSelf(match (value.kgp, value.sixel) {\n\t\t\t(true, true) => vec![A::Sixel, A::KgpOld],\n\t\t\t(true, false) => vec![A::KgpOld],\n\t\t\t(false, true) => vec![A::Sixel],\n\t\t\t(false, false) => vec![],\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-adapter/src/drivers/chafa.rs",
    "content": "use std::{io::Write, path::PathBuf, process::Stdio};\n\nuse ansi_to_tui::IntoText;\nuse anyhow::{Result, anyhow, bail};\nuse crossterm::{cursor::MoveTo, queue};\nuse ratatui::layout::Rect;\nuse tokio::process::Command;\nuse yazi_emulator::Emulator;\n\nuse crate::Adapter;\n\npub(crate) struct Chafa;\n\nimpl Chafa {\n\tpub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> {\n\t\tlet child = Command::new(\"chafa\")\n\t\t\t.args([\n\t\t\t\t\"-f\",\n\t\t\t\t\"symbols\",\n\t\t\t\t\"--relative\",\n\t\t\t\t\"off\",\n\t\t\t\t\"--probe\",\n\t\t\t\t\"off\",\n\t\t\t\t\"--polite\",\n\t\t\t\t\"on\",\n\t\t\t\t\"--passthrough\",\n\t\t\t\t\"none\",\n\t\t\t\t\"--animate\",\n\t\t\t\t\"off\",\n\t\t\t\t\"--view-size\",\n\t\t\t])\n\t\t\t.arg(format!(\"{}x{}\", max.width, max.height))\n\t\t\t.arg(path)\n\t\t\t.stdin(Stdio::null())\n\t\t\t.stdout(Stdio::piped())\n\t\t\t.stderr(Stdio::null())\n\t\t\t.kill_on_drop(true)\n\t\t\t.spawn()\n\t\t\t.map_err(|e| anyhow!(\"failed to spawn chafa: {e}\"))?;\n\n\t\tlet output = child.wait_with_output().await?;\n\t\tif !output.status.success() {\n\t\t\tbail!(\"chafa failed with status: {}\", output.status);\n\t\t} else if output.stdout.is_empty() {\n\t\t\tbail!(\"chafa returned no output\");\n\t\t}\n\n\t\tlet lines: Vec<_> = output.stdout.split(|&b| b == b'\\n').collect();\n\t\tlet Ok(Some(first)) = lines[0].to_text().map(|mut t| t.lines.pop()) else {\n\t\t\tbail!(\"failed to parse chafa output\");\n\t\t};\n\n\t\tlet area = Rect {\n\t\t\tx:      max.x,\n\t\t\ty:      max.y,\n\t\t\twidth:  first.width() as u16,\n\t\t\theight: lines.len() as u16,\n\t\t};\n\n\t\tAdapter::Chafa.image_hide()?;\n\t\tAdapter::shown_store(area);\n\t\tEmulator::move_lock((max.x, max.y), |w| {\n\t\t\tfor (i, line) in lines.into_iter().enumerate() {\n\t\t\t\tw.write_all(line)?;\n\t\t\t\tqueue!(w, MoveTo(max.x, max.y + i as u16 + 1))?;\n\t\t\t}\n\t\t\tOk(area)\n\t\t})\n\t}\n\n\tpub(crate) fn image_erase(area: Rect) -> Result<()> {\n\t\tlet s = \" \".repeat(area.width as usize);\n\t\tEmulator::move_lock((0, 0), |w| {\n\t\t\tfor y in area.top()..area.bottom() {\n\t\t\t\tqueue!(w, MoveTo(area.x, y))?;\n\t\t\t\twrite!(w, \"{s}\")?;\n\t\t\t}\n\t\t\tOk(())\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-adapter/src/drivers/iip.rs",
    "content": "use std::{fmt::Write, io::Write as ioWrite, path::PathBuf};\n\nuse anyhow::Result;\nuse base64::{Engine, engine::{Config, general_purpose::STANDARD}};\nuse crossterm::{cursor::MoveTo, queue};\nuse image::{DynamicImage, ExtendedColorType, ImageEncoder, codecs::{jpeg::JpegEncoder, png::PngEncoder}};\nuse ratatui::layout::Rect;\nuse yazi_config::YAZI;\nuse yazi_emulator::{CLOSE, Emulator, START};\n\nuse crate::{Image, adapter::Adapter};\n\npub(crate) struct Iip;\n\nimpl Iip {\n\tpub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> {\n\t\tlet img = Image::downscale(path, max).await?;\n\t\tlet area = Image::pixel_area((img.width(), img.height()), max);\n\t\tlet b = Self::encode(img).await?;\n\n\t\tAdapter::Iip.image_hide()?;\n\t\tAdapter::shown_store(area);\n\t\tEmulator::move_lock((max.x, max.y), |w| {\n\t\t\tw.write_all(&b)?;\n\t\t\tOk(area)\n\t\t})\n\t}\n\n\tpub(crate) fn image_erase(area: Rect) -> Result<()> {\n\t\tlet s = \" \".repeat(area.width as usize);\n\t\tEmulator::move_lock((0, 0), |w| {\n\t\t\tfor y in area.top()..area.bottom() {\n\t\t\t\tqueue!(w, MoveTo(area.x, y))?;\n\t\t\t\twrite!(w, \"{s}\")?;\n\t\t\t}\n\t\t\tOk(())\n\t\t})\n\t}\n\n\tasync fn encode(img: DynamicImage) -> Result<Vec<u8>> {\n\t\ttokio::task::spawn_blocking(move || {\n\t\t\tlet (w, h) = (img.width(), img.height());\n\n\t\t\tlet mut b = vec![];\n\t\t\tif img.color().has_alpha() {\n\t\t\t\tPngEncoder::new(&mut b).write_image(&img.into_rgba8(), w, h, ExtendedColorType::Rgba8)?;\n\t\t\t} else {\n\t\t\t\tJpegEncoder::new_with_quality(&mut b, YAZI.preview.image_quality).encode_image(&img)?;\n\t\t\t};\n\n\t\t\tlet mut buf = String::with_capacity(\n\t\t\t\t200 + base64::encoded_len(b.len(), STANDARD.config().encode_padding()).unwrap_or(0),\n\t\t\t);\n\n\t\t\twrite!(\n\t\t\t\tbuf,\n\t\t\t\t\"{START}]1337;File=inline=1;size={};width={w}px;height={h}px;doNotMoveCursor=1:\",\n\t\t\t\tb.len(),\n\t\t\t)?;\n\t\t\tSTANDARD.encode_string(b, &mut buf);\n\t\t\twrite!(buf, \"\\x07{CLOSE}\")?;\n\n\t\t\tOk(buf.into_bytes())\n\t\t})\n\t\t.await?\n\t}\n}\n"
  },
  {
    "path": "yazi-adapter/src/drivers/kgp.rs",
    "content": "use core::str;\nuse std::{io::Write, path::PathBuf};\n\nuse anyhow::Result;\nuse base64::{Engine, engine::general_purpose};\nuse crossterm::{cursor::MoveTo, queue};\nuse image::DynamicImage;\nuse ratatui::layout::Rect;\nuse yazi_emulator::{CLOSE, ESCAPE, Emulator, START};\nuse yazi_shared::SyncCell;\n\nuse crate::{adapter::Adapter, image::Image};\n\nstatic DIACRITICS: [char; 297] = [\n\t'\\u{0305}',\n\t'\\u{030D}',\n\t'\\u{030E}',\n\t'\\u{0310}',\n\t'\\u{0312}',\n\t'\\u{033D}',\n\t'\\u{033E}',\n\t'\\u{033F}',\n\t'\\u{0346}',\n\t'\\u{034A}',\n\t'\\u{034B}',\n\t'\\u{034C}',\n\t'\\u{0350}',\n\t'\\u{0351}',\n\t'\\u{0352}',\n\t'\\u{0357}',\n\t'\\u{035B}',\n\t'\\u{0363}',\n\t'\\u{0364}',\n\t'\\u{0365}',\n\t'\\u{0366}',\n\t'\\u{0367}',\n\t'\\u{0368}',\n\t'\\u{0369}',\n\t'\\u{036A}',\n\t'\\u{036B}',\n\t'\\u{036C}',\n\t'\\u{036D}',\n\t'\\u{036E}',\n\t'\\u{036F}',\n\t'\\u{0483}',\n\t'\\u{0484}',\n\t'\\u{0485}',\n\t'\\u{0486}',\n\t'\\u{0487}',\n\t'\\u{0592}',\n\t'\\u{0593}',\n\t'\\u{0594}',\n\t'\\u{0595}',\n\t'\\u{0597}',\n\t'\\u{0598}',\n\t'\\u{0599}',\n\t'\\u{059C}',\n\t'\\u{059D}',\n\t'\\u{059E}',\n\t'\\u{059F}',\n\t'\\u{05A0}',\n\t'\\u{05A1}',\n\t'\\u{05A8}',\n\t'\\u{05A9}',\n\t'\\u{05AB}',\n\t'\\u{05AC}',\n\t'\\u{05AF}',\n\t'\\u{05C4}',\n\t'\\u{0610}',\n\t'\\u{0611}',\n\t'\\u{0612}',\n\t'\\u{0613}',\n\t'\\u{0614}',\n\t'\\u{0615}',\n\t'\\u{0616}',\n\t'\\u{0617}',\n\t'\\u{0657}',\n\t'\\u{0658}',\n\t'\\u{0659}',\n\t'\\u{065A}',\n\t'\\u{065B}',\n\t'\\u{065D}',\n\t'\\u{065E}',\n\t'\\u{06D6}',\n\t'\\u{06D7}',\n\t'\\u{06D8}',\n\t'\\u{06D9}',\n\t'\\u{06DA}',\n\t'\\u{06DB}',\n\t'\\u{06DC}',\n\t'\\u{06DF}',\n\t'\\u{06E0}',\n\t'\\u{06E1}',\n\t'\\u{06E2}',\n\t'\\u{06E4}',\n\t'\\u{06E7}',\n\t'\\u{06E8}',\n\t'\\u{06EB}',\n\t'\\u{06EC}',\n\t'\\u{0730}',\n\t'\\u{0732}',\n\t'\\u{0733}',\n\t'\\u{0735}',\n\t'\\u{0736}',\n\t'\\u{073A}',\n\t'\\u{073D}',\n\t'\\u{073F}',\n\t'\\u{0740}',\n\t'\\u{0741}',\n\t'\\u{0743}',\n\t'\\u{0745}',\n\t'\\u{0747}',\n\t'\\u{0749}',\n\t'\\u{074A}',\n\t'\\u{07EB}',\n\t'\\u{07EC}',\n\t'\\u{07ED}',\n\t'\\u{07EE}',\n\t'\\u{07EF}',\n\t'\\u{07F0}',\n\t'\\u{07F1}',\n\t'\\u{07F3}',\n\t'\\u{0816}',\n\t'\\u{0817}',\n\t'\\u{0818}',\n\t'\\u{0819}',\n\t'\\u{081B}',\n\t'\\u{081C}',\n\t'\\u{081D}',\n\t'\\u{081E}',\n\t'\\u{081F}',\n\t'\\u{0820}',\n\t'\\u{0821}',\n\t'\\u{0822}',\n\t'\\u{0823}',\n\t'\\u{0825}',\n\t'\\u{0826}',\n\t'\\u{0827}',\n\t'\\u{0829}',\n\t'\\u{082A}',\n\t'\\u{082B}',\n\t'\\u{082C}',\n\t'\\u{082D}',\n\t'\\u{0951}',\n\t'\\u{0953}',\n\t'\\u{0954}',\n\t'\\u{0F82}',\n\t'\\u{0F83}',\n\t'\\u{0F86}',\n\t'\\u{0F87}',\n\t'\\u{135D}',\n\t'\\u{135E}',\n\t'\\u{135F}',\n\t'\\u{17DD}',\n\t'\\u{193A}',\n\t'\\u{1A17}',\n\t'\\u{1A75}',\n\t'\\u{1A76}',\n\t'\\u{1A77}',\n\t'\\u{1A78}',\n\t'\\u{1A79}',\n\t'\\u{1A7A}',\n\t'\\u{1A7B}',\n\t'\\u{1A7C}',\n\t'\\u{1B6B}',\n\t'\\u{1B6D}',\n\t'\\u{1B6E}',\n\t'\\u{1B6F}',\n\t'\\u{1B70}',\n\t'\\u{1B71}',\n\t'\\u{1B72}',\n\t'\\u{1B73}',\n\t'\\u{1CD0}',\n\t'\\u{1CD1}',\n\t'\\u{1CD2}',\n\t'\\u{1CDA}',\n\t'\\u{1CDB}',\n\t'\\u{1CE0}',\n\t'\\u{1DC0}',\n\t'\\u{1DC1}',\n\t'\\u{1DC3}',\n\t'\\u{1DC4}',\n\t'\\u{1DC5}',\n\t'\\u{1DC6}',\n\t'\\u{1DC7}',\n\t'\\u{1DC8}',\n\t'\\u{1DC9}',\n\t'\\u{1DCB}',\n\t'\\u{1DCC}',\n\t'\\u{1DD1}',\n\t'\\u{1DD2}',\n\t'\\u{1DD3}',\n\t'\\u{1DD4}',\n\t'\\u{1DD5}',\n\t'\\u{1DD6}',\n\t'\\u{1DD7}',\n\t'\\u{1DD8}',\n\t'\\u{1DD9}',\n\t'\\u{1DDA}',\n\t'\\u{1DDB}',\n\t'\\u{1DDC}',\n\t'\\u{1DDD}',\n\t'\\u{1DDE}',\n\t'\\u{1DDF}',\n\t'\\u{1DE0}',\n\t'\\u{1DE1}',\n\t'\\u{1DE2}',\n\t'\\u{1DE3}',\n\t'\\u{1DE4}',\n\t'\\u{1DE5}',\n\t'\\u{1DE6}',\n\t'\\u{1DFE}',\n\t'\\u{20D0}',\n\t'\\u{20D1}',\n\t'\\u{20D4}',\n\t'\\u{20D5}',\n\t'\\u{20D6}',\n\t'\\u{20D7}',\n\t'\\u{20DB}',\n\t'\\u{20DC}',\n\t'\\u{20E1}',\n\t'\\u{20E7}',\n\t'\\u{20E9}',\n\t'\\u{20F0}',\n\t'\\u{2CEF}',\n\t'\\u{2CF0}',\n\t'\\u{2CF1}',\n\t'\\u{2DE0}',\n\t'\\u{2DE1}',\n\t'\\u{2DE2}',\n\t'\\u{2DE3}',\n\t'\\u{2DE4}',\n\t'\\u{2DE5}',\n\t'\\u{2DE6}',\n\t'\\u{2DE7}',\n\t'\\u{2DE8}',\n\t'\\u{2DE9}',\n\t'\\u{2DEA}',\n\t'\\u{2DEB}',\n\t'\\u{2DEC}',\n\t'\\u{2DED}',\n\t'\\u{2DEE}',\n\t'\\u{2DEF}',\n\t'\\u{2DF0}',\n\t'\\u{2DF1}',\n\t'\\u{2DF2}',\n\t'\\u{2DF3}',\n\t'\\u{2DF4}',\n\t'\\u{2DF5}',\n\t'\\u{2DF6}',\n\t'\\u{2DF7}',\n\t'\\u{2DF8}',\n\t'\\u{2DF9}',\n\t'\\u{2DFA}',\n\t'\\u{2DFB}',\n\t'\\u{2DFC}',\n\t'\\u{2DFD}',\n\t'\\u{2DFE}',\n\t'\\u{2DFF}',\n\t'\\u{A66F}',\n\t'\\u{A67C}',\n\t'\\u{A67D}',\n\t'\\u{A6F0}',\n\t'\\u{A6F1}',\n\t'\\u{A8E0}',\n\t'\\u{A8E1}',\n\t'\\u{A8E2}',\n\t'\\u{A8E3}',\n\t'\\u{A8E4}',\n\t'\\u{A8E5}',\n\t'\\u{A8E6}',\n\t'\\u{A8E7}',\n\t'\\u{A8E8}',\n\t'\\u{A8E9}',\n\t'\\u{A8EA}',\n\t'\\u{A8EB}',\n\t'\\u{A8EC}',\n\t'\\u{A8ED}',\n\t'\\u{A8EE}',\n\t'\\u{A8EF}',\n\t'\\u{A8F0}',\n\t'\\u{A8F1}',\n\t'\\u{AAB0}',\n\t'\\u{AAB2}',\n\t'\\u{AAB3}',\n\t'\\u{AAB7}',\n\t'\\u{AAB8}',\n\t'\\u{AABE}',\n\t'\\u{AABF}',\n\t'\\u{AAC1}',\n\t'\\u{FE20}',\n\t'\\u{FE21}',\n\t'\\u{FE22}',\n\t'\\u{FE23}',\n\t'\\u{FE24}',\n\t'\\u{FE25}',\n\t'\\u{FE26}',\n\t'\\u{10A0F}',\n\t'\\u{10A38}',\n\t'\\u{1D185}',\n\t'\\u{1D186}',\n\t'\\u{1D187}',\n\t'\\u{1D188}',\n\t'\\u{1D189}',\n\t'\\u{1D1AA}',\n\t'\\u{1D1AB}',\n\t'\\u{1D1AC}',\n\t'\\u{1D1AD}',\n\t'\\u{1D242}',\n\t'\\u{1D243}',\n\t'\\u{1D244}',\n];\n\npub(crate) struct Kgp;\n\nimpl Kgp {\n\tpub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> {\n\t\tlet img = Image::downscale(path, max).await?;\n\t\tlet area = Image::pixel_area((img.width(), img.height()), max);\n\n\t\tlet b1 = Self::encode(img).await?;\n\t\tlet b2 = Self::place(&area)?;\n\n\t\tAdapter::Kgp.image_hide()?;\n\t\tAdapter::shown_store(area);\n\t\tEmulator::move_lock((area.x, area.y), |w| {\n\t\t\tw.write_all(&b1)?;\n\t\t\tw.write_all(&b2)?;\n\t\t\tOk(area)\n\t\t})\n\t}\n\n\tpub(crate) fn image_erase(area: Rect) -> Result<()> {\n\t\tlet s = \" \".repeat(area.width as usize);\n\t\tEmulator::move_lock((0, 0), |w| {\n\t\t\tfor y in area.top()..area.bottom() {\n\t\t\t\tqueue!(w, MoveTo(area.x, y))?;\n\t\t\t\twrite!(w, \"{s}\")?;\n\t\t\t}\n\n\t\t\twrite!(w, \"{START}_Gq=2,a=d,d=A{ESCAPE}\\\\{CLOSE}\")?;\n\t\t\tOk(())\n\t\t})\n\t}\n\n\tasync fn encode(img: DynamicImage) -> Result<Vec<u8>> {\n\t\tfn output(raw: &[u8], format: u8, size: (u32, u32)) -> Result<Vec<u8>> {\n\t\t\tlet b64 = general_purpose::STANDARD.encode(raw).into_bytes();\n\n\t\t\tlet mut it = b64.chunks(4096).peekable();\n\t\t\tlet mut buf = Vec::with_capacity(b64.len() + it.len() * 50);\n\t\t\tif let Some(first) = it.next() {\n\t\t\t\twrite!(\n\t\t\t\t\tbuf,\n\t\t\t\t\t\"{START}_Gq=2,a=T,C=1,U=1,f={format},s={},v={},i={},m={};{}{ESCAPE}\\\\{CLOSE}\",\n\t\t\t\t\tsize.0,\n\t\t\t\t\tsize.1,\n\t\t\t\t\tKgp::image_id(),\n\t\t\t\t\tit.peek().is_some() as u8,\n\t\t\t\t\tunsafe { str::from_utf8_unchecked(first) },\n\t\t\t\t)?;\n\t\t\t}\n\n\t\t\twhile let Some(chunk) = it.next() {\n\t\t\t\twrite!(buf, \"{START}_Gm={};{}{ESCAPE}\\\\{CLOSE}\", it.peek().is_some() as u8, unsafe {\n\t\t\t\t\tstr::from_utf8_unchecked(chunk)\n\t\t\t\t})?;\n\t\t\t}\n\n\t\t\twrite!(buf, \"{CLOSE}\")?;\n\t\t\tOk(buf)\n\t\t}\n\n\t\tlet size = (img.width(), img.height());\n\t\ttokio::task::spawn_blocking(move || match img {\n\t\t\tDynamicImage::ImageRgb8(v) => output(v.as_raw(), 24, size),\n\t\t\tDynamicImage::ImageRgba8(v) => output(v.as_raw(), 32, size),\n\t\t\tv => output(v.into_rgb8().as_raw(), 24, size),\n\t\t})\n\t\t.await?\n\t}\n\n\tfn place(area: &Rect) -> Result<Vec<u8>> {\n\t\tlet mut buf = Vec::with_capacity(area.width as usize * area.height as usize * 3 + 50);\n\n\t\tlet id = Self::image_id();\n\t\tlet (r, g, b) = ((id >> 16) & 0xff, (id >> 8) & 0xff, id & 0xff);\n\t\twrite!(buf, \"\\x1b[38;2;{r};{g};{b}m\")?;\n\n\t\tfor y in 0..area.height {\n\t\t\twrite!(buf, \"\\x1b[{};{}H\", area.y + y + 1, area.x + 1)?;\n\t\t\tfor x in 0..area.width {\n\t\t\t\twrite!(buf, \"\\u{10EEEE}\")?;\n\t\t\t\twrite!(buf, \"{}\", *DIACRITICS.get(y as usize).unwrap_or(&DIACRITICS[0]))?;\n\t\t\t\twrite!(buf, \"{}\", *DIACRITICS.get(x as usize).unwrap_or(&DIACRITICS[0]))?;\n\t\t\t}\n\t\t}\n\t\tOk(buf)\n\t}\n\n\tfn image_id() -> u32 {\n\t\tstatic CACHE: SyncCell<Option<u32>> = SyncCell::new(None);\n\t\tmatch CACHE.get() {\n\t\t\tSome(n) => n,\n\t\t\tNone => {\n\t\t\t\tlet n = std::process::id() % (0xffffff + 1);\n\t\t\t\tCACHE.set(Some(n));\n\t\t\t\tn\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-adapter/src/drivers/kgp_old.rs",
    "content": "use core::str;\nuse std::{io::Write, path::PathBuf};\n\nuse anyhow::Result;\nuse base64::{Engine, engine::general_purpose};\nuse image::DynamicImage;\nuse ratatui::layout::Rect;\nuse yazi_emulator::{CLOSE, ESCAPE, Emulator, START};\nuse yazi_tty::TTY;\n\nuse crate::{Image, adapter::Adapter};\n\npub(crate) struct KgpOld;\n\nimpl KgpOld {\n\tpub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> {\n\t\tlet img = Image::downscale(path, max).await?;\n\t\tlet area = Image::pixel_area((img.width(), img.height()), max);\n\t\tlet b = Self::encode(img).await?;\n\n\t\tAdapter::KgpOld.image_hide()?;\n\t\tAdapter::shown_store(area);\n\t\tEmulator::move_lock((area.x, area.y), |w| {\n\t\t\tw.write_all(&b)?;\n\t\t\tOk(area)\n\t\t})\n\t}\n\n\tpub(crate) fn image_erase(_: Rect) -> Result<()> {\n\t\tlet mut w = TTY.lockout();\n\t\twrite!(w, \"{START}_Gq=2,a=d,d=A{ESCAPE}\\\\{CLOSE}\")?;\n\t\tw.flush()?;\n\t\tOk(())\n\t}\n\n\tasync fn encode(img: DynamicImage) -> Result<Vec<u8>> {\n\t\tfn output(raw: &[u8], format: u8, size: (u32, u32)) -> Result<Vec<u8>> {\n\t\t\tlet b64 = general_purpose::STANDARD.encode(raw).into_bytes();\n\n\t\t\tlet mut it = b64.chunks(4096).peekable();\n\t\t\tlet mut buf = Vec::with_capacity(b64.len() + it.len() * 50);\n\t\t\tif let Some(first) = it.next() {\n\t\t\t\twrite!(\n\t\t\t\t\tbuf,\n\t\t\t\t\t\"{START}_Gq=2,a=T,z=-1,C=1,f={format},s={},v={},m={};{}{ESCAPE}\\\\{CLOSE}\",\n\t\t\t\t\tsize.0,\n\t\t\t\t\tsize.1,\n\t\t\t\t\tit.peek().is_some() as u8,\n\t\t\t\t\tunsafe { str::from_utf8_unchecked(first) },\n\t\t\t\t)?;\n\t\t\t}\n\n\t\t\twhile let Some(chunk) = it.next() {\n\t\t\t\twrite!(buf, \"{START}_Gm={};{}{ESCAPE}\\\\{CLOSE}\", it.peek().is_some() as u8, unsafe {\n\t\t\t\t\tstr::from_utf8_unchecked(chunk)\n\t\t\t\t})?;\n\t\t\t}\n\n\t\t\twrite!(buf, \"{CLOSE}\")?;\n\t\t\tOk(buf)\n\t\t}\n\n\t\tlet size = (img.width(), img.height());\n\t\ttokio::task::spawn_blocking(move || match img {\n\t\t\tDynamicImage::ImageRgb8(v) => output(v.as_raw(), 24, size),\n\t\t\tDynamicImage::ImageRgba8(v) => output(v.as_raw(), 32, size),\n\t\t\tv => output(v.into_rgb8().as_raw(), 24, size),\n\t\t})\n\t\t.await?\n\t}\n}\n"
  },
  {
    "path": "yazi-adapter/src/drivers/mod.rs",
    "content": "yazi_macro::mod_flat!(chafa iip kgp kgp_old sixel ueberzug);\n"
  },
  {
    "path": "yazi-adapter/src/drivers/sixel.rs",
    "content": "use std::{io::Write, path::PathBuf};\n\nuse anyhow::{Result, bail};\nuse crossterm::{cursor::MoveTo, queue};\nuse image::{DynamicImage, GenericImageView, RgbImage};\nuse palette::{Srgb, cast::ComponentsAs};\nuse quantette::{PaletteSize, color_map::IndexedColorMap, wu::{BinnerU8x3, WuU8x3}};\nuse ratatui::layout::Rect;\nuse yazi_emulator::{CLOSE, ESCAPE, Emulator, START};\n\nuse crate::{Image, adapter::Adapter};\n\npub(crate) struct Sixel;\n\nstruct QuantizeOutput<T> {\n\tindices: Vec<u8>,\n\tpalette: Vec<T>,\n}\n\nimpl Sixel {\n\tpub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> {\n\t\tlet img = Image::downscale(path, max).await?;\n\t\tlet area = Image::pixel_area((img.width(), img.height()), max);\n\t\tlet b = Self::encode(img).await?;\n\n\t\tAdapter::Sixel.image_hide()?;\n\t\tAdapter::shown_store(area);\n\t\tEmulator::move_lock((area.x, area.y), |w| {\n\t\t\tw.write_all(&b)?;\n\t\t\tOk(area)\n\t\t})\n\t}\n\n\tpub(crate) fn image_erase(area: Rect) -> Result<()> {\n\t\tlet s = \" \".repeat(area.width as usize);\n\t\tEmulator::move_lock((0, 0), |w| {\n\t\t\tfor y in area.top()..area.bottom() {\n\t\t\t\tqueue!(w, MoveTo(area.x, y))?;\n\t\t\t\twrite!(w, \"{s}\")?;\n\t\t\t}\n\t\t\tOk(())\n\t\t})\n\t}\n\n\tasync fn encode(img: DynamicImage) -> Result<Vec<u8>> {\n\t\tlet alpha = img.color().has_alpha();\n\t\tif img.width() == 0 || img.height() == 0 {\n\t\t\tbail!(\"image is empty\");\n\t\t}\n\n\t\tlet (qo, img) = tokio::task::spawn_blocking(move || match &img {\n\t\t\tDynamicImage::ImageRgb8(rgb) => Self::quantify(rgb, false).map(|q| (q, img)),\n\t\t\t_ => Self::quantify(&img.to_rgb8(), alpha).map(|q| (q, img)),\n\t\t})\n\t\t.await??;\n\n\t\ttokio::task::spawn_blocking(move || {\n\t\t\tlet mut buf = vec![];\n\t\t\twrite!(buf, \"{START}P9;1q\\\"1;1;{};{}\", img.width(), img.height())?;\n\n\t\t\t// Palette\n\t\t\tfor (i, c) in qo.palette.iter().enumerate() {\n\t\t\t\twrite!(\n\t\t\t\t\tbuf,\n\t\t\t\t\t\"#{};2;{};{};{}\",\n\t\t\t\t\ti + alpha as usize,\n\t\t\t\t\tc.red as u16 * 100 / 255,\n\t\t\t\t\tc.green as u16 * 100 / 255,\n\t\t\t\t\tc.blue as u16 * 100 / 255\n\t\t\t\t)?;\n\t\t\t}\n\n\t\t\tfor y in 0..img.height() {\n\t\t\t\tlet c = (b'?' + (1 << (y % 6))) as char;\n\n\t\t\t\tlet mut last = 0;\n\t\t\t\tlet mut repeat = 0usize;\n\t\t\t\tfor x in 0..img.width() {\n\t\t\t\t\tlet idx = if img.get_pixel(x, y)[3] == 0 {\n\t\t\t\t\t\t0\n\t\t\t\t\t} else {\n\t\t\t\t\t\tqo.indices[y as usize * img.width() as usize + x as usize] + alpha as u8\n\t\t\t\t\t};\n\n\t\t\t\t\tif idx == last || repeat == 0 {\n\t\t\t\t\t\t(last, repeat) = (idx, repeat + 1);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif repeat > 1 {\n\t\t\t\t\t\twrite!(buf, \"#{last}!{repeat}{c}\")?;\n\t\t\t\t\t} else {\n\t\t\t\t\t\twrite!(buf, \"#{last}{c}\")?;\n\t\t\t\t\t}\n\n\t\t\t\t\t(last, repeat) = (idx, 1);\n\t\t\t\t}\n\n\t\t\t\tif repeat > 1 {\n\t\t\t\t\twrite!(buf, \"#{last}!{repeat}{c}\")?;\n\t\t\t\t} else {\n\t\t\t\t\twrite!(buf, \"#{last}{c}\")?;\n\t\t\t\t}\n\n\t\t\t\twrite!(buf, \"$\")?;\n\t\t\t\tif y % 6 == 5 {\n\t\t\t\t\twrite!(buf, \"-\")?;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\twrite!(buf, \"{ESCAPE}\\\\{CLOSE}\")?;\n\t\t\tOk(buf)\n\t\t})\n\t\t.await?\n\t}\n\n\tfn quantify(rgb: &RgbImage, alpha: bool) -> Result<QuantizeOutput<Srgb<u8>>> {\n\t\tlet buf = &rgb.as_raw()[..(rgb.pixels().len() * 3)];\n\t\tlet colors: &[Srgb<u8>] = buf.components_as();\n\n\t\tlet wu = WuU8x3::run_slice(colors, BinnerU8x3::rgb())?;\n\t\tlet color_map = wu.color_map(PaletteSize::try_from(256u16 - alpha as u16)?);\n\n\t\tOk(QuantizeOutput {\n\t\t\tindices: color_map.map_to_indices(colors),\n\t\t\tpalette: color_map.into_palette().into_vec(),\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-adapter/src/drivers/ueberzug.rs",
    "content": "use std::{path::PathBuf, process::Stdio};\n\nuse anyhow::{Result, bail};\nuse image::ImageReader;\nuse ratatui::layout::Rect;\nuse tokio::{io::AsyncWriteExt, process::{Child, Command}, sync::mpsc::{self, UnboundedSender}};\nuse tracing::{debug, warn};\nuse yazi_config::YAZI;\nuse yazi_emulator::Dimension;\nuse yazi_shared::{LOG_LEVEL, RoCell, env_exists};\n\nuse crate::Adapter;\n\ntype Cmd = Option<(PathBuf, Rect)>;\n\nstatic DEMON: RoCell<Option<UnboundedSender<Cmd>>> = RoCell::new();\n\npub(crate) struct Ueberzug;\n\nimpl Ueberzug {\n\tpub(crate) fn start(adapter: Adapter) {\n\t\tif !adapter.needs_ueberzug() {\n\t\t\treturn DEMON.init(None);\n\t\t}\n\n\t\tlet mut child = Self::create_demon(adapter).ok();\n\t\tlet (tx, mut rx) = mpsc::unbounded_channel();\n\n\t\ttokio::spawn(async move {\n\t\t\twhile let Some(cmd) = rx.recv().await {\n\t\t\t\tlet exit = child.as_mut().and_then(|c| c.try_wait().ok());\n\t\t\t\tif exit != Some(None) {\n\t\t\t\t\tchild = None;\n\t\t\t\t}\n\t\t\t\tif child.is_none() {\n\t\t\t\t\tchild = Self::create_demon(adapter).ok();\n\t\t\t\t}\n\t\t\t\tif let Some(c) = &mut child {\n\t\t\t\t\tSelf::send_command(adapter, c, cmd).await.ok();\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tDEMON.init(Some(tx))\n\t}\n\n\tpub(crate) async fn image_show(path: PathBuf, max: Rect) -> Result<Rect> {\n\t\tlet Some(tx) = &*DEMON else {\n\t\t\tbail!(\"uninitialized ueberzugpp\");\n\t\t};\n\n\t\tlet (w, h, path) = tokio::task::spawn_blocking(move || {\n\t\t\tImageReader::open(&path)?.with_guessed_format()?.into_dimensions().map(|(w, h)| (w, h, path))\n\t\t})\n\t\t.await??;\n\n\t\tlet area = Dimension::cell_size()\n\t\t\t.map(|(cw, ch)| Rect {\n\t\t\t\tx:      max.x,\n\t\t\t\ty:      max.y,\n\t\t\t\twidth:  max.width.min((w.min(YAZI.preview.max_width as _) as f64 / cw).ceil() as _),\n\t\t\t\theight: max.height.min((h.min(YAZI.preview.max_height as _) as f64 / ch).ceil() as _),\n\t\t\t})\n\t\t\t.unwrap_or(max);\n\n\t\ttx.send(Some((path, area)))?;\n\t\tAdapter::shown_store(area);\n\t\tOk(area)\n\t}\n\n\tpub(crate) fn image_erase(_: Rect) -> Result<()> {\n\t\tif let Some(tx) = &*DEMON {\n\t\t\tOk(tx.send(None)?)\n\t\t} else {\n\t\t\tbail!(\"uninitialized ueberzugpp\");\n\t\t}\n\t}\n\n\t// Currently Überzug++'s Wayland output only supports Sway, Hyprland and Wayfire\n\t// as it requires information from specific compositor socket directly.\n\t// These environment variables are from ueberzugpp src/canvas/wayland/config.cpp\n\tpub(crate) fn supported_compositor() -> bool {\n\t\tenv_exists(\"SWAYSOCK\")\n\t\t\t|| env_exists(\"HYPRLAND_INSTANCE_SIGNATURE\")\n\t\t\t|| env_exists(\"WAYFIRE_SOCKET\")\n\t}\n\n\tfn create_demon(adapter: Adapter) -> Result<Child> {\n\t\tlet result = Command::new(\"ueberzugpp\")\n\t\t\t.args([\"layer\", \"-so\", &adapter.to_string()])\n\t\t\t.env(\"SPDLOG_LEVEL\", if LOG_LEVEL.get().is_none() { \"\" } else { \"debug\" })\n\t\t\t.kill_on_drop(true)\n\t\t\t.stdin(Stdio::piped())\n\t\t\t.stdout(Stdio::null())\n\t\t\t.stderr(Stdio::null())\n\t\t\t.spawn();\n\n\t\tif let Err(ref e) = result {\n\t\t\twarn!(\"Failed to start ueberzugpp: {e}\");\n\t\t}\n\t\tOk(result?)\n\t}\n\n\tfn adjust_rect(mut rect: Rect) -> Rect {\n\t\tlet scale = YAZI.preview.ueberzug_scale;\n\t\tlet (x, y, w, h) = YAZI.preview.ueberzug_offset;\n\n\t\trect.x = 0f32.max(rect.x as f32 * scale + x) as u16;\n\t\trect.y = 0f32.max(rect.y as f32 * scale + y) as u16;\n\t\trect.width = 0f32.max(rect.width as f32 * scale + w) as u16;\n\t\trect.height = 0f32.max(rect.height as f32 * scale + h) as u16;\n\t\trect\n\t}\n\n\tasync fn send_command(adapter: Adapter, child: &mut Child, cmd: Cmd) -> Result<()> {\n\t\tlet s = if let Some((path, rect)) = cmd {\n\t\t\tdebug!(\"ueberzugpp rect before adjustment: {:?}\", rect);\n\t\t\tlet rect = Self::adjust_rect(rect);\n\t\t\tdebug!(\"ueberzugpp rect after adjustment: {:?}\", rect);\n\n\t\t\tformat!(\n\t\t\t\tr#\"{{\"action\":\"add\",\"identifier\":\"yazi\",\"x\":{},\"y\":{},\"max_width\":{},\"max_height\":{},\"path\":\"{}\"}}{}\"#,\n\t\t\t\trect.x,\n\t\t\t\trect.y,\n\t\t\t\trect.width,\n\t\t\t\trect.height,\n\t\t\t\tpath.to_string_lossy(),\n\t\t\t\t'\\n'\n\t\t\t)\n\t\t} else {\n\t\t\tformat!(r#\"{{\"action\":\"remove\",\"identifier\":\"yazi\"}}{}\"#, '\\n')\n\t\t};\n\n\t\tdebug!(\"`ueberzugpp layer -so {adapter}` command: {s}\");\n\t\tchild.stdin.as_mut().unwrap().write_all(s.as_bytes()).await?;\n\n\t\tOk(())\n\t}\n}\n"
  },
  {
    "path": "yazi-adapter/src/icc.rs",
    "content": "use anyhow::Context;\nuse image::{ColorType, DynamicImage, GrayAlphaImage, GrayImage, ImageDecoder, RgbImage, RgbaImage, metadata::Cicp};\nuse moxcms::{CicpColorPrimaries, ColorProfile, DataColorSpace, Layout, TransferCharacteristics, TransformOptions};\n\npub(super) struct Icc;\nimpl Icc {\n\tpub(super) fn transform(mut decoder: impl ImageDecoder) -> anyhow::Result<DynamicImage> {\n\t\tif let Some(layout) = Self::color_type_to_layout(decoder.color_type())\n\t\t\t&& let Some(icc) = decoder.icc_profile().unwrap_or_default()\n\t\t\t&& let Ok(profile) = ColorProfile::new_from_slice(&icc)\n\t\t\t&& Self::requires_transform(&profile)\n\t\t{\n\t\t\tlet mut buf = vec![0u8; decoder.total_bytes() as usize];\n\t\t\tlet (w, h) = decoder.dimensions();\n\t\t\tdecoder.read_image(&mut buf)?;\n\n\t\t\tlet transformer = profile\n\t\t\t\t// TODO: Use `create_transform_in_place_nbit` in the next minor version of moxcms.\n\t\t\t\t.create_transform_8bit(layout, &ColorProfile::new_srgb(), layout, TransformOptions::default())\n\t\t\t\t.context(\"cannot make a profile transformer\")?;\n\n\t\t\tlet mut converted = vec![0u8; buf.len()];\n\t\t\ttransformer.transform(&buf, &mut converted).context(\"cannot transform image\")?;\n\n\t\t\tlet mut image: DynamicImage = match layout {\n\t\t\t\tLayout::Gray => {\n\t\t\t\t\tGrayImage::from_raw(w, h, converted).context(\"cannot load transformed image\")?.into()\n\t\t\t\t}\n\t\t\t\tLayout::GrayAlpha => {\n\t\t\t\t\tGrayAlphaImage::from_raw(w, h, converted).context(\"cannot load transformed image\")?.into()\n\t\t\t\t}\n\t\t\t\tLayout::Rgb => {\n\t\t\t\t\tRgbImage::from_raw(w, h, converted).context(\"cannot load transformed image\")?.into()\n\t\t\t\t}\n\t\t\t\tLayout::Rgba => {\n\t\t\t\t\tRgbaImage::from_raw(w, h, converted).context(\"cannot load transformed image\")?.into()\n\t\t\t\t}\n\t\t\t\t_ => unreachable!(),\n\t\t\t};\n\n\t\t\timage.set_rgb_primaries(Cicp::SRGB.primaries);\n\t\t\timage.set_transfer_function(Cicp::SRGB.transfer);\n\t\t\tOk(image)\n\t\t} else {\n\t\t\tOk(DynamicImage::from_decoder(decoder)?)\n\t\t}\n\t}\n\n\tfn color_type_to_layout(color_type: ColorType) -> Option<Layout> {\n\t\tmatch color_type {\n\t\t\tColorType::L8 => Some(Layout::Gray),\n\t\t\tColorType::La8 => Some(Layout::GrayAlpha),\n\t\t\tColorType::Rgb8 => Some(Layout::Rgb),\n\t\t\tColorType::Rgba8 => Some(Layout::Rgba),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tfn requires_transform(profile: &ColorProfile) -> bool {\n\t\tif profile.color_space == DataColorSpace::Cmyk {\n\t\t\treturn false;\n\t\t}\n\n\t\tprofile.cicp.is_none_or(|c| {\n\t\t\tc.color_primaries != CicpColorPrimaries::Bt709\n\t\t\t\t|| c.transfer_characteristics != TransferCharacteristics::Srgb\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-adapter/src/image.rs",
    "content": "use std::path::{Path, PathBuf};\n\nuse anyhow::Result;\nuse image::{DynamicImage, ImageDecoder, ImageError, ImageReader, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::FilterType, metadata::Orientation};\nuse ratatui::layout::Rect;\nuse yazi_config::YAZI;\nuse yazi_emulator::Dimension;\nuse yazi_fs::provider::{Provider, local::Local};\n\nuse crate::Icc;\n\npub struct Image;\n\nimpl Image {\n\tpub async fn precache(src: PathBuf, cache: &Path) -> Result<()> {\n\t\tlet (mut img, orientation) = Self::decode_from(src).await?;\n\t\tlet (w, h) = Self::flip_size(orientation, (YAZI.preview.max_width, YAZI.preview.max_height));\n\n\t\tlet buf = tokio::task::spawn_blocking(move || {\n\t\t\tif img.width() > w || img.height() > h {\n\t\t\t\timg = img.resize(w, h, Self::filter());\n\t\t\t}\n\t\t\tif orientation != Orientation::NoTransforms {\n\t\t\t\timg.apply_orientation(orientation);\n\t\t\t}\n\n\t\t\tlet mut buf = Vec::new();\n\t\t\tif img.color().has_alpha() {\n\t\t\t\tlet encoder = PngEncoder::new(&mut buf);\n\t\t\t\timg.write_with_encoder(encoder)?;\n\t\t\t} else {\n\t\t\t\tlet encoder = JpegEncoder::new_with_quality(&mut buf, YAZI.preview.image_quality);\n\t\t\t\timg.write_with_encoder(encoder)?;\n\t\t\t}\n\n\t\t\tOk::<_, ImageError>(buf)\n\t\t})\n\t\t.await??;\n\n\t\tOk(Local::regular(&cache).write(buf).await?)\n\t}\n\n\tpub(super) async fn downscale(path: PathBuf, rect: Rect) -> Result<DynamicImage> {\n\t\tlet (mut img, orientation) = Self::decode_from(path).await?;\n\t\tlet (w, h) = Self::flip_size(orientation, Self::max_pixel(rect));\n\n\t\t// Fast path.\n\t\tif img.width() <= w && img.height() <= h && orientation == Orientation::NoTransforms {\n\t\t\treturn Ok(img);\n\t\t}\n\n\t\tlet img = tokio::task::spawn_blocking(move || {\n\t\t\tif img.width() > w || img.height() > h {\n\t\t\t\timg = img.resize(w, h, Self::filter())\n\t\t\t}\n\t\t\tif orientation != Orientation::NoTransforms {\n\t\t\t\timg.apply_orientation(orientation);\n\t\t\t}\n\t\t\timg\n\t\t})\n\t\t.await?;\n\n\t\tOk(img)\n\t}\n\n\tpub(super) fn max_pixel(rect: Rect) -> (u16, u16) {\n\t\tDimension::cell_size()\n\t\t\t.map(|(cw, ch)| {\n\t\t\t\tlet (w, h) = ((rect.width as f64 * cw) as u16, (rect.height as f64 * ch) as u16);\n\t\t\t\t(w.min(YAZI.preview.max_width), h.min(YAZI.preview.max_height))\n\t\t\t})\n\t\t\t.unwrap_or((YAZI.preview.max_width, YAZI.preview.max_height))\n\t}\n\n\tpub(super) fn pixel_area(size: (u32, u32), rect: Rect) -> Rect {\n\t\tDimension::cell_size()\n\t\t\t.map(|(cw, ch)| Rect {\n\t\t\t\tx:      rect.x,\n\t\t\t\ty:      rect.y,\n\t\t\t\twidth:  (size.0 as f64 / cw).ceil() as u16,\n\t\t\t\theight: (size.1 as f64 / ch).ceil() as u16,\n\t\t\t})\n\t\t\t.unwrap_or(rect)\n\t}\n\n\tfn filter() -> FilterType {\n\t\tmatch YAZI.preview.image_filter.as_str() {\n\t\t\t\"nearest\" => FilterType::Nearest,\n\t\t\t\"triangle\" => FilterType::Triangle,\n\t\t\t\"catmull-rom\" => FilterType::CatmullRom,\n\t\t\t\"gaussian\" => FilterType::Gaussian,\n\t\t\t\"lanczos3\" => FilterType::Lanczos3,\n\t\t\t_ => FilterType::Triangle,\n\t\t}\n\t}\n\n\tasync fn decode_from(path: PathBuf) -> Result<(DynamicImage, Orientation)> {\n\t\tlet mut limits = Limits::no_limits();\n\t\tif YAZI.tasks.image_alloc > 0 {\n\t\t\tlimits.max_alloc = Some(YAZI.tasks.image_alloc as u64);\n\t\t}\n\t\tif YAZI.tasks.image_bound[0] > 0 {\n\t\t\tlimits.max_image_width = Some(YAZI.tasks.image_bound[0] as u32);\n\t\t}\n\t\tif YAZI.tasks.image_bound[1] > 0 {\n\t\t\tlimits.max_image_height = Some(YAZI.tasks.image_bound[1] as u32);\n\t\t}\n\n\t\ttokio::task::spawn_blocking(move || {\n\t\t\tlet mut reader = ImageReader::open(path)?;\n\t\t\treader.limits(limits);\n\n\t\t\tlet mut decoder = reader.with_guessed_format()?.into_decoder()?;\n\t\t\tlet orientation = decoder.orientation().unwrap_or(Orientation::NoTransforms);\n\t\t\tOk((Icc::transform(decoder)?, orientation))\n\t\t})\n\t\t.await\n\t\t.map_err(|e| ImageError::IoError(e.into()))?\n\t}\n\n\tfn flip_size(orientation: Orientation, (w, h): (u16, u16)) -> (u32, u32) {\n\t\tuse image::metadata::Orientation::{Rotate90, Rotate90FlipH, Rotate270, Rotate270FlipH};\n\t\tmatch orientation {\n\t\t\tRotate90 | Rotate270 | Rotate90FlipH | Rotate270FlipH => (h as u32, w as u32),\n\t\t\t_ => (w as u32, h as u32),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-adapter/src/info.rs",
    "content": "use std::path::PathBuf;\n\nuse image::{ImageDecoder, ImageError};\n\npub type ImageFormat = image::ImageFormat;\npub type ImageColor = image::ColorType;\npub type ImageOrientation = image::metadata::Orientation;\n\n#[derive(Clone, Copy)]\npub struct ImageInfo {\n\tpub format:      ImageFormat,\n\tpub width:       u32,\n\tpub height:      u32,\n\tpub color:       ImageColor,\n\tpub orientation: Option<ImageOrientation>,\n}\n\nimpl ImageInfo {\n\tpub async fn new(path: PathBuf) -> image::ImageResult<Self> {\n\t\ttokio::task::spawn_blocking(move || {\n\t\t\tlet reader = image::ImageReader::open(path)?.with_guessed_format()?;\n\n\t\t\tlet Some(format) = reader.format() else {\n\t\t\t\treturn Err(ImageError::IoError(std::io::Error::new(\n\t\t\t\t\tstd::io::ErrorKind::InvalidData,\n\t\t\t\t\t\"unknown image format\",\n\t\t\t\t)));\n\t\t\t};\n\n\t\t\tlet mut decoder = reader.into_decoder()?;\n\t\t\tlet (width, height) = decoder.dimensions();\n\t\t\tOk(Self {\n\t\t\t\tformat,\n\t\t\t\twidth,\n\t\t\t\theight,\n\t\t\t\tcolor: decoder.color_type(),\n\t\t\t\torientation: decoder.orientation().ok(),\n\t\t\t})\n\t\t})\n\t\t.await\n\t\t.map_err(|e| ImageError::IoError(e.into()))?\n\t}\n}\n"
  },
  {
    "path": "yazi-adapter/src/lib.rs",
    "content": "yazi_macro::mod_pub!(drivers);\n\nyazi_macro::mod_flat!(adapter adapters icc image info);\n\nuse yazi_emulator::{Brand, CLOSE, EMULATOR, ESCAPE, Emulator, Mux, START, TMUX};\nuse yazi_shared::{SyncCell, in_wsl};\n\npub static ADAPTOR: SyncCell<Adapter> = SyncCell::new(Adapter::Chafa);\n\n// Image state\nstatic SHOWN: SyncCell<Option<ratatui::layout::Rect>> = SyncCell::new(None);\n\n// WSL support\npub static WSL: SyncCell<bool> = SyncCell::new(false);\n\npub fn init() -> anyhow::Result<()> {\n\t// WSL support\n\tWSL.set(in_wsl());\n\n\t// Emulator detection\n\tlet mut emulator = Emulator::detect().unwrap_or_default();\n\tTMUX.set(emulator.kind.left() == Some(Brand::Tmux));\n\n\t// Tmux support\n\tif TMUX.get() {\n\t\tESCAPE.set(\"\\x1b\\x1b\");\n\t\tSTART.set(\"\\x1bPtmux;\\x1b\\x1b\");\n\t\tCLOSE.set(\"\\x1b\\\\\");\n\t\tMux::tmux_passthrough();\n\t\temulator = Emulator::detect().unwrap_or_default();\n\t}\n\n\tEMULATOR.init(emulator);\n\tyazi_config::init_flavor(EMULATOR.light)?;\n\n\tADAPTOR.set(Adapter::matches(&EMULATOR));\n\tADAPTOR.get().start();\n\tOk(())\n}\n"
  },
  {
    "path": "yazi-binding/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-binding\"\ndescription            = \"Yazi Lua bindings\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[features]\ndefault      = [ \"vendored-lua\" ]\nvendored-lua = [ \"mlua/vendored\" ]\n\n[dependencies]\nyazi-adapter = { path = \"../yazi-adapter\", version = \"26.2.2\" }\nyazi-codegen = { path = \"../yazi-codegen\", version = \"26.2.2\" }\nyazi-config  = { path = \"../yazi-config\", version = \"26.2.2\" }\nyazi-fs      = { path = \"../yazi-fs\", version = \"26.2.2\" }\nyazi-macro   = { path = \"../yazi-macro\", version = \"26.2.2\" }\nyazi-shared  = { path = \"../yazi-shared\", version = \"26.2.2\" }\nyazi-vfs     = { path = \"../yazi-vfs\", version = \"26.2.2\" }\nyazi-widgets = { path = \"../yazi-widgets\", version = \"26.2.2\" }\n\n# External dependencies\nansi-to-tui   = { workspace = true }\nanyhow        = { workspace = true }\ncrossterm     = { workspace = true }\nfutures       = { workspace = true }\nhashbrown     = { workspace = true }\nmlua          = { workspace = true }\npaste         = { workspace = true }\nratatui       = { workspace = true }\nserde_json    = { workspace = true }\ntokio  \t      = { workspace = true }\ntokio-stream  = { workspace = true }\ntracing       = { workspace = true }\nunicode-width = { workspace = true }\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\ncrossterm = { workspace = true, features = [ \"use-dev-tty\", \"libc\" ] }\n"
  },
  {
    "path": "yazi-binding/README.md",
    "content": "# yazi-binding\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-binding/src/access.rs",
    "content": "use mlua::{AnyUserData, IntoLuaMulti, UserData, UserDataMethods, Value};\nuse yazi_fs::provider::FileBuilder;\n\nuse crate::{Error, Fd, UrlRef};\n\n#[derive(Default)]\npub struct Access(yazi_vfs::provider::Gate);\n\nimpl UserData for Access {\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_function_mut(\"append\", |_, (ud, append): (AnyUserData, bool)| {\n\t\t\tud.borrow_mut::<Self>()?.0.append(append);\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"create\", |_, (ud, create): (AnyUserData, bool)| {\n\t\t\tud.borrow_mut::<Self>()?.0.create(create);\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"create_new\", |_, (ud, create_new): (AnyUserData, bool)| {\n\t\t\tud.borrow_mut::<Self>()?.0.create_new(create_new);\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_async_method(\"open\", |lua, me, url: UrlRef| async move {\n\t\t\tmatch me.0.open(&*url).await {\n\t\t\t\tOk(fd) => Fd(fd).into_lua_multi(&lua),\n\t\t\t\tErr(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),\n\t\t\t}\n\t\t});\n\t\tmethods.add_function_mut(\"read\", |_, (ud, read): (AnyUserData, bool)| {\n\t\t\tud.borrow_mut::<Self>()?.0.read(read);\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"truncate\", |_, (ud, truncate): (AnyUserData, bool)| {\n\t\t\tud.borrow_mut::<Self>()?.0.truncate(truncate);\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"write\", |_, (ud, write): (AnyUserData, bool)| {\n\t\t\tud.borrow_mut::<Self>()?.0.write(write);\n\t\t\tOk(ud)\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/calculator.rs",
    "content": "use mlua::{IntoLuaMulti, UserData, UserDataFields, UserDataMethods, Value};\n\nuse crate::{Cha, Error};\n\npub enum SizeCalculator {\n\tLocal(yazi_fs::provider::local::SizeCalculator),\n\tRemote(yazi_vfs::provider::SizeCalculator),\n}\n\nimpl UserData for SizeCalculator {\n\tfn add_fields<F: UserDataFields<Self>>(fields: &mut F) {\n\t\tfields.add_field_method_get(\"cha\", |_, me| {\n\t\t\tOk(Cha(match me {\n\t\t\t\tSelf::Local(c) => c.cha(),\n\t\t\t\tSelf::Remote(c) => c.cha(),\n\t\t\t}))\n\t\t});\n\t}\n\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_async_method_mut(\"recv\", |lua, mut me, ()| async move {\n\t\t\tlet next = match &mut *me {\n\t\t\t\tSelf::Local(c) => c.next().await,\n\t\t\t\tSelf::Remote(c) => c.next().await,\n\t\t\t};\n\n\t\t\tmatch next {\n\t\t\t\tOk(value) => value.into_lua_multi(&lua),\n\t\t\t\tErr(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),\n\t\t\t}\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/cha.rs",
    "content": "use std::{ops::Deref, time::{Duration, SystemTime}};\n\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Table, UserData, UserDataFields, UserDataMethods};\nuse yazi_fs::{FsHash128, cha::{ChaKind, ChaMode}};\n\n#[derive(Clone, Copy, FromLua)]\npub struct Cha(pub yazi_fs::cha::Cha);\n\nimpl Deref for Cha {\n\ttype Target = yazi_fs::cha::Cha;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl Cha {\n\tpub fn install(lua: &Lua) -> mlua::Result<()> {\n\t\tfn parse_time(f: Option<f64>) -> mlua::Result<Option<SystemTime>> {\n\t\t\tOk(match f {\n\t\t\t\tSome(n) if n >= 0.0 => Some(SystemTime::UNIX_EPOCH + Duration::from_secs_f64(n)),\n\t\t\t\tSome(n) => Err(format!(\"Invalid timestamp: {n}\").into_lua_err())?,\n\t\t\t\tNone => None,\n\t\t\t})\n\t\t}\n\n\t\tlua.globals().raw_set(\n\t\t\t\"Cha\",\n\t\t\tlua.create_function(|lua, t: Table| {\n\t\t\t\tlet kind = ChaKind::from_bits(t.raw_get(\"kind\").unwrap_or_default())\n\t\t\t\t\t.ok_or_else(|| \"Invalid kind\".into_lua_err())?;\n\n\t\t\t\tlet mode = ChaMode::try_from(t.raw_get::<u16>(\"mode\")?)?;\n\n\t\t\t\tSelf(yazi_fs::cha::Cha {\n\t\t\t\t\tkind,\n\t\t\t\t\tmode,\n\t\t\t\t\tlen: t.raw_get(\"len\").unwrap_or_default(),\n\t\t\t\t\tatime: parse_time(t.raw_get(\"atime\").ok())?,\n\t\t\t\t\tbtime: parse_time(t.raw_get(\"btime\").ok())?,\n\t\t\t\t\tctime: parse_time(t.raw_get(\"ctime\").ok())?,\n\t\t\t\t\tmtime: parse_time(t.raw_get(\"mtime\").ok())?,\n\t\t\t\t\tdev: t.raw_get(\"dev\").unwrap_or_default(),\n\t\t\t\t\tuid: t.raw_get(\"uid\").unwrap_or_default(),\n\t\t\t\t\tgid: t.raw_get(\"gid\").unwrap_or_default(),\n\t\t\t\t\tnlink: t.raw_get(\"nlink\").unwrap_or_default(),\n\t\t\t\t})\n\t\t\t\t.into_lua(lua)\n\t\t\t})?,\n\t\t)\n\t}\n}\n\nimpl UserData for Cha {\n\tfn add_fields<F: UserDataFields<Self>>(fields: &mut F) {\n\t\tfields.add_field_method_get(\"mode\", |_, me| Ok(me.mode.bits()));\n\t\tfields.add_field_method_get(\"is_dir\", |_, me| Ok(me.is_dir()));\n\t\tfields.add_field_method_get(\"is_hidden\", |_, me| Ok(me.is_hidden()));\n\t\tfields.add_field_method_get(\"is_link\", |_, me| Ok(me.is_link()));\n\t\tfields.add_field_method_get(\"is_orphan\", |_, me| Ok(me.is_orphan()));\n\t\tfields.add_field_method_get(\"is_dummy\", |_, me| Ok(me.is_dummy()));\n\t\tfields.add_field_method_get(\"is_block\", |_, me| Ok(me.is_block()));\n\t\tfields.add_field_method_get(\"is_char\", |_, me| Ok(me.is_char()));\n\t\tfields.add_field_method_get(\"is_fifo\", |_, me| Ok(me.is_fifo()));\n\t\tfields.add_field_method_get(\"is_sock\", |_, me| Ok(me.is_sock()));\n\t\tfields.add_field_method_get(\"is_exec\", |_, me| Ok(me.is_exec()));\n\t\tfields.add_field_method_get(\"is_sticky\", |_, me| Ok(me.is_sticky()));\n\n\t\tfields.add_field_method_get(\"len\", |_, me| Ok(me.len));\n\t\tfields.add_field_method_get(\"atime\", |_, me| Ok(me.atime_dur().ok().map(|d| d.as_secs_f64())));\n\t\tfields.add_field_method_get(\"btime\", |_, me| Ok(me.btime_dur().ok().map(|d| d.as_secs_f64())));\n\t\tfields.add_field_method_get(\"ctime\", |_, me| Ok(me.ctime_dur().ok().map(|d| d.as_secs_f64())));\n\t\tfields.add_field_method_get(\"mtime\", |_, me| Ok(me.mtime_dur().ok().map(|d| d.as_secs_f64())));\n\t\tfields.add_field_method_get(\"dev\", |_, me| Ok(me.dev));\n\t\tfields.add_field_method_get(\"uid\", |_, me| Ok(me.uid));\n\t\tfields.add_field_method_get(\"gid\", |_, me| Ok(me.gid));\n\t\tfields.add_field_method_get(\"nlink\", |_, me| Ok(me.nlink));\n\t}\n\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_method(\"hash\", |_, me, long: Option<bool>| {\n\t\t\tOk(if long.unwrap_or(false) {\n\t\t\t\tformat!(\"{:x}\", me.hash_u128())\n\t\t\t} else {\n\t\t\t\tErr(\"Short hash not supported\".into_lua_err())?\n\t\t\t})\n\t\t});\n\t\tmethods.add_method(\"perm\", |lua, _me, ()| {\n\t\t\tOk(\n\t\t\t\t#[cfg(unix)]\n\t\t\t\tlua.create_string(_me.mode.permissions(_me.is_dummy())),\n\t\t\t\t#[cfg(windows)]\n\t\t\t\tOk::<_, mlua::Error>(mlua::Value::Nil),\n\t\t\t)\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/chan.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, IntoLuaMulti, UserData, Value};\nuse yazi_codegen::FromLuaOwned;\n\nuse crate::Error;\n\n#[derive(FromLuaOwned)]\npub struct MpscTx<T: FromLua + 'static>(pub tokio::sync::mpsc::Sender<T>);\npub struct MpscRx<T: IntoLua + 'static>(pub tokio::sync::mpsc::Receiver<T>);\n\nimpl<T: FromLua> UserData for MpscTx<T> {\n\tfn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_async_method(\"send\", |lua, me, value: Value| async move {\n\t\t\tmatch me.0.send(T::from_lua(value, &lua)?).await {\n\t\t\t\tOk(()) => true.into_lua_multi(&lua),\n\t\t\t\tErr(e) => (false, Error::custom(e.to_string())).into_lua_multi(&lua),\n\t\t\t}\n\t\t});\n\t}\n}\n\nimpl<T: IntoLua + 'static> UserData for MpscRx<T> {\n\tfn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_async_method_mut(\"recv\", |lua, mut me, ()| async move {\n\t\t\tmatch me.0.recv().await {\n\t\t\t\tSome(value) => (value, true).into_lua_multi(&lua),\n\t\t\t\tNone => (Value::Nil, false).into_lua_multi(&lua),\n\t\t\t}\n\t\t});\n\t}\n}\n\n#[derive(FromLuaOwned)]\npub struct MpscUnboundedTx<T: FromLua + 'static>(pub tokio::sync::mpsc::UnboundedSender<T>);\npub struct MpscUnboundedRx<T: IntoLua + 'static>(pub tokio::sync::mpsc::UnboundedReceiver<T>);\n\nimpl<T: FromLua> UserData for MpscUnboundedTx<T> {\n\tfn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_method(\"send\", |lua, me, value: Value| match me.0.send(T::from_lua(value, lua)?) {\n\t\t\tOk(()) => true.into_lua_multi(lua),\n\t\t\tErr(e) => (false, Error::custom(e.to_string())).into_lua_multi(lua),\n\t\t});\n\t}\n}\n\nimpl<T: IntoLua + 'static> UserData for MpscUnboundedRx<T> {\n\tfn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_async_method_mut(\"recv\", |lua, mut me, ()| async move {\n\t\t\tmatch me.0.recv().await {\n\t\t\t\tSome(value) => (value, true).into_lua_multi(&lua),\n\t\t\t\tNone => (Value::Nil, false).into_lua_multi(&lua),\n\t\t\t}\n\t\t});\n\t}\n}\n\n#[derive(FromLuaOwned)]\npub struct OneshotTx<T: FromLua + 'static>(pub Option<tokio::sync::oneshot::Sender<T>>);\npub struct OneshotRx<T: IntoLua + 'static>(pub Option<tokio::sync::oneshot::Receiver<T>>);\n\nimpl<T: FromLua> UserData for OneshotTx<T> {\n\tfn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_method_mut(\"send\", |lua, me, value: Value| {\n\t\t\tlet Some(tx) = me.0.take() else {\n\t\t\t\treturn Err(\"Oneshot sender already used\".into_lua_err());\n\t\t\t};\n\t\t\tmatch tx.send(T::from_lua(value, lua)?) {\n\t\t\t\tOk(()) => true.into_lua_multi(lua),\n\t\t\t\tErr(_) => (false, Error::custom(\"Oneshot receiver closed\")).into_lua_multi(lua),\n\t\t\t}\n\t\t});\n\t}\n}\n\nimpl<T: IntoLua + 'static> UserData for OneshotRx<T> {\n\tfn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_async_method_mut(\"recv\", |lua, mut me, ()| async move {\n\t\t\tlet Some(rx) = me.0.take() else {\n\t\t\t\treturn Err(\"Oneshot receiver already used\".into_lua_err());\n\t\t\t};\n\t\t\tmatch rx.await {\n\t\t\t\tOk(value) => value.into_lua_multi(&lua),\n\t\t\t\tErr(e) => (Value::Nil, Error::custom(e.to_string())).into_lua_multi(&lua),\n\t\t\t}\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/chord_cow.rs",
    "content": "use mlua::UserData;\nuse yazi_codegen::FromLuaOwned;\n\n#[derive(Clone, FromLuaOwned)]\npub struct ChordCow(pub yazi_config::keymap::ChordCow);\n\nimpl From<yazi_config::keymap::ChordCow> for ChordCow {\n\tfn from(value: yazi_config::keymap::ChordCow) -> Self { Self(value) }\n}\n\nimpl From<ChordCow> for yazi_config::keymap::ChordCow {\n\tfn from(value: ChordCow) -> Self { value.0 }\n}\n\nimpl UserData for ChordCow {}\n"
  },
  {
    "path": "yazi-binding/src/color.rs",
    "content": "use std::str::FromStr;\n\nuse mlua::{ExternalError, ExternalResult, FromLua, Lua, UserData, Value};\n\n#[derive(Clone, Copy, Default)]\npub struct Color(pub ratatui::style::Color);\n\nimpl FromLua for Color {\n\tfn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {\n\t\tOk(Self(match value {\n\t\t\tValue::String(s) => ratatui::style::Color::from_str(&s.to_str()?).into_lua_err()?,\n\t\t\tValue::UserData(ud) => ud.borrow::<Self>()?.0,\n\t\t\t_ => Err(\"expected a Color\".into_lua_err())?,\n\t\t}))\n\t}\n}\n\nimpl UserData for Color {}\n"
  },
  {
    "path": "yazi-binding/src/composer.rs",
    "content": "use hashbrown::HashMap;\nuse mlua::{Lua, MetaMethod, UserData, UserDataMethods, Value};\n\npub type ComposerGet = fn(&Lua, &[u8]) -> mlua::Result<Value>;\npub type ComposerSet = fn(&Lua, &[u8], Value) -> mlua::Result<Value>;\n\npub struct Composer<G, S> {\n\tget:    G,\n\tset:    S,\n\tparent: Option<(G, S)>,\n\tcache:  HashMap<Vec<u8>, Value>,\n}\n\nimpl<G, S> Composer<G, S>\nwhere\n\tG: Fn(&Lua, &[u8]) -> mlua::Result<Value> + 'static,\n\tS: Fn(&Lua, &[u8], Value) -> mlua::Result<Value> + 'static,\n{\n\t#[inline]\n\tpub fn new(get: G, set: S) -> Self { Self { get, set, parent: None, cache: Default::default() } }\n\n\t#[inline]\n\tpub fn with_parent(get: G, set: S, p_get: G, p_set: S) -> Self {\n\t\tSelf { get, set, parent: Some((p_get, p_set)), cache: Default::default() }\n\t}\n}\n\nimpl<G, S> UserData for Composer<G, S>\nwhere\n\tG: Fn(&Lua, &[u8]) -> mlua::Result<Value> + 'static,\n\tS: Fn(&Lua, &[u8], Value) -> mlua::Result<Value> + 'static,\n{\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_meta_method_mut(MetaMethod::Index, |lua, me, key: mlua::String| {\n\t\t\tlet key = key.as_bytes();\n\t\t\tif let Some(v) = me.cache.get(key.as_ref()) {\n\t\t\t\treturn Ok(v.clone());\n\t\t\t}\n\n\t\t\tlet mut value = (me.get)(lua, &key)?;\n\t\t\tif value.is_nil()\n\t\t\t\t&& let Some((p_get, _)) = &me.parent\n\t\t\t{\n\t\t\t\tvalue = p_get(lua, &key)?;\n\t\t\t}\n\n\t\t\tme.cache.insert(key.to_owned(), value.clone());\n\t\t\tOk(value)\n\t\t});\n\n\t\tmethods.add_meta_method_mut(\n\t\t\tMetaMethod::NewIndex,\n\t\t\t|lua, me, (key, value): (mlua::String, Value)| {\n\t\t\t\tlet key = key.as_bytes();\n\t\t\t\tlet value = (me.set)(lua, key.as_ref(), value)?;\n\n\t\t\t\tif value.is_nil() {\n\t\t\t\t\tme.cache.remove(key.as_ref());\n\t\t\t\t} else if let Some((_, p_set)) = &me.parent {\n\t\t\t\t\tmatch p_set(lua, key.as_ref(), value)? {\n\t\t\t\t\t\tValue::Nil => me.cache.remove(key.as_ref()),\n\t\t\t\t\t\tv => me.cache.insert(key.to_owned(), v),\n\t\t\t\t\t};\n\t\t\t\t} else {\n\t\t\t\t\tme.cache.insert(key.to_owned(), value);\n\t\t\t\t}\n\n\t\t\t\tOk(())\n\t\t\t},\n\t\t);\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/elements/align.rs",
    "content": "use std::ops::Deref;\n\nuse mlua::{FromLua, IntoLua, Lua, Value};\n\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]\npub struct Align(pub(super) ratatui::layout::Alignment);\n\nimpl Deref for Align {\n\ttype Target = ratatui::layout::Alignment;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl Align {\n\tpub fn compose(lua: &Lua) -> mlua::Result<Value> {\n\t\tlua.create_table_from([(\"LEFT\", 0), (\"CENTER\", 1), (\"RIGHT\", 2)])?.into_lua(lua)\n\t}\n}\n\nimpl FromLua for Align {\n\tfn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {\n\t\tlet Value::Integer(n) = value else {\n\t\t\treturn Err(mlua::Error::FromLuaConversionError {\n\t\t\t\tfrom:    value.type_name(),\n\t\t\t\tto:      \"Align\".to_string(),\n\t\t\t\tmessage: Some(\"expected an integer representation of Align\".to_string()),\n\t\t\t});\n\t\t};\n\t\tOk(Self(match n {\n\t\t\t0 => ratatui::layout::Alignment::Left,\n\t\t\t1 => ratatui::layout::Alignment::Center,\n\t\t\t2 => ratatui::layout::Alignment::Right,\n\t\t\t_ => Err(mlua::Error::FromLuaConversionError {\n\t\t\t\tfrom:    value.type_name(),\n\t\t\t\tto:      \"Align\".to_string(),\n\t\t\t\tmessage: Some(\"invalid value for Align\".to_string()),\n\t\t\t})?,\n\t\t}))\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/elements/area.rs",
    "content": "use std::fmt::Debug;\n\nuse mlua::{AnyUserData, ExternalError, FromLua, IntoLua, Lua, Value};\n\nuse super::{Pos, Rect};\n\nconst EXPECTED: &str = \"expected a Pos or Rect\";\n\n#[derive(Clone, Copy)]\npub enum Area {\n\tPos(Pos),\n\tRect(Rect),\n}\n\nimpl Default for Area {\n\tfn default() -> Self { Self::Rect(Default::default()) }\n}\n\nimpl Area {\n\tpub fn size(self) -> ratatui::layout::Size {\n\t\tmatch self {\n\t\t\tSelf::Pos(pos) => {\n\t\t\t\tratatui::layout::Size { width: pos.offset.width, height: pos.offset.height }\n\t\t\t}\n\t\t\tSelf::Rect(rect) => (*rect).into(),\n\t\t}\n\t}\n\n\tpub fn inner(self, padding: ratatui::widgets::Padding) -> Self {\n\t\tmatch self {\n\t\t\tSelf::Pos(mut pos) => {\n\t\t\t\tpos.pad += padding;\n\t\t\t\tSelf::Pos(pos)\n\t\t\t}\n\t\t\tSelf::Rect(rect) => Self::Rect(rect.pad(padding.into())),\n\t\t}\n\t}\n\n\tpub fn transform(\n\t\tself,\n\t\tf: impl FnOnce(yazi_config::popup::Position) -> ratatui::layout::Rect,\n\t) -> ratatui::layout::Rect {\n\t\tmatch self {\n\t\t\tSelf::Pos(pos) => *Rect(f(*pos)).pad(pos.pad),\n\t\t\tSelf::Rect(rect) => *rect,\n\t\t}\n\t}\n}\n\nimpl From<Rect> for Area {\n\tfn from(rect: Rect) -> Self { Self::Rect(rect) }\n}\n\nimpl From<ratatui::layout::Rect> for Area {\n\tfn from(rect: ratatui::layout::Rect) -> Self { Self::Rect(rect.into()) }\n}\n\nimpl TryFrom<AnyUserData> for Area {\n\ttype Error = mlua::Error;\n\n\tfn try_from(value: AnyUserData) -> Result<Self, Self::Error> {\n\t\tOk(if let Ok(rect) = value.borrow::<Rect>() {\n\t\t\tSelf::Rect(*rect)\n\t\t} else if let Ok(pos) = value.borrow::<Pos>() {\n\t\t\tSelf::Pos(*pos)\n\t\t} else {\n\t\t\treturn Err(EXPECTED.into_lua_err());\n\t\t})\n\t}\n}\n\nimpl FromLua for Area {\n\tfn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {\n\t\tmatch value {\n\t\t\tValue::UserData(ud) => Self::try_from(ud),\n\t\t\t_ => Err(EXPECTED.into_lua_err()),\n\t\t}\n\t}\n}\n\nimpl IntoLua for Area {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tmatch self {\n\t\t\tSelf::Pos(pos) => pos.into_lua(lua),\n\t\t\tSelf::Rect(rect) => rect.into_lua(lua),\n\t\t}\n\t}\n}\n\nimpl Debug for Area {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tmatch self {\n\t\t\tSelf::Pos(pos) => write!(f, \"{:?}\", **pos),\n\t\t\tSelf::Rect(rect) => write!(f, \"{:?}\", **rect),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/elements/bar.rs",
    "content": "use mlua::{AnyUserData, IntoLua, Lua, MetaMethod, Table, UserData, Value};\nuse ratatui::widgets::{Borders, Widget};\n\nuse super::{Area, Edge};\n\n#[derive(Clone, Debug, Default)]\npub struct Bar {\n\tpub(super) area: Area,\n\n\tedge:   Edge,\n\tsymbol: String,\n\tstyle:  ratatui::style::Style,\n}\n\nimpl Bar {\n\tpub fn compose(lua: &Lua) -> mlua::Result<Value> {\n\t\tlet new =\n\t\t\tlua.create_function(|_, (_, edge): (Table, Edge)| Ok(Self { edge, ..Default::default() }))?;\n\n\t\tlet bar = lua.create_table()?;\n\t\tbar.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;\n\t\tbar.into_lua(lua)\n\t}\n}\n\nimpl Widget for Bar {\n\tfn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)\n\twhere\n\t\tSelf: Sized,\n\t{\n\t\t(&self).render(rect, buf);\n\t}\n}\n\nimpl Widget for &Bar {\n\tfn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)\n\twhere\n\t\tSelf: Sized,\n\t{\n\t\tif rect.area() == 0 {\n\t\t\treturn;\n\t\t}\n\n\t\tlet symbol = if !self.symbol.is_empty() {\n\t\t\t&self.symbol\n\t\t} else if self.edge.intersects(Borders::TOP | Borders::BOTTOM) {\n\t\t\t\"─\"\n\t\t} else if self.edge.intersects(Borders::LEFT | Borders::RIGHT) {\n\t\t\t\"│\"\n\t\t} else {\n\t\t\t\" \"\n\t\t};\n\n\t\tif self.edge.contains(Borders::LEFT) {\n\t\t\tfor y in rect.top()..rect.bottom() {\n\t\t\t\tbuf[(rect.left(), y)].set_style(self.style).set_symbol(symbol);\n\t\t\t}\n\t\t}\n\t\tif self.edge.contains(Borders::TOP) {\n\t\t\tfor x in rect.left()..rect.right() {\n\t\t\t\tbuf[(x, rect.top())].set_style(self.style).set_symbol(symbol);\n\t\t\t}\n\t\t}\n\t\tif self.edge.contains(Borders::RIGHT) {\n\t\t\tlet x = rect.right() - 1;\n\t\t\tfor y in rect.top()..rect.bottom() {\n\t\t\t\tbuf[(x, y)].set_style(self.style).set_symbol(symbol);\n\t\t\t}\n\t\t}\n\t\tif self.edge.contains(Borders::BOTTOM) {\n\t\t\tlet y = rect.bottom() - 1;\n\t\t\tfor x in rect.left()..rect.right() {\n\t\t\t\tbuf[(x, y)].set_style(self.style).set_symbol(symbol);\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl UserData for Bar {\n\tfn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {\n\t\tcrate::impl_area_method!(methods);\n\t\tcrate::impl_style_method!(methods, style);\n\n\t\tmethods.add_function_mut(\"edge\", |_, (ud, edge): (AnyUserData, Edge)| {\n\t\t\tud.borrow_mut::<Self>()?.edge = edge;\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"symbol\", |_, (ud, symbol): (AnyUserData, String)| {\n\t\t\tud.borrow_mut::<Self>()?.symbol = symbol;\n\t\t\tOk(ud)\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/elements/border.rs",
    "content": "use mlua::{AnyUserData, IntoLua, Lua, MetaMethod, Table, UserData, Value};\nuse ratatui::widgets::{Borders, Widget};\n\nuse super::{Area, Edge};\nuse crate::elements::Line;\n\n// Type\nconst PLAIN: u8 = 0;\nconst ROUNDED: u8 = 1;\nconst DOUBLE: u8 = 2;\nconst THICK: u8 = 3;\nconst QUADRANT_INSIDE: u8 = 4;\nconst QUADRANT_OUTSIDE: u8 = 5;\n\n#[derive(Clone, Debug, Default)]\npub struct Border {\n\tpub area: Area,\n\n\tpub edge:   Edge,\n\tpub r#type: ratatui::widgets::BorderType,\n\tpub style:  ratatui::style::Style,\n\n\tpub titles: Vec<(ratatui::widgets::TitlePosition, ratatui::text::Line<'static>)>,\n}\n\nimpl Border {\n\tpub fn compose(lua: &Lua) -> mlua::Result<Value> {\n\t\tlet new = lua.create_function(|_, (_, edge): (Table, Edge)| {\n\t\t\tOk(Self { edge, r#type: ratatui::widgets::BorderType::Rounded, ..Default::default() })\n\t\t})?;\n\n\t\tlet border = lua.create_table_from([\n\t\t\t// Type\n\t\t\t(\"PLAIN\", PLAIN),\n\t\t\t(\"ROUNDED\", ROUNDED),\n\t\t\t(\"DOUBLE\", DOUBLE),\n\t\t\t(\"THICK\", THICK),\n\t\t\t(\"QUADRANT_INSIDE\", QUADRANT_INSIDE),\n\t\t\t(\"QUADRANT_OUTSIDE\", QUADRANT_OUTSIDE),\n\t\t])?;\n\n\t\tborder.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;\n\t\tborder.into_lua(lua)\n\t}\n}\n\nimpl Widget for Border {\n\tfn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)\n\twhere\n\t\tSelf: Sized,\n\t{\n\t\tlet mut block = ratatui::widgets::Block::default()\n\t\t\t.borders(self.edge.0)\n\t\t\t.border_type(self.r#type)\n\t\t\t.border_style(self.style);\n\n\t\tfor title in self.titles {\n\t\t\tblock = match title {\n\t\t\t\t(ratatui::widgets::TitlePosition::Top, line) => block.title_top(line),\n\t\t\t\t(ratatui::widgets::TitlePosition::Bottom, line) => block.title_bottom(line),\n\t\t\t};\n\t\t}\n\n\t\tblock.render(rect, buf);\n\t}\n}\n\nimpl Widget for &Border {\n\tfn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)\n\twhere\n\t\tSelf: Sized,\n\t{\n\t\tself.clone().render(rect, buf);\n\t}\n}\n\nimpl UserData for Border {\n\tfn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {\n\t\tcrate::impl_area_method!(methods);\n\t\tcrate::impl_style_method!(methods, style);\n\n\t\tmethods.add_function_mut(\"type\", |_, (ud, value): (AnyUserData, u8)| {\n\t\t\tud.borrow_mut::<Self>()?.r#type = match value {\n\t\t\t\tROUNDED => ratatui::widgets::BorderType::Rounded,\n\t\t\t\tDOUBLE => ratatui::widgets::BorderType::Double,\n\t\t\t\tTHICK => ratatui::widgets::BorderType::Thick,\n\t\t\t\tQUADRANT_INSIDE => ratatui::widgets::BorderType::QuadrantInside,\n\t\t\t\tQUADRANT_OUTSIDE => ratatui::widgets::BorderType::QuadrantOutside,\n\t\t\t\t_ => ratatui::widgets::BorderType::Plain,\n\t\t\t};\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\n\t\t\t\"title\",\n\t\t\t|_, (ud, line, position): (AnyUserData, Line, Option<u8>)| {\n\t\t\t\tlet position = if position == Some(Borders::BOTTOM.bits()) {\n\t\t\t\t\tratatui::widgets::TitlePosition::Bottom\n\t\t\t\t} else {\n\t\t\t\t\tratatui::widgets::TitlePosition::Top\n\t\t\t\t};\n\n\t\t\t\tud.borrow_mut::<Self>()?.titles.push((position, line.inner));\n\t\t\t\tOk(ud)\n\t\t\t},\n\t\t);\n\t\tmethods.add_function_mut(\"edge\", |_, (ud, edge): (AnyUserData, Edge)| {\n\t\t\tud.borrow_mut::<Self>()?.edge = edge;\n\t\t\tOk(ud)\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/elements/cell.rs",
    "content": "use mlua::{FromLua, Lua, Value};\n\nuse super::Text;\n\n#[derive(Clone, Debug)]\npub struct Cell {\n\tpub(super) text: ratatui::text::Text<'static>,\n}\n\nimpl From<Cell> for ratatui::widgets::Cell<'static> {\n\tfn from(value: Cell) -> Self { Self::new(value.text) }\n}\n\nimpl FromLua for Cell {\n\tfn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {\n\t\tOk(Self { text: Text::from_lua(value, lua)?.inner })\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/elements/clear.rs",
    "content": "use mlua::{IntoLua, Lua, MetaMethod, Table, UserData, Value};\nuse ratatui::widgets::Widget;\n\nuse super::Area;\n\n#[derive(Clone, Copy, Debug, Default)]\npub struct Clear {\n\tpub area: Area,\n}\n\nimpl Clear {\n\tpub fn compose(lua: &Lua) -> mlua::Result<Value> {\n\t\tlet new = lua.create_function(|_, (_, area): (Table, Area)| Ok(Self { area }))?;\n\n\t\tlet clear = lua.create_table()?;\n\t\tclear.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;\n\n\t\tclear.into_lua(lua)\n\t}\n}\n\nimpl Widget for Clear {\n\tfn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)\n\twhere\n\t\tSelf: Sized,\n\t{\n\t\t(&self).render(rect, buf);\n\t}\n}\n\nimpl Widget for &Clear {\n\tfn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)\n\twhere\n\t\tSelf: Sized,\n\t{\n\t\tyazi_widgets::Clear.render(rect, buf);\n\t}\n}\n\nimpl UserData for Clear {\n\tfn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {\n\t\tcrate::impl_area_method!(methods);\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/elements/constraint.rs",
    "content": "use mlua::{FromLua, IntoLua, Lua, UserData, Value};\n\n#[derive(Clone, Copy, Default, FromLua)]\npub struct Constraint(pub(super) ratatui::layout::Constraint);\n\nimpl Constraint {\n\tpub fn compose(lua: &Lua) -> mlua::Result<Value> {\n\t\tuse ratatui::layout::Constraint as C;\n\n\t\tlua\n\t\t\t.create_table_from([\n\t\t\t\t(\"Min\", lua.create_function(|_, n: u16| Ok(Self(C::Min(n))))?),\n\t\t\t\t(\"Max\", lua.create_function(|_, n: u16| Ok(Self(C::Max(n))))?),\n\t\t\t\t(\"Length\", lua.create_function(|_, n: u16| Ok(Self(C::Length(n))))?),\n\t\t\t\t(\"Percentage\", lua.create_function(|_, n: u16| Ok(Self(C::Percentage(n))))?),\n\t\t\t\t(\"Ratio\", lua.create_function(|_, (a, b): (u32, u32)| Ok(Self(C::Ratio(a, b))))?),\n\t\t\t\t(\"Fill\", lua.create_function(|_, n: u16| Ok(Self(C::Fill(n))))?),\n\t\t\t])?\n\t\t\t.into_lua(lua)\n\t}\n}\n\nimpl From<Constraint> for ratatui::layout::Constraint {\n\tfn from(value: Constraint) -> Self { value.0 }\n}\n\nimpl UserData for Constraint {}\n"
  },
  {
    "path": "yazi-binding/src/elements/edge.rs",
    "content": "use std::ops::Deref;\n\nuse mlua::{FromLua, IntoLua, Lua, Value};\nuse ratatui::widgets::Borders;\n\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]\npub struct Edge(pub Borders);\n\nimpl Deref for Edge {\n\ttype Target = Borders;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl Edge {\n\tpub fn compose(lua: &Lua) -> mlua::Result<Value> {\n\t\tlua\n\t\t\t.create_table_from([\n\t\t\t\t(\"NONE\", Borders::NONE.bits()),\n\t\t\t\t(\"TOP\", Borders::TOP.bits()),\n\t\t\t\t(\"RIGHT\", Borders::RIGHT.bits()),\n\t\t\t\t(\"BOTTOM\", Borders::BOTTOM.bits()),\n\t\t\t\t(\"LEFT\", Borders::LEFT.bits()),\n\t\t\t\t(\"ALL\", Borders::ALL.bits()),\n\t\t\t])?\n\t\t\t.into_lua(lua)\n\t}\n}\n\nimpl FromLua for Edge {\n\tfn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {\n\t\tlet Value::Integer(n) = value else {\n\t\t\treturn Err(mlua::Error::FromLuaConversionError {\n\t\t\t\tfrom:    value.type_name(),\n\t\t\t\tto:      \"Edge\".to_string(),\n\t\t\t\tmessage: Some(\"expected an integer representation of an Edge\".to_string()),\n\t\t\t});\n\t\t};\n\t\tlet Ok(Some(b)) = u8::try_from(n).map(Borders::from_bits) else {\n\t\t\treturn Err(mlua::Error::FromLuaConversionError {\n\t\t\t\tfrom:    value.type_name(),\n\t\t\t\tto:      \"Edge\".to_string(),\n\t\t\t\tmessage: Some(\"invalid bits for Edge\".to_string()),\n\t\t\t});\n\t\t};\n\t\tOk(Self(b))\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/elements/elements.rs",
    "content": "use mlua::{IntoLua, Lua, Value};\nuse tracing::error;\n\nuse super::Renderable;\nuse crate::{Composer, ComposerGet, ComposerSet};\n\npub fn compose(p_get: ComposerGet, p_set: ComposerSet) -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tmatch key {\n\t\t\tb\"Align\" => super::Align::compose(lua)?,\n\t\t\tb\"Bar\" => super::Bar::compose(lua)?,\n\t\t\tb\"Border\" => super::Border::compose(lua)?,\n\t\t\tb\"Clear\" => super::Clear::compose(lua)?,\n\t\t\tb\"Constraint\" => super::Constraint::compose(lua)?,\n\t\t\tb\"Edge\" => super::Edge::compose(lua)?,\n\t\t\tb\"Gauge\" => super::Gauge::compose(lua)?,\n\t\t\tb\"Layout\" => super::Layout::compose(lua)?,\n\t\t\tb\"Line\" => super::Line::compose(lua)?,\n\t\t\tb\"List\" => super::List::compose(lua)?,\n\t\t\tb\"Pad\" => super::Pad::compose(lua)?,\n\t\t\tb\"Pos\" => super::Pos::compose(lua)?,\n\t\t\tb\"Rect\" => super::Rect::compose(lua)?,\n\t\t\tb\"Row\" => super::Row::compose(lua)?,\n\t\t\tb\"Span\" => super::Span::compose(lua)?,\n\t\t\tb\"Style\" => crate::Style::compose(lua)?,\n\t\t\tb\"Table\" => super::Table::compose(lua)?,\n\t\t\tb\"Text\" => super::Text::compose(lua)?,\n\t\t\tb\"Wrap\" => super::Wrap::compose(lua)?,\n\t\t\t_ => return Ok(Value::Nil),\n\t\t}\n\t\t.into_lua(lua)\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::with_parent(get, set, p_get, p_set)\n}\n\npub fn render_once<F>(value: Value, buf: &mut ratatui::buffer::Buffer, trans: F)\nwhere\n\tF: FnOnce(yazi_config::popup::Position) -> ratatui::layout::Rect + Copy,\n{\n\tmatch value {\n\t\tValue::Table(tbl) => {\n\t\t\tfor widget in tbl.sequence_values::<Renderable>() {\n\t\t\t\tmatch widget {\n\t\t\t\t\tOk(w) => w.render_with(buf, trans),\n\t\t\t\t\tErr(e) => error!(\"Failed to convert to renderable elements: {e}\"),\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tValue::UserData(ud) => match Renderable::try_from(&ud) {\n\t\t\tOk(w) => w.render_with(buf, trans),\n\t\t\tErr(e) => error!(\"Failed to convert to renderable element: {e}\"),\n\t\t},\n\t\t_ => error!(\"Expected a renderable element, or a table of them, got: {value:?}\"),\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/elements/gauge.rs",
    "content": "use mlua::{AnyUserData, ExternalError, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value};\nuse ratatui::widgets::Widget;\n\nuse super::{Area, Span};\nuse crate::Style;\n\n#[derive(Clone, Debug, Default)]\npub struct Gauge {\n\tpub(super) area: Area,\n\n\tratio:       f64,\n\tlabel:       Option<ratatui::text::Span<'static>>,\n\tstyle:       ratatui::style::Style,\n\tgauge_style: ratatui::style::Style,\n}\n\nimpl Gauge {\n\tpub fn compose(lua: &Lua) -> mlua::Result<Value> {\n\t\tlet new = lua.create_function(|_, _: Table| Ok(Self::default()))?;\n\n\t\tlet gauge = lua.create_table()?;\n\t\tgauge.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;\n\n\t\tgauge.into_lua(lua)\n\t}\n}\n\nimpl Widget for Gauge {\n\tfn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)\n\twhere\n\t\tSelf: Sized,\n\t{\n\t\tlet mut gauge = ratatui::widgets::Gauge::default()\n\t\t\t.ratio(self.ratio)\n\t\t\t.style(self.style)\n\t\t\t.gauge_style(self.gauge_style);\n\n\t\tif let Some(s) = self.label {\n\t\t\tgauge = gauge.label(s)\n\t\t}\n\n\t\tgauge.render(rect, buf);\n\t}\n}\n\nimpl Widget for &Gauge {\n\tfn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)\n\twhere\n\t\tSelf: Sized,\n\t{\n\t\tself.clone().render(rect, buf);\n\t}\n}\n\nimpl UserData for Gauge {\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tcrate::impl_area_method!(methods);\n\t\tcrate::impl_style_method!(methods, style);\n\n\t\tmethods.add_function_mut(\"percent\", |_, (ud, percent): (AnyUserData, u8)| {\n\t\t\tif percent > 100 {\n\t\t\t\treturn Err(\"percent must be between 0 and 100\".into_lua_err());\n\t\t\t}\n\n\t\t\tud.borrow_mut::<Self>()?.ratio = percent as f64 / 100.0;\n\t\t\tOk(ud)\n\t\t});\n\n\t\tmethods.add_function_mut(\"ratio\", |_, (ud, ratio): (AnyUserData, f64)| {\n\t\t\tif !(0.0..1.0).contains(&ratio) {\n\t\t\t\treturn Err(\"ratio must be between 0 and 1\".into_lua_err());\n\t\t\t}\n\n\t\t\tud.borrow_mut::<Self>()?.ratio = ratio;\n\t\t\tOk(ud)\n\t\t});\n\n\t\tmethods.add_function_mut(\"label\", |_, (ud, label): (AnyUserData, Span)| {\n\t\t\tud.borrow_mut::<Self>()?.label = Some(label.0);\n\t\t\tOk(ud)\n\t\t});\n\n\t\tmethods.add_function_mut(\"gauge_style\", |_, (ud, style): (AnyUserData, Style)| {\n\t\t\tud.borrow_mut::<Self>()?.gauge_style = style.0;\n\t\t\tOk(ud)\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/elements/layout.rs",
    "content": "use mlua::{AnyUserData, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value};\n\nuse super::{Constraint, Rect};\n\nconst HORIZONTAL: bool = true;\nconst VERTICAL: bool = false;\n\n#[derive(Clone, Default)]\npub struct Layout {\n\tdirection:   bool,\n\tmargin:      Option<ratatui::layout::Margin>,\n\tconstraints: Vec<ratatui::layout::Constraint>,\n}\n\nimpl Layout {\n\tpub fn compose(lua: &Lua) -> mlua::Result<Value> {\n\t\tlet new = lua.create_function(|_, _: Table| Ok(Self::default()))?;\n\n\t\tlet layout = lua.create_table_from([(\"HORIZONTAL\", HORIZONTAL), (\"VERTICAL\", VERTICAL)])?;\n\t\tlayout.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;\n\n\t\tlayout.into_lua(lua)\n\t}\n}\n\nimpl UserData for Layout {\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_function_mut(\"direction\", |_, (ud, value): (AnyUserData, bool)| {\n\t\t\tud.borrow_mut::<Self>()?.direction = value;\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"margin\", |_, (ud, value): (AnyUserData, u16)| {\n\t\t\tud.borrow_mut::<Self>()?.margin = Some(ratatui::layout::Margin::new(value, value));\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"margin_h\", |_, (ud, value): (AnyUserData, u16)| {\n\t\t\t{\n\t\t\t\tlet mut me = ud.borrow_mut::<Self>()?;\n\t\t\t\tif let Some(margin) = &mut me.margin {\n\t\t\t\t\tmargin.horizontal = value;\n\t\t\t\t} else {\n\t\t\t\t\tme.margin = Some(ratatui::layout::Margin::new(value, 0));\n\t\t\t\t}\n\t\t\t}\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"margin_v\", |_, (ud, value): (AnyUserData, u16)| {\n\t\t\t{\n\t\t\t\tlet mut me = ud.borrow_mut::<Self>()?;\n\t\t\t\tif let Some(margin) = &mut me.margin {\n\t\t\t\t\tmargin.vertical = value;\n\t\t\t\t} else {\n\t\t\t\t\tme.margin = Some(ratatui::layout::Margin::new(0, value));\n\t\t\t\t}\n\t\t\t}\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"constraints\", |_, (ud, value): (AnyUserData, Vec<Constraint>)| {\n\t\t\tud.borrow_mut::<Self>()?.constraints = value.into_iter().map(Into::into).collect();\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_method(\"split\", |lua, me, value: Rect| {\n\t\t\tlet mut layout = ratatui::layout::Layout::new(\n\t\t\t\tif me.direction == VERTICAL {\n\t\t\t\t\tratatui::layout::Direction::Vertical\n\t\t\t\t} else {\n\t\t\t\t\tratatui::layout::Direction::Horizontal\n\t\t\t\t},\n\t\t\t\t&me.constraints,\n\t\t\t);\n\n\t\t\tif let Some(margin) = me.margin {\n\t\t\t\tlayout = layout.horizontal_margin(margin.horizontal);\n\t\t\t\tlayout = layout.vertical_margin(margin.vertical);\n\t\t\t}\n\n\t\t\tlua.create_sequence_from(layout.split(*value).iter().map(|chunk| Rect::from(*chunk)))\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/elements/line.rs",
    "content": "use std::{borrow::Cow, mem, ops::{Deref, DerefMut}};\n\nuse ansi_to_tui::IntoText;\nuse mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value};\nuse ratatui::widgets::Widget;\nuse unicode_width::UnicodeWidthChar;\n\nuse super::{Area, Span};\nuse crate::elements::Align;\n\nconst EXPECTED: &str = \"expected a string, Span, Line, or a table of them\";\n\n#[derive(Clone, Debug, Default)]\npub struct Line {\n\tpub(super) area: Area,\n\n\tpub(super) inner: ratatui::text::Line<'static>,\n}\n\nimpl Deref for Line {\n\ttype Target = ratatui::text::Line<'static>;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl DerefMut for Line {\n\tfn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }\n}\n\nimpl Line {\n\tpub fn compose(lua: &Lua) -> mlua::Result<Value> {\n\t\tlet new = lua.create_function(|_, (_, line): (Table, Self)| Ok(line))?;\n\n\t\tlet parse = lua.create_function(|_, code: mlua::String| {\n\t\t\tlet code = code.as_bytes();\n\t\t\tlet Some(line) = code.split_inclusive(|&b| b == b'\\n').next() else {\n\t\t\t\treturn Ok(Self::default());\n\t\t\t};\n\n\t\t\tlet mut lines = line.into_text().into_lua_err()?.lines;\n\t\t\tif lines.is_empty() {\n\t\t\t\treturn Ok(Self::default());\n\t\t\t}\n\n\t\t\tOk(Self { inner: mem::take(&mut lines[0]), ..Default::default() })\n\t\t})?;\n\n\t\tlet line = lua.create_table_from([(\"parse\", parse)])?;\n\t\tline.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;\n\t\tline.into_lua(lua)\n\t}\n}\n\nimpl TryFrom<Table> for Line {\n\ttype Error = mlua::Error;\n\n\tfn try_from(tb: Table) -> Result<Self, Self::Error> {\n\t\tlet mut spans = Vec::with_capacity(tb.raw_len());\n\t\tfor v in tb.sequence_values() {\n\t\t\tmatch v? {\n\t\t\t\tValue::String(s) => spans.push(s.to_string_lossy().into()),\n\t\t\t\tValue::UserData(ud) => {\n\t\t\t\t\tif let Ok(Span(span)) = ud.take() {\n\t\t\t\t\t\tspans.push(span);\n\t\t\t\t\t} else if let Ok(Self { inner: mut line, .. }) = ud.take() {\n\t\t\t\t\t\tline.spans.iter_mut().for_each(|s| s.style = line.style.patch(s.style));\n\t\t\t\t\t\tspans.extend(line.spans);\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn Err(EXPECTED.into_lua_err());\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t_ => Err(EXPECTED.into_lua_err())?,\n\t\t\t}\n\t\t}\n\t\tOk(Self { inner: spans.into(), ..Default::default() })\n\t}\n}\n\nimpl From<Line> for ratatui::text::Line<'static> {\n\tfn from(value: Line) -> Self { value.inner }\n}\n\nimpl Widget for Line {\n\tfn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)\n\twhere\n\t\tSelf: Sized,\n\t{\n\t\t(&self).render(rect, buf);\n\t}\n}\n\nimpl Widget for &Line {\n\tfn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)\n\twhere\n\t\tSelf: Sized,\n\t{\n\t\t(&self.inner).render(rect, buf);\n\t}\n}\n\nimpl FromLua for Line {\n\tfn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {\n\t\tOk(Self {\n\t\t\tinner: match value {\n\t\t\t\tValue::Table(tb) => return Self::try_from(tb),\n\t\t\t\tValue::String(s) => s.to_string_lossy().into(),\n\t\t\t\tValue::UserData(ud) => {\n\t\t\t\t\tif let Ok(Span(span)) = ud.take() {\n\t\t\t\t\t\tspan.into()\n\t\t\t\t\t} else if let Ok(line) = ud.take() {\n\t\t\t\t\t\treturn Ok(line);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tErr(EXPECTED.into_lua_err())?\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t_ => Err(EXPECTED.into_lua_err())?,\n\t\t\t},\n\t\t\t..Default::default()\n\t\t})\n\t}\n}\n\nimpl UserData for Line {\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tcrate::impl_area_method!(methods);\n\t\tcrate::impl_style_method!(methods, style);\n\t\tcrate::impl_style_shorthands!(methods, style);\n\n\t\tmethods.add_method(\"width\", |_, me, ()| Ok(me.width()));\n\t\tmethods.add_function_mut(\"align\", |_, (ud, align): (AnyUserData, Align)| {\n\t\t\tud.borrow_mut::<Self>()?.alignment = Some(align.0);\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_method(\"visible\", |_, me, ()| {\n\t\t\tOk(me.iter().flat_map(|s| s.content.chars()).any(|c| c.width().unwrap_or(0) > 0))\n\t\t});\n\t\tmethods.add_function_mut(\"truncate\", |lua, (ud, t): (AnyUserData, Table)| {\n\t\t\tlet mut me = ud.borrow_mut::<Self>()?;\n\n\t\t\tlet max = t.raw_get(\"max\")?;\n\t\t\tif max < 1 {\n\t\t\t\tme.spans.clear();\n\t\t\t\treturn Ok(ud);\n\t\t\t}\n\n\t\t\tlet ellipsis = match t.raw_get::<Value>(\"ellipsis\")? {\n\t\t\t\tValue::Nil => (1, ratatui::text::Span::raw(\"…\")),\n\t\t\t\tv => {\n\t\t\t\t\tlet mut span = Span::from_lua(v, lua)?;\n\t\t\t\t\t(span.truncate(max), span.0)\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tfn traverse(\n\t\t\t\tmax: usize,\n\t\t\t\tthreshold: usize,\n\t\t\t\tit: impl Iterator<Item = (usize, usize, char)>,\n\t\t\t) -> (Option<(usize, usize, usize)>, bool) {\n\t\t\t\tlet (mut adv, mut cut) = (0, None);\n\t\t\t\tfor (x, y, c) in it {\n\t\t\t\t\tadv += c.width().unwrap_or(0);\n\t\t\t\t\tif adv <= threshold {\n\t\t\t\t\t\tcut = Some((x, y, adv));\n\t\t\t\t\t} else if adv > max {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t(cut, adv > max)\n\t\t\t}\n\n\t\t\tlet rtl = t.raw_get(\"rtl\")?;\n\t\t\tlet (cut, remain) = if rtl {\n\t\t\t\ttraverse(\n\t\t\t\t\tmax,\n\t\t\t\t\tmax - ellipsis.0,\n\t\t\t\t\tme.iter()\n\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t.rev()\n\t\t\t\t\t\t.flat_map(|(x, s)| s.content.char_indices().rev().map(move |(y, c)| (x, y, c))),\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\ttraverse(\n\t\t\t\t\tmax,\n\t\t\t\t\tmax - ellipsis.0,\n\t\t\t\t\tme.iter()\n\t\t\t\t\t\t.enumerate()\n\t\t\t\t\t\t.flat_map(|(x, s)| s.content.char_indices().map(move |(y, c)| (x, y, c))),\n\t\t\t\t)\n\t\t\t};\n\n\t\t\tlet Some((x, y, width)) = cut else {\n\t\t\t\tme.spans.clear();\n\t\t\t\tme.spans.push(ellipsis.1);\n\t\t\t\treturn Ok(ud);\n\t\t\t};\n\t\t\tif !remain {\n\t\t\t\treturn Ok(ud);\n\t\t\t}\n\n\t\t\tlet spans = &mut me.spans;\n\t\t\tlet len = match (rtl, width == max) {\n\t\t\t\t(a, b) if a == b => spans[x].content[y..].chars().next().map_or(0, |c| c.len_utf8()),\n\t\t\t\t_ => 0,\n\t\t\t};\n\n\t\t\tif rtl {\n\t\t\t\tmatch &mut spans[x].content {\n\t\t\t\t\tCow::Borrowed(s) => spans[x].content = Cow::Borrowed(&s[y + len..]),\n\t\t\t\t\tCow::Owned(s) => _ = s.drain(..y + len),\n\t\t\t\t}\n\t\t\t\tspans.splice(..x, [ellipsis.1]);\n\t\t\t} else {\n\t\t\t\tmatch &mut spans[x].content {\n\t\t\t\t\tCow::Borrowed(s) => spans[x].content = Cow::Borrowed(&s[..y + len]),\n\t\t\t\t\tCow::Owned(s) => s.truncate(y + len),\n\t\t\t\t}\n\t\t\t\tspans.truncate(x + 1);\n\t\t\t\tspans.push(ellipsis.1);\n\t\t\t}\n\n\t\t\tOk(ud)\n\t\t});\n\t}\n}\n\n#[cfg(test)]\nmod tests {\n\tuse mlua::{UserDataRef, chunk};\n\n\tuse super::*;\n\n\tfn truncate(s: &str, max: usize, rtl: bool) -> String {\n\t\tlet lua = Lua::new();\n\t\tlet comp = Line::compose(&lua).unwrap();\n\t\tlet line: UserDataRef<Line> = lua\n\t\t\t.load(chunk! {\n\t\t\t\treturn $comp($s):truncate { max = $max, rtl = $rtl }\n\t\t\t})\n\t\t\t.call(())\n\t\t\t.unwrap();\n\n\t\tline.spans.iter().map(|s| &*s.content).collect()\n\t}\n\n\t#[test]\n\tfn test_truncate() {\n\t\tassert_eq!(truncate(\"你好，world\", 0, false), \"\");\n\t\tassert_eq!(truncate(\"你好，world\", 1, false), \"…\");\n\t\tassert_eq!(truncate(\"你好，world\", 2, false), \"…\");\n\n\t\tassert_eq!(truncate(\"你好，世界\", 3, false), \"你…\");\n\t\tassert_eq!(truncate(\"你好，世界\", 4, false), \"你…\");\n\t\tassert_eq!(truncate(\"你好，世界\", 5, false), \"你好…\");\n\n\t\tassert_eq!(truncate(\"Hello, world\", 5, false), \"Hell…\");\n\t\tassert_eq!(truncate(\"Ni好，世界\", 3, false), \"Ni…\");\n\t}\n\n\t#[test]\n\tfn test_truncate_rtl() {\n\t\tassert_eq!(truncate(\"world，你好\", 0, true), \"\");\n\t\tassert_eq!(truncate(\"world，你好\", 1, true), \"…\");\n\t\tassert_eq!(truncate(\"world，你好\", 2, true), \"…\");\n\n\t\tassert_eq!(truncate(\"你好，世界\", 3, true), \"…界\");\n\t\tassert_eq!(truncate(\"你好，世界\", 4, true), \"…界\");\n\t\tassert_eq!(truncate(\"你好，世界\", 5, true), \"…世界\");\n\n\t\tassert_eq!(truncate(\"Hello, world\", 5, true), \"…orld\");\n\t\tassert_eq!(truncate(\"你好，Shi界\", 3, true), \"…界\");\n\t}\n\n\t#[test]\n\tfn test_truncate_oboe() {\n\t\tassert_eq!(truncate(\"Hello, world\", 11, false), \"Hello, wor…\");\n\t\tassert_eq!(truncate(\"你好，世界\", 9, false), \"你好，世…\");\n\t\tassert_eq!(truncate(\"你好，世Jie\", 9, false), \"你好，世…\");\n\n\t\tassert_eq!(truncate(\"Hello, world\", 11, true), \"…llo, world\");\n\t\tassert_eq!(truncate(\"你好，世界\", 9, true), \"…好，世界\");\n\t\tassert_eq!(truncate(\"Ni好，世界\", 9, true), \"…好，世界\");\n\t}\n\n\t#[test]\n\tfn test_truncate_exact() {\n\t\tassert_eq!(truncate(\"Hello, world\", 12, false), \"Hello, world\");\n\t\tassert_eq!(truncate(\"你好，世界\", 10, false), \"你好，世界\");\n\n\t\tassert_eq!(truncate(\"Hello, world\", 12, true), \"Hello, world\");\n\t\tassert_eq!(truncate(\"你好，世界\", 10, true), \"你好，世界\");\n\t}\n\n\t#[test]\n\tfn test_truncate_overflow() {\n\t\tassert_eq!(truncate(\"Hello, world\", 13, false), \"Hello, world\");\n\t\tassert_eq!(truncate(\"你好，世界\", 11, false), \"你好，世界\");\n\n\t\tassert_eq!(truncate(\"Hello, world\", 13, true), \"Hello, world\");\n\t\tassert_eq!(truncate(\"你好，世界\", 11, true), \"你好，世界\");\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/elements/list.rs",
    "content": "use mlua::{IntoLua, Lua, MetaMethod, Table, UserData, Value};\nuse ratatui::widgets::Widget;\n\nuse super::{Area, Text};\n\n// --- List\n#[derive(Clone, Debug, Default)]\npub struct List {\n\tpub(super) area: Area,\n\n\tinner: ratatui::widgets::List<'static>,\n}\n\nimpl List {\n\tpub fn compose(lua: &Lua) -> mlua::Result<Value> {\n\t\tlet new = lua.create_function(|_, (_, items): (Table, Vec<Text>)| {\n\t\t\tOk(Self { inner: ratatui::widgets::List::new(items), ..Default::default() })\n\t\t})?;\n\n\t\tlet list = lua.create_table()?;\n\t\tlist.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;\n\n\t\tlist.into_lua(lua)\n\t}\n}\n\nimpl Widget for List {\n\tfn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)\n\twhere\n\t\tSelf: Sized,\n\t{\n\t\t(&self).render(rect, buf);\n\t}\n}\n\nimpl Widget for &List {\n\tfn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)\n\twhere\n\t\tSelf: Sized,\n\t{\n\t\t(&self.inner).render(rect, buf);\n\t}\n}\n\nimpl UserData for List {\n\tfn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {\n\t\tcrate::impl_area_method!(methods);\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/elements/mod.rs",
    "content": "yazi_macro::mod_flat!(align area bar border cell clear constraint edge elements gauge layout line list pad pos rect renderable row span table text wrap);\n"
  },
  {
    "path": "yazi-binding/src/elements/pad.rs",
    "content": "use std::ops::{Add, AddAssign, Deref};\n\nuse mlua::{FromLua, IntoLua, Lua, MetaMethod, Table, UserData, Value};\n\n#[derive(Clone, Copy, Default, FromLua)]\npub struct Pad(ratatui::widgets::Padding);\n\nimpl Deref for Pad {\n\ttype Target = ratatui::widgets::Padding;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl From<ratatui::widgets::Padding> for Pad {\n\tfn from(pad: ratatui::widgets::Padding) -> Self { Self(pad) }\n}\n\nimpl Pad {\n\tpub fn compose(lua: &Lua) -> mlua::Result<Value> {\n\t\tlet new =\n\t\t\tlua.create_function(|_, (_, top, right, bottom, left): (Table, u16, u16, u16, u16)| {\n\t\t\t\tOk(Self(ratatui::widgets::Padding::new(left, right, top, bottom)))\n\t\t\t})?;\n\n\t\tlet pad = lua.create_table_from([\n\t\t\t(\n\t\t\t\t\"left\",\n\t\t\t\tlua.create_function(|_, left: u16| Ok(Self(ratatui::widgets::Padding::left(left))))?,\n\t\t\t),\n\t\t\t(\n\t\t\t\t\"right\",\n\t\t\t\tlua.create_function(|_, right: u16| Ok(Self(ratatui::widgets::Padding::right(right))))?,\n\t\t\t),\n\t\t\t(\"top\", lua.create_function(|_, top: u16| Ok(Self(ratatui::widgets::Padding::top(top))))?),\n\t\t\t(\n\t\t\t\t\"bottom\",\n\t\t\t\tlua\n\t\t\t\t\t.create_function(|_, bottom: u16| Ok(Self(ratatui::widgets::Padding::bottom(bottom))))?,\n\t\t\t),\n\t\t\t(\"x\", lua.create_function(|_, x: u16| Ok(Self(ratatui::widgets::Padding::new(x, x, 0, 0))))?),\n\t\t\t(\"y\", lua.create_function(|_, y: u16| Ok(Self(ratatui::widgets::Padding::new(0, 0, y, y))))?),\n\t\t\t(\n\t\t\t\t\"xy\",\n\t\t\t\tlua\n\t\t\t\t\t.create_function(|_, xy: u16| Ok(Self(ratatui::widgets::Padding::new(xy, xy, xy, xy))))?,\n\t\t\t),\n\t\t])?;\n\n\t\tpad.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;\n\t\tpad.into_lua(lua)\n\t}\n}\n\nimpl UserData for Pad {\n\tfn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {\n\t\tfields.add_field_method_get(\"left\", |_, me| Ok(me.left));\n\t\tfields.add_field_method_get(\"right\", |_, me| Ok(me.right));\n\t\tfields.add_field_method_get(\"top\", |_, me| Ok(me.top));\n\t\tfields.add_field_method_get(\"bottom\", |_, me| Ok(me.bottom));\n\t}\n}\n\nimpl Add<ratatui::widgets::Padding> for Pad {\n\ttype Output = Self;\n\n\tfn add(self, rhs: ratatui::widgets::Padding) -> Self::Output {\n\t\tSelf(ratatui::widgets::Padding::new(\n\t\t\tself.left.saturating_add(rhs.left),\n\t\t\tself.right.saturating_add(rhs.right),\n\t\t\tself.top.saturating_add(rhs.top),\n\t\t\tself.bottom.saturating_add(rhs.bottom),\n\t\t))\n\t}\n}\n\nimpl AddAssign<ratatui::widgets::Padding> for Pad {\n\tfn add_assign(&mut self, rhs: ratatui::widgets::Padding) { *self = *self + rhs; }\n}\n"
  },
  {
    "path": "yazi-binding/src/elements/pos.rs",
    "content": "use std::{ops::Deref, str::FromStr};\n\nuse mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, Value};\n\nuse super::Pad;\n\nconst EXPECTED: &str = \"expected a Pos\";\n\n#[derive(Clone, Copy, Default)]\npub struct Pos {\n\tinner: yazi_config::popup::Position,\n\n\tpub(super) pad: Pad,\n}\n\nimpl Deref for Pos {\n\ttype Target = yazi_config::popup::Position;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl From<yazi_config::popup::Position> for Pos {\n\tfn from(value: yazi_config::popup::Position) -> Self {\n\t\tSelf { inner: value, ..Default::default() }\n\t}\n}\n\nimpl From<Pos> for yazi_config::popup::Position {\n\tfn from(value: Pos) -> Self { value.inner }\n}\n\nimpl TryFrom<Table> for Pos {\n\ttype Error = mlua::Error;\n\n\tfn try_from(t: Table) -> Result<Self, Self::Error> {\n\t\tuse yazi_config::popup::{Offset, Origin, Position};\n\n\t\tOk(Self::from(Position {\n\t\t\torigin: Origin::from_str(&t.raw_get::<mlua::String>(1)?.to_str()?).into_lua_err()?,\n\t\t\toffset: Offset {\n\t\t\t\tx:      t.raw_get(\"x\").unwrap_or_default(),\n\t\t\t\ty:      t.raw_get(\"y\").unwrap_or_default(),\n\t\t\t\twidth:  t.raw_get(\"w\").unwrap_or_default(),\n\t\t\t\theight: t.raw_get(\"h\").unwrap_or_default(),\n\t\t\t},\n\t\t}))\n\t}\n}\n\nimpl FromLua for Pos {\n\tfn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {\n\t\tOk(match value {\n\t\t\tValue::Table(tbl) => Self::try_from(tbl)?,\n\t\t\tValue::UserData(ud) => {\n\t\t\t\tif let Ok(pos) = ud.borrow() {\n\t\t\t\t\t*pos\n\t\t\t\t} else {\n\t\t\t\t\tErr(EXPECTED.into_lua_err())?\n\t\t\t\t}\n\t\t\t}\n\t\t\t_ => Err(EXPECTED.into_lua_err())?,\n\t\t})\n\t}\n}\n\nimpl Pos {\n\tpub fn compose(lua: &Lua) -> mlua::Result<Value> {\n\t\tlet new = lua.create_function(|_, (_, t): (Table, Table)| Self::try_from(t))?;\n\n\t\tlet position = lua.create_table()?;\n\t\tposition.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;\n\n\t\tposition.into_lua(lua)\n\t}\n\n\tpub fn with_height(mut self, height: u16) -> Self {\n\t\tself.inner.offset.height = height;\n\t\tself\n\t}\n}\n\nimpl UserData for Pos {\n\tfn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {\n\t\t// TODO: cache\n\t\tfields.add_field_method_get(\"1\", |_, me| Ok(me.origin.to_string()));\n\t\tfields.add_field_method_get(\"x\", |_, me| Ok(me.offset.x));\n\t\tfields.add_field_method_get(\"y\", |_, me| Ok(me.offset.y));\n\t\tfields.add_field_method_get(\"w\", |_, me| Ok(me.offset.width));\n\t\tfields.add_field_method_get(\"h\", |_, me| Ok(me.offset.height));\n\t}\n\n\tfn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_function_mut(\"pad\", |_, (ud, pad): (AnyUserData, Pad)| {\n\t\t\tud.borrow_mut::<Self>()?.pad = pad;\n\t\t\tOk(ud)\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/elements/rect.rs",
    "content": "use std::ops::Deref;\n\nuse mlua::{FromLua, IntoLua, Lua, MetaMethod, Table, UserData, Value};\n\nuse super::Pad;\n\n#[derive(Clone, Copy, Debug, Default, FromLua)]\npub struct Rect(pub ratatui::layout::Rect);\n\nimpl Deref for Rect {\n\ttype Target = ratatui::layout::Rect;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl From<ratatui::layout::Rect> for Rect {\n\tfn from(rect: ratatui::layout::Rect) -> Self { Self(rect) }\n}\n\nimpl From<ratatui::layout::Size> for Rect {\n\tfn from(size: ratatui::layout::Size) -> Self {\n\t\tSelf(ratatui::layout::Rect { x: 0, y: 0, width: size.width, height: size.height })\n\t}\n}\n\nimpl Rect {\n\tpub fn compose(lua: &Lua) -> mlua::Result<Value> {\n\t\tlet new = lua.create_function(|_, (_, args): (Table, Table)| {\n\t\t\tOk(Self(ratatui::layout::Rect {\n\t\t\t\tx:      args.raw_get(\"x\").unwrap_or_default(),\n\t\t\t\ty:      args.raw_get(\"y\").unwrap_or_default(),\n\t\t\t\twidth:  args.raw_get(\"w\").unwrap_or_default(),\n\t\t\t\theight: args.raw_get(\"h\").unwrap_or_default(),\n\t\t\t}))\n\t\t})?;\n\n\t\tlet rect = lua.create_table()?;\n\t\trect.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;\n\t\trect.into_lua(lua)\n\t}\n\n\tpub(super) fn pad(self, pad: Pad) -> Self {\n\t\tlet mut r = *self;\n\t\tr.x = r.x.saturating_add(pad.left);\n\t\tr.y = r.y.saturating_add(pad.top);\n\n\t\tr.width = r.width.saturating_sub(pad.left + pad.right);\n\t\tr.height = r.height.saturating_sub(pad.top + pad.bottom);\n\t\tSelf(r)\n\t}\n}\n\nimpl UserData for Rect {\n\tfn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {\n\t\tfields.add_field_method_get(\"x\", |_, me| Ok(me.x));\n\t\tfields.add_field_method_get(\"y\", |_, me| Ok(me.y));\n\t\tfields.add_field_method_get(\"w\", |_, me| Ok(me.width));\n\t\tfields.add_field_method_get(\"h\", |_, me| Ok(me.height));\n\n\t\tfields.add_field_method_set(\"x\", |_, me, x| Ok(me.0.x = x));\n\t\tfields.add_field_method_set(\"y\", |_, me, y| Ok(me.0.y = y));\n\t\tfields.add_field_method_set(\"w\", |_, me, w| Ok(me.0.width = w));\n\t\tfields.add_field_method_set(\"h\", |_, me, h| Ok(me.0.height = h));\n\n\t\tfields.add_field_method_get(\"left\", |_, me| Ok(me.left()));\n\t\tfields.add_field_method_get(\"right\", |_, me| Ok(me.right()));\n\t\tfields.add_field_method_get(\"top\", |_, me| Ok(me.top()));\n\t\tfields.add_field_method_get(\"bottom\", |_, me| Ok(me.bottom()));\n\t}\n\n\tfn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_method(\"pad\", |_, me, pad: Pad| Ok(me.pad(pad)));\n\t\tmethods.add_method(\"contains\", |_, me, Self(rect)| Ok(me.contains(rect.into())));\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/elements/renderable.rs",
    "content": "use std::any::TypeId;\n\nuse mlua::{AnyUserData, ExternalError, FromLua, Lua, Value};\nuse ratatui::widgets::Widget;\n\nuse super::{Bar, Border, Clear, Gauge, Line, List, Table, Text};\nuse crate::{Error, elements::Area};\n\n#[derive(Clone, Debug)]\npub enum Renderable {\n\tLine(Line),\n\tText(Text),\n\tList(Box<List>),\n\tBar(Bar),\n\tClear(Clear),\n\tBorder(Border),\n\tGauge(Box<Gauge>),\n\tTable(Box<Table>),\n}\n\nimpl Renderable {\n\tpub fn area(&self) -> Area {\n\t\tmatch self {\n\t\t\tSelf::Line(line) => line.area,\n\t\t\tSelf::Text(text) => text.area,\n\t\t\tSelf::List(list) => list.area,\n\t\t\tSelf::Bar(bar) => bar.area,\n\t\t\tSelf::Clear(clear) => clear.area,\n\t\t\tSelf::Border(border) => border.area,\n\t\t\tSelf::Gauge(gauge) => gauge.area,\n\t\t\tSelf::Table(table) => table.area,\n\t\t}\n\t}\n\n\tpub fn with_area(mut self, area: impl Into<Area>) -> Self {\n\t\tlet area = area.into();\n\t\tmatch &mut self {\n\t\t\tSelf::Line(line) => line.area = area,\n\t\t\tSelf::Text(text) => text.area = area,\n\t\t\tSelf::List(list) => list.area = area,\n\t\t\tSelf::Bar(bar) => bar.area = area,\n\t\t\tSelf::Clear(clear) => clear.area = area,\n\t\t\tSelf::Border(border) => border.area = area,\n\t\t\tSelf::Gauge(gauge) => gauge.area = area,\n\t\t\tSelf::Table(table) => table.area = area,\n\t\t}\n\t\tself\n\t}\n\n\tpub fn render_with<T>(self, buf: &mut ratatui::buffer::Buffer, trans: T)\n\twhere\n\t\tT: FnOnce(yazi_config::popup::Position) -> ratatui::layout::Rect,\n\t{\n\t\tlet rect = self.area().transform(trans);\n\t\tself.render(rect, buf);\n\t}\n}\n\nimpl TryFrom<&AnyUserData> for Renderable {\n\ttype Error = mlua::Error;\n\n\tfn try_from(ud: &AnyUserData) -> Result<Self, Self::Error> {\n\t\tOk(match ud.type_id() {\n\t\t\tSome(t) if t == TypeId::of::<Line>() => Self::Line(ud.take()?),\n\t\t\tSome(t) if t == TypeId::of::<Text>() => Self::Text(ud.take()?),\n\t\t\tSome(t) if t == TypeId::of::<List>() => Self::List(Box::new(ud.take()?)),\n\t\t\tSome(t) if t == TypeId::of::<Bar>() => Self::Bar(ud.take()?),\n\t\t\tSome(t) if t == TypeId::of::<Clear>() => Self::Clear(ud.take()?),\n\t\t\tSome(t) if t == TypeId::of::<Border>() => Self::Border(ud.take()?),\n\t\t\tSome(t) if t == TypeId::of::<Gauge>() => Self::Gauge(Box::new(ud.take()?)),\n\t\t\tSome(t) if t == TypeId::of::<Table>() => Self::Table(Box::new(ud.take()?)),\n\t\t\t_ => Err(format!(\"expected a renderable userdata, not: {ud:#?}\").into_lua_err())?,\n\t\t})\n\t}\n}\n\nimpl From<Error> for Renderable {\n\tfn from(error: Error) -> Self {\n\t\tSelf::Text(Text {\n\t\t\tinner: error.into_string().into(),\n\t\t\twrap: ratatui::widgets::Wrap { trim: false }.into(),\n\t\t\t..Default::default()\n\t\t})\n\t}\n}\n\nimpl Widget for Renderable {\n\tfn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)\n\twhere\n\t\tSelf: Sized,\n\t{\n\t\tmatch self {\n\t\t\tSelf::Line(line) => line.render(rect, buf),\n\t\t\tSelf::Text(text) => text.render(rect, buf),\n\t\t\tSelf::List(list) => list.render(rect, buf),\n\t\t\tSelf::Bar(bar) => bar.render(rect, buf),\n\t\t\tSelf::Clear(clear) => clear.render(rect, buf),\n\t\t\tSelf::Border(border) => border.render(rect, buf),\n\t\t\tSelf::Gauge(gauge) => gauge.render(rect, buf),\n\t\t\tSelf::Table(table) => table.render(rect, buf),\n\t\t}\n\t}\n}\n\nimpl Widget for &Renderable {\n\tfn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)\n\twhere\n\t\tSelf: Sized,\n\t{\n\t\tmatch self {\n\t\t\tRenderable::Line(line) => line.render(rect, buf),\n\t\t\tRenderable::Text(text) => text.render(rect, buf),\n\t\t\tRenderable::List(list) => (&**list).render(rect, buf),\n\t\t\tRenderable::Bar(bar) => bar.render(rect, buf),\n\t\t\tRenderable::Clear(clear) => clear.render(rect, buf),\n\t\t\tRenderable::Border(border) => border.render(rect, buf),\n\t\t\tRenderable::Gauge(gauge) => (&**gauge).render(rect, buf),\n\t\t\tRenderable::Table(table) => (&**table).render(rect, buf),\n\t\t}\n\t}\n}\n\nimpl FromLua for Renderable {\n\tfn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {\n\t\tmatch value {\n\t\t\tValue::UserData(ud) => Self::try_from(&ud),\n\t\t\t_ => Err(format!(\"expected a renderable userdata, not: {value:#?}\").into_lua_err()),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/elements/row.rs",
    "content": "use mlua::{AnyUserData, ExternalError, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, Value};\n\nuse super::Cell;\n\nconst EXPECTED: &str = \"expected a Row\";\n\n#[derive(Clone, Debug, Default)]\npub struct Row {\n\tpub(super) cells: Vec<Cell>,\n\theight:           u16,\n\ttop_margin:       u16,\n\tbottom_margin:    u16,\n\tstyle:            ratatui::style::Style,\n}\n\nimpl Row {\n\tpub fn compose(lua: &Lua) -> mlua::Result<Value> {\n\t\tlet new = lua.create_function(|_, (_, cells): (Table, Vec<Cell>)| {\n\t\t\tOk(Self { cells, ..Default::default() })\n\t\t})?;\n\n\t\tlet row = lua.create_table()?;\n\t\trow.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;\n\n\t\trow.into_lua(lua)\n\t}\n}\n\nimpl From<Row> for ratatui::widgets::Row<'static> {\n\tfn from(value: Row) -> Self {\n\t\tSelf::new(value.cells)\n\t\t\t.height(value.height.max(1))\n\t\t\t.top_margin(value.top_margin)\n\t\t\t.bottom_margin(value.bottom_margin)\n\t\t\t.style(value.style)\n\t}\n}\n\nimpl FromLua for Row {\n\tfn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {\n\t\tOk(match value {\n\t\t\tValue::UserData(ud) => {\n\t\t\t\tif let Ok(row) = ud.take() {\n\t\t\t\t\trow\n\t\t\t\t} else {\n\t\t\t\t\tErr(EXPECTED.into_lua_err())?\n\t\t\t\t}\n\t\t\t}\n\t\t\t_ => Err(EXPECTED.into_lua_err())?,\n\t\t})\n\t}\n}\n\nimpl UserData for Row {\n\tfn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {\n\t\tcrate::impl_style_method!(methods, style);\n\n\t\tmethods.add_function_mut(\"height\", |_, (ud, value): (AnyUserData, u16)| {\n\t\t\tud.borrow_mut::<Self>()?.height = value;\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"margin_t\", |_, (ud, value): (AnyUserData, u16)| {\n\t\t\tud.borrow_mut::<Self>()?.top_margin = value;\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"margin_b\", |_, (ud, value): (AnyUserData, u16)| {\n\t\t\tud.borrow_mut::<Self>()?.bottom_margin = value;\n\t\t\tOk(ud)\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/elements/span.rs",
    "content": "use std::{borrow::Cow, ops::{Deref, DerefMut}};\n\nuse mlua::{AnyUserData, ExternalError, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value};\nuse unicode_width::UnicodeWidthChar;\n\nconst EXPECTED: &str = \"expected a string or Span\";\n\npub struct Span(pub(super) ratatui::text::Span<'static>);\n\nimpl Deref for Span {\n\ttype Target = ratatui::text::Span<'static>;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl DerefMut for Span {\n\tfn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }\n}\n\nimpl Span {\n\tpub fn compose(lua: &Lua) -> mlua::Result<Value> {\n\t\tlet new = lua.create_function(|_, (_, span): (Table, Self)| Ok(span))?;\n\n\t\tlet span = lua.create_table()?;\n\t\tspan.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;\n\n\t\tspan.into_lua(lua)\n\t}\n\n\tpub(super) fn truncate(&mut self, max: usize) -> usize {\n\t\tif max < 1 {\n\t\t\tmatch &mut self.0.content {\n\t\t\t\tCow::Borrowed(_) => self.0.content = Cow::Borrowed(\"\"),\n\t\t\t\tCow::Owned(s) => s.clear(),\n\t\t\t}\n\t\t\treturn 0;\n\t\t}\n\n\t\tlet mut adv = 0;\n\t\tlet mut last;\n\t\tfor (i, c) in self.0.content.char_indices() {\n\t\t\t(last, adv) = (adv, adv + c.width().unwrap_or(0));\n\t\t\tif adv < max {\n\t\t\t\tcontinue;\n\t\t\t} else if adv == max && self.0.content[i..].chars().nth(1).is_none() {\n\t\t\t\treturn max;\n\t\t\t}\n\t\t\tmatch &mut self.0.content {\n\t\t\t\tCow::Borrowed(s) => self.0.content = format!(\"{}…\", &s[..i]).into(),\n\t\t\t\tCow::Owned(s) => {\n\t\t\t\t\ts.truncate(i);\n\t\t\t\t\ts.push('…');\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn last + 1;\n\t\t}\n\t\tadv\n\t}\n}\n\nimpl FromLua for Span {\n\tfn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {\n\t\tOk(Self(match value {\n\t\t\tValue::String(s) => s.to_string_lossy().into(),\n\t\t\tValue::UserData(ud) => {\n\t\t\t\tif let Ok(Self(span)) = ud.take() {\n\t\t\t\t\tspan\n\t\t\t\t} else {\n\t\t\t\t\tErr(EXPECTED.into_lua_err())?\n\t\t\t\t}\n\t\t\t}\n\t\t\t_ => Err(EXPECTED.into_lua_err())?,\n\t\t}))\n\t}\n}\n\nimpl UserData for Span {\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tcrate::impl_style_method!(methods, 0.style);\n\t\tcrate::impl_style_shorthands!(methods, 0.style);\n\n\t\tmethods.add_method(\"visible\", |_, Self(me), ()| {\n\t\t\tOk(me.content.chars().any(|c| c.width().unwrap_or(0) > 0))\n\t\t});\n\t\tmethods.add_function_mut(\"truncate\", |_, (ud, t): (AnyUserData, Table)| {\n\t\t\tud.borrow_mut::<Self>()?.truncate(t.raw_get(\"max\")?);\n\t\t\tOk(ud)\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/elements/table.rs",
    "content": "use mlua::{AnyUserData, IntoLua, Lua, MetaMethod, UserData, Value};\nuse ratatui::widgets::{StatefulWidget, Widget};\n\nuse super::{Area, Row};\nuse crate::{Style, elements::Constraint};\n\n// --- Table\n#[derive(Clone, Debug, Default)]\npub struct Table {\n\tpub area: Area,\n\n\trows:           Vec<Row>,\n\theader:         Option<ratatui::widgets::Row<'static>>,\n\tfooter:         Option<ratatui::widgets::Row<'static>>,\n\twidths:         Vec<ratatui::layout::Constraint>,\n\tcolumn_spacing: u16,\n\tblock:          Option<ratatui::widgets::Block<'static>>, // TODO\n\n\tstyle:                  ratatui::style::Style,\n\trow_highlight_style:    ratatui::style::Style,\n\tcolumn_highlight_style: ratatui::style::Style,\n\tcell_highlight_style:   ratatui::style::Style,\n\n\thighlight_symbol:  ratatui::text::Text<'static>, // TODO\n\thighlight_spacing: ratatui::widgets::HighlightSpacing, // TODO\n\n\tflex: ratatui::layout::Flex,\n\n\tstate: ratatui::widgets::TableState,\n}\n\nimpl Table {\n\tpub fn compose(lua: &Lua) -> mlua::Result<Value> {\n\t\tlet new = lua.create_function(|_, (_, rows): (mlua::Table, Vec<Row>)| {\n\t\t\tOk(Self { rows, ..Default::default() })\n\t\t})?;\n\n\t\tlet table = lua.create_table()?;\n\t\ttable.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;\n\n\t\ttable.into_lua(lua)\n\t}\n\n\tpub fn selected_cell(&self) -> Option<&ratatui::text::Text<'_>> {\n\t\tlet row = &self.rows[self.selected()?];\n\t\tlet col = self.state.selected_column()?;\n\t\tif row.cells.is_empty() { None } else { Some(&row.cells[col.min(row.cells.len() - 1)].text) }\n\t}\n\n\tpub fn len(&self) -> usize { self.rows.len() }\n\n\tpub fn select(&mut self, idx: Option<usize>) {\n\t\tself\n\t\t\t.state\n\t\t\t.select(idx.map(|i| if self.rows.is_empty() { 0 } else { i.min(self.rows.len() - 1) }));\n\t}\n\n\tpub fn selected(&self) -> Option<usize> {\n\t\tif self.rows.is_empty() { None } else { Some(self.state.selected()?.min(self.rows.len() - 1)) }\n\t}\n}\n\nimpl TryFrom<AnyUserData> for Table {\n\ttype Error = mlua::Error;\n\n\tfn try_from(value: AnyUserData) -> Result<Self, Self::Error> { value.take() }\n}\n\nimpl Widget for Table {\n\tfn render(mut self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)\n\twhere\n\t\tSelf: Sized,\n\t{\n\t\tlet mut table = ratatui::widgets::Table::new(self.rows, self.widths)\n\t\t\t.column_spacing(self.column_spacing)\n\t\t\t.style(self.style)\n\t\t\t.row_highlight_style(self.row_highlight_style)\n\t\t\t.column_highlight_style(self.column_highlight_style)\n\t\t\t.cell_highlight_style(self.cell_highlight_style)\n\t\t\t.highlight_symbol(self.highlight_symbol)\n\t\t\t.highlight_spacing(self.highlight_spacing)\n\t\t\t.flex(self.flex);\n\n\t\tif let Some(header) = self.header {\n\t\t\ttable = table.header(header);\n\t\t}\n\t\tif let Some(footer) = self.footer {\n\t\t\ttable = table.footer(footer);\n\t\t}\n\t\tif let Some(block) = self.block {\n\t\t\ttable = table.block(block);\n\t\t}\n\n\t\tStatefulWidget::render(table, rect, buf, &mut self.state);\n\t}\n}\n\nimpl Widget for &Table {\n\tfn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)\n\twhere\n\t\tSelf: Sized,\n\t{\n\t\tself.clone().render(rect, buf);\n\t}\n}\n\nimpl UserData for Table {\n\tfn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {\n\t\tcrate::impl_area_method!(methods);\n\n\t\tmethods.add_function_mut(\"header\", |_, (ud, header): (AnyUserData, Row)| {\n\t\t\tud.borrow_mut::<Self>()?.header = Some(header.into());\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"footer\", |_, (ud, footer): (AnyUserData, Row)| {\n\t\t\tud.borrow_mut::<Self>()?.footer = Some(footer.into());\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"widths\", |_, (ud, widths): (AnyUserData, Vec<Constraint>)| {\n\t\t\tud.borrow_mut::<Self>()?.widths = widths.into_iter().map(Into::into).collect();\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"spacing\", |_, (ud, spacing): (AnyUserData, u16)| {\n\t\t\tud.borrow_mut::<Self>()?.column_spacing = spacing;\n\t\t\tOk(ud)\n\t\t});\n\n\t\tmethods.add_function_mut(\"row\", |_, (ud, idx): (AnyUserData, Option<usize>)| {\n\t\t\tud.borrow_mut::<Self>()?.state.select(idx);\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"col\", |_, (ud, idx): (AnyUserData, Option<usize>)| {\n\t\t\tud.borrow_mut::<Self>()?.state.select_column(idx);\n\t\t\tOk(ud)\n\t\t});\n\n\t\tmethods.add_function_mut(\"style\", |_, (ud, style): (AnyUserData, Style)| {\n\t\t\tud.borrow_mut::<Self>()?.style = style.0;\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"row_style\", |_, (ud, style): (AnyUserData, Style)| {\n\t\t\tud.borrow_mut::<Self>()?.row_highlight_style = style.0;\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"col_style\", |_, (ud, style): (AnyUserData, Style)| {\n\t\t\tud.borrow_mut::<Self>()?.column_highlight_style = style.0;\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"cell_style\", |_, (ud, style): (AnyUserData, Style)| {\n\t\t\tud.borrow_mut::<Self>()?.cell_highlight_style = style.0;\n\t\t\tOk(ud)\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/elements/text.rs",
    "content": "use std::{any::TypeId, mem};\n\nuse ansi_to_tui::IntoText;\nuse mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, Value};\nuse ratatui::widgets::Widget;\n\nuse super::{Area, Line, Span, Wrap};\nuse crate::{Error, elements::Align};\n\nconst EXPECTED: &str = \"expected a string, Line, Span, or a table of them\";\n\n#[derive(Clone, Debug, Default)]\npub struct Text {\n\tpub area: Area,\n\n\t// TODO: block\n\tpub inner:  ratatui::text::Text<'static>,\n\tpub wrap:   Wrap,\n\tpub scroll: ratatui::layout::Position,\n}\n\nimpl Text {\n\tpub fn compose(lua: &Lua) -> mlua::Result<Value> {\n\t\tlet new = lua.create_function(|_, (_, text): (Table, Self)| Ok(text))?;\n\n\t\tlet parse = lua.create_function(|_, code: mlua::String| {\n\t\t\tOk(Self { inner: code.as_bytes().into_text().into_lua_err()?, ..Default::default() })\n\t\t})?;\n\n\t\tlet text = lua.create_table_from([(\"parse\", parse)])?;\n\t\ttext.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;\n\t\ttext.into_lua(lua)\n\t}\n}\n\nimpl TryFrom<Table> for Text {\n\ttype Error = mlua::Error;\n\n\tfn try_from(tb: Table) -> Result<Self, Self::Error> {\n\t\tlet mut lines = Vec::with_capacity(tb.raw_len());\n\t\tfor v in tb.sequence_values() {\n\t\t\tlines.push(match v? {\n\t\t\t\tValue::String(s) => s.to_string_lossy().into(),\n\t\t\t\tValue::UserData(ud) => match ud.type_id() {\n\t\t\t\t\tSome(t) if t == TypeId::of::<Span>() => ud.take::<Span>()?.0.into(),\n\t\t\t\t\tSome(t) if t == TypeId::of::<Line>() => ud.take::<Line>()?.inner,\n\t\t\t\t\tSome(t) if t == TypeId::of::<Error>() => ud.take::<Error>()?.into_string().into(),\n\t\t\t\t\t_ => Err(EXPECTED.into_lua_err())?,\n\t\t\t\t},\n\t\t\t\t_ => Err(EXPECTED.into_lua_err())?,\n\t\t\t})\n\t\t}\n\t\tOk(Self { inner: lines.into(), ..Default::default() })\n\t}\n}\n\nimpl From<Text> for ratatui::text::Text<'static> {\n\tfn from(value: Text) -> Self { value.inner }\n}\n\nimpl From<Text> for ratatui::widgets::Paragraph<'static> {\n\tfn from(mut value: Text) -> Self {\n\t\tlet align = value.inner.alignment.take();\n\t\tlet style = mem::take(&mut value.inner.style);\n\n\t\tlet mut p = ratatui::widgets::Paragraph::new(value.inner).style(style);\n\t\tif let Some(align) = align {\n\t\t\tp = p.alignment(align);\n\t\t}\n\t\tif let Some(wrap) = value.wrap.0 {\n\t\t\tp = p.wrap(wrap);\n\t\t}\n\t\tp.scroll((value.scroll.y, value.scroll.x))\n\t}\n}\n\nimpl Widget for Text {\n\tfn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)\n\twhere\n\t\tSelf: Sized,\n\t{\n\t\tif self.wrap.is_none() && self.scroll == Default::default() {\n\t\t\tself.inner.render(rect, buf);\n\t\t} else {\n\t\t\tratatui::widgets::Paragraph::from(self).render(rect, buf);\n\t\t}\n\t}\n}\n\nimpl Widget for &Text {\n\tfn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)\n\twhere\n\t\tSelf: Sized,\n\t{\n\t\tif self.wrap.is_none() && self.scroll == Default::default() {\n\t\t\t(&self.inner).render(rect, buf);\n\t\t} else {\n\t\t\tratatui::widgets::Paragraph::from(self.clone()).render(rect, buf);\n\t\t}\n\t}\n}\n\nimpl FromLua for Text {\n\tfn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {\n\t\tlet inner = match value {\n\t\t\tValue::Table(tb) => return Self::try_from(tb),\n\t\t\tValue::String(s) => s.to_string_lossy().into(),\n\t\t\tValue::UserData(ud) => match ud.type_id() {\n\t\t\t\tSome(t) if t == TypeId::of::<Line>() => ud.take::<Line>()?.inner.into(),\n\t\t\t\tSome(t) if t == TypeId::of::<Span>() => ud.take::<Span>()?.0.into(),\n\t\t\t\tSome(t) if t == TypeId::of::<Self>() => return ud.take(),\n\t\t\t\tSome(t) if t == TypeId::of::<Error>() => ud.take::<Error>()?.into_string().into(),\n\t\t\t\t_ => Err(EXPECTED.into_lua_err())?,\n\t\t\t},\n\t\t\t_ => Err(EXPECTED.into_lua_err())?,\n\t\t};\n\t\tOk(Self { inner, ..Default::default() })\n\t}\n}\n\nimpl UserData for Text {\n\tfn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {\n\t\tcrate::impl_area_method!(methods);\n\t\tcrate::impl_style_method!(methods, inner.style);\n\t\tcrate::impl_style_shorthands!(methods, inner.style);\n\n\t\tmethods.add_function_mut(\"align\", |_, (ud, align): (AnyUserData, Align)| {\n\t\t\tud.borrow_mut::<Self>()?.inner.alignment = Some(align.0);\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"wrap\", |_, (ud, wrap): (AnyUserData, Wrap)| {\n\t\t\tud.borrow_mut::<Self>()?.wrap = wrap;\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"scroll\", |_, (ud, x, y): (AnyUserData, u16, u16)| {\n\t\t\tud.borrow_mut::<Self>()?.scroll = ratatui::layout::Position { x, y };\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_method(\"max_width\", |_, me, ()| {\n\t\t\tOk(me.inner.lines.iter().take(me.area.size().height as usize).map(|l| l.width()).max())\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/elements/wrap.rs",
    "content": "use std::ops::Deref;\n\nuse mlua::{FromLua, IntoLua, Lua, Value};\nuse yazi_config::preview::PreviewWrap;\n\n#[derive(Clone, Copy, Debug, Default)]\npub struct Wrap(pub(super) Option<ratatui::widgets::Wrap>);\n\nimpl Deref for Wrap {\n\ttype Target = Option<ratatui::widgets::Wrap>;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl Wrap {\n\tpub fn compose(lua: &Lua) -> mlua::Result<Value> {\n\t\tlua.create_table_from([(\"NO\", 0), (\"YES\", 1), (\"TRIM\", 2)])?.into_lua(lua)\n\t}\n}\n\nimpl From<Wrap> for Option<ratatui::widgets::Wrap> {\n\tfn from(value: Wrap) -> Self { value.0 }\n}\n\nimpl From<ratatui::widgets::Wrap> for Wrap {\n\tfn from(value: ratatui::widgets::Wrap) -> Self { Self(Some(value)) }\n}\n\nimpl From<PreviewWrap> for Wrap {\n\tfn from(value: PreviewWrap) -> Self {\n\t\tSelf(match value {\n\t\t\tPreviewWrap::No => None,\n\t\t\tPreviewWrap::Yes => Some(ratatui::widgets::Wrap { trim: false }),\n\t\t})\n\t}\n}\n\nimpl FromLua for Wrap {\n\tfn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {\n\t\tlet Value::Integer(n) = value else {\n\t\t\treturn Err(mlua::Error::FromLuaConversionError {\n\t\t\t\tfrom:    value.type_name(),\n\t\t\t\tto:      \"Wrap\".to_string(),\n\t\t\t\tmessage: Some(\"expected an integer representation of a Wrap\".to_string()),\n\t\t\t});\n\t\t};\n\t\tOk(Self(match n {\n\t\t\t0 => None,\n\t\t\t1 => Some(ratatui::widgets::Wrap { trim: false }),\n\t\t\t2 => Some(ratatui::widgets::Wrap { trim: true }),\n\t\t\t_ => Err(mlua::Error::FromLuaConversionError {\n\t\t\t\tfrom:    value.type_name(),\n\t\t\t\tto:      \"Wrap\".to_string(),\n\t\t\t\tmessage: Some(\"invalid value for Wrap\".to_string()),\n\t\t\t})?,\n\t\t}))\n\t}\n}\n\nimpl IntoLua for Wrap {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tmatch self.0 {\n\t\t\tNone => 0.into_lua(lua),\n\t\t\tSome(ratatui::widgets::Wrap { trim: false }) => 1.into_lua(lua),\n\t\t\tSome(ratatui::widgets::Wrap { trim: true }) => 2.into_lua(lua),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/error.rs",
    "content": "use std::{borrow::Cow, fmt::Display};\n\nuse mlua::{ExternalError, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, Value};\nuse yazi_codegen::FromLuaOwned;\nuse yazi_shared::SStr;\n\n#[derive(FromLuaOwned)]\npub enum Error {\n\tIo(std::io::Error),\n\tFs(yazi_fs::error::Error),\n\tSerde(serde_json::Error),\n\tCustom(SStr),\n}\n\nimpl Error {\n\tpub fn install(lua: &Lua) -> mlua::Result<()> {\n\t\tlet custom = lua.create_function(|_, msg: String| Ok(Self::custom(msg)))?;\n\n\t\tlet fs = lua.create_function(|_, value: Value| {\n\t\t\tOk(Self::Fs(match value {\n\t\t\t\tValue::Table(t) => yazi_fs::error::Error::custom(\n\t\t\t\t\t&t.raw_get::<mlua::String>(\"kind\")?.to_str()?,\n\t\t\t\t\tt.raw_get(\"code\")?,\n\t\t\t\t\t&t.raw_get::<mlua::String>(\"message\")?.to_str()?,\n\t\t\t\t)?,\n\t\t\t\t_ => Err(\"expected a table\".into_lua_err())?,\n\t\t\t}))\n\t\t})?;\n\n\t\tlua.globals().raw_set(\"Error\", lua.create_table_from([(\"custom\", custom), (\"fs\", fs)])?)\n\t}\n\n\tpub fn custom(msg: impl Into<SStr>) -> Self { Self::Custom(msg.into()) }\n\n\tpub fn into_string(self) -> SStr {\n\t\tmatch self {\n\t\t\tSelf::Io(e) => Cow::Owned(e.to_string()),\n\t\t\tSelf::Fs(e) => Cow::Owned(e.to_string()),\n\t\t\tSelf::Serde(e) => Cow::Owned(e.to_string()),\n\t\t\tSelf::Custom(s) => s,\n\t\t}\n\t}\n}\n\nimpl Display for Error {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tmatch self {\n\t\t\tSelf::Io(e) => write!(f, \"{e}\"),\n\t\t\tSelf::Fs(e) => write!(f, \"{e}\"),\n\t\t\tSelf::Serde(e) => write!(f, \"{e}\"),\n\t\t\tSelf::Custom(s) => write!(f, \"{s}\"),\n\t\t}\n\t}\n}\n\nimpl UserData for Error {\n\tfn add_fields<F: UserDataFields<Self>>(fields: &mut F) {\n\t\tfields.add_field_method_get(\"code\", |_, me| {\n\t\t\tOk(match me {\n\t\t\t\tSelf::Io(e) => e.raw_os_error(),\n\t\t\t\tSelf::Fs(e) => e.raw_os_error(),\n\t\t\t\t_ => None,\n\t\t\t})\n\t\t});\n\t\tfields.add_field_method_get(\"kind\", |_, me| {\n\t\t\tOk(match me {\n\t\t\t\tSelf::Io(e) => Some(yazi_fs::error::Error::from(e.kind()).kind_str()),\n\t\t\t\tSelf::Fs(e) => Some(e.kind_str()),\n\t\t\t\t_ => None,\n\t\t\t})\n\t\t});\n\t}\n\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_meta_method(MetaMethod::ToString, |lua, me, ()| {\n\t\t\tOk(match me {\n\t\t\t\tSelf::Io(_) | Self::Fs(_) | Self::Serde(_) => lua.create_external_string(me.to_string()),\n\t\t\t\tSelf::Custom(s) => lua.create_string(&**s),\n\t\t\t})\n\t\t});\n\t\tmethods.add_meta_function(MetaMethod::Concat, |lua, (lhs, rhs): (Value, Value)| {\n\t\t\tmatch (lhs, rhs) {\n\t\t\t\t(Value::String(l), Value::UserData(r)) => {\n\t\t\t\t\tlet r = r.borrow::<Self>()?;\n\t\t\t\t\tlua.create_external_string([&l.as_bytes(), r.to_string().as_bytes()].concat())\n\t\t\t\t}\n\t\t\t\t(Value::UserData(l), Value::String(r)) => {\n\t\t\t\t\tlet l = l.borrow::<Self>()?;\n\t\t\t\t\tlua.create_external_string([l.to_string().as_bytes(), &r.as_bytes()].concat())\n\t\t\t\t}\n\t\t\t\t_ => Err(\"only string can be concatenated with Error\".into_lua_err()),\n\t\t\t}\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/fd.rs",
    "content": "use mlua::{IntoLuaMulti, UserData, UserDataMethods, Value};\nuse tokio::io::{AsyncReadExt, AsyncWriteExt};\n\nuse crate::Error;\n\npub struct Fd(pub yazi_vfs::provider::RwFile);\n\nimpl UserData for Fd {\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_async_method_mut(\"flush\", |lua, mut me, ()| async move {\n\t\t\tmatch me.0.flush().await {\n\t\t\t\tOk(()) => true.into_lua_multi(&lua),\n\t\t\t\tErr(e) => (false, Error::Io(e)).into_lua_multi(&lua),\n\t\t\t}\n\t\t});\n\t\tmethods.add_async_method_mut(\"read\", |lua, mut me, len: usize| async move {\n\t\t\tlet mut buf = vec![0; len];\n\t\t\tmatch me.0.read(&mut buf).await {\n\t\t\t\tOk(n) => {\n\t\t\t\t\tbuf.truncate(n);\n\t\t\t\t\tlua.create_external_string(buf)?.into_lua_multi(&lua)\n\t\t\t\t}\n\t\t\t\tErr(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),\n\t\t\t}\n\t\t});\n\t\tmethods.add_async_method_mut(\"write_all\", |lua, mut me, src: mlua::String| async move {\n\t\t\tmatch me.0.write_all(&src.as_bytes()).await {\n\t\t\t\tOk(()) => true.into_lua_multi(&lua),\n\t\t\t\tErr(e) => (false, Error::Io(e)).into_lua_multi(&lua),\n\t\t\t}\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/file.rs",
    "content": "use std::ops::Deref;\n\nuse mlua::{AnyUserData, ExternalError, FromLua, Lua, ObjectLike, Table, UserData, UserDataFields, UserDataMethods, UserDataRef, Value};\n\nuse crate::{Cha, Url, impl_file_fields, impl_file_methods};\n\npub type FileRef = UserDataRef<File>;\n\nconst EXPECTED: &str = \"expected a table, File, or fs::File\";\n\n#[derive(Clone)]\npub struct File {\n\tinner: yazi_fs::File,\n\n\tv_cha:     Option<Value>,\n\tv_url:     Option<Value>,\n\tv_link_to: Option<Value>,\n\n\tv_name:  Option<Value>,\n\tv_path:  Option<Value>,\n\tv_cache: Option<Value>,\n}\n\nimpl Deref for File {\n\ttype Target = yazi_fs::File;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl From<File> for yazi_fs::File {\n\tfn from(value: File) -> Self { value.inner }\n}\n\nimpl File {\n\tpub fn new(inner: impl Into<yazi_fs::File>) -> Self {\n\t\tSelf {\n\t\t\tinner:     inner.into(),\n\t\t\tv_cha:     None,\n\t\t\tv_url:     None,\n\t\t\tv_link_to: None,\n\n\t\t\tv_name:  None,\n\t\t\tv_path:  None,\n\t\t\tv_cache: None,\n\t\t}\n\t}\n\n\tpub fn install(lua: &Lua) -> mlua::Result<()> {\n\t\tlua.globals().raw_set(\"File\", lua.create_function(|_, file: Self| Ok(file))?)\n\t}\n}\n\nimpl TryFrom<Table> for File {\n\ttype Error = mlua::Error;\n\n\tfn try_from(value: Table) -> Result<Self, Self::Error> {\n\t\tOk(Self::new(yazi_fs::File {\n\t\t\turl: value.raw_get::<Url>(\"url\")?.into(),\n\t\t\tcha: *value.raw_get::<Cha>(\"cha\")?,\n\t\t\t..Default::default()\n\t\t}))\n\t}\n}\n\nimpl TryFrom<AnyUserData> for File {\n\ttype Error = mlua::Error;\n\n\tfn try_from(value: AnyUserData) -> Result<Self, Self::Error> {\n\t\tOk(if let Ok(me) = value.take::<Self>() { me } else { value.get(\"bare\")? })\n\t}\n}\n\nimpl FromLua for File {\n\tfn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {\n\t\tmatch value {\n\t\t\tValue::Table(tbl) => Self::try_from(tbl),\n\t\t\tValue::UserData(ud) => Self::try_from(ud),\n\t\t\t_ => Err(EXPECTED.into_lua_err())?,\n\t\t}\n\t}\n}\n\nimpl UserData for File {\n\tfn add_fields<F: UserDataFields<Self>>(fields: &mut F) {\n\t\timpl_file_fields!(fields);\n\t}\n\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\timpl_file_methods!(methods);\n\n\t\tmethods.add_method(\"icon\", |_, me, ()| {\n\t\t\tuse crate::Icon;\n\t\t\t// TODO: use a cache\n\t\t\tOk(yazi_config::THEME.icon.matches(me, false).map(Icon::from))\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/handle.rs",
    "content": "use mlua::{MultiValue, UserData, UserDataMethods};\nuse tokio::task::JoinHandle;\n\npub enum Handle {\n\tAsyncFn(JoinHandle<mlua::Result<MultiValue>>),\n}\n\nimpl UserData for Handle {\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_method(\"abort\", |_, me, ()| {\n\t\t\tOk(match me {\n\t\t\t\tSelf::AsyncFn(h) => h.abort(),\n\t\t\t})\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/icon.rs",
    "content": "use std::ops::Deref;\n\nuse mlua::{UserData, UserDataFields, Value};\n\nuse crate::{Style, cached_field};\n\npub struct Icon {\n\tinner: &'static yazi_config::Icon,\n\n\tv_text:  Option<Value>,\n\tv_style: Option<Value>,\n}\n\nimpl Deref for Icon {\n\ttype Target = yazi_config::Icon;\n\n\tfn deref(&self) -> &Self::Target { self.inner }\n}\n\nimpl From<&'static yazi_config::Icon> for Icon {\n\tfn from(icon: &'static yazi_config::Icon) -> Self {\n\t\tSelf { inner: icon, v_text: None, v_style: None }\n\t}\n}\n\nimpl UserData for Icon {\n\tfn add_fields<F: UserDataFields<Self>>(fields: &mut F) {\n\t\tcached_field!(fields, text, |lua, me| lua.create_string(&me.text));\n\t\tcached_field!(fields, style, |_, me| Ok(Style::from(me.style)));\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/id.rs",
    "content": "use std::ops::Deref;\n\nuse mlua::{ExternalError, ExternalResult, FromLua, Lua, UserData, Value};\n\n#[derive(Clone, Copy)]\npub struct Id(pub yazi_shared::Id);\n\nimpl Deref for Id {\n\ttype Target = yazi_shared::Id;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl FromLua for Id {\n\tfn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {\n\t\tOk(match value {\n\t\t\tValue::Integer(i) => Self(i.try_into().into_lua_err()?),\n\t\t\tValue::UserData(ud) => *ud.borrow::<Self>()?,\n\t\t\t_ => Err(\"expected integer or userdata\".into_lua_err())?,\n\t\t})\n\t}\n}\n\nimpl UserData for Id {\n\tfn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {\n\t\tfields.add_field_method_get(\"value\", |_, me| Ok(me.0.get()));\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/image.rs",
    "content": "use std::ops::Deref;\n\nuse mlua::{MetaMethod, UserData};\n\npub struct ImageInfo(yazi_adapter::ImageInfo);\n\nimpl Deref for ImageInfo {\n\ttype Target = yazi_adapter::ImageInfo;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl From<yazi_adapter::ImageInfo> for ImageInfo {\n\tfn from(value: yazi_adapter::ImageInfo) -> Self { Self(value) }\n}\n\nimpl UserData for ImageInfo {\n\tfn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {\n\t\tfields.add_field_method_get(\"w\", |_, me| Ok(me.width));\n\t\tfields.add_field_method_get(\"h\", |_, me| Ok(me.height));\n\t\tfields.add_field_method_get(\"ori\", |_, me| Ok(me.orientation.map(|o| o.to_exif())));\n\t\tfields.add_field_method_get(\"format\", |_, me| Ok(ImageFormat(me.format)));\n\t\tfields.add_field_method_get(\"color\", |_, me| Ok(ImageColor(me.color)));\n\t}\n}\n\n// --- ImageFormat\nstruct ImageFormat(yazi_adapter::ImageFormat);\n\nimpl UserData for ImageFormat {\n\tfn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_meta_method(MetaMethod::ToString, |_, me, ()| {\n\t\t\tuse yazi_adapter::ImageFormat as F;\n\n\t\t\tOk(match me.0 {\n\t\t\t\tF::Png => \"PNG\",\n\t\t\t\tF::Jpeg => \"JPEG\",\n\t\t\t\tF::Gif => \"GIF\",\n\t\t\t\tF::WebP => \"WEBP\",\n\t\t\t\tF::Pnm => \"PNM\",\n\t\t\t\tF::Tiff => \"TIFF\",\n\t\t\t\tF::Tga => \"TGA\",\n\t\t\t\tF::Dds => \"DDS\",\n\t\t\t\tF::Bmp => \"BMP\",\n\t\t\t\tF::Ico => \"ICO\",\n\t\t\t\tF::Hdr => \"HDR\",\n\t\t\t\tF::OpenExr => \"OpenEXR\",\n\t\t\t\tF::Farbfeld => \"Farbfeld\",\n\t\t\t\tF::Avif => \"AVIF\",\n\t\t\t\tF::Qoi => \"QOI\",\n\t\t\t\t_ => \"Unknown\",\n\t\t\t})\n\t\t});\n\t}\n}\n\n// --- ImageColor\nstruct ImageColor(yazi_adapter::ImageColor);\n\nimpl UserData for ImageColor {\n\tfn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_meta_method(MetaMethod::ToString, |_, me, ()| {\n\t\t\tuse yazi_adapter::ImageColor as C;\n\n\t\t\tOk(match me.0 {\n\t\t\t\tC::L8 => \"L8\",\n\t\t\t\tC::La8 => \"La8\",\n\t\t\t\tC::Rgb8 => \"Rgb8\",\n\t\t\t\tC::Rgba8 => \"Rgba8\",\n\n\t\t\t\tC::L16 => \"L16\",\n\t\t\t\tC::La16 => \"La16\",\n\t\t\t\tC::Rgb16 => \"Rgb16\",\n\t\t\t\tC::Rgba16 => \"Rgba16\",\n\n\t\t\t\tC::Rgb32F => \"Rgb32F\",\n\t\t\t\tC::Rgba32F => \"Rgba32F\",\n\t\t\t\t_ => \"Unknown\",\n\t\t\t})\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/input.rs",
    "content": "use std::pin::Pin;\n\nuse mlua::{UserData, UserDataMethods};\nuse tokio::pin;\nuse tokio_stream::StreamExt;\nuse yazi_widgets::input::InputEvent;\n\npub struct InputRx<T: StreamExt<Item = InputEvent>> {\n\tinner: T,\n}\n\nimpl<T: StreamExt<Item = InputEvent>> InputRx<T> {\n\tpub fn new(inner: T) -> Self { Self { inner } }\n\n\tpub async fn consume(inner: T) -> (Option<String>, u8) {\n\t\tpin!(inner);\n\t\tinner.next().await.map(Self::parse).unwrap_or((None, 0))\n\t}\n\n\tfn parse(res: InputEvent) -> (Option<String>, u8) {\n\t\tmatch res {\n\t\t\tInputEvent::Submit(s) => (Some(s), 1),\n\t\t\tInputEvent::Cancel(s) => (Some(s), 2),\n\t\t\tInputEvent::Type(s) => (Some(s), 3),\n\t\t\t_ => (None, 0),\n\t\t}\n\t}\n}\n\nimpl<T: StreamExt<Item = InputEvent> + 'static> UserData for InputRx<T> {\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_async_method_mut(\"recv\", |_, mut me, ()| async move {\n\t\t\tlet mut inner = unsafe { Pin::new_unchecked(&mut me.inner) };\n\t\t\tOk(inner.next().await.map(Self::parse).unwrap_or((None, 0)))\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/iter.rs",
    "content": "use mlua::{AnyUserData, ExternalError, FromLua, IntoLua, IntoLuaMulti, Lua, MetaMethod, UserData, UserDataMethods, UserDataRefMut, Value};\n\npub struct Iter<I: Iterator<Item = T>, T> {\n\titer: I,\n\tlen:  Option<usize>,\n\n\tcount: usize,\n\tcache: Vec<Value>,\n}\n\nimpl<I, T> Iter<I, T>\nwhere\n\tI: Iterator<Item = T> + 'static,\n\tT: IntoLua + 'static,\n{\n\tpub fn new(iter: I, len: Option<usize>) -> Self { Self { iter, len, count: 0, cache: vec![] } }\n}\n\nimpl<I, T> Iter<I, T>\nwhere\n\tI: Iterator<Item = T> + 'static,\n\tT: FromLua + 'static,\n{\n\tpub fn into_iter(self, lua: &Lua) -> impl Iterator<Item = mlua::Result<T>> {\n\t\tself\n\t\t\t.cache\n\t\t\t.into_iter()\n\t\t\t.map(|cached| T::from_lua(cached, lua))\n\t\t\t.chain(self.iter.map(|rest| Ok(rest)))\n\t}\n}\n\nimpl<I, T> UserData for Iter<I, T>\nwhere\n\tI: Iterator<Item = T> + 'static,\n\tT: IntoLua + 'static,\n{\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_meta_method(MetaMethod::Len, |_, me, ()| {\n\t\t\tif let Some(len) = me.len {\n\t\t\t\tOk(len)\n\t\t\t} else {\n\t\t\t\tErr(format!(\"Length is unknown for {}\", std::any::type_name::<Self>()).into_lua_err())\n\t\t\t}\n\t\t});\n\n\t\tmethods.add_meta_function(MetaMethod::Pairs, |lua, ud: AnyUserData| {\n\t\t\tlet iter = lua.create_function(|lua, mut me: UserDataRefMut<Self>| {\n\t\t\t\tif let Some(next) = me.cache.get(me.count).cloned() {\n\t\t\t\t\tme.count += 1;\n\t\t\t\t\t(me.count, next).into_lua_multi(lua)\n\t\t\t\t} else if let Some(next) = me.iter.next() {\n\t\t\t\t\tlet value = next.into_lua(lua)?;\n\t\t\t\t\tme.cache.push(value.clone());\n\t\t\t\t\tme.count += 1;\n\t\t\t\t\t(me.count, value).into_lua_multi(lua)\n\t\t\t\t} else {\n\t\t\t\t\t().into_lua_multi(lua)\n\t\t\t\t}\n\t\t\t})?;\n\n\t\t\tud.borrow_mut::<Self>()?.count = 0;\n\t\t\tOk((iter, ud))\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/layer.rs",
    "content": "use mlua::{MetaMethod, UserData};\n\n#[derive(Clone, Copy)]\npub struct Layer(yazi_shared::Layer);\n\nimpl From<yazi_shared::Layer> for Layer {\n\tfn from(event: yazi_shared::Layer) -> Self { Self(event) }\n}\n\nimpl UserData for Layer {\n\tfn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_meta_method(MetaMethod::ToString, |_, me, ()| Ok(me.0.to_string()));\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/lib.rs",
    "content": "mod macros;\n\nyazi_macro::mod_pub!(elements);\n\nyazi_macro::mod_flat!(access calculator cha chan chord_cow color composer error fd file handle icon id image input iter layer mouse path permit range runtime scheme stage style url utils);\n"
  },
  {
    "path": "yazi-binding/src/macros.rs",
    "content": "#[macro_export]\nmacro_rules! runtime {\n\t($lua:ident) => {{\n\t\tuse mlua::ExternalError;\n\t\t$lua.app_data_ref::<$crate::Runtime>().ok_or_else(|| \"Runtime not found\".into_lua_err())\n\t}};\n}\n\n#[macro_export]\nmacro_rules! runtime_mut {\n\t($lua:ident) => {{\n\t\tuse mlua::ExternalError;\n\t\t$lua.app_data_mut::<$crate::Runtime>().ok_or_else(|| \"Runtime not found\".into_lua_err())\n\t}};\n}\n\n#[macro_export]\nmacro_rules! runtime_scope {\n\t($lua:ident, $id:expr, $block:expr) => {{\n\t\tlet mut f = || {\n\t\t\tlet blocking = $crate::runtime_mut!($lua)?.critical_push($id, true);\n\t\t\tlet result = (|| $block)();\n\t\t\t$crate::runtime_mut!($lua)?.critical_pop(blocking)?;\n\t\t\tresult\n\t\t};\n\t\tf()\n\t}};\n}\n\n#[macro_export]\nmacro_rules! deprecate {\n\t($lua:ident, $tt:tt) => {{\n\t\tstatic WARNED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);\n\t\tif !WARNED.swap(true, std::sync::atomic::Ordering::Relaxed) {\n\t\t\tlet id = match $crate::runtime!($lua)?.current()? {\n\t\t\t\t\"init\" => \"`init.lua` config file\",\n\t\t\t\ts => &format!(\"`{s}.yazi` plugin\"),\n\t\t\t};\n\t\t\tyazi_macro::emit!(Call(\n\t\t\t\tyazi_macro::relay!(app:deprecate).with(\"content\", format!($tt, id))\n\t\t\t));\n\t\t}\n\t}};\n}\n\n#[macro_export]\nmacro_rules! cached_field {\n\t($fields:ident, $key:ident, $value:expr) => {\n\t\t$fields.add_field_function_get(stringify!($key), |lua, ud| {\n\t\t\tuse mlua::{Error::UserDataDestructed, IntoLua, Lua, Result, Value, Value::UserData};\n\t\t\tud.borrow_mut_scoped::<Self, Result<Value>>(|me| match paste::paste! { &me.[<v_ $key>] } {\n\t\t\t\tSome(v) if !v.is_userdata() => Ok(v.clone()),\n\t\t\t\tSome(v @ UserData(ud)) if !matches!(ud.borrow::<()>(), Err(UserDataDestructed)) => {\n\t\t\t\t\tOk(v.clone())\n\t\t\t\t}\n\t\t\t\t_ => {\n\t\t\t\t\tlet v = ($value as fn(&Lua, &Self) -> Result<_>)(lua, me)?.into_lua(lua)?;\n\t\t\t\t\tpaste::paste! { me.[<v_ $key>] = Some(v.clone()) };\n\t\t\t\t\tOk(v)\n\t\t\t\t}\n\t\t\t})?\n\t\t});\n\t};\n}\n\n#[macro_export]\nmacro_rules! cached_field_mut {\n\t($fields:ident, $key:ident, $value:expr) => {\n\t\t$fields.add_field_function_get(stringify!($key), |lua, ud| {\n\t\t\tuse mlua::{Error::UserDataDestructed, IntoLua, Lua, Result, Value, Value::UserData};\n\t\t\tud.borrow_mut_scoped::<Self, Result<Value>>(|me| match paste::paste! { &me.[<v_ $key>] } {\n\t\t\t\tSome(Ok(v)) if !v.is_userdata() => Ok(v.clone()),\n\t\t\t\tSome(Ok(v @ UserData(ud))) if !matches!(ud.borrow::<()>(), Err(UserDataDestructed)) => {\n\t\t\t\t\tOk(v.clone())\n\t\t\t\t}\n\t\t\t\tSome(Err(e)) => Err(e.clone()),\n\t\t\t\t_ => {\n\t\t\t\t\tlet v =\n\t\t\t\t\t\t($value as fn(&Lua, &mut Self) -> Result<_>)(lua, me).and_then(|v| v.into_lua(lua));\n\t\t\t\t\tpaste::paste! { me.[<v_ $key>] = Some(v.clone()) };\n\t\t\t\t\tv\n\t\t\t\t}\n\t\t\t})?\n\t\t});\n\t};\n}\n\n#[macro_export]\nmacro_rules! impl_area_method {\n\t($methods:ident) => {\n\t\t$methods.add_function_mut(\n\t\t\t\"area\",\n\t\t\t|lua, (ud, area): (mlua::AnyUserData, Option<mlua::AnyUserData>)| {\n\t\t\t\tuse mlua::IntoLua;\n\t\t\t\tif let Some(v) = area {\n\t\t\t\t\tud.borrow_mut::<Self>()?.area = $crate::elements::Area::try_from(v)?;\n\t\t\t\t\tud.into_lua(lua)\n\t\t\t\t} else {\n\t\t\t\t\tud.borrow::<Self>()?.area.into_lua(lua)\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\t};\n}\n\n#[macro_export]\nmacro_rules! impl_style_method {\n\t($methods:ident, $($field:tt).+) => {\n\t\t$methods.add_function_mut(\"style\", |_, (ud, style): (mlua::AnyUserData, $crate::Style)| {\n\t\t\tud.borrow_mut::<Self>()?.$($field).+ = style.0;\n\t\t\tOk(ud)\n\t\t});\n\t};\n}\n\n#[macro_export]\nmacro_rules! impl_style_shorthands {\n\t($methods:ident, $($field:tt).+) => {\n\t\t$methods.add_function_mut(\"fg\", |lua, (ud, value): (mlua::AnyUserData, mlua::Value)| {\n\t\t\tuse mlua::FromLua;\n\t\t\tuse ratatui::style::Modifier;\n\n\t\t\tlet me = &mut ud.borrow_mut::<Self>()?.$($field).+;\n\t\t\tmatch value {\n\t\t\t\tmlua::Value::Boolean(true) if me.add_modifier.contains(Modifier::REVERSED) && !me.sub_modifier.contains(Modifier::REVERSED) => {\n\t\t\t\t\tme.bg.map($crate::Color).into_lua(lua)\n\t\t\t\t}\n\t\t\t\tmlua::Value::Nil | mlua::Value::Boolean(_) => {\n\t\t\t\t\tme.fg.map($crate::Color).into_lua(lua)\n\t\t\t\t}\n\t\t\t\t_ => {\n\t\t\t\t\tme.fg = Some($crate::Color::from_lua(value, lua)?.0);\n\t\t\t\t\tud.into_lua(lua)\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\t$methods.add_function_mut(\"bg\", |lua, (ud, value): (mlua::AnyUserData, mlua::Value)| {\n\t\t\tuse mlua::FromLua;\n\t\t\tuse ratatui::style::Modifier;\n\n\t\t\tlet me = &mut ud.borrow_mut::<Self>()?.$($field).+;\n\t\t\tmatch value {\n\t\t\t\tmlua::Value::Boolean(true) if me.add_modifier.contains(Modifier::REVERSED) && !me.sub_modifier.contains(Modifier::REVERSED) => {\n\t\t\t\t\tme.fg.map($crate::Color).into_lua(lua)\n\t\t\t\t}\n\t\t\t\tmlua::Value::Nil | mlua::Value::Boolean(_) => {\n\t\t\t\t\tme.bg.map($crate::Color).into_lua(lua)\n\t\t\t\t}\n\t\t\t\t_ => {\n\t\t\t\t\tme.bg = Some($crate::Color::from_lua(value, lua)?.0);\n\t\t\t\t\tud.into_lua(lua)\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\t$methods.add_function_mut(\"bold\", |_, (ud, remove): (mlua::AnyUserData, bool)| {\n\t\t\tlet me = &mut ud.borrow_mut::<Self>()?.$($field).+;\n\t\t\tif remove {\n\t\t\t\t*me = me.remove_modifier(ratatui::style::Modifier::BOLD);\n\t\t\t} else {\n\t\t\t\t*me = me.add_modifier(ratatui::style::Modifier::BOLD);\n\t\t\t}\n\t\t\tOk(ud)\n\t\t});\n\t\t$methods.add_function_mut(\"dim\", |_, (ud, remove): (mlua::AnyUserData, bool)| {\n\t\t\tlet me = &mut ud.borrow_mut::<Self>()?.$($field).+;\n\t\t\tif remove {\n\t\t\t\t*me = me.remove_modifier(ratatui::style::Modifier::DIM);\n\t\t\t} else {\n\t\t\t\t*me = me.add_modifier(ratatui::style::Modifier::DIM);\n\t\t\t}\n\t\t\tOk(ud)\n\t\t});\n\t\t$methods.add_function_mut(\"italic\", |_, (ud, remove): (mlua::AnyUserData, bool)| {\n\t\t\tlet me = &mut ud.borrow_mut::<Self>()?.$($field).+;\n\t\t\tif remove {\n\t\t\t\t*me = me.remove_modifier(ratatui::style::Modifier::ITALIC);\n\t\t\t} else {\n\t\t\t\t*me = me.add_modifier(ratatui::style::Modifier::ITALIC);\n\t\t\t}\n\t\t\tOk(ud)\n\t\t});\n\t\t$methods.add_function_mut(\"underline\", |_, (ud, remove): (mlua::AnyUserData, bool)| {\n\t\t\tlet me = &mut ud.borrow_mut::<Self>()?.$($field).+;\n\t\t\tif remove {\n\t\t\t\t*me = me.remove_modifier(ratatui::style::Modifier::UNDERLINED);\n\t\t\t} else {\n\t\t\t\t*me = me.add_modifier(ratatui::style::Modifier::UNDERLINED);\n\t\t\t}\n\t\t\tOk(ud)\n\t\t});\n\t\t$methods.add_function_mut(\"blink\", |_, (ud, remove): (mlua::AnyUserData, bool)| {\n\t\t\tlet me = &mut ud.borrow_mut::<Self>()?.$($field).+;\n\t\t\tif remove {\n\t\t\t\t*me = me.remove_modifier(ratatui::style::Modifier::SLOW_BLINK);\n\t\t\t} else {\n\t\t\t\t*me = me.add_modifier(ratatui::style::Modifier::SLOW_BLINK);\n\t\t\t}\n\t\t\tOk(ud)\n\t\t});\n\t\t$methods.add_function_mut(\"blink_rapid\", |_, (ud, remove): (mlua::AnyUserData, bool)| {\n\t\t\tlet me = &mut ud.borrow_mut::<Self>()?.$($field).+;\n\t\t\tif remove {\n\t\t\t\t*me = me.remove_modifier(ratatui::style::Modifier::RAPID_BLINK);\n\t\t\t} else {\n\t\t\t\t*me = me.add_modifier(ratatui::style::Modifier::RAPID_BLINK);\n\t\t\t}\n\t\t\tOk(ud)\n\t\t});\n\t\t$methods.add_function_mut(\"reverse\", |_, (ud, remove): (mlua::AnyUserData, bool)| {\n\t\t\tlet me = &mut ud.borrow_mut::<Self>()?.$($field).+;\n\t\t\tif remove {\n\t\t\t\t*me = me.remove_modifier(ratatui::style::Modifier::REVERSED);\n\t\t\t} else {\n\t\t\t\t*me = me.add_modifier(ratatui::style::Modifier::REVERSED);\n\t\t\t}\n\t\t\tOk(ud)\n\t\t});\n\t\t$methods.add_function_mut(\"hidden\", |_, (ud, remove): (mlua::AnyUserData, bool)| {\n\t\t\tlet me = &mut ud.borrow_mut::<Self>()?.$($field).+;\n\t\t\tif remove {\n\t\t\t\t*me = me.remove_modifier(ratatui::style::Modifier::HIDDEN);\n\t\t\t} else {\n\t\t\t\t*me = me.add_modifier(ratatui::style::Modifier::HIDDEN);\n\t\t\t}\n\t\t\tOk(ud)\n\t\t});\n\t\t$methods.add_function_mut(\"crossed\", |_, (ud, remove): (mlua::AnyUserData, bool)| {\n\t\t\tlet me = &mut ud.borrow_mut::<Self>()?.$($field).+;\n\t\t\tif remove {\n\t\t\t\t*me = me.remove_modifier(ratatui::style::Modifier::CROSSED_OUT);\n\t\t\t} else {\n\t\t\t\t*me = me.add_modifier(ratatui::style::Modifier::CROSSED_OUT);\n\t\t\t}\n\t\t\tOk(ud)\n\t\t});\n\t\t$methods.add_function_mut(\"reset\", |_, ud: mlua::AnyUserData| {\n\t\t\tud.borrow_mut::<Self>()?.$($field).+ = ratatui::style::Style::reset();\n\t\t\tOk(ud)\n\t\t});\n\t};\n}\n\n#[macro_export]\nmacro_rules! impl_file_fields {\n\t($fields:ident) => {\n\t\t$crate::cached_field!($fields, cha, |_, me| Ok($crate::Cha(me.cha)));\n\t\t$crate::cached_field!($fields, url, |_, me| Ok($crate::Url::new(me.url_owned())));\n\t\t$crate::cached_field!($fields, link_to, |_, me| Ok(me.link_to.as_ref().map($crate::Path::new)));\n\n\t\t$crate::cached_field!($fields, name, |lua, me| {\n\t\t\tme.name().map(|s| lua.create_string(s.encoded_bytes())).transpose()\n\t\t});\n\t\t$crate::cached_field!($fields, path, |_, me| {\n\t\t\tuse yazi_fs::FsUrl;\n\t\t\tuse yazi_shared::url::AsUrl;\n\t\t\tOk($crate::Path::new(me.url.as_url().unified_path()))\n\t\t});\n\t\t$crate::cached_field!($fields, cache, |_, me| {\n\t\t\tuse yazi_fs::FsUrl;\n\t\t\tOk(me.url.cache().map($crate::Path::new))\n\t\t});\n\t};\n}\n\n#[macro_export]\nmacro_rules! impl_file_methods {\n\t($methods:ident) => {\n\t\t$methods.add_method(\"hash\", |_, me, ()| {\n\t\t\tuse yazi_fs::FsHash64;\n\t\t\tOk(me.hash_u64())\n\t\t});\n\t};\n}\n"
  },
  {
    "path": "yazi-binding/src/mouse.rs",
    "content": "use std::ops::Deref;\n\nuse crossterm::event::MouseButton;\nuse mlua::{UserData, UserDataFields};\n\n#[derive(Clone, Copy)]\npub struct MouseEvent(crossterm::event::MouseEvent);\n\nimpl Deref for MouseEvent {\n\ttype Target = crossterm::event::MouseEvent;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl From<crossterm::event::MouseEvent> for MouseEvent {\n\tfn from(event: crossterm::event::MouseEvent) -> Self { Self(event) }\n}\n\nimpl UserData for MouseEvent {\n\tfn add_fields<F: UserDataFields<Self>>(fields: &mut F) {\n\t\tfields.add_field_method_get(\"x\", |_, me| Ok(me.column));\n\t\tfields.add_field_method_get(\"y\", |_, me| Ok(me.row));\n\t\tfields.add_field_method_get(\"is_left\", |_, me| {\n\t\t\tuse crossterm::event::MouseEventKind as K;\n\t\t\tOk(matches!(me.kind, K::Down(b) | K::Up(b) | K::Drag(b) if b == MouseButton::Left))\n\t\t});\n\t\tfields.add_field_method_get(\"is_right\", |_, me| {\n\t\t\tuse crossterm::event::MouseEventKind as K;\n\t\t\tOk(matches!(me.kind, K::Down(b) | K::Up(b) | K::Drag(b) if b == MouseButton::Right))\n\t\t});\n\t\tfields.add_field_method_get(\"is_middle\", |_, me| {\n\t\t\tuse crossterm::event::MouseEventKind as K;\n\t\t\tOk(matches!(me.kind, K::Down(b) | K::Up(b) | K::Drag(b) if b == MouseButton::Middle))\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/path.rs",
    "content": "use std::ops::Deref;\n\nuse mlua::{ExternalError, ExternalResult, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value};\nuse yazi_codegen::FromLuaOwned;\nuse yazi_shared::{path::{PathBufDyn, PathLike, StripPrefixError}, strand::{AsStrand, Strand, StrandCow}};\n\nuse crate::cached_field;\n\npub type PathRef = UserDataRef<Path>;\n\n#[derive(FromLuaOwned)]\npub struct Path {\n\tinner: PathBufDyn,\n\n\tv_ext:    Option<Value>,\n\tv_name:   Option<Value>,\n\tv_parent: Option<Value>,\n\tv_stem:   Option<Value>,\n}\n\nimpl Deref for Path {\n\ttype Target = PathBufDyn;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl From<Path> for PathBufDyn {\n\tfn from(value: Path) -> Self { value.inner }\n}\n\nimpl AsStrand for Path {\n\tfn as_strand(&self) -> Strand<'_> { self.inner.as_strand() }\n}\n\nimpl AsStrand for &Path {\n\tfn as_strand(&self) -> Strand<'_> { self.inner.as_strand() }\n}\n\nimpl Path {\n\tpub fn new(path: impl Into<PathBufDyn>) -> Self {\n\t\tSelf {\n\t\t\tinner: path.into(),\n\n\t\t\tv_ext:    None,\n\t\t\tv_name:   None,\n\t\t\tv_parent: None,\n\t\t\tv_stem:   None,\n\t\t}\n\t}\n\n\tpub fn install(lua: &Lua) -> mlua::Result<()> {\n\t\tlua.globals().raw_set(\n\t\t\t\"Path\",\n\t\t\tlua.create_table_from([(\n\t\t\t\t\"os\",\n\t\t\t\tlua.create_function(|_, s: mlua::String| {\n\t\t\t\t\tOk(Self::new(s.as_bytes().as_strand().as_os_path().into_lua_err()?))\n\t\t\t\t})?,\n\t\t\t)])?,\n\t\t)\n\t}\n\n\tfn ends_with(&self, child: Value) -> mlua::Result<bool> {\n\t\tmatch child {\n\t\t\tValue::String(s) => {\n\t\t\t\tself.try_ends_with(StrandCow::with(self.kind(), &*s.as_bytes())?).into_lua_err()\n\t\t\t}\n\t\t\tValue::UserData(ud) => self.try_ends_with(&*ud.borrow::<Self>()?).into_lua_err(),\n\t\t\t_ => Err(\"must be a string or Path\".into_lua_err())?,\n\t\t}\n\t}\n\n\tfn join(&self, other: Value) -> mlua::Result<Self> {\n\t\tOk(Self::new(match other {\n\t\t\tValue::String(s) => {\n\t\t\t\tself.try_join(StrandCow::with(self.kind(), &*s.as_bytes())?).into_lua_err()?\n\t\t\t}\n\t\t\tValue::UserData(ref ud) => {\n\t\t\t\tlet path = ud.borrow::<Self>()?;\n\t\t\t\tself.try_join(&*path).into_lua_err()?\n\t\t\t}\n\t\t\t_ => Err(\"must be a string or Path\".into_lua_err())?,\n\t\t}))\n\t}\n\n\tfn starts_with(&self, base: Value) -> mlua::Result<bool> {\n\t\tmatch base {\n\t\t\tValue::String(s) => {\n\t\t\t\tself.try_starts_with(StrandCow::with(self.kind(), &*s.as_bytes())?).into_lua_err()\n\t\t\t}\n\t\t\tValue::UserData(ud) => self.try_starts_with(&*ud.borrow::<Self>()?).into_lua_err(),\n\t\t\t_ => Err(\"must be a string or Path\".into_lua_err())?,\n\t\t}\n\t}\n\n\tfn strip_prefix(&self, base: Value) -> mlua::Result<Option<Self>> {\n\t\tlet strip = match base {\n\t\t\tValue::String(s) => self.try_strip_prefix(StrandCow::with(self.kind(), &*s.as_bytes())?),\n\t\t\tValue::UserData(ud) => self.try_strip_prefix(&*ud.borrow::<Self>()?),\n\t\t\t_ => Err(\"must be a string or Path\".into_lua_err())?,\n\t\t};\n\n\t\tOk(match strip {\n\t\t\tOk(p) => Some(Self::new(p)),\n\t\t\tErr(StripPrefixError::Exotic | StripPrefixError::NotPrefix) => None,\n\t\t\tErr(e @ StripPrefixError::WrongEncoding) => Err(e.into_lua_err())?,\n\t\t})\n\t}\n}\n\nimpl UserData for Path {\n\tfn add_fields<F: UserDataFields<Self>>(fields: &mut F) {\n\t\tcached_field!(fields, ext, |lua, me| {\n\t\t\tme.ext().map(|s| lua.create_string(s.encoded_bytes())).transpose()\n\t\t});\n\t\tcached_field!(fields, name, |lua, me| {\n\t\t\tme.name().map(|s| lua.create_string(s.encoded_bytes())).transpose()\n\t\t});\n\t\tcached_field!(fields, parent, |_, me| Ok(me.parent().map(Self::new)));\n\t\tcached_field!(fields, stem, |lua, me| {\n\t\t\tme.stem().map(|s| lua.create_string(s.encoded_bytes())).transpose()\n\t\t});\n\n\t\tfields.add_field_method_get(\"is_absolute\", |_, me| Ok(me.is_absolute()));\n\t\tfields.add_field_method_get(\"has_root\", |_, me| Ok(me.has_root()));\n\t}\n\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_method(\"ends_with\", |_, me, child: Value| me.ends_with(child));\n\t\tmethods.add_method(\"join\", |_, me, other: Value| me.join(other));\n\t\tmethods.add_method(\"starts_with\", |_, me, base: Value| me.starts_with(base));\n\t\tmethods.add_method(\"strip_prefix\", |_, me, base: Value| me.strip_prefix(base));\n\n\t\tmethods.add_meta_method(MetaMethod::Concat, |lua, lhs, rhs: mlua::String| {\n\t\t\tlua.create_external_string([lhs.encoded_bytes(), &rhs.as_bytes()].concat())\n\t\t});\n\t\tmethods.add_meta_method(MetaMethod::Eq, |_, me, other: PathRef| Ok(me.inner == other.inner));\n\t\tmethods\n\t\t\t.add_meta_method(MetaMethod::ToString, |lua, me, ()| lua.create_string(me.encoded_bytes()));\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/permit.rs",
    "content": "use std::{mem, ops::Deref};\n\nuse futures::{FutureExt, future::BoxFuture};\nuse mlua::{UserData, UserDataMethods};\nuse tokio::sync::SemaphorePermit;\n\npub type PermitRef = mlua::UserDataRef<Permit>;\n\npub struct Permit {\n\tinner:    Option<SemaphorePermit<'static>>,\n\tdestruct: Option<BoxFuture<'static, ()>>,\n}\n\nimpl Deref for Permit {\n\ttype Target = Option<SemaphorePermit<'static>>;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl Permit {\n\tpub fn new<F>(inner: SemaphorePermit<'static>, f: F) -> Self\n\twhere\n\t\tF: Future<Output = ()> + 'static + Send,\n\t{\n\t\tSelf { inner: Some(inner), destruct: Some(f.boxed()) }\n\t}\n\n\tfn dropping(&mut self) -> impl Future<Output = ()> + 'static {\n\t\tlet inner = self.inner.take();\n\t\tlet destruct = self.destruct.take();\n\n\t\tasync move {\n\t\t\tif let Some(f) = destruct {\n\t\t\t\tf.await;\n\t\t\t}\n\t\t\tif let Some(p) = inner {\n\t\t\t\tmem::drop(p);\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl Drop for Permit {\n\tfn drop(&mut self) { tokio::spawn(self.dropping()); }\n}\n\nimpl UserData for Permit {\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_async_method_mut(\"drop\", |_, mut me, ()| async move { Ok(me.dropping().await) });\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/range.rs",
    "content": "use mlua::{IntoLua, Lua, Value};\n\npub struct Range<T>(std::ops::Range<T>);\n\nimpl<T> From<std::ops::Range<T>> for Range<T> {\n\tfn from(value: std::ops::Range<T>) -> Self { Self(value) }\n}\n\nimpl<T> IntoLua for Range<T>\nwhere\n\tT: IntoLua,\n{\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tlua.create_sequence_from([self.0.start, self.0.end])?.into_lua(lua)\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/runtime.rs",
    "content": "use std::{collections::VecDeque, mem};\n\nuse anyhow::{Context, Result};\nuse hashbrown::{HashMap, hash_map::EntryRef};\nuse mlua::Function;\n\n#[derive(Debug, Default)]\npub struct Runtime {\n\tframes:       VecDeque<RuntimeFrame>,\n\tblocks:       HashMap<String, Vec<Function>>,\n\tpub blocking: bool,\n}\n\n#[derive(Debug)]\npub struct RuntimeFrame {\n\tid: String,\n}\n\nimpl Runtime {\n\tpub fn new_isolate(id: &str) -> Self {\n\t\tSelf { frames: VecDeque::from([RuntimeFrame { id: id.to_owned() }]), ..Default::default() }\n\t}\n\n\tpub fn push(&mut self, id: &str) { self.frames.push_back(RuntimeFrame { id: id.to_owned() }); }\n\n\tpub fn pop(&mut self) -> Result<RuntimeFrame> {\n\t\tself.frames.pop_back().context(\"Runtime stack underflow\")\n\t}\n\n\tpub fn critical_push(&mut self, id: &str, blocking: bool) -> bool {\n\t\tself.push(id);\n\t\tmem::replace(&mut self.blocking, blocking)\n\t}\n\n\tpub fn critical_pop(&mut self, blocking: bool) -> Result<RuntimeFrame> {\n\t\tself.blocking = blocking;\n\t\tself.pop()\n\t}\n\n\tpub fn current(&self) -> Result<&str> {\n\t\tself.frames.back().map(|f| f.id.as_str()).context(\"No current runtime frame\")\n\t}\n\n\tpub fn current_owned(&self) -> Result<String> { self.current().map(ToOwned::to_owned) }\n\n\tpub fn get_block(&self, id: &str, calls: usize) -> Option<Function> {\n\t\tself.blocks.get(id).and_then(|v| v.get(calls)).cloned()\n\t}\n\n\tpub fn put_block(&mut self, f: &Function) -> Option<usize> {\n\t\tlet cur = self.frames.back().filter(|f| f.id != \"init\")?;\n\t\tSome(match self.blocks.entry_ref(&cur.id) {\n\t\t\tEntryRef::Occupied(mut oe) => {\n\t\t\t\toe.get_mut().push(f.clone());\n\t\t\t\toe.get().len() - 1\n\t\t\t}\n\t\t\tEntryRef::Vacant(ve) => {\n\t\t\t\tve.insert(vec![f.clone()]);\n\t\t\t\t0\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/scheme.rs",
    "content": "use std::ops::Deref;\n\nuse mlua::{UserData, UserDataFields, Value};\nuse yazi_fs::FsScheme;\nuse yazi_shared::scheme::SchemeLike;\n\nuse crate::{Path, cached_field};\n\npub struct Scheme {\n\tinner: yazi_shared::scheme::Scheme,\n\n\tv_kind:  Option<Value>,\n\tv_cache: Option<Value>,\n}\n\nimpl Deref for Scheme {\n\ttype Target = yazi_shared::scheme::Scheme;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl Scheme {\n\tpub fn new(scheme: impl Into<yazi_shared::scheme::Scheme>) -> Self {\n\t\tSelf { inner: scheme.into(), v_kind: None, v_cache: None }\n\t}\n}\n\nimpl UserData for Scheme {\n\tfn add_fields<F: UserDataFields<Self>>(fields: &mut F) {\n\t\tcached_field!(fields, kind, |_, me| Ok(me.kind().as_str()));\n\t\tcached_field!(fields, cache, |_, me| Ok(me.cache().map(Path::new)));\n\n\t\tfields.add_field_method_get(\"is_virtual\", |_, me| Ok(me.is_virtual()));\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/stage.rs",
    "content": "use mlua::{IntoLuaMulti, MetaMethod, UserData, UserDataMethods};\n\npub struct FolderStage(yazi_fs::FolderStage);\n\nimpl FolderStage {\n\tpub fn new(inner: yazi_fs::FolderStage) -> Self { Self(inner) }\n}\n\nimpl UserData for FolderStage {\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_meta_method(MetaMethod::Call, |lua, me, ()| {\n\t\t\tuse yazi_fs::FolderStage::*;\n\n\t\t\tmatch &me.0 {\n\t\t\t\tLoading => false.into_lua_multi(lua),\n\t\t\t\tLoaded => true.into_lua_multi(lua),\n\t\t\t\tFailed(e) => (true, crate::Error::Fs(e.clone())).into_lua_multi(lua),\n\t\t\t}\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/style.rs",
    "content": "use mlua::{AnyUserData, ExternalError, FromLua, IntoLua, Lua, LuaSerdeExt, MetaMethod, Table, UserData, UserDataMethods, Value};\n\nuse crate::SER_OPT;\n\n#[derive(Clone, Copy, Default)]\npub struct Style(pub ratatui::style::Style);\n\nimpl Style {\n\tpub fn compose(lua: &Lua) -> mlua::Result<Value> {\n\t\tlet new = lua.create_function(|_, (_, style): (Table, Self)| Ok(style))?;\n\n\t\tlet style = lua.create_table()?;\n\t\tstyle.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;\n\n\t\tstyle.into_lua(lua)\n\t}\n}\n\nimpl From<yazi_config::Style> for Style {\n\tfn from(value: yazi_config::Style) -> Self { Self(value.into()) }\n}\n\nimpl FromLua for Style {\n\tfn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {\n\t\tOk(Self(match value {\n\t\t\tValue::Nil => Default::default(),\n\t\t\tValue::UserData(ud) => ud.borrow::<Self>()?.0,\n\t\t\t_ => Err(\"expected a Style or nil\".into_lua_err())?,\n\t\t}))\n\t}\n}\n\nimpl UserData for Style {\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tcrate::impl_style_shorthands!(methods, 0);\n\n\t\tmethods\n\t\t\t.add_method(\"raw\", |lua, me, ()| lua.to_value_with(&yazi_config::Style::from(me.0), SER_OPT));\n\n\t\tmethods.add_function_mut(\"patch\", |_, (ud, style): (AnyUserData, Self)| {\n\t\t\tlet mut me = ud.borrow_mut::<Self>()?;\n\t\t\tme.0 = me.0.patch(style.0);\n\t\t\tOk(ud)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/url.rs",
    "content": "use std::ops::Deref;\n\nuse mlua::{AnyUserData, ExternalError, ExternalResult, IntoLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value};\nuse yazi_codegen::FromLuaOwned;\nuse yazi_fs::{FsHash64, FsHash128, FsUrl};\nuse yazi_shared::{path::{PathLike, StripPrefixError}, scheme::{SchemeCow, SchemeLike}, strand::{StrandLike, ToStrand}, url::{AsUrl, UrlCow, UrlLike}};\n\nuse crate::{Path, Scheme, cached_field};\n\npub type UrlRef = UserDataRef<Url>;\n\nconst EXPECTED: &str = \"expected a string, Url, or Path\";\n\n#[derive(FromLuaOwned)]\npub struct Url {\n\tinner: yazi_shared::url::UrlBuf,\n\n\tv_path:   Option<Value>,\n\tv_name:   Option<Value>,\n\tv_stem:   Option<Value>,\n\tv_ext:    Option<Value>,\n\tv_urn:    Option<Value>,\n\tv_base:   Option<Value>,\n\tv_parent: Option<Value>,\n\n\tv_scheme: Option<Value>,\n\tv_domain: Option<Value>,\n\n\tv_cache: Option<Value>,\n}\n\nimpl Deref for Url {\n\ttype Target = yazi_shared::url::UrlBuf;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl AsUrl for Url {\n\tfn as_url(&self) -> yazi_shared::url::Url<'_> { self.inner.as_url() }\n}\n\nimpl AsUrl for &Url {\n\tfn as_url(&self) -> yazi_shared::url::Url<'_> { self.inner.as_url() }\n}\n\nimpl From<Url> for yazi_shared::url::UrlBuf {\n\tfn from(value: Url) -> Self { value.inner }\n}\n\nimpl From<Url> for yazi_shared::url::UrlBufCov {\n\tfn from(value: Url) -> Self { Self(value.inner) }\n}\n\nimpl From<Url> for yazi_shared::url::UrlCow<'_> {\n\tfn from(value: Url) -> Self { value.inner.into() }\n}\n\nimpl TryFrom<&[u8]> for Url {\n\ttype Error = mlua::Error;\n\n\tfn try_from(value: &[u8]) -> mlua::Result<Self> { Ok(Self::new(UrlCow::try_from(value)?)) }\n}\n\nimpl Url {\n\tpub fn new(url: impl Into<yazi_shared::url::UrlBuf>) -> Self {\n\t\tSelf {\n\t\t\tinner: url.into(),\n\n\t\t\tv_path:   None,\n\t\t\tv_name:   None,\n\t\t\tv_stem:   None,\n\t\t\tv_ext:    None,\n\t\t\tv_urn:    None,\n\t\t\tv_base:   None,\n\t\t\tv_parent: None,\n\n\t\t\tv_scheme: None,\n\t\t\tv_domain: None,\n\n\t\t\tv_cache: None,\n\t\t}\n\t}\n\n\tpub fn install(lua: &Lua) -> mlua::Result<()> {\n\t\tlua.globals().raw_set(\n\t\t\t\"Url\",\n\t\t\tlua.create_function(|_, value: Value| {\n\t\t\t\tOk(match value {\n\t\t\t\t\tValue::String(s) => Self::try_from(&*s.as_bytes())?,\n\t\t\t\t\tValue::UserData(ud) => {\n\t\t\t\t\t\tif let Ok(url) = ud.borrow::<Self>() {\n\t\t\t\t\t\t\tSelf::new(&url.inner)\n\t\t\t\t\t\t} else if let Ok(path) = ud.borrow::<Path>() {\n\t\t\t\t\t\t\tSelf::new(path.as_os().into_lua_err()?)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tErr(EXPECTED.into_lua_err())?\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t_ => Err(EXPECTED.into_lua_err())?,\n\t\t\t\t})\n\t\t\t})?,\n\t\t)\n\t}\n\n\tfn ends_with(&self, child: Value) -> mlua::Result<bool> {\n\t\tmatch child {\n\t\t\tValue::String(s) => self.try_ends_with(UrlCow::try_from(&*s.as_bytes())?).into_lua_err(),\n\t\t\tValue::UserData(ud) => self.try_ends_with(&*ud.borrow::<Self>()?).into_lua_err(),\n\t\t\t_ => Err(\"must be a string or Url\".into_lua_err())?,\n\t\t}\n\t}\n\n\tfn hash(&self, long: Option<bool>) -> mlua::Result<String> {\n\t\tOk(if long.unwrap_or(false) {\n\t\t\tformat!(\"{:x}\", self.hash_u128())\n\t\t} else {\n\t\t\tformat!(\"{:x}\", self.hash_u64())\n\t\t})\n\t}\n\n\tfn join(&self, lua: &Lua, other: Value) -> mlua::Result<Value> {\n\t\tmatch other {\n\t\t\tValue::String(s) => {\n\t\t\t\tlet b = s.as_bytes();\n\t\t\t\tlet (scheme, path) = SchemeCow::parse(&b)?;\n\t\t\t\tif scheme.covariant(self.scheme()) {\n\t\t\t\t\tSelf::new(self.try_join(path).into_lua_err()?).into_lua(lua)\n\t\t\t\t} else {\n\t\t\t\t\tSelf::new(UrlCow::try_from((scheme, path))?).into_lua(lua)\n\t\t\t\t}\n\t\t\t}\n\t\t\tValue::UserData(ref ud) => {\n\t\t\t\tlet url = ud.borrow::<Self>()?;\n\t\t\t\tif url.scheme().covariant(self.scheme()) {\n\t\t\t\t\tSelf::new(self.try_join(url.loc()).into_lua_err()?).into_lua(lua)\n\t\t\t\t} else {\n\t\t\t\t\tOk(other)\n\t\t\t\t}\n\t\t\t}\n\t\t\t_ => Err(\"must be a string or Url\".into_lua_err())?,\n\t\t}\n\t}\n\n\tfn starts_with(&self, base: Value) -> mlua::Result<bool> {\n\t\tmatch base {\n\t\t\tValue::String(s) => self.try_starts_with(UrlCow::try_from(&*s.as_bytes())?).into_lua_err(),\n\t\t\tValue::UserData(ud) => self.try_starts_with(&*ud.borrow::<Self>()?).into_lua_err(),\n\t\t\t_ => Err(\"must be a string or Url\".into_lua_err())?,\n\t\t}\n\t}\n\n\tfn strip_prefix(&self, base: Value) -> mlua::Result<Option<Path>> {\n\t\tlet strip = match base {\n\t\t\tValue::String(s) => self.try_strip_prefix(UrlCow::try_from(&*s.as_bytes())?),\n\t\t\tValue::UserData(ud) => self.try_strip_prefix(&*ud.borrow::<Self>()?),\n\t\t\t_ => Err(\"must be a string or Url\".into_lua_err())?,\n\t\t};\n\n\t\tOk(match strip {\n\t\t\tOk(p) => Some(Path::new(p)),\n\t\t\tErr(StripPrefixError::Exotic | StripPrefixError::NotPrefix) => None,\n\t\t\tErr(e @ StripPrefixError::WrongEncoding) => Err(e.into_lua_err())?,\n\t\t})\n\t}\n}\n\nimpl UserData for Url {\n\tfn add_fields<F: UserDataFields<Self>>(fields: &mut F) {\n\t\tcached_field!(fields, path, |_, me| Ok(Path::new(me.loc())));\n\t\tcached_field!(fields, name, |lua, me| {\n\t\t\tme.name().map(|s| lua.create_string(s.encoded_bytes())).transpose()\n\t\t});\n\t\tcached_field!(fields, stem, |lua, me| {\n\t\t\tme.stem().map(|s| lua.create_string(s.encoded_bytes())).transpose()\n\t\t});\n\t\tcached_field!(fields, ext, |lua, me| {\n\t\t\tme.ext().map(|s| lua.create_string(s.encoded_bytes())).transpose()\n\t\t});\n\t\tcached_field!(fields, urn, |_, me| Ok(Path::new(me.urn())));\n\t\tcached_field!(fields, base, |_, me| {\n\t\t\tOk(Some(me.base()).filter(|u| !u.loc().is_empty()).map(Self::new))\n\t\t});\n\t\tcached_field!(fields, parent, |_, me| Ok(me.parent().map(Self::new)));\n\n\t\tcached_field!(fields, scheme, |_, me| Ok(Scheme::new(me.scheme())));\n\t\tcached_field!(fields, domain, |lua, me| {\n\t\t\tme.scheme().domain().map(|s| lua.create_string(s)).transpose()\n\t\t});\n\n\t\tcached_field!(fields, cache, |_, me| Ok(me.cache().map(Path::new)));\n\n\t\tfields.add_field_method_get(\"is_regular\", |_, me| Ok(me.is_regular()));\n\t\tfields.add_field_method_get(\"is_search\", |_, me| Ok(me.is_search()));\n\t\tfields.add_field_method_get(\"is_archive\", |_, me| Ok(me.is_archive()));\n\t\tfields.add_field_method_get(\"is_absolute\", |_, me| Ok(me.is_absolute()));\n\t\tfields.add_field_method_get(\"has_root\", |_, me| Ok(me.has_root()));\n\t}\n\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_method(\"ends_with\", |_, me, child: Value| me.ends_with(child));\n\t\tmethods.add_method(\"hash\", |_, me, long: Option<bool>| me.hash(long));\n\t\tmethods.add_method(\"join\", |lua, me, other: Value| me.join(lua, other));\n\t\tmethods.add_method(\"starts_with\", |_, me, base: Value| me.starts_with(base));\n\t\tmethods.add_method(\"strip_prefix\", |_, me, base: Value| me.strip_prefix(base));\n\n\t\tmethods.add_function_mut(\"into_search\", |_, (ud, domain): (AnyUserData, mlua::String)| {\n\t\t\tlet url = ud.take::<Self>()?.inner.into_search(domain.to_str()?).into_lua_err()?;\n\t\t\tOk(Self::new(url))\n\t\t});\n\n\t\tmethods.add_meta_method(MetaMethod::Eq, |_, me, other: UrlRef| Ok(me.inner == other.inner));\n\t\tmethods.add_meta_method(MetaMethod::ToString, |lua, me, ()| {\n\t\t\tlua.create_string(me.to_strand().encoded_bytes())\n\t\t});\n\t\tmethods.add_meta_method(MetaMethod::Concat, |lua, lhs, rhs: mlua::String| {\n\t\t\tlua.create_external_string([lhs.to_strand().encoded_bytes(), &rhs.as_bytes()].concat())\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-binding/src/utils.rs",
    "content": "use mlua::{IntoLua, Lua, SerializeOptions, Table, Value};\n\npub const SER_OPT: SerializeOptions =\n\tSerializeOptions::new().serialize_none_to_null(false).serialize_unit_to_null(false);\n\npub fn get_metatable(lua: &Lua, value: impl IntoLua) -> mlua::Result<Table> {\n\tlet (_, mt): (Value, Table) = unsafe {\n\t\tlua.exec_raw(value.into_lua(lua)?, |state| {\n\t\t\tmlua::ffi::lua_getmetatable(state, -1);\n\t\t})\n\t}?;\n\tOk(mt)\n}\n"
  },
  {
    "path": "yazi-boot/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-boot\"\ndescription            = \"Yazi bootstrapper\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[dependencies]\nyazi-adapter  = { path = \"../yazi-adapter\", version = \"26.2.2\" }\nyazi-config   = { path = \"../yazi-config\", version = \"26.2.2\" }\nyazi-emulator = { path = \"../yazi-emulator\", version = \"26.2.2\" }\nyazi-fs       = { path = \"../yazi-fs\", version = \"26.2.2\" }\nyazi-macro    = { path = \"../yazi-macro\", version = \"26.2.2\" }\nyazi-shared   = { path = \"../yazi-shared\", version = \"26.2.2\" }\nyazi-vfs      = { path = \"../yazi-vfs\", version = \"26.2.2\" }\n\n# External dependencies\nclap      = { workspace = true }\nfutures   = { workspace = true }\nhashbrown = { workspace = true }\nregex     = { workspace = true }\n\n[build-dependencies]\nyazi-shared = { path = \"../yazi-shared\", version = \"26.2.2\" }\n\n# External dependencies\nclap                  = { workspace = true }\nclap_complete         = \"4.6.0\"\nclap_complete_fig     = \"4.5.2\"\nclap_complete_nushell = \"4.6.0\"\nvergen-gitcl          = { version = \"9.1.0\", features = [ \"build\", \"rustc\" ] }\n"
  },
  {
    "path": "yazi-boot/README.md",
    "content": "# yazi-boot\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-boot/build.rs",
    "content": "#[path = \"src/args.rs\"]\nmod args;\n\nuse std::{env, error::Error};\n\nuse clap::CommandFactory;\nuse clap_complete::{Shell, generate_to};\nuse vergen_gitcl::{BuildBuilder, Emitter, GitclBuilder, RustcBuilder};\n\nfn main() -> Result<(), Box<dyn Error>> {\n\tEmitter::default()\n\t\t.add_instructions(&BuildBuilder::default().build_date(true).build()?)?\n\t\t.add_instructions(\n\t\t\t&RustcBuilder::default()\n\t\t\t\t.commit_date(true)\n\t\t\t\t.commit_hash(true)\n\t\t\t\t.host_triple(true)\n\t\t\t\t.semver(true)\n\t\t\t\t.build()?,\n\t\t)?\n\t\t.add_instructions(&GitclBuilder::default().commit_date(true).sha(true).build()?)?\n\t\t.emit()?;\n\n\tif env::var_os(\"YAZI_GEN_COMPLETIONS\").is_none() {\n\t\treturn Ok(());\n\t}\n\n\tlet cmd = &mut args::Args::command();\n\tlet bin = \"yazi\";\n\tlet out = \"completions\";\n\n\tstd::fs::create_dir_all(out)?;\n\tfor sh in [Shell::Bash, Shell::Fish, Shell::Zsh, Shell::Elvish, Shell::PowerShell] {\n\t\tgenerate_to(sh, cmd, bin, out)?;\n\t}\n\n\tgenerate_to(clap_complete_nushell::Nushell, cmd, bin, out)?;\n\tgenerate_to(clap_complete_fig::Fig, cmd, bin, out)?;\n\n\tOk(())\n}\n"
  },
  {
    "path": "yazi-boot/src/actions/actions.rs",
    "content": "use std::process;\n\npub struct Actions;\n\nimpl Actions {\n\tpub(crate) fn act(args: &crate::Args) {\n\t\tif args.debug {\n\t\t\tprintln!(\"{}\", Self::debug().unwrap());\n\t\t\tprocess::exit(0);\n\t\t}\n\n\t\tif args.version {\n\t\t\tprintln!(\"Yazi {}\", Self::version());\n\t\t\tprocess::exit(0);\n\t\t}\n\n\t\tif args.clear_cache {\n\t\t\tSelf::clear_cache();\n\t\t\tprocess::exit(0);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-boot/src/actions/clear_cache.rs",
    "content": "use yazi_config::YAZI;\nuse yazi_fs::Xdg;\n\nuse super::Actions;\n\nimpl Actions {\n\tpub(super) fn clear_cache() {\n\t\tif YAZI.preview.cache_dir == *Xdg::cache_dir() {\n\t\t\tprintln!(\"Clearing cache directory: \\n{:?}\", YAZI.preview.cache_dir);\n\t\t\tstd::fs::remove_dir_all(&YAZI.preview.cache_dir).unwrap();\n\t\t} else {\n\t\t\tprintln!(\n\t\t\t\t\"You've changed the default cache directory, for your data's safety, please clear it manually: \\n{:?}\",\n\t\t\t\tYAZI.preview.cache_dir\n\t\t\t);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-boot/src/actions/debug.rs",
    "content": "use std::{env, ffi::OsStr, fmt::Write, path::Path};\n\nuse regex::Regex;\nuse yazi_config::{THEME, YAZI};\nuse yazi_emulator::Mux;\nuse yazi_fs::Xdg;\nuse yazi_shared::timestamp_us;\n\nuse super::Actions;\n\nimpl Actions {\n\tpub(super) fn debug() -> Result<String, std::fmt::Error> {\n\t\tlet mut s = String::new();\n\t\twriteln!(s, \"\\nYazi\")?;\n\t\twriteln!(s, \"    Version  : {}\", Self::version())?;\n\t\twriteln!(s, \"    Debug    : {}\", cfg!(debug_assertions))?;\n\t\twriteln!(s, \"    Triple   : {}\", Self::triple())?;\n\t\twriteln!(s, \"    Rustc    : {}\", Self::rustc())?;\n\t\twriteln!(s, \"    Backtrace: {:?}\", env::var_os(\"RUST_BACKTRACE\"))?;\n\n\t\twriteln!(s, \"\\nYa\")?;\n\t\twriteln!(s, \"    Version: {}\", Self::process_output(\"ya\", \"--version\"))?;\n\n\t\twriteln!(s, \"\\nConfig\")?;\n\t\twriteln!(s, \"    Init             : {}\", Self::config_state(\"init.lua\"))?;\n\t\twriteln!(s, \"    Yazi             : {}\", Self::config_state(\"yazi.toml\"))?;\n\t\twriteln!(s, \"    Keymap           : {}\", Self::config_state(\"keymap.toml\"))?;\n\t\twriteln!(s, \"    Theme            : {}\", Self::config_state(\"theme.toml\"))?;\n\t\twriteln!(s, \"    VFS              : {}\", Self::config_state(\"vfs.toml\"))?;\n\t\twriteln!(s, \"    Package          : {}\", Self::config_state(\"package.toml\"))?;\n\t\twriteln!(s, \"    Dark/light flavor: {:?} / {:?}\", THEME.flavor.dark, THEME.flavor.light)?;\n\n\t\twriteln!(s, \"\\nEmulator\")?;\n\t\twriteln!(s, \"    TERM                : {:?}\", env::var_os(\"TERM\"))?;\n\t\twriteln!(s, \"    TERM_PROGRAM        : {:?}\", env::var_os(\"TERM_PROGRAM\"))?;\n\t\twriteln!(s, \"    TERM_PROGRAM_VERSION: {:?}\", env::var_os(\"TERM_PROGRAM_VERSION\"))?;\n\t\twriteln!(s, \"    Brand.from_env      : {:?}\", yazi_emulator::Brand::from_env())?;\n\t\twriteln!(s, \"    Emulator.detect     : {:?}\", &*yazi_emulator::EMULATOR)?;\n\n\t\twriteln!(s, \"\\nAdapter\")?;\n\t\twriteln!(s, \"    Adapter.matches    : {:?}\", yazi_adapter::ADAPTOR)?;\n\t\twriteln!(s, \"    Dimension.available: {:?}\", yazi_emulator::Dimension::available())?;\n\n\t\twriteln!(s, \"\\nDesktop\")?;\n\t\twriteln!(s, \"    XDG_SESSION_TYPE           : {:?}\", env::var_os(\"XDG_SESSION_TYPE\"))?;\n\t\twriteln!(s, \"    WAYLAND_DISPLAY            : {:?}\", env::var_os(\"WAYLAND_DISPLAY\"))?;\n\t\twriteln!(s, \"    DISPLAY                    : {:?}\", env::var_os(\"DISPLAY\"))?;\n\t\twriteln!(s, \"    SWAYSOCK                   : {:?}\", env::var_os(\"SWAYSOCK\"))?;\n\t\t#[rustfmt::skip]\n\t\twriteln!(s, \"    HYPRLAND_INSTANCE_SIGNATURE: {:?}\", env::var_os(\"HYPRLAND_INSTANCE_SIGNATURE\"))?;\n\t\twriteln!(s, \"    WAYFIRE_SOCKET             : {:?}\", env::var_os(\"WAYFIRE_SOCKET\"))?;\n\n\t\twriteln!(s, \"\\nSSH\")?;\n\t\twriteln!(s, \"    shared.in_ssh_connection: {}\", yazi_shared::in_ssh_connection())?;\n\n\t\twriteln!(s, \"\\nWSL\")?;\n\t\twriteln!(s, \"    WSL: {:?}\", yazi_adapter::WSL)?;\n\n\t\twriteln!(s, \"\\nVariables\")?;\n\t\twriteln!(s, \"    SHELL              : {:?}\", env::var_os(\"SHELL\"))?;\n\t\twriteln!(s, \"    EDITOR             : {:?}\", env::var_os(\"EDITOR\"))?;\n\t\twriteln!(s, \"    VISUAL             : {:?}\", env::var_os(\"VISUAL\"))?;\n\t\twriteln!(s, \"    YAZI_FILE_ONE      : {:?}\", env::var_os(\"YAZI_FILE_ONE\"))?;\n\t\twriteln!(s, \"    YAZI_CONFIG_HOME   : {:?}\", env::var_os(\"YAZI_CONFIG_HOME\"))?;\n\t\twriteln!(s, \"    YAZI_ZOXIDE_OPTS   : {:?}\", env::var_os(\"YAZI_ZOXIDE_OPTS\"))?;\n\t\twriteln!(s, \"    SSH_AUTH_SOCK      : {:?}\", env::var_os(\"SSH_AUTH_SOCK\"))?;\n\t\twriteln!(s, \"    FZF_DEFAULT_OPTS   : {:?}\", env::var_os(\"FZF_DEFAULT_OPTS\"))?;\n\t\twriteln!(s, \"    FZF_DEFAULT_COMMAND: {:?}\", env::var_os(\"FZF_DEFAULT_COMMAND\"))?;\n\n\t\twriteln!(s, \"\\nText Opener\")?;\n\t\twriteln!(\n\t\t\ts,\n\t\t\t\"    default     : {:?}\",\n\t\t\tYAZI.opener.first(YAZI.open.all(Path::new(\"f75a.txt\"), \"text/plain\"))\n\t\t)?;\n\t\twriteln!(\n\t\t\ts,\n\t\t\t\"    block-create: {:?}\",\n\t\t\tYAZI.opener.block(YAZI.open.all(Path::new(\"bulk-create.txt\"), \"text/plain\"))\n\t\t)?;\n\t\twriteln!(\n\t\t\ts,\n\t\t\t\"    block-rename: {:?}\",\n\t\t\tYAZI.opener.block(YAZI.open.all(Path::new(\"bulk-rename.txt\"), \"text/plain\"))\n\t\t)?;\n\n\t\twriteln!(s, \"\\nMultiplexers\")?;\n\t\twriteln!(s, \"    TMUX               : {}\", yazi_emulator::TMUX)?;\n\t\twriteln!(s, \"    tmux version       : {}\", Self::process_output(\"tmux\", \"-V\"))?;\n\t\twriteln!(s, \"    tmux build flags   : enable-sixel={}\", Mux::tmux_sixel_flag())?;\n\t\twriteln!(s, \"    ZELLIJ_SESSION_NAME: {:?}\", env::var_os(\"ZELLIJ_SESSION_NAME\"))?;\n\t\twriteln!(s, \"    Zellij version     : {}\", Self::process_output(\"zellij\", \"--version\"))?;\n\n\t\twriteln!(s, \"\\nDependencies\")?;\n\t\t#[rustfmt::skip]\n\t\twriteln!(s, \"    file          : {}\", Self::process_output(env::var_os(\"YAZI_FILE_ONE\").unwrap_or(\"file\".into()), \"--version\"))?;\n\t\twriteln!(s, \"    ueberzugpp    : {}\", Self::process_output(\"ueberzugpp\", \"--version\"))?;\n\t\t#[rustfmt::skip]\n\t\twriteln!(s, \"    ffmpeg/ffprobe: {} / {}\", Self::process_output(\"ffmpeg\", \"-version\"), Self::process_output(\"ffprobe\", \"-version\"))?;\n\t\twriteln!(s, \"    pdftoppm      : {}\", Self::process_output(\"pdftoppm\", \"--help\"))?;\n\t\twriteln!(s, \"    magick        : {}\", Self::process_output(\"magick\", \"--version\"))?;\n\t\twriteln!(s, \"    fzf           : {}\", Self::process_output(\"fzf\", \"--version\"))?;\n\t\t#[rustfmt::skip]\n\t\twriteln!(s, \"    fd/fdfind     : {} / {}\", Self::process_output(\"fd\", \"--version\"), Self::process_output(\"fdfind\", \"--version\"))?;\n\t\twriteln!(s, \"    rg            : {}\", Self::process_output(\"rg\", \"--version\"))?;\n\t\twriteln!(s, \"    chafa         : {}\", Self::process_output(\"chafa\", \"--version\"))?;\n\t\twriteln!(s, \"    zoxide        : {}\", Self::process_output(\"zoxide\", \"--version\"))?;\n\t\t#[rustfmt::skip]\n\t\twriteln!(s, \"    7zz/7z        : {} / {}\", Self::process_output(\"7zz\", \"i\"), Self::process_output(\"7z\", \"i\"))?;\n\t\twriteln!(s, \"    resvg         : {}\", Self::process_output(\"resvg\", \"--version\"))?;\n\t\twriteln!(s, \"    jq            : {}\", Self::process_output(\"jq\", \"--version\"))?;\n\n\t\twriteln!(s, \"\\nClipboard\")?;\n\t\t#[rustfmt::skip]\n\t\twriteln!(s, \"    wl-copy/paste: {} / {}\", Self::process_output(\"wl-copy\", \"--version\"), Self::process_output(\"wl-paste\", \"--version\"))?;\n\t\twriteln!(s, \"    xclip        : {}\", Self::process_output(\"xclip\", \"-version\"))?;\n\t\twriteln!(s, \"    xsel         : {}\", Self::process_output(\"xsel\", \"--version\"))?;\n\n\t\twriteln!(s, \"\\nRoutine\")?;\n\t\twriteln!(s, \"    `file -bL --mime-type`: {}\", Self::file1_output())?;\n\n\t\twriteln!(\n\t\t\ts,\n\t\t\t\"\\n\\nSee https://yazi-rs.github.io/docs/plugins/overview#debugging on how to enable logging or debug runtime errors.\"\n\t\t)?;\n\n\t\tOk(s)\n\t}\n\n\tfn config_state(name: &str) -> String {\n\t\tlet p = Xdg::config_dir().join(name);\n\t\tmatch std::fs::read_to_string(&p) {\n\t\t\tOk(s) if s.is_empty() => format!(\"{} (empty)\", p.display()),\n\t\t\tOk(s) if s.trim().is_empty() => format!(\"{} (whitespaces)\", p.display()),\n\t\t\tOk(s) => format!(\"{} ({} chars)\", p.display(), s.chars().count()),\n\t\t\tErr(e) => format!(\"{} ({e})\", p.display()),\n\t\t}\n\t}\n\n\tfn process_output(name: impl AsRef<OsStr>, arg: impl AsRef<OsStr>) -> String {\n\t\tmatch std::process::Command::new(&name).arg(arg).output() {\n\t\t\tOk(out) if out.status.success() => {\n\t\t\t\tlet line =\n\t\t\t\t\tString::from_utf8_lossy(&if out.stdout.is_empty() { out.stderr } else { out.stdout })\n\t\t\t\t\t\t.trim()\n\t\t\t\t\t\t.lines()\n\t\t\t\t\t\t.next()\n\t\t\t\t\t\t.unwrap_or_default()\n\t\t\t\t\t\t.to_owned();\n\t\t\t\tif name.as_ref() == \"ya\" {\n\t\t\t\t\tline.trim_start_matches(\"Ya \").to_owned()\n\t\t\t\t} else {\n\t\t\t\t\tRegex::new(r\"\\d+\\.\\d+(\\.\\d+-\\d+|\\.\\d+|\\b)\")\n\t\t\t\t\t\t.unwrap()\n\t\t\t\t\t\t.find(&line)\n\t\t\t\t\t\t.map(|m| m.as_str().to_owned())\n\t\t\t\t\t\t.unwrap_or(line)\n\t\t\t\t}\n\t\t\t}\n\t\t\tOk(out) => format!(\"{:?}, {:?}\", out.status, String::from_utf8_lossy(&out.stderr)),\n\t\t\tErr(e) => format!(\"{e}\"),\n\t\t}\n\t}\n\n\tfn file1_output() -> String {\n\t\tuse std::io::Write;\n\n\t\tlet p = env::temp_dir().join(format!(\".yazi-debug-{}.tmp\", timestamp_us()));\n\t\tstd::fs::File::create_new(&p).map(|mut f| f.write_all(b\"Hello, World!\")).ok();\n\n\t\tlet program = env::var_os(\"YAZI_FILE_ONE\").unwrap_or(\"file\".into());\n\t\tmatch std::process::Command::new(program).args([\"-bL\", \"--mime-type\"]).arg(&p).output() {\n\t\t\tOk(out) => {\n\t\t\t\tString::from_utf8_lossy(&out.stdout).trim().lines().next().unwrap_or_default().to_owned()\n\t\t\t}\n\t\t\tErr(e) => format!(\"{e}\"),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-boot/src/actions/mod.rs",
    "content": "yazi_macro::mod_flat!(actions clear_cache debug rustc triple version);\n"
  },
  {
    "path": "yazi-boot/src/actions/rustc.rs",
    "content": "use super::Actions;\n\nimpl Actions {\n\tpub(super) fn rustc() -> String {\n\t\tformat!(\n\t\t\t\"{} ({} {})\",\n\t\t\tenv!(\"VERGEN_RUSTC_SEMVER\"),\n\t\t\t&env!(\"VERGEN_RUSTC_COMMIT_HASH\")[..8],\n\t\t\tenv!(\"VERGEN_RUSTC_COMMIT_DATE\")\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "yazi-boot/src/actions/triple.rs",
    "content": "use super::Actions;\n\nimpl Actions {\n\tpub(super) fn triple() -> String {\n\t\tformat!(\n\t\t\t\"{} ({}-{})\",\n\t\t\tenv!(\"VERGEN_RUSTC_HOST_TRIPLE\"),\n\t\t\tstd::env::consts::OS,\n\t\t\tstd::env::consts::ARCH\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "yazi-boot/src/actions/version.rs",
    "content": "use super::Actions;\n\nimpl Actions {\n\tpub fn version() -> &'static str {\n\t\tconcat!(\n\t\t\tenv!(\"CARGO_PKG_VERSION\"),\n\t\t\t\" (\",\n\t\t\tenv!(\"VERGEN_GIT_SHA\"),\n\t\t\t\" \",\n\t\t\tenv!(\"VERGEN_BUILD_DATE\"),\n\t\t\t\")\"\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "yazi-boot/src/args.rs",
    "content": "use std::path::PathBuf;\n\nuse clap::Parser;\nuse yazi_shared::{Id, url::UrlBuf};\n\n#[derive(Debug, Default, Parser)]\n#[command(name = \"yazi\")]\n#[command(after_help = \"See https://yazi-rs.github.io/docs/quick-start for a quick starter.\")]\npub struct Args {\n\t/// Set the current working entry\n\t#[arg(index = 1, num_args = 1..=9)]\n\tpub entries: Vec<UrlBuf>,\n\n\t/// Write the cwd on exit to this file\n\t#[arg(long)]\n\tpub cwd_file:     Option<PathBuf>,\n\t/// Write the selected files to this file on open fired\n\t#[arg(long)]\n\tpub chooser_file: Option<PathBuf>,\n\n\t/// Clear the cache directory\n\t#[arg(long)]\n\tpub clear_cache: bool,\n\n\t/// Use the specified client ID, must be a globally unique number\n\t#[arg(long)]\n\tpub client_id:     Option<Id>,\n\t/// Report the specified local events to stdout\n\t#[arg(long)]\n\tpub local_events:  Option<String>,\n\t/// Report the specified remote events to stdout\n\t#[arg(long)]\n\tpub remote_events: Option<String>,\n\n\t/// Print debug information\n\t#[arg(long)]\n\tpub debug: bool,\n\n\t/// Print version\n\t#[arg(short = 'V', long)]\n\tpub version: bool,\n}\n"
  },
  {
    "path": "yazi-boot/src/boot.rs",
    "content": "use std::path::PathBuf;\n\nuse futures::executor::block_on;\nuse hashbrown::HashSet;\nuse yazi_fs::{CWD, Xdg, path::clean_url};\nuse yazi_shared::{strand::StrandBuf, url::{UrlBuf, UrlLike}};\nuse yazi_vfs::provider;\n\n#[derive(Debug, Default)]\npub struct Boot {\n\tpub cwds:  Vec<UrlBuf>,\n\tpub files: Vec<StrandBuf>,\n\n\tpub local_events:  HashSet<String>,\n\tpub remote_events: HashSet<String>,\n\n\tpub config_dir: PathBuf,\n\tpub flavor_dir: PathBuf,\n\tpub plugin_dir: PathBuf,\n\tpub state_dir:  PathBuf,\n}\n\nimpl Boot {\n\tasync fn parse_entries(entries: &[UrlBuf]) -> (Vec<UrlBuf>, Vec<StrandBuf>) {\n\t\tif entries.is_empty() {\n\t\t\treturn (vec![CWD.load().as_ref().clone()], vec![Default::default()]);\n\t\t}\n\n\t\tasync fn go(entry: &UrlBuf) -> (UrlBuf, StrandBuf) {\n\t\t\tlet mut entry = clean_url(entry);\n\n\t\t\tif let Ok(u) = provider::absolute(&entry).await\n\t\t\t\t&& u.is_owned()\n\t\t\t{\n\t\t\t\tentry = u.into_owned();\n\t\t\t}\n\n\t\t\tlet Some((parent, child)) = entry.pair() else {\n\t\t\t\treturn (entry, Default::default());\n\t\t\t};\n\n\t\t\tif provider::metadata(&entry).await.is_ok_and(|m| m.is_file()) {\n\t\t\t\t(parent.into(), child.into())\n\t\t\t} else {\n\t\t\t\t(entry, Default::default())\n\t\t\t}\n\t\t}\n\n\t\tfutures::future::join_all(entries.iter().map(go)).await.into_iter().unzip()\n\t}\n}\n\nimpl From<&crate::Args> for Boot {\n\tfn from(args: &crate::Args) -> Self {\n\t\tlet config_dir = Xdg::config_dir();\n\t\tlet (cwds, files) = block_on(Self::parse_entries(&args.entries));\n\n\t\tlet local_events = args\n\t\t\t.local_events\n\t\t\t.as_ref()\n\t\t\t.map(|s| s.split(',').map(|s| s.to_owned()).collect())\n\t\t\t.unwrap_or_default();\n\t\tlet remote_events = args\n\t\t\t.remote_events\n\t\t\t.as_ref()\n\t\t\t.map(|s| s.split(',').map(|s| s.to_owned()).collect())\n\t\t\t.unwrap_or_default();\n\n\t\tSelf {\n\t\t\tcwds,\n\t\t\tfiles,\n\n\t\t\tlocal_events,\n\t\t\tremote_events,\n\n\t\t\tflavor_dir: config_dir.join(\"flavors\"),\n\t\t\tplugin_dir: config_dir.join(\"plugins\"),\n\t\t\tconfig_dir,\n\t\t\tstate_dir: Xdg::state_dir(),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-boot/src/lib.rs",
    "content": "yazi_macro::mod_pub!(actions);\n\nyazi_macro::mod_flat!(args boot);\n\nuse clap::Parser;\nuse yazi_shared::RoCell;\n\npub static ARGS: RoCell<Args> = RoCell::new();\npub static BOOT: RoCell<Boot> = RoCell::new();\n\npub fn init() {\n\tARGS.with(<_>::parse);\n\tBOOT.init(<_>::from(&*ARGS));\n\n\tactions::Actions::act(&ARGS);\n}\n\npub fn init_default() {\n\tARGS.with(<_>::default);\n\tBOOT.with(<_>::default);\n}\n"
  },
  {
    "path": "yazi-build/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-build\"\ndescription            = \"Yazi build system\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[profile.release]\ncodegen-units = 1\nlto           = true\npanic         = \"abort\"\nstrip         = true\n\n[build-dependencies]\nyazi-tty = { path = \"../yazi-tty\", version = \"26.2.2\" }\n\n[[bin]]\nname = \"yazi-build\"\npath = \"src/main.rs\"\n"
  },
  {
    "path": "yazi-build/README.md",
    "content": "# yazi-build\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-build/build.rs",
    "content": "use std::{env, error::Error, io::{BufRead, BufReader, Read, Write}, process::{Command, Stdio}, thread};\n\nuse yazi_tty::TTY;\n\nfn main() -> Result<(), Box<dyn Error>> {\n\tyazi_tty::init();\n\n\tlet manifest = env::var_os(\"CARGO_MANIFEST_DIR\").unwrap().to_string_lossy().replace(r\"\\\", \"/\");\n\tlet crates = if manifest.contains(\"/git/checkouts/yazi-\") {\n\t\t&[\"--git\", \"https://github.com/sxyazi/yazi.git\", \"yazi-fm\", \"yazi-cli\"]\n\t} else if manifest.contains(\"/registry/src/index.crates.io-\") {\n\t\t&[\"yazi-fm\", \"yazi-cli\"][..]\n\t} else {\n\t\treturn Ok(());\n\t};\n\n\tlet target = env::var(\"TARGET\").unwrap();\n\tlet target_os = env::var(\"CARGO_CFG_TARGET_OS\").unwrap();\n\tunsafe {\n\t\tenv::set_var(\"CARGO_TARGET_DIR\", \"target\");\n\t\tenv::set_var(\"VERGEN_GIT_SHA\", \"Crates.io\");\n\t\tenv::set_var(\"YAZI_CRATE_BUILD\", \"1\");\n\n\t\tenv::set_var(\"JEMALLOC_SYS_WITH_LG_PAGE\", \"16\");\n\t\tenv::set_var(\"JEMALLOC_SYS_WITH_MALLOC_CONF\", \"narenas:1\");\n\n\t\tenv::set_var(\n\t\t\t\"MACOSX_DEPLOYMENT_TARGET\",\n\t\t\tif target == \"aarch64-apple-darwin\" { \"11.0\" } else { \"10.12\" },\n\t\t);\n\t\tif target == \"aarch64-apple-darwin\" {\n\t\t\tenv::set_var(\"RUSTFLAGS\", \"-Ctarget-cpu=apple-m1\");\n\t\t}\n\t};\n\n\tlet profile = if target_os == \"windows\" { &[\"--profile\", \"release-windows\"][..] } else { &[] };\n\tlet mut child = Command::new(env::var_os(\"CARGO\").unwrap())\n\t\t.args([\"install\", \"--force\", \"--locked\"])\n\t\t.args(profile)\n\t\t.args(crates)\n\t\t.stdout(Stdio::piped())\n\t\t.stderr(Stdio::piped())\n\t\t.spawn()?;\n\n\tlet out = flash(child.stdout.take().unwrap());\n\tlet err = flash(child.stderr.take().unwrap());\n\n\tchild.wait()?;\n\tout.join().ok();\n\terr.join().ok();\n\n\tOk(())\n}\n\nfn flash<R: Read + Send + 'static>(src: R) -> thread::JoinHandle<()> {\n\tthread::spawn(move || {\n\t\tlet reader = BufReader::new(src);\n\t\tfor part in reader.split(b'\\n') {\n\t\t\tmatch part {\n\t\t\t\tOk(mut bytes) => {\n\t\t\t\t\tbytes.push(b'\\n');\n\t\t\t\t\tlet mut out = TTY.lockout();\n\t\t\t\t\tout.write_all(&bytes).ok();\n\t\t\t\t\tout.flush().ok();\n\t\t\t\t}\n\t\t\t\tErr(_) => break,\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "yazi-build/src/main.rs",
    "content": "fn main() {\n\tprintln!(\"See https://yazi-rs.github.io/docs/installation#crates on how to install Yazi.\");\n}\n"
  },
  {
    "path": "yazi-cli/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-cli\"\ndescription            = \"Yazi command-line interface\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[profile.release]\ncodegen-units = 1\nlto           = true\npanic         = \"abort\"\nstrip         = true\n\n[profile.release-windows]\ninherits = \"release\"\npanic    = \"unwind\"\n\n[dependencies]\nyazi-boot   = { path = \"../yazi-boot\", version = \"26.2.2\" }\nyazi-dds    = { path = \"../yazi-dds\", version = \"26.2.2\" }\nyazi-fs     = { path = \"../yazi-fs\", version = \"26.2.2\" }\nyazi-macro  = { path = \"../yazi-macro\", version = \"26.2.2\" }\nyazi-shared = { path = \"../yazi-shared\", version = \"26.2.2\" }\nyazi-shim   = { path = \"../yazi-shim\", version = \"26.2.2\" }\n\n# External dependencies\nanyhow     = { workspace = true }\nclap       = { workspace = true }\ncrossterm  = { workspace = true }\nhashbrown  = { workspace = true }\nserde      = { workspace = true }\nserde_json = { workspace = true }\ntokio      = { workspace = true }\ntoml       = { workspace = true }\ntwox-hash  = { workspace = true }\n\n[build-dependencies]\nyazi-shared = { path = \"../yazi-shared\", version = \"26.2.2\" }\n\n# External build dependencies\nanyhow                = { workspace = true }\nclap                  = { workspace = true }\nclap_complete         = \"4.6.0\"\nclap_complete_fig     = \"4.5.2\"\nclap_complete_nushell = \"4.6.0\"\nserde                 = { workspace = true }\nserde_json            = { workspace = true }\nvergen-gitcl          = { version = \"9.1.0\", features = [ \"build\" ] }\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\ncrossterm = { workspace = true, features = [ \"use-dev-tty\", \"libc\" ] }\n\n[[bin]]\nname = \"ya\"\npath = \"src/main.rs\"\n\n[package.metadata.binstall]\npkg-url = \"{ repo }/releases/download/v{ version }/yazi-{ target }{ archive-suffix }\"\nbin-dir = \"yazi-{ target }/{ bin }{ binary-ext }\"\n"
  },
  {
    "path": "yazi-cli/README.md",
    "content": "# yazi-cli\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-cli/build.rs",
    "content": "#[path = \"src/args.rs\"]\nmod args;\n\nuse std::{env, error::Error};\n\nuse clap::CommandFactory;\nuse clap_complete::{Shell, generate_to};\nuse vergen_gitcl::{BuildBuilder, Emitter, GitclBuilder};\n\nfn main() -> Result<(), Box<dyn Error>> {\n\tlet manifest = env::var_os(\"CARGO_MANIFEST_DIR\").unwrap().to_string_lossy().replace(r\"\\\", \"/\");\n\tif env::var_os(\"YAZI_CRATE_BUILD\").is_none()\n\t\t&& (manifest.contains(\"/git/checkouts/yazi-\")\n\t\t\t|| manifest.contains(\"/registry/src/index.crates.io-\"))\n\t{\n\t\tpanic!(\n\t\t\t\"Due to Cargo's limitations, the `yazi-fm` and `yazi-cli` crates on crates.io must be built with `cargo install --force yazi-build`\"\n\t\t);\n\t}\n\n\tgenerate()\n}\n\nfn generate() -> Result<(), Box<dyn Error>> {\n\tEmitter::default()\n\t\t.add_instructions(&BuildBuilder::default().build_date(true).build()?)?\n\t\t.add_instructions(&GitclBuilder::default().commit_date(true).sha(true).build()?)?\n\t\t.emit()?;\n\n\tif env::var_os(\"YAZI_GEN_COMPLETIONS\").is_none() {\n\t\treturn Ok(());\n\t}\n\n\tlet cmd = &mut args::Args::command();\n\tlet bin = \"ya\";\n\tlet out = \"completions\";\n\n\tstd::fs::create_dir_all(out)?;\n\tfor sh in [Shell::Bash, Shell::Fish, Shell::Zsh, Shell::Elvish, Shell::PowerShell] {\n\t\tgenerate_to(sh, cmd, bin, out)?;\n\t}\n\n\tgenerate_to(clap_complete_nushell::Nushell, cmd, bin, out)?;\n\tgenerate_to(clap_complete_fig::Fig, cmd, bin, out)?;\n\tOk(())\n}\n"
  },
  {
    "path": "yazi-cli/src/args.rs",
    "content": "use std::{borrow::Cow, ffi::OsString};\n\nuse anyhow::{Result, bail};\nuse clap::{Parser, Subcommand};\nuse yazi_shared::Id;\n\n#[derive(Parser)]\n#[command(name = \"Ya\", about, long_about = None)]\npub(super) struct Args {\n\t#[command(subcommand)]\n\tpub(super) command: Command,\n\n\t/// Print version\n\t#[arg(short = 'V', long)]\n\tpub(super) version: bool,\n}\n\n#[derive(Subcommand)]\npub(super) enum Command {\n\t/// Emit an action to be executed by the current instance.\n\tEmit(CommandEmit),\n\t/// Emit an action to be executed by the specified instance.\n\tEmitTo(CommandEmitTo),\n\t/// Execute an action on the current instance and print its result.\n\tExec(CommandExec),\n\t/// Manage packages.\n\t#[command(subcommand)]\n\tPkg(CommandPkg),\n\t/// Publish a message to the current instance.\n\tPub(CommandPub),\n\t/// Publish a message to the specified instance.\n\tPubTo(CommandPubTo),\n\t/// Subscribe to messages from all remote instances.\n\tSub(CommandSub),\n}\n\n#[derive(clap::Args)]\npub(super) struct CommandEmit {\n\t/// Name of the action.\n\tpub(super) name: String,\n\t/// Arguments of the action.\n\t#[arg(allow_hyphen_values = true, trailing_var_arg = true)]\n\tpub(super) args: Vec<OsString>,\n}\n\n#[derive(clap::Args)]\npub(super) struct CommandEmitTo {\n\t/// Receiver ID.\n\tpub(super) receiver: Id,\n\t/// Name of the action.\n\tpub(super) name:     String,\n\t/// Arguments of the action.\n\t#[arg(allow_hyphen_values = true, trailing_var_arg = true)]\n\tpub(super) args:     Vec<OsString>,\n}\n\n#[derive(clap::Args)]\npub(super) struct CommandExec {\n\t/// Name of the action.\n\tpub(super) name: String,\n\t/// Arguments of the action.\n\t#[arg(allow_hyphen_values = true, trailing_var_arg = true)]\n\tpub(super) args: Vec<OsString>,\n}\n\n#[derive(Subcommand)]\npub(super) enum CommandPkg {\n\t/// Add packages.\n\t#[command(arg_required_else_help = true)]\n\tAdd {\n\t\t/// Packages to add.\n\t\t#[arg(index = 1, num_args = 1..)]\n\t\tids: Vec<String>,\n\t},\n\t/// Delete packages.\n\t#[command(arg_required_else_help = true)]\n\tDelete {\n\t\t/// Packages to delete.\n\t\t#[arg(index = 1, num_args = 1..)]\n\t\tids: Vec<String>,\n\t},\n\t/// Install all packages.\n\tInstall,\n\t/// List all packages.\n\tList,\n\t/// Upgrade all packages.\n\tUpgrade {\n\t\t/// Packages to upgrade, upgrade all if unspecified.\n\t\t#[arg(index = 1, num_args = 0..)]\n\t\tids: Vec<String>,\n\t},\n}\n\n#[derive(clap::Args)]\npub(super) struct CommandPub {\n\t/// Kind of message.\n\t#[arg(index = 1)]\n\tpub(super) kind: String,\n\t/// Send the message with a string body.\n\t#[arg(long)]\n\tpub(super) str:  Option<String>,\n\t/// Send the message with a JSON body.\n\t#[arg(long)]\n\tpub(super) json: Option<String>,\n\t/// Send the message as a list of strings.\n\t#[arg(long, num_args = 0..)]\n\tpub(super) list: Vec<String>,\n}\n\nimpl CommandPub {\n\t#[allow(dead_code)]\n\tpub(super) fn receiver() -> Result<Id> {\n\t\tif let Some(s) = std::env::var(\"YAZI_PID\").ok().filter(|s| !s.is_empty()) {\n\t\t\tOk(s.parse()?)\n\t\t} else {\n\t\t\tbail!(\"No `YAZI_ID` environment variable found.\")\n\t\t}\n\t}\n}\n\n#[derive(clap::Args)]\npub(super) struct CommandPubTo {\n\t/// Receiver ID.\n\t#[arg(index = 1)]\n\tpub(super) receiver: Id,\n\t/// Kind of message.\n\t#[arg(index = 2)]\n\tpub(super) kind:     String,\n\t/// Send the message with a string body.\n\t#[arg(long)]\n\tpub(super) str:      Option<String>,\n\t/// Send the message with a JSON body.\n\t#[arg(long)]\n\tpub(super) json:     Option<String>,\n\t/// Send the message as a list of strings.\n\t#[arg(long, num_args = 0..)]\n\tpub(super) list:     Vec<String>,\n}\n\n#[derive(clap::Args)]\npub(super) struct CommandSub {\n\t/// Kind of messages to subscribe to, separated by commas if multiple.\n\t#[arg(index = 1)]\n\tpub(super) kinds: String,\n}\n\n// --- Macros\nmacro_rules! impl_emit_body {\n\t($name:ident) => {\n\t\timpl $name {\n\t\t\t#[allow(dead_code)]\n\t\t\tpub(super) fn body(self) -> Result<String> {\n\t\t\t\t#[derive(serde::Serialize)]\n\t\t\t\t#[serde(untagged)]\n\t\t\t\tenum Elem {\n\t\t\t\t\tName(String),\n\t\t\t\t\tArg(Vec<u8>),\n\t\t\t\t}\n\n\t\t\t\tlet action: Vec<_> = [Elem::Name(self.name)]\n\t\t\t\t\t.into_iter()\n\t\t\t\t\t.chain(self.args.into_iter().map(|s| Elem::Arg(s.into_encoded_bytes())))\n\t\t\t\t\t.collect();\n\t\t\t\tOk(serde_json::to_string(&action)?)\n\t\t\t}\n\t\t}\n\t};\n}\n\nmacro_rules! impl_exec_body {\n\t($name:ident) => {\n\t\timpl $name {\n\t\t\t#[allow(dead_code)]\n\t\t\tpub(super) fn body(self, reply_to: Id) -> Result<String> {\n\t\t\t\t#[derive(serde::Serialize)]\n\t\t\t\t#[serde(untagged)]\n\t\t\t\tenum Elem {\n\t\t\t\t\tId(Id),\n\t\t\t\t\tName(String),\n\t\t\t\t\tArg(Vec<u8>),\n\t\t\t\t}\n\n\t\t\t\tlet action: Vec<_> = [Elem::Id(reply_to), Elem::Name(self.name)]\n\t\t\t\t\t.into_iter()\n\t\t\t\t\t.chain(self.args.into_iter().map(|s| Elem::Arg(s.into_encoded_bytes())))\n\t\t\t\t\t.collect();\n\t\t\t\tOk(serde_json::to_string(&action)?)\n\t\t\t}\n\t\t}\n\t};\n}\n\nmacro_rules! impl_pub_body {\n\t($name:ident) => {\n\t\timpl $name {\n\t\t\t#[allow(dead_code)]\n\t\t\tpub(super) fn body(&self) -> Result<Cow<'_, str>> {\n\t\t\t\tOk(if let Some(json) = &self.json {\n\t\t\t\t\tjson.into()\n\t\t\t\t} else if let Some(str) = &self.str {\n\t\t\t\t\tserde_json::to_string(str)?.into()\n\t\t\t\t} else if !self.list.is_empty() {\n\t\t\t\t\tserde_json::to_string(&self.list)?.into()\n\t\t\t\t} else {\n\t\t\t\t\t\"\".into()\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t};\n}\n\nimpl_emit_body!(CommandEmit);\nimpl_emit_body!(CommandEmitTo);\n\nimpl_exec_body!(CommandExec);\n\nimpl_pub_body!(CommandPub);\nimpl_pub_body!(CommandPubTo);\n"
  },
  {
    "path": "yazi-cli/src/dds/draw.rs",
    "content": "use anyhow::{Context, Result};\nuse hashbrown::HashSet;\nuse tokio::{io::AsyncWriteExt, time};\nuse yazi_dds::{ClientReader, Payload, Stream, ember::EmberHi};\nuse yazi_macro::try_format;\n\nuse crate::dds::Dds;\n\nimpl Dds {\n\t/// Connect to an existing server and listen in on the messages that are being\n\t/// sent by other yazi instances:\n\t///   - If no server is running, fail right away;\n\t///   - If a server is closed, attempt to reconnect forever.\n\tpub(crate) async fn draw(kinds: HashSet<&str>) -> Result<()> {\n\t\tasync fn make(kinds: &HashSet<&str>) -> Result<ClientReader> {\n\t\t\tlet (lines, mut writer) = Stream::connect().await?;\n\t\t\tlet hi = Payload::new(EmberHi::borrowed(kinds.iter().copied()));\n\t\t\twriter.write_all(try_format!(\"{hi}\\n\")?.as_bytes()).await?;\n\t\t\twriter.flush().await?;\n\t\t\tOk(lines)\n\t\t}\n\n\t\tlet mut lines = make(&kinds).await.context(\"No running Yazi instance found\")?;\n\t\tloop {\n\t\t\tmatch lines.next_line().await? {\n\t\t\t\tSome(s) => {\n\t\t\t\t\tlet kind = s.split(',').next();\n\t\t\t\t\tif matches!(kind, Some(kind) if kinds.contains(kind)) {\n\t\t\t\t\t\tprintln!(\"{s}\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tNone => loop {\n\t\t\t\t\ttime::sleep(time::Duration::from_secs(1)).await;\n\t\t\t\t\tif let Ok(new) = make(&kinds).await {\n\t\t\t\t\t\tlines = new;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-cli/src/dds/exec.rs",
    "content": "use std::str::FromStr;\n\nuse anyhow::{Result, bail};\nuse serde::Deserialize;\nuse tokio::io::AsyncWriteExt;\nuse yazi_dds::{ID, Payload, Stream, ember::{Ember, EmberHi}};\nuse yazi_macro::try_format;\nuse yazi_shared::{Id, data::Data};\n\nuse crate::{CommandExec, CommandPub, dds::Dds};\n\nimpl Dds {\n\tpub(crate) async fn exec(cmd: CommandExec) -> anyhow::Result<Data> {\n\t\tlet receiver = CommandPub::receiver()?;\n\t\tlet req = cmd.body(*yazi_dds::ID)?;\n\t\tlet resp = Self::ask(\"dds-exec\", receiver, &req, \"dds-exec-result\").await?;\n\n\t\t#[derive(Deserialize)]\n\t\tstruct Body {\n\t\t\tok:    bool,\n\t\t\t#[serde(default)]\n\t\t\tvalue: Data,\n\t\t\t#[serde(default)]\n\t\t\terror: String,\n\t\t}\n\n\t\tlet body = Body::deserialize(&resp)?;\n\t\tif body.ok {\n\t\t\tOk(body.value)\n\t\t} else if !body.error.is_empty() {\n\t\t\tbail!(\"{}\", body.error)\n\t\t} else {\n\t\t\tbail!(\"Unknown error\")\n\t\t}\n\t}\n\n\t/// Send one custom message and wait for a matching custom reply.\n\tasync fn ask(kind: &str, receiver: Id, body: &str, reply_kind: &str) -> Result<Data> {\n\t\tEmber::validate(kind)?;\n\t\tEmber::validate(reply_kind)?;\n\n\t\tlet payload = try_format!(\n\t\t\t\"{}\\n{kind},{receiver},{ID},{body}\\n\",\n\t\t\tPayload::new(EmberHi::borrowed([reply_kind])),\n\t\t)?;\n\n\t\tlet (mut lines, mut writer) = Stream::connect().await?;\n\t\twriter.write_all(payload.as_bytes()).await?;\n\t\twriter.flush().await?;\n\t\tdrop(writer);\n\n\t\twhile let Ok(Some(line)) = lines.next_line().await {\n\t\t\tmatch line.split(',').next() {\n\t\t\t\tSome(\"hey\") => {\n\t\t\t\t\tif let Ok(Ember::Hey(hey)) = Payload::from_str(&line).map(|p| p.body) {\n\t\t\t\t\t\tSelf::ensure_version(Some(&hey.version))?;\n\t\t\t\t\t\tSelf::ensure_ability(&hey.peers, kind, receiver)?;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tSome(kind) if kind == reply_kind => match Payload::from_str(&line)?.body {\n\t\t\t\t\tEmber::Custom(body) => return Ok(body.data),\n\t\t\t\t\t_ => bail!(\"Expected custom payload of kind `{reply_kind}`\"),\n\t\t\t\t},\n\t\t\t\t_ => {}\n\t\t\t}\n\t\t}\n\n\t\tbail!(\"Connection closed before receiving reply\")\n\t}\n}\n"
  },
  {
    "path": "yazi-cli/src/dds/mod.rs",
    "content": "yazi_macro::mod_flat!(draw exec shot);\n\npub(crate) struct Dds;\n"
  },
  {
    "path": "yazi-cli/src/dds/shot.rs",
    "content": "use std::str::FromStr;\n\nuse anyhow::{Result, bail};\nuse hashbrown::HashMap;\nuse tokio::io::AsyncWriteExt;\nuse yazi_dds::{ID, Payload, Peer, Stream, ember::{Ember, EmberBye, EmberHi}};\nuse yazi_macro::try_format;\nuse yazi_shared::Id;\n\nuse crate::dds::Dds;\n\nimpl Dds {\n\t/// Connect to an existing server to send a single message.\n\tpub(crate) async fn shot(kind: &str, receiver: Id, body: &str) -> Result<()> {\n\t\tEmber::validate(kind)?;\n\n\t\tlet payload = try_format!(\n\t\t\t\"{}\\n{kind},{receiver},{ID},{body}\\n{}\\n\",\n\t\t\tPayload::new(EmberHi::borrowed([])),\n\t\t\tPayload::new(EmberBye::borrowed())\n\t\t)?;\n\n\t\tlet (mut lines, mut writer) = Stream::connect().await?;\n\t\twriter.write_all(payload.as_bytes()).await?;\n\t\twriter.flush().await?;\n\t\tdrop(writer);\n\n\t\tlet (mut peers, mut version) = Default::default();\n\t\twhile let Ok(Some(line)) = lines.next_line().await {\n\t\t\tmatch line.split(',').next() {\n\t\t\t\tSome(\"hey\") => {\n\t\t\t\t\tif let Ok(Ember::Hey(hey)) = Payload::from_str(&line).map(|p| p.body) {\n\t\t\t\t\t\t(peers, version) = (hey.peers, Some(hey.version));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tSome(\"bye\") => break,\n\t\t\t\t_ => {}\n\t\t\t}\n\t\t}\n\n\t\tSelf::ensure_version(version.as_deref())?;\n\t\tSelf::ensure_ability(&peers, kind, receiver)?;\n\t\tOk(())\n\t}\n\n\tpub(super) fn ensure_version(version: Option<&str>) -> Result<()> {\n\t\tif version.as_deref() != Some(EmberHi::version()) {\n\t\t\tbail!(\n\t\t\t\t\"Incompatible version (Ya {}, Yazi {}). Restart all `ya` and `yazi` processes if you upgrade either one.\",\n\t\t\t\tEmberHi::version(),\n\t\t\t\tversion.as_deref().unwrap_or(\"Unknown\")\n\t\t\t);\n\t\t}\n\t\tOk(())\n\t}\n\n\tpub(super) fn ensure_ability(peers: &HashMap<Id, Peer>, kind: &str, receiver: Id) -> Result<()> {\n\t\tmatch (receiver, peers.get(&receiver).map(|p| p.able(kind))) {\n\t\t\t// Send to all receivers\n\t\t\t(Id(0), _) if peers.is_empty() => {\n\t\t\t\tbail!(\"No receiver found. Check if any receivers are running.\")\n\t\t\t}\n\t\t\t(Id(0), _) if peers.values().all(|p| !p.able(kind)) => {\n\t\t\t\tbail!(\"No receiver has the ability to receive `{kind}` messages.\")\n\t\t\t}\n\t\t\t(Id(0), _) => Ok(()),\n\n\t\t\t// Send to a specific receiver\n\t\t\t(_, Some(true)) => Ok(()),\n\t\t\t(_, Some(false)) => {\n\t\t\t\tbail!(\"Receiver `{receiver}` does not have the ability to receive `{kind}` messages.\")\n\t\t\t}\n\t\t\t(_, None) => bail!(\"Receiver `{receiver}` not found. Check if the receiver is running.\"),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-cli/src/main.rs",
    "content": "yazi_macro::mod_pub!(dds package shared);\n\nyazi_macro::mod_flat!(args);\n\nuse std::process::ExitCode;\n\nuse clap::Parser;\nuse yazi_macro::{errln, outln};\nuse yazi_shared::LOCAL_SET;\n\n#[tokio::main]\nasync fn main() -> ExitCode {\n\tyazi_shared::init();\n\tyazi_fs::init();\n\n\tmatch LOCAL_SET.run_until(run()).await {\n\t\tOk(()) => ExitCode::SUCCESS,\n\t\tErr(e) => {\n\t\t\tfor cause in e.chain() {\n\t\t\t\tif let Some(ioerr) = cause.downcast_ref::<std::io::Error>()\n\t\t\t\t\t&& ioerr.kind() == std::io::ErrorKind::BrokenPipe\n\t\t\t\t{\n\t\t\t\t\treturn ExitCode::from(0);\n\t\t\t\t}\n\t\t\t}\n\t\t\terrln!(\"{e:#}\").ok();\n\t\t\tExitCode::FAILURE\n\t\t}\n\t}\n}\n\nasync fn run() -> anyhow::Result<()> {\n\tif std::env::args_os().nth(1).is_some_and(|s| s == \"-V\" || s == \"--version\") {\n\t\toutln!(\n\t\t\t\"Ya {} ({} {})\",\n\t\t\tenv!(\"CARGO_PKG_VERSION\"),\n\t\t\tenv!(\"VERGEN_GIT_SHA\"),\n\t\t\tenv!(\"VERGEN_BUILD_DATE\")\n\t\t)?;\n\t\treturn Ok(());\n\t}\n\n\tmatch Args::parse().command {\n\t\tCommand::Emit(cmd) => {\n\t\t\tyazi_boot::init_default();\n\t\t\tyazi_dds::init();\n\t\t\tif let Err(e) = dds::Dds::shot(\"dds-emit\", CommandPub::receiver()?, &cmd.body()?).await {\n\t\t\t\terrln!(\"Cannot emit command: {e}\")?;\n\t\t\t\tstd::process::exit(1);\n\t\t\t}\n\t\t}\n\n\t\tCommand::EmitTo(cmd) => {\n\t\t\tyazi_boot::init_default();\n\t\t\tyazi_dds::init();\n\t\t\tif let Err(e) = dds::Dds::shot(\"dds-emit\", cmd.receiver, &cmd.body()?).await {\n\t\t\t\terrln!(\"Cannot emit command: {e}\")?;\n\t\t\t\tstd::process::exit(1);\n\t\t\t}\n\t\t}\n\n\t\tCommand::Exec(cmd) => {\n\t\t\tyazi_boot::init_default();\n\t\t\tyazi_dds::init();\n\n\t\t\tmatch dds::Dds::exec(cmd).await {\n\t\t\t\tOk(data) => outln!(\"{}\", serde_json::to_string(&data)?)?,\n\t\t\t\tErr(e) => {\n\t\t\t\t\terrln!(\"Cannot execute command: {e}\")?;\n\t\t\t\t\tstd::process::exit(1);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tCommand::Pkg(cmd) => {\n\t\t\tpackage::init()?;\n\n\t\t\tlet mut pkg = package::Package::load().await?;\n\t\t\tmatch cmd {\n\t\t\t\tCommandPkg::Add { ids } => pkg.add_many(&ids).await?,\n\t\t\t\tCommandPkg::Delete { ids } => pkg.delete_many(&ids).await?,\n\t\t\t\tCommandPkg::Install => pkg.install().await?,\n\t\t\t\tCommandPkg::List => pkg.print()?,\n\t\t\t\tCommandPkg::Upgrade { ids } => pkg.upgrade_many(&ids).await?,\n\t\t\t}\n\t\t}\n\n\t\tCommand::Pub(cmd) => {\n\t\t\tyazi_boot::init_default();\n\t\t\tyazi_dds::init();\n\t\t\tif let Err(e) = dds::Dds::shot(&cmd.kind, CommandPub::receiver()?, &cmd.body()?).await {\n\t\t\t\terrln!(\"Cannot send message: {e}\")?;\n\t\t\t\tstd::process::exit(1);\n\t\t\t}\n\t\t}\n\n\t\tCommand::PubTo(cmd) => {\n\t\t\tyazi_boot::init_default();\n\t\t\tyazi_dds::init();\n\t\t\tif let Err(e) = dds::Dds::shot(&cmd.kind, cmd.receiver, &cmd.body()?).await {\n\t\t\t\terrln!(\"Cannot send message: {e}\")?;\n\t\t\t\tstd::process::exit(1);\n\t\t\t}\n\t\t}\n\n\t\tCommand::Sub(cmd) => {\n\t\t\tyazi_boot::init_default();\n\t\t\tyazi_dds::init();\n\t\t\tdds::Dds::draw(cmd.kinds.split(',').collect()).await?;\n\n\t\t\ttokio::signal::ctrl_c().await?;\n\t\t}\n\t}\n\n\tOk(())\n}\n"
  },
  {
    "path": "yazi-cli/src/package/add.rs",
    "content": "use anyhow::Result;\n\nuse super::{Dependency, Git};\nuse crate::shared::must_exists;\n\nimpl Dependency {\n\tpub(super) async fn add(&mut self) -> Result<()> {\n\t\tself.header(\"Upgrading package `{name}`\")?;\n\n\t\tlet path = self.local();\n\t\tif must_exists(&path).await {\n\t\t\tGit::pull(&path).await?;\n\t\t} else {\n\t\t\tGit::clone(&self.remote(), &path).await?;\n\t\t};\n\n\t\tself.deploy().await?;\n\t\tself.rev = Git::revision(&path).await?;\n\t\tOk(())\n\t}\n}\n"
  },
  {
    "path": "yazi-cli/src/package/delete.rs",
    "content": "use anyhow::{Context, Result};\nuse yazi_fs::{ok_or_not_found, provider::{Provider, local::Local}};\nuse yazi_macro::outln;\n\nuse super::Dependency;\nuse crate::shared::{maybe_exists, remove_sealed};\n\nimpl Dependency {\n\tpub(super) async fn delete(&self) -> Result<()> {\n\t\tself.header(\"Deleting package `{name}`\")?;\n\n\t\tlet dir = self.target();\n\t\tif !maybe_exists(&dir).await {\n\t\t\treturn Ok(outln!(\"Not found, skipping\")?);\n\t\t}\n\n\t\tself.hash_check().await?;\n\t\tself.delete_assets().await?;\n\t\tself.delete_sources().await?;\n\n\t\tOk(())\n\t}\n\n\tpub(super) async fn delete_assets(&self) -> Result<()> {\n\t\tlet assets = self.target().join(\"assets\");\n\t\tmatch tokio::fs::read_dir(&assets).await {\n\t\t\tOk(mut it) => {\n\t\t\t\twhile let Some(entry) = it.next_entry().await? {\n\t\t\t\t\tremove_sealed(&entry.path())\n\t\t\t\t\t\t.await\n\t\t\t\t\t\t.with_context(|| format!(\"failed to remove `{}`\", entry.path().display()))?;\n\t\t\t\t}\n\t\t\t}\n\t\t\tErr(e) if e.kind() == std::io::ErrorKind::NotFound => {}\n\t\t\tErr(e) => Err(e).context(format!(\"failed to read `{}`\", assets.display()))?,\n\t\t};\n\n\t\tLocal::regular(&assets).remove_dir_clean().await;\n\t\tOk(())\n\t}\n\n\tpub(super) async fn delete_sources(&self) -> Result<()> {\n\t\tlet dir = self.target();\n\t\tlet files =\n\t\t\tif self.is_flavor { Self::flavor_files() } else { Self::plugin_files(&dir).await? };\n\n\t\tfor path in files.iter().map(|s| dir.join(s)) {\n\t\t\tok_or_not_found(remove_sealed(&path).await)\n\t\t\t\t.with_context(|| format!(\"failed to delete `{}`\", path.display()))?;\n\t\t}\n\n\t\tif ok_or_not_found(Local::regular(&dir).remove_dir().await).is_ok() {\n\t\t\toutln!(\"Done!\")?;\n\t\t} else {\n\t\t\toutln!(\n\t\t\t\t\"Done!\nFor safety, user data has been preserved, please manually delete them within: {}\",\n\t\t\t\tdir.display()\n\t\t\t)?;\n\t\t}\n\t\tOk(())\n\t}\n}\n"
  },
  {
    "path": "yazi-cli/src/package/dependency.rs",
    "content": "use std::{env, io::{self, BufWriter}, path::{Path, PathBuf}, str::FromStr};\n\nuse anyhow::{Result, bail};\nuse serde::{Deserialize, Deserializer, Serialize, Serializer};\nuse twox_hash::XxHash3_128;\nuse yazi_fs::Xdg;\nuse yazi_macro::ok_or_not_found;\nuse yazi_shared::BytesExt;\n\n#[derive(Clone, Default)]\npub(crate) struct Dependency {\n\tpub(crate) r#use: String, // owner/repo:child\n\tpub(crate) name:  String, // child.yazi\n\n\tpub(crate) parent: String, // owner/repo\n\tpub(crate) child:  String, // child.yazi\n\n\tpub(crate) rev:  String,\n\tpub(crate) hash: String,\n\n\tpub(super) is_flavor: bool,\n}\n\nimpl Dependency {\n\tpub(super) fn local(&self) -> PathBuf {\n\t\tXdg::state_dir()\n\t\t\t.join(\"packages\")\n\t\t\t.join(format!(\"{:x}\", XxHash3_128::oneshot(self.remote().as_bytes())))\n\t}\n\n\tpub(super) fn remote(&self) -> String {\n\t\t// Support more Git hosting services in the future\n\t\tformat!(\"https://github.com/{}.git\", self.parent)\n\t}\n\n\tpub(super) fn target(&self) -> PathBuf {\n\t\tif self.is_flavor {\n\t\t\tXdg::config_dir().join(format!(\"flavors/{}\", self.name))\n\t\t} else {\n\t\t\tXdg::config_dir().join(format!(\"plugins/{}\", self.name))\n\t\t}\n\t}\n\n\tpub(super) fn identical(&self, other: &Self) -> bool {\n\t\tself.parent == other.parent && self.child == other.child\n\t}\n\n\tpub(super) fn header(&self, s: &str) -> Result<()> {\n\t\tuse std::io::IsTerminal;\n\n\t\tuse crossterm::style::{Attribute, Print, SetAttributes};\n\t\tuse yazi_shim::crossterm::If;\n\n\t\tlet ansi = env::var_os(\"YA_FORCE_ANSI\").is_some_and(|v| v == \"1\") || io::stdout().is_terminal();\n\t\tcrossterm::execute!(\n\t\t\tBufWriter::new(io::stdout()),\n\t\t\tPrint(\"\\n\"),\n\t\t\tIf(ansi, SetAttributes(Attribute::Reverse.into())),\n\t\t\tIf(ansi, SetAttributes(Attribute::Bold.into())),\n\t\t\tPrint(\"  \"),\n\t\t\tPrint(s.replacen(\"{name}\", &self.name, 1)),\n\t\t\tPrint(\"  \"),\n\t\t\tIf(ansi, SetAttributes(Attribute::Reset.into())),\n\t\t\tPrint(\"\\n\\n\"),\n\t\t)?;\n\t\tOk(())\n\t}\n\n\tpub(super) async fn plugin_files(dir: &Path) -> io::Result<Vec<String>> {\n\t\tlet mut it = ok_or_not_found!(tokio::fs::read_dir(dir).await, return Ok(vec![]));\n\t\tlet mut files: Vec<String> =\n\t\t\t[\"LICENSE\", \"README.md\", \"main.lua\"].into_iter().map(Into::into).collect();\n\t\twhile let Some(entry) = it.next_entry().await? {\n\t\t\tif let Ok(name) = entry.file_name().into_string()\n\t\t\t\t&& let Some(stripped) = name.strip_suffix(\".lua\")\n\t\t\t\t&& stripped != \"main\"\n\t\t\t\t&& stripped.as_bytes().kebab_cased()\n\t\t\t{\n\t\t\t\tfiles.push(name);\n\t\t\t}\n\t\t}\n\t\tfiles.sort_unstable();\n\t\tOk(files)\n\t}\n\n\tpub(super) fn flavor_files() -> Vec<String> {\n\t\t[\"LICENSE\", \"LICENSE-tmtheme\", \"README.md\", \"flavor.toml\", \"preview.png\", \"tmtheme.xml\"]\n\t\t\t.into_iter()\n\t\t\t.map(Into::into)\n\t\t\t.collect()\n\t}\n}\n\nimpl FromStr for Dependency {\n\ttype Err = anyhow::Error;\n\n\tfn from_str(s: &str) -> std::result::Result<Self, Self::Err> {\n\t\tlet mut parts = s.splitn(2, ':');\n\n\t\tlet Some(parent) = parts.next() else { bail!(\"Package URL cannot be empty\") };\n\t\tlet child = parts.next().unwrap_or_default();\n\n\t\tlet Some((_, repo)) = parent.split_once('/') else {\n\t\t\tbail!(\"Package URL `{parent}` must be in the format `owner/repository`\")\n\t\t};\n\n\t\tlet name = if child.is_empty() { repo } else { child };\n\t\tif !name.as_bytes().kebab_cased() {\n\t\t\tbail!(\"Package name `{name}` must be in kebab-case\")\n\t\t}\n\n\t\tOk(Self {\n\t\t\tr#use: s.to_owned(),\n\t\t\tname: format!(\"{name}.yazi\"),\n\t\t\tparent: format!(\"{parent}{}\", if child.is_empty() { \".yazi\" } else { \"\" }),\n\t\t\tchild: if child.is_empty() { String::new() } else { format!(\"{child}.yazi\") },\n\t\t\t..Default::default()\n\t\t})\n\t}\n}\n\nimpl<'de> Deserialize<'de> for Dependency {\n\tfn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n\twhere\n\t\tD: Deserializer<'de>,\n\t{\n\t\t#[derive(Deserialize)]\n\t\tstruct Shadow {\n\t\t\tr#use: String,\n\t\t\t#[serde(default)]\n\t\t\trev:   String,\n\t\t\t#[serde(default)]\n\t\t\thash:  String,\n\t\t}\n\n\t\tlet outer = Shadow::deserialize(deserializer)?;\n\t\tOk(Self {\n\t\t\trev: outer.rev,\n\t\t\thash: outer.hash,\n\t\t\t..Self::from_str(&outer.r#use).map_err(serde::de::Error::custom)?\n\t\t})\n\t}\n}\n\nimpl Serialize for Dependency {\n\tfn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n\twhere\n\t\tS: Serializer,\n\t{\n\t\t#[derive(Serialize)]\n\t\tstruct Shadow<'a> {\n\t\t\tr#use: &'a str,\n\t\t\trev:   &'a str,\n\t\t\thash:  &'a str,\n\t\t}\n\n\t\tShadow { r#use: &self.r#use, rev: &self.rev, hash: &self.hash }.serialize(serializer)\n\t}\n}\n"
  },
  {
    "path": "yazi-cli/src/package/deploy.rs",
    "content": "use std::path::{Path, PathBuf};\n\nuse anyhow::{Context, Result};\nuse yazi_fs::provider::{Provider, local::Local};\nuse yazi_macro::outln;\n\nuse super::Dependency;\nuse crate::shared::{copy_and_seal, maybe_exists};\n\nimpl Dependency {\n\tpub(super) async fn deploy(&mut self) -> Result<()> {\n\t\tlet from = self.local().join(&self.child);\n\n\t\tself.header(\"Deploying package `{name}`\")?;\n\t\tself.is_flavor = maybe_exists(&from.join(\"flavor.toml\")).await;\n\n\t\tlet to = self.target();\n\t\tlet exists = maybe_exists(&to).await;\n\t\tif exists {\n\t\t\tself.hash_check().await?;\n\t\t}\n\n\t\tLocal::regular(&to).create_dir_all().await?;\n\t\tself.delete_assets().await?;\n\n\t\tlet res1 = Self::deploy_assets(from.join(\"assets\"), to.join(\"assets\")).await;\n\t\tlet res2 = Self::deploy_sources(&from, &to, self.is_flavor).await;\n\t\tif !exists && (res2.is_err() || res1.is_err()) {\n\t\t\tself.delete_assets().await?;\n\t\t\tself.delete_sources().await?;\n\t\t}\n\n\t\tLocal::regular(&to).remove_dir_clean().await;\n\t\tself.hash = self.hash().await?;\n\t\tres2?;\n\t\tres1?;\n\n\t\toutln!(\"Done!\")?;\n\t\tOk(())\n\t}\n\n\tasync fn deploy_assets(from: PathBuf, to: PathBuf) -> Result<()> {\n\t\tmatch tokio::fs::read_dir(&from).await {\n\t\t\tOk(mut it) => {\n\t\t\t\tLocal::regular(&to).create_dir_all().await?;\n\t\t\t\twhile let Some(entry) = it.next_entry().await? {\n\t\t\t\t\tlet (src, dist) = (entry.path(), to.join(entry.file_name()));\n\t\t\t\t\tcopy_and_seal(&src, &dist).await.with_context(|| {\n\t\t\t\t\t\tformat!(\"failed to copy `{}` to `{}`\", src.display(), dist.display())\n\t\t\t\t\t})?;\n\t\t\t\t}\n\t\t\t}\n\t\t\tErr(e) if e.kind() == std::io::ErrorKind::NotFound => {}\n\t\t\tErr(e) => Err(e).context(format!(\"failed to read `{}`\", from.display()))?,\n\t\t}\n\t\tOk(())\n\t}\n\n\tasync fn deploy_sources(from: &Path, to: &Path, is_flavor: bool) -> Result<()> {\n\t\tlet files = if is_flavor { Self::flavor_files() } else { Self::plugin_files(from).await? };\n\t\tfor file in files {\n\t\t\tlet (from, to) = (from.join(&file), to.join(&file));\n\t\t\tcopy_and_seal(&from, &to)\n\t\t\t\t.await\n\t\t\t\t.with_context(|| format!(\"failed to copy `{}` to `{}`\", from.display(), to.display()))?;\n\t\t}\n\t\tOk(())\n\t}\n}\n"
  },
  {
    "path": "yazi-cli/src/package/git.rs",
    "content": "use std::path::Path;\n\nuse anyhow::{Context, Result, bail};\nuse tokio::process::Command;\nuse yazi_shared::strip_trailing_newline;\n\npub(super) struct Git;\n\nimpl Git {\n\tpub(super) async fn clone(url: &str, path: &Path) -> Result<()> {\n\t\tSelf::exec(|c| c.args([\"clone\", url]).arg(path)).await\n\t}\n\n\tpub(super) async fn fetch(path: &Path) -> Result<()> {\n\t\tSelf::exec(|c| c.arg(\"fetch\").current_dir(path)).await\n\t}\n\n\tpub(super) async fn checkout(path: &Path, rev: &str) -> Result<()> {\n\t\tSelf::exec(|c| c.args([\"checkout\", rev, \"--force\"]).current_dir(path)).await\n\t}\n\n\tpub(super) async fn pull(path: &Path) -> Result<()> {\n\t\tSelf::fetch(path).await?;\n\t\tSelf::checkout(path, \"origin/HEAD\").await?;\n\t\tOk(())\n\t}\n\n\tpub(super) async fn revision(path: &Path) -> Result<String> {\n\t\tlet output = Command::new(\"git\")\n\t\t\t.args([\"rev-parse\", \"--short\", \"HEAD\"])\n\t\t\t.current_dir(path)\n\t\t\t.output()\n\t\t\t.await\n\t\t\t.context(\"Failed to get current revision\")?;\n\n\t\tif !output.status.success() {\n\t\t\tbail!(\"Getting revision failed: {}\", output.status);\n\t\t}\n\n\t\tOk(strip_trailing_newline(\n\t\t\tString::from_utf8(output.stdout).context(\"Failed to parse revision\")?,\n\t\t))\n\t}\n\n\tasync fn exec(f: impl FnOnce(&mut Command) -> &mut Command) -> Result<()> {\n\t\tlet status = f(Command::new(\"git\").args([\n\t\t\t\"-c\",\n\t\t\t\"advice.detachedHead=false\",\n\t\t\t\"-c\",\n\t\t\t\"checkout.defaultRemote=origin\",\n\t\t\t\"-c\",\n\t\t\t\"clone.defaultRemoteName=origin\",\n\t\t]))\n\t\t.status()\n\t\t.await\n\t\t.context(\"Failed to execute `git` command\")?;\n\n\t\tif !status.success() {\n\t\t\tbail!(\"`git` command failed: {status}\");\n\t\t}\n\n\t\tOk(())\n\t}\n}\n"
  },
  {
    "path": "yazi-cli/src/package/hash.rs",
    "content": "use anyhow::{Context, Result, bail};\nuse twox_hash::XxHash3_128;\nuse yazi_fs::provider::local::Local;\nuse yazi_macro::ok_or_not_found;\n\nuse super::Dependency;\n\nimpl Dependency {\n\tpub(crate) async fn hash(&self) -> Result<String> {\n\t\tlet dir = self.target();\n\t\tlet files =\n\t\t\tif self.is_flavor { Self::flavor_files() } else { Self::plugin_files(&dir).await? };\n\n\t\tlet mut h = XxHash3_128::new();\n\t\tfor file in files {\n\t\t\th.write(file.as_bytes());\n\t\t\th.write(b\"VpvFw9Atb7cWGOdqhZCra634CcJJRlsRl72RbZeV0vpG1\\0\");\n\t\t\th.write(&ok_or_not_found!(Local::regular(&dir.join(file)).read().await));\n\t\t}\n\n\t\tlet mut assets = vec![];\n\t\tmatch tokio::fs::read_dir(dir.join(\"assets\")).await {\n\t\t\tOk(mut it) => {\n\t\t\t\twhile let Some(entry) = it.next_entry().await? {\n\t\t\t\t\tlet Ok(name) = entry.file_name().into_string() else {\n\t\t\t\t\t\tbail!(\"asset path is not valid UTF-8: {}\", entry.path().display());\n\t\t\t\t\t};\n\t\t\t\t\tassets.push((name, Local::regular(&entry.path()).read().await?));\n\t\t\t\t}\n\t\t\t}\n\t\t\tErr(e) if e.kind() == std::io::ErrorKind::NotFound => {}\n\t\t\tErr(e) => Err(e).context(format!(\"failed to read `{}`\", dir.join(\"assets\").display()))?,\n\t\t}\n\n\t\tassets.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));\n\t\tfor (name, data) in assets {\n\t\t\th.write(name.as_bytes());\n\t\t\th.write(b\"pQU2in0xcsu97Y77Nuq2LnT8mczMlFj22idcYRmMrglqU\\0\");\n\t\t\th.write(&data);\n\t\t}\n\n\t\tOk(format!(\"{:x}\", h.finish_128()))\n\t}\n\n\tpub(super) async fn hash_check(&self) -> Result<()> {\n\t\tif self.hash != self.hash().await? {\n\t\t\tbail!(\n\t\t\t\t\"You have modified the contents of the `{}` {}. For safety, the operation has been aborted.\nPlease manually delete it from `{}` and re-run the command.\",\n\t\t\t\tself.name,\n\t\t\t\tif self.is_flavor { \"flavor\" } else { \"plugin\" },\n\t\t\t\tself.target().display()\n\t\t\t);\n\t\t}\n\t\tOk(())\n\t}\n}\n"
  },
  {
    "path": "yazi-cli/src/package/install.rs",
    "content": "use anyhow::Result;\n\nuse super::{Dependency, Git};\nuse crate::shared::must_exists;\n\nimpl Dependency {\n\tpub(super) async fn install(&mut self) -> Result<()> {\n\t\tself.header(\"Fetching package `{name}`\")?;\n\n\t\tlet path = self.local();\n\t\tif must_exists(&path).await {\n\t\t\tGit::fetch(&path).await?;\n\t\t} else {\n\t\t\tGit::clone(&self.remote(), &path).await?;\n\t\t};\n\n\t\tif !self.rev.is_empty() {\n\t\t\tGit::checkout(&path, self.rev.trim_start_matches('=')).await?;\n\t\t}\n\n\t\tself.deploy().await?;\n\t\tif self.rev.is_empty() {\n\t\t\tself.rev = Git::revision(&path).await?;\n\t\t}\n\n\t\tOk(())\n\t}\n}\n"
  },
  {
    "path": "yazi-cli/src/package/mod.rs",
    "content": "yazi_macro::mod_flat!(add delete dependency deploy git hash install package upgrade);\n\nuse anyhow::Context;\nuse yazi_fs::Xdg;\n\npub(super) fn init() -> anyhow::Result<()> {\n\tlet packages_dir = Xdg::state_dir().join(\"packages\");\n\tstd::fs::create_dir_all(&packages_dir)\n\t\t.with_context(|| format!(\"failed to create packages directory: {packages_dir:?}\"))?;\n\n\tlet config_dir = Xdg::config_dir();\n\tstd::fs::create_dir_all(&config_dir)\n\t\t.with_context(|| format!(\"failed to create config directory: {config_dir:?}\"))?;\n\n\tOk(())\n}\n"
  },
  {
    "path": "yazi-cli/src/package/package.rs",
    "content": "use std::{path::PathBuf, str::FromStr};\n\nuse anyhow::{Context, Result, bail};\nuse serde::{Deserialize, Deserializer, Serialize, Serializer};\nuse yazi_fs::{Xdg, provider::{Provider, local::Local}};\nuse yazi_macro::{ok_or_not_found, outln};\n\nuse super::Dependency;\n\n#[derive(Default)]\npub(crate) struct Package {\n\tpub(crate) plugins: Vec<Dependency>,\n\tpub(crate) flavors: Vec<Dependency>,\n}\n\nimpl Package {\n\tpub(crate) async fn load() -> Result<Self> {\n\t\tlet s = ok_or_not_found!(Local::regular(&Self::toml()).read_to_string().await);\n\t\tOk(toml::from_str(&s)?)\n\t}\n\n\tpub(crate) async fn add_many(&mut self, uses: &[String]) -> Result<()> {\n\t\tfor u in uses {\n\t\t\tlet r = self.add(u).await;\n\t\t\tself.save().await?;\n\t\t\tr?;\n\t\t}\n\t\tOk(())\n\t}\n\n\tpub(crate) async fn delete_many(&mut self, uses: &[String]) -> Result<()> {\n\t\tfor u in uses {\n\t\t\tlet r = self.delete(u).await;\n\t\t\tself.save().await?;\n\t\t\tr?;\n\t\t}\n\t\tOk(())\n\t}\n\n\tpub(crate) async fn install(&mut self) -> Result<()> {\n\t\tmacro_rules! go {\n\t\t\t($dep:expr) => {\n\t\t\t\tlet r = $dep.install().await;\n\t\t\t\tself.save().await?;\n\t\t\t\tr?;\n\t\t\t};\n\t\t}\n\n\t\tfor i in 0..self.plugins.len() {\n\t\t\tgo!(self.plugins[i]);\n\t\t}\n\t\tfor i in 0..self.flavors.len() {\n\t\t\tgo!(self.flavors[i]);\n\t\t}\n\t\tOk(())\n\t}\n\n\tpub(crate) async fn upgrade_many(&mut self, uses: &[String]) -> Result<()> {\n\t\tmacro_rules! go {\n\t\t\t($dep:expr) => {\n\t\t\t\tif uses.is_empty() || uses.contains(&$dep.r#use) {\n\t\t\t\t\tlet r = $dep.upgrade().await;\n\t\t\t\t\tself.save().await?;\n\t\t\t\t\tr?;\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\tfor i in 0..self.plugins.len() {\n\t\t\tgo!(self.plugins[i]);\n\t\t}\n\t\tfor i in 0..self.flavors.len() {\n\t\t\tgo!(self.flavors[i]);\n\t\t}\n\t\tOk(())\n\t}\n\n\tpub(crate) fn print(&self) -> Result<()> {\n\t\toutln!(\"Plugins:\")?;\n\t\tfor d in &self.plugins {\n\t\t\tif d.rev.is_empty() {\n\t\t\t\toutln!(\"\\t{}\", d.r#use)?;\n\t\t\t} else {\n\t\t\t\toutln!(\"\\t{} ({})\", d.r#use, d.rev)?;\n\t\t\t}\n\t\t}\n\n\t\toutln!(\"Flavors:\")?;\n\t\tfor d in &self.flavors {\n\t\t\tif d.rev.is_empty() {\n\t\t\t\toutln!(\"\\t{}\", d.r#use)?;\n\t\t\t} else {\n\t\t\t\toutln!(\"\\t{} ({})\", d.r#use, d.rev)?;\n\t\t\t}\n\t\t}\n\n\t\tOk(())\n\t}\n\n\tasync fn add(&mut self, r#use: &str) -> Result<()> {\n\t\tlet mut dep = Dependency::from_str(r#use)?;\n\t\tif let Some(d) = self.identical(&dep) {\n\t\t\tbail!(\n\t\t\t\t\"{} `{}` already exists in package.toml\",\n\t\t\t\tif d.is_flavor { \"Flavor\" } else { \"Plugin\" },\n\t\t\t\tdep.name\n\t\t\t)\n\t\t}\n\n\t\tdep.add().await?;\n\t\tif dep.is_flavor {\n\t\t\tself.flavors.push(dep);\n\t\t} else {\n\t\t\tself.plugins.push(dep);\n\t\t}\n\t\tOk(())\n\t}\n\n\tasync fn delete(&mut self, r#use: &str) -> Result<()> {\n\t\tlet Some(dep) = self.identical(&Dependency::from_str(r#use)?).cloned() else {\n\t\t\tbail!(\"`{}` was not found in package.toml\", r#use)\n\t\t};\n\n\t\tdep.delete().await?;\n\t\tif dep.is_flavor {\n\t\t\tself.flavors.retain(|d| !d.identical(&dep));\n\t\t} else {\n\t\t\tself.plugins.retain(|d| !d.identical(&dep));\n\t\t}\n\t\tOk(())\n\t}\n\n\tasync fn save(&self) -> Result<()> {\n\t\tlet s = toml::to_string_pretty(self)?;\n\t\tLocal::regular(&Self::toml()).write(s).await.context(\"Failed to write package.toml\")\n\t}\n\n\tfn toml() -> PathBuf { Xdg::config_dir().join(\"package.toml\") }\n\n\tfn identical(&self, other: &Dependency) -> Option<&Dependency> {\n\t\tself.plugins.iter().chain(&self.flavors).find(|d| d.identical(other))\n\t}\n}\n\nimpl<'de> Deserialize<'de> for Package {\n\tfn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n\twhere\n\t\tD: Deserializer<'de>,\n\t{\n\t\t#[derive(Deserialize)]\n\t\tstruct Outer {\n\t\t\t#[serde(default)]\n\t\t\tplugin: Shadow,\n\t\t\t#[serde(default)]\n\t\t\tflavor: Shadow,\n\t\t}\n\t\t#[derive(Default, Deserialize)]\n\t\tstruct Shadow {\n\t\t\tdeps: Vec<Dependency>,\n\t\t}\n\n\t\tlet mut outer = Outer::deserialize(deserializer)?;\n\t\touter.flavor.deps.iter_mut().for_each(|d| d.is_flavor = true);\n\n\t\tOk(Self { plugins: outer.plugin.deps, flavors: outer.flavor.deps })\n\t}\n}\n\nimpl Serialize for Package {\n\tfn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n\twhere\n\t\tS: Serializer,\n\t{\n\t\t#[derive(Serialize)]\n\t\tstruct Outer<'a> {\n\t\t\tplugin: Shadow<'a>,\n\t\t\tflavor: Shadow<'a>,\n\t\t}\n\t\t#[derive(Serialize)]\n\t\tstruct Shadow<'a> {\n\t\t\tdeps: &'a [Dependency],\n\t\t}\n\n\t\tOuter { plugin: Shadow { deps: &self.plugins }, flavor: Shadow { deps: &self.flavors } }\n\t\t\t.serialize(serializer)\n\t}\n}\n"
  },
  {
    "path": "yazi-cli/src/package/upgrade.rs",
    "content": "use anyhow::Result;\n\nuse super::Dependency;\n\nimpl Dependency {\n\tpub(super) async fn upgrade(&mut self) -> Result<()> {\n\t\tif self.rev.starts_with('=') { Ok(()) } else { self.add().await }\n\t}\n}\n"
  },
  {
    "path": "yazi-cli/src/shared/mod.rs",
    "content": "yazi_macro::mod_flat!(shared);\n"
  },
  {
    "path": "yazi-cli/src/shared/shared.rs",
    "content": "use std::{io, path::Path};\n\nuse tokio::io::AsyncWriteExt;\nuse yazi_fs::provider::{FileBuilder, Provider, local::{Gate, Local}};\nuse yazi_macro::ok_or_not_found;\n\n#[inline]\npub async fn must_exists(path: impl AsRef<Path>) -> bool {\n\tLocal::regular(&path).symlink_metadata().await.is_ok()\n}\n\n#[inline]\npub async fn maybe_exists(path: impl AsRef<Path>) -> bool {\n\tmatch Local::regular(&path).symlink_metadata().await {\n\t\tOk(_) => true,\n\t\tErr(e) => e.kind() != std::io::ErrorKind::NotFound,\n\t}\n}\n\npub async fn copy_and_seal(from: &Path, to: &Path) -> io::Result<()> {\n\tlet b = Local::regular(from).read().await?;\n\tok_or_not_found!(remove_sealed(to).await);\n\n\tlet mut file = Gate::default().create_new(true).write(true).truncate(true).open(to).await?;\n\tfile.write_all(&b).await?;\n\n\tlet mut perm = file.metadata().await?.permissions();\n\tperm.set_readonly(true);\n\tfile.set_permissions(perm).await?;\n\n\tOk(())\n}\n\npub async fn remove_sealed(p: &Path) -> io::Result<()> {\n\t#[cfg(windows)]\n\t{\n\t\tlet mut perm = tokio::fs::metadata(p).await?.permissions();\n\t\tperm.set_readonly(false);\n\t\ttokio::fs::set_permissions(p, perm).await?;\n\t}\n\n\tLocal::regular(p).remove_file().await\n}\n"
  },
  {
    "path": "yazi-codegen/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-codegen\"\ndescription            = \"Yazi code generator\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[lib]\nproc-macro = true\n\n[dependencies]\n# External dependencies\nsyn   = { version = \"2.0.117\", features = [ \"full\" ] }\nquote = \"1.0.45\"\n"
  },
  {
    "path": "yazi-codegen/README.md",
    "content": "# yazi-codegen\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-codegen/src/lib.rs",
    "content": "use proc_macro::TokenStream;\nuse quote::quote;\nuse syn::{Data, DeriveInput, Fields, parse_macro_input};\n\n#[proc_macro_derive(DeserializeOver)]\npub fn deserialize_over(input: TokenStream) -> TokenStream {\n\tlet DeriveInput { ident, .. } = parse_macro_input!(input as DeriveInput);\n\n\tquote! {\n\t\timpl #ident {\n\t\t\tpub(crate) fn deserialize_over(self, input: &str) -> Result<Self, toml::de::Error> {\n\t\t\t\tcrate::error_with_input(self.deserialize_over_with(toml::de::DeTable::parse(input)?), input)\n\t\t\t}\n\t\t}\n\t}\n\t.into()\n}\n\n#[proc_macro_derive(DeserializeOver1)]\npub fn deserialize_over1(input: TokenStream) -> TokenStream {\n\tlet DeriveInput { ident, data, .. } = parse_macro_input!(input as DeriveInput);\n\n\tlet assignments = match data {\n\t\tData::Struct(struct_) => match struct_.fields {\n\t\t\tFields::Named(fields) => {\n\t\t\t\tlet mut assignments = Vec::with_capacity(fields.named.len());\n\n\t\t\t\tfor field in fields.named {\n\t\t\t\t\tlet field_ident = &field.ident;\n\t\t\t\t\tlet field_name = field_ident.as_ref().unwrap().to_string();\n\n\t\t\t\t\tassignments.push(quote! {\n\t\t\t\t\t\tif let Some(value) = table.remove(#field_name) {\n\t\t\t\t\t\t\tif !matches!(value.get_ref(), toml::de::DeValue::Table(_)) {\n\t\t\t\t\t\t\t\t_ = toml::Table::deserialize(value.into_deserializer())?;\n\t\t\t\t\t\t\t\treturn Err(serde::de::Error::custom(format!(\"expected top-level `{}` to be a TOML table\", #field_name)));\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tlet span = value.span();\n\t\t\t\t\t\t\tif let toml::de::DeValue::Table(table) = value.into_inner() {\n\t\t\t\t\t\t\t\tself.#field_ident = self.#field_ident.deserialize_over_with(toml::Spanned::new(span, table))?;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tassignments\n\t\t\t}\n\t\t\t_ => panic!(\"DeserializeOver1 only supports structs with named fields\"),\n\t\t},\n\t\t_ => panic!(\"DeserializeOver1 only supports structs\"),\n\t};\n\n\tquote! {\n\t\timpl #ident {\n\t\t\tpub(crate) fn deserialize_over_with<'de>(mut self, table: toml::Spanned<toml::de::DeTable<'de>>) -> Result<Self, toml::de::Error> {\n\t\t\t\tuse serde::{Deserialize, de::IntoDeserializer};\n\n\t\t\t\tlet mut table = table.into_inner();\n\t\t\t\t#(#assignments)*\n\n\t\t\t\tOk(self)\n\t\t\t}\n\t\t}\n\t}\n\t.into()\n}\n\n#[proc_macro_derive(DeserializeOver2)]\npub fn deserialize_over2(input: TokenStream) -> TokenStream {\n\tlet DeriveInput { ident, data, .. } = parse_macro_input!(input as DeriveInput);\n\n\tlet assignments = match data {\n\t\tData::Struct(struct_) => match struct_.fields {\n\t\t\tFields::Named(fields) => {\n\t\t\t\tlet mut assignments = Vec::with_capacity(fields.named.len());\n\n\t\t\t\tfor field in fields.named {\n\t\t\t\t\tlet field_ident = field.ident;\n\t\t\t\t\tlet field_name = field_ident.as_ref().unwrap().to_string();\n\n\t\t\t\t\tassignments.push(quote! {\n\t\t\t\t\t\tif let Some(value) = table.remove(#field_name) {\n\t\t\t\t\t\t\tself.#field_ident = <_>::deserialize(value.into_deserializer())?;\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tassignments\n\t\t\t}\n\t\t\t_ => panic!(\"DeserializeOver2 only supports structs with named fields\"),\n\t\t},\n\t\t_ => panic!(\"DeserializeOver2 only supports structs\"),\n\t};\n\n\tquote! {\n\t\timpl #ident {\n\t\t\tpub(crate) fn deserialize_over_with<'de>(mut self, table: toml::Spanned<toml::de::DeTable<'de>>) -> Result<Self, toml::de::Error> {\n\t\t\t\tuse serde::{Deserialize, de::IntoDeserializer};\n\n\t\t\t\tlet mut table = table.into_inner();\n\t\t\t\t#(#assignments)*\n\n\t\t\t\tOk(self)\n\t\t\t}\n\t\t}\n\t}\n\t.into()\n}\n\n#[proc_macro_derive(FromLuaOwned)]\npub fn from_lua(input: TokenStream) -> TokenStream {\n\tlet DeriveInput { ident, generics, .. } = parse_macro_input!(input as DeriveInput);\n\n\tlet ident_str = ident.to_string();\n\tlet (impl_generics, ty_generics, where_clause) = generics.split_for_impl();\n\n\tquote! {\n\t\timpl #impl_generics ::mlua::FromLua for #ident #ty_generics #where_clause {\n\t\t\t#[inline]\n\t\t\tfn from_lua(value: ::mlua::Value, _: &::mlua::Lua) -> ::mlua::Result<Self> {\n\t\t\t\tmatch value {\n\t\t\t\t\t::mlua::Value::UserData(ud) => ud.take::<Self>(),\n\t\t\t\t\t_ => Err(::mlua::Error::FromLuaConversionError {\n\t\t\t\t\t\t\tfrom: value.type_name(),\n\t\t\t\t\t\t\tto: #ident_str.to_owned(),\n\t\t\t\t\t\t\tmessage: None,\n\t\t\t\t\t}),\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t.into()\n}\n"
  },
  {
    "path": "yazi-config/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-config\"\ndescription            = \"Yazi configuration file parser\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[dependencies]\nyazi-codegen = { path = \"../yazi-codegen\", version = \"26.2.2\" }\nyazi-fs      = { path = \"../yazi-fs\", version = \"26.2.2\" }\nyazi-macro   = { path = \"../yazi-macro\", version = \"26.2.2\" }\nyazi-shared  = { path = \"../yazi-shared\", version = \"26.2.2\" }\nyazi-tty     = { path = \"../yazi-tty\", version = \"26.2.2\" }\n\n# External dependencies\nanyhow    = { workspace = true }\nbitflags  = { workspace = true }\ncrossterm = { workspace = true }\nglobset   = { workspace = true }\nhashbrown = { workspace = true }\nindexmap  = { workspace = true }\nratatui   = { workspace = true }\nregex     = { workspace = true }\nserde     = { workspace = true }\ntokio     = { workspace = true }\ntoml      = { workspace = true }\ntracing   = { workspace = true }\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\ncrossterm = { workspace = true, features = [ \"use-dev-tty\", \"libc\" ] }\n"
  },
  {
    "path": "yazi-config/README.md",
    "content": "# yazi-config\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-config/preset/README.md",
    "content": "# Default Configuration\n\n> [!IMPORTANT]\n> If you're using a stable release of Yazi instead of the newest nightly build, make sure you're checking these files out from [the `shipped` tag][shipped], not the newest `main` branch.\n\nThis directory contains the default configuration files for Yazi:\n\n- [`yazi-default.toml`][yazi-default]: General configuration\n- [`keymap-default.toml`][keymap-default]: Keybindings configuration\n- [`theme-dark.toml`][theme-dark]: Dark color scheme (loaded when your terminal is in dark mode)\n- [`theme-light.toml`][theme-light]: Light color scheme (loaded when your terminal is in light mode)\n\nThese files are already included with Yazi when you install the release, so you don't need to manually download or copy them to your Yazi configuration directory.\n\nHowever, if you want to customize certain configurations:\n\n- Create a `yazi.toml` in your config directory to override certain settings in [`yazi-default.toml`][yazi-default], so either:\n  - `~/.config/yazi/yazi.toml` on Unix-like systems\n  - `%AppData%\\yazi\\config\\yazi.toml` on Windows\n- Create a `keymap.toml` in your config directory to override certain settings in [`keymap-default.toml`][keymap-default], so either:\n  - `~/.config/yazi/keymap.toml` on Unix-like systems\n  - `%AppData%\\yazi\\config\\keymap.toml` on Windows\n- Create a `theme.toml` in your config directory to override certain settings in [`theme-light.toml`][theme-light] and [`theme-dark.toml`][theme-dark], so either:\n  - `~/.config/yazi/theme.toml` on Unix-like systems\n  - `%AppData%\\yazi\\config\\theme.toml` on Windows\n\nFor the user's `theme.toml` file, you can only apply the same color scheme to both the light and dark themes.\n\nIf you want more granular control over colors, specify two different flavors for light and dark modes under the `[flavor]` section of your `theme.toml`, and override them in your respective flavor instead.\n\n[shipped]: https://github.com/sxyazi/yazi/tree/shipped\n[yazi-default]: yazi-default.toml\n[keymap-default]: keymap-default.toml\n[theme-dark]: theme-dark.toml\n[theme-light]: theme-light.toml\n\n## Learn more\n\n- [Configuration documentation](https://yazi-rs.github.io/docs/configuration/overview)\n- [Flavors documentation](https://yazi-rs.github.io/docs/flavors/overview)\n"
  },
  {
    "path": "yazi-config/preset/keymap-default.toml",
    "content": "# A TOML linter such as https://taplo.tamasfe.dev/ can use this schema to validate your config.\n# If you encounter any issues, please make an issue at https://github.com/yazi-rs/schemas.\n\"$schema\" = \"https://yazi-rs.github.io/schemas/keymap.json\"\n\n[mgr]\n\nkeymap = [\n\t{ on = \"<Esc>\", run = \"escape\",             desc = \"Exit visual mode, clear selection, or cancel search\" },\n\t{ on = \"<C-[>\", run = \"escape\",             desc = \"Exit visual mode, clear selection, or cancel search\" },\n\t{ on = \"q\",     run = \"quit\",               desc = \"Quit the process\" },\n\t{ on = \"Q\",     run = \"quit --no-cwd-file\", desc = \"Quit without outputting cwd-file\" },\n\t{ on = \"<C-c>\", run = \"close\",              desc = \"Close the current tab, or quit if it's last\" },\n\t{ on = \"<C-z>\", run = \"suspend\",            desc = \"Suspend the process\" },\n\n\t# Hopping\n\t{ on = \"k\", run = \"arrow prev\", desc = \"Previous file\" },\n\t{ on = \"j\", run = \"arrow next\", desc = \"Next file\" },\n\n\t{ on = \"<Up>\",   run = \"arrow prev\", desc = \"Previous file\" },\n\t{ on = \"<Down>\", run = \"arrow next\", desc = \"Next file\" },\n\n\t{ on = \"<C-u>\", run = \"arrow -50%\",  desc = \"Move cursor up half page\" },\n\t{ on = \"<C-d>\", run = \"arrow 50%\",   desc = \"Move cursor down half page\" },\n\t{ on = \"<C-b>\", run = \"arrow -100%\", desc = \"Move cursor up one page\" },\n\t{ on = \"<C-f>\", run = \"arrow 100%\",  desc = \"Move cursor down one page\" },\n\n\t{ on = \"<S-PageUp>\",   run = \"arrow -50%\",  desc = \"Move cursor up half page\" },\n\t{ on = \"<S-PageDown>\", run = \"arrow 50%\",   desc = \"Move cursor down half page\" },\n\t{ on = \"<PageUp>\",     run = \"arrow -100%\", desc = \"Move cursor up one page\" },\n\t{ on = \"<PageDown>\",   run = \"arrow 100%\",  desc = \"Move cursor down one page\" },\n\n\t{ on = [ \"g\", \"g\" ], run = \"arrow top\", desc = \"Go to top\" },\n\t{ on = \"G\",          run = \"arrow bot\", desc = \"Go to bottom\" },\n\n\t# Navigation\n\t{ on = \"h\", run = \"leave\", desc = \"Back to the parent directory\" },\n\t{ on = \"l\", run = \"enter\", desc = \"Enter the child directory\" },\n\n\t{ on = \"<Left>\",  run = \"leave\", desc = \"Back to the parent directory\" },\n\t{ on = \"<Right>\", run = \"enter\", desc = \"Enter the child directory\" },\n\n\t{ on = \"H\", run = \"back\",    desc = \"Back to previous directory\" },\n\t{ on = \"L\", run = \"forward\", desc = \"Forward to next directory\" },\n\n\t# Toggle\n\t{ on = \"<Space>\", run = [ \"toggle\", \"arrow 1\" ], desc = \"Toggle the current selection state\" },\n\t{ on = \"<C-a>\",   run = \"toggle_all --state=on\", desc = \"Select all files\" },\n\t{ on = \"<C-r>\",   run = \"toggle_all\",            desc = \"Invert selection of all files\" },\n\n\t# Visual mode\n\t{ on = \"v\", run = \"visual_mode\",         desc = \"Enter visual mode (selection mode)\" },\n\t{ on = \"V\", run = \"visual_mode --unset\", desc = \"Enter visual mode (unset mode)\" },\n\n\t# Seeking\n\t{ on = \"K\", run = \"seek -5\", desc = \"Seek up 5 units in the preview\" },\n\t{ on = \"J\", run = \"seek 5\",  desc = \"Seek down 5 units in the preview\" },\n\n\t# Spotting\n\t{ on = \"<Tab>\", run = \"spot\", desc = \"Spot hovered file\" },\n\n\t# Operation\n\t{ on = \"o\",         run = \"open\",                        desc = \"Open selected files\" },\n\t{ on = \"O\",         run = \"open --interactive\",          desc = \"Open selected files interactively\" },\n\t{ on = \"<Enter>\",   run = \"open\",                        desc = \"Open selected files\" },\n\t{ on = \"<S-Enter>\", run = \"open --interactive\",          desc = \"Open selected files interactively\" },\n\t{ on = \"y\",         run = \"yank\",                        desc = \"Yank selected files (copy)\" },\n\t{ on = \"x\",         run = \"yank --cut\",                  desc = \"Yank selected files (cut)\" },\n\t{ on = \"p\",         run = \"paste\",                       desc = \"Paste yanked files\" },\n\t{ on = \"P\",         run = \"paste --force\",               desc = \"Paste yanked files (overwrite if the destination exists)\" },\n\t{ on = \"-\",         run = \"link\",                        desc = \"Symlink the absolute path of yanked files\" },\n\t{ on = \"_\",         run = \"link --relative\",             desc = \"Symlink the relative path of yanked files\" },\n\t{ on = \"<C-->\",     run = \"hardlink\",                    desc = \"Hardlink yanked files\" },\n\t{ on = \"Y\",         run = \"unyank\",                      desc = \"Cancel the yank status\" },\n\t{ on = \"X\",         run = \"unyank\",                      desc = \"Cancel the yank status\" },\n\t{ on = \"d\",         run = \"remove\",                      desc = \"Trash selected files\" },\n\t{ on = \"D\",         run = \"remove --permanently\",        desc = \"Permanently delete selected files\" },\n\t{ on = \"a\",         run = \"create\",                      desc = \"Create a file (ends with / for directories)\" },\n\t{ on = \"r\",         run = \"rename --cursor=before_ext\",  desc = \"Rename selected file(s)\" },\n\t{ on = \";\",         run = \"shell --interactive\",         desc = \"Run a shell command\" },\n\t{ on = \":\",         run = \"shell --block --interactive\", desc = \"Run a shell command (block until finishes)\" },\n\t{ on = \".\",         run = \"hidden toggle\",               desc = \"Toggle the visibility of hidden files\" },\n\t{ on = \"s\",         run = \"search --via=fd\",             desc = \"Search files by name via fd\" },\n\t{ on = \"S\",         run = \"search --via=rg\",             desc = \"Search files by content via ripgrep\" },\n\t{ on = \"<C-s>\",     run = \"escape --search\",             desc = \"Cancel the ongoing search\" },\n\t{ on = \"z\",         run = \"plugin fzf\",                  desc = \"Jump to a file/directory via fzf\" },\n\t{ on = \"Z\",         run = \"plugin zoxide\",               desc = \"Jump to a directory via zoxide\" },\n\n\t# Linemode\n\t{ on = [ \"m\", \"s\" ], run = \"linemode size\",        desc = \"Linemode: size\" },\n\t{ on = [ \"m\", \"p\" ], run = \"linemode permissions\", desc = \"Linemode: permissions\" },\n\t{ on = [ \"m\", \"b\" ], run = \"linemode btime\",       desc = \"Linemode: btime\" },\n\t{ on = [ \"m\", \"m\" ], run = \"linemode mtime\",       desc = \"Linemode: mtime\" },\n\t{ on = [ \"m\", \"o\" ], run = \"linemode owner\",       desc = \"Linemode: owner\" },\n\t{ on = [ \"m\", \"n\" ], run = \"linemode none\",        desc = \"Linemode: none\" },\n\n\t# Copy\n\t{ on = [ \"c\", \"c\" ], run = \"copy path\",             desc = \"Copy file URL\" },\n\t{ on = [ \"c\", \"d\" ], run = \"copy dirname\",          desc = \"Copy directory URL\" },\n\t{ on = [ \"c\", \"f\" ], run = \"copy filename\",         desc = \"Copy filename\" },\n\t{ on = [ \"c\", \"n\" ], run = \"copy name_without_ext\", desc = \"Copy filename without extension\" },\n\n\t# Filter\n\t{ on = \"f\", run = \"filter --smart\", desc = \"Filter files\" },\n\n\t# Find\n\t{ on = \"/\", run = \"find --smart\",            desc = \"Find next file\" },\n\t{ on = \"?\", run = \"find --previous --smart\", desc = \"Find previous file\" },\n\t{ on = \"n\", run = \"find_arrow\",              desc = \"Next found\" },\n\t{ on = \"N\", run = \"find_arrow --previous\",   desc = \"Previous found\" },\n\n\t# Sorting\n\t{ on = [ \",\", \"m\" ], run = [ \"sort mtime --reverse=no\", \"linemode mtime\" ],  desc = \"Sort by modified time\" },\n\t{ on = [ \",\", \"M\" ], run = [ \"sort mtime --reverse=yes\", \"linemode mtime\" ], desc = \"Sort by modified time (reverse)\" },\n\t{ on = [ \",\", \"b\" ], run = [ \"sort btime --reverse=no\", \"linemode btime\" ],  desc = \"Sort by birth time\" },\n\t{ on = [ \",\", \"B\" ], run = [ \"sort btime --reverse=yes\", \"linemode btime\" ], desc = \"Sort by birth time (reverse)\" },\n\t{ on = [ \",\", \"e\" ], run = \"sort extension --reverse=no\",                    desc = \"Sort by extension\" },\n\t{ on = [ \",\", \"E\" ], run = \"sort extension --reverse=yes\",                   desc = \"Sort by extension (reverse)\" },\n\t{ on = [ \",\", \"a\" ], run = \"sort alphabetical --reverse=no\",                 desc = \"Sort alphabetically\" },\n\t{ on = [ \",\", \"A\" ], run = \"sort alphabetical --reverse=yes\",                desc = \"Sort alphabetically (reverse)\" },\n\t{ on = [ \",\", \"n\" ], run = \"sort natural --reverse=no\",                      desc = \"Sort naturally\" },\n\t{ on = [ \",\", \"N\" ], run = \"sort natural --reverse=yes\",                     desc = \"Sort naturally (reverse)\" },\n\t{ on = [ \",\", \"s\" ], run = [ \"sort size --reverse=no\", \"linemode size\" ],    desc = \"Sort by size\" },\n\t{ on = [ \",\", \"S\" ], run = [ \"sort size --reverse=yes\", \"linemode size\" ],   desc = \"Sort by size (reverse)\" },\n\t{ on = [ \",\", \"r\" ], run = \"sort random --reverse=no\",                       desc = \"Sort randomly\" },\n\n\t# Goto\n\t{ on = [ \"g\", \"h\" ],       run = \"cd ~\",             desc = \"Go home\" },\n\t{ on = [ \"g\", \"c\" ],       run = \"cd ~/.config\",     desc = \"Go ~/.config\" },\n\t{ on = [ \"g\", \"d\" ],       run = \"cd ~/Downloads\",   desc = \"Go ~/Downloads\" },\n\t{ on = [ \"g\", \"<Space>\" ], run = \"cd --interactive\", desc = \"Jump interactively\" },\n\t{ on = [ \"g\", \"f\" ],       run = \"follow\",           desc = \"Follow hovered symlink\" },\n\n\t# Tabs\n\t{ on = [ \"t\", \"t\" ], run = \"tab_create --current\",     desc = \"Create a new tab in CWD\" },\n\t{ on = [ \"t\", \"r\" ], run = \"tab_rename --interactive\", desc = \"Rename current tab\" },\n\n\t{ on = \"1\", run = \"tab_switch 0\", desc = \"Switch to first tab\" },\n\t{ on = \"2\", run = \"tab_switch 1\", desc = \"Switch to second tab\" },\n\t{ on = \"3\", run = \"tab_switch 2\", desc = \"Switch to third tab\" },\n\t{ on = \"4\", run = \"tab_switch 3\", desc = \"Switch to fourth tab\" },\n\t{ on = \"5\", run = \"tab_switch 4\", desc = \"Switch to fifth tab\" },\n\t{ on = \"6\", run = \"tab_switch 5\", desc = \"Switch to sixth tab\" },\n\t{ on = \"7\", run = \"tab_switch 6\", desc = \"Switch to seventh tab\" },\n\t{ on = \"8\", run = \"tab_switch 7\", desc = \"Switch to eighth tab\" },\n\t{ on = \"9\", run = \"tab_switch 8\", desc = \"Switch to ninth tab\" },\n\n\t{ on = \"[\", run = \"tab_switch -1 --relative\", desc = \"Switch to previous tab\" },\n\t{ on = \"]\", run = \"tab_switch 1 --relative\",  desc = \"Switch to next tab\" },\n\n\t{ on = \"{\", run = \"tab_swap -1\", desc = \"Swap current tab with previous tab\" },\n\t{ on = \"}\", run = \"tab_swap 1\",  desc = \"Swap current tab with next tab\" },\n\n\t# Tasks\n\t{ on = \"w\", run = \"tasks:show\", desc = \"Show task manager\" },\n\n\t# Help\n\t{ on = \"~\",    run = \"help\", desc = \"Open help\" },\n\t{ on = \"<F1>\", run = \"help\", desc = \"Open help\" },\n]\n\n[tasks]\n\nkeymap = [\n\t{ on = \"<Esc>\", run = \"close\", desc = \"Close task manager\" },\n\t{ on = \"<C-[>\", run = \"close\", desc = \"Close task manager\" },\n\t{ on = \"<C-c>\", run = \"close\", desc = \"Close task manager\" },\n\t{ on = \"w\",     run = \"close\", desc = \"Close task manager\" },\n\n\t{ on = \"k\", run = \"arrow prev\", desc = \"Previous task\" },\n\t{ on = \"j\", run = \"arrow next\", desc = \"Next task\" },\n\n\t{ on = \"<Up>\",   run = \"arrow prev\", desc = \"Previous task\" },\n\t{ on = \"<Down>\", run = \"arrow next\", desc = \"Next task\" },\n\n\t{ on = \"<Enter>\", run = \"inspect\", desc = \"Inspect the task\" },\n\t{ on = \"x\",       run = \"cancel\",  desc = \"Cancel the task\" },\n\n\t# Help\n\t{ on = \"~\",    run = \"help\", desc = \"Open help\" },\n\t{ on = \"<F1>\", run = \"help\", desc = \"Open help\" },\n]\n\n[spot]\n\nkeymap = [\n\t{ on = \"<Esc>\", run = \"close\", desc = \"Close the spot\" },\n\t{ on = \"<C-[>\", run = \"close\", desc = \"Close the spot\" },\n\t{ on = \"<C-c>\", run = \"close\", desc = \"Close the spot\" },\n\t{ on = \"<Tab>\", run = \"close\", desc = \"Close the spot\" },\n\n\t{ on = \"k\", run = \"arrow prev\", desc = \"Previous line\" },\n\t{ on = \"j\", run = \"arrow next\", desc = \"Next line\" },\n\t{ on = \"h\", run = \"swipe prev\", desc = \"Swipe to previous file\" },\n\t{ on = \"l\", run = \"swipe next\", desc = \"Swipe to next file\" },\n\n\t{ on = \"<Up>\",    run = \"arrow prev\", desc = \"Previous line\" },\n\t{ on = \"<Down>\",  run = \"arrow next\", desc = \"Next line\" },\n\t{ on = \"<Left>\",  run = \"swipe prev\", desc = \"Swipe to previous file\" },\n\t{ on = \"<Right>\", run = \"swipe next\", desc = \"Swipe to next file\" },\n\n\t# Copy\n\t{ on = [ \"c\", \"c\" ], run = \"copy cell\", desc = \"Copy selected cell\" },\n\n\t# Help\n\t{ on = \"~\",    run = \"help\", desc = \"Open help\" },\n\t{ on = \"<F1>\", run = \"help\", desc = \"Open help\" },\n]\n\n[pick]\n\nkeymap = [\n\t{ on = \"<Esc>\",   run = \"close\",          desc = \"Cancel pick\" },\n\t{ on = \"<C-[>\",   run = \"close\",          desc = \"Cancel pick\" },\n\t{ on = \"<C-c>\",   run = \"close\",          desc = \"Cancel pick\" },\n\t{ on = \"<Enter>\", run = \"close --submit\", desc = \"Submit the pick\" },\n\n\t{ on = \"k\", run = \"arrow prev\", desc = \"Previous option\" },\n\t{ on = \"j\", run = \"arrow next\", desc = \"Next option\" },\n\n\t{ on = \"<Up>\",   run = \"arrow prev\", desc = \"Previous option\" },\n\t{ on = \"<Down>\", run = \"arrow next\", desc = \"Next option\" },\n\n\t# Help\n\t{ on = \"~\",    run = \"help\", desc = \"Open help\" },\n\t{ on = \"<F1>\", run = \"help\", desc = \"Open help\" },\n]\n\n[input]\n\nkeymap = [\n\t{ on = \"<C-c>\",   run = \"close\",          desc = \"Cancel input\" },\n\t{ on = \"<Enter>\", run = \"close --submit\", desc = \"Submit input\" },\n\t{ on = \"<Esc>\",   run = \"escape\",         desc = \"Back to normal mode, or cancel input\" },\n\t{ on = \"<C-[>\",   run = \"escape\",         desc = \"Back to normal mode, or cancel input\" },\n\n\t# Mode\n\t{ on = \"i\", run = \"insert\",                          desc = \"Enter insert mode\" },\n\t{ on = \"I\", run = [ \"move first-char\", \"insert\" ],   desc = \"Move to the BOL, and enter insert mode\" },\n\t{ on = \"a\", run = \"insert --append\",                 desc = \"Enter append mode\" },\n\t{ on = \"A\", run = [ \"move eol\", \"insert --append\" ], desc = \"Move to the EOL, and enter append mode\" },\n\t{ on = \"v\", run = \"visual\",                          desc = \"Enter visual mode\" },\n\t{ on = \"r\", run = \"replace\",                         desc = \"Replace a single character\" },\n\n\t# Selection\n\t{ on = \"V\",     run = [ \"move bol\", \"visual\", \"move eol\" ], desc = \"Select from BOL to EOL\" },\n\t{ on = \"<C-A>\", run = [ \"move eol\", \"visual\", \"move bol\" ], desc = \"Select from EOL to BOL\" },\n\t{ on = \"<C-E>\", run = [ \"move bol\", \"visual\", \"move eol\" ], desc = \"Select from BOL to EOL\" },\n\n\t# Character-wise movement\n\t{ on = \"h\",       run = \"move -1\", desc = \"Move back a character\" },\n\t{ on = \"l\",       run = \"move 1\",  desc = \"Move forward a character\" },\n\t{ on = \"<Left>\",  run = \"move -1\", desc = \"Move back a character\" },\n\t{ on = \"<Right>\", run = \"move 1\",  desc = \"Move forward a character\" },\n\t{ on = \"<C-b>\",   run = \"move -1\", desc = \"Move back a character\" },\n\t{ on = \"<C-f>\",   run = \"move 1\",  desc = \"Move forward a character\" },\n\n\t# Word-wise movement\n\t{ on = \"b\",         run = \"backward\",                    desc = \"Move back to the start of the current or previous word\" },\n\t{ on = \"B\",         run = \"backward --far\",              desc = \"Move back to the start of the current or previous WORD\" },\n\t{ on = \"w\",         run = \"forward\",                     desc = \"Move forward to the start of the next word\" },\n\t{ on = \"W\",         run = \"forward --far\",               desc = \"Move forward to the start of the next WORD\" },\n\t{ on = \"e\",         run = \"forward --end-of-word\",       desc = \"Move forward to the end of the current or next word\" },\n\t{ on = \"E\",         run = \"forward --far --end-of-word\", desc = \"Move forward to the end of the current or next WORD\" },\n\t{ on = \"<A-b>\",     run = \"backward\",                    desc = \"Move back to the start of the current or previous word\" },\n\t{ on = \"<A-f>\",     run = \"forward --end-of-word\",       desc = \"Move forward to the end of the current or next word\" },\n\t{ on = \"<C-Left>\",  run = \"backward\",                    desc = \"Move back to the start of the current or previous word\" },\n\t{ on = \"<C-Right>\", run = \"forward --end-of-word\",       desc = \"Move forward to the end of the current or next word\" },\n\n\t# Line-wise movement\n\t{ on = \"0\",      run = \"move bol\",        desc = \"Move to the BOL\" },\n\t{ on = \"$\",      run = \"move eol\",        desc = \"Move to the EOL\" },\n\t{ on = \"_\",      run = \"move first-char\", desc = \"Move to the first non-whitespace character\" },\n\t{ on = \"^\",      run = \"move first-char\", desc = \"Move to the first non-whitespace character\" },\n\t{ on = \"<C-a>\",  run = \"move bol\",        desc = \"Move to the BOL\" },\n\t{ on = \"<C-e>\",  run = \"move eol\",        desc = \"Move to the EOL\" },\n\t{ on = \"<Home>\", run = \"move bol\",        desc = \"Move to the BOL\" },\n\t{ on = \"<End>\",  run = \"move eol\",        desc = \"Move to the EOL\" },\n\n\t# Delete\n\t{ on = \"<Backspace>\", run = \"backspace\",         desc = \"Delete the character before the cursor\" },\n\t{ on = \"<Delete>\",    run = \"backspace --under\", desc = \"Delete the character under the cursor\" },\n\t{ on = \"<C-h>\",       run = \"backspace\",         desc = \"Delete the character before the cursor\" },\n\t{ on = \"<C-d>\",       run = \"backspace --under\", desc = \"Delete the character under the cursor\" },\n\n\t# Kill\n\t{ on = \"<C-u>\",         run = \"kill bol\",      desc = \"Kill backwards to the BOL\" },\n\t{ on = \"<C-k>\",         run = \"kill eol\",      desc = \"Kill forwards to the EOL\" },\n\t{ on = \"<C-w>\",         run = \"kill backward\", desc = \"Kill backwards to the start of the current word\" },\n\t{ on = \"<A-d>\",         run = \"kill forward\",  desc = \"Kill forwards to the end of the current word\" },\n\t{ on = \"<C-Backspace>\", run = \"kill backward\", desc = \"Kill backwards to the start of the current word\" },\n\t{ on = \"<C-Delete>\",    run = \"kill forward\",  desc = \"Kill forwards to the end of the current word\" },\n\n\t# Cut/Yank/Paste\n\t{ on = \"d\", run = \"delete --cut\",                                      desc = \"Cut selected characters\" },\n\t{ on = \"D\", run = [ \"delete --cut\", \"move eol\" ],                      desc = \"Cut until EOL\" },\n\t{ on = \"c\", run = \"delete --cut --insert\",                             desc = \"Cut selected characters, and enter insert mode\" },\n\t{ on = \"C\", run = [ \"delete --cut --insert\", \"move eol\" ],             desc = \"Cut until EOL, and enter insert mode\" },\n\t{ on = \"s\", run = [ \"delete --cut --insert\", \"move 1\" ],               desc = \"Cut current character, and enter insert mode\" },\n\t{ on = \"S\", run = [ \"move bol\", \"delete --cut --insert\", \"move eol\" ], desc = \"Cut from BOL until EOL, and enter insert mode\" },\n\t{ on = \"x\", run = [ \"delete --cut\", \"move 1 --in-operating\" ],         desc = \"Cut current character\" },\n\t{ on = \"y\", run = \"yank\",                                              desc = \"Copy selected characters\" },\n\t{ on = \"p\", run = \"paste\",                                             desc = \"Paste copied characters after the cursor\" },\n\t{ on = \"P\", run = \"paste --before\",                                    desc = \"Paste copied characters before the cursor\" },\n\n\t# Undo/Redo/Casefy\n\t{ on = \"u\",     run = [ \"undo\", \"casefy lower\" ], desc = \"Undo, or lowercase if in visual mode\" },\n\t{ on = \"U\",     run = \"casefy upper\",             desc = \"Uppercase\" },\n\t{ on = \"<C-r>\", run = \"redo\",                     desc = \"Redo the last operation\" },\n\n\t# Help\n\t{ on = \"~\",    run = \"help\", desc = \"Open help\" },\n\t{ on = \"<F1>\", run = \"help\", desc = \"Open help\" },\n]\n\n[confirm]\n\nkeymap = [\n\t{ on = \"<Esc>\",   run = \"close\",          desc = \"Cancel the confirm\" },\n\t{ on = \"<C-[>\",   run = \"close\",          desc = \"Cancel the confirm\" },\n\t{ on = \"<C-c>\",   run = \"close\",          desc = \"Cancel the confirm\" },\n\t{ on = \"<Enter>\", run = \"close --submit\", desc = \"Submit the confirm\" },\n\n\t{ on = \"n\", run = \"close\",          desc = \"Cancel the confirm\" },\n\t{ on = \"y\", run = \"close --submit\", desc = \"Submit the confirm\" },\n\n\t{ on = \"k\", run = \"arrow prev\", desc = \"Previous line\" },\n\t{ on = \"j\", run = \"arrow next\", desc = \"Next line\" },\n\n\t{ on = \"<Up>\",   run = \"arrow prev\", desc = \"Previous line\" },\n\t{ on = \"<Down>\", run = \"arrow next\", desc = \"Next line\" },\n\n\t# Help\n\t{ on = \"~\",    run = \"help\", desc = \"Open help\" },\n\t{ on = \"<F1>\", run = \"help\", desc = \"Open help\" },\n]\n\n[cmp]\n\nkeymap = [\n\t{ on = \"<C-c>\",   run = \"close\",                                      desc = \"Cancel completion\" },\n\t{ on = \"<Tab>\",   run = \"close --submit\",                             desc = \"Submit the completion\" },\n\t{ on = \"<Enter>\", run = [ \"close --submit\", \"input:close --submit\" ], desc = \"Complete and submit the input\" },\n\n\t{ on = \"<A-k>\", run = \"arrow prev\", desc = \"Previous item\" },\n\t{ on = \"<A-j>\", run = \"arrow next\", desc = \"Next item\" },\n\n\t{ on = \"<Up>\",   run = \"arrow prev\", desc = \"Previous item\" },\n\t{ on = \"<Down>\", run = \"arrow next\", desc = \"Next item\" },\n\n\t{ on = \"<C-p>\", run = \"arrow prev\", desc = \"Previous item\" },\n\t{ on = \"<C-n>\", run = \"arrow next\", desc = \"Next item\" },\n\n\t# Help\n\t{ on = \"~\",    run = \"help\", desc = \"Open help\" },\n\t{ on = \"<F1>\", run = \"help\", desc = \"Open help\" },\n]\n\n[help]\n\nkeymap = [\n\t{ on = \"<Esc>\", run = \"escape\", desc = \"Clear the filter, or hide the help\" },\n\t{ on = \"<C-[>\", run = \"escape\", desc = \"Clear the filter, or hide the help\" },\n\t{ on = \"<C-c>\", run = \"close\",  desc = \"Hide the help\" },\n\n\t# Navigation\n\t{ on = \"k\", run = \"arrow prev\", desc = \"Previous line\" },\n\t{ on = \"j\", run = \"arrow next\", desc = \"Next line\" },\n\n\t{ on = \"<Up>\",   run = \"arrow prev\", desc = \"Previous line\" },\n\t{ on = \"<Down>\", run = \"arrow next\", desc = \"Next line\" },\n\n\t# Filtering\n\t{ on = \"f\", run = \"filter\", desc = \"Filter help items\" },\n]\n"
  },
  {
    "path": "yazi-config/preset/theme-dark.toml",
    "content": "# If the user's terminal is in dark mode, Yazi will load `theme-dark.toml` on startup; otherwise, `theme-light.toml`.\n# You can override any parts of them that are not related to the dark/light mode in your own `theme.toml`.\n\n# If you want to dynamically override their content based on dark/light mode, you can specify two different flavors\n# for dark and light modes under `[flavor]`, and do so in those flavors instead.\n\"$schema\" = \"https://yazi-rs.github.io/schemas/theme.json\"\n\n# vim:fileencoding=utf-8:foldmethod=marker\n\n# : Flavor {{{\n\n[flavor]\ndark  = \"\"\nlight = \"\"\n\n# : }}}\n\n\n# : App {{{\n\n[app]\noverall = {}\n\n# : }}}\n\n\n# : Manager {{{\n\n[mgr]\ncwd = { fg = \"cyan\" }\n\n# Find\nfind_keyword  = { fg = \"yellow\", bold = true, italic = true, underline = true }\nfind_position = { fg = \"magenta\", bg = \"reset\", bold = true, italic = true }\n\n# Symlink\nsymlink_target = { italic = true }\n\n# Marker\nmarker_copied   = { fg = \"lightgreen\",  bg = \"lightgreen\" }\nmarker_cut      = { fg = \"lightred\",    bg = \"lightred\" }\nmarker_marked   = { fg = \"lightcyan\",   bg = \"lightcyan\" }\nmarker_selected = { fg = \"lightyellow\", bg = \"lightyellow\" }\nmarker_symbol   = \"│\"\n\n# Count\ncount_copied   = { fg = \"white\", bg = \"green\" }\ncount_cut      = { fg = \"white\", bg = \"red\" }\ncount_selected = { fg = \"black\", bg = \"yellow\" }\n\n# Border\nborder_symbol = \"│\"\nborder_style  = { fg = \"gray\" }\n\n# Highlighting\nsyntect_theme = \"\"\n\n# : }}}\n\n\n# : Tabs {{{\n\n[tabs]\nactive   = { bg = \"blue\", bold = true }\ninactive = { fg = \"blue\", bg = \"gray\" }\n\n# Separator\nsep_inner = { open = \"\", close = \"\" }\nsep_outer = { open = \"\", close = \"\" }\n\n# : }}}\n\n\n# : Mode {{{\n\n[mode]\nnormal_main = { bg = \"blue\", bold = true }\nnormal_alt  = { fg = \"blue\", bg = \"gray\" }\n\n# Select mode\nselect_main = { bg = \"red\", bold = true }\nselect_alt  = { fg = \"red\", bg = \"gray\" }\n\n# Unset mode\nunset_main = { bg = \"red\", bold = true }\nunset_alt  = { fg = \"red\", bg = \"gray\" }\n\n# : }}}\n\n\n# : Indicator of hovered file {{{\n\n[indicator]\nparent  = { reversed = true }\ncurrent = { reversed = true }\npreview = { underline = true }\npadding = { open = \"\", close = \"\" }\n\n# : }}}\n\n\n# : Status bar {{{\n\n[status]\noverall   = {}\nsep_left  = { open = \"\", close = \"\" }\nsep_right = { open = \"\", close = \"\" }\n\n# Permissions\nperm_sep   = { fg = \"darkgray\" }\nperm_type  = { fg = \"green\" }\nperm_read  = { fg = \"yellow\" }\nperm_write = { fg = \"red\" }\nperm_exec  = { fg = \"cyan\" }\n\n# Progress\nprogress_label  = { bold = true }\nprogress_normal = { fg = \"green\", bg = \"black\" }\nprogress_error  = { fg = \"yellow\", bg = \"red\" }\n\n# : }}}\n\n\n# : Which {{{\n\n[which]\ncols            = 3\nmask            = { bg = \"black\" }\ncand            = { fg = \"lightcyan\" }\nrest            = { fg = \"darkgray\" }\ndesc            = { fg = \"lightmagenta\" }\nseparator       = \"  \"\nseparator_style = { fg = \"darkgray\" }\n\n# : }}}\n\n\n# : Confirmation {{{\n\n[confirm]\nborder     = { fg = \"blue\" }\ntitle      = { fg = \"blue\" }\nbody       = {}\nlist       = {}\nbtn_yes    = { reversed = true }\nbtn_no     = {}\nbtn_labels = [ \"  [Y]es  \", \"  (N)o  \" ]\n\n# : }}}\n\n\n# : Spotter {{{\n\n[spot]\nborder = { fg = \"blue\" }\ntitle  = { fg = \"blue\" }\n\n# Table\ntbl_col  = { fg = \"blue\" }\ntbl_cell = { fg = \"yellow\", reversed = true }\n\n# : }}}\n\n\n# : Notification {{{\n\n[notify]\ntitle_info  = { fg = \"green\" }\ntitle_warn  = { fg = \"yellow\" }\ntitle_error = { fg = \"red\" }\n\n# Icons\nicon_info  = \"\"\nicon_warn  = \"\"\nicon_error = \"\"\n\n# : }}}\n\n\n# : Picker {{{\n\n[pick]\nborder   = { fg = \"blue\" }\nactive   = { fg = \"magenta\", bold = true }\ninactive = {}\n\n# : }}}\n\n\n# : Input {{{\n\n[input]\nborder   = { fg = \"blue\" }\ntitle    = {}\nvalue    = {}\nselected = { reversed = true }\n\n# : }}}\n\n\n# : Completion {{{\n\n[cmp]\nborder   = { fg = \"blue\" }\nactive   = { reversed = true }\ninactive = {}\n\n# Icons\nicon_file    = \"\"\nicon_folder  = \"\"\nicon_command = \"\"\n\n# : }}}\n\n\n# : Task manager {{{\n\n[tasks]\nborder  = { fg = \"blue\" }\ntitle   = {}\nhovered = { fg = \"magenta\", bold = true }\n\n# : }}}\n\n\n# : Help menu {{{\n\n[help]\non      = { fg = \"cyan\" }\nrun     = { fg = \"magenta\" }\ndesc    = {}\nhovered = { reversed = true, bold = true }\nfooter  = { fg = \"black\", bg = \"white\" }\n\n# : }}}\n\n\n# : File-specific styles {{{\n\n[filetype]\nrules = [\n\t# Image\n\t{ mime = \"image/*\", fg = \"yellow\" },\n\t# Media\n\t{ mime = \"{audio,video}/*\", fg = \"magenta\" },\n\t# Archive\n\t{ mime = \"application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}\", fg = \"red\" },\n\t# Document\n\t{ mime = \"application/{pdf,doc,rtf}\", fg = \"cyan\" },\n\t# Virtual file system\n\t{ mime = \"vfs/{absent,stale}\", fg = \"gray\" },\n\t# Special file\n\t{ url = \"*\", is = \"orphan\", bg = \"red\" },\n\t{ url = \"*\", is = \"exec\"  , fg = \"green\" },\n\t# Dummy file\n\t{ url = \"*\", is = \"dummy\", bg = \"red\" },\n\t{ url = \"*/\", is = \"dummy\", bg = \"red\" },\n\t# Fallback\n\t{ url = \"*/\", fg = \"blue\" }\n]\n\n# : }}}\n\n\n# : Icons {{{\n\n[icon]\nglobs = []\ndirs  = [\n\t{ name = \".config\", text = \"\", fg = \"#ff9800\" },\n\t{ name = \".git\", text = \"\", fg = \"#00bcd4\" },\n\t{ name = \".github\", text = \"\", fg = \"#03a9f4\" },\n\t{ name = \".npm\", text = \"\", fg = \"#03a9f4\" },\n\t{ name = \"Desktop\", text = \"\", fg = \"#00bcd4\" },\n\t{ name = \"Development\", text = \"\", fg = \"#00bcd4\" },\n\t{ name = \"Documents\", text = \"\", fg = \"#00bcd4\" },\n\t{ name = \"Downloads\", text = \"\", fg = \"#00bcd4\" },\n\t{ name = \"Library\", text = \"\", fg = \"#00bcd4\" },\n\t{ name = \"Movies\", text = \"\", fg = \"#00bcd4\" },\n\t{ name = \"Music\", text = \"\", fg = \"#00bcd4\" },\n\t{ name = \"Pictures\", text = \"\", fg = \"#00bcd4\" },\n\t{ name = \"Public\", text = \"\", fg = \"#00bcd4\" },\n\t{ name = \"Videos\", text = \"\", fg = \"#00bcd4\" },\n]\nfiles = [\n\t{ name = \".babelrc\", text = \"\", fg = \"#cbcb41\" },\n\t{ name = \".bash_profile\", text = \"\", fg = \"#89e051\" },\n\t{ name = \".bashrc\", text = \"\", fg = \"#89e051\" },\n\t{ name = \".clang-format\", text = \"\", fg = \"#6d8086\" },\n\t{ name = \".clang-tidy\", text = \"\", fg = \"#6d8086\" },\n\t{ name = \".codespellrc\", text = \"󰓆\", fg = \"#35da60\" },\n\t{ name = \".condarc\", text = \"\", fg = \"#43b02a\" },\n\t{ name = \".dockerignore\", text = \"󰡨\", fg = \"#458ee6\" },\n\t{ name = \".ds_store\", text = \"\", fg = \"#41535b\" },\n\t{ name = \".editorconfig\", text = \"\", fg = \"#fff2f2\" },\n\t{ name = \".env\", text = \"\", fg = \"#faf743\" },\n\t{ name = \".eslintignore\", text = \"\", fg = \"#4b32c3\" },\n\t{ name = \".eslintrc\", text = \"\", fg = \"#4b32c3\" },\n\t{ name = \".git-blame-ignore-revs\", text = \"\", fg = \"#f54d27\" },\n\t{ name = \".gitattributes\", text = \"\", fg = \"#f54d27\" },\n\t{ name = \".gitconfig\", text = \"\", fg = \"#f54d27\" },\n\t{ name = \".gitignore\", text = \"\", fg = \"#f54d27\" },\n\t{ name = \".gitlab-ci.yml\", text = \"\", fg = \"#e24329\" },\n\t{ name = \".gitmodules\", text = \"\", fg = \"#f54d27\" },\n\t{ name = \".gtkrc-2.0\", text = \"\", fg = \"#ffffff\" },\n\t{ name = \".gvimrc\", text = \"\", fg = \"#019833\" },\n\t{ name = \".justfile\", text = \"\", fg = \"#6d8086\" },\n\t{ name = \".luacheckrc\", text = \"\", fg = \"#00a2ff\" },\n\t{ name = \".luaurc\", text = \"\", fg = \"#00a2ff\" },\n\t{ name = \".mailmap\", text = \"󰊢\", fg = \"#f54d27\" },\n\t{ name = \".nanorc\", text = \"\", fg = \"#440077\" },\n\t{ name = \".npmignore\", text = \"\", fg = \"#e8274b\" },\n\t{ name = \".npmrc\", text = \"\", fg = \"#e8274b\" },\n\t{ name = \".nuxtrc\", text = \"󱄆\", fg = \"#00c58e\" },\n\t{ name = \".nvmrc\", text = \"\", fg = \"#5fa04e\" },\n\t{ name = \".pnpmfile.cjs\", text = \"\", fg = \"#f9ad02\" },\n\t{ name = \".pre-commit-config.yaml\", text = \"󰛢\", fg = \"#f8b424\" },\n\t{ name = \".prettierignore\", text = \"\", fg = \"#4285f4\" },\n\t{ name = \".prettierrc\", text = \"\", fg = \"#4285f4\" },\n\t{ name = \".prettierrc.cjs\", text = \"\", fg = \"#4285f4\" },\n\t{ name = \".prettierrc.js\", text = \"\", fg = \"#4285f4\" },\n\t{ name = \".prettierrc.json\", text = \"\", fg = \"#4285f4\" },\n\t{ name = \".prettierrc.json5\", text = \"\", fg = \"#4285f4\" },\n\t{ name = \".prettierrc.mjs\", text = \"\", fg = \"#4285f4\" },\n\t{ name = \".prettierrc.toml\", text = \"\", fg = \"#4285f4\" },\n\t{ name = \".prettierrc.yaml\", text = \"\", fg = \"#4285f4\" },\n\t{ name = \".prettierrc.yml\", text = \"\", fg = \"#4285f4\" },\n\t{ name = \".pylintrc\", text = \"\", fg = \"#6d8086\" },\n\t{ name = \".settings.json\", text = \"\", fg = \"#854cc7\" },\n\t{ name = \".SRCINFO\", text = \"󰣇\", fg = \"#0f94d2\" },\n\t{ name = \".vimrc\", text = \"\", fg = \"#019833\" },\n\t{ name = \".Xauthority\", text = \"\", fg = \"#e54d18\" },\n\t{ name = \".xinitrc\", text = \"\", fg = \"#e54d18\" },\n\t{ name = \".Xresources\", text = \"\", fg = \"#e54d18\" },\n\t{ name = \".xsession\", text = \"\", fg = \"#e54d18\" },\n\t{ name = \".zprofile\", text = \"\", fg = \"#89e051\" },\n\t{ name = \".zshenv\", text = \"\", fg = \"#89e051\" },\n\t{ name = \".zshrc\", text = \"\", fg = \"#89e051\" },\n\t{ name = \"_gvimrc\", text = \"\", fg = \"#019833\" },\n\t{ name = \"_vimrc\", text = \"\", fg = \"#019833\" },\n\t{ name = \"AUTHORS\", text = \"\", fg = \"#a172ff\" },\n\t{ name = \"AUTHORS.txt\", text = \"\", fg = \"#a172ff\" },\n\t{ name = \"brewfile\", text = \"\", fg = \"#701516\" },\n\t{ name = \"bspwmrc\", text = \"\", fg = \"#2f2f2f\" },\n\t{ name = \"build\", text = \"\", fg = \"#89e051\" },\n\t{ name = \"build.gradle\", text = \"\", fg = \"#005f87\" },\n\t{ name = \"build.zig.zon\", text = \"\", fg = \"#f69a1b\" },\n\t{ name = \"bun.lock\", text = \"\", fg = \"#eadcd1\" },\n\t{ name = \"bun.lockb\", text = \"\", fg = \"#eadcd1\" },\n\t{ name = \"cantorrc\", text = \"\", fg = \"#1c99f3\" },\n\t{ name = \"checkhealth\", text = \"󰓙\", fg = \"#75b4fb\" },\n\t{ name = \"cmakelists.txt\", text = \"\", fg = \"#dce3eb\" },\n\t{ name = \"code_of_conduct\", text = \"\", fg = \"#e41662\" },\n\t{ name = \"code_of_conduct.md\", text = \"\", fg = \"#e41662\" },\n\t{ name = \"commit_editmsg\", text = \"\", fg = \"#f54d27\" },\n\t{ name = \"commitlint.config.js\", text = \"󰜘\", fg = \"#2b9689\" },\n\t{ name = \"commitlint.config.ts\", text = \"󰜘\", fg = \"#2b9689\" },\n\t{ name = \"compose.yaml\", text = \"󰡨\", fg = \"#458ee6\" },\n\t{ name = \"compose.yml\", text = \"󰡨\", fg = \"#458ee6\" },\n\t{ name = \"config\", text = \"\", fg = \"#6d8086\" },\n\t{ name = \"containerfile\", text = \"󰡨\", fg = \"#458ee6\" },\n\t{ name = \"copying\", text = \"\", fg = \"#cbcb41\" },\n\t{ name = \"copying.lesser\", text = \"\", fg = \"#cbcb41\" },\n\t{ name = \"Directory.Build.props\", text = \"\", fg = \"#00a2ff\" },\n\t{ name = \"Directory.Build.targets\", text = \"\", fg = \"#00a2ff\" },\n\t{ name = \"Directory.Packages.props\", text = \"\", fg = \"#00a2ff\" },\n\t{ name = \"docker-compose.yaml\", text = \"󰡨\", fg = \"#458ee6\" },\n\t{ name = \"docker-compose.yml\", text = \"󰡨\", fg = \"#458ee6\" },\n\t{ name = \"dockerfile\", text = \"󰡨\", fg = \"#458ee6\" },\n\t{ name = \"eslint.config.cjs\", text = \"\", fg = \"#4b32c3\" },\n\t{ name = \"eslint.config.js\", text = \"\", fg = \"#4b32c3\" },\n\t{ name = \"eslint.config.mjs\", text = \"\", fg = \"#4b32c3\" },\n\t{ name = \"eslint.config.ts\", text = \"\", fg = \"#4b32c3\" },\n\t{ name = \"ext_typoscript_setup.txt\", text = \"\", fg = \"#ff8700\" },\n\t{ name = \"favicon.ico\", text = \"\", fg = \"#cbcb41\" },\n\t{ name = \"fp-info-cache\", text = \"\", fg = \"#ffffff\" },\n\t{ name = \"fp-lib-table\", text = \"\", fg = \"#ffffff\" },\n\t{ name = \"FreeCAD.conf\", text = \"\", fg = \"#cb333b\" },\n\t{ name = \"Gemfile\", text = \"\", fg = \"#701516\" },\n\t{ name = \"gnumakefile\", text = \"\", fg = \"#6d8086\" },\n\t{ name = \"go.mod\", text = \"\", fg = \"#00add8\" },\n\t{ name = \"go.sum\", text = \"\", fg = \"#00add8\" },\n\t{ name = \"go.work\", text = \"\", fg = \"#00add8\" },\n\t{ name = \"gradle-wrapper.properties\", text = \"\", fg = \"#005f87\" },\n\t{ name = \"gradle.properties\", text = \"\", fg = \"#005f87\" },\n\t{ name = \"gradlew\", text = \"\", fg = \"#005f87\" },\n\t{ name = \"groovy\", text = \"\", fg = \"#4a687c\" },\n\t{ name = \"gruntfile.babel.js\", text = \"\", fg = \"#e37933\" },\n\t{ name = \"gruntfile.coffee\", text = \"\", fg = \"#e37933\" },\n\t{ name = \"gruntfile.js\", text = \"\", fg = \"#e37933\" },\n\t{ name = \"gruntfile.ts\", text = \"\", fg = \"#e37933\" },\n\t{ name = \"gtkrc\", text = \"\", fg = \"#ffffff\" },\n\t{ name = \"gulpfile.babel.js\", text = \"\", fg = \"#cc3e44\" },\n\t{ name = \"gulpfile.coffee\", text = \"\", fg = \"#cc3e44\" },\n\t{ name = \"gulpfile.js\", text = \"\", fg = \"#cc3e44\" },\n\t{ name = \"gulpfile.ts\", text = \"\", fg = \"#cc3e44\" },\n\t{ name = \"hypridle.conf\", text = \"\", fg = \"#00aaae\" },\n\t{ name = \"hyprland.conf\", text = \"\", fg = \"#00aaae\" },\n\t{ name = \"hyprlandd.conf\", text = \"\", fg = \"#00aaae\" },\n\t{ name = \"hyprlock.conf\", text = \"\", fg = \"#00aaae\" },\n\t{ name = \"hyprpaper.conf\", text = \"\", fg = \"#00aaae\" },\n\t{ name = \"hyprsunset.conf\", text = \"\", fg = \"#00aaae\" },\n\t{ name = \"i18n.config.js\", text = \"󰗊\", fg = \"#7986cb\" },\n\t{ name = \"i18n.config.ts\", text = \"󰗊\", fg = \"#7986cb\" },\n\t{ name = \"i3blocks.conf\", text = \"\", fg = \"#e8ebee\" },\n\t{ name = \"i3status.conf\", text = \"\", fg = \"#e8ebee\" },\n\t{ name = \"index.theme\", text = \"\", fg = \"#2db96f\" },\n\t{ name = \"ionic.config.json\", text = \"\", fg = \"#4f8ff7\" },\n\t{ name = \"Jenkinsfile\", text = \"\", fg = \"#d33833\" },\n\t{ name = \"justfile\", text = \"\", fg = \"#6d8086\" },\n\t{ name = \"kalgebrarc\", text = \"\", fg = \"#1c99f3\" },\n\t{ name = \"kdeglobals\", text = \"\", fg = \"#1c99f3\" },\n\t{ name = \"kdenlive-layoutsrc\", text = \"\", fg = \"#83b8f2\" },\n\t{ name = \"kdenliverc\", text = \"\", fg = \"#83b8f2\" },\n\t{ name = \"kritadisplayrc\", text = \"\", fg = \"#f245fb\" },\n\t{ name = \"kritarc\", text = \"\", fg = \"#f245fb\" },\n\t{ name = \"license\", text = \"\", fg = \"#d0bf41\" },\n\t{ name = \"license.md\", text = \"\", fg = \"#d0bf41\" },\n\t{ name = \"lxde-rc.xml\", text = \"\", fg = \"#909090\" },\n\t{ name = \"lxqt.conf\", text = \"\", fg = \"#0192d3\" },\n\t{ name = \"makefile\", text = \"\", fg = \"#6d8086\" },\n\t{ name = \"mix.lock\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"mpv.conf\", text = \"\", fg = \"#3b1342\" },\n\t{ name = \"next.config.cjs\", text = \"\", fg = \"#ffffff\" },\n\t{ name = \"next.config.js\", text = \"\", fg = \"#ffffff\" },\n\t{ name = \"next.config.ts\", text = \"\", fg = \"#ffffff\" },\n\t{ name = \"node_modules\", text = \"\", fg = \"#e8274b\" },\n\t{ name = \"nuxt.config.cjs\", text = \"󱄆\", fg = \"#00c58e\" },\n\t{ name = \"nuxt.config.js\", text = \"󱄆\", fg = \"#00c58e\" },\n\t{ name = \"nuxt.config.mjs\", text = \"󱄆\", fg = \"#00c58e\" },\n\t{ name = \"nuxt.config.ts\", text = \"󱄆\", fg = \"#00c58e\" },\n\t{ name = \"package-lock.json\", text = \"\", fg = \"#7a0d21\" },\n\t{ name = \"package.json\", text = \"\", fg = \"#e8274b\" },\n\t{ name = \"PKGBUILD\", text = \"\", fg = \"#0f94d2\" },\n\t{ name = \"platformio.ini\", text = \"\", fg = \"#f6822b\" },\n\t{ name = \"playwright.config.cjs\", text = \"\", fg = \"#2fad33\" },\n\t{ name = \"playwright.config.cts\", text = \"\", fg = \"#2fad33\" },\n\t{ name = \"playwright.config.js\", text = \"\", fg = \"#2fad33\" },\n\t{ name = \"playwright.config.mjs\", text = \"\", fg = \"#2fad33\" },\n\t{ name = \"playwright.config.mts\", text = \"\", fg = \"#2fad33\" },\n\t{ name = \"playwright.config.ts\", text = \"\", fg = \"#2fad33\" },\n\t{ name = \"pnpm-lock.yaml\", text = \"\", fg = \"#f9ad02\" },\n\t{ name = \"pnpm-workspace.yaml\", text = \"\", fg = \"#f9ad02\" },\n\t{ name = \"pom.xml\", text = \"\", fg = \"#7a0d21\" },\n\t{ name = \"prettier.config.cjs\", text = \"\", fg = \"#4285f4\" },\n\t{ name = \"prettier.config.js\", text = \"\", fg = \"#4285f4\" },\n\t{ name = \"prettier.config.mjs\", text = \"\", fg = \"#4285f4\" },\n\t{ name = \"prettier.config.ts\", text = \"\", fg = \"#4285f4\" },\n\t{ name = \"prisma.config.mts\", text = \"\", fg = \"#5a67d8\" },\n\t{ name = \"prisma.config.ts\", text = \"\", fg = \"#5a67d8\" },\n\t{ name = \"procfile\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"PrusaSlicer.ini\", text = \"\", fg = \"#ec6b23\" },\n\t{ name = \"PrusaSlicerGcodeViewer.ini\", text = \"\", fg = \"#ec6b23\" },\n\t{ name = \"py.typed\", text = \"\", fg = \"#ffbc03\" },\n\t{ name = \"QtProject.conf\", text = \"\", fg = \"#40cd52\" },\n\t{ name = \"rakefile\", text = \"\", fg = \"#701516\" },\n\t{ name = \"readme\", text = \"󰂺\", fg = \"#ededed\" },\n\t{ name = \"readme.md\", text = \"󰂺\", fg = \"#ededed\" },\n\t{ name = \"rmd\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"robots.txt\", text = \"󰚩\", fg = \"#5d7096\" },\n\t{ name = \"security\", text = \"󰒃\", fg = \"#bec4c9\" },\n\t{ name = \"security.md\", text = \"󰒃\", fg = \"#bec4c9\" },\n\t{ name = \"settings.gradle\", text = \"\", fg = \"#005f87\" },\n\t{ name = \"svelte.config.js\", text = \"\", fg = \"#ff3e00\" },\n\t{ name = \"sxhkdrc\", text = \"\", fg = \"#2f2f2f\" },\n\t{ name = \"sym-lib-table\", text = \"\", fg = \"#ffffff\" },\n\t{ name = \"tailwind.config.js\", text = \"󱏿\", fg = \"#20c2e3\" },\n\t{ name = \"tailwind.config.mjs\", text = \"󱏿\", fg = \"#20c2e3\" },\n\t{ name = \"tailwind.config.ts\", text = \"󱏿\", fg = \"#20c2e3\" },\n\t{ name = \"tmux.conf\", text = \"\", fg = \"#14ba19\" },\n\t{ name = \"tmux.conf.local\", text = \"\", fg = \"#14ba19\" },\n\t{ name = \"tsconfig.json\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"unlicense\", text = \"\", fg = \"#d0bf41\" },\n\t{ name = \"vagrantfile\", text = \"\", fg = \"#1563ff\" },\n\t{ name = \"vercel.json\", text = \"\", fg = \"#ffffff\" },\n\t{ name = \"vite.config.cjs\", text = \"\", fg = \"#ffa800\" },\n\t{ name = \"vite.config.cts\", text = \"\", fg = \"#ffa800\" },\n\t{ name = \"vite.config.js\", text = \"\", fg = \"#ffa800\" },\n\t{ name = \"vite.config.mjs\", text = \"\", fg = \"#ffa800\" },\n\t{ name = \"vite.config.mts\", text = \"\", fg = \"#ffa800\" },\n\t{ name = \"vite.config.ts\", text = \"\", fg = \"#ffa800\" },\n\t{ name = \"vitest.config.cjs\", text = \"\", fg = \"#739b1b\" },\n\t{ name = \"vitest.config.cts\", text = \"\", fg = \"#739b1b\" },\n\t{ name = \"vitest.config.js\", text = \"\", fg = \"#739b1b\" },\n\t{ name = \"vitest.config.mjs\", text = \"\", fg = \"#739b1b\" },\n\t{ name = \"vitest.config.mts\", text = \"\", fg = \"#739b1b\" },\n\t{ name = \"vitest.config.ts\", text = \"\", fg = \"#739b1b\" },\n\t{ name = \"vlcrc\", text = \"󰕼\", fg = \"#ee7a00\" },\n\t{ name = \"webpack\", text = \"󰜫\", fg = \"#519aba\" },\n\t{ name = \"weston.ini\", text = \"\", fg = \"#ffbb01\" },\n\t{ name = \"workspace\", text = \"\", fg = \"#89e051\" },\n\t{ name = \"wrangler.jsonc\", text = \"\", fg = \"#f48120\" },\n\t{ name = \"wrangler.toml\", text = \"\", fg = \"#f48120\" },\n\t{ name = \"xdph.conf\", text = \"\", fg = \"#00aaae\" },\n\t{ name = \"xmobarrc\", text = \"\", fg = \"#fd4d5d\" },\n\t{ name = \"xmobarrc.hs\", text = \"\", fg = \"#fd4d5d\" },\n\t{ name = \"xmonad.hs\", text = \"\", fg = \"#fd4d5d\" },\n\t{ name = \"xorg.conf\", text = \"\", fg = \"#e54d18\" },\n\t{ name = \"xsettingsd.conf\", text = \"\", fg = \"#e54d18\" },\n]\nexts = [\n\t{ name = \"3gp\", text = \"\", fg = \"#fd971f\" },\n\t{ name = \"3mf\", text = \"󰆧\", fg = \"#888888\" },\n\t{ name = \"7z\", text = \"\", fg = \"#eca517\" },\n\t{ name = \"a\", text = \"\", fg = \"#dcddd6\" },\n\t{ name = \"aac\", text = \"\", fg = \"#00afff\" },\n\t{ name = \"ada\", text = \"\", fg = \"#599eff\" },\n\t{ name = \"adb\", text = \"\", fg = \"#599eff\" },\n\t{ name = \"ads\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"ai\", text = \"\", fg = \"#cbcb41\" },\n\t{ name = \"aif\", text = \"\", fg = \"#00afff\" },\n\t{ name = \"aiff\", text = \"\", fg = \"#00afff\" },\n\t{ name = \"android\", text = \"\", fg = \"#34a853\" },\n\t{ name = \"ape\", text = \"\", fg = \"#00afff\" },\n\t{ name = \"apk\", text = \"\", fg = \"#34a853\" },\n\t{ name = \"apl\", text = \"\", fg = \"#24a148\" },\n\t{ name = \"app\", text = \"\", fg = \"#9f0500\" },\n\t{ name = \"applescript\", text = \"\", fg = \"#6d8085\" },\n\t{ name = \"asc\", text = \"󰦝\", fg = \"#576d7f\" },\n\t{ name = \"asm\", text = \"\", fg = \"#0091bd\" },\n\t{ name = \"ass\", text = \"󰨖\", fg = \"#ffb713\" },\n\t{ name = \"astro\", text = \"\", fg = \"#e23f67\" },\n\t{ name = \"avif\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"awk\", text = \"\", fg = \"#4d5a5e\" },\n\t{ name = \"azcli\", text = \"\", fg = \"#0078d4\" },\n\t{ name = \"bak\", text = \"󰁯\", fg = \"#6d8086\" },\n\t{ name = \"bash\", text = \"\", fg = \"#89e051\" },\n\t{ name = \"bat\", text = \"\", fg = \"#c1f12e\" },\n\t{ name = \"bazel\", text = \"\", fg = \"#89e051\" },\n\t{ name = \"bib\", text = \"󱉟\", fg = \"#cbcb41\" },\n\t{ name = \"bicep\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"bicepparam\", text = \"\", fg = \"#9f74b3\" },\n\t{ name = \"bin\", text = \"\", fg = \"#9f0500\" },\n\t{ name = \"blade.php\", text = \"\", fg = \"#f05340\" },\n\t{ name = \"blend\", text = \"󰂫\", fg = \"#ea7600\" },\n\t{ name = \"blp\", text = \"󰺾\", fg = \"#5796e2\" },\n\t{ name = \"bmp\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"bqn\", text = \"\", fg = \"#24a148\" },\n\t{ name = \"brep\", text = \"󰻫\", fg = \"#839463\" },\n\t{ name = \"bz\", text = \"\", fg = \"#eca517\" },\n\t{ name = \"bz2\", text = \"\", fg = \"#eca517\" },\n\t{ name = \"bz3\", text = \"\", fg = \"#eca517\" },\n\t{ name = \"bzl\", text = \"\", fg = \"#89e051\" },\n\t{ name = \"c\", text = \"\", fg = \"#599eff\" },\n\t{ name = \"c++\", text = \"\", fg = \"#f34b7d\" },\n\t{ name = \"cache\", text = \"\", fg = \"#ffffff\" },\n\t{ name = \"cast\", text = \"\", fg = \"#fd971f\" },\n\t{ name = \"cbl\", text = \"\", fg = \"#005ca5\" },\n\t{ name = \"cc\", text = \"\", fg = \"#f34b7d\" },\n\t{ name = \"ccm\", text = \"\", fg = \"#f34b7d\" },\n\t{ name = \"cfc\", text = \"\", fg = \"#01a4ba\" },\n\t{ name = \"cfg\", text = \"\", fg = \"#6d8086\" },\n\t{ name = \"cfm\", text = \"\", fg = \"#01a4ba\" },\n\t{ name = \"cjs\", text = \"\", fg = \"#cbcb41\" },\n\t{ name = \"clj\", text = \"\", fg = \"#8dc149\" },\n\t{ name = \"cljc\", text = \"\", fg = \"#8dc149\" },\n\t{ name = \"cljd\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"cljs\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"cmake\", text = \"\", fg = \"#dce3eb\" },\n\t{ name = \"cob\", text = \"\", fg = \"#005ca5\" },\n\t{ name = \"cobol\", text = \"\", fg = \"#005ca5\" },\n\t{ name = \"coffee\", text = \"\", fg = \"#cbcb41\" },\n\t{ name = \"conda\", text = \"\", fg = \"#43b02a\" },\n\t{ name = \"conf\", text = \"\", fg = \"#6d8086\" },\n\t{ name = \"config.ru\", text = \"\", fg = \"#701516\" },\n\t{ name = \"cow\", text = \"󰆚\", fg = \"#965824\" },\n\t{ name = \"cp\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"cpp\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"cppm\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"cpy\", text = \"\", fg = \"#005ca5\" },\n\t{ name = \"cr\", text = \"\", fg = \"#c8c8c8\" },\n\t{ name = \"crdownload\", text = \"\", fg = \"#44cda8\" },\n\t{ name = \"cs\", text = \"󰌛\", fg = \"#596706\" },\n\t{ name = \"csh\", text = \"\", fg = \"#4d5a5e\" },\n\t{ name = \"cshtml\", text = \"󱦗\", fg = \"#512bd4\" },\n\t{ name = \"cson\", text = \"\", fg = \"#cbcb41\" },\n\t{ name = \"csproj\", text = \"󰪮\", fg = \"#512bd4\" },\n\t{ name = \"css\", text = \"\", fg = \"#663399\" },\n\t{ name = \"csv\", text = \"\", fg = \"#89e051\" },\n\t{ name = \"cts\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"cu\", text = \"\", fg = \"#89e051\" },\n\t{ name = \"cue\", text = \"󰲹\", fg = \"#ed95ae\" },\n\t{ name = \"cuh\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"cxx\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"cxxm\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"d\", text = \"\", fg = \"#b03931\" },\n\t{ name = \"d.ts\", text = \"\", fg = \"#d59855\" },\n\t{ name = \"dart\", text = \"\", fg = \"#03589c\" },\n\t{ name = \"db\", text = \"\", fg = \"#dad8d8\" },\n\t{ name = \"dconf\", text = \"\", fg = \"#ffffff\" },\n\t{ name = \"desktop\", text = \"\", fg = \"#563d7c\" },\n\t{ name = \"diff\", text = \"\", fg = \"#41535b\" },\n\t{ name = \"dll\", text = \"\", fg = \"#4d2c0b\" },\n\t{ name = \"doc\", text = \"󰈬\", fg = \"#185abd\" },\n\t{ name = \"Dockerfile\", text = \"󰡨\", fg = \"#458ee6\" },\n\t{ name = \"dockerignore\", text = \"󰡨\", fg = \"#458ee6\" },\n\t{ name = \"docx\", text = \"󰈬\", fg = \"#185abd\" },\n\t{ name = \"dot\", text = \"󱁉\", fg = \"#30638e\" },\n\t{ name = \"download\", text = \"\", fg = \"#44cda8\" },\n\t{ name = \"drl\", text = \"\", fg = \"#ffafaf\" },\n\t{ name = \"dropbox\", text = \"\", fg = \"#0061fe\" },\n\t{ name = \"dump\", text = \"\", fg = \"#dad8d8\" },\n\t{ name = \"dwg\", text = \"󰻫\", fg = \"#839463\" },\n\t{ name = \"dxf\", text = \"󰻫\", fg = \"#839463\" },\n\t{ name = \"ebook\", text = \"\", fg = \"#eab16d\" },\n\t{ name = \"ebuild\", text = \"\", fg = \"#4c416e\" },\n\t{ name = \"edn\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"eex\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"ejs\", text = \"\", fg = \"#cbcb41\" },\n\t{ name = \"el\", text = \"\", fg = \"#8172be\" },\n\t{ name = \"elc\", text = \"\", fg = \"#8172be\" },\n\t{ name = \"elf\", text = \"\", fg = \"#9f0500\" },\n\t{ name = \"elm\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"eln\", text = \"\", fg = \"#8172be\" },\n\t{ name = \"env\", text = \"\", fg = \"#faf743\" },\n\t{ name = \"eot\", text = \"\", fg = \"#ececec\" },\n\t{ name = \"epp\", text = \"\", fg = \"#ffa61a\" },\n\t{ name = \"epub\", text = \"\", fg = \"#eab16d\" },\n\t{ name = \"erb\", text = \"\", fg = \"#701516\" },\n\t{ name = \"erl\", text = \"\", fg = \"#b83998\" },\n\t{ name = \"ex\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"exe\", text = \"\", fg = \"#9f0500\" },\n\t{ name = \"exs\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"f#\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"f3d\", text = \"󰻫\", fg = \"#839463\" },\n\t{ name = \"f90\", text = \"󱈚\", fg = \"#734f96\" },\n\t{ name = \"fbx\", text = \"󰆧\", fg = \"#888888\" },\n\t{ name = \"fcbak\", text = \"\", fg = \"#cb333b\" },\n\t{ name = \"fcmacro\", text = \"\", fg = \"#cb333b\" },\n\t{ name = \"fcmat\", text = \"\", fg = \"#cb333b\" },\n\t{ name = \"fcparam\", text = \"\", fg = \"#cb333b\" },\n\t{ name = \"fcscript\", text = \"\", fg = \"#cb333b\" },\n\t{ name = \"fcstd\", text = \"\", fg = \"#cb333b\" },\n\t{ name = \"fcstd1\", text = \"\", fg = \"#cb333b\" },\n\t{ name = \"fctb\", text = \"\", fg = \"#cb333b\" },\n\t{ name = \"fctl\", text = \"\", fg = \"#cb333b\" },\n\t{ name = \"fdmdownload\", text = \"\", fg = \"#44cda8\" },\n\t{ name = \"feature\", text = \"\", fg = \"#00a818\" },\n\t{ name = \"fish\", text = \"\", fg = \"#4d5a5e\" },\n\t{ name = \"flac\", text = \"\", fg = \"#0075aa\" },\n\t{ name = \"flc\", text = \"\", fg = \"#ececec\" },\n\t{ name = \"flf\", text = \"\", fg = \"#ececec\" },\n\t{ name = \"fnl\", text = \"\", fg = \"#fff3d7\" },\n\t{ name = \"fodg\", text = \"\", fg = \"#fffb57\" },\n\t{ name = \"fodp\", text = \"\", fg = \"#fe9c45\" },\n\t{ name = \"fods\", text = \"\", fg = \"#78fc4e\" },\n\t{ name = \"fodt\", text = \"\", fg = \"#2dcbfd\" },\n\t{ name = \"frag\", text = \"\", fg = \"#5586a6\" },\n\t{ name = \"fs\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"fsi\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"fsscript\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"fsx\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"gcode\", text = \"󰐫\", fg = \"#1471ad\" },\n\t{ name = \"gd\", text = \"\", fg = \"#6d8086\" },\n\t{ name = \"gemspec\", text = \"\", fg = \"#701516\" },\n\t{ name = \"geom\", text = \"\", fg = \"#5586a6\" },\n\t{ name = \"gif\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"git\", text = \"\", fg = \"#f14c28\" },\n\t{ name = \"glb\", text = \"\", fg = \"#ffb13b\" },\n\t{ name = \"gleam\", text = \"\", fg = \"#ffaff3\" },\n\t{ name = \"glsl\", text = \"\", fg = \"#5586a6\" },\n\t{ name = \"gnumakefile\", text = \"\", fg = \"#6d8086\" },\n\t{ name = \"go\", text = \"\", fg = \"#00add8\" },\n\t{ name = \"godot\", text = \"\", fg = \"#6d8086\" },\n\t{ name = \"gpr\", text = \"\", fg = \"#6d8086\" },\n\t{ name = \"gql\", text = \"\", fg = \"#e535ab\" },\n\t{ name = \"gradle\", text = \"\", fg = \"#005f87\" },\n\t{ name = \"graphql\", text = \"\", fg = \"#e535ab\" },\n\t{ name = \"gresource\", text = \"\", fg = \"#ffffff\" },\n\t{ name = \"gv\", text = \"󱁉\", fg = \"#30638e\" },\n\t{ name = \"gz\", text = \"\", fg = \"#eca517\" },\n\t{ name = \"h\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"haml\", text = \"\", fg = \"#eaeae1\" },\n\t{ name = \"hbs\", text = \"\", fg = \"#f0772b\" },\n\t{ name = \"heex\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"hex\", text = \"\", fg = \"#2e63ff\" },\n\t{ name = \"hh\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"hpp\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"hrl\", text = \"\", fg = \"#b83998\" },\n\t{ name = \"hs\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"htm\", text = \"\", fg = \"#e34c26\" },\n\t{ name = \"html\", text = \"\", fg = \"#e44d26\" },\n\t{ name = \"http\", text = \"\", fg = \"#008ec7\" },\n\t{ name = \"huff\", text = \"󰡘\", fg = \"#4242c7\" },\n\t{ name = \"hurl\", text = \"\", fg = \"#ff0288\" },\n\t{ name = \"hx\", text = \"\", fg = \"#ea8220\" },\n\t{ name = \"hxx\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"ical\", text = \"\", fg = \"#2b2e83\" },\n\t{ name = \"icalendar\", text = \"\", fg = \"#2b2e83\" },\n\t{ name = \"ico\", text = \"\", fg = \"#cbcb41\" },\n\t{ name = \"ics\", text = \"\", fg = \"#2b2e83\" },\n\t{ name = \"ifb\", text = \"\", fg = \"#2b2e83\" },\n\t{ name = \"ifc\", text = \"󰻫\", fg = \"#839463\" },\n\t{ name = \"ige\", text = \"󰻫\", fg = \"#839463\" },\n\t{ name = \"iges\", text = \"󰻫\", fg = \"#839463\" },\n\t{ name = \"igs\", text = \"󰻫\", fg = \"#839463\" },\n\t{ name = \"image\", text = \"\", fg = \"#d0bec8\" },\n\t{ name = \"img\", text = \"\", fg = \"#d0bec8\" },\n\t{ name = \"import\", text = \"\", fg = \"#ececec\" },\n\t{ name = \"info\", text = \"\", fg = \"#ffffcd\" },\n\t{ name = \"ini\", text = \"\", fg = \"#6d8086\" },\n\t{ name = \"ino\", text = \"\", fg = \"#56b6c2\" },\n\t{ name = \"ipynb\", text = \"\", fg = \"#f57d01\" },\n\t{ name = \"iso\", text = \"\", fg = \"#d0bec8\" },\n\t{ name = \"ixx\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"jar\", text = \"\", fg = \"#ffaf67\" },\n\t{ name = \"java\", text = \"\", fg = \"#cc3e44\" },\n\t{ name = \"jl\", text = \"\", fg = \"#a270ba\" },\n\t{ name = \"jpeg\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"jpg\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"js\", text = \"\", fg = \"#cbcb41\" },\n\t{ name = \"json\", text = \"\", fg = \"#cbcb41\" },\n\t{ name = \"json5\", text = \"\", fg = \"#cbcb41\" },\n\t{ name = \"jsonc\", text = \"\", fg = \"#cbcb41\" },\n\t{ name = \"jsx\", text = \"\", fg = \"#20c2e3\" },\n\t{ name = \"jwmrc\", text = \"\", fg = \"#0078cd\" },\n\t{ name = \"jxl\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"kbx\", text = \"󰯄\", fg = \"#737672\" },\n\t{ name = \"kdb\", text = \"\", fg = \"#529b34\" },\n\t{ name = \"kdbx\", text = \"\", fg = \"#529b34\" },\n\t{ name = \"kdenlive\", text = \"\", fg = \"#83b8f2\" },\n\t{ name = \"kdenlivetitle\", text = \"\", fg = \"#83b8f2\" },\n\t{ name = \"kicad_dru\", text = \"\", fg = \"#ffffff\" },\n\t{ name = \"kicad_mod\", text = \"\", fg = \"#ffffff\" },\n\t{ name = \"kicad_pcb\", text = \"\", fg = \"#ffffff\" },\n\t{ name = \"kicad_prl\", text = \"\", fg = \"#ffffff\" },\n\t{ name = \"kicad_pro\", text = \"\", fg = \"#ffffff\" },\n\t{ name = \"kicad_sch\", text = \"\", fg = \"#ffffff\" },\n\t{ name = \"kicad_sym\", text = \"\", fg = \"#ffffff\" },\n\t{ name = \"kicad_wks\", text = \"\", fg = \"#ffffff\" },\n\t{ name = \"ko\", text = \"\", fg = \"#dcddd6\" },\n\t{ name = \"kpp\", text = \"\", fg = \"#f245fb\" },\n\t{ name = \"kra\", text = \"\", fg = \"#f245fb\" },\n\t{ name = \"krz\", text = \"\", fg = \"#f245fb\" },\n\t{ name = \"ksh\", text = \"\", fg = \"#4d5a5e\" },\n\t{ name = \"kt\", text = \"\", fg = \"#7f52ff\" },\n\t{ name = \"kts\", text = \"\", fg = \"#7f52ff\" },\n\t{ name = \"lck\", text = \"\", fg = \"#bbbbbb\" },\n\t{ name = \"leex\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"less\", text = \"\", fg = \"#563d7c\" },\n\t{ name = \"lff\", text = \"\", fg = \"#ececec\" },\n\t{ name = \"lhs\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"lib\", text = \"\", fg = \"#4d2c0b\" },\n\t{ name = \"license\", text = \"\", fg = \"#cbcb41\" },\n\t{ name = \"liquid\", text = \"\", fg = \"#95bf47\" },\n\t{ name = \"lock\", text = \"\", fg = \"#bbbbbb\" },\n\t{ name = \"log\", text = \"󰌱\", fg = \"#dddddd\" },\n\t{ name = \"lrc\", text = \"󰨖\", fg = \"#ffb713\" },\n\t{ name = \"lua\", text = \"\", fg = \"#51a0cf\" },\n\t{ name = \"luac\", text = \"\", fg = \"#51a0cf\" },\n\t{ name = \"luau\", text = \"\", fg = \"#00a2ff\" },\n\t{ name = \"m\", text = \"\", fg = \"#599eff\" },\n\t{ name = \"m3u\", text = \"󰲹\", fg = \"#ed95ae\" },\n\t{ name = \"m3u8\", text = \"󰲹\", fg = \"#ed95ae\" },\n\t{ name = \"m4a\", text = \"\", fg = \"#00afff\" },\n\t{ name = \"m4v\", text = \"\", fg = \"#fd971f\" },\n\t{ name = \"magnet\", text = \"\", fg = \"#a51b16\" },\n\t{ name = \"makefile\", text = \"\", fg = \"#6d8086\" },\n\t{ name = \"markdown\", text = \"\", fg = \"#dddddd\" },\n\t{ name = \"material\", text = \"\", fg = \"#b83998\" },\n\t{ name = \"md\", text = \"\", fg = \"#dddddd\" },\n\t{ name = \"md5\", text = \"󰕥\", fg = \"#8c86af\" },\n\t{ name = \"mdx\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"mint\", text = \"󰌪\", fg = \"#87c095\" },\n\t{ name = \"mjs\", text = \"\", fg = \"#f1e05a\" },\n\t{ name = \"mk\", text = \"\", fg = \"#6d8086\" },\n\t{ name = \"mkv\", text = \"\", fg = \"#fd971f\" },\n\t{ name = \"ml\", text = \"\", fg = \"#e37933\" },\n\t{ name = \"mli\", text = \"\", fg = \"#e37933\" },\n\t{ name = \"mm\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"mo\", text = \"\", fg = \"#9772fb\" },\n\t{ name = \"mobi\", text = \"\", fg = \"#eab16d\" },\n\t{ name = \"mojo\", text = \"\", fg = \"#ff4c1f\" },\n\t{ name = \"mov\", text = \"\", fg = \"#fd971f\" },\n\t{ name = \"mp3\", text = \"\", fg = \"#00afff\" },\n\t{ name = \"mp4\", text = \"\", fg = \"#fd971f\" },\n\t{ name = \"mpp\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"msf\", text = \"\", fg = \"#137be1\" },\n\t{ name = \"mts\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"mustache\", text = \"\", fg = \"#e37933\" },\n\t{ name = \"nfo\", text = \"\", fg = \"#ffffcd\" },\n\t{ name = \"nim\", text = \"\", fg = \"#f3d400\" },\n\t{ name = \"nix\", text = \"\", fg = \"#7ebae4\" },\n\t{ name = \"norg\", text = \"\", fg = \"#4878be\" },\n\t{ name = \"nswag\", text = \"\", fg = \"#85ea2d\" },\n\t{ name = \"nu\", text = \"\", fg = \"#3aa675\" },\n\t{ name = \"o\", text = \"\", fg = \"#9f0500\" },\n\t{ name = \"obj\", text = \"󰆧\", fg = \"#888888\" },\n\t{ name = \"odf\", text = \"\", fg = \"#ff5a96\" },\n\t{ name = \"odg\", text = \"\", fg = \"#fffb57\" },\n\t{ name = \"odin\", text = \"󰟢\", fg = \"#3882d2\" },\n\t{ name = \"odp\", text = \"\", fg = \"#fe9c45\" },\n\t{ name = \"ods\", text = \"\", fg = \"#78fc4e\" },\n\t{ name = \"odt\", text = \"\", fg = \"#2dcbfd\" },\n\t{ name = \"oga\", text = \"\", fg = \"#0075aa\" },\n\t{ name = \"ogg\", text = \"\", fg = \"#0075aa\" },\n\t{ name = \"ogv\", text = \"\", fg = \"#fd971f\" },\n\t{ name = \"ogx\", text = \"\", fg = \"#fd971f\" },\n\t{ name = \"opus\", text = \"\", fg = \"#0075aa\" },\n\t{ name = \"org\", text = \"\", fg = \"#77aa99\" },\n\t{ name = \"otf\", text = \"\", fg = \"#ececec\" },\n\t{ name = \"out\", text = \"\", fg = \"#9f0500\" },\n\t{ name = \"part\", text = \"\", fg = \"#44cda8\" },\n\t{ name = \"patch\", text = \"\", fg = \"#41535b\" },\n\t{ name = \"pck\", text = \"\", fg = \"#6d8086\" },\n\t{ name = \"pcm\", text = \"\", fg = \"#0075aa\" },\n\t{ name = \"pdf\", text = \"\", fg = \"#b30b00\" },\n\t{ name = \"php\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"pl\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"pls\", text = \"󰲹\", fg = \"#ed95ae\" },\n\t{ name = \"ply\", text = \"󰆧\", fg = \"#888888\" },\n\t{ name = \"pm\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"png\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"po\", text = \"\", fg = \"#2596be\" },\n\t{ name = \"pot\", text = \"\", fg = \"#2596be\" },\n\t{ name = \"pp\", text = \"\", fg = \"#ffa61a\" },\n\t{ name = \"ppt\", text = \"󰈧\", fg = \"#cb4a32\" },\n\t{ name = \"pptx\", text = \"󰈧\", fg = \"#cb4a32\" },\n\t{ name = \"prisma\", text = \"\", fg = \"#5a67d8\" },\n\t{ name = \"pro\", text = \"\", fg = \"#e4b854\" },\n\t{ name = \"ps1\", text = \"󰨊\", fg = \"#4273ca\" },\n\t{ name = \"psb\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"psd\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"psd1\", text = \"󰨊\", fg = \"#6975c4\" },\n\t{ name = \"psm1\", text = \"󰨊\", fg = \"#6975c4\" },\n\t{ name = \"pub\", text = \"󰷖\", fg = \"#e3c58e\" },\n\t{ name = \"pxd\", text = \"\", fg = \"#5aa7e4\" },\n\t{ name = \"pxi\", text = \"\", fg = \"#5aa7e4\" },\n\t{ name = \"py\", text = \"\", fg = \"#ffbc03\" },\n\t{ name = \"pyc\", text = \"\", fg = \"#ffe291\" },\n\t{ name = \"pyd\", text = \"\", fg = \"#ffe291\" },\n\t{ name = \"pyi\", text = \"\", fg = \"#ffbc03\" },\n\t{ name = \"pyo\", text = \"\", fg = \"#ffe291\" },\n\t{ name = \"pyw\", text = \"\", fg = \"#5aa7e4\" },\n\t{ name = \"pyx\", text = \"\", fg = \"#5aa7e4\" },\n\t{ name = \"qm\", text = \"\", fg = \"#2596be\" },\n\t{ name = \"qml\", text = \"\", fg = \"#40cd52\" },\n\t{ name = \"qrc\", text = \"\", fg = \"#40cd52\" },\n\t{ name = \"qss\", text = \"\", fg = \"#40cd52\" },\n\t{ name = \"query\", text = \"\", fg = \"#90a850\" },\n\t{ name = \"R\", text = \"󰟔\", fg = \"#2266ba\" },\n\t{ name = \"r\", text = \"󰟔\", fg = \"#2266ba\" },\n\t{ name = \"rake\", text = \"\", fg = \"#701516\" },\n\t{ name = \"rar\", text = \"\", fg = \"#eca517\" },\n\t{ name = \"rasi\", text = \"\", fg = \"#cbcb41\" },\n\t{ name = \"razor\", text = \"󱦘\", fg = \"#512bd4\" },\n\t{ name = \"rb\", text = \"\", fg = \"#701516\" },\n\t{ name = \"res\", text = \"\", fg = \"#cc3e44\" },\n\t{ name = \"resi\", text = \"\", fg = \"#f55385\" },\n\t{ name = \"rlib\", text = \"\", fg = \"#dea584\" },\n\t{ name = \"rmd\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"rproj\", text = \"󰗆\", fg = \"#358a5b\" },\n\t{ name = \"rs\", text = \"\", fg = \"#dea584\" },\n\t{ name = \"rss\", text = \"\", fg = \"#fb9d3b\" },\n\t{ name = \"s\", text = \"\", fg = \"#0071c5\" },\n\t{ name = \"sass\", text = \"\", fg = \"#f55385\" },\n\t{ name = \"sbt\", text = \"\", fg = \"#cc3e44\" },\n\t{ name = \"sc\", text = \"\", fg = \"#cc3e44\" },\n\t{ name = \"scad\", text = \"\", fg = \"#f9d72c\" },\n\t{ name = \"scala\", text = \"\", fg = \"#cc3e44\" },\n\t{ name = \"scm\", text = \"󰘧\", fg = \"#eeeeee\" },\n\t{ name = \"scss\", text = \"\", fg = \"#f55385\" },\n\t{ name = \"sh\", text = \"\", fg = \"#4d5a5e\" },\n\t{ name = \"sha1\", text = \"󰕥\", fg = \"#8c86af\" },\n\t{ name = \"sha224\", text = \"󰕥\", fg = \"#8c86af\" },\n\t{ name = \"sha256\", text = \"󰕥\", fg = \"#8c86af\" },\n\t{ name = \"sha384\", text = \"󰕥\", fg = \"#8c86af\" },\n\t{ name = \"sha512\", text = \"󰕥\", fg = \"#8c86af\" },\n\t{ name = \"sig\", text = \"󰘧\", fg = \"#e37933\" },\n\t{ name = \"signature\", text = \"󰘧\", fg = \"#e37933\" },\n\t{ name = \"skp\", text = \"󰻫\", fg = \"#839463\" },\n\t{ name = \"sldasm\", text = \"󰻫\", fg = \"#839463\" },\n\t{ name = \"sldprt\", text = \"󰻫\", fg = \"#839463\" },\n\t{ name = \"slim\", text = \"\", fg = \"#e34c26\" },\n\t{ name = \"sln\", text = \"\", fg = \"#854cc7\" },\n\t{ name = \"slnx\", text = \"\", fg = \"#854cc7\" },\n\t{ name = \"slvs\", text = \"󰻫\", fg = \"#839463\" },\n\t{ name = \"sml\", text = \"󰘧\", fg = \"#e37933\" },\n\t{ name = \"so\", text = \"\", fg = \"#dcddd6\" },\n\t{ name = \"sol\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"spec.js\", text = \"\", fg = \"#cbcb41\" },\n\t{ name = \"spec.jsx\", text = \"\", fg = \"#20c2e3\" },\n\t{ name = \"spec.ts\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"spec.tsx\", text = \"\", fg = \"#1354bf\" },\n\t{ name = \"spx\", text = \"\", fg = \"#0075aa\" },\n\t{ name = \"sql\", text = \"\", fg = \"#dad8d8\" },\n\t{ name = \"sqlite\", text = \"\", fg = \"#dad8d8\" },\n\t{ name = \"sqlite3\", text = \"\", fg = \"#dad8d8\" },\n\t{ name = \"srt\", text = \"󰨖\", fg = \"#ffb713\" },\n\t{ name = \"ssa\", text = \"󰨖\", fg = \"#ffb713\" },\n\t{ name = \"ste\", text = \"󰻫\", fg = \"#839463\" },\n\t{ name = \"step\", text = \"󰻫\", fg = \"#839463\" },\n\t{ name = \"stl\", text = \"󰆧\", fg = \"#888888\" },\n\t{ name = \"stories.js\", text = \"\", fg = \"#ff4785\" },\n\t{ name = \"stories.jsx\", text = \"\", fg = \"#ff4785\" },\n\t{ name = \"stories.mjs\", text = \"\", fg = \"#ff4785\" },\n\t{ name = \"stories.svelte\", text = \"\", fg = \"#ff4785\" },\n\t{ name = \"stories.ts\", text = \"\", fg = \"#ff4785\" },\n\t{ name = \"stories.tsx\", text = \"\", fg = \"#ff4785\" },\n\t{ name = \"stories.vue\", text = \"\", fg = \"#ff4785\" },\n\t{ name = \"stp\", text = \"󰻫\", fg = \"#839463\" },\n\t{ name = \"strings\", text = \"\", fg = \"#2596be\" },\n\t{ name = \"styl\", text = \"\", fg = \"#8dc149\" },\n\t{ name = \"sub\", text = \"󰨖\", fg = \"#ffb713\" },\n\t{ name = \"sublime\", text = \"\", fg = \"#e37933\" },\n\t{ name = \"suo\", text = \"\", fg = \"#854cc7\" },\n\t{ name = \"sv\", text = \"󰍛\", fg = \"#019833\" },\n\t{ name = \"svelte\", text = \"\", fg = \"#ff3e00\" },\n\t{ name = \"svg\", text = \"󰜡\", fg = \"#ffb13b\" },\n\t{ name = \"svgz\", text = \"󰜡\", fg = \"#ffb13b\" },\n\t{ name = \"svh\", text = \"󰍛\", fg = \"#019833\" },\n\t{ name = \"swift\", text = \"\", fg = \"#e37933\" },\n\t{ name = \"t\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"tbc\", text = \"󰛓\", fg = \"#1e5cb3\" },\n\t{ name = \"tcl\", text = \"󰛓\", fg = \"#1e5cb3\" },\n\t{ name = \"templ\", text = \"\", fg = \"#dbbd30\" },\n\t{ name = \"terminal\", text = \"\", fg = \"#31b53e\" },\n\t{ name = \"test.js\", text = \"\", fg = \"#cbcb41\" },\n\t{ name = \"test.jsx\", text = \"\", fg = \"#20c2e3\" },\n\t{ name = \"test.ts\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"test.tsx\", text = \"\", fg = \"#1354bf\" },\n\t{ name = \"tex\", text = \"\", fg = \"#3d6117\" },\n\t{ name = \"tf\", text = \"\", fg = \"#5f43e9\" },\n\t{ name = \"tfvars\", text = \"\", fg = \"#5f43e9\" },\n\t{ name = \"tgz\", text = \"\", fg = \"#eca517\" },\n\t{ name = \"tmpl\", text = \"\", fg = \"#dbbd30\" },\n\t{ name = \"tmux\", text = \"\", fg = \"#14ba19\" },\n\t{ name = \"toml\", text = \"\", fg = \"#9c4221\" },\n\t{ name = \"torrent\", text = \"\", fg = \"#44cda8\" },\n\t{ name = \"tres\", text = \"\", fg = \"#6d8086\" },\n\t{ name = \"ts\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"tscn\", text = \"\", fg = \"#6d8086\" },\n\t{ name = \"tsconfig\", text = \"\", fg = \"#ff8700\" },\n\t{ name = \"tsx\", text = \"\", fg = \"#1354bf\" },\n\t{ name = \"ttf\", text = \"\", fg = \"#ececec\" },\n\t{ name = \"twig\", text = \"\", fg = \"#8dc149\" },\n\t{ name = \"txt\", text = \"󰈙\", fg = \"#89e051\" },\n\t{ name = \"txz\", text = \"\", fg = \"#eca517\" },\n\t{ name = \"typ\", text = \"\", fg = \"#0dbcc0\" },\n\t{ name = \"typoscript\", text = \"\", fg = \"#ff8700\" },\n\t{ name = \"ui\", text = \"\", fg = \"#015bf0\" },\n\t{ name = \"v\", text = \"󰍛\", fg = \"#019833\" },\n\t{ name = \"vala\", text = \"\", fg = \"#7b3db9\" },\n\t{ name = \"vert\", text = \"\", fg = \"#5586a6\" },\n\t{ name = \"vh\", text = \"󰍛\", fg = \"#019833\" },\n\t{ name = \"vhd\", text = \"󰍛\", fg = \"#019833\" },\n\t{ name = \"vhdl\", text = \"󰍛\", fg = \"#019833\" },\n\t{ name = \"vi\", text = \"\", fg = \"#fec60a\" },\n\t{ name = \"vim\", text = \"\", fg = \"#019833\" },\n\t{ name = \"vsh\", text = \"\", fg = \"#5d87bf\" },\n\t{ name = \"vsix\", text = \"\", fg = \"#854cc7\" },\n\t{ name = \"vue\", text = \"\", fg = \"#8dc149\" },\n\t{ name = \"wasm\", text = \"\", fg = \"#5c4cdb\" },\n\t{ name = \"wav\", text = \"\", fg = \"#00afff\" },\n\t{ name = \"webm\", text = \"\", fg = \"#fd971f\" },\n\t{ name = \"webmanifest\", text = \"\", fg = \"#f1e05a\" },\n\t{ name = \"webp\", text = \"\", fg = \"#a074c4\" },\n\t{ name = \"webpack\", text = \"󰜫\", fg = \"#519aba\" },\n\t{ name = \"wma\", text = \"\", fg = \"#00afff\" },\n\t{ name = \"wmv\", text = \"\", fg = \"#fd971f\" },\n\t{ name = \"woff\", text = \"\", fg = \"#ececec\" },\n\t{ name = \"woff2\", text = \"\", fg = \"#ececec\" },\n\t{ name = \"wrl\", text = \"󰆧\", fg = \"#888888\" },\n\t{ name = \"wrz\", text = \"󰆧\", fg = \"#888888\" },\n\t{ name = \"wv\", text = \"\", fg = \"#00afff\" },\n\t{ name = \"wvc\", text = \"\", fg = \"#00afff\" },\n\t{ name = \"x\", text = \"\", fg = \"#599eff\" },\n\t{ name = \"xaml\", text = \"󰙳\", fg = \"#512bd4\" },\n\t{ name = \"xcf\", text = \"\", fg = \"#635b46\" },\n\t{ name = \"xcplayground\", text = \"\", fg = \"#e37933\" },\n\t{ name = \"xcstrings\", text = \"\", fg = \"#2596be\" },\n\t{ name = \"xls\", text = \"󰈛\", fg = \"#207245\" },\n\t{ name = \"xlsx\", text = \"󰈛\", fg = \"#207245\" },\n\t{ name = \"xm\", text = \"\", fg = \"#519aba\" },\n\t{ name = \"xml\", text = \"󰗀\", fg = \"#e37933\" },\n\t{ name = \"xpi\", text = \"\", fg = \"#ff1b01\" },\n\t{ name = \"xslt\", text = \"󰗀\", fg = \"#33a9dc\" },\n\t{ name = \"xul\", text = \"\", fg = \"#e37933\" },\n\t{ name = \"xz\", text = \"\", fg = \"#eca517\" },\n\t{ name = \"yaml\", text = \"\", fg = \"#6d8086\" },\n\t{ name = \"yml\", text = \"\", fg = \"#6d8086\" },\n\t{ name = \"zig\", text = \"\", fg = \"#f69a1b\" },\n\t{ name = \"zip\", text = \"\", fg = \"#eca517\" },\n\t{ name = \"zsh\", text = \"\", fg = \"#89e051\" },\n\t{ name = \"zst\", text = \"\", fg = \"#eca517\" },\n\t{ name = \"🔥\", text = \"\", fg = \"#ff4c1f\" },\n]\nconds = [\n\t# Special files\n\t{ if = \"orphan\", text = \"\", fg = \"#ffffff\" },\n\t{ if = \"link\", text = \"\", fg = \"#9e9e9e\" },\n\t{ if = \"block\", text = \"\", fg = \"#cddc39\" },\n\t{ if = \"char\", text = \"\", fg = \"#cddc39\" },\n\t{ if = \"fifo\", text = \"\", fg = \"#cddc39\" },\n\t{ if = \"sock\", text = \"\", fg = \"#cddc39\" },\n\t{ if = \"sticky\", text = \"\", fg = \"#cddc39\" },\n\t{ if = \"dummy\", text = \"\", fg = \"#f44336\" },\n\n\t# Fallback\n\t{ if = \"dir & hovered\", text = \"\", fg = \"#03a9f4\" },\n\t{ if = \"dir\", text = \"\", fg = \"#03a9f4\" },\n\t{ if = \"exec\", text = \"\", fg = \"#8bc34a\" },\n\t{ if = \"!dir\", text = \"\", fg = \"#ffffff\" },\n]\n\n# : }}}\n"
  },
  {
    "path": "yazi-config/preset/theme-light.toml",
    "content": "# If the user's terminal is in dark mode, Yazi will load `theme-dark.toml` on startup; otherwise, `theme-light.toml`.\n# You can override any parts of them that are not related to the dark/light mode in your own `theme.toml`.\n\n# If you want to dynamically override their content based on dark/light mode, you can specify two different flavors\n# for dark and light modes under `[flavor]`, and do so in those flavors instead.\n\"$schema\" = \"https://yazi-rs.github.io/schemas/theme.json\"\n\n# vim:fileencoding=utf-8:foldmethod=marker\n\n# : Flavor {{{\n\n[flavor]\ndark  = \"\"\nlight = \"\"\n\n# : }}}\n\n\n# : App {{{\n\n[app]\noverall = {}\n\n# : }}}\n\n\n# : Manager {{{\n\n[mgr]\ncwd = { fg = \"cyan\" }\n\n# Find\nfind_keyword  = { fg = \"yellow\", bold = true, italic = true, underline = true }\nfind_position = { fg = \"magenta\", bg = \"reset\", bold = true, italic = true }\n\n# Symlink\nsymlink_target = { italic = true }\n\n# Marker\nmarker_copied   = { fg = \"lightgreen\",  bg = \"lightgreen\" }\nmarker_cut      = { fg = \"lightred\",    bg = \"lightred\" }\nmarker_marked   = { fg = \"lightcyan\",   bg = \"lightcyan\" }\nmarker_selected = { fg = \"lightyellow\", bg = \"lightyellow\" }\nmarker_symbol   = \"│\"\n\n# Count\ncount_copied   = { fg = \"white\", bg = \"green\" }\ncount_cut      = { fg = \"white\", bg = \"red\" }\ncount_selected = { fg = \"white\", bg = \"yellow\" }\n\n# Border\nborder_symbol = \"│\"\nborder_style  = { fg = \"gray\" }\n\n# Highlighting\nsyntect_theme = \"\"\n\n# : }}}\n\n\n# : Tabs {{{\n\n[tabs]\nactive   = { bg = \"blue\", bold = true }\ninactive = { fg = \"blue\", bg = \"gray\" }\n\n# Separator\nsep_inner = { open = \"\", close = \"\" }\nsep_outer = { open = \"\", close = \"\" }\n\n# : }}}\n\n\n# : Mode {{{\n\n[mode]\nnormal_main = { bg = \"blue\", bold = true }\nnormal_alt  = { fg = \"blue\", bg = \"gray\" }\n\n# Select mode\nselect_main = { bg = \"red\", bold = true }\nselect_alt  = { fg = \"red\", bg = \"gray\" }\n\n# Unset mode\nunset_main = { bg = \"red\", bold = true }\nunset_alt  = { fg = \"red\", bg = \"gray\" }\n\n# : }}}\n\n\n# : Indicator of hovered file {{{\n\n[indicator]\nparent  = { reversed = true }\ncurrent = { reversed = true }\npreview = { underline = true }\npadding = { open = \"\", close = \"\" }\n\n# : }}}\n\n\n# : Status bar {{{\n\n[status]\noverall   = {}\nsep_left  = { open = \"\", close = \"\" }\nsep_right = { open = \"\", close = \"\" }\n\n# Permissions\nperm_sep   = { fg = \"darkgray\" }\nperm_type  = { fg = \"green\" }\nperm_read  = { fg = \"yellow\" }\nperm_write = { fg = \"red\" }\nperm_exec  = { fg = \"cyan\" }\n\n# Progress\nprogress_label  = { bold = true }\nprogress_normal = { fg = \"green\", bg = \"gray\" }\nprogress_error  = { fg = \"yellow\", bg = \"red\" }\n\n# : }}}\n\n\n# : Which {{{\n\n[which]\ncols            = 3\nmask            = { bg = \"black\" }\ncand            = { fg = \"lightcyan\" }\nrest            = { fg = \"darkgray\" }\ndesc            = { fg = \"lightmagenta\" }\nseparator       = \"  \"\nseparator_style = { fg = \"darkgray\" }\n\n# : }}}\n\n\n# : Confirmation {{{\n\n[confirm]\nborder     = { fg = \"blue\" }\ntitle      = { fg = \"blue\" }\nbody       = {}\nlist       = {}\nbtn_yes    = { reversed = true }\nbtn_no     = {}\nbtn_labels = [ \"  [Y]es  \", \"  (N)o  \" ]\n\n# : }}}\n\n\n# : Spotter {{{\n\n[spot]\nborder = { fg = \"blue\" }\ntitle  = { fg = \"blue\" }\n\n# Table\ntbl_col  = { fg = \"blue\" }\ntbl_cell = { fg = \"yellow\", reversed = true }\n\n# : }}}\n\n\n# : Notification {{{\n\n[notify]\ntitle_info  = { fg = \"green\" }\ntitle_warn  = { fg = \"yellow\" }\ntitle_error = { fg = \"red\" }\n\n# Icons\nicon_info  = \"\"\nicon_warn  = \"\"\nicon_error = \"\"\n\n# : }}}\n\n\n# : Picker {{{\n\n[pick]\nborder   = { fg = \"blue\" }\nactive   = { fg = \"magenta\", bold = true }\ninactive = {}\n\n# : }}}\n\n\n# : Input {{{\n\n[input]\nborder   = { fg = \"blue\" }\ntitle    = {}\nvalue    = {}\nselected = { reversed = true }\n\n# : }}}\n\n\n# : Completion {{{\n\n[cmp]\nborder   = { fg = \"blue\" }\nactive   = { reversed = true }\ninactive = {}\n\n# Icons\nicon_file    = \"\"\nicon_folder  = \"\"\nicon_command = \"\"\n\n# : }}}\n\n\n# : Task manager {{{\n\n[tasks]\nborder  = { fg = \"blue\" }\ntitle   = {}\nhovered = { fg = \"magenta\", bold = true }\n\n# : }}}\n\n\n# : Help menu {{{\n\n[help]\non      = { fg = \"cyan\" }\nrun     = { fg = \"magenta\" }\ndesc    = {}\nhovered = { reversed = true, bold = true }\nfooter  = { fg = \"black\", bg = \"white\" }\n\n# : }}}\n\n\n# : File-specific styles {{{\n\n[filetype]\nrules = [\n\t# Image\n\t{ mime = \"image/*\", fg = \"yellow\" },\n\t# Media\n\t{ mime = \"{audio,video}/*\", fg = \"magenta\" },\n\t# Archive\n\t{ mime = \"application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}\", fg = \"red\" },\n\t# Document\n\t{ mime = \"application/{pdf,doc,rtf}\", fg = \"cyan\" },\n\t# Virtual file system\n\t{ mime = \"vfs/{absent,stale}\", fg = \"darkgray\" },\n\t# Special file\n\t{ url = \"*\", is = \"orphan\", bg = \"red\" },\n\t{ url = \"*\", is = \"exec\"  , fg = \"green\" },\n\t# Dummy file\n\t{ url = \"*\", is = \"dummy\", bg = \"red\" },\n\t{ url = \"*/\", is = \"dummy\", bg = \"red\" },\n\t# Fallback\n\t{ url = \"*/\", fg = \"blue\" }\n]\n\n# : }}}\n\n\n# : Icons {{{\n\n[icon]\nglobs = []\ndirs  = [\n\t{ name = \".config\", text = \"\", fg = \"#ff9800\" },\n\t{ name = \".git\", text = \"\", fg = \"#009688\" },\n\t{ name = \".github\", text = \"\", fg = \"#03a9f4\" },\n\t{ name = \".npm\", text = \"\", fg = \"#03a9f4\" },\n\t{ name = \"Desktop\", text = \"\", fg = \"#009688\" },\n\t{ name = \"Development\", text = \"\", fg = \"#009688\" },\n\t{ name = \"Documents\", text = \"\", fg = \"#009688\" },\n\t{ name = \"Downloads\", text = \"\", fg = \"#009688\" },\n\t{ name = \"Library\", text = \"\", fg = \"#009688\" },\n\t{ name = \"Movies\", text = \"\", fg = \"#009688\" },\n\t{ name = \"Music\", text = \"\", fg = \"#009688\" },\n\t{ name = \"Pictures\", text = \"\", fg = \"#009688\" },\n\t{ name = \"Public\", text = \"\", fg = \"#009688\" },\n\t{ name = \"Videos\", text = \"\", fg = \"#009688\" },\n]\nfiles = [\n\t{ name = \".babelrc\", text = \"\", fg = \"#666620\" },\n\t{ name = \".bash_profile\", text = \"\", fg = \"#447028\" },\n\t{ name = \".bashrc\", text = \"\", fg = \"#447028\" },\n\t{ name = \".clang-format\", text = \"\", fg = \"#526064\" },\n\t{ name = \".clang-tidy\", text = \"\", fg = \"#526064\" },\n\t{ name = \".codespellrc\", text = \"󰓆\", fg = \"#239140\" },\n\t{ name = \".condarc\", text = \"\", fg = \"#2d751c\" },\n\t{ name = \".dockerignore\", text = \"󰡨\", fg = \"#2e5f99\" },\n\t{ name = \".ds_store\", text = \"\", fg = \"#41535b\" },\n\t{ name = \".editorconfig\", text = \"\", fg = \"#333030\" },\n\t{ name = \".env\", text = \"\", fg = \"#32310d\" },\n\t{ name = \".eslintignore\", text = \"\", fg = \"#4b32c3\" },\n\t{ name = \".eslintrc\", text = \"\", fg = \"#4b32c3\" },\n\t{ name = \".git-blame-ignore-revs\", text = \"\", fg = \"#b83a1d\" },\n\t{ name = \".gitattributes\", text = \"\", fg = \"#b83a1d\" },\n\t{ name = \".gitconfig\", text = \"\", fg = \"#b83a1d\" },\n\t{ name = \".gitignore\", text = \"\", fg = \"#b83a1d\" },\n\t{ name = \".gitlab-ci.yml\", text = \"\", fg = \"#aa321f\" },\n\t{ name = \".gitmodules\", text = \"\", fg = \"#b83a1d\" },\n\t{ name = \".gtkrc-2.0\", text = \"\", fg = \"#333333\" },\n\t{ name = \".gvimrc\", text = \"\", fg = \"#017226\" },\n\t{ name = \".justfile\", text = \"\", fg = \"#526064\" },\n\t{ name = \".luacheckrc\", text = \"\", fg = \"#007abf\" },\n\t{ name = \".luaurc\", text = \"\", fg = \"#007abf\" },\n\t{ name = \".mailmap\", text = \"󰊢\", fg = \"#b83a1d\" },\n\t{ name = \".nanorc\", text = \"\", fg = \"#440077\" },\n\t{ name = \".npmignore\", text = \"\", fg = \"#ae1d38\" },\n\t{ name = \".npmrc\", text = \"\", fg = \"#ae1d38\" },\n\t{ name = \".nuxtrc\", text = \"󱄆\", fg = \"#00835f\" },\n\t{ name = \".nvmrc\", text = \"\", fg = \"#3f6b34\" },\n\t{ name = \".pnpmfile.cjs\", text = \"\", fg = \"#7c5601\" },\n\t{ name = \".pre-commit-config.yaml\", text = \"󰛢\", fg = \"#7c5a12\" },\n\t{ name = \".prettierignore\", text = \"\", fg = \"#3264b7\" },\n\t{ name = \".prettierrc\", text = \"\", fg = \"#3264b7\" },\n\t{ name = \".prettierrc.cjs\", text = \"\", fg = \"#3264b7\" },\n\t{ name = \".prettierrc.js\", text = \"\", fg = \"#3264b7\" },\n\t{ name = \".prettierrc.json\", text = \"\", fg = \"#3264b7\" },\n\t{ name = \".prettierrc.json5\", text = \"\", fg = \"#3264b7\" },\n\t{ name = \".prettierrc.mjs\", text = \"\", fg = \"#3264b7\" },\n\t{ name = \".prettierrc.toml\", text = \"\", fg = \"#3264b7\" },\n\t{ name = \".prettierrc.yaml\", text = \"\", fg = \"#3264b7\" },\n\t{ name = \".prettierrc.yml\", text = \"\", fg = \"#3264b7\" },\n\t{ name = \".pylintrc\", text = \"\", fg = \"#526064\" },\n\t{ name = \".settings.json\", text = \"\", fg = \"#643995\" },\n\t{ name = \".SRCINFO\", text = \"󰣇\", fg = \"#0b6f9e\" },\n\t{ name = \".vimrc\", text = \"\", fg = \"#017226\" },\n\t{ name = \".Xauthority\", text = \"\", fg = \"#ac3a12\" },\n\t{ name = \".xinitrc\", text = \"\", fg = \"#ac3a12\" },\n\t{ name = \".Xresources\", text = \"\", fg = \"#ac3a12\" },\n\t{ name = \".xsession\", text = \"\", fg = \"#ac3a12\" },\n\t{ name = \".zprofile\", text = \"\", fg = \"#447028\" },\n\t{ name = \".zshenv\", text = \"\", fg = \"#447028\" },\n\t{ name = \".zshrc\", text = \"\", fg = \"#447028\" },\n\t{ name = \"_gvimrc\", text = \"\", fg = \"#017226\" },\n\t{ name = \"_vimrc\", text = \"\", fg = \"#017226\" },\n\t{ name = \"AUTHORS\", text = \"\", fg = \"#6b4caa\" },\n\t{ name = \"AUTHORS.txt\", text = \"\", fg = \"#6b4caa\" },\n\t{ name = \"brewfile\", text = \"\", fg = \"#701516\" },\n\t{ name = \"bspwmrc\", text = \"\", fg = \"#2f2f2f\" },\n\t{ name = \"build\", text = \"\", fg = \"#447028\" },\n\t{ name = \"build.gradle\", text = \"\", fg = \"#005f87\" },\n\t{ name = \"build.zig.zon\", text = \"\", fg = \"#7b4d0e\" },\n\t{ name = \"bun.lock\", text = \"\", fg = \"#4e4946\" },\n\t{ name = \"bun.lockb\", text = \"\", fg = \"#4e4946\" },\n\t{ name = \"cantorrc\", text = \"\", fg = \"#1573b6\" },\n\t{ name = \"checkhealth\", text = \"󰓙\", fg = \"#3a5a7e\" },\n\t{ name = \"cmakelists.txt\", text = \"\", fg = \"#2c2d2f\" },\n\t{ name = \"code_of_conduct\", text = \"\", fg = \"#ab104a\" },\n\t{ name = \"code_of_conduct.md\", text = \"\", fg = \"#ab104a\" },\n\t{ name = \"commit_editmsg\", text = \"\", fg = \"#b83a1d\" },\n\t{ name = \"commitlint.config.js\", text = \"󰜘\", fg = \"#207067\" },\n\t{ name = \"commitlint.config.ts\", text = \"󰜘\", fg = \"#207067\" },\n\t{ name = \"compose.yaml\", text = \"󰡨\", fg = \"#2e5f99\" },\n\t{ name = \"compose.yml\", text = \"󰡨\", fg = \"#2e5f99\" },\n\t{ name = \"config\", text = \"\", fg = \"#526064\" },\n\t{ name = \"containerfile\", text = \"󰡨\", fg = \"#2e5f99\" },\n\t{ name = \"copying\", text = \"\", fg = \"#666620\" },\n\t{ name = \"copying.lesser\", text = \"\", fg = \"#666620\" },\n\t{ name = \"Directory.Build.props\", text = \"\", fg = \"#007abf\" },\n\t{ name = \"Directory.Build.targets\", text = \"\", fg = \"#007abf\" },\n\t{ name = \"Directory.Packages.props\", text = \"\", fg = \"#007abf\" },\n\t{ name = \"docker-compose.yaml\", text = \"󰡨\", fg = \"#2e5f99\" },\n\t{ name = \"docker-compose.yml\", text = \"󰡨\", fg = \"#2e5f99\" },\n\t{ name = \"dockerfile\", text = \"󰡨\", fg = \"#2e5f99\" },\n\t{ name = \"eslint.config.cjs\", text = \"\", fg = \"#4b32c3\" },\n\t{ name = \"eslint.config.js\", text = \"\", fg = \"#4b32c3\" },\n\t{ name = \"eslint.config.mjs\", text = \"\", fg = \"#4b32c3\" },\n\t{ name = \"eslint.config.ts\", text = \"\", fg = \"#4b32c3\" },\n\t{ name = \"ext_typoscript_setup.txt\", text = \"\", fg = \"#aa5a00\" },\n\t{ name = \"favicon.ico\", text = \"\", fg = \"#666620\" },\n\t{ name = \"fp-info-cache\", text = \"\", fg = \"#333333\" },\n\t{ name = \"fp-lib-table\", text = \"\", fg = \"#333333\" },\n\t{ name = \"FreeCAD.conf\", text = \"\", fg = \"#98262c\" },\n\t{ name = \"Gemfile\", text = \"\", fg = \"#701516\" },\n\t{ name = \"gnumakefile\", text = \"\", fg = \"#526064\" },\n\t{ name = \"go.mod\", text = \"\", fg = \"#0082a2\" },\n\t{ name = \"go.sum\", text = \"\", fg = \"#0082a2\" },\n\t{ name = \"go.work\", text = \"\", fg = \"#0082a2\" },\n\t{ name = \"gradle-wrapper.properties\", text = \"\", fg = \"#005f87\" },\n\t{ name = \"gradle.properties\", text = \"\", fg = \"#005f87\" },\n\t{ name = \"gradlew\", text = \"\", fg = \"#005f87\" },\n\t{ name = \"groovy\", text = \"\", fg = \"#384e5d\" },\n\t{ name = \"gruntfile.babel.js\", text = \"\", fg = \"#975122\" },\n\t{ name = \"gruntfile.coffee\", text = \"\", fg = \"#975122\" },\n\t{ name = \"gruntfile.js\", text = \"\", fg = \"#975122\" },\n\t{ name = \"gruntfile.ts\", text = \"\", fg = \"#975122\" },\n\t{ name = \"gtkrc\", text = \"\", fg = \"#333333\" },\n\t{ name = \"gulpfile.babel.js\", text = \"\", fg = \"#992e33\" },\n\t{ name = \"gulpfile.coffee\", text = \"\", fg = \"#992e33\" },\n\t{ name = \"gulpfile.js\", text = \"\", fg = \"#992e33\" },\n\t{ name = \"gulpfile.ts\", text = \"\", fg = \"#992e33\" },\n\t{ name = \"hypridle.conf\", text = \"\", fg = \"#008082\" },\n\t{ name = \"hyprland.conf\", text = \"\", fg = \"#008082\" },\n\t{ name = \"hyprlandd.conf\", text = \"\", fg = \"#008082\" },\n\t{ name = \"hyprlock.conf\", text = \"\", fg = \"#008082\" },\n\t{ name = \"hyprpaper.conf\", text = \"\", fg = \"#008082\" },\n\t{ name = \"hyprsunset.conf\", text = \"\", fg = \"#008082\" },\n\t{ name = \"i18n.config.js\", text = \"󰗊\", fg = \"#515987\" },\n\t{ name = \"i18n.config.ts\", text = \"󰗊\", fg = \"#515987\" },\n\t{ name = \"i3blocks.conf\", text = \"\", fg = \"#2e2f30\" },\n\t{ name = \"i3status.conf\", text = \"\", fg = \"#2e2f30\" },\n\t{ name = \"index.theme\", text = \"\", fg = \"#1e7b4a\" },\n\t{ name = \"ionic.config.json\", text = \"\", fg = \"#355fa5\" },\n\t{ name = \"Jenkinsfile\", text = \"\", fg = \"#9e2a26\" },\n\t{ name = \"justfile\", text = \"\", fg = \"#526064\" },\n\t{ name = \"kalgebrarc\", text = \"\", fg = \"#1573b6\" },\n\t{ name = \"kdeglobals\", text = \"\", fg = \"#1573b6\" },\n\t{ name = \"kdenlive-layoutsrc\", text = \"\", fg = \"#425c79\" },\n\t{ name = \"kdenliverc\", text = \"\", fg = \"#425c79\" },\n\t{ name = \"kritadisplayrc\", text = \"\", fg = \"#a12ea7\" },\n\t{ name = \"kritarc\", text = \"\", fg = \"#a12ea7\" },\n\t{ name = \"license\", text = \"\", fg = \"#686020\" },\n\t{ name = \"license.md\", text = \"\", fg = \"#686020\" },\n\t{ name = \"lxde-rc.xml\", text = \"\", fg = \"#606060\" },\n\t{ name = \"lxqt.conf\", text = \"\", fg = \"#016e9e\" },\n\t{ name = \"makefile\", text = \"\", fg = \"#526064\" },\n\t{ name = \"mix.lock\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"mpv.conf\", text = \"\", fg = \"#3b1342\" },\n\t{ name = \"next.config.cjs\", text = \"\", fg = \"#333333\" },\n\t{ name = \"next.config.js\", text = \"\", fg = \"#333333\" },\n\t{ name = \"next.config.ts\", text = \"\", fg = \"#333333\" },\n\t{ name = \"node_modules\", text = \"\", fg = \"#ae1d38\" },\n\t{ name = \"nuxt.config.cjs\", text = \"󱄆\", fg = \"#00835f\" },\n\t{ name = \"nuxt.config.js\", text = \"󱄆\", fg = \"#00835f\" },\n\t{ name = \"nuxt.config.mjs\", text = \"󱄆\", fg = \"#00835f\" },\n\t{ name = \"nuxt.config.ts\", text = \"󱄆\", fg = \"#00835f\" },\n\t{ name = \"package-lock.json\", text = \"\", fg = \"#7a0d21\" },\n\t{ name = \"package.json\", text = \"\", fg = \"#ae1d38\" },\n\t{ name = \"PKGBUILD\", text = \"\", fg = \"#0b6f9e\" },\n\t{ name = \"platformio.ini\", text = \"\", fg = \"#a4571d\" },\n\t{ name = \"playwright.config.cjs\", text = \"\", fg = \"#238226\" },\n\t{ name = \"playwright.config.cts\", text = \"\", fg = \"#238226\" },\n\t{ name = \"playwright.config.js\", text = \"\", fg = \"#238226\" },\n\t{ name = \"playwright.config.mjs\", text = \"\", fg = \"#238226\" },\n\t{ name = \"playwright.config.mts\", text = \"\", fg = \"#238226\" },\n\t{ name = \"playwright.config.ts\", text = \"\", fg = \"#238226\" },\n\t{ name = \"pnpm-lock.yaml\", text = \"\", fg = \"#7c5601\" },\n\t{ name = \"pnpm-workspace.yaml\", text = \"\", fg = \"#7c5601\" },\n\t{ name = \"pom.xml\", text = \"\", fg = \"#7a0d21\" },\n\t{ name = \"prettier.config.cjs\", text = \"\", fg = \"#3264b7\" },\n\t{ name = \"prettier.config.js\", text = \"\", fg = \"#3264b7\" },\n\t{ name = \"prettier.config.mjs\", text = \"\", fg = \"#3264b7\" },\n\t{ name = \"prettier.config.ts\", text = \"\", fg = \"#3264b7\" },\n\t{ name = \"prisma.config.mts\", text = \"\", fg = \"#444da2\" },\n\t{ name = \"prisma.config.ts\", text = \"\", fg = \"#444da2\" },\n\t{ name = \"procfile\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"PrusaSlicer.ini\", text = \"\", fg = \"#9d4717\" },\n\t{ name = \"PrusaSlicerGcodeViewer.ini\", text = \"\", fg = \"#9d4717\" },\n\t{ name = \"py.typed\", text = \"\", fg = \"#805e02\" },\n\t{ name = \"QtProject.conf\", text = \"\", fg = \"#2b8937\" },\n\t{ name = \"rakefile\", text = \"\", fg = \"#701516\" },\n\t{ name = \"readme\", text = \"󰂺\", fg = \"#2f2f2f\" },\n\t{ name = \"readme.md\", text = \"󰂺\", fg = \"#2f2f2f\" },\n\t{ name = \"rmd\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"robots.txt\", text = \"󰚩\", fg = \"#465470\" },\n\t{ name = \"security\", text = \"󰒃\", fg = \"#3f4143\" },\n\t{ name = \"security.md\", text = \"󰒃\", fg = \"#3f4143\" },\n\t{ name = \"settings.gradle\", text = \"\", fg = \"#005f87\" },\n\t{ name = \"svelte.config.js\", text = \"\", fg = \"#bf2e00\" },\n\t{ name = \"sxhkdrc\", text = \"\", fg = \"#2f2f2f\" },\n\t{ name = \"sym-lib-table\", text = \"\", fg = \"#333333\" },\n\t{ name = \"tailwind.config.js\", text = \"󱏿\", fg = \"#158197\" },\n\t{ name = \"tailwind.config.mjs\", text = \"󱏿\", fg = \"#158197\" },\n\t{ name = \"tailwind.config.ts\", text = \"󱏿\", fg = \"#158197\" },\n\t{ name = \"tmux.conf\", text = \"\", fg = \"#0f8c13\" },\n\t{ name = \"tmux.conf.local\", text = \"\", fg = \"#0f8c13\" },\n\t{ name = \"tsconfig.json\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"unlicense\", text = \"\", fg = \"#686020\" },\n\t{ name = \"vagrantfile\", text = \"\", fg = \"#104abf\" },\n\t{ name = \"vercel.json\", text = \"\", fg = \"#333333\" },\n\t{ name = \"vite.config.cjs\", text = \"\", fg = \"#805400\" },\n\t{ name = \"vite.config.cts\", text = \"\", fg = \"#805400\" },\n\t{ name = \"vite.config.js\", text = \"\", fg = \"#805400\" },\n\t{ name = \"vite.config.mjs\", text = \"\", fg = \"#805400\" },\n\t{ name = \"vite.config.mts\", text = \"\", fg = \"#805400\" },\n\t{ name = \"vite.config.ts\", text = \"\", fg = \"#805400\" },\n\t{ name = \"vitest.config.cjs\", text = \"\", fg = \"#4d6712\" },\n\t{ name = \"vitest.config.cts\", text = \"\", fg = \"#4d6712\" },\n\t{ name = \"vitest.config.js\", text = \"\", fg = \"#4d6712\" },\n\t{ name = \"vitest.config.mjs\", text = \"\", fg = \"#4d6712\" },\n\t{ name = \"vitest.config.mts\", text = \"\", fg = \"#4d6712\" },\n\t{ name = \"vitest.config.ts\", text = \"\", fg = \"#4d6712\" },\n\t{ name = \"vlcrc\", text = \"󰕼\", fg = \"#9f5100\" },\n\t{ name = \"webpack\", text = \"󰜫\", fg = \"#36677c\" },\n\t{ name = \"weston.ini\", text = \"\", fg = \"#805e00\" },\n\t{ name = \"workspace\", text = \"\", fg = \"#447028\" },\n\t{ name = \"wrangler.jsonc\", text = \"\", fg = \"#a35615\" },\n\t{ name = \"wrangler.toml\", text = \"\", fg = \"#a35615\" },\n\t{ name = \"xdph.conf\", text = \"\", fg = \"#008082\" },\n\t{ name = \"xmobarrc\", text = \"\", fg = \"#a9333e\" },\n\t{ name = \"xmobarrc.hs\", text = \"\", fg = \"#a9333e\" },\n\t{ name = \"xmonad.hs\", text = \"\", fg = \"#a9333e\" },\n\t{ name = \"xorg.conf\", text = \"\", fg = \"#ac3a12\" },\n\t{ name = \"xsettingsd.conf\", text = \"\", fg = \"#ac3a12\" },\n]\nexts = [\n\t{ name = \"3gp\", text = \"\", fg = \"#7e4c10\" },\n\t{ name = \"3mf\", text = \"󰆧\", fg = \"#5b5b5b\" },\n\t{ name = \"7z\", text = \"\", fg = \"#76520c\" },\n\t{ name = \"a\", text = \"\", fg = \"#494a47\" },\n\t{ name = \"aac\", text = \"\", fg = \"#0075aa\" },\n\t{ name = \"ada\", text = \"\", fg = \"#3b69aa\" },\n\t{ name = \"adb\", text = \"\", fg = \"#3b69aa\" },\n\t{ name = \"ads\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"ai\", text = \"\", fg = \"#666620\" },\n\t{ name = \"aif\", text = \"\", fg = \"#0075aa\" },\n\t{ name = \"aiff\", text = \"\", fg = \"#0075aa\" },\n\t{ name = \"android\", text = \"\", fg = \"#277e3e\" },\n\t{ name = \"ape\", text = \"\", fg = \"#0075aa\" },\n\t{ name = \"apk\", text = \"\", fg = \"#277e3e\" },\n\t{ name = \"apl\", text = \"\", fg = \"#1b7936\" },\n\t{ name = \"app\", text = \"\", fg = \"#9f0500\" },\n\t{ name = \"applescript\", text = \"\", fg = \"#526064\" },\n\t{ name = \"asc\", text = \"󰦝\", fg = \"#41525f\" },\n\t{ name = \"asm\", text = \"\", fg = \"#006d8e\" },\n\t{ name = \"ass\", text = \"󰨖\", fg = \"#805c0a\" },\n\t{ name = \"astro\", text = \"\", fg = \"#aa2f4d\" },\n\t{ name = \"avif\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"awk\", text = \"\", fg = \"#3a4446\" },\n\t{ name = \"azcli\", text = \"\", fg = \"#005a9f\" },\n\t{ name = \"bak\", text = \"󰁯\", fg = \"#526064\" },\n\t{ name = \"bash\", text = \"\", fg = \"#447028\" },\n\t{ name = \"bat\", text = \"\", fg = \"#40500f\" },\n\t{ name = \"bazel\", text = \"\", fg = \"#447028\" },\n\t{ name = \"bib\", text = \"󱉟\", fg = \"#666620\" },\n\t{ name = \"bicep\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"bicepparam\", text = \"\", fg = \"#6a4d77\" },\n\t{ name = \"bin\", text = \"\", fg = \"#9f0500\" },\n\t{ name = \"blade.php\", text = \"\", fg = \"#a0372b\" },\n\t{ name = \"blend\", text = \"󰂫\", fg = \"#9c4f00\" },\n\t{ name = \"blp\", text = \"󰺾\", fg = \"#3a6497\" },\n\t{ name = \"bmp\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"bqn\", text = \"\", fg = \"#1b7936\" },\n\t{ name = \"brep\", text = \"󰻫\", fg = \"#576342\" },\n\t{ name = \"bz\", text = \"\", fg = \"#76520c\" },\n\t{ name = \"bz2\", text = \"\", fg = \"#76520c\" },\n\t{ name = \"bz3\", text = \"\", fg = \"#76520c\" },\n\t{ name = \"bzl\", text = \"\", fg = \"#447028\" },\n\t{ name = \"c\", text = \"\", fg = \"#3b69aa\" },\n\t{ name = \"c++\", text = \"\", fg = \"#a23253\" },\n\t{ name = \"cache\", text = \"\", fg = \"#333333\" },\n\t{ name = \"cast\", text = \"\", fg = \"#7e4c10\" },\n\t{ name = \"cbl\", text = \"\", fg = \"#005ca5\" },\n\t{ name = \"cc\", text = \"\", fg = \"#a23253\" },\n\t{ name = \"ccm\", text = \"\", fg = \"#a23253\" },\n\t{ name = \"cfc\", text = \"\", fg = \"#017b8c\" },\n\t{ name = \"cfg\", text = \"\", fg = \"#526064\" },\n\t{ name = \"cfm\", text = \"\", fg = \"#017b8c\" },\n\t{ name = \"cjs\", text = \"\", fg = \"#666620\" },\n\t{ name = \"clj\", text = \"\", fg = \"#466024\" },\n\t{ name = \"cljc\", text = \"\", fg = \"#466024\" },\n\t{ name = \"cljd\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"cljs\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"cmake\", text = \"\", fg = \"#2c2d2f\" },\n\t{ name = \"cob\", text = \"\", fg = \"#005ca5\" },\n\t{ name = \"cobol\", text = \"\", fg = \"#005ca5\" },\n\t{ name = \"coffee\", text = \"\", fg = \"#666620\" },\n\t{ name = \"conda\", text = \"\", fg = \"#2d751c\" },\n\t{ name = \"conf\", text = \"\", fg = \"#526064\" },\n\t{ name = \"config.ru\", text = \"\", fg = \"#701516\" },\n\t{ name = \"cow\", text = \"󰆚\", fg = \"#70421b\" },\n\t{ name = \"cp\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"cpp\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"cppm\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"cpy\", text = \"\", fg = \"#005ca5\" },\n\t{ name = \"cr\", text = \"\", fg = \"#434343\" },\n\t{ name = \"crdownload\", text = \"\", fg = \"#226654\" },\n\t{ name = \"cs\", text = \"󰌛\", fg = \"#434d04\" },\n\t{ name = \"csh\", text = \"\", fg = \"#3a4446\" },\n\t{ name = \"cshtml\", text = \"󱦗\", fg = \"#512bd4\" },\n\t{ name = \"cson\", text = \"\", fg = \"#666620\" },\n\t{ name = \"csproj\", text = \"󰪮\", fg = \"#512bd4\" },\n\t{ name = \"css\", text = \"\", fg = \"#663399\" },\n\t{ name = \"csv\", text = \"\", fg = \"#447028\" },\n\t{ name = \"cts\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"cu\", text = \"\", fg = \"#447028\" },\n\t{ name = \"cue\", text = \"󰲹\", fg = \"#764a57\" },\n\t{ name = \"cuh\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"cxx\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"cxxm\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"d\", text = \"\", fg = \"#842b25\" },\n\t{ name = \"d.ts\", text = \"\", fg = \"#6a4c2a\" },\n\t{ name = \"dart\", text = \"\", fg = \"#03589c\" },\n\t{ name = \"db\", text = \"\", fg = \"#494848\" },\n\t{ name = \"dconf\", text = \"\", fg = \"#333333\" },\n\t{ name = \"desktop\", text = \"\", fg = \"#563d7c\" },\n\t{ name = \"diff\", text = \"\", fg = \"#41535b\" },\n\t{ name = \"dll\", text = \"\", fg = \"#4d2c0b\" },\n\t{ name = \"doc\", text = \"󰈬\", fg = \"#185abd\" },\n\t{ name = \"Dockerfile\", text = \"󰡨\", fg = \"#2e5f99\" },\n\t{ name = \"dockerignore\", text = \"󰡨\", fg = \"#2e5f99\" },\n\t{ name = \"docx\", text = \"󰈬\", fg = \"#185abd\" },\n\t{ name = \"dot\", text = \"󱁉\", fg = \"#244a6a\" },\n\t{ name = \"download\", text = \"\", fg = \"#226654\" },\n\t{ name = \"drl\", text = \"\", fg = \"#553a3a\" },\n\t{ name = \"dropbox\", text = \"\", fg = \"#0049be\" },\n\t{ name = \"dump\", text = \"\", fg = \"#494848\" },\n\t{ name = \"dwg\", text = \"󰻫\", fg = \"#576342\" },\n\t{ name = \"dxf\", text = \"󰻫\", fg = \"#576342\" },\n\t{ name = \"ebook\", text = \"\", fg = \"#755836\" },\n\t{ name = \"ebuild\", text = \"\", fg = \"#4c416e\" },\n\t{ name = \"edn\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"eex\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"ejs\", text = \"\", fg = \"#666620\" },\n\t{ name = \"el\", text = \"\", fg = \"#61568e\" },\n\t{ name = \"elc\", text = \"\", fg = \"#61568e\" },\n\t{ name = \"elf\", text = \"\", fg = \"#9f0500\" },\n\t{ name = \"elm\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"eln\", text = \"\", fg = \"#61568e\" },\n\t{ name = \"env\", text = \"\", fg = \"#32310d\" },\n\t{ name = \"eot\", text = \"\", fg = \"#2f2f2f\" },\n\t{ name = \"epp\", text = \"\", fg = \"#80530d\" },\n\t{ name = \"epub\", text = \"\", fg = \"#755836\" },\n\t{ name = \"erb\", text = \"\", fg = \"#701516\" },\n\t{ name = \"erl\", text = \"\", fg = \"#8a2b72\" },\n\t{ name = \"ex\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"exe\", text = \"\", fg = \"#9f0500\" },\n\t{ name = \"exs\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"f#\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"f3d\", text = \"󰻫\", fg = \"#576342\" },\n\t{ name = \"f90\", text = \"󱈚\", fg = \"#563b70\" },\n\t{ name = \"fbx\", text = \"󰆧\", fg = \"#5b5b5b\" },\n\t{ name = \"fcbak\", text = \"\", fg = \"#98262c\" },\n\t{ name = \"fcmacro\", text = \"\", fg = \"#98262c\" },\n\t{ name = \"fcmat\", text = \"\", fg = \"#98262c\" },\n\t{ name = \"fcparam\", text = \"\", fg = \"#98262c\" },\n\t{ name = \"fcscript\", text = \"\", fg = \"#98262c\" },\n\t{ name = \"fcstd\", text = \"\", fg = \"#98262c\" },\n\t{ name = \"fcstd1\", text = \"\", fg = \"#98262c\" },\n\t{ name = \"fctb\", text = \"\", fg = \"#98262c\" },\n\t{ name = \"fctl\", text = \"\", fg = \"#98262c\" },\n\t{ name = \"fdmdownload\", text = \"\", fg = \"#226654\" },\n\t{ name = \"feature\", text = \"\", fg = \"#007e12\" },\n\t{ name = \"fish\", text = \"\", fg = \"#3a4446\" },\n\t{ name = \"flac\", text = \"\", fg = \"#005880\" },\n\t{ name = \"flc\", text = \"\", fg = \"#2f2f2f\" },\n\t{ name = \"flf\", text = \"\", fg = \"#2f2f2f\" },\n\t{ name = \"fnl\", text = \"\", fg = \"#33312b\" },\n\t{ name = \"fodg\", text = \"\", fg = \"#333211\" },\n\t{ name = \"fodp\", text = \"\", fg = \"#7f4e22\" },\n\t{ name = \"fods\", text = \"\", fg = \"#28541a\" },\n\t{ name = \"fodt\", text = \"\", fg = \"#16667e\" },\n\t{ name = \"frag\", text = \"\", fg = \"#40647c\" },\n\t{ name = \"fs\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"fsi\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"fsscript\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"fsx\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"gcode\", text = \"󰐫\", fg = \"#0f5582\" },\n\t{ name = \"gd\", text = \"\", fg = \"#526064\" },\n\t{ name = \"gemspec\", text = \"\", fg = \"#701516\" },\n\t{ name = \"geom\", text = \"\", fg = \"#40647c\" },\n\t{ name = \"gif\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"git\", text = \"\", fg = \"#b5391e\" },\n\t{ name = \"glb\", text = \"\", fg = \"#80581e\" },\n\t{ name = \"gleam\", text = \"\", fg = \"#553a51\" },\n\t{ name = \"glsl\", text = \"\", fg = \"#40647c\" },\n\t{ name = \"gnumakefile\", text = \"\", fg = \"#526064\" },\n\t{ name = \"go\", text = \"\", fg = \"#0082a2\" },\n\t{ name = \"godot\", text = \"\", fg = \"#526064\" },\n\t{ name = \"gpr\", text = \"\", fg = \"#526064\" },\n\t{ name = \"gql\", text = \"\", fg = \"#ac2880\" },\n\t{ name = \"gradle\", text = \"\", fg = \"#005f87\" },\n\t{ name = \"graphql\", text = \"\", fg = \"#ac2880\" },\n\t{ name = \"gresource\", text = \"\", fg = \"#333333\" },\n\t{ name = \"gv\", text = \"󱁉\", fg = \"#244a6a\" },\n\t{ name = \"gz\", text = \"\", fg = \"#76520c\" },\n\t{ name = \"h\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"haml\", text = \"\", fg = \"#2f2f2d\" },\n\t{ name = \"hbs\", text = \"\", fg = \"#a04f1d\" },\n\t{ name = \"heex\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"hex\", text = \"\", fg = \"#224abf\" },\n\t{ name = \"hh\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"hpp\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"hrl\", text = \"\", fg = \"#8a2b72\" },\n\t{ name = \"hs\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"htm\", text = \"\", fg = \"#aa391c\" },\n\t{ name = \"html\", text = \"\", fg = \"#ab3a1c\" },\n\t{ name = \"http\", text = \"\", fg = \"#006a95\" },\n\t{ name = \"huff\", text = \"󰡘\", fg = \"#4242c7\" },\n\t{ name = \"hurl\", text = \"\", fg = \"#bf0266\" },\n\t{ name = \"hx\", text = \"\", fg = \"#9c5715\" },\n\t{ name = \"hxx\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"ical\", text = \"\", fg = \"#2b2e83\" },\n\t{ name = \"icalendar\", text = \"\", fg = \"#2b2e83\" },\n\t{ name = \"ico\", text = \"\", fg = \"#666620\" },\n\t{ name = \"ics\", text = \"\", fg = \"#2b2e83\" },\n\t{ name = \"ifb\", text = \"\", fg = \"#2b2e83\" },\n\t{ name = \"ifc\", text = \"󰻫\", fg = \"#576342\" },\n\t{ name = \"ige\", text = \"󰻫\", fg = \"#576342\" },\n\t{ name = \"iges\", text = \"󰻫\", fg = \"#576342\" },\n\t{ name = \"igs\", text = \"󰻫\", fg = \"#576342\" },\n\t{ name = \"image\", text = \"\", fg = \"#453f43\" },\n\t{ name = \"img\", text = \"\", fg = \"#453f43\" },\n\t{ name = \"import\", text = \"\", fg = \"#2f2f2f\" },\n\t{ name = \"info\", text = \"\", fg = \"#333329\" },\n\t{ name = \"ini\", text = \"\", fg = \"#526064\" },\n\t{ name = \"ino\", text = \"\", fg = \"#397981\" },\n\t{ name = \"ipynb\", text = \"\", fg = \"#a35301\" },\n\t{ name = \"iso\", text = \"\", fg = \"#453f43\" },\n\t{ name = \"ixx\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"jar\", text = \"\", fg = \"#805834\" },\n\t{ name = \"java\", text = \"\", fg = \"#992e33\" },\n\t{ name = \"jl\", text = \"\", fg = \"#6c4b7c\" },\n\t{ name = \"jpeg\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"jpg\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"js\", text = \"\", fg = \"#666620\" },\n\t{ name = \"json\", text = \"\", fg = \"#666620\" },\n\t{ name = \"json5\", text = \"\", fg = \"#666620\" },\n\t{ name = \"jsonc\", text = \"\", fg = \"#666620\" },\n\t{ name = \"jsx\", text = \"\", fg = \"#158197\" },\n\t{ name = \"jwmrc\", text = \"\", fg = \"#005a9a\" },\n\t{ name = \"jxl\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"kbx\", text = \"󰯄\", fg = \"#565856\" },\n\t{ name = \"kdb\", text = \"\", fg = \"#3e7427\" },\n\t{ name = \"kdbx\", text = \"\", fg = \"#3e7427\" },\n\t{ name = \"kdenlive\", text = \"\", fg = \"#425c79\" },\n\t{ name = \"kdenlivetitle\", text = \"\", fg = \"#425c79\" },\n\t{ name = \"kicad_dru\", text = \"\", fg = \"#333333\" },\n\t{ name = \"kicad_mod\", text = \"\", fg = \"#333333\" },\n\t{ name = \"kicad_pcb\", text = \"\", fg = \"#333333\" },\n\t{ name = \"kicad_prl\", text = \"\", fg = \"#333333\" },\n\t{ name = \"kicad_pro\", text = \"\", fg = \"#333333\" },\n\t{ name = \"kicad_sch\", text = \"\", fg = \"#333333\" },\n\t{ name = \"kicad_sym\", text = \"\", fg = \"#333333\" },\n\t{ name = \"kicad_wks\", text = \"\", fg = \"#333333\" },\n\t{ name = \"ko\", text = \"\", fg = \"#494a47\" },\n\t{ name = \"kpp\", text = \"\", fg = \"#a12ea7\" },\n\t{ name = \"kra\", text = \"\", fg = \"#a12ea7\" },\n\t{ name = \"krz\", text = \"\", fg = \"#a12ea7\" },\n\t{ name = \"ksh\", text = \"\", fg = \"#3a4446\" },\n\t{ name = \"kt\", text = \"\", fg = \"#5f3ebf\" },\n\t{ name = \"kts\", text = \"\", fg = \"#5f3ebf\" },\n\t{ name = \"lck\", text = \"\", fg = \"#5e5e5e\" },\n\t{ name = \"leex\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"less\", text = \"\", fg = \"#563d7c\" },\n\t{ name = \"lff\", text = \"\", fg = \"#2f2f2f\" },\n\t{ name = \"lhs\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"lib\", text = \"\", fg = \"#4d2c0b\" },\n\t{ name = \"license\", text = \"\", fg = \"#666620\" },\n\t{ name = \"liquid\", text = \"\", fg = \"#4a6024\" },\n\t{ name = \"lock\", text = \"\", fg = \"#5e5e5e\" },\n\t{ name = \"log\", text = \"󰌱\", fg = \"#4a4a4a\" },\n\t{ name = \"lrc\", text = \"󰨖\", fg = \"#805c0a\" },\n\t{ name = \"lua\", text = \"\", fg = \"#366b8a\" },\n\t{ name = \"luac\", text = \"\", fg = \"#366b8a\" },\n\t{ name = \"luau\", text = \"\", fg = \"#007abf\" },\n\t{ name = \"m\", text = \"\", fg = \"#3b69aa\" },\n\t{ name = \"m3u\", text = \"󰲹\", fg = \"#764a57\" },\n\t{ name = \"m3u8\", text = \"󰲹\", fg = \"#764a57\" },\n\t{ name = \"m4a\", text = \"\", fg = \"#0075aa\" },\n\t{ name = \"m4v\", text = \"\", fg = \"#7e4c10\" },\n\t{ name = \"magnet\", text = \"\", fg = \"#a51b16\" },\n\t{ name = \"makefile\", text = \"\", fg = \"#526064\" },\n\t{ name = \"markdown\", text = \"\", fg = \"#4a4a4a\" },\n\t{ name = \"material\", text = \"\", fg = \"#8a2b72\" },\n\t{ name = \"md\", text = \"\", fg = \"#4a4a4a\" },\n\t{ name = \"md5\", text = \"󰕥\", fg = \"#5d5975\" },\n\t{ name = \"mdx\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"mint\", text = \"󰌪\", fg = \"#44604a\" },\n\t{ name = \"mjs\", text = \"\", fg = \"#504b1e\" },\n\t{ name = \"mk\", text = \"\", fg = \"#526064\" },\n\t{ name = \"mkv\", text = \"\", fg = \"#7e4c10\" },\n\t{ name = \"ml\", text = \"\", fg = \"#975122\" },\n\t{ name = \"mli\", text = \"\", fg = \"#975122\" },\n\t{ name = \"mm\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"mo\", text = \"\", fg = \"#654ca7\" },\n\t{ name = \"mobi\", text = \"\", fg = \"#755836\" },\n\t{ name = \"mojo\", text = \"\", fg = \"#bf3917\" },\n\t{ name = \"mov\", text = \"\", fg = \"#7e4c10\" },\n\t{ name = \"mp3\", text = \"\", fg = \"#0075aa\" },\n\t{ name = \"mp4\", text = \"\", fg = \"#7e4c10\" },\n\t{ name = \"mpp\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"msf\", text = \"\", fg = \"#0e5ca9\" },\n\t{ name = \"mts\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"mustache\", text = \"\", fg = \"#975122\" },\n\t{ name = \"nfo\", text = \"\", fg = \"#333329\" },\n\t{ name = \"nim\", text = \"\", fg = \"#514700\" },\n\t{ name = \"nix\", text = \"\", fg = \"#3f5d72\" },\n\t{ name = \"norg\", text = \"\", fg = \"#365a8e\" },\n\t{ name = \"nswag\", text = \"\", fg = \"#427516\" },\n\t{ name = \"nu\", text = \"\", fg = \"#276f4e\" },\n\t{ name = \"o\", text = \"\", fg = \"#9f0500\" },\n\t{ name = \"obj\", text = \"󰆧\", fg = \"#5b5b5b\" },\n\t{ name = \"odf\", text = \"\", fg = \"#aa3c64\" },\n\t{ name = \"odg\", text = \"\", fg = \"#333211\" },\n\t{ name = \"odin\", text = \"󰟢\", fg = \"#2a629e\" },\n\t{ name = \"odp\", text = \"\", fg = \"#7f4e22\" },\n\t{ name = \"ods\", text = \"\", fg = \"#28541a\" },\n\t{ name = \"odt\", text = \"\", fg = \"#16667e\" },\n\t{ name = \"oga\", text = \"\", fg = \"#005880\" },\n\t{ name = \"ogg\", text = \"\", fg = \"#005880\" },\n\t{ name = \"ogv\", text = \"\", fg = \"#7e4c10\" },\n\t{ name = \"ogx\", text = \"\", fg = \"#7e4c10\" },\n\t{ name = \"opus\", text = \"\", fg = \"#005880\" },\n\t{ name = \"org\", text = \"\", fg = \"#4f7166\" },\n\t{ name = \"otf\", text = \"\", fg = \"#2f2f2f\" },\n\t{ name = \"out\", text = \"\", fg = \"#9f0500\" },\n\t{ name = \"part\", text = \"\", fg = \"#226654\" },\n\t{ name = \"patch\", text = \"\", fg = \"#41535b\" },\n\t{ name = \"pck\", text = \"\", fg = \"#526064\" },\n\t{ name = \"pcm\", text = \"\", fg = \"#005880\" },\n\t{ name = \"pdf\", text = \"\", fg = \"#b30b00\" },\n\t{ name = \"php\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"pl\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"pls\", text = \"󰲹\", fg = \"#764a57\" },\n\t{ name = \"ply\", text = \"󰆧\", fg = \"#5b5b5b\" },\n\t{ name = \"pm\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"png\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"po\", text = \"\", fg = \"#1c708e\" },\n\t{ name = \"pot\", text = \"\", fg = \"#1c708e\" },\n\t{ name = \"pp\", text = \"\", fg = \"#80530d\" },\n\t{ name = \"ppt\", text = \"󰈧\", fg = \"#983826\" },\n\t{ name = \"pptx\", text = \"󰈧\", fg = \"#983826\" },\n\t{ name = \"prisma\", text = \"\", fg = \"#444da2\" },\n\t{ name = \"pro\", text = \"\", fg = \"#725c2a\" },\n\t{ name = \"ps1\", text = \"󰨊\", fg = \"#325698\" },\n\t{ name = \"psb\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"psd\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"psd1\", text = \"󰨊\", fg = \"#4f5893\" },\n\t{ name = \"psm1\", text = \"󰨊\", fg = \"#4f5893\" },\n\t{ name = \"pub\", text = \"󰷖\", fg = \"#4c422f\" },\n\t{ name = \"pxd\", text = \"\", fg = \"#3c6f98\" },\n\t{ name = \"pxi\", text = \"\", fg = \"#3c6f98\" },\n\t{ name = \"py\", text = \"\", fg = \"#805e02\" },\n\t{ name = \"pyc\", text = \"\", fg = \"#332d1d\" },\n\t{ name = \"pyd\", text = \"\", fg = \"#332d1d\" },\n\t{ name = \"pyi\", text = \"\", fg = \"#805e02\" },\n\t{ name = \"pyo\", text = \"\", fg = \"#332d1d\" },\n\t{ name = \"pyw\", text = \"\", fg = \"#3c6f98\" },\n\t{ name = \"pyx\", text = \"\", fg = \"#3c6f98\" },\n\t{ name = \"qm\", text = \"\", fg = \"#1c708e\" },\n\t{ name = \"qml\", text = \"\", fg = \"#2b8937\" },\n\t{ name = \"qrc\", text = \"\", fg = \"#2b8937\" },\n\t{ name = \"qss\", text = \"\", fg = \"#2b8937\" },\n\t{ name = \"query\", text = \"\", fg = \"#607035\" },\n\t{ name = \"R\", text = \"󰟔\", fg = \"#1a4c8c\" },\n\t{ name = \"r\", text = \"󰟔\", fg = \"#1a4c8c\" },\n\t{ name = \"rake\", text = \"\", fg = \"#701516\" },\n\t{ name = \"rar\", text = \"\", fg = \"#76520c\" },\n\t{ name = \"rasi\", text = \"\", fg = \"#666620\" },\n\t{ name = \"razor\", text = \"󱦘\", fg = \"#512bd4\" },\n\t{ name = \"rb\", text = \"\", fg = \"#701516\" },\n\t{ name = \"res\", text = \"\", fg = \"#992e33\" },\n\t{ name = \"resi\", text = \"\", fg = \"#a33759\" },\n\t{ name = \"rlib\", text = \"\", fg = \"#6f5242\" },\n\t{ name = \"rmd\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"rproj\", text = \"󰗆\", fg = \"#286844\" },\n\t{ name = \"rs\", text = \"\", fg = \"#6f5242\" },\n\t{ name = \"rss\", text = \"\", fg = \"#7e4e1e\" },\n\t{ name = \"s\", text = \"\", fg = \"#005594\" },\n\t{ name = \"sass\", text = \"\", fg = \"#a33759\" },\n\t{ name = \"sbt\", text = \"\", fg = \"#992e33\" },\n\t{ name = \"sc\", text = \"\", fg = \"#992e33\" },\n\t{ name = \"scad\", text = \"\", fg = \"#53480f\" },\n\t{ name = \"scala\", text = \"\", fg = \"#992e33\" },\n\t{ name = \"scm\", text = \"󰘧\", fg = \"#303030\" },\n\t{ name = \"scss\", text = \"\", fg = \"#a33759\" },\n\t{ name = \"sh\", text = \"\", fg = \"#3a4446\" },\n\t{ name = \"sha1\", text = \"󰕥\", fg = \"#5d5975\" },\n\t{ name = \"sha224\", text = \"󰕥\", fg = \"#5d5975\" },\n\t{ name = \"sha256\", text = \"󰕥\", fg = \"#5d5975\" },\n\t{ name = \"sha384\", text = \"󰕥\", fg = \"#5d5975\" },\n\t{ name = \"sha512\", text = \"󰕥\", fg = \"#5d5975\" },\n\t{ name = \"sig\", text = \"󰘧\", fg = \"#975122\" },\n\t{ name = \"signature\", text = \"󰘧\", fg = \"#975122\" },\n\t{ name = \"skp\", text = \"󰻫\", fg = \"#576342\" },\n\t{ name = \"sldasm\", text = \"󰻫\", fg = \"#576342\" },\n\t{ name = \"sldprt\", text = \"󰻫\", fg = \"#576342\" },\n\t{ name = \"slim\", text = \"\", fg = \"#aa391c\" },\n\t{ name = \"sln\", text = \"\", fg = \"#643995\" },\n\t{ name = \"slnx\", text = \"\", fg = \"#643995\" },\n\t{ name = \"slvs\", text = \"󰻫\", fg = \"#576342\" },\n\t{ name = \"sml\", text = \"󰘧\", fg = \"#975122\" },\n\t{ name = \"so\", text = \"\", fg = \"#494a47\" },\n\t{ name = \"sol\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"spec.js\", text = \"\", fg = \"#666620\" },\n\t{ name = \"spec.jsx\", text = \"\", fg = \"#158197\" },\n\t{ name = \"spec.ts\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"spec.tsx\", text = \"\", fg = \"#1354bf\" },\n\t{ name = \"spx\", text = \"\", fg = \"#005880\" },\n\t{ name = \"sql\", text = \"\", fg = \"#494848\" },\n\t{ name = \"sqlite\", text = \"\", fg = \"#494848\" },\n\t{ name = \"sqlite3\", text = \"\", fg = \"#494848\" },\n\t{ name = \"srt\", text = \"󰨖\", fg = \"#805c0a\" },\n\t{ name = \"ssa\", text = \"󰨖\", fg = \"#805c0a\" },\n\t{ name = \"ste\", text = \"󰻫\", fg = \"#576342\" },\n\t{ name = \"step\", text = \"󰻫\", fg = \"#576342\" },\n\t{ name = \"stl\", text = \"󰆧\", fg = \"#5b5b5b\" },\n\t{ name = \"stories.js\", text = \"\", fg = \"#aa2f59\" },\n\t{ name = \"stories.jsx\", text = \"\", fg = \"#aa2f59\" },\n\t{ name = \"stories.mjs\", text = \"\", fg = \"#aa2f59\" },\n\t{ name = \"stories.svelte\", text = \"\", fg = \"#aa2f59\" },\n\t{ name = \"stories.ts\", text = \"\", fg = \"#aa2f59\" },\n\t{ name = \"stories.tsx\", text = \"\", fg = \"#aa2f59\" },\n\t{ name = \"stories.vue\", text = \"\", fg = \"#aa2f59\" },\n\t{ name = \"stp\", text = \"󰻫\", fg = \"#576342\" },\n\t{ name = \"strings\", text = \"\", fg = \"#1c708e\" },\n\t{ name = \"styl\", text = \"\", fg = \"#466024\" },\n\t{ name = \"sub\", text = \"󰨖\", fg = \"#805c0a\" },\n\t{ name = \"sublime\", text = \"\", fg = \"#975122\" },\n\t{ name = \"suo\", text = \"\", fg = \"#643995\" },\n\t{ name = \"sv\", text = \"󰍛\", fg = \"#017226\" },\n\t{ name = \"svelte\", text = \"\", fg = \"#bf2e00\" },\n\t{ name = \"svg\", text = \"󰜡\", fg = \"#80581e\" },\n\t{ name = \"svgz\", text = \"󰜡\", fg = \"#80581e\" },\n\t{ name = \"svh\", text = \"󰍛\", fg = \"#017226\" },\n\t{ name = \"swift\", text = \"\", fg = \"#975122\" },\n\t{ name = \"t\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"tbc\", text = \"󰛓\", fg = \"#1e5cb3\" },\n\t{ name = \"tcl\", text = \"󰛓\", fg = \"#1e5cb3\" },\n\t{ name = \"templ\", text = \"\", fg = \"#6e5e18\" },\n\t{ name = \"terminal\", text = \"\", fg = \"#217929\" },\n\t{ name = \"test.js\", text = \"\", fg = \"#666620\" },\n\t{ name = \"test.jsx\", text = \"\", fg = \"#158197\" },\n\t{ name = \"test.ts\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"test.tsx\", text = \"\", fg = \"#1354bf\" },\n\t{ name = \"tex\", text = \"\", fg = \"#3d6117\" },\n\t{ name = \"tf\", text = \"\", fg = \"#4732af\" },\n\t{ name = \"tfvars\", text = \"\", fg = \"#4732af\" },\n\t{ name = \"tgz\", text = \"\", fg = \"#76520c\" },\n\t{ name = \"tmpl\", text = \"\", fg = \"#6e5e18\" },\n\t{ name = \"tmux\", text = \"\", fg = \"#0f8c13\" },\n\t{ name = \"toml\", text = \"\", fg = \"#753219\" },\n\t{ name = \"torrent\", text = \"\", fg = \"#226654\" },\n\t{ name = \"tres\", text = \"\", fg = \"#526064\" },\n\t{ name = \"ts\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"tscn\", text = \"\", fg = \"#526064\" },\n\t{ name = \"tsconfig\", text = \"\", fg = \"#aa5a00\" },\n\t{ name = \"tsx\", text = \"\", fg = \"#1354bf\" },\n\t{ name = \"ttf\", text = \"\", fg = \"#2f2f2f\" },\n\t{ name = \"twig\", text = \"\", fg = \"#466024\" },\n\t{ name = \"txt\", text = \"󰈙\", fg = \"#447028\" },\n\t{ name = \"txz\", text = \"\", fg = \"#76520c\" },\n\t{ name = \"typ\", text = \"\", fg = \"#097d80\" },\n\t{ name = \"typoscript\", text = \"\", fg = \"#aa5a00\" },\n\t{ name = \"ui\", text = \"\", fg = \"#015bf0\" },\n\t{ name = \"v\", text = \"󰍛\", fg = \"#017226\" },\n\t{ name = \"vala\", text = \"\", fg = \"#5c2e8b\" },\n\t{ name = \"vert\", text = \"\", fg = \"#40647c\" },\n\t{ name = \"vh\", text = \"󰍛\", fg = \"#017226\" },\n\t{ name = \"vhd\", text = \"󰍛\", fg = \"#017226\" },\n\t{ name = \"vhdl\", text = \"󰍛\", fg = \"#017226\" },\n\t{ name = \"vi\", text = \"\", fg = \"#554203\" },\n\t{ name = \"vim\", text = \"\", fg = \"#017226\" },\n\t{ name = \"vsh\", text = \"\", fg = \"#3e5a7f\" },\n\t{ name = \"vsix\", text = \"\", fg = \"#643995\" },\n\t{ name = \"vue\", text = \"\", fg = \"#466024\" },\n\t{ name = \"wasm\", text = \"\", fg = \"#4539a4\" },\n\t{ name = \"wav\", text = \"\", fg = \"#0075aa\" },\n\t{ name = \"webm\", text = \"\", fg = \"#7e4c10\" },\n\t{ name = \"webmanifest\", text = \"\", fg = \"#504b1e\" },\n\t{ name = \"webp\", text = \"\", fg = \"#6b4d83\" },\n\t{ name = \"webpack\", text = \"󰜫\", fg = \"#36677c\" },\n\t{ name = \"wma\", text = \"\", fg = \"#0075aa\" },\n\t{ name = \"wmv\", text = \"\", fg = \"#7e4c10\" },\n\t{ name = \"woff\", text = \"\", fg = \"#2f2f2f\" },\n\t{ name = \"woff2\", text = \"\", fg = \"#2f2f2f\" },\n\t{ name = \"wrl\", text = \"󰆧\", fg = \"#5b5b5b\" },\n\t{ name = \"wrz\", text = \"󰆧\", fg = \"#5b5b5b\" },\n\t{ name = \"wv\", text = \"\", fg = \"#0075aa\" },\n\t{ name = \"wvc\", text = \"\", fg = \"#0075aa\" },\n\t{ name = \"x\", text = \"\", fg = \"#3b69aa\" },\n\t{ name = \"xaml\", text = \"󰙳\", fg = \"#512bd4\" },\n\t{ name = \"xcf\", text = \"\", fg = \"#4a4434\" },\n\t{ name = \"xcplayground\", text = \"\", fg = \"#975122\" },\n\t{ name = \"xcstrings\", text = \"\", fg = \"#1c708e\" },\n\t{ name = \"xls\", text = \"󰈛\", fg = \"#207245\" },\n\t{ name = \"xlsx\", text = \"󰈛\", fg = \"#207245\" },\n\t{ name = \"xm\", text = \"\", fg = \"#36677c\" },\n\t{ name = \"xml\", text = \"󰗀\", fg = \"#975122\" },\n\t{ name = \"xpi\", text = \"\", fg = \"#bf1401\" },\n\t{ name = \"xslt\", text = \"󰗀\", fg = \"#227193\" },\n\t{ name = \"xul\", text = \"\", fg = \"#975122\" },\n\t{ name = \"xz\", text = \"\", fg = \"#76520c\" },\n\t{ name = \"yaml\", text = \"\", fg = \"#526064\" },\n\t{ name = \"yml\", text = \"\", fg = \"#526064\" },\n\t{ name = \"zig\", text = \"\", fg = \"#7b4d0e\" },\n\t{ name = \"zip\", text = \"\", fg = \"#76520c\" },\n\t{ name = \"zsh\", text = \"\", fg = \"#447028\" },\n\t{ name = \"zst\", text = \"\", fg = \"#76520c\" },\n\t{ name = \"🔥\", text = \"\", fg = \"#bf3917\" },\n]\nconds = [\n\t# Special files\n\t{ if = \"orphan\", text = \"\", fg = \"#000000\" },\n\t{ if = \"link\", text = \"\", fg = \"#9e9e9e\" },\n\t{ if = \"block\", text = \"\", fg = \"#ffc107\" },\n\t{ if = \"char\", text = \"\", fg = \"#ffc107\" },\n\t{ if = \"fifo\", text = \"\", fg = \"#ffc107\" },\n\t{ if = \"sock\", text = \"\", fg = \"#ffc107\" },\n\t{ if = \"sticky\", text = \"\", fg = \"#ffc107\" },\n\t{ if = \"dummy\", text = \"\", fg = \"#f44336\" },\n\n\t# Fallback\n\t{ if = \"dir & hovered\", text = \"\", fg = \"#03a9f4\" },\n\t{ if = \"dir\", text = \"\", fg = \"#03a9f4\" },\n\t{ if = \"exec\", text = \"\", fg = \"#8bc34a\" },\n\t{ if = \"!dir\", text = \"\", fg = \"#000000\" },\n]\n\n# : }}}\n"
  },
  {
    "path": "yazi-config/preset/vfs-default.toml",
    "content": "[services]\n"
  },
  {
    "path": "yazi-config/preset/yazi-default.toml",
    "content": "# A TOML linter such as https://taplo.tamasfe.dev/ can use this schema to validate your config.\n# If you encounter any issues, please make an issue at https://github.com/yazi-rs/schemas.\n\"$schema\" = \"https://yazi-rs.github.io/schemas/yazi.json\"\n\n[mgr]\nratio          = [ 1, 4, 3 ]\nsort_by        = \"alphabetical\"\nsort_sensitive = false\nsort_reverse \t = false\nsort_dir_first = true\nsort_translit  = false\nsort_fallback  = \"alphabetical\"\nlinemode       = \"none\"\nshow_hidden    = false\nshow_symlink   = true\nscrolloff      = 5\nmouse_events   = [ \"click\", \"scroll\" ]\n\n[preview]\nwrap            = \"no\"\ntab_size        = 2\nmax_width       = 600\nmax_height      = 900\ncache_dir       = \"\"\nimage_delay     = 30\nimage_filter    = \"triangle\"\nimage_quality   = 75\nueberzug_scale  = 1\nueberzug_offset = [ 0, 0, 0, 0 ]\n\n[opener]\nedit = [\n\t{ run = \"${EDITOR:-vi} %s\", desc = \"$EDITOR\",      for = \"unix\", block = true },\n\t{ run = \"code %s\",          desc = \"code\",         for = \"windows\", orphan = true },\n\t{ run = \"code -w %s\",       desc = \"code (block)\", for = \"windows\", block = true },\n]\nplay = [\n\t{ run = \"xdg-open %s1\",    desc = \"Play\", for = \"linux\", orphan = true },\n\t{ run = \"open %s\",         desc = \"Play\", for = \"macos\" },\n\t{ run = 'start \"\" %s1',    desc = \"Play\", for = \"windows\", orphan = true },\n\t{ run = \"termux-open %s1\", desc = \"Play\", for = \"android\" },\n\t{ run = \"mediainfo %s1; echo 'Press enter to exit'; read _\", block = true, desc = \"Show media info\", for = \"unix\" },\n\t{ run = \"mediainfo %s1 & pause\", block = true, desc = \"Show media info\", for = \"windows\" },\n]\nopen = [\n\t{ run = \"xdg-open %s1\",    desc = \"Open\", for = \"linux\" },\n\t{ run = \"open %s\",         desc = \"Open\", for = \"macos\" },\n\t{ run = 'start \"\" %s1',    desc = \"Open\", for = \"windows\", orphan = true },\n\t{ run = \"termux-open %s1\", desc = \"Open\", for = \"android\" },\n]\nreveal = [\n\t{ run = \"xdg-open %d1\",         desc = \"Reveal\", for = \"linux\" },\n\t{ run = \"open -R %s1\",          desc = \"Reveal\", for = \"macos\" },\n\t{ run = \"explorer /select,%s1\", desc = \"Reveal\", for = \"windows\", orphan = true },\n\t{ run = \"termux-open %d1\",      desc = \"Reveal\", for = \"android\" },\n\t{ run = \"clear; exiftool %s1; echo 'Press enter to exit'; read _\", desc = \"Show EXIF\", for = \"unix\", block = true },\n]\nextract = [\n\t{ run = \"ya pub extract --list %s\", desc = \"Extract here\" },\n]\ndownload = [\n\t{ run = \"ya emit download --open %S\", desc = \"Download and open\" },\n\t{ run = \"ya emit download %S\",        desc = \"Download\" },\n]\n\n[open]\nrules = [\n\t# Folder\n\t{ url = \"*/\", use = [ \"edit\", \"open\", \"reveal\" ] },\n\t# Text\n\t{ mime = \"text/*\", use = [ \"edit\", \"reveal\" ] },\n\t# Image\n\t{ mime = \"image/*\", use = [ \"open\", \"reveal\" ] },\n\t# Media\n\t{ mime = \"{audio,video}/*\", use = [ \"play\", \"reveal\" ] },\n\t# Code\n\t{ mime = \"application/{json,ndjson,javascript,wine-extension-ini}\", use = [ \"edit\", \"reveal\" ] },\n\t# Archive\n\t{ mime = \"application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}\", use = [ \"extract\", \"reveal\" ] },\n\t# Empty file\n\t{ mime = \"inode/empty\", use = [ \"edit\", \"reveal\" ] },\n\t# Virtual file system\n\t{ mime = \"vfs/{absent,stale}\", use = \"download\" },\n\t# Fallback\n\t{ url = \"*\", use = [ \"open\", \"reveal\" ] },\n]\n\n[tasks]\nfile_workers     = 3\nplugin_workers   = 5\nfetch_workers    = 5\npreload_workers  = 2\nprocess_workers  = 5\nbizarre_retry    = 3\nimage_alloc      = 536870912  # 512MB\nimage_bound      = [ 10000, 10000 ]\nsuppress_preload = false\n\n[plugin]\nfetchers = [\n\t# Mimetype\n\t{ id = \"mime\", url = \"*/\",         run = \"mime.dir\", prio = \"high\" },\n\t{ id = \"mime\", url = \"local://*\",  run = \"mime.local\", prio = \"high\" },\n\t{ id = \"mime\", url = \"remote://*\", run = \"mime.remote\", prio = \"high\" },\n]\nspotters = [\n\t# Multi-file\n\t{ mime = \"multi/*\", run = \"multi\" },\n\t# Folder\n\t{ url = \"*/\", run = \"folder\" },\n\t# Code\n\t{ mime = \"text/*\", run = \"code\" },\n\t{ mime = \"application/{mbox,javascript,wine-extension-ini}\", run = \"code\" },\n\t# Image\n\t{ mime = \"image/{avif,hei?,jxl}\", run = \"magick\" },\n\t{ mime = \"image/svg+xml\", run = \"svg\" },\n\t{ mime = \"image/*\", run = \"image\" },\n\t# Video\n\t{ mime = \"video/*\", run = \"video\" },\n\t# Virtual file system\n\t{ mime = \"vfs/*\", run = \"vfs\" },\n\t# Error\n\t{ mime = \"null/*\", run = \"null\" },\n\t# Fallback\n\t{ url = \"*\", run = \"file\" },\n]\npreloaders = [\n\t# Image\n\t{ mime = \"image/{avif,hei?,jxl}\", run = \"magick\" },\n\t{ mime = \"image/svg+xml\", run = \"svg\" },\n\t{ mime = \"image/*\", run = \"image\" },\n\t# Video\n\t{ mime = \"video/*\", run = \"video\" },\n\t# PDF\n\t{ mime = \"application/pdf\", run = \"pdf\" },\n\t# Font\n\t{ mime = \"font/*\", run = \"font\" },\n\t{ mime = \"application/ms-opentype\", run = \"font\" },\n]\npreviewers = [\n\t{ url = \"*/\", run = \"folder\" },\n\t# Code\n\t{ mime = \"text/*\", run = \"code\" },\n\t{ mime = \"application/{mbox,javascript,wine-extension-ini}\", run = \"code\" },\n\t# JSON\n\t{ mime = \"application/{json,ndjson}\", run = \"json\" },\n\t# Image\n\t{ mime = \"image/{avif,hei?,jxl}\", run = \"magick\" },\n\t{ mime = \"image/svg+xml\", run = \"svg\" },\n\t{ mime = \"image/*\", run = \"image\" },\n\t# Video\n\t{ mime = \"video/*\", run = \"video\" },\n\t# PDF\n\t{ mime = \"application/pdf\", run = \"pdf\" },\n\t# Archive\n\t{ mime = \"application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}\", run = \"archive\" },\n\t{ mime = \"application/{debian*-package,redhat-package-manager,rpm,android.package-archive}\", run = \"archive\" },\n\t{ url = \"*.{AppImage,appimage}\", run = \"archive\" },\n\t# Virtual Disk / Disk Image\n\t{ mime = \"application/{iso9660-image,qemu-disk,ms-wim,apple-diskimage}\", run = \"archive\" },\n\t{ mime = \"application/virtualbox-{vhd,vhdx}\", run = \"archive\" },\n\t{ url = \"*.{img,fat,ext,ext2,ext3,ext4,squashfs,ntfs,hfs,hfsx}\", run = \"archive\" },\n\t# Font\n\t{ mime = \"font/*\", run = \"font\" },\n\t{ mime = \"application/ms-opentype\", run = \"font\" },\n\t# Empty file\n\t{ mime = \"inode/empty\", run = \"empty\" },\n\t# Virtual file system\n\t{ mime = \"vfs/*\", run = \"vfs\" },\n\t# Error\n\t{ mime = \"null/*\", run = \"null\" },\n\t# Fallback\n\t{ url = \"*\", run = \"file\" },\n]\n\n[input]\ncursor_blink = false\n\n# cd\ncd_title  = \"Change directory:\"\ncd_origin = \"top-center\"\ncd_offset = [ 0, 2, 50, 3 ]\n\n# create\ncreate_title  = [ \"Create:\", \"Create (dir):\" ]\ncreate_origin = \"top-center\"\ncreate_offset = [ 0, 2, 50, 3 ]\n\n# rename\nrename_title  = \"Rename:\"\nrename_origin = \"hovered\"\nrename_offset = [ 0, 1, 50, 3 ]\n\n# filter\nfilter_title  = \"Filter:\"\nfilter_origin = \"top-center\"\nfilter_offset = [ 0, 2, 50, 3 ]\n\n# find\nfind_title  = [ \"Find next:\", \"Find previous:\" ]\nfind_origin = \"top-center\"\nfind_offset = [ 0, 2, 50, 3 ]\n\n# search\nsearch_title  = \"Search via {n}:\"\nsearch_origin = \"top-center\"\nsearch_offset = [ 0, 2, 50, 3 ]\n\n# shell\nshell_title  = [ \"Shell:\", \"Shell (block):\" ]\nshell_origin = \"top-center\"\nshell_offset = [ 0, 2, 50, 3 ]\n\n[confirm]\n# trash\ntrash_title \t= \"Trash {n} selected file{s}?\"\ntrash_origin\t= \"center\"\ntrash_offset\t= [ 0, 0, 70, 20 ]\n\n# delete\ndelete_title \t= \"Permanently delete {n} selected file{s}?\"\ndelete_origin\t= \"center\"\ndelete_offset\t= [ 0, 0, 70, 20 ]\n\n# overwrite\noverwrite_title  = \"Overwrite file?\"\noverwrite_body   = \"Will overwrite the following file:\"\noverwrite_origin = \"center\"\noverwrite_offset = [ 0, 0, 50, 15 ]\n\n# quit\nquit_title  = \"Quit?\"\nquit_body   = \"There are unfinished tasks, quit anyway?\\n(Open task manager with default key 'w')\"\nquit_origin = \"center\"\nquit_offset = [ 0, 0, 50, 15 ]\n\n[pick]\nopen_title  = \"Open with:\"\nopen_origin = \"hovered\"\nopen_offset = [ 0, 1, 50, 7 ]\n\n[which]\nsort_by      \t = \"none\"\nsort_sensitive = false\nsort_reverse \t = false\nsort_translit  = false\n"
  },
  {
    "path": "yazi-config/src/icon.rs",
    "content": "use crate::Style;\n\n#[derive(Clone, Debug)]\npub struct Icon {\n\tpub text:  String,\n\tpub style: Style,\n}\n"
  },
  {
    "path": "yazi-config/src/keymap/chord.rs",
    "content": "use std::{borrow::Cow, hash::{Hash, Hasher}, sync::OnceLock};\n\nuse anyhow::Result;\nuse regex::Regex;\nuse serde::Deserialize;\nuse yazi_shared::{Layer, Source, event::Action};\n\nuse super::Key;\n\nstatic RE: OnceLock<Regex> = OnceLock::new();\n\n#[derive(Clone, Debug, Default, Deserialize)]\npub struct Chord {\n\t#[serde(deserialize_with = \"super::deserialize_on\")]\n\tpub on:    Vec<Key>,\n\t#[serde(deserialize_with = \"super::deserialize_run\")]\n\tpub run:   Vec<Action>,\n\tpub desc:  Option<String>,\n\tpub r#for: Option<String>,\n}\n\nimpl PartialEq for Chord {\n\tfn eq(&self, other: &Self) -> bool { self.on == other.on }\n}\n\nimpl Eq for Chord {}\n\nimpl Hash for Chord {\n\tfn hash<H: Hasher>(&self, state: &mut H) { self.on.hash(state) }\n}\n\nimpl Chord {\n\tpub fn on(&self) -> String { self.on.iter().map(ToString::to_string).collect() }\n\n\tpub fn run(&self) -> String {\n\t\tRE.get_or_init(|| Regex::new(r\"\\s+\").unwrap())\n\t\t\t.replace_all(&self.run.iter().map(|c| c.to_string()).collect::<Vec<_>>().join(\"; \"), \" \")\n\t\t\t.into_owned()\n\t}\n\n\tpub fn desc(&self) -> Option<Cow<'_, str>> {\n\t\tself.desc.as_ref().map(|s| RE.get_or_init(|| Regex::new(r\"\\s+\").unwrap()).replace_all(s, \" \"))\n\t}\n\n\tpub fn desc_or_run(&self) -> Cow<'_, str> { self.desc().unwrap_or_else(|| self.run().into()) }\n\n\tpub fn contains(&self, s: &str) -> bool {\n\t\tlet s = s.to_lowercase();\n\t\tself.desc().map(|d| d.to_lowercase().contains(&s)) == Some(true)\n\t\t\t|| self.run().to_lowercase().contains(&s)\n\t\t\t|| self.on().to_lowercase().contains(&s)\n\t}\n\n\t#[inline]\n\tpub(super) fn noop(&self) -> bool {\n\t\tself.run.len() == 1 && self.run[0].name == \"noop\" && self.run[0].args.is_empty()\n\t}\n}\n\nimpl Chord {\n\tpub(super) fn reshape(mut self, layer: Layer) -> Result<Self> {\n\t\tfor action in &mut self.run {\n\t\t\taction.source = Source::Key;\n\t\t\tif action.layer == Default::default() {\n\t\t\t\taction.layer = layer;\n\t\t\t}\n\t\t}\n\t\tOk(self)\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/keymap/cow.rs",
    "content": "use std::ops::Deref;\n\nuse yazi_shared::event::ActionCow;\n\nuse super::Chord;\n\n#[derive(Clone, Debug)]\npub enum ChordCow {\n\tOwned(Chord),\n\tBorrowed(&'static Chord),\n}\n\nimpl From<Chord> for ChordCow {\n\tfn from(c: Chord) -> Self { Self::Owned(c) }\n}\n\nimpl From<&'static Chord> for ChordCow {\n\tfn from(c: &'static Chord) -> Self { Self::Borrowed(c) }\n}\n\nimpl Deref for ChordCow {\n\ttype Target = Chord;\n\n\tfn deref(&self) -> &Self::Target {\n\t\tmatch self {\n\t\t\tSelf::Owned(c) => c,\n\t\t\tSelf::Borrowed(c) => c,\n\t\t}\n\t}\n}\n\nimpl Default for ChordCow {\n\tfn default() -> Self {\n\t\tconst C: &Chord = &Chord { on: vec![], run: vec![], desc: None, r#for: None };\n\t\tSelf::Borrowed(C)\n\t}\n}\n\nimpl ChordCow {\n\tpub fn into_seq(self) -> Vec<ActionCow> {\n\t\tmatch self {\n\t\t\tSelf::Owned(c) => c.run.into_iter().rev().map(Into::into).collect(),\n\t\t\tSelf::Borrowed(c) => c.run.iter().rev().map(Into::into).collect(),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/keymap/deserializers.rs",
    "content": "use std::{fmt, str::FromStr};\n\nuse anyhow::Result;\nuse serde::{Deserializer, de::{self, Visitor}};\nuse yazi_shared::event::Action;\n\nuse crate::keymap::Key;\n\npub(super) fn deserialize_on<'de, D>(deserializer: D) -> Result<Vec<Key>, D::Error>\nwhere\n\tD: Deserializer<'de>,\n{\n\tstruct OnVisitor;\n\n\timpl<'de> Visitor<'de> for OnVisitor {\n\t\ttype Value = Vec<Key>;\n\n\t\tfn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n\t\t\tformatter.write_str(\"a `on` string or array of strings within keymap.toml\")\n\t\t}\n\n\t\tfn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>\n\t\twhere\n\t\t\tA: de::SeqAccess<'de>,\n\t\t{\n\t\t\tlet mut keys = Vec::with_capacity(seq.size_hint().unwrap_or(0));\n\t\t\twhile let Some(value) = &seq.next_element::<String>()? {\n\t\t\t\tkeys.push(Key::from_str(value).map_err(de::Error::custom)?);\n\t\t\t}\n\t\t\tif keys.is_empty() {\n\t\t\t\treturn Err(de::Error::custom(\"`on` within keymap.toml cannot be empty\"));\n\t\t\t}\n\t\t\tOk(keys)\n\t\t}\n\n\t\tfn visit_str<E>(self, value: &str) -> Result<Self::Value, E>\n\t\twhere\n\t\t\tE: de::Error,\n\t\t{\n\t\t\tOk(vec![Key::from_str(value).map_err(de::Error::custom)?])\n\t\t}\n\t}\n\n\tdeserializer.deserialize_any(OnVisitor)\n}\n\npub(super) fn deserialize_run<'de, D>(deserializer: D) -> Result<Vec<Action>, D::Error>\nwhere\n\tD: Deserializer<'de>,\n{\n\tstruct RunVisitor;\n\n\timpl<'de> Visitor<'de> for RunVisitor {\n\t\ttype Value = Vec<Action>;\n\n\t\tfn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n\t\t\tformatter.write_str(\"a `run` string or array of strings within keymap.toml\")\n\t\t}\n\n\t\tfn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>\n\t\twhere\n\t\t\tA: de::SeqAccess<'de>,\n\t\t{\n\t\t\tlet mut actions = Vec::with_capacity(seq.size_hint().unwrap_or(0));\n\t\t\twhile let Some(value) = &seq.next_element::<String>()? {\n\t\t\t\tactions.push(Action::from_str(value).map_err(de::Error::custom)?);\n\t\t\t}\n\t\t\tif actions.is_empty() {\n\t\t\t\treturn Err(de::Error::custom(\"`run` within keymap.toml cannot be empty\"));\n\t\t\t}\n\t\t\tOk(actions)\n\t\t}\n\n\t\tfn visit_str<E>(self, value: &str) -> Result<Self::Value, E>\n\t\twhere\n\t\t\tE: de::Error,\n\t\t{\n\t\t\tOk(vec![Action::from_str(value).map_err(de::Error::custom)?])\n\t\t}\n\t}\n\n\tdeserializer.deserialize_any(RunVisitor)\n}\n"
  },
  {
    "path": "yazi-config/src/keymap/key.rs",
    "content": "use std::{fmt::{Display, Write}, str::FromStr};\n\nuse anyhow::bail;\nuse crossterm::event::{KeyCode, KeyEvent, KeyModifiers};\n\n#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]\npub struct Key {\n\tpub code:   KeyCode,\n\tpub shift:  bool,\n\tpub ctrl:   bool,\n\tpub alt:    bool,\n\tpub super_: bool,\n}\n\nimpl Key {\n\t#[inline]\n\tpub fn plain(&self) -> Option<char> {\n\t\tmatch self.code {\n\t\t\tKeyCode::Char(c) if !self.ctrl && !self.alt && !self.super_ => Some(c),\n\t\t\t_ => None,\n\t\t}\n\t}\n}\n\nimpl Default for Key {\n\tfn default() -> Self {\n\t\tSelf { code: KeyCode::Null, shift: false, ctrl: false, alt: false, super_: false }\n\t}\n}\n\nimpl From<KeyEvent> for Key {\n\tfn from(value: KeyEvent) -> Self {\n\t\t// For alphabet:\n\t\t//   Unix    :  <S-a> => Char(\"A\") + SHIFT\n\t\t//   Windows :  <S-a> => Char(\"A\") + SHIFT\n\t\t//\n\t\t// For non-alphabet:\n\t\t//   Unix    :  <S-`> => Char(\"~\") + NULL\n\t\t//   Windows :  <S-`> => Char(\"~\") + SHIFT\n\t\t//\n\t\t// So we detect `Char(\"~\") + SHIFT`, and change it to `Char(\"~\") + NULL`\n\t\t// for consistent behavior between OSs.\n\n\t\tlet shift = match (value.code, value.modifiers) {\n\t\t\t(KeyCode::Char(c), _) => c.is_ascii_uppercase(),\n\t\t\t(KeyCode::BackTab, _) => false,\n\t\t\t(_, m) => m.contains(KeyModifiers::SHIFT),\n\t\t};\n\n\t\tSelf {\n\t\t\tcode: value.code,\n\t\t\tshift,\n\t\t\tctrl: value.modifiers.contains(KeyModifiers::CONTROL),\n\t\t\talt: value.modifiers.contains(KeyModifiers::ALT),\n\t\t\tsuper_: value.modifiers.contains(KeyModifiers::SUPER),\n\t\t}\n\t}\n}\n\nimpl FromStr for Key {\n\ttype Err = anyhow::Error;\n\n\tfn from_str(s: &str) -> Result<Self, Self::Err> {\n\t\tif s.is_empty() {\n\t\t\tbail!(\"empty key\")\n\t\t}\n\n\t\tlet mut key = Self::default();\n\t\tif !s.starts_with('<') || !s.ends_with('>') {\n\t\t\tkey.code = KeyCode::Char(s.chars().next().unwrap());\n\t\t\tkey.shift = matches!(key.code, KeyCode::Char(c) if c.is_ascii_uppercase());\n\t\t\treturn Ok(key);\n\t\t}\n\n\t\tlet mut it = s[1..s.len() - 1].split_inclusive('-').peekable();\n\t\twhile let Some(next) = it.next() {\n\t\t\tmatch next.to_ascii_lowercase().as_str() {\n\t\t\t\t\"s-\" => key.shift = true,\n\t\t\t\t\"c-\" => key.ctrl = true,\n\t\t\t\t\"a-\" => key.alt = true,\n\t\t\t\t\"d-\" => key.super_ = true,\n\n\t\t\t\t\"space\" => key.code = KeyCode::Char(' '),\n\t\t\t\t\"backspace\" => key.code = KeyCode::Backspace,\n\t\t\t\t\"enter\" => key.code = KeyCode::Enter,\n\t\t\t\t\"left\" => key.code = KeyCode::Left,\n\t\t\t\t\"right\" => key.code = KeyCode::Right,\n\t\t\t\t\"up\" => key.code = KeyCode::Up,\n\t\t\t\t\"down\" => key.code = KeyCode::Down,\n\t\t\t\t\"home\" => key.code = KeyCode::Home,\n\t\t\t\t\"end\" => key.code = KeyCode::End,\n\t\t\t\t\"pageup\" => key.code = KeyCode::PageUp,\n\t\t\t\t\"pagedown\" => key.code = KeyCode::PageDown,\n\t\t\t\t\"tab\" => key.code = KeyCode::Tab,\n\t\t\t\t\"backtab\" => key.code = KeyCode::BackTab,\n\t\t\t\t\"delete\" => key.code = KeyCode::Delete,\n\t\t\t\t\"insert\" => key.code = KeyCode::Insert,\n\t\t\t\t\"f1\" => key.code = KeyCode::F(1),\n\t\t\t\t\"f2\" => key.code = KeyCode::F(2),\n\t\t\t\t\"f3\" => key.code = KeyCode::F(3),\n\t\t\t\t\"f4\" => key.code = KeyCode::F(4),\n\t\t\t\t\"f5\" => key.code = KeyCode::F(5),\n\t\t\t\t\"f6\" => key.code = KeyCode::F(6),\n\t\t\t\t\"f7\" => key.code = KeyCode::F(7),\n\t\t\t\t\"f8\" => key.code = KeyCode::F(8),\n\t\t\t\t\"f9\" => key.code = KeyCode::F(9),\n\t\t\t\t\"f10\" => key.code = KeyCode::F(10),\n\t\t\t\t\"f11\" => key.code = KeyCode::F(11),\n\t\t\t\t\"f12\" => key.code = KeyCode::F(12),\n\t\t\t\t\"f13\" => key.code = KeyCode::F(13),\n\t\t\t\t\"f14\" => key.code = KeyCode::F(14),\n\t\t\t\t\"f15\" => key.code = KeyCode::F(15),\n\t\t\t\t\"f16\" => key.code = KeyCode::F(16),\n\t\t\t\t\"f17\" => key.code = KeyCode::F(17),\n\t\t\t\t\"f18\" => key.code = KeyCode::F(18),\n\t\t\t\t\"f19\" => key.code = KeyCode::F(19),\n\t\t\t\t\"esc\" => key.code = KeyCode::Esc,\n\n\t\t\t\t_ => match next {\n\t\t\t\t\ts if it.peek().is_none() => {\n\t\t\t\t\t\tlet c = s.chars().next().unwrap();\n\t\t\t\t\t\tkey.shift |= c.is_ascii_uppercase();\n\t\t\t\t\t\tkey.code = KeyCode::Char(if key.shift { c.to_ascii_uppercase() } else { c });\n\t\t\t\t\t}\n\t\t\t\t\ts => bail!(\"unknown key: {s}\"),\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\tif key.code == KeyCode::Null {\n\t\t\tbail!(\"empty key\")\n\t\t}\n\t\tOk(key)\n\t}\n}\n\nimpl Display for Key {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tif let Some(c) = self.plain() {\n\t\t\treturn if c == ' ' { write!(f, \"<Space>\") } else { f.write_char(c) };\n\t\t}\n\n\t\twrite!(f, \"<\")?;\n\t\tif self.super_ {\n\t\t\twrite!(f, \"D-\")?;\n\t\t}\n\t\tif self.ctrl {\n\t\t\twrite!(f, \"C-\")?;\n\t\t}\n\t\tif self.alt {\n\t\t\twrite!(f, \"A-\")?;\n\t\t}\n\t\tif self.shift && !matches!(self.code, KeyCode::Char(_)) {\n\t\t\twrite!(f, \"S-\")?;\n\t\t}\n\n\t\tlet code = match self.code {\n\t\t\tKeyCode::Backspace => \"Backspace\",\n\t\t\tKeyCode::Enter => \"Enter\",\n\t\t\tKeyCode::Left => \"Left\",\n\t\t\tKeyCode::Right => \"Right\",\n\t\t\tKeyCode::Up => \"Up\",\n\t\t\tKeyCode::Down => \"Down\",\n\t\t\tKeyCode::Home => \"Home\",\n\t\t\tKeyCode::End => \"End\",\n\t\t\tKeyCode::PageUp => \"PageUp\",\n\t\t\tKeyCode::PageDown => \"PageDown\",\n\t\t\tKeyCode::Tab => \"Tab\",\n\t\t\tKeyCode::BackTab => \"BackTab\",\n\t\t\tKeyCode::Delete => \"Delete\",\n\t\t\tKeyCode::Insert => \"Insert\",\n\t\t\tKeyCode::F(1) => \"F1\",\n\t\t\tKeyCode::F(2) => \"F2\",\n\t\t\tKeyCode::F(3) => \"F3\",\n\t\t\tKeyCode::F(4) => \"F4\",\n\t\t\tKeyCode::F(5) => \"F5\",\n\t\t\tKeyCode::F(6) => \"F6\",\n\t\t\tKeyCode::F(7) => \"F7\",\n\t\t\tKeyCode::F(8) => \"F8\",\n\t\t\tKeyCode::F(9) => \"F9\",\n\t\t\tKeyCode::F(10) => \"F10\",\n\t\t\tKeyCode::F(11) => \"F11\",\n\t\t\tKeyCode::F(12) => \"F12\",\n\t\t\tKeyCode::F(13) => \"F13\",\n\t\t\tKeyCode::F(14) => \"F14\",\n\t\t\tKeyCode::F(15) => \"F15\",\n\t\t\tKeyCode::F(16) => \"F16\",\n\t\t\tKeyCode::F(17) => \"F17\",\n\t\t\tKeyCode::F(18) => \"F18\",\n\t\t\tKeyCode::F(19) => \"F19\",\n\t\t\tKeyCode::Esc => \"Esc\",\n\n\t\t\tKeyCode::Char(' ') => \"Space\",\n\t\t\tKeyCode::Char(c) => {\n\t\t\t\tf.write_char(c)?;\n\t\t\t\t\"\"\n\t\t\t}\n\t\t\t_ => \"Unknown\",\n\t\t};\n\n\t\twrite!(f, \"{code}>\")\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/keymap/keymap.rs",
    "content": "use anyhow::{Context, Result};\nuse serde::Deserialize;\nuse yazi_codegen::{DeserializeOver, DeserializeOver1};\nuse yazi_fs::{Xdg, ok_or_not_found};\nuse yazi_shared::Layer;\n\nuse super::{Chord, KeymapRules};\n\n#[derive(Deserialize, DeserializeOver, DeserializeOver1)]\npub struct Keymap {\n\tpub mgr:     KeymapRules,\n\tpub tasks:   KeymapRules,\n\tpub spot:    KeymapRules,\n\tpub pick:    KeymapRules,\n\tpub input:   KeymapRules,\n\tpub confirm: KeymapRules,\n\tpub help:    KeymapRules,\n\tpub cmp:     KeymapRules,\n}\n\nimpl Keymap {\n\tpub fn get(&self, layer: Layer) -> &[Chord] {\n\t\tmatch layer {\n\t\t\tLayer::App => &[],\n\t\t\tLayer::Mgr => &self.mgr,\n\t\t\tLayer::Tasks => &self.tasks,\n\t\t\tLayer::Spot => &self.spot,\n\t\t\tLayer::Pick => &self.pick,\n\t\t\tLayer::Input => &self.input,\n\t\t\tLayer::Confirm => &self.confirm,\n\t\t\tLayer::Help => &self.help,\n\t\t\tLayer::Cmp => &self.cmp,\n\t\t\tLayer::Which => &[],\n\t\t\tLayer::Notify => &[],\n\t\t}\n\t}\n}\n\nimpl Keymap {\n\tpub(crate) fn read() -> Result<String> {\n\t\tlet p = Xdg::config_dir().join(\"keymap.toml\");\n\t\tok_or_not_found(std::fs::read_to_string(&p))\n\t\t\t.with_context(|| format!(\"Failed to read keymap {p:?}\"))\n\t}\n\n\tpub(crate) fn reshape(self) -> Result<Self> {\n\t\tOk(Self {\n\t\t\tmgr:     self.mgr.reshape(Layer::Mgr)?,\n\t\t\ttasks:   self.tasks.reshape(Layer::Tasks)?,\n\t\t\tspot:    self.spot.reshape(Layer::Spot)?,\n\t\t\tpick:    self.pick.reshape(Layer::Pick)?,\n\t\t\tinput:   self.input.reshape(Layer::Input)?,\n\t\t\tconfirm: self.confirm.reshape(Layer::Confirm)?,\n\t\t\thelp:    self.help.reshape(Layer::Help)?,\n\t\t\tcmp:     self.cmp.reshape(Layer::Cmp)?,\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/keymap/mod.rs",
    "content": "yazi_macro::mod_flat!(chord cow deserializers key keymap rules);\n"
  },
  {
    "path": "yazi-config/src/keymap/rules.rs",
    "content": "use std::ops::Deref;\n\nuse anyhow::Result;\nuse hashbrown::HashSet;\nuse serde::Deserialize;\nuse yazi_codegen::DeserializeOver2;\nuse yazi_shared::Layer;\n\nuse super::Chord;\nuse crate::{Preset, check_for, keymap::Key};\n\n#[derive(Default, Deserialize, DeserializeOver2)]\npub struct KeymapRules {\n\tpub keymap:     Vec<Chord>,\n\t#[serde(default)]\n\tprepend_keymap: Vec<Chord>,\n\t#[serde(default)]\n\tappend_keymap:  Vec<Chord>,\n}\n\nimpl Deref for KeymapRules {\n\ttype Target = Vec<Chord>;\n\n\tfn deref(&self) -> &Self::Target { &self.keymap }\n}\n\nimpl KeymapRules {\n\tpub(crate) fn reshape(self, layer: Layer) -> Result<Self> {\n\t\t#[inline]\n\t\tfn on(Chord { on, .. }: &Chord) -> [Key; 2] {\n\t\t\t[on.first().copied().unwrap_or_default(), on.get(1).copied().unwrap_or_default()]\n\t\t}\n\n\t\tlet a_seen: HashSet<_> = self.prepend_keymap.iter().map(on).collect();\n\t\tlet b_seen: HashSet<_> = self.keymap.iter().map(on).collect();\n\n\t\tlet keymap = Preset::mix(\n\t\t\tself.prepend_keymap,\n\t\t\tself.keymap.into_iter().filter(|v| !a_seen.contains(&on(v))),\n\t\t\tself.append_keymap.into_iter().filter(|v| !b_seen.contains(&on(v))),\n\t\t)\n\t\t.map(|mut chord| (chord.r#for.take(), chord))\n\t\t.filter(|(r#for, chord)| !chord.noop() && check_for(r#for.as_deref()))\n\t\t.map(|(_, chord)| chord.reshape(layer))\n\t\t.collect::<Result<_>>()?;\n\n\t\tOk(Self { keymap, ..Default::default() })\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/layout.rs",
    "content": "use ratatui::layout::Rect;\n\n#[derive(Clone, Copy, Default, Eq, PartialEq)]\npub struct Layout {\n\tpub current:  Rect,\n\tpub preview:  Rect,\n\tpub progress: Rect,\n}\n\nimpl Layout {\n\tpub const fn default() -> Self {\n\t\tSelf { current: Rect::ZERO, preview: Rect::ZERO, progress: Rect::ZERO }\n\t}\n\n\tpub const fn folder_limit(self) -> usize { self.current.height as _ }\n}\n"
  },
  {
    "path": "yazi-config/src/lib.rs",
    "content": "yazi_macro::mod_pub!(keymap mgr open opener plugin popup preview tasks theme which vfs);\n\nyazi_macro::mod_flat!(icon layout pattern platform preset priority style utils yazi);\n\nuse std::io::{Read, Write};\n\nuse yazi_shared::{RoCell, SyncCell};\nuse yazi_tty::TTY;\n\npub static YAZI: RoCell<yazi::Yazi> = RoCell::new();\npub static KEYMAP: RoCell<keymap::Keymap> = RoCell::new();\npub static THEME: RoCell<theme::Theme> = RoCell::new();\npub static LAYOUT: SyncCell<Layout> = SyncCell::new(Layout::default());\n\npub fn init() -> anyhow::Result<()> {\n\tif let Err(e) = try_init(true) {\n\t\twait_for_key(e)?;\n\t\ttry_init(false)?;\n\t}\n\tOk(())\n}\n\nfn try_init(merge: bool) -> anyhow::Result<()> {\n\tlet mut yazi = Preset::yazi()?;\n\tlet mut keymap = Preset::keymap()?;\n\n\tif merge {\n\t\tyazi = yazi.deserialize_over(&yazi::Yazi::read()?)?;\n\t\tkeymap = keymap.deserialize_over(&keymap::Keymap::read()?)?;\n\t}\n\n\tYAZI.init(yazi.reshape()?);\n\tKEYMAP.init(keymap.reshape()?);\n\tOk(())\n}\n\npub fn init_flavor(light: bool) -> anyhow::Result<()> {\n\tif let Err(e) = try_init_flavor(light, true) {\n\t\twait_for_key(e)?;\n\t\ttry_init_flavor(light, false)?;\n\t}\n\tOk(())\n}\n\nfn try_init_flavor(light: bool, merge: bool) -> anyhow::Result<()> {\n\tlet mut preset = Preset::theme(light)?;\n\n\tif merge {\n\t\tlet theme_str = theme::Theme::read()?;\n\t\tlet theme = toml::de::DeTable::parse(&theme_str)?;\n\n\t\tlet flavor_str = theme::Flavor::from_theme(&theme, &theme_str)?.read(light)?;\n\n\t\tpreset = preset.deserialize_over(&flavor_str)?;\n\t\tpreset = error_with_input(preset.deserialize_over_with(theme), &theme_str)?;\n\t}\n\n\tTHEME.init(preset.reshape(light)?);\n\tOk(())\n}\n\nfn wait_for_key(e: anyhow::Error) -> anyhow::Result<()> {\n\tlet stdout = &mut *TTY.lockout();\n\n\twriteln!(stdout, \"{e}\")?;\n\tif let Some(src) = e.source() {\n\t\twriteln!(stdout, \"\\nCaused by:\\n{src}\")?;\n\t}\n\n\tuse crossterm::style::{Attribute, Print, SetAttributes};\n\tcrossterm::execute!(\n\t\tstdout,\n\t\tSetAttributes(Attribute::Reverse.into()),\n\t\tSetAttributes(Attribute::Bold.into()),\n\t\tPrint(\"Press <Enter> to continue with preset settings...\"),\n\t\tSetAttributes(Attribute::Reset.into()),\n\t\tPrint(\"\\n\"),\n\t)?;\n\n\tTTY.reader().read_exact(&mut [0])?;\n\tOk(())\n}\n\npub(crate) fn error_with_input<T>(\n\tresult: Result<T, toml::de::Error>,\n\tinput: &str,\n) -> Result<T, toml::de::Error> {\n\tresult.map_err(|mut err| {\n\t\terr.set_input(Some(input));\n\t\terr\n\t})\n}\n"
  },
  {
    "path": "yazi-config/src/mgr/mgr.rs",
    "content": "use anyhow::{Result, bail};\nuse serde::Deserialize;\nuse yazi_codegen::DeserializeOver2;\nuse yazi_fs::{SortBy, SortFallback};\nuse yazi_shared::SyncCell;\n\nuse super::{MgrRatio, MouseEvents};\n\n#[derive(Debug, Deserialize, DeserializeOver2)]\npub struct Mgr {\n\tpub ratio: SyncCell<MgrRatio>,\n\n\t// Sorting\n\tpub sort_by:        SyncCell<SortBy>,\n\tpub sort_sensitive: SyncCell<bool>,\n\tpub sort_reverse:   SyncCell<bool>,\n\tpub sort_dir_first: SyncCell<bool>,\n\tpub sort_translit:  SyncCell<bool>,\n\tpub sort_fallback:  SyncCell<SortFallback>,\n\n\t// Display\n\tpub linemode:     String,\n\tpub show_hidden:  SyncCell<bool>,\n\tpub show_symlink: SyncCell<bool>,\n\tpub scrolloff:    SyncCell<u8>,\n\tpub mouse_events: SyncCell<MouseEvents>,\n}\n\nimpl Mgr {\n\tpub(crate) fn reshape(self) -> Result<Self> {\n\t\tif self.linemode.is_empty() || self.linemode.len() > 20 {\n\t\t\tbail!(\"[mgr].linemode must be between 1 and 20 characters.\");\n\t\t}\n\n\t\tOk(self)\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/mgr/mod.rs",
    "content": "yazi_macro::mod_flat!(mgr mouse ratio);\n"
  },
  {
    "path": "yazi-config/src/mgr/mouse.rs",
    "content": "use anyhow::{Result, bail};\nuse bitflags::bitflags;\nuse crossterm::event::MouseEventKind;\nuse serde::{Deserialize, Serialize};\n\nbitflags! {\n\t#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]\n\t#[serde(try_from = \"Vec<String>\", into = \"Vec<String>\")]\n\tpub struct MouseEvents: u8 {\n\t\tconst CLICK  = 0b00001;\n\t\tconst SCROLL = 0b00010;\n\t\tconst TOUCH  = 0b00100;\n\t\tconst MOVE   = 0b01000;\n\t\tconst DRAG   = 0b10000;\n\t}\n}\n\nimpl MouseEvents {\n\tpub const fn draggable(self) -> bool { self.contains(Self::DRAG) }\n}\n\nimpl TryFrom<Vec<String>> for MouseEvents {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(value: Vec<String>) -> Result<Self, Self::Error> {\n\t\tvalue.into_iter().try_fold(Self::empty(), |aac, s| {\n\t\t\tOk(match s.as_str() {\n\t\t\t\t\"click\" => aac | Self::CLICK,\n\t\t\t\t\"scroll\" => aac | Self::SCROLL,\n\t\t\t\t\"touch\" => aac | Self::TOUCH,\n\t\t\t\t\"move\" => aac | Self::MOVE,\n\t\t\t\t\"drag\" => aac | Self::DRAG,\n\t\t\t\t_ => bail!(\"Invalid mouse event: {s}\"),\n\t\t\t})\n\t\t})\n\t}\n}\n\nimpl From<MouseEvents> for Vec<String> {\n\tfn from(value: MouseEvents) -> Self {\n\t\tlet events = [\n\t\t\t(MouseEvents::CLICK, \"click\"),\n\t\t\t(MouseEvents::SCROLL, \"scroll\"),\n\t\t\t(MouseEvents::TOUCH, \"touch\"),\n\t\t\t(MouseEvents::MOVE, \"move\"),\n\t\t\t(MouseEvents::DRAG, \"drag\"),\n\t\t];\n\t\tevents.into_iter().filter(|v| value.contains(v.0)).map(|v| v.1.to_owned()).collect()\n\t}\n}\n\nimpl From<crossterm::event::MouseEventKind> for MouseEvents {\n\tfn from(value: crossterm::event::MouseEventKind) -> Self {\n\t\tmatch value {\n\t\t\tMouseEventKind::Down(_) | MouseEventKind::Up(_) => Self::CLICK,\n\t\t\tMouseEventKind::ScrollDown | MouseEventKind::ScrollUp => Self::SCROLL,\n\t\t\tMouseEventKind::ScrollLeft | MouseEventKind::ScrollRight => Self::TOUCH,\n\t\t\tMouseEventKind::Moved => Self::MOVE,\n\t\t\tMouseEventKind::Drag(_) => Self::DRAG,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/mgr/ratio.rs",
    "content": "use anyhow::bail;\nuse serde::{Deserialize, Serialize};\n\n#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]\n#[serde(try_from = \"[u16; 3]\")]\npub struct MgrRatio {\n\tpub parent:  u16,\n\tpub current: u16,\n\tpub preview: u16,\n\tpub all:     u16,\n}\n\nimpl TryFrom<[u16; 3]> for MgrRatio {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(ratio: [u16; 3]) -> Result<Self, Self::Error> {\n\t\tif ratio.len() != 3 {\n\t\t\tbail!(\"invalid layout ratio: {:?}\", ratio);\n\t\t}\n\t\tif ratio.iter().all(|&r| r == 0) {\n\t\t\tbail!(\"at least one layout ratio must be non-zero: {:?}\", ratio);\n\t\t}\n\n\t\tOk(Self {\n\t\t\tparent:  ratio[0],\n\t\t\tcurrent: ratio[1],\n\t\t\tpreview: ratio[2],\n\t\t\tall:     ratio[0] + ratio[1] + ratio[2],\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/open/mod.rs",
    "content": "yazi_macro::mod_flat!(open rule);\n"
  },
  {
    "path": "yazi-config/src/open/open.rs",
    "content": "use std::ops::Deref;\n\nuse anyhow::Result;\nuse indexmap::IndexSet;\nuse serde::Deserialize;\nuse yazi_codegen::DeserializeOver2;\nuse yazi_shared::url::AsUrl;\n\nuse crate::{Preset, open::OpenRule};\n\n#[derive(Default, Deserialize, DeserializeOver2)]\npub struct Open {\n\trules:         Vec<OpenRule>,\n\t#[serde(default)]\n\tprepend_rules: Vec<OpenRule>,\n\t#[serde(default)]\n\tappend_rules:  Vec<OpenRule>,\n}\n\nimpl Deref for Open {\n\ttype Target = Vec<OpenRule>;\n\n\tfn deref(&self) -> &Self::Target { &self.rules }\n}\n\nimpl Open {\n\tpub fn all<'a, 'b, U, M>(&'a self, url: U, mime: M) -> impl Iterator<Item = &'a str> + 'b\n\twhere\n\t\t'a: 'b,\n\t\tU: AsUrl + 'b,\n\t\tM: AsRef<str> + 'b,\n\t{\n\t\tlet is_dir = mime.as_ref().starts_with(\"folder/\");\n\t\tself\n\t\t\t.rules\n\t\t\t.iter()\n\t\t\t.find(move |&r| {\n\t\t\t\tr.mime.as_ref().is_some_and(|p| p.match_mime(&mime))\n\t\t\t\t\t|| r.url.as_ref().is_some_and(|p| p.match_url(url.as_url(), is_dir))\n\t\t\t})\n\t\t\t.into_iter()\n\t\t\t.flat_map(|r| &r.r#use)\n\t\t\t.map(String::as_str)\n\t}\n\n\tpub fn common<'a, 'b, U, M>(&'a self, targets: &'b [(U, M)]) -> IndexSet<&'a str>\n\twhere\n\t\t&'b U: AsUrl,\n\t\tM: AsRef<str>,\n\t{\n\t\tlet each: Vec<IndexSet<&str>> = targets\n\t\t\t.iter()\n\t\t\t.map(|(u, m)| self.all(u, m).collect::<IndexSet<_>>())\n\t\t\t.filter(|s| !s.is_empty())\n\t\t\t.collect();\n\n\t\tlet mut flat: IndexSet<_> = each.iter().flatten().copied().collect();\n\t\tflat.retain(|use_| each.iter().all(|e| e.contains(use_)));\n\t\tflat\n\t}\n}\n\nimpl Open {\n\tpub(crate) fn reshape(self) -> Result<Self> {\n\t\tlet any_file = self.append_rules.iter().any(|r| r.any_file());\n\t\tlet any_dir = self.append_rules.iter().any(|r| r.any_dir());\n\n\t\tlet it =\n\t\t\tself.rules.into_iter().filter(|r| !(any_file && r.any_file() || any_dir && r.any_dir()));\n\n\t\tOk(Self {\n\t\t\trules: Preset::mix(self.prepend_rules, it, self.append_rules).collect(),\n\t\t\t..Default::default()\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/open/rule.rs",
    "content": "use std::fmt;\n\nuse serde::{Deserialize, Deserializer, de::{self, Visitor}};\n\nuse crate::pattern::Pattern;\n\n#[derive(Debug, Deserialize)]\npub struct OpenRule {\n\tpub url:   Option<Pattern>,\n\tpub mime:  Option<Pattern>,\n\t#[serde(deserialize_with = \"OpenRule::deserialize\")]\n\tpub r#use: Vec<String>,\n}\n\nimpl OpenRule {\n\t#[inline]\n\tpub fn any_file(&self) -> bool { self.url.as_ref().is_some_and(|p| p.any_file()) }\n\n\t#[inline]\n\tpub fn any_dir(&self) -> bool { self.url.as_ref().is_some_and(|p| p.any_dir()) }\n}\n\nimpl OpenRule {\n\tfn deserialize<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>\n\twhere\n\t\tD: Deserializer<'de>,\n\t{\n\t\tstruct UseVisitor;\n\n\t\timpl<'de> Visitor<'de> for UseVisitor {\n\t\t\ttype Value = Vec<String>;\n\n\t\t\tfn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n\t\t\t\tformatter.write_str(\"a string, or array of strings\")\n\t\t\t}\n\n\t\t\tfn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>\n\t\t\twhere\n\t\t\t\tA: de::SeqAccess<'de>,\n\t\t\t{\n\t\t\t\tlet mut uses = Vec::with_capacity(seq.size_hint().unwrap_or(0));\n\t\t\t\twhile let Some(use_) = seq.next_element::<String>()? {\n\t\t\t\t\tuses.push(use_);\n\t\t\t\t}\n\t\t\t\tOk(uses)\n\t\t\t}\n\n\t\t\tfn visit_str<E>(self, value: &str) -> Result<Self::Value, E>\n\t\t\twhere\n\t\t\t\tE: de::Error,\n\t\t\t{\n\t\t\t\tOk(vec![value.to_owned()])\n\t\t\t}\n\n\t\t\tfn visit_string<E>(self, v: String) -> Result<Self::Value, E>\n\t\t\twhere\n\t\t\t\tE: de::Error,\n\t\t\t{\n\t\t\t\tOk(vec![v])\n\t\t\t}\n\t\t}\n\n\t\tdeserializer.deserialize_any(UseVisitor)\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/opener/mod.rs",
    "content": "yazi_macro::mod_flat!(opener rule);\n"
  },
  {
    "path": "yazi-config/src/opener/opener.rs",
    "content": "use std::{mem, ops::Deref};\n\nuse anyhow::Result;\nuse hashbrown::HashMap;\nuse indexmap::IndexSet;\nuse serde::{Deserialize, de::IntoDeserializer};\nuse toml::{Spanned, de::DeTable};\nuse yazi_codegen::DeserializeOver;\n\nuse super::OpenerRule;\nuse crate::check_for;\n\n#[derive(Debug, Deserialize, DeserializeOver)]\npub struct Opener(HashMap<String, Vec<OpenerRule>>);\n\nimpl Deref for Opener {\n\ttype Target = HashMap<String, Vec<OpenerRule>>;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl Opener {\n\tpub fn all<'a, I>(&self, uses: I) -> impl Iterator<Item = &OpenerRule>\n\twhere\n\t\tI: Iterator<Item = &'a str>,\n\t{\n\t\tuses.flat_map(|use_| self.get(use_)).flatten()\n\t}\n\n\tpub fn first<'a, I>(&self, uses: I) -> Option<&OpenerRule>\n\twhere\n\t\tI: Iterator<Item = &'a str>,\n\t{\n\t\tuses.flat_map(|use_| self.get(use_)).flatten().next()\n\t}\n\n\tpub fn block<'a, I>(&self, uses: I) -> Option<&OpenerRule>\n\twhere\n\t\tI: Iterator<Item = &'a str>,\n\t{\n\t\tuses.flat_map(|use_| self.get(use_)).flatten().find(|&o| o.block)\n\t}\n}\n\nimpl Opener {\n\tpub(crate) fn reshape(mut self) -> Result<Self> {\n\t\tfor rules in self.0.values_mut() {\n\t\t\t*rules = mem::take(rules)\n\t\t\t\t.into_iter()\n\t\t\t\t.map(|mut r| (r.r#for.take(), r))\n\t\t\t\t.filter(|(r#for, _)| check_for(r#for.as_deref()))\n\t\t\t\t.map(|(_, r)| r.reshape())\n\t\t\t\t.collect::<Result<IndexSet<_>>>()?\n\t\t\t\t.into_iter()\n\t\t\t\t.collect();\n\t\t}\n\n\t\tOk(self)\n\t}\n\n\tpub(crate) fn deserialize_over_with<'de>(\n\t\tmut self,\n\t\ttable: Spanned<DeTable<'de>>,\n\t) -> Result<Self, toml::de::Error> {\n\t\tfor (key, value) in table.into_inner() {\n\t\t\tself.0.insert(key.into_inner().into_owned(), <_>::deserialize(value.into_deserializer())?);\n\t\t}\n\n\t\tOk(self)\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/opener/rule.rs",
    "content": "use anyhow::{Result, bail};\nuse serde::Deserialize;\nuse yazi_fs::Splatter;\n\n#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)]\npub struct OpenerRule {\n\tpub run:    String,\n\t#[serde(default)]\n\tpub block:  bool,\n\t#[serde(default)]\n\tpub orphan: bool,\n\t#[serde(default)]\n\tpub desc:   String,\n\tpub r#for:  Option<String>,\n\t#[serde(skip)]\n\tpub spread: bool,\n}\n\nimpl OpenerRule {\n\tpub fn desc(&self) -> String {\n\t\tif !self.desc.is_empty() {\n\t\t\tself.desc.clone()\n\t\t} else if let Some(first) = self.run.split_whitespace().next() {\n\t\t\tfirst.to_owned()\n\t\t} else {\n\t\t\tString::new()\n\t\t}\n\t}\n}\n\nimpl OpenerRule {\n\tpub(super) fn reshape(mut self) -> Result<Self> {\n\t\tif self.run.is_empty() {\n\t\t\tbail!(\"[open].rules.*.run cannot be empty.\");\n\t\t}\n\n\t\t#[cfg(unix)]\n\t\t{\n\t\t\tself.spread =\n\t\t\t\tSplatter::<()>::spread(&self.run) || self.run.contains(\"$@\") || self.run.contains(\"$*\");\n\t\t}\n\t\t#[cfg(windows)]\n\t\t{\n\t\t\tself.spread = Splatter::<()>::spread(&self.run) || self.run.contains(\"%*\");\n\t\t}\n\n\t\tOk(self)\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/pattern.rs",
    "content": "use std::{fmt::Debug, str::FromStr};\n\nuse anyhow::{Result, bail};\nuse globset::{Candidate, GlobBuilder};\nuse serde::Deserialize;\nuse yazi_shared::{scheme::SchemeKind, url::AsUrl};\n\n#[derive(Deserialize)]\n#[serde(try_from = \"String\")]\npub struct Pattern {\n\tinner:      globset::GlobMatcher,\n\tscheme:     PatternScheme,\n\tpub is_dir: bool,\n\tis_star:    bool,\n\t#[cfg(windows)]\n\tsep_lit:    bool,\n}\n\nimpl Debug for Pattern {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tf.debug_struct(\"Pattern\")\n\t\t\t.field(\"regex\", &self.inner.glob().regex())\n\t\t\t.field(\"scheme\", &self.scheme)\n\t\t\t.field(\"is_dir\", &self.is_dir)\n\t\t\t.field(\"is_star\", &self.is_star)\n\t\t\t.finish()\n\t}\n}\n\nimpl Pattern {\n\tpub fn match_url(&self, url: impl AsUrl, is_dir: bool) -> bool {\n\t\tlet url = url.as_url();\n\n\t\tif is_dir != self.is_dir {\n\t\t\treturn false;\n\t\t} else if !self.scheme.matches(url.kind()) {\n\t\t\treturn false;\n\t\t} else if self.is_star {\n\t\t\treturn true;\n\t\t}\n\n\t\t#[cfg(unix)]\n\t\t{\n\t\t\tself.inner.is_match_candidate(&Candidate::from_bytes(url.loc().encoded_bytes()))\n\t\t}\n\n\t\t#[cfg(windows)]\n\t\tif self.sep_lit {\n\t\t\tuse yazi_shared::strand::{AsStrand, StrandLike};\n\t\t\tself.inner.is_match_candidate(&Candidate::from_bytes(\n\t\t\t\turl.loc().as_strand().backslash_to_slash().encoded_bytes(),\n\t\t\t))\n\t\t} else {\n\t\t\tself.inner.is_match_candidate(&Candidate::from_bytes(url.loc().encoded_bytes()))\n\t\t}\n\t}\n\n\tpub fn match_mime(&self, mime: impl AsRef<str>) -> bool {\n\t\tself.is_star || (!mime.as_ref().is_empty() && self.inner.is_match(mime.as_ref()))\n\t}\n\n\t#[inline]\n\tpub fn any_file(&self) -> bool { self.is_star && !self.is_dir }\n\n\t#[inline]\n\tpub fn any_dir(&self) -> bool { self.is_star && self.is_dir }\n}\n\nimpl FromStr for Pattern {\n\ttype Err = anyhow::Error;\n\n\tfn from_str(s: &str) -> Result<Self, Self::Err> {\n\t\t// Trim leading case-sensitive indicator\n\t\tlet a = s.trim_start_matches(r\"\\s\");\n\n\t\t// Parse the URL scheme if present\n\t\tlet (scheme, skip) = PatternScheme::parse(a)?;\n\t\tlet b = &a[skip..];\n\n\t\t// Trim the ending slash which indicates a directory\n\t\tlet c = b.trim_end_matches('/');\n\n\t\t// Check whether it's a filename pattern or a full path pattern\n\t\tlet sep_lit = c.contains('/');\n\n\t\tlet inner = GlobBuilder::new(c)\n\t\t\t.case_insensitive(a.len() == s.len())\n\t\t\t.literal_separator(sep_lit)\n\t\t\t.backslash_escape(false)\n\t\t\t.empty_alternates(true)\n\t\t\t.build()?\n\t\t\t.compile_matcher();\n\n\t\tOk(Self {\n\t\t\tinner,\n\t\t\tscheme,\n\t\t\tis_dir: c.len() < b.len(),\n\t\t\tis_star: c == \"*\",\n\t\t\t#[cfg(windows)]\n\t\t\tsep_lit,\n\t\t})\n\t}\n}\n\nimpl TryFrom<String> for Pattern {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(s: String) -> Result<Self, Self::Error> { Self::from_str(s.as_str()) }\n}\n\n// --- Scheme\n#[derive(Clone, Copy, Debug)]\nenum PatternScheme {\n\tAny,\n\tLocal,\n\tRemote,\n\tVirtual,\n\n\tRegular,\n\tSearch,\n\tArchive,\n\tSftp,\n}\n\nimpl PatternScheme {\n\tfn parse(s: &str) -> Result<(Self, usize)> {\n\t\tlet Some((protocol, _)) = s.split_once(\"://\") else {\n\t\t\treturn Ok((Self::Any, 0));\n\t\t};\n\n\t\tlet scheme = match protocol {\n\t\t\t\"*\" => Self::Any,\n\t\t\t\"local\" => Self::Local,\n\t\t\t\"remote\" => Self::Remote,\n\t\t\t\"virtual\" => Self::Virtual,\n\n\t\t\t\"regular\" => Self::Regular,\n\t\t\t\"search\" => Self::Search,\n\t\t\t\"archive\" => Self::Archive,\n\t\t\t\"sftp\" => Self::Sftp,\n\n\t\t\t\"\" => bail!(\"Invalid URL pattern: protocol is empty\"),\n\t\t\t_ => bail!(\"Unknown protocol in URL pattern: {protocol}\"),\n\t\t};\n\n\t\tOk((scheme, protocol.len() + 3))\n\t}\n\n\t#[inline]\n\tfn matches(self, kind: SchemeKind) -> bool {\n\t\tuse SchemeKind as K;\n\n\t\tmatch (self, kind) {\n\t\t\t(Self::Any, _) => true,\n\t\t\t(Self::Local, s) => s.is_local(),\n\t\t\t(Self::Remote, s) => s.is_remote(),\n\t\t\t(Self::Virtual, s) => s.is_virtual(),\n\n\t\t\t(Self::Regular, K::Regular) => true,\n\t\t\t(Self::Search, K::Search) => true,\n\t\t\t(Self::Archive, K::Archive) => true,\n\t\t\t(Self::Sftp, K::Sftp) => true,\n\n\t\t\t_ => false,\n\t\t}\n\t}\n}\n\n// --- Tests\n#[cfg(test)]\nmod tests {\n\tuse yazi_shared::url::UrlCow;\n\n\tuse super::*;\n\n\tfn matches(glob: &str, url: &str) -> bool {\n\t\tPattern::from_str(glob).unwrap().match_url(UrlCow::try_from(url).unwrap(), false)\n\t}\n\n\t#[cfg(unix)]\n\t#[test]\n\tfn test_unix() {\n\t\t// Wildcard\n\t\tassert!(matches(\"*\", \"/foo\"));\n\t\tassert!(matches(\"*\", \"/foo/bar\"));\n\t\tassert!(matches(\"**\", \"foo\"));\n\t\tassert!(matches(\"**\", \"/foo\"));\n\t\tassert!(matches(\"**\", \"/foo/bar\"));\n\n\t\t// Filename\n\t\tassert!(matches(\"*.md\", \"foo.md\"));\n\t\tassert!(matches(\"*.md\", \"/foo.md\"));\n\t\tassert!(matches(\"*.md\", \"/foo/bar.md\"));\n\n\t\t// 1-star\n\t\tassert!(matches(\"/*\", \"/foo\"));\n\t\tassert!(matches(\"/*/*.md\", \"/foo/bar.md\"));\n\n\t\t// 2-star\n\t\tassert!(matches(\"/**\", \"/foo\"));\n\t\tassert!(matches(\"/**\", \"/foo/bar\"));\n\t\tassert!(matches(\"**/**\", \"/foo\"));\n\t\tassert!(matches(\"**/**\", \"/foo/bar\"));\n\t\tassert!(matches(\"/**/*\", \"/foo\"));\n\t\tassert!(matches(\"/**/*\", \"/foo/bar\"));\n\n\t\t// Failures\n\t\tassert!(!matches(\"/*/*\", \"/foo\"));\n\t\tassert!(!matches(\"/*/*.md\", \"/foo.md\"));\n\t\tassert!(!matches(\"/*\", \"/foo/bar\"));\n\t\tassert!(!matches(\"/*.md\", \"/foo/bar.md\"));\n\t}\n\n\t#[cfg(windows)]\n\t#[test]\n\tfn test_windows() {\n\t\t// Wildcard\n\t\tassert!(matches(\"*\", r#\"C:\\foo\"#));\n\t\tassert!(matches(\"*\", r#\"C:\\foo\\bar\"#));\n\t\tassert!(matches(\"**\", r#\"foo\"#));\n\t\tassert!(matches(\"**\", r#\"C:\\foo\"#));\n\t\tassert!(matches(\"**\", r#\"C:\\foo\\bar\"#));\n\n\t\t// Filename\n\t\tassert!(matches(\"*.md\", r#\"foo.md\"#));\n\t\tassert!(matches(\"*.md\", r#\"C:\\foo.md\"#));\n\t\tassert!(matches(\"*.md\", r#\"C:\\foo\\bar.md\"#));\n\n\t\t// 1-star\n\t\tassert!(matches(r#\"C:/*\"#, r#\"C:\\foo\"#));\n\t\tassert!(matches(r#\"C:/*/*.md\"#, r#\"C:\\foo\\bar.md\"#));\n\n\t\t// 2-star\n\t\tassert!(matches(r#\"C:/**\"#, r#\"C:\\foo\"#));\n\t\tassert!(matches(r#\"C:/**\"#, r#\"C:\\foo\\bar\"#));\n\t\tassert!(matches(r#\"**/**\"#, r#\"C:\\foo\"#));\n\t\tassert!(matches(r#\"**/**\"#, r#\"C:\\foo\\bar\"#));\n\t\tassert!(matches(r#\"C:/**/*\"#, r#\"C:\\foo\"#));\n\t\tassert!(matches(r#\"C:/**/*\"#, r#\"C:\\foo\\bar\"#));\n\n\t\t// Drive letter\n\t\tassert!(matches(r#\"*:/*\"#, r#\"C:\\foo\"#));\n\t\tassert!(matches(r#\"*:/**/*.md\"#, r#\"C:\\foo\\bar.md\"#));\n\n\t\t// Failures\n\t\tassert!(!matches(r#\"C:/*/*\"#, r#\"C:\\foo\"#));\n\t\tassert!(!matches(r#\"C:/*/*.md\"#, r#\"C:\\foo.md\"#));\n\t\tassert!(!matches(r#\"C:/*\"#, r#\"C:\\foo\\bar\"#));\n\t\tassert!(!matches(r#\"C:/*.md\"#, r#\"C:\\foo\\bar.md\"#));\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/platform.rs",
    "content": "#[inline]\npub(crate) fn check_for(r#for: Option<&str>) -> bool {\n\tmatch r#for.as_ref().map(|s| s.as_ref()) {\n\t\tSome(\"unix\") if cfg!(unix) => true,\n\t\tSome(os) if os == std::env::consts::OS => true,\n\t\tSome(_) => false,\n\t\tNone => true,\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/plugin/fetcher.rs",
    "content": "use serde::Deserialize;\nuse yazi_fs::File;\nuse yazi_shared::event::Action;\n\nuse crate::{Pattern, Priority};\n\n#[derive(Debug, Deserialize)]\npub struct Fetcher {\n\t#[serde(skip)]\n\tpub idx: u8,\n\n\tpub id:   String,\n\tpub url:  Option<Pattern>,\n\tpub mime: Option<Pattern>,\n\tpub run:  Action,\n\t#[serde(default)]\n\tpub prio: Priority,\n}\n\nimpl Fetcher {\n\t#[inline]\n\tpub fn matches(&self, file: &File, mime: &str) -> bool {\n\t\tself.mime.as_ref().is_some_and(|p| p.match_mime(mime))\n\t\t\t|| self.url.as_ref().is_some_and(|p| p.match_url(&file.url, file.is_dir()))\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/plugin/mod.rs",
    "content": "yazi_macro::mod_flat!(fetcher plugin preloader previewer spotter);\n\npub const MAX_FETCHERS: u8 = 16;\npub const MAX_PRELOADERS: u8 = 16;\n"
  },
  {
    "path": "yazi-config/src/plugin/plugin.rs",
    "content": "use anyhow::{Result, bail};\nuse hashbrown::HashSet;\nuse serde::Deserialize;\nuse tracing::warn;\nuse yazi_codegen::DeserializeOver2;\nuse yazi_fs::File;\n\nuse super::{Fetcher, Preloader, Previewer, Spotter};\nuse crate::{Preset, plugin::{MAX_FETCHERS, MAX_PRELOADERS}};\n\n#[derive(Default, Deserialize, DeserializeOver2)]\npub struct Plugin {\n\tpub fetchers:     Vec<Fetcher>,\n\t#[serde(default)]\n\tprepend_fetchers: Vec<Fetcher>,\n\t#[serde(default)]\n\tappend_fetchers:  Vec<Fetcher>,\n\n\tpub spotters:     Vec<Spotter>,\n\t#[serde(default)]\n\tprepend_spotters: Vec<Spotter>,\n\t#[serde(default)]\n\tappend_spotters:  Vec<Spotter>,\n\n\tpub preloaders:     Vec<Preloader>,\n\t#[serde(default)]\n\tprepend_preloaders: Vec<Preloader>,\n\t#[serde(default)]\n\tappend_preloaders:  Vec<Preloader>,\n\n\tpub previewers:     Vec<Previewer>,\n\t#[serde(default)]\n\tprepend_previewers: Vec<Previewer>,\n\t#[serde(default)]\n\tappend_previewers:  Vec<Previewer>,\n}\n\nimpl Plugin {\n\tpub fn fetchers<'a, 'b: 'a>(\n\t\t&'b self,\n\t\tfile: &'a File,\n\t\tmime: &'a str,\n\t) -> impl Iterator<Item = &'b Fetcher> + 'a {\n\t\tlet mut seen = HashSet::new();\n\t\tself.fetchers.iter().filter(move |&f| {\n\t\t\tif seen.contains(&f.id) || !f.matches(file, mime) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tseen.insert(&f.id);\n\t\t\ttrue\n\t\t})\n\t}\n\n\tpub fn mime_fetchers(&self, files: Vec<File>) -> impl Iterator<Item = (&Fetcher, Vec<File>)> {\n\t\tlet mut tasks: [Vec<_>; MAX_FETCHERS as usize] = Default::default();\n\t\tfor f in files {\n\t\t\tlet found = self.fetchers.iter().find(|&g| g.id == \"mime\" && g.matches(&f, \"\"));\n\t\t\tif let Some(g) = found {\n\t\t\t\ttasks[g.idx as usize].push(f);\n\t\t\t} else {\n\t\t\t\twarn!(\"No mime fetcher for {f:?}\");\n\t\t\t}\n\t\t}\n\n\t\ttasks.into_iter().enumerate().filter_map(|(i, tasks)| {\n\t\t\tif tasks.is_empty() { None } else { Some((&self.fetchers[i], tasks)) }\n\t\t})\n\t}\n\n\tpub fn spotter(&self, file: &File, mime: &str) -> Option<&Spotter> {\n\t\tself.spotters.iter().find(|&p| p.matches(file, mime))\n\t}\n\n\tpub fn preloaders<'a, 'b: 'a>(\n\t\t&'b self,\n\t\tfile: &'a File,\n\t\tmime: &'a str,\n\t) -> impl Iterator<Item = &'b Preloader> + 'a {\n\t\tlet mut next = true;\n\t\tself.preloaders.iter().filter(move |&p| {\n\t\t\tif !next || !p.matches(file, mime) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tnext = p.next;\n\t\t\ttrue\n\t\t})\n\t}\n\n\tpub fn previewer(&self, file: &File, mime: &str) -> Option<&Previewer> {\n\t\tself.previewers.iter().find(|&p| p.matches(file, mime))\n\t}\n}\n\nimpl Plugin {\n\t// TODO: remove .retain() and .collect()\n\tpub(crate) fn reshape(mut self) -> Result<Self> {\n\t\tif self.append_spotters.iter().any(|r| r.any_file()) {\n\t\t\tself.spotters.retain(|r| !r.any_file());\n\t\t}\n\t\tif self.append_spotters.iter().any(|r| r.any_dir()) {\n\t\t\tself.spotters.retain(|r| !r.any_dir());\n\t\t}\n\t\tif self.append_previewers.iter().any(|r| r.any_file()) {\n\t\t\tself.previewers.retain(|r| !r.any_file());\n\t\t}\n\t\tif self.append_previewers.iter().any(|r| r.any_dir()) {\n\t\t\tself.previewers.retain(|r| !r.any_dir());\n\t\t}\n\n\t\tself.fetchers =\n\t\t\tPreset::mix(self.prepend_fetchers, self.fetchers, self.append_fetchers).collect();\n\t\tself.spotters =\n\t\t\tPreset::mix(self.prepend_spotters, self.spotters, self.append_spotters).collect();\n\t\tself.preloaders =\n\t\t\tPreset::mix(self.prepend_preloaders, self.preloaders, self.append_preloaders).collect();\n\t\tself.previewers =\n\t\t\tPreset::mix(self.prepend_previewers, self.previewers, self.append_previewers).collect();\n\n\t\tif self.fetchers.len() > MAX_FETCHERS as usize {\n\t\t\tbail!(\"Fetchers exceed the limit of {MAX_FETCHERS}\");\n\t\t} else if self.preloaders.len() > MAX_PRELOADERS as usize {\n\t\t\tbail!(\"Preloaders exceed the limit of {MAX_PRELOADERS}\");\n\t\t}\n\n\t\tfor (i, p) in self.fetchers.iter_mut().enumerate() {\n\t\t\tp.idx = i as u8;\n\t\t}\n\t\tfor (i, p) in self.preloaders.iter_mut().enumerate() {\n\t\t\tp.idx = i as u8;\n\t\t}\n\n\t\tOk(Self {\n\t\t\tfetchers: self.fetchers,\n\t\t\tspotters: self.spotters,\n\t\t\tpreloaders: self.preloaders,\n\t\t\tpreviewers: self.previewers,\n\t\t\t..Default::default()\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/plugin/preloader.rs",
    "content": "use serde::Deserialize;\nuse yazi_fs::File;\nuse yazi_shared::event::Action;\n\nuse crate::{Pattern, Priority};\n\n#[derive(Debug, Deserialize)]\npub struct Preloader {\n\t#[serde(skip)]\n\tpub idx: u8,\n\n\tpub url:  Option<Pattern>,\n\tpub mime: Option<Pattern>,\n\tpub run:  Action,\n\t#[serde(default)]\n\tpub next: bool,\n\t#[serde(default)]\n\tpub prio: Priority,\n}\n\nimpl Preloader {\n\t#[inline]\n\tpub fn matches(&self, file: &File, mime: &str) -> bool {\n\t\tself.mime.as_ref().is_some_and(|p| p.match_mime(mime))\n\t\t\t|| self.url.as_ref().is_some_and(|p| p.match_url(&file.url, file.is_dir()))\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/plugin/previewer.rs",
    "content": "use serde::Deserialize;\nuse yazi_fs::File;\nuse yazi_shared::event::Action;\n\nuse crate::Pattern;\n\n#[derive(Debug, Deserialize)]\npub struct Previewer {\n\tpub url:  Option<Pattern>,\n\tpub mime: Option<Pattern>,\n\tpub run:  Action,\n}\n\nimpl Previewer {\n\t#[inline]\n\tpub fn matches(&self, file: &File, mime: &str) -> bool {\n\t\tself.mime.as_ref().is_some_and(|p| p.match_mime(mime))\n\t\t\t|| self.url.as_ref().is_some_and(|p| p.match_url(&file.url, file.is_dir()))\n\t}\n\n\t#[inline]\n\tpub fn any_file(&self) -> bool { self.url.as_ref().is_some_and(|p| p.any_file()) }\n\n\t#[inline]\n\tpub fn any_dir(&self) -> bool { self.url.as_ref().is_some_and(|p| p.any_dir()) }\n}\n"
  },
  {
    "path": "yazi-config/src/plugin/spotter.rs",
    "content": "use serde::Deserialize;\nuse yazi_fs::File;\nuse yazi_shared::event::Action;\n\nuse crate::Pattern;\n\n#[derive(Debug, Deserialize)]\npub struct Spotter {\n\tpub url:  Option<Pattern>,\n\tpub mime: Option<Pattern>,\n\tpub run:  Action,\n}\n\nimpl Spotter {\n\t#[inline]\n\tpub fn matches(&self, file: &File, mime: &str) -> bool {\n\t\tself.mime.as_ref().is_some_and(|p| p.match_mime(mime))\n\t\t\t|| self.url.as_ref().is_some_and(|p| p.match_url(&file.url, file.is_dir()))\n\t}\n\n\t#[inline]\n\tpub fn any_file(&self) -> bool { self.url.as_ref().is_some_and(|p| p.any_file()) }\n\n\t#[inline]\n\tpub fn any_dir(&self) -> bool { self.url.as_ref().is_some_and(|p| p.any_dir()) }\n}\n"
  },
  {
    "path": "yazi-config/src/popup/confirm.rs",
    "content": "use serde::Deserialize;\nuse yazi_codegen::DeserializeOver2;\n\nuse super::{Offset, Origin};\nuse crate::popup::Position;\n\n#[derive(Deserialize, DeserializeOver2)]\npub struct Confirm {\n\t// trash\n\tpub trash_title:  String,\n\tpub trash_origin: Origin,\n\tpub trash_offset: Offset,\n\n\t// delete\n\tpub delete_title:  String,\n\tpub delete_origin: Origin,\n\tpub delete_offset: Offset,\n\n\t// overwrite\n\tpub overwrite_title:  String,\n\tpub overwrite_body:   String,\n\tpub overwrite_origin: Origin,\n\tpub overwrite_offset: Offset,\n\n\t// quit\n\tpub quit_title:  String,\n\tpub quit_body:   String,\n\tpub quit_origin: Origin,\n\tpub quit_offset: Offset,\n}\n\nimpl Confirm {\n\tpub const fn border(&self) -> u16 { 2 }\n\n\tpub const fn trash_position(&self) -> Position {\n\t\tPosition::new(self.trash_origin, self.trash_offset)\n\t}\n\n\tpub const fn delete_position(&self) -> Position {\n\t\tPosition::new(self.delete_origin, self.delete_offset)\n\t}\n\n\tpub const fn overwrite_position(&self) -> Position {\n\t\tPosition::new(self.overwrite_origin, self.overwrite_offset)\n\t}\n\n\tpub const fn quit_position(&self) -> Position {\n\t\tPosition::new(self.quit_origin, self.quit_offset)\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/popup/input.rs",
    "content": "use serde::Deserialize;\nuse yazi_codegen::DeserializeOver2;\n\nuse super::{Offset, Origin};\n\n#[derive(Deserialize, DeserializeOver2)]\npub struct Input {\n\tpub cursor_blink: bool,\n\n\t// cd\n\tpub cd_title:  String,\n\tpub cd_origin: Origin,\n\tpub cd_offset: Offset,\n\n\t// create\n\tpub create_title:  [String; 2],\n\tpub create_origin: Origin,\n\tpub create_offset: Offset,\n\n\t// rename\n\tpub rename_title:  String,\n\tpub rename_origin: Origin,\n\tpub rename_offset: Offset,\n\n\t// filter\n\tpub filter_title:  String,\n\tpub filter_origin: Origin,\n\tpub filter_offset: Offset,\n\n\t// find\n\tpub find_title:  [String; 2],\n\tpub find_origin: Origin,\n\tpub find_offset: Offset,\n\n\t// search\n\tpub search_title:  String,\n\tpub search_origin: Origin,\n\tpub search_offset: Offset,\n\n\t// shell\n\tpub shell_title:  [String; 2],\n\tpub shell_origin: Origin,\n\tpub shell_offset: Offset,\n}\n\nimpl Input {\n\tpub const fn border(&self) -> u16 { 2 }\n}\n"
  },
  {
    "path": "yazi-config/src/popup/mod.rs",
    "content": "yazi_macro::mod_flat!(confirm input offset options origin pick position);\n"
  },
  {
    "path": "yazi-config/src/popup/offset.rs",
    "content": "use anyhow::bail;\nuse serde::Deserialize;\n\n#[derive(Clone, Copy, Debug, Default, Deserialize)]\n#[serde(try_from = \"[i16; 4]\")]\npub struct Offset {\n\tpub x:      i16,\n\tpub y:      i16,\n\tpub width:  u16,\n\tpub height: u16,\n}\n\nimpl TryFrom<[i16; 4]> for Offset {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(values: [i16; 4]) -> Result<Self, Self::Error> {\n\t\tif values.len() != 4 {\n\t\t\tbail!(\"offset must have 4 values: {:?}\", values);\n\t\t}\n\t\tif values[2] < 0 || values[3] < 0 {\n\t\t\tbail!(\"offset width and height must be positive: {:?}\", values);\n\t\t}\n\t\tif values[3] < 3 {\n\t\t\tbail!(\"offset height must be at least 3: {:?}\", values);\n\t\t}\n\n\t\tOk(Self {\n\t\t\tx:      values[0],\n\t\t\ty:      values[1],\n\t\t\twidth:  values[2] as u16,\n\t\t\theight: values[3] as u16,\n\t\t})\n\t}\n}\n\nimpl Offset {\n\t#[inline]\n\tpub fn line() -> Self { Self { x: 0, y: 0, width: u16::MAX, height: 1 } }\n}\n"
  },
  {
    "path": "yazi-config/src/popup/options.rs",
    "content": "use ratatui::{text::{Line, Text}, widgets::{Paragraph, Wrap}};\nuse yazi_shared::{scheme::Encode as EncodeScheme, strand::ToStrand, url::{Url, UrlBuf}};\n\nuse super::{Offset, Position};\nuse crate::{YAZI, popup::Origin};\n\n#[derive(Clone, Debug, Default)]\npub struct InputCfg {\n\tpub title:      String,\n\tpub value:      String,\n\tpub cursor:     Option<usize>,\n\tpub obscure:    bool,\n\tpub position:   Position,\n\tpub realtime:   bool,\n\tpub completion: bool,\n}\n\n#[derive(Clone, Debug, Default)]\npub struct PickCfg {\n\tpub title:    String,\n\tpub items:    Vec<String>,\n\tpub position: Position,\n}\n\n#[derive(Clone, Debug, Default)]\npub struct ConfirmCfg {\n\tpub position: Position,\n\tpub title:    Line<'static>,\n\tpub body:     Paragraph<'static>,\n\tpub list:     Paragraph<'static>,\n}\n\nimpl InputCfg {\n\tpub fn cd(cwd: Url) -> Self {\n\t\tSelf {\n\t\t\ttitle: YAZI.input.cd_title.clone(),\n\t\t\tvalue: if cwd.kind().is_local() { String::new() } else { EncodeScheme(cwd).to_string() },\n\t\t\tposition: Position::new(YAZI.input.cd_origin, YAZI.input.cd_offset),\n\t\t\tcompletion: true,\n\t\t\t..Default::default()\n\t\t}\n\t}\n\n\tpub fn create(dir: bool) -> Self {\n\t\tSelf {\n\t\t\ttitle: YAZI.input.create_title[dir as usize].clone(),\n\t\t\tposition: Position::new(YAZI.input.create_origin, YAZI.input.create_offset),\n\t\t\t..Default::default()\n\t\t}\n\t}\n\n\tpub fn rename() -> Self {\n\t\tSelf {\n\t\t\ttitle: YAZI.input.rename_title.clone(),\n\t\t\tposition: Position::new(YAZI.input.rename_origin, YAZI.input.rename_offset),\n\t\t\t..Default::default()\n\t\t}\n\t}\n\n\tpub fn filter() -> Self {\n\t\tSelf {\n\t\t\ttitle: YAZI.input.filter_title.clone(),\n\t\t\tposition: Position::new(YAZI.input.filter_origin, YAZI.input.filter_offset),\n\t\t\trealtime: true,\n\t\t\t..Default::default()\n\t\t}\n\t}\n\n\tpub fn find(prev: bool) -> Self {\n\t\tSelf {\n\t\t\ttitle: YAZI.input.find_title[prev as usize].clone(),\n\t\t\tposition: Position::new(YAZI.input.find_origin, YAZI.input.find_offset),\n\t\t\trealtime: true,\n\t\t\t..Default::default()\n\t\t}\n\t}\n\n\tpub fn search(name: &str) -> Self {\n\t\tSelf {\n\t\t\ttitle: YAZI.input.search_title.replace(\"{n}\", name),\n\t\t\tposition: Position::new(YAZI.input.search_origin, YAZI.input.search_offset),\n\t\t\t..Default::default()\n\t\t}\n\t}\n\n\tpub fn shell(block: bool) -> Self {\n\t\tSelf {\n\t\t\ttitle: YAZI.input.shell_title[block as usize].clone(),\n\t\t\tposition: Position::new(YAZI.input.shell_origin, YAZI.input.shell_offset),\n\t\t\t..Default::default()\n\t\t}\n\t}\n\n\tpub fn tab_rename() -> Self {\n\t\tSelf {\n\t\t\ttitle: \"Rename tab:\".to_owned(),\n\t\t\tposition: Position::new(Origin::TopCenter, Offset {\n\t\t\t\tx:      0,\n\t\t\t\ty:      2,\n\t\t\t\twidth:  50,\n\t\t\t\theight: 3,\n\t\t\t}),\n\t\t\t..Default::default()\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn with_value(mut self, value: impl Into<String>) -> Self {\n\t\tself.value = value.into();\n\t\tself\n\t}\n\n\t#[inline]\n\tpub fn with_cursor(mut self, cursor: Option<usize>) -> Self {\n\t\tself.cursor = cursor;\n\t\tself\n\t}\n}\n\nimpl ConfirmCfg {\n\tfn new(\n\t\ttitle: String,\n\t\tposition: Position,\n\t\tbody: Option<Text<'static>>,\n\t\tlist: Option<Text<'static>>,\n\t) -> Self {\n\t\tSelf {\n\t\t\tposition,\n\t\t\ttitle: Line::raw(title),\n\t\t\tbody: body.map(|b| Paragraph::new(b).wrap(Wrap { trim: false })).unwrap_or_default(),\n\t\t\tlist: list.map(|l| Paragraph::new(l).wrap(Wrap { trim: false })).unwrap_or_default(),\n\t\t}\n\t}\n\n\tpub fn trash(urls: &[yazi_shared::url::UrlBuf]) -> Self {\n\t\tSelf::new(\n\t\t\tSelf::replace_number(&YAZI.confirm.trash_title, urls.len()),\n\t\t\tYAZI.confirm.trash_position(),\n\t\t\tNone,\n\t\t\tSelf::truncate_list(urls, urls.len(), 100),\n\t\t)\n\t}\n\n\tpub fn delete(urls: &[yazi_shared::url::UrlBuf]) -> Self {\n\t\tSelf::new(\n\t\t\tSelf::replace_number(&YAZI.confirm.delete_title, urls.len()),\n\t\t\tYAZI.confirm.delete_position(),\n\t\t\tNone,\n\t\t\tSelf::truncate_list(urls, urls.len(), 100),\n\t\t)\n\t}\n\n\tpub fn overwrite(url: &UrlBuf) -> Self {\n\t\tSelf::new(\n\t\t\tYAZI.confirm.overwrite_title.clone(),\n\t\t\tYAZI.confirm.overwrite_position(),\n\t\t\tSome(Text::raw(&YAZI.confirm.overwrite_body)),\n\t\t\tSome(url.to_strand().into_string_lossy().into()),\n\t\t)\n\t}\n\n\tpub fn quit(len: usize, names: Vec<String>) -> Self {\n\t\tSelf::new(\n\t\t\tSelf::replace_number(&YAZI.confirm.quit_title, len),\n\t\t\tYAZI.confirm.quit_position(),\n\t\t\tSome(Text::raw(&YAZI.confirm.quit_body)),\n\t\t\tSelf::truncate_list(names, len, 10),\n\t\t)\n\t}\n\n\tfn replace_number(tpl: &str, n: usize) -> String {\n\t\ttpl.replace(\"{n}\", &n.to_string()).replace(\"{s}\", if n > 1 { \"s\" } else { \"\" })\n\t}\n\n\tfn truncate_list<I>(it: I, len: usize, max: usize) -> Option<Text<'static>>\n\twhere\n\t\tI: IntoIterator,\n\t\tI::Item: ToStrand,\n\t{\n\t\tlet mut lines = Vec::with_capacity(len.min(max + 1));\n\t\tfor (i, s) in it.into_iter().enumerate() {\n\t\t\tif i >= max {\n\t\t\t\tlines.push(format!(\"... and {} more\", len - max));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tlines.push(s.to_strand().into_string_lossy());\n\t\t}\n\t\tSome(Text::from_iter(lines))\n\t}\n}\n\nimpl PickCfg {\n\tfn max_height(len: usize) -> u16 {\n\t\tYAZI.pick.open_offset.height.min(YAZI.pick.border().saturating_add(len as u16))\n\t}\n\n\tpub fn open(items: Vec<String>) -> Self {\n\t\tlet max_height = Self::max_height(items.len());\n\t\tSelf {\n\t\t\ttitle: YAZI.pick.open_title.clone(),\n\t\t\titems,\n\t\t\tposition: Position::new(YAZI.pick.open_origin, Offset {\n\t\t\t\theight: max_height,\n\t\t\t\t..YAZI.pick.open_offset\n\t\t\t}),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/popup/origin.rs",
    "content": "use std::{fmt::Display, str::FromStr};\n\nuse serde::Deserialize;\n\n#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]\n#[serde(rename_all = \"kebab-case\")]\npub enum Origin {\n\t#[default]\n\tTopLeft,\n\tTopCenter,\n\tTopRight,\n\n\tBottomLeft,\n\tBottomCenter,\n\tBottomRight,\n\n\tCenter,\n\tHovered,\n}\n\nimpl Display for Origin {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tf.write_str(match self {\n\t\t\tSelf::TopLeft => \"top-left\",\n\t\t\tSelf::TopCenter => \"top-center\",\n\t\t\tSelf::TopRight => \"top-right\",\n\n\t\t\tSelf::BottomLeft => \"bottom-left\",\n\t\t\tSelf::BottomCenter => \"bottom-center\",\n\t\t\tSelf::BottomRight => \"bottom-right\",\n\n\t\t\tSelf::Center => \"center\",\n\t\t\tSelf::Hovered => \"hovered\",\n\t\t})\n\t}\n}\n\nimpl FromStr for Origin {\n\ttype Err = serde::de::value::Error;\n\n\tfn from_str(s: &str) -> Result<Self, Self::Err> {\n\t\tSelf::deserialize(serde::de::value::StrDeserializer::new(s))\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/popup/pick.rs",
    "content": "use serde::Deserialize;\nuse yazi_codegen::DeserializeOver2;\n\nuse super::{Offset, Origin};\n\n#[derive(Deserialize, DeserializeOver2)]\npub struct Pick {\n\t// open\n\tpub open_title:  String,\n\tpub open_origin: Origin,\n\tpub open_offset: Offset,\n}\n\nimpl Pick {\n\tpub const fn border(&self) -> u16 { 2 }\n}\n"
  },
  {
    "path": "yazi-config/src/popup/position.rs",
    "content": "use crossterm::terminal::WindowSize;\nuse ratatui::layout::Rect;\n\nuse super::{Offset, Origin};\n\n#[derive(Clone, Copy, Debug, Default)]\npub struct Position {\n\tpub origin: Origin,\n\tpub offset: Offset,\n}\n\nimpl Position {\n\tpub const fn new(origin: Origin, offset: Offset) -> Self { Self { origin, offset } }\n\n\tpub fn rect(&self, WindowSize { columns, rows, .. }: WindowSize) -> Rect {\n\t\tuse Origin::*;\n\t\tlet Offset { x, y, width, height } = self.offset;\n\n\t\tlet max_x = columns.saturating_sub(width);\n\t\tlet new_x = match self.origin {\n\t\t\tTopLeft | BottomLeft => x.clamp(0, max_x as i16) as u16,\n\t\t\tTopCenter | BottomCenter | Center => {\n\t\t\t\t(columns / 2).saturating_sub(width / 2).saturating_add_signed(x).clamp(0, max_x)\n\t\t\t}\n\t\t\tTopRight | BottomRight => max_x.saturating_add_signed(x).clamp(0, max_x),\n\t\t\tHovered => unreachable!(),\n\t\t};\n\n\t\tlet max_y = rows.saturating_sub(height);\n\t\tlet new_y = match self.origin {\n\t\t\tTopLeft | TopCenter | TopRight => y.clamp(0, max_y as i16) as u16,\n\t\t\tCenter => (rows / 2).saturating_sub(height / 2).saturating_add_signed(y).clamp(0, max_y),\n\t\t\tBottomLeft | BottomCenter | BottomRight => max_y.saturating_add_signed(y).clamp(0, max_y),\n\t\t\tHovered => unreachable!(),\n\t\t};\n\n\t\tRect {\n\t\t\tx:      new_x,\n\t\t\ty:      new_y,\n\t\t\twidth:  width.min(columns.saturating_sub(new_x)),\n\t\t\theight: height.min(rows.saturating_sub(new_y)),\n\t\t}\n\t}\n\n\tpub fn sticky(WindowSize { columns, rows, .. }: WindowSize, base: Rect, offset: Offset) -> Rect {\n\t\tlet Offset { x, y, width, height } = offset;\n\n\t\tlet above =\n\t\t\tbase.y.saturating_add(base.height).saturating_add(height).saturating_add_signed(y) > rows;\n\n\t\tlet new_x = base.x.saturating_add_signed(x).clamp(0, columns.saturating_sub(width));\n\t\tlet new_y = if above {\n\t\t\tbase.y.saturating_sub(height.saturating_sub(y.unsigned_abs()))\n\t\t} else {\n\t\t\tbase.y.saturating_add(base.height).saturating_add_signed(y)\n\t\t};\n\n\t\tRect {\n\t\t\tx:      new_x,\n\t\t\ty:      new_y,\n\t\t\twidth:  width.min(columns.saturating_sub(new_x)),\n\t\t\theight: height.min(rows.saturating_sub(new_y)),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/preset.rs",
    "content": "use crate::{Yazi, keymap::Keymap, theme::Theme, vfs::Vfs};\n\npub(crate) struct Preset;\n\nimpl Preset {\n\tpub(super) fn yazi() -> Result<Yazi, toml::de::Error> {\n\t\ttoml::from_str(&yazi_macro::config_preset!(\"yazi\"))\n\t}\n\n\tpub(super) fn keymap() -> Result<Keymap, toml::de::Error> {\n\t\ttoml::from_str(&yazi_macro::config_preset!(\"keymap\"))\n\t}\n\n\tpub(super) fn theme(light: bool) -> Result<Theme, toml::de::Error> {\n\t\ttoml::from_str(&if light {\n\t\t\tyazi_macro::theme_preset!(\"light\")\n\t\t} else {\n\t\t\tyazi_macro::theme_preset!(\"dark\")\n\t\t})\n\t}\n\n\tpub(super) fn vfs() -> Result<Vfs, toml::de::Error> {\n\t\ttoml::from_str(&yazi_macro::config_preset!(\"vfs\"))\n\t}\n\n\t#[inline]\n\tpub(crate) fn mix<E, A, B, C>(a: A, b: B, c: C) -> impl Iterator<Item = E>\n\twhere\n\t\tA: IntoIterator<Item = E>,\n\t\tB: IntoIterator<Item = E>,\n\t\tC: IntoIterator<Item = E>,\n\t{\n\t\ta.into_iter().chain(b).chain(c)\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/preview/mod.rs",
    "content": "yazi_macro::mod_flat!(preview wrap);\n"
  },
  {
    "path": "yazi-config/src/preview/preview.rs",
    "content": "use std::path::PathBuf;\n\nuse anyhow::{Context, Result, bail};\nuse serde::{Deserialize, Serialize};\nuse yazi_codegen::DeserializeOver2;\nuse yazi_fs::Xdg;\nuse yazi_shared::{SStr, timestamp_us};\n\nuse super::PreviewWrap;\nuse crate::normalize_path;\n\n#[derive(Debug, Deserialize, DeserializeOver2, Serialize)]\npub struct Preview {\n\tpub wrap:       PreviewWrap,\n\tpub tab_size:   u8,\n\tpub max_width:  u16,\n\tpub max_height: u16,\n\n\tpub cache_dir: PathBuf,\n\n\tpub image_delay:   u8,\n\tpub image_filter:  String,\n\tpub image_quality: u8,\n\n\tpub ueberzug_scale:  f32,\n\tpub ueberzug_offset: (f32, f32, f32, f32),\n}\n\nimpl Preview {\n\tpub fn tmpfile(&self, prefix: &str) -> PathBuf {\n\t\tself.cache_dir.join(format!(\"{prefix}-{}\", timestamp_us()))\n\t}\n\n\tpub fn indent(&self) -> SStr {\n\t\t#[rustfmt::skip]\n\t\tconst TABS: &[&str] = &[\"\", \" \", \"  \", \"   \", \"    \", \"     \", \"      \", \"       \", \"        \", \"         \", \"          \", \"           \", \"            \", \"             \", \"              \", \"               \", \"                \"];\n\n\t\tif let Some(&s) = TABS.get(self.tab_size as usize) {\n\t\t\ts.into()\n\t\t} else {\n\t\t\t\" \".repeat(self.tab_size as usize).into()\n\t\t}\n\t}\n}\n\nimpl Preview {\n\tpub(crate) fn reshape(mut self) -> Result<Self> {\n\t\tif self.image_delay > 100 {\n\t\t\tbail!(\"[preview].image_delay must be between 0 and 100.\");\n\t\t} else if self.image_quality < 50 || self.image_quality > 90 {\n\t\t\tbail!(\"[preview].image_quality must be between 50 and 90.\");\n\t\t}\n\n\t\tself.cache_dir = if self.cache_dir.as_os_str().is_empty() {\n\t\t\tXdg::cache_dir().to_owned()\n\t\t} else if let Some(p) = normalize_path(self.cache_dir) {\n\t\t\tp\n\t\t} else {\n\t\t\tbail!(\"[preview].cache_dir must be either empty or an absolute path.\");\n\t\t};\n\n\t\tstd::fs::create_dir_all(&self.cache_dir)\n\t\t\t.context(format!(\"Failed to create cache directory: {}\", self.cache_dir.display()))?;\n\n\t\tOk(self)\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/preview/wrap.rs",
    "content": "use serde::{Deserialize, Serialize};\n\n#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]\n#[serde(rename_all = \"kebab-case\")]\npub enum PreviewWrap {\n\tNo,\n\tYes,\n}\n\nimpl From<PreviewWrap> for Option<ratatui::widgets::Wrap> {\n\tfn from(wrap: PreviewWrap) -> Self {\n\t\tmatch wrap {\n\t\t\tPreviewWrap::No => None,\n\t\t\tPreviewWrap::Yes => Some(ratatui::widgets::Wrap { trim: false }),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/priority.rs",
    "content": "use serde::Deserialize;\n\n#[derive(Clone, Copy, Debug, Default, Deserialize)]\n#[serde(rename_all = \"kebab-case\")]\npub enum Priority {\n\tLow    = 0,\n\t#[default]\n\tNormal = 1,\n\tHigh   = 2,\n}\n"
  },
  {
    "path": "yazi-config/src/style.rs",
    "content": "use ratatui::style::Color;\nuse serde::{Deserialize, Serialize};\n\n#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)]\npub struct Style {\n\tpub fg:          Option<Color>,\n\tpub bg:          Option<Color>,\n\tpub bold:        Option<bool>,\n\tpub dim:         Option<bool>,\n\tpub italic:      Option<bool>,\n\tpub underline:   Option<bool>,\n\tpub blink:       Option<bool>,\n\tpub blink_rapid: Option<bool>,\n\tpub reversed:    Option<bool>,\n\tpub hidden:      Option<bool>,\n\tpub crossed:     Option<bool>,\n}\n\nimpl From<ratatui::style::Style> for Style {\n\t#[rustfmt::skip]\n\tfn from(value: ratatui::style::Style) -> Self {\n\t\tuse ratatui::style::Modifier as M;\n\n\t\tlet sub = value.sub_modifier;\n\t\tlet all = value.add_modifier - sub;\n\n\t\tSelf {\n\t\t\tfg:          value.fg,\n\t\t\tbg:          value.bg,\n\t\t\tbold:        if all.contains(M::BOLD) { Some(true) } else if sub.contains(M::BOLD) { Some(false) } else { None },\n\t\t\tdim:         if all.contains(M::DIM) { Some(true) } else if sub.contains(M::DIM) { Some(false) } else { None },\n\t\t\titalic:      if all.contains(M::ITALIC) { Some(true) } else if sub.contains(M::ITALIC) { Some(false) } else { None },\n\t\t\tunderline:   if all.contains(M::UNDERLINED) { Some(true) } else if sub.contains(M::UNDERLINED) { Some(false) } else { None },\n\t\t\tblink:       if all.contains(M::SLOW_BLINK) { Some(true) } else if sub.contains(M::SLOW_BLINK) { Some(false) } else { None },\n\t\t\tblink_rapid: if all.contains(M::RAPID_BLINK) { Some(true) } else if sub.contains(M::RAPID_BLINK) { Some(false) } else { None },\n\t\t\treversed:    if all.contains(M::REVERSED) { Some(true) } else if sub.contains(M::REVERSED) { Some(false) } else { None },\n\t\t\thidden:      if all.contains(M::HIDDEN) { Some(true) } else if sub.contains(M::HIDDEN) { Some(false) } else { None },\n\t\t\tcrossed:     if all.contains(M::CROSSED_OUT) { Some(true) } else if sub.contains(M::CROSSED_OUT) { Some(false) } else { None },\n\t\t}\n\t}\n}\n\nimpl From<Style> for ratatui::style::Style {\n\t#[rustfmt::skip]\n\tfn from(value: Style) -> Self {\n\t\tuse ratatui::style::Modifier as M;\n\n\t\tlet (mut add, mut sub) = (M::empty(), M::empty());\n\t\tif let Some(b) = value.bold {\n\t\t\tif b { add |= M::BOLD } else { sub |= M::BOLD };\n\t\t}\n\t\tif let Some(b) = value.dim {\n\t\t\tif b { add |= M::DIM } else { sub |= M::DIM };\n\t\t}\n\t\tif let Some(b) = value.italic {\n\t\t\tif b { add |= M::ITALIC } else { sub |= M::ITALIC };\n\t\t}\n\t\tif let Some(b) = value.underline {\n\t\t\tif b { add |= M::UNDERLINED } else { sub |= M::UNDERLINED };\n\t\t}\n\t\tif let Some(b) = value.blink {\n\t\t\tif b { add |= M::SLOW_BLINK } else { sub |= M::SLOW_BLINK };\n\t\t}\n\t\tif let Some(b) = value.blink_rapid {\n\t\t\tif b { add |= M::RAPID_BLINK } else { sub |= M::RAPID_BLINK };\n\t\t}\n\t\tif let Some(b) = value.reversed {\n\t\t\tif b { add |= M::REVERSED } else { sub |= M::REVERSED };\n\t\t}\n\t\tif let Some(b) = value.hidden {\n\t\t\tif b { add |= M::HIDDEN } else { sub |= M::HIDDEN };\n\t\t}\n\t\tif let Some(b) = value.crossed {\n\t\t\tif b { add |= M::CROSSED_OUT } else { sub |= M::CROSSED_OUT };\n\t\t}\n\n\t\tSelf {\n\t\t\tfg:              value.fg,\n\t\t\tbg:              value.bg,\n\t\t\tunderline_color: None,\n\t\t\tadd_modifier:    add,\n\t\t\tsub_modifier:    sub,\n\t\t}\n\t}\n}\n\nimpl Style {\n\tpub fn derive(self, other: ratatui::style::Style) -> ratatui::style::Style {\n\t\tratatui::style::Style::from(self).patch(other)\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/tasks/mod.rs",
    "content": "yazi_macro::mod_flat!(tasks);\n"
  },
  {
    "path": "yazi-config/src/tasks/tasks.rs",
    "content": "use anyhow::{Result, bail};\nuse serde::Deserialize;\nuse yazi_codegen::DeserializeOver2;\n\n#[derive(Debug, Deserialize, DeserializeOver2)]\npub struct Tasks {\n\tpub file_workers:    u8,\n\tpub plugin_workers:  u8,\n\tpub fetch_workers:   u8,\n\tpub preload_workers: u8,\n\tpub process_workers: u8,\n\n\tpub bizarre_retry: u8,\n\n\tpub image_alloc: u32,\n\tpub image_bound: [u16; 2],\n\n\tpub suppress_preload: bool,\n}\n\nimpl Tasks {\n\tpub(crate) fn reshape(self) -> Result<Self> {\n\t\tif self.file_workers < 1 {\n\t\t\tbail!(\"[tasks].file_workers must be at least 1.\");\n\t\t} else if self.plugin_workers < 1 {\n\t\t\tbail!(\"[tasks].plugin_workers must be at least 1.\");\n\t\t} else if self.fetch_workers < 1 {\n\t\t\tbail!(\"[tasks].fetch_workers must be at least 1.\");\n\t\t} else if self.preload_workers < 1 {\n\t\t\tbail!(\"[tasks].preload_workers must be at least 1.\");\n\t\t} else if self.process_workers < 1 {\n\t\t\tbail!(\"[tasks].process_workers must be at least 1.\");\n\t\t} else if self.bizarre_retry < 1 {\n\t\t\tbail!(\"[tasks].bizarre_retry must be at least 1.\");\n\t\t}\n\t\tOk(self)\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/theme/filetype.rs",
    "content": "use std::ops::Deref;\n\nuse serde::Deserialize;\nuse yazi_codegen::DeserializeOver2;\nuse yazi_fs::File;\n\nuse super::Is;\nuse crate::{Pattern, Style};\n\n#[derive(Deserialize, DeserializeOver2)]\npub struct Filetype {\n\trules: Vec<FiletypeRule>,\n}\n\nimpl Deref for Filetype {\n\ttype Target = Vec<FiletypeRule>;\n\n\tfn deref(&self) -> &Self::Target { &self.rules }\n}\n\n#[derive(Deserialize)]\npub struct FiletypeRule {\n\t#[serde(default)]\n\tis:        Is,\n\turl:       Option<Pattern>,\n\tmime:      Option<Pattern>,\n\t#[serde(flatten)]\n\tpub style: Style,\n}\n\nimpl FiletypeRule {\n\tpub fn matches(&self, file: &File, mime: &str) -> bool {\n\t\tif !self.is.check(&file.cha) {\n\t\t\treturn false;\n\t\t}\n\n\t\tself.mime.as_ref().is_some_and(|p| p.match_mime(mime))\n\t\t\t|| self.url.as_ref().is_some_and(|n| n.match_url(&file.url, file.is_dir()))\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/theme/flavor.rs",
    "content": "use std::path::PathBuf;\n\nuse anyhow::{Context, Result};\nuse serde::{Deserialize, Serialize, de::IntoDeserializer};\nuse toml::{Spanned, de::DeTable};\nuse yazi_codegen::DeserializeOver2;\nuse yazi_fs::Xdg;\n\nuse crate::error_with_input;\n\n#[derive(Default, Deserialize, DeserializeOver2, Serialize)]\npub struct Flavor {\n\t#[serde(default)]\n\tpub dark:  String,\n\t#[serde(default)]\n\tpub light: String,\n}\n\nimpl Flavor {\n\tpub(crate) fn from_theme(theme: &Spanned<DeTable>, input: &str) -> Result<Self, toml::de::Error> {\n\t\tif let Some(value) = theme.get_ref().get(\"flavor\").cloned() {\n\t\t\terror_with_input(Self::deserialize(value.into_deserializer()), input)\n\t\t} else {\n\t\t\tOk(Self::default())\n\t\t}\n\t}\n\n\tpub(crate) fn read(&self, light: bool) -> Result<String> {\n\t\tOk(match if light { self.light.as_str() } else { self.dark.as_str() } {\n\t\t\t\"\" => String::new(),\n\t\t\tname => {\n\t\t\t\tlet p = Xdg::config_dir().join(format!(\"flavors/{name}.yazi/flavor.toml\"));\n\t\t\t\tstd::fs::read_to_string(&p).with_context(|| format!(\"Failed to read flavor {p:?}\"))?\n\t\t\t}\n\t\t})\n\t}\n\n\tpub(crate) fn syntect_path(&self, light: bool) -> Option<PathBuf> {\n\t\tmatch if light { self.light.as_str() } else { self.dark.as_str() } {\n\t\t\t\"\" => None,\n\t\t\tname => Some(Xdg::config_dir().join(format!(\"flavors/{name}.yazi/tmtheme.xml\"))),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/theme/icon.rs",
    "content": "use std::ops::Deref;\n\nuse anyhow::Result;\nuse hashbrown::HashMap;\nuse ratatui::style::Color;\nuse serde::{Deserialize, Deserializer};\nuse yazi_codegen::DeserializeOver2;\nuse yazi_fs::File;\nuse yazi_shared::{Condition, url::UrlLike};\n\nuse crate::{Icon as I, Pattern, Style};\n\n#[derive(Default, Deserialize, DeserializeOver2)]\npub struct Icon {\n\tglobs:         PatIcons,\n\t#[serde(default)]\n\tprepend_globs: PatIcons,\n\t#[serde(default)]\n\tappend_globs:  PatIcons,\n\n\tdirs:         StrIcons,\n\t#[serde(default)]\n\tprepend_dirs: StrIcons,\n\t#[serde(default)]\n\tappend_dirs:  StrIcons,\n\n\tfiles:         StrIcons,\n\t#[serde(default)]\n\tprepend_files: StrIcons,\n\t#[serde(default)]\n\tappend_files:  StrIcons,\n\n\texts:         StrIcons,\n\t#[serde(default)]\n\tprepend_exts: StrIcons,\n\t#[serde(default)]\n\tappend_exts:  StrIcons,\n\n\tconds:         CondIcons,\n\t#[serde(default)]\n\tprepend_conds: CondIcons,\n\t#[serde(default)]\n\tappend_conds:  CondIcons,\n}\n\nimpl Icon {\n\tpub fn matches(&self, file: &File, hovered: bool) -> Option<&I> {\n\t\tif let Some(i) = self.match_by_glob(file) {\n\t\t\treturn Some(i);\n\t\t}\n\n\t\tif let Some(i) = self.match_by_name(file) {\n\t\t\treturn Some(i);\n\t\t}\n\n\t\tlet f = |s: &str| match s {\n\t\t\t\"dir\" => file.is_dir(),\n\t\t\t\"hidden\" => file.is_hidden(),\n\t\t\t\"link\" => file.is_link(),\n\t\t\t\"orphan\" => file.is_orphan(),\n\t\t\t\"dummy\" => file.is_dummy(),\n\t\t\t\"block\" => file.is_block(),\n\t\t\t\"char\" => file.is_char(),\n\t\t\t\"fifo\" => file.is_fifo(),\n\t\t\t\"sock\" => file.is_sock(),\n\t\t\t\"exec\" => file.is_exec(),\n\t\t\t\"sticky\" => file.is_sticky(),\n\t\t\t\"hovered\" => hovered,\n\t\t\t_ => false,\n\t\t};\n\t\tself.conds.iter().find(|(c, _)| c.eval(f) == Some(true)).map(|(_, i)| i)\n\t}\n\n\tfn match_by_glob(&self, file: &File) -> Option<&I> {\n\t\tself.globs.iter().find(|(p, _)| p.match_url(&file.url, file.is_dir())).map(|(_, i)| i)\n\t}\n\n\tfn match_by_name(&self, file: &File) -> Option<&I> {\n\t\tlet name = file.name()?.to_str().ok()?;\n\t\tif file.is_dir() {\n\t\t\tself.dirs.get(name).or_else(|| self.dirs.get(&name.to_ascii_lowercase()))\n\t\t} else {\n\t\t\tself\n\t\t\t\t.files\n\t\t\t\t.get(name)\n\t\t\t\t.or_else(|| self.files.get(&name.to_ascii_lowercase()))\n\t\t\t\t.or_else(|| self.match_by_ext(file))\n\t\t}\n\t}\n\n\tfn match_by_ext(&self, file: &File) -> Option<&I> {\n\t\tlet ext = file.url.ext()?.to_str().ok()?;\n\t\tself.exts.get(ext).or_else(|| self.exts.get(&ext.to_ascii_lowercase()))\n\t}\n}\n\nimpl Icon {\n\tpub(crate) fn reshape(self) -> Result<Self> {\n\t\tOk(Self {\n\t\t\tglobs: PatIcons(\n\t\t\t\tself.prepend_globs.0.into_iter().chain(self.globs.0).chain(self.append_globs.0).collect(),\n\t\t\t),\n\t\t\tdirs: StrIcons(\n\t\t\t\tself.append_dirs.0.into_iter().chain(self.dirs.0).chain(self.prepend_dirs.0).collect(),\n\t\t\t),\n\t\t\tfiles: StrIcons(\n\t\t\t\tself.append_files.0.into_iter().chain(self.files.0).chain(self.prepend_files.0).collect(),\n\t\t\t),\n\t\t\texts: StrIcons(\n\t\t\t\tself.append_exts.0.into_iter().chain(self.exts.0).chain(self.prepend_exts.0).collect(),\n\t\t\t),\n\t\t\tconds: CondIcons(\n\t\t\t\tself.prepend_conds.0.into_iter().chain(self.conds.0).chain(self.append_conds.0).collect(),\n\t\t\t),\n\t\t\t..Default::default()\n\t\t})\n\t}\n}\n\n#[derive(Default)]\npub struct PatIcons(Vec<(Pattern, I)>);\n\nimpl Deref for PatIcons {\n\ttype Target = Vec<(Pattern, I)>;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl<'de> Deserialize<'de> for PatIcons {\n\tfn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n\twhere\n\t\tD: Deserializer<'de>,\n\t{\n\t\t#[derive(Deserialize)]\n\t\tstruct Shadow {\n\t\t\turl:  Pattern,\n\t\t\ttext: String,\n\t\t\tfg:   Option<Color>,\n\t\t}\n\n\t\tOk(Self(\n\t\t\t<Vec<Shadow>>::deserialize(deserializer)?\n\t\t\t\t.into_iter()\n\t\t\t\t.map(|s| (s.url, I { text: s.text, style: Style { fg: s.fg, ..Default::default() } }))\n\t\t\t\t.collect(),\n\t\t))\n\t}\n}\n\n#[derive(Default)]\npub struct StrIcons(HashMap<String, I>);\n\nimpl Deref for StrIcons {\n\ttype Target = HashMap<String, I>;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl<'de> Deserialize<'de> for StrIcons {\n\tfn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n\twhere\n\t\tD: Deserializer<'de>,\n\t{\n\t\t#[derive(Deserialize)]\n\t\tstruct Shadow {\n\t\t\tname: String,\n\t\t\ttext: String,\n\t\t\tfg:   Option<Color>,\n\t\t}\n\n\t\tOk(Self(\n\t\t\t<Vec<Shadow>>::deserialize(deserializer)?\n\t\t\t\t.into_iter()\n\t\t\t\t.map(|s| (s.name, I { text: s.text, style: Style { fg: s.fg, ..Default::default() } }))\n\t\t\t\t.collect(),\n\t\t))\n\t}\n}\n\n#[derive(Default)]\npub struct CondIcons(Vec<(Condition, I)>);\n\nimpl Deref for CondIcons {\n\ttype Target = Vec<(Condition, I)>;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl<'de> Deserialize<'de> for CondIcons {\n\tfn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n\twhere\n\t\tD: Deserializer<'de>,\n\t{\n\t\t#[derive(Deserialize)]\n\t\tstruct Shadow {\n\t\t\tr#if: Condition,\n\t\t\ttext: String,\n\t\t\tfg:   Option<Color>,\n\t\t}\n\n\t\tOk(Self(\n\t\t\t<Vec<Shadow>>::deserialize(deserializer)?\n\t\t\t\t.into_iter()\n\t\t\t\t.map(|s| (s.r#if, I { text: s.text, style: Style { fg: s.fg, ..Default::default() } }))\n\t\t\t\t.collect(),\n\t\t))\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/theme/is.rs",
    "content": "use serde::Deserialize;\nuse yazi_fs::cha::Cha;\n\n#[derive(Default, Deserialize)]\n#[serde(rename_all = \"kebab-case\")]\npub enum Is {\n\t#[default]\n\tNone,\n\tHidden,\n\tLink,\n\tOrphan,\n\tDummy,\n\tBlock,\n\tChar,\n\tFifo,\n\tSock,\n\tExec,\n\tSticky,\n}\n\nimpl Is {\n\tpub fn check(&self, cha: &Cha) -> bool {\n\t\tmatch self {\n\t\t\tSelf::None => true,\n\t\t\tSelf::Hidden => cha.is_hidden(),\n\t\t\tSelf::Link => cha.is_link(),\n\t\t\tSelf::Orphan => cha.is_orphan(),\n\t\t\tSelf::Dummy => cha.is_dummy(),\n\t\t\tSelf::Block => cha.is_block(),\n\t\t\tSelf::Char => cha.is_char(),\n\t\t\tSelf::Fifo => cha.is_fifo(),\n\t\t\tSelf::Sock => cha.is_sock(),\n\t\t\tSelf::Exec => cha.is_exec(),\n\t\t\tSelf::Sticky => cha.is_sticky(),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/theme/mod.rs",
    "content": "yazi_macro::mod_flat!(filetype flavor icon is theme);\n"
  },
  {
    "path": "yazi-config/src/theme/theme.rs",
    "content": "use std::path::PathBuf;\n\nuse anyhow::{Context, Result, anyhow, bail};\nuse serde::Deserialize;\nuse yazi_codegen::{DeserializeOver, DeserializeOver1, DeserializeOver2};\nuse yazi_fs::{Xdg, ok_or_not_found};\n\nuse super::{Filetype, Flavor, Icon};\nuse crate::{Style, normalize_path};\n\n#[derive(Deserialize, DeserializeOver, DeserializeOver1)]\npub struct Theme {\n\tpub flavor:    Flavor,\n\tpub app:       App,\n\tpub mgr:       Mgr,\n\tpub tabs:      Tabs,\n\tpub mode:      Mode,\n\tpub indicator: Indicator,\n\tpub status:    Status,\n\tpub which:     Which,\n\tpub confirm:   Confirm,\n\tpub spot:      Spot,\n\tpub notify:    Notify,\n\tpub pick:      Pick,\n\tpub input:     Input,\n\tpub cmp:       Cmp,\n\tpub tasks:     Tasks,\n\tpub help:      Help,\n\n\t// File-specific styles\n\t#[serde(skip_serializing)]\n\tpub filetype: Filetype,\n\t#[serde(skip_serializing)]\n\tpub icon:     Icon,\n}\n\n#[derive(Deserialize, DeserializeOver2)]\npub struct App {\n\tpub overall: Style,\n}\n\n#[derive(Deserialize, DeserializeOver2)]\npub struct Mgr {\n\tpub cwd: Style,\n\n\t// Find\n\tpub find_keyword:  Style,\n\tpub find_position: Style,\n\n\t// Symlink\n\tpub symlink_target: Style,\n\n\t// Marker\n\tpub marker_copied:   Style,\n\tpub marker_cut:      Style,\n\tpub marker_marked:   Style,\n\tpub marker_selected: Style,\n\tpub marker_symbol:   String,\n\n\t// Count\n\tpub count_copied:   Style,\n\tpub count_cut:      Style,\n\tpub count_selected: Style,\n\n\t// Border\n\tpub border_symbol: String,\n\tpub border_style:  Style,\n\n\t// Highlighting\n\tpub syntect_theme: PathBuf,\n}\n\n#[derive(Deserialize, DeserializeOver2)]\npub struct Tabs {\n\tpub active:   Style,\n\tpub inactive: Style,\n\n\tpub sep_inner: TabsSep,\n\tpub sep_outer: TabsSep,\n}\n\n#[derive(Deserialize, DeserializeOver2)]\npub struct TabsSep {\n\tpub open:  String,\n\tpub close: String,\n}\n\n#[derive(Deserialize, DeserializeOver2)]\npub struct Mode {\n\tpub normal_main: Style,\n\tpub normal_alt:  Style,\n\n\tpub select_main: Style,\n\tpub select_alt:  Style,\n\n\tpub unset_main: Style,\n\tpub unset_alt:  Style,\n}\n\n#[derive(Deserialize, DeserializeOver2)]\npub struct Indicator {\n\tpub parent:  Style,\n\tpub current: Style,\n\tpub preview: Style,\n\tpub padding: IndicatorPadding,\n}\n\n#[derive(Deserialize, DeserializeOver2)]\npub struct IndicatorPadding {\n\tpub open:  String,\n\tpub close: String,\n}\n\n#[derive(Deserialize, DeserializeOver2)]\npub struct Status {\n\tpub overall:   Style,\n\tpub sep_left:  StatusSep,\n\tpub sep_right: StatusSep,\n\n\t// Permissions\n\tpub perm_sep:   Style,\n\tpub perm_type:  Style,\n\tpub perm_read:  Style,\n\tpub perm_write: Style,\n\tpub perm_exec:  Style,\n\n\t// Progress\n\tpub progress_label:  Style,\n\tpub progress_normal: Style,\n\tpub progress_error:  Style,\n}\n\n#[derive(Deserialize, DeserializeOver2)]\npub struct StatusSep {\n\tpub open:  String,\n\tpub close: String,\n}\n\n#[derive(Deserialize, DeserializeOver2)]\npub struct Which {\n\tpub cols: u8,\n\tpub mask: Style,\n\tpub cand: Style,\n\tpub rest: Style,\n\tpub desc: Style,\n\n\tpub separator:       String,\n\tpub separator_style: Style,\n}\n\n#[derive(Deserialize, DeserializeOver2)]\npub struct Confirm {\n\tpub border: Style,\n\tpub title:  Style,\n\tpub body:   Style,\n\tpub list:   Style,\n\n\tpub btn_yes:    Style,\n\tpub btn_no:     Style,\n\tpub btn_labels: [String; 2],\n}\n\n#[derive(Deserialize, DeserializeOver2)]\npub struct Spot {\n\tpub border: Style,\n\tpub title:  Style,\n\n\tpub tbl_col:  Style,\n\tpub tbl_cell: Style,\n}\n\n#[derive(Deserialize, DeserializeOver2)]\npub struct Notify {\n\tpub title_info:  Style,\n\tpub title_warn:  Style,\n\tpub title_error: Style,\n\n\tpub icon_info:  String,\n\tpub icon_warn:  String,\n\tpub icon_error: String,\n}\n\n#[derive(Deserialize, DeserializeOver2)]\npub struct Pick {\n\tpub border:   Style,\n\tpub active:   Style,\n\tpub inactive: Style,\n}\n\n#[derive(Deserialize, DeserializeOver2)]\npub struct Input {\n\tpub border:   Style,\n\tpub title:    Style,\n\tpub value:    Style,\n\tpub selected: Style,\n}\n\n#[derive(Deserialize, DeserializeOver2)]\npub struct Cmp {\n\tpub border:   Style,\n\tpub active:   Style,\n\tpub inactive: Style,\n\n\tpub icon_file:    String,\n\tpub icon_folder:  String,\n\tpub icon_command: String,\n}\n\n#[derive(Deserialize, DeserializeOver2)]\npub struct Tasks {\n\tpub border:  Style,\n\tpub title:   Style,\n\tpub hovered: Style,\n}\n\n#[derive(Deserialize, DeserializeOver2)]\npub struct Help {\n\tpub on:   Style,\n\tpub run:  Style,\n\tpub desc: Style,\n\n\tpub hovered: Style,\n\tpub footer:  Style,\n}\n\nimpl Theme {\n\tpub(crate) fn read() -> Result<String> {\n\t\tlet p = Xdg::config_dir().join(\"theme.toml\");\n\t\tok_or_not_found(std::fs::read_to_string(&p))\n\t\t\t.with_context(|| format!(\"Failed to read theme {p:?}\"))\n\t}\n\n\tpub(crate) fn reshape(mut self, light: bool) -> Result<Self> {\n\t\tif self.which.cols < 1 || self.which.cols > 3 {\n\t\t\tbail!(\"[which].cols must be between 1 and 3\");\n\t\t}\n\n\t\tself.icon = self.icon.reshape()?;\n\n\t\tself.mgr.syntect_theme = self\n\t\t\t.flavor\n\t\t\t.syntect_path(light)\n\t\t\t.or_else(|| normalize_path(self.mgr.syntect_theme))\n\t\t\t.ok_or(anyhow!(\"[mgr].syntect_theme must be either empty or an absolute path.\"))?;\n\n\t\tOk(self)\n\t}\n}\n\nimpl App {\n\tpub fn bg_color(&self) -> String { self.overall.bg.map(|c| c.to_string()).unwrap_or_default() }\n}\n"
  },
  {
    "path": "yazi-config/src/utils.rs",
    "content": "use std::path::PathBuf;\n\nuse yazi_fs::path::{clean_url, expand_url};\nuse yazi_shared::url::UrlBuf;\n\npub(crate) fn normalize_path(path: PathBuf) -> Option<PathBuf> {\n\tclean_url(yazi_fs::provider::local::try_absolute(expand_url(UrlBuf::from(path)))?)\n\t\t.into_loc()\n\t\t.into_os()\n\t\t.ok()\n\t\t.filter(|p| p.as_os_str().is_empty() || p.is_absolute())\n}\n"
  },
  {
    "path": "yazi-config/src/vfs/mod.rs",
    "content": "yazi_macro::mod_flat!(service services vfs);\n"
  },
  {
    "path": "yazi-config/src/vfs/service.rs",
    "content": "use std::{io, mem, path::PathBuf};\n\nuse serde::{Deserialize, Serialize};\n\nuse crate::normalize_path;\n\n#[derive(Deserialize, Serialize)]\n#[serde(tag = \"type\", rename_all = \"kebab-case\")]\npub enum Service {\n\tSftp(ServiceSftp),\n}\n\nimpl TryFrom<&'static Service> for &'static ServiceSftp {\n\ttype Error = &'static str;\n\n\tfn try_from(value: &'static Service) -> Result<Self, Self::Error> {\n\t\tmatch value {\n\t\t\tService::Sftp(p) => Ok(p),\n\t\t}\n\t}\n}\n\nimpl Service {\n\tpub(super) fn reshape(&mut self) -> io::Result<()> {\n\t\tmatch self {\n\t\t\tSelf::Sftp(p) => p.reshape(),\n\t\t}\n\t}\n}\n\n// --- SFTP\n#[derive(Deserialize, Eq, Hash, PartialEq, Serialize)]\npub struct ServiceSftp {\n\tpub host:           String,\n\tpub user:           String,\n\tpub port:           u16,\n\tpub password:       Option<String>,\n\t#[serde(default)]\n\tpub key_file:       PathBuf,\n\tpub key_passphrase: Option<String>,\n\t#[serde(default)]\n\tpub cert_file:      PathBuf,\n\t#[serde(default)]\n\tpub no_cert_verify: bool,\n\t#[serde(default)]\n\tpub identity_agent: PathBuf,\n}\n\nimpl ServiceSftp {\n\tfn reshape(&mut self) -> io::Result<()> {\n\t\tif !self.key_file.as_os_str().is_empty() {\n\t\t\tself.key_file = normalize_path(mem::take(&mut self.key_file))\n\t\t\t\t.ok_or_else(|| io::Error::other(\"key_file must be either empty or an absolute path\"))?;\n\t\t}\n\n\t\tif !self.cert_file.as_os_str().is_empty() {\n\t\t\tself.cert_file = normalize_path(mem::take(&mut self.cert_file))\n\t\t\t\t.ok_or_else(|| io::Error::other(\"cert_file must be either empty or an absolute path\"))?;\n\t\t}\n\n\t\tself.identity_agent = if self.identity_agent.as_os_str().is_empty() {\n\t\t\tstd::env::var_os(\"SSH_AUTH_SOCK\")\n\t\t\t\t.map(PathBuf::from)\n\t\t\t\t.filter(|p| p.is_absolute())\n\t\t\t\t.unwrap_or_default()\n\t\t} else {\n\t\t\tnormalize_path(mem::take(&mut self.identity_agent)).ok_or_else(|| {\n\t\t\t\tio::Error::other(\"identity_agent must be either empty or an absolute path\")\n\t\t\t})?\n\t\t};\n\n\t\tOk(())\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/vfs/services.rs",
    "content": "use std::ops::Deref;\n\nuse anyhow::{Result, bail};\nuse hashbrown::HashMap;\nuse serde::{Deserialize, Serialize, de::IntoDeserializer};\nuse toml::{Spanned, de::DeTable};\nuse yazi_codegen::DeserializeOver;\n\nuse crate::vfs::Service;\n\n#[derive(Deserialize, Serialize, DeserializeOver)]\npub struct Services(HashMap<String, Service>);\n\nimpl Deref for Services {\n\ttype Target = HashMap<String, Service>;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl Services {\n\tpub(super) fn reshape(mut self) -> Result<Self> {\n\t\tfor (name, service) in &mut self.0 {\n\t\t\tif name.is_empty() || name.len() > 20 {\n\t\t\t\tbail!(\"VFS name `{name}` must be between 1 and 20 characters\");\n\t\t\t} else if !name.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'z' | b'-')) {\n\t\t\t\tbail!(\"VFS name `{name}` must be in kebab-case\");\n\t\t\t}\n\n\t\t\tservice.reshape()?;\n\t\t}\n\n\t\tOk(self)\n\t}\n\n\tpub(super) fn deserialize_over_with<'de>(\n\t\tmut self,\n\t\ttable: Spanned<DeTable<'de>>,\n\t) -> Result<Self, toml::de::Error> {\n\t\tfor (key, value) in table.into_inner() {\n\t\t\tself.0.insert(key.into_inner().into_owned(), <_>::deserialize(value.into_deserializer())?);\n\t\t}\n\n\t\tOk(self)\n\t}\n}\n"
  },
  {
    "path": "yazi-config/src/vfs/vfs.rs",
    "content": "use std::io;\n\nuse anyhow::{Context, Result};\nuse serde::{Deserialize, Serialize};\nuse tokio::sync::OnceCell;\nuse yazi_codegen::{DeserializeOver, DeserializeOver1};\nuse yazi_fs::{Xdg, ok_or_not_found};\n\nuse super::Service;\nuse crate::{Preset, vfs::Services};\n\n#[derive(Deserialize, Serialize, DeserializeOver, DeserializeOver1)]\npub struct Vfs {\n\tpub services: Services,\n}\n\nimpl Vfs {\n\tpub async fn load() -> io::Result<&'static Self> {\n\t\tpub static LOADED: OnceCell<Vfs> = OnceCell::const_new();\n\n\t\tasync fn init() -> io::Result<Vfs> {\n\t\t\ttokio::task::spawn_blocking(|| Preset::vfs()?.deserialize_over(&Vfs::read()?)?.reshape())\n\t\t\t\t.await?\n\t\t\t\t.map_err(io::Error::other)\n\t\t}\n\n\t\tLOADED.get_or_try_init(init).await\n\t}\n\n\tpub async fn service<'a, P>(name: &str) -> io::Result<(&'a str, P)>\n\twhere\n\t\tP: TryFrom<&'a Service, Error = &'static str>,\n\t{\n\t\tlet Some((key, value)) = Self::load().await?.services.get_key_value(name) else {\n\t\t\treturn Err(io::Error::other(format!(\"No such VFS service: {name}\")));\n\t\t};\n\t\tmatch value.try_into() {\n\t\t\tOk(p) => Ok((key.as_str(), p)),\n\t\t\tErr(e) => Err(io::Error::other(format!(\"VFS service `{key}` has wrong type: {e}\"))),\n\t\t}\n\t}\n\n\tfn read() -> Result<String> {\n\t\tlet p = Xdg::config_dir().join(\"vfs.toml\");\n\t\tok_or_not_found(std::fs::read_to_string(&p))\n\t\t\t.with_context(|| format!(\"Failed to read config {p:?}\"))\n\t}\n\n\tfn reshape(self) -> Result<Self> { Ok(Self { services: self.services.reshape()? }) }\n}\n"
  },
  {
    "path": "yazi-config/src/which/mod.rs",
    "content": "yazi_macro::mod_flat!(sorting which);\n"
  },
  {
    "path": "yazi-config/src/which/sorting.rs",
    "content": "use serde::{Deserialize, Serialize};\n\n#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]\n#[serde(rename_all = \"kebab-case\")]\npub enum SortBy {\n\t#[default]\n\tNone,\n\tKey,\n\tDesc,\n}\n"
  },
  {
    "path": "yazi-config/src/which/which.rs",
    "content": "use serde::{Deserialize, Serialize};\nuse yazi_codegen::DeserializeOver2;\n\nuse super::SortBy;\n\n#[derive(Debug, Deserialize, DeserializeOver2, Serialize)]\npub struct Which {\n\t// Sorting\n\tpub sort_by:        SortBy,\n\tpub sort_sensitive: bool,\n\tpub sort_reverse:   bool,\n\tpub sort_translit:  bool,\n}\n"
  },
  {
    "path": "yazi-config/src/yazi.rs",
    "content": "use anyhow::{Context, Result};\nuse serde::Deserialize;\nuse yazi_codegen::{DeserializeOver, DeserializeOver1};\nuse yazi_fs::{Xdg, ok_or_not_found};\n\nuse crate::{mgr, open, opener, plugin, popup, preview, tasks, which};\n\n#[derive(Deserialize, DeserializeOver, DeserializeOver1)]\npub struct Yazi {\n\tpub mgr:     mgr::Mgr,\n\tpub preview: preview::Preview,\n\tpub opener:  opener::Opener,\n\tpub open:    open::Open,\n\tpub tasks:   tasks::Tasks,\n\tpub plugin:  plugin::Plugin,\n\tpub input:   popup::Input,\n\tpub confirm: popup::Confirm,\n\tpub pick:    popup::Pick,\n\tpub which:   which::Which,\n}\n\nimpl Yazi {\n\tpub(super) fn read() -> Result<String> {\n\t\tlet p = Xdg::config_dir().join(\"yazi.toml\");\n\t\tok_or_not_found(std::fs::read_to_string(&p))\n\t\t\t.with_context(|| format!(\"Failed to read config {p:?}\"))\n\t}\n\n\tpub(super) fn reshape(self) -> Result<Self> {\n\t\tOk(Self {\n\t\t\tmgr:     self.mgr.reshape()?,\n\t\t\tpreview: self.preview.reshape()?,\n\t\t\topener:  self.opener.reshape()?,\n\t\t\topen:    self.open.reshape()?,\n\t\t\ttasks:   self.tasks.reshape()?,\n\t\t\tplugin:  self.plugin.reshape()?,\n\t\t\tinput:   self.input,\n\t\t\tconfirm: self.confirm,\n\t\t\tpick:    self.pick,\n\t\t\twhich:   self.which,\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-core/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-core\"\ndescription            = \"Yazi core logic\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[dependencies]\nyazi-adapter   = { path = \"../yazi-adapter\", version = \"26.2.2\" }\nyazi-binding   = { path = \"../yazi-binding\", version = \"26.2.2\" }\nyazi-config    = { path = \"../yazi-config\", version = \"26.2.2\" }\nyazi-dds       = { path = \"../yazi-dds\", version = \"26.2.2\" }\nyazi-emulator  = { path = \"../yazi-emulator\", version = \"26.2.2\" }\nyazi-fs        = { path = \"../yazi-fs\", version = \"26.2.2\" }\nyazi-macro     = { path = \"../yazi-macro\", version = \"26.2.2\" }\nyazi-parser    = { path = \"../yazi-parser\", version = \"26.2.2\" }\nyazi-plugin    = { path = \"../yazi-plugin\", version = \"26.2.2\" }\nyazi-proxy     = { path = \"../yazi-proxy\", version = \"26.2.2\" }\nyazi-scheduler = { path = \"../yazi-scheduler\", version = \"26.2.2\" }\nyazi-shared    = { path = \"../yazi-shared\", version = \"26.2.2\" }\nyazi-vfs       = { path = \"../yazi-vfs\", version = \"26.2.2\" }\nyazi-watcher   = { path = \"../yazi-watcher\", version = \"26.2.2\" }\nyazi-widgets   = { path = \"../yazi-widgets\", version = \"26.2.2\" }\n\n# External dependencies\nanyhow        = { workspace = true }\ncrossterm     = { workspace = true }\nhashbrown     = { workspace = true }\nindexmap      = { workspace = true }\nparking_lot   = { workspace = true }\nratatui       = { workspace = true }\ntokio         = { workspace = true }\ntokio-stream  = { workspace = true }\ntokio-util    = { workspace = true }\ntracing       = { workspace = true }\nunicode-width = { workspace = true }\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\ncrossterm = { workspace = true, features = [ \"use-dev-tty\", \"libc\" ] }\n"
  },
  {
    "path": "yazi-core/README.md",
    "content": "# yazi-core\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-core/src/cmp/cmp.rs",
    "content": "use std::io;\n\nuse hashbrown::HashMap;\nuse tokio::task::JoinHandle;\nuse yazi_parser::cmp::CmpItem;\nuse yazi_shared::{Id, url::UrlBuf};\nuse yazi_widgets::Scrollable;\n\n#[derive(Default)]\npub struct Cmp {\n\tpub caches:  HashMap<UrlBuf, Vec<CmpItem>>,\n\tpub matches: Vec<CmpItem>,\n\tpub offset:  usize,\n\tpub cursor:  usize,\n\n\tpub ticket:  Id,\n\tpub handle:  Option<JoinHandle<io::Result<()>>>,\n\tpub visible: bool,\n}\n\nimpl Cmp {\n\t// --- Matches\n\tpub fn window(&self) -> &[CmpItem] {\n\t\tlet end = (self.offset + self.limit()).min(self.matches.len());\n\t\t&self.matches[self.offset..end]\n\t}\n\n\tpub fn selected(&self) -> Option<&CmpItem> { self.matches.get(self.cursor) }\n\n\t// --- Cursor\n\tpub fn rel_cursor(&self) -> usize { self.cursor - self.offset }\n}\n\nimpl Scrollable for Cmp {\n\tfn total(&self) -> usize { self.matches.len() }\n\n\tfn limit(&self) -> usize { self.matches.len().min(10) }\n\n\tfn cursor_mut(&mut self) -> &mut usize { &mut self.cursor }\n\n\tfn offset_mut(&mut self) -> &mut usize { &mut self.offset }\n}\n"
  },
  {
    "path": "yazi-core/src/cmp/mod.rs",
    "content": "yazi_macro::mod_flat!(cmp);\n"
  },
  {
    "path": "yazi-core/src/confirm/confirm.rs",
    "content": "use ratatui::{text::Line, widgets::Paragraph};\nuse yazi_config::popup::Position;\nuse yazi_shared::CompletionToken;\n\n#[derive(Default)]\npub struct Confirm {\n\tpub title: Line<'static>,\n\tpub body:  Paragraph<'static>,\n\tpub list:  Paragraph<'static>,\n\n\tpub position: Position,\n\tpub offset:   usize,\n\n\tpub token:   CompletionToken,\n\tpub visible: bool,\n}\n"
  },
  {
    "path": "yazi-core/src/confirm/mod.rs",
    "content": "yazi_macro::mod_flat!(confirm);\n"
  },
  {
    "path": "yazi-core/src/core.rs",
    "content": "use crossterm::cursor::SetCursorStyle;\nuse ratatui::layout::{Position, Rect};\nuse yazi_shared::Layer;\n\nuse crate::{cmp::Cmp, confirm::Confirm, help::Help, input::Input, mgr::Mgr, notify::Notify, pick::Pick, tab::{Folder, Tab}, tasks::Tasks, which::Which};\n\npub struct Core {\n\tpub mgr:     Mgr,\n\tpub tasks:   Tasks,\n\tpub pick:    Pick,\n\tpub input:   Input,\n\tpub confirm: Confirm,\n\tpub help:    Help,\n\tpub cmp:     Cmp,\n\tpub which:   Which,\n\tpub notify:  Notify,\n}\n\nimpl Core {\n\tpub fn make() -> Self {\n\t\tSelf {\n\t\t\tmgr:     Mgr::make(),\n\t\t\ttasks:   Tasks::serve(),\n\t\t\tpick:    Default::default(),\n\t\t\tinput:   Default::default(),\n\t\t\tconfirm: Default::default(),\n\t\t\thelp:    Default::default(),\n\t\t\tcmp:     Default::default(),\n\t\t\twhich:   Default::default(),\n\t\t\tnotify:  Default::default(),\n\t\t}\n\t}\n\n\tpub fn cursor(&self) -> Option<(Position, SetCursorStyle)> {\n\t\tif self.input.visible {\n\t\t\tlet Rect { x, y, .. } = self.mgr.area(self.input.position);\n\t\t\treturn Some((\n\t\t\t\tPosition { x: x + 1 + self.input.cursor(), y: y + 1 },\n\t\t\t\tself.input.cursor_shape(),\n\t\t\t));\n\t\t}\n\t\tif let Some((x, y)) = self.help.cursor() {\n\t\t\treturn Some((Position { x, y }, self.help.cursor_shape()));\n\t\t}\n\t\tNone\n\t}\n\n\tpub fn layer(&self) -> Layer {\n\t\tif self.which.active {\n\t\t\tLayer::Which\n\t\t} else if self.cmp.visible {\n\t\t\tLayer::Cmp\n\t\t} else if self.help.visible {\n\t\t\tLayer::Help\n\t\t} else if self.confirm.visible {\n\t\t\tLayer::Confirm\n\t\t} else if self.input.visible {\n\t\t\tLayer::Input\n\t\t} else if self.pick.visible {\n\t\t\tLayer::Pick\n\t\t} else if self.active().spot.visible() {\n\t\t\tLayer::Spot\n\t\t} else if self.tasks.visible {\n\t\t\tLayer::Tasks\n\t\t} else {\n\t\t\tLayer::Mgr\n\t\t}\n\t}\n}\n\nimpl Core {\n\t#[inline]\n\tpub fn active(&self) -> &Tab { self.mgr.active() }\n\n\t#[inline]\n\tpub fn active_mut(&mut self) -> &mut Tab { self.mgr.active_mut() }\n\n\t#[inline]\n\tpub fn current_mut(&mut self) -> &mut Folder { self.mgr.current_mut() }\n\n\t#[inline]\n\tpub fn parent_mut(&mut self) -> Option<&mut Folder> { self.mgr.parent_mut() }\n}\n"
  },
  {
    "path": "yazi-core/src/help/help.rs",
    "content": "use anyhow::Result;\nuse crossterm::{cursor::SetCursorStyle, event::KeyCode};\nuse unicode_width::UnicodeWidthStr;\nuse yazi_config::{KEYMAP, YAZI, keymap::{Chord, Key}};\nuse yazi_emulator::Dimension;\nuse yazi_macro::{act, render, render_and};\nuse yazi_shared::Layer;\nuse yazi_widgets::Scrollable;\n\nuse crate::help::HELP_MARGIN;\n\n#[derive(Default)]\npub struct Help {\n\tpub visible:         bool,\n\tpub layer:           Layer,\n\tpub(super) bindings: Vec<&'static Chord>,\n\n\t// Filter\n\tpub keyword:   String,\n\tpub in_filter: Option<yazi_widgets::input::Input>,\n\n\tpub offset: usize,\n\tpub cursor: usize,\n}\n\nimpl Help {\n\tpub fn r#type(&mut self, key: &Key) -> Result<bool> {\n\t\tlet Some(input) = &mut self.in_filter else { return Ok(false) };\n\t\tmatch key {\n\t\t\tKey { code: KeyCode::Esc, shift: false, ctrl: false, alt: false, super_: false } => {\n\t\t\t\tself.in_filter = None;\n\t\t\t\trender!();\n\t\t\t}\n\t\t\tKey { code: KeyCode::Enter, shift: false, ctrl: false, alt: false, super_: false } => {\n\t\t\t\tself.in_filter = None;\n\t\t\t\treturn Ok(render_and!(true)); // Don't do the `filter_apply` below, since we already have the filtered results.\n\t\t\t}\n\t\t\tKey { code: KeyCode::Backspace, shift: false, ctrl: false, alt: false, super_: false } => {\n\t\t\t\tact!(backspace, input)?;\n\t\t\t}\n\t\t\t_ => {\n\t\t\t\tinput.r#type(key)?;\n\t\t\t}\n\t\t}\n\n\t\tself.filter_apply();\n\t\tOk(true)\n\t}\n\n\tpub fn filter_apply(&mut self) {\n\t\tlet kw = self.in_filter.as_ref().map_or(\"\", |i| i.value());\n\n\t\tif kw.is_empty() {\n\t\t\tself.keyword = String::new();\n\t\t\tself.bindings = KEYMAP.get(self.layer).iter().collect();\n\t\t} else if self.keyword != kw {\n\t\t\tself.keyword = kw.to_owned();\n\t\t\tself.bindings = KEYMAP.get(self.layer).iter().filter(|&c| c.contains(kw)).collect();\n\t\t}\n\n\t\trender!(self.scroll(0));\n\t}\n}\n\nimpl Help {\n\t// --- Keyword\n\tpub fn keyword(&self) -> Option<String> {\n\t\tself\n\t\t\t.in_filter\n\t\t\t.as_ref()\n\t\t\t.map(|i| i.value())\n\t\t\t.or(Some(self.keyword.as_str()).filter(|&s| !s.is_empty()))\n\t\t\t.map(|s| format!(\"Filter: {s}\"))\n\t}\n\n\t// --- Bindings\n\tpub fn window(&self) -> &[&Chord] {\n\t\tlet end = (self.offset + self.limit()).min(self.bindings.len());\n\t\t&self.bindings[self.offset..end]\n\t}\n\n\t// --- Cursor\n\tpub fn cursor(&self) -> Option<(u16, u16)> {\n\t\tif !self.visible || self.in_filter.is_none() {\n\t\t\treturn None;\n\t\t}\n\t\tif let Some(kw) = self.keyword() {\n\t\t\treturn Some((kw.width() as u16, Dimension::available().rows));\n\t\t}\n\t\tNone\n\t}\n\n\tpub fn rel_cursor(&self) -> usize { self.cursor - self.offset }\n\n\tpub fn cursor_shape(&self) -> SetCursorStyle {\n\t\tif YAZI.input.cursor_blink {\n\t\t\tSetCursorStyle::BlinkingBlock\n\t\t} else {\n\t\t\tSetCursorStyle::SteadyBlock\n\t\t}\n\t}\n}\n\nimpl Scrollable for Help {\n\tfn total(&self) -> usize { self.bindings.len() }\n\n\tfn limit(&self) -> usize { Dimension::available().rows.saturating_sub(HELP_MARGIN) as usize }\n\n\tfn cursor_mut(&mut self) -> &mut usize { &mut self.cursor }\n\n\tfn offset_mut(&mut self) -> &mut usize { &mut self.offset }\n}\n"
  },
  {
    "path": "yazi-core/src/help/mod.rs",
    "content": "yazi_macro::mod_flat!(help);\n\nconst HELP_MARGIN: u16 = 1;\n"
  },
  {
    "path": "yazi-core/src/input/input.rs",
    "content": "use std::ops::{Deref, DerefMut};\n\nuse yazi_config::popup::Position;\n\n#[derive(Default)]\npub struct Input {\n\tpub(super) inner: yazi_widgets::input::Input,\n\n\tpub visible:  bool,\n\tpub title:    String,\n\tpub position: Position,\n}\n\nimpl Deref for Input {\n\ttype Target = yazi_widgets::input::Input;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl DerefMut for Input {\n\tfn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }\n}\n"
  },
  {
    "path": "yazi-core/src/input/mod.rs",
    "content": "yazi_macro::mod_flat!(input);\n"
  },
  {
    "path": "yazi-core/src/lib.rs",
    "content": "yazi_macro::mod_pub!(cmp confirm help input mgr notify pick spot tab tasks which);\n\nyazi_macro::mod_flat!(core);\n"
  },
  {
    "path": "yazi-core/src/mgr/mgr.rs",
    "content": "use std::iter;\n\nuse ratatui::layout::Rect;\nuse yazi_config::popup::{Origin, Position};\nuse yazi_emulator::Dimension;\nuse yazi_fs::Splatable;\nuse yazi_shared::url::{AsUrl, Url, UrlBuf};\nuse yazi_watcher::Watcher;\n\nuse super::{Mimetype, Tabs, Yanked};\nuse crate::tab::{Folder, Tab};\n\npub struct Mgr {\n\tpub tabs:   Tabs,\n\tpub yanked: Yanked,\n\n\tpub watcher:  Watcher,\n\tpub mimetype: Mimetype,\n}\n\nimpl Mgr {\n\tpub fn make() -> Self {\n\t\tSelf {\n\t\t\ttabs:   Default::default(),\n\t\t\tyanked: Default::default(),\n\n\t\t\twatcher:  Watcher::serve(),\n\t\t\tmimetype: Default::default(),\n\t\t}\n\t}\n\n\tpub fn area(&self, pos: Position) -> Rect {\n\t\tif pos.origin == Origin::Hovered {\n\t\t\tself.active().hovered_rect_based(pos)\n\t\t} else {\n\t\t\tpos.rect(Dimension::available().into())\n\t\t}\n\t}\n\n\tpub fn shutdown(&mut self) { self.tabs.iter_mut().for_each(|t| t.shutdown()); }\n}\n\nimpl Mgr {\n\t#[inline]\n\tpub fn cwd(&self) -> &UrlBuf { self.active().cwd() }\n\n\t#[inline]\n\tpub fn active(&self) -> &Tab { self.tabs.active() }\n\n\t#[inline]\n\tpub fn active_mut(&mut self) -> &mut Tab { self.tabs.active_mut() }\n\n\t#[inline]\n\tpub fn current_mut(&mut self) -> &mut Folder { &mut self.active_mut().current }\n\n\t#[inline]\n\tpub fn parent_mut(&mut self) -> Option<&mut Folder> { self.active_mut().parent.as_mut() }\n}\n\nimpl Splatable for Mgr {\n\tfn tab(&self) -> usize { self.tabs.cursor }\n\n\tfn selected(&self, tab: usize, mut idx: Option<usize>) -> impl Iterator<Item = Url<'_>> {\n\t\tidx = idx.and_then(|i| i.checked_sub(1));\n\t\ttab\n\t\t\t.checked_sub(1)\n\t\t\t.and_then(|tab| self.tabs.get(tab))\n\t\t\t.map(|tab| tab.selected_or_hovered())\n\t\t\t.unwrap_or_else(|| Box::new(iter::empty()))\n\t\t\t.skip(idx.unwrap_or(0))\n\t\t\t.take(if idx.is_some() { 1 } else { usize::MAX })\n\t\t\t.map(|u| u.as_url())\n\t}\n\n\tfn hovered(&self, tab: usize) -> Option<Url<'_>> {\n\t\ttab\n\t\t\t.checked_sub(1)\n\t\t\t.and_then(|tab| self.tabs.get(tab))\n\t\t\t.and_then(|tab| tab.hovered())\n\t\t\t.map(|h| h.url.as_url())\n\t}\n\n\tfn yanked(&self) -> impl Iterator<Item = Url<'_>> { self.yanked.iter().map(|u| u.as_url()) }\n}\n"
  },
  {
    "path": "yazi-core/src/mgr/mimetype.rs",
    "content": "use hashbrown::HashMap;\nuse yazi_shared::{pool::Symbol, url::{Url, UrlBufCov, UrlCov}};\n\n#[derive(Default)]\npub struct Mimetype(HashMap<UrlBufCov, Symbol<str>>);\n\nimpl Mimetype {\n\tpub fn get<'a, 'b>(&'a self, url: impl Into<Url<'b>>) -> Option<&'a str> {\n\t\tself.0.get(&UrlCov::new(url)).map(|s| s.as_ref())\n\t}\n\n\tpub fn owned<'a>(&self, url: impl Into<Url<'a>>) -> Option<Symbol<str>> {\n\t\tself.0.get(&UrlCov::new(url)).cloned()\n\t}\n\n\tpub fn contains<'a>(&self, url: impl Into<Url<'a>>) -> bool {\n\t\tself.0.contains_key(&UrlCov::new(url))\n\t}\n\n\tpub fn extend(&mut self, iter: impl IntoIterator<Item = (UrlBufCov, Symbol<str>)>) {\n\t\tself.0.extend(iter);\n\t}\n}\n"
  },
  {
    "path": "yazi-core/src/mgr/mod.rs",
    "content": "yazi_macro::mod_flat!(mgr mimetype tabs yanked);\n"
  },
  {
    "path": "yazi-core/src/mgr/tabs.rs",
    "content": "use std::ops::{Deref, DerefMut};\n\nuse yazi_dds::Pubsub;\nuse yazi_fs::File;\nuse yazi_macro::err;\n\nuse crate::tab::{Folder, Tab};\n\npub struct Tabs {\n\tpub cursor: usize,\n\tpub items:  Vec<Tab>,\n}\n\nimpl Default for Tabs {\n\tfn default() -> Self { Self { cursor: 0, items: vec![Default::default()] } }\n}\n\nimpl Tabs {\n\tpub fn set_idx(&mut self, idx: usize) {\n\t\t// Reset the preview of the last active tab\n\t\tif let Some(active) = self.items.get_mut(self.cursor) {\n\t\t\tactive.preview.reset_image();\n\t\t}\n\n\t\tself.cursor = idx;\n\t\terr!(Pubsub::pub_after_tab(self.active().id));\n\t}\n}\n\nimpl Tabs {\n\t#[inline]\n\tpub fn active(&self) -> &Tab { &self[self.cursor] }\n\n\t#[inline]\n\tpub(super) fn active_mut(&mut self) -> &mut Tab { &mut self.items[self.cursor] }\n\n\t#[inline]\n\tpub fn parent(&self) -> Option<&Folder> { self.active().parent.as_ref() }\n\n\t#[inline]\n\tpub fn current(&self) -> &Folder { &self.active().current }\n\n\t#[inline]\n\tpub fn hovered(&self) -> Option<&File> { self.current().hovered() }\n}\n\nimpl Deref for Tabs {\n\ttype Target = Vec<Tab>;\n\n\tfn deref(&self) -> &Self::Target { &self.items }\n}\n\nimpl DerefMut for Tabs {\n\tfn deref_mut(&mut self) -> &mut Self::Target { &mut self.items }\n}\n"
  },
  {
    "path": "yazi-core/src/mgr/yanked.rs",
    "content": "use std::ops::Deref;\n\nuse hashbrown::HashSet;\nuse yazi_dds::Pubsub;\nuse yazi_fs::FilesOp;\nuse yazi_macro::err;\nuse yazi_shared::url::{Url, UrlBuf, UrlBufCov, UrlCov, UrlLike};\n\n#[derive(Debug, Default)]\npub struct Yanked {\n\tpub cut: bool,\n\turls:    HashSet<UrlBufCov>,\n\n\tversion:  u64,\n\trevision: u64,\n}\n\nimpl Deref for Yanked {\n\ttype Target = HashSet<UrlBufCov>;\n\n\tfn deref(&self) -> &Self::Target { &self.urls }\n}\n\nimpl Yanked {\n\tpub fn new(cut: bool, urls: HashSet<UrlBufCov>) -> Self {\n\t\tSelf { cut, urls, ..Default::default() }\n\t}\n\n\tpub fn remove<'a>(&mut self, url: impl Into<Url<'a>>) {\n\t\tif self.urls.remove(&UrlCov::new(url)) {\n\t\t\tself.revision += 1;\n\t\t}\n\t}\n\n\tpub fn clear(&mut self) {\n\t\tif self.urls.is_empty() {\n\t\t\treturn;\n\t\t}\n\n\t\tself.urls.clear();\n\t\tself.revision += 1;\n\t}\n\n\tpub fn contains<'a>(&self, url: impl Into<Url<'a>>) -> bool {\n\t\tself.urls.contains(&UrlCov::new(url))\n\t}\n\n\tpub fn contains_in(&self, dir: &UrlBuf) -> bool {\n\t\tself.urls.iter().any(|u| {\n\t\t\tlet mut it = u.components();\n\t\t\tit.next_back().is_some()\n\t\t\t\t&& it.covariant(&dir.components())\n\t\t\t\t&& u.parent().is_some_and(|p| p == *dir)\n\t\t})\n\t}\n\n\tpub fn apply_op(&mut self, op: &FilesOp) {\n\t\tlet (removal, addition) = op.diff_recoverable(|u| self.contains(u));\n\t\tif !removal.is_empty() {\n\t\t\tlet old = self.urls.len();\n\t\t\tself.urls.retain(|u| !removal.contains(u));\n\t\t\tself.revision += (old != self.urls.len()) as u64;\n\t\t}\n\n\t\tif !addition.is_empty() {\n\t\t\tlet old = self.urls.len();\n\t\t\tself.urls.extend(addition.into_iter().map(UrlBufCov));\n\t\t\tself.revision += (old != self.urls.len()) as u64;\n\t\t}\n\t}\n\n\tpub fn catchup_revision(&mut self, force: bool) -> bool {\n\t\tif self.version == self.revision && !force {\n\t\t\treturn false;\n\t\t}\n\n\t\tself.version = self.revision;\n\t\terr!(Pubsub::pub_after_yank(self.cut, &self.urls));\n\t\ttrue\n\t}\n}\n"
  },
  {
    "path": "yazi-core/src/notify/message.rs",
    "content": "use std::time::{Duration, Instant};\n\nuse unicode_width::UnicodeWidthStr;\nuse yazi_parser::notify::{PushLevel, PushOpt};\n\nuse super::NOTIFY_BORDER;\n\npub struct Message {\n\tpub title:   String,\n\tpub content: String,\n\tpub level:   PushLevel,\n\tpub timeout: Duration,\n\n\tpub instant:   Instant,\n\tpub percent:   u8,\n\tpub max_width: usize,\n}\n\nimpl From<PushOpt> for Message {\n\tfn from(opt: PushOpt) -> Self {\n\t\tlet title = opt.title.lines().next().unwrap_or_default();\n\t\tlet title_width = title.width() + (opt.level.icon().width() + /* Space */ 1);\n\n\t\tlet max_width = opt.content.lines().map(|s| s.width()).max().unwrap_or(0).max(title_width);\n\n\t\tSelf {\n\t\t\ttitle:   title.to_owned(),\n\t\t\tcontent: opt.content,\n\t\t\tlevel:   opt.level,\n\t\t\ttimeout: opt.timeout,\n\n\t\t\tinstant:   Instant::now(),\n\t\t\tpercent:   0,\n\t\t\tmax_width: max_width + NOTIFY_BORDER as usize,\n\t\t}\n\t}\n}\n\nimpl Message {\n\tpub fn height(&self, width: u16) -> usize {\n\t\tlet lines = ratatui::widgets::Paragraph::new(self.content.as_str())\n\t\t\t.wrap(ratatui::widgets::Wrap { trim: false })\n\t\t\t.line_count(width.saturating_sub(NOTIFY_BORDER));\n\n\t\tlines + NOTIFY_BORDER as usize\n\t}\n}\n\nimpl PartialEq for Message {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tself.level == other.level && self.content == other.content && self.title == other.title\n\t}\n}\n"
  },
  {
    "path": "yazi-core/src/notify/mod.rs",
    "content": "yazi_macro::mod_flat!(message notify);\n\npub const NOTIFY_BORDER: u16 = 2;\npub const NOTIFY_SPACING: u16 = 1;\n"
  },
  {
    "path": "yazi-core/src/notify/notify.rs",
    "content": "use std::ops::ControlFlow;\n\nuse ratatui::layout::{Constraint, Layout, Rect};\nuse tokio::task::JoinHandle;\n\nuse super::{Message, NOTIFY_SPACING};\n\n#[derive(Default)]\npub struct Notify {\n\tpub tick_handle: Option<JoinHandle<()>>,\n\tpub messages:    Vec<Message>,\n}\n\nimpl Notify {\n\tpub fn available(area: Rect) -> Rect {\n\t\tlet chunks = Layout::horizontal([Constraint::Fill(1), Constraint::Min(80)]).split(area);\n\t\tlet chunks = Layout::vertical([Constraint::Fill(1), Constraint::Max(1)]).split(chunks[1]);\n\t\tchunks[0]\n\t}\n\n\tpub fn limit(&self, area: Rect) -> usize {\n\t\tif self.messages.is_empty() {\n\t\t\treturn 0;\n\t\t}\n\n\t\tlet mut height = area.height as usize;\n\t\tlet flow = (0..self.messages.len().min(3)).try_fold(0, |acc, i| {\n\t\t\tmatch height.checked_sub(self.messages[i].height(area.width) + NOTIFY_SPACING as usize) {\n\t\t\t\tSome(h) => {\n\t\t\t\t\theight = h;\n\t\t\t\t\tControlFlow::Continue(acc + 1)\n\t\t\t\t}\n\t\t\t\tNone => ControlFlow::Break(acc),\n\t\t\t}\n\t\t});\n\n\t\t1.max(match flow {\n\t\t\tControlFlow::Continue(i) => i,\n\t\t\tControlFlow::Break(i) => i,\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-core/src/pick/mod.rs",
    "content": "yazi_macro::mod_flat!(pick);\n"
  },
  {
    "path": "yazi-core/src/pick/pick.rs",
    "content": "use tokio::sync::mpsc::UnboundedSender;\nuse yazi_config::{YAZI, popup::Position};\nuse yazi_widgets::Scrollable;\n\n#[derive(Default)]\npub struct Pick {\n\tpub title:    String,\n\tpub items:    Vec<String>,\n\tpub position: Position,\n\n\tpub offset:   usize,\n\tpub cursor:   usize,\n\tpub callback: Option<UnboundedSender<Option<usize>>>,\n\n\tpub visible: bool,\n}\n\nimpl Pick {\n\tpub fn title(&self) -> &str { &self.title }\n\n\tpub fn window(&self) -> impl Iterator<Item = (usize, &str)> {\n\t\tself.items.iter().map(AsRef::as_ref).enumerate().skip(self.offset).take(self.limit())\n\t}\n}\n\nimpl Scrollable for Pick {\n\tfn total(&self) -> usize { self.items.len() }\n\n\tfn limit(&self) -> usize {\n\t\tself.position.offset.height.saturating_sub(YAZI.pick.border()) as usize\n\t}\n\n\tfn cursor_mut(&mut self) -> &mut usize { &mut self.cursor }\n\n\tfn offset_mut(&mut self) -> &mut usize { &mut self.offset }\n}\n"
  },
  {
    "path": "yazi-core/src/spot/mod.rs",
    "content": "yazi_macro::mod_flat!(spot);\n"
  },
  {
    "path": "yazi-core/src/spot/spot.rs",
    "content": "use tokio_util::sync::CancellationToken;\nuse yazi_config::YAZI;\nuse yazi_fs::File;\nuse yazi_macro::render;\nuse yazi_parser::mgr::SpotLock;\nuse yazi_plugin::isolate;\nuse yazi_shared::{pool::Symbol, url::UrlBuf};\n\n#[derive(Default)]\npub struct Spot {\n\tpub lock: Option<SpotLock>,\n\tpub skip: usize,\n\n\tpub(super) ct: Option<CancellationToken>,\n}\n\nimpl Spot {\n\tpub fn go(&mut self, file: File, mime: Symbol<str>) {\n\t\tif mime.is_empty() {\n\t\t\treturn; // Wait till mimetype is resolved to avoid flickering\n\t\t} else if self.same_lock(&file, &mime) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet Some(spotter) = YAZI.plugin.spotter(&file, &mime) else {\n\t\t\treturn self.reset();\n\t\t};\n\n\t\tself.abort();\n\t\tself.ct = Some(isolate::spot(&spotter.run, file, mime, self.skip));\n\t}\n\n\tpub fn visible(&self) -> bool { self.lock.is_some() }\n\n\tpub fn abort(&mut self) { self.ct.take().map(|ct| ct.cancel()); }\n\n\tpub fn reset(&mut self) {\n\t\tself.abort();\n\t\trender!(self.lock.take().is_some());\n\t}\n\n\tpub fn same_url(&self, url: &UrlBuf) -> bool { self.lock.as_ref().is_some_and(|l| *url == l.url) }\n\n\tpub fn same_file(&self, file: &File, mime: &str) -> bool {\n\t\tself.same_url(&file.url)\n\t\t\t&& self.lock.as_ref().is_some_and(|l| file.cha.hits(l.cha) && mime == l.mime)\n\t}\n\n\tpub fn same_lock(&self, file: &File, mime: &str) -> bool {\n\t\tself.same_file(file, mime) && self.lock.as_ref().is_some_and(|l| self.skip == l.skip)\n\t}\n}\n"
  },
  {
    "path": "yazi-core/src/tab/backstack.rs",
    "content": "use yazi_shared::url::{Url, UrlBuf};\n\n#[derive(Default)]\npub struct Backstack {\n\tcursor: usize,\n\tstack:  Vec<UrlBuf>,\n}\n\nimpl Backstack {\n\tpub fn push(&mut self, url: Url) {\n\t\tif self.stack.is_empty() {\n\t\t\tself.stack.push(url.to_owned());\n\t\t\treturn;\n\t\t}\n\n\t\tif self.stack[self.cursor] == url {\n\t\t\treturn;\n\t\t}\n\n\t\tself.cursor += 1;\n\t\tif self.cursor == self.stack.len() {\n\t\t\tself.stack.push(url.to_owned());\n\t\t} else {\n\t\t\tself.stack[self.cursor] = url.to_owned();\n\t\t\tself.stack.truncate(self.cursor + 1);\n\t\t}\n\n\t\t// Only keep 30 URLs before the cursor, the cleanup threshold is 60\n\t\tif self.stack.len() > 60 {\n\t\t\tlet start = self.cursor.saturating_sub(30);\n\t\t\tself.stack.drain(..start);\n\t\t\tself.cursor -= start;\n\t\t}\n\t}\n\n\tpub fn current(&self) -> Option<&UrlBuf> { self.stack.get(self.cursor) }\n\n\tpub fn shift_backward(&mut self) -> Option<&UrlBuf> {\n\t\tif self.cursor > 0 {\n\t\t\tself.cursor -= 1;\n\t\t\tSome(&self.stack[self.cursor])\n\t\t} else {\n\t\t\tNone\n\t\t}\n\t}\n\n\tpub fn shift_forward(&mut self) -> Option<&UrlBuf> {\n\t\tif self.cursor + 1 >= self.stack.len() {\n\t\t\tNone\n\t\t} else {\n\t\t\tself.cursor += 1;\n\t\t\tSome(&self.stack[self.cursor])\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\n\t#[test]\n\tfn test_backstack() {\n\t\tlet mut bs: Backstack = Backstack::default();\n\t\tassert_eq!(bs.shift_forward(), None);\n\n\t\tbs.push(Url::regular(\"1\"));\n\t\tassert_eq!(bs.stack[bs.cursor], Url::regular(\"1\"));\n\n\t\tbs.push(Url::regular(\"2\"));\n\t\tbs.push(Url::regular(\"3\"));\n\t\tassert_eq!(bs.stack[bs.cursor], Url::regular(\"3\"));\n\n\t\tassert_eq!(bs.shift_backward().unwrap(), Url::regular(\"2\"));\n\t\tassert_eq!(bs.shift_backward().unwrap(), Url::regular(\"1\"));\n\t\tassert_eq!(bs.shift_backward(), None);\n\t\tassert_eq!(bs.shift_backward(), None);\n\t\tassert_eq!(bs.stack[bs.cursor], Url::regular(\"1\"));\n\t\tassert_eq!(bs.shift_forward().unwrap(), Url::regular(\"2\"));\n\t\tassert_eq!(bs.shift_forward().unwrap(), Url::regular(\"3\"));\n\t\tassert_eq!(bs.shift_forward(), None);\n\n\t\tbs.shift_backward();\n\t\tbs.push(Url::regular(\"4\"));\n\n\t\tassert_eq!(bs.stack[bs.cursor], Url::regular(\"4\"));\n\t\tassert_eq!(bs.shift_forward(), None);\n\t\tassert_eq!(bs.shift_backward().unwrap(), Url::regular(\"2\"));\n\t}\n}\n"
  },
  {
    "path": "yazi-core/src/tab/finder.rs",
    "content": "use anyhow::Result;\nuse hashbrown::HashMap;\nuse yazi_fs::{Files, Filter, FilterCase};\nuse yazi_shared::{path::{AsPath, PathBufDyn}, url::UrlBuf};\n\nuse crate::tab::Folder;\n\npub struct Finder {\n\tpub filter:  Filter,\n\tpub matched: HashMap<PathBufDyn, u8>,\n\tlock:        FinderLock,\n}\n\n#[derive(Default)]\nstruct FinderLock {\n\tcwd:      UrlBuf,\n\trevision: u64,\n}\n\nimpl Finder {\n\tpub fn new(s: &str, case: FilterCase) -> Result<Self> {\n\t\tOk(Self {\n\t\t\tfilter:  Filter::new(s, case)?,\n\t\t\tmatched: Default::default(),\n\t\t\tlock:    Default::default(),\n\t\t})\n\t}\n\n\tpub fn prev(&self, files: &Files, cursor: usize, include: bool) -> Option<isize> {\n\t\tfor i in !include as usize..files.len() {\n\t\t\tlet idx = (cursor + files.len() - i) % files.len();\n\t\t\tif let Some(s) = files[idx].name()\n\t\t\t\t&& self.filter.matches(s)\n\t\t\t{\n\t\t\t\treturn Some(idx as isize - cursor as isize);\n\t\t\t}\n\t\t}\n\t\tNone\n\t}\n\n\tpub fn next(&self, files: &Files, cursor: usize, include: bool) -> Option<isize> {\n\t\tfor i in !include as usize..files.len() {\n\t\t\tlet idx = (cursor + i) % files.len();\n\t\t\tif let Some(s) = files[idx].name()\n\t\t\t\t&& self.filter.matches(s)\n\t\t\t{\n\t\t\t\treturn Some(idx as isize - cursor as isize);\n\t\t\t}\n\t\t}\n\t\tNone\n\t}\n\n\tpub fn catchup(&mut self, folder: &Folder) -> bool {\n\t\tif self.lock == *folder {\n\t\t\treturn false;\n\t\t}\n\t\tself.matched.clear();\n\n\t\tlet mut i = 0u8;\n\t\tfor file in folder.files.iter() {\n\t\t\tif file.name().is_none_or(|s| !self.filter.matches(s)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tself.matched.insert(file.urn().into(), i);\n\t\t\tif self.matched.len() > 99 {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\ti += 1;\n\t\t}\n\n\t\tself.lock = folder.into();\n\t\ttrue\n\t}\n}\n\nimpl Finder {\n\tpub fn matched_idx<T>(&self, folder: &Folder, urn: T) -> Option<u8>\n\twhere\n\t\tT: AsPath,\n\t{\n\t\tif self.lock == *folder { self.matched.get(&urn.as_path()).copied() } else { None }\n\t}\n}\n\n// --- Lock\nimpl From<&Folder> for FinderLock {\n\tfn from(value: &Folder) -> Self {\n\t\tSelf { cwd: value.url.clone(), revision: value.files.revision }\n\t}\n}\n\nimpl PartialEq<Folder> for FinderLock {\n\tfn eq(&self, other: &Folder) -> bool {\n\t\tself.revision == other.files.revision && self.cwd == other.url\n\t}\n}\n"
  },
  {
    "path": "yazi-core/src/tab/folder.rs",
    "content": "use std::mem;\n\nuse yazi_config::{LAYOUT, YAZI};\nuse yazi_dds::Pubsub;\nuse yazi_fs::{File, Files, FilesOp, FolderStage, cha::Cha};\nuse yazi_macro::err;\nuse yazi_proxy::MgrProxy;\nuse yazi_shared::{Id, path::{AsPath, PathBufDyn, PathDyn}, url::UrlBuf};\nuse yazi_widgets::{Scrollable, Step};\n\npub struct Folder {\n\tpub url:   UrlBuf,\n\tpub cha:   Cha,\n\tpub files: Files,\n\tpub stage: FolderStage,\n\n\tpub offset: usize,\n\tpub cursor: usize,\n\n\tpub page:  usize,\n\tpub trace: Option<PathBufDyn>,\n}\n\nimpl Default for Folder {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\turl:    Default::default(),\n\t\t\tcha:    Default::default(),\n\t\t\tfiles:  Files::new(YAZI.mgr.show_hidden.get()),\n\t\t\tstage:  Default::default(),\n\t\t\toffset: Default::default(),\n\t\t\tcursor: Default::default(),\n\t\t\tpage:   Default::default(),\n\t\t\ttrace:  Default::default(),\n\t\t}\n\t}\n}\n\nimpl<T: Into<UrlBuf>> From<T> for Folder {\n\tfn from(value: T) -> Self { Self { url: value.into(), ..Default::default() } }\n}\n\nimpl Folder {\n\tpub fn update(&mut self, op: FilesOp) -> bool {\n\t\tlet (stage, revision) = (self.stage.clone(), self.files.revision);\n\t\tmatch op {\n\t\t\tFilesOp::Full(_, _, cha) => {\n\t\t\t\t(self.cha, self.stage) = (cha, FolderStage::Loaded);\n\t\t\t}\n\t\t\tFilesOp::Part(_, ref files, _) if files.is_empty() => {\n\t\t\t\t(self.cha, self.stage) = (Cha::default(), FolderStage::Loading);\n\t\t\t}\n\t\t\tFilesOp::Part(_, _, ticket) if ticket == self.files.ticket() => {\n\t\t\t\tself.stage = FolderStage::Loading;\n\t\t\t}\n\t\t\tFilesOp::Done(_, cha, ticket) if ticket == self.files.ticket() => {\n\t\t\t\t(self.cha, self.stage) = (cha, FolderStage::Loaded);\n\t\t\t}\n\t\t\tFilesOp::IOErr(_, ref err) => {\n\t\t\t\t(self.cha, self.stage) = (Cha::default(), FolderStage::Failed(err.clone()));\n\t\t\t}\n\t\t\t_ => {}\n\t\t}\n\n\t\tmatch op {\n\t\t\tFilesOp::Full(_, files, _) => self.files.update_full(files),\n\t\t\tFilesOp::Part(_, files, ticket) => self.files.update_part(files, ticket),\n\t\t\tFilesOp::Done(..) => {}\n\t\t\tFilesOp::Size(_, sizes) => self.files.update_size(sizes),\n\t\t\tFilesOp::IOErr(..) => self.files.update_ioerr(),\n\n\t\t\tFilesOp::Creating(_, files) => self.files.update_creating(files),\n\t\t\tFilesOp::Deleting(_, urns) => {\n\t\t\t\tlet deleted = self.files.update_deleting(urns);\n\t\t\t\tlet delta = deleted.into_iter().filter(|&i| i < self.cursor).count() as isize;\n\t\t\t\tself.arrow(-delta);\n\t\t\t}\n\t\t\tFilesOp::Updating(_, files) => _ = self.files.update_updating(files),\n\t\t\tFilesOp::Upserting(_, files) => self.files.update_upserting(files),\n\t\t}\n\n\t\tself.trace = self.trace.take_if(|_| !self.files.is_empty() || self.stage.is_loading());\n\t\tself.repos(None);\n\n\t\t(&stage, revision) != (&self.stage, self.files.revision)\n\t}\n\n\tpub fn update_pub(&mut self, tab: Id, op: FilesOp) -> bool {\n\t\tif self.update(op) {\n\t\t\terr!(Pubsub::pub_after_load(tab, &self.url, &self.stage));\n\t\t\treturn true;\n\t\t}\n\t\tfalse\n\t}\n\n\tpub fn arrow(&mut self, step: impl Into<Step>) -> bool {\n\t\tlet mut b = if self.files.is_empty() {\n\t\t\t(mem::take(&mut self.cursor), mem::take(&mut self.offset)) != (0, 0)\n\t\t} else {\n\t\t\tself.scroll(step)\n\t\t};\n\n\t\tself.trace = self.hovered().filter(|_| b).map(|h| h.urn().into()).or(self.trace.take());\n\t\tb |= self.squeeze_offset();\n\n\t\tself.sync_page(false);\n\t\tb\n\t}\n\n\tpub fn hover(&mut self, urn: PathDyn) -> bool {\n\t\tif self.hovered().map(|h| h.urn()) == Some(urn) {\n\t\t\treturn self.arrow(0);\n\t\t}\n\n\t\tlet new = self.files.position(urn).unwrap_or(self.cursor) as isize;\n\t\tself.arrow(new - self.cursor as isize)\n\t}\n\n\tpub fn repos(&mut self, urn: Option<PathDyn>) -> bool {\n\t\tif let Some(u) = urn {\n\t\t\tself.hover(u)\n\t\t} else if let Some(u) = &self.trace {\n\t\t\tself.hover(u.clone().as_path())\n\t\t} else {\n\t\t\tself.arrow(0)\n\t\t}\n\t}\n\n\tpub fn sync_page(&mut self, force: bool) {\n\t\tlet limit = LAYOUT.get().folder_limit();\n\t\tif limit == 0 {\n\t\t\treturn;\n\t\t}\n\n\t\tlet new = self.cursor / limit;\n\t\tif mem::replace(&mut self.page, new) != new || force {\n\t\t\tMgrProxy::update_paged_by(new, &self.url);\n\t\t}\n\t}\n\n\tfn squeeze_offset(&mut self) -> bool {\n\t\tlet old = self.offset;\n\t\tlet len = self.files.len();\n\n\t\tlet limit = LAYOUT.get().folder_limit();\n\t\tlet scrolloff = (limit / 2).min(YAZI.mgr.scrolloff.get() as usize);\n\n\t\tself.offset = if self.cursor < (self.offset + limit).min(len).saturating_sub(scrolloff) {\n\t\t\tlen.saturating_sub(limit).min(self.offset)\n\t\t} else {\n\t\t\tlen.saturating_sub(limit).min(self.cursor.saturating_sub(limit) + 1 + scrolloff)\n\t\t};\n\n\t\told != self.offset\n\t}\n}\n\nimpl Folder {\n\t#[inline]\n\tpub fn hovered(&self) -> Option<&File> { self.files.get(self.cursor) }\n\n\t#[inline]\n\tpub fn hovered_mut(&mut self) -> Option<&mut File> { self.files.get_mut(self.cursor) }\n\n\tpub fn paginate(&self, page: usize) -> &[File] {\n\t\tlet len = self.files.len();\n\t\tlet limit = LAYOUT.get().folder_limit();\n\n\t\tlet start = (page.saturating_sub(1) * limit).min(len.saturating_sub(1));\n\t\tlet end = ((page + 2) * limit).min(len);\n\t\t&self.files[start..end]\n\t}\n}\n\nimpl Scrollable for Folder {\n\tfn total(&self) -> usize { self.files.len() }\n\n\tfn limit(&self) -> usize { LAYOUT.get().folder_limit() }\n\n\tfn scrolloff(&self) -> usize { (self.limit() / 2).min(YAZI.mgr.scrolloff.get() as usize) }\n\n\tfn cursor_mut(&mut self) -> &mut usize { &mut self.cursor }\n\n\tfn offset_mut(&mut self) -> &mut usize { &mut self.offset }\n}\n"
  },
  {
    "path": "yazi-core/src/tab/history.rs",
    "content": "use std::ops::{Deref, DerefMut};\n\nuse hashbrown::HashMap;\nuse yazi_shared::url::{AsUrl, UrlBuf};\n\nuse super::Folder;\n\n#[derive(Default)]\npub struct History(HashMap<UrlBuf, Folder>);\n\nimpl Deref for History {\n\ttype Target = HashMap<UrlBuf, Folder>;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl DerefMut for History {\n\tfn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }\n}\n\nimpl History {\n\tpub fn remove_or(&mut self, url: impl AsUrl) -> Folder {\n\t\tlet url = url.as_url();\n\t\tself.0.remove(&url).unwrap_or_else(|| Folder::from(url))\n\t}\n}\n"
  },
  {
    "path": "yazi-core/src/tab/mod.rs",
    "content": "yazi_macro::mod_flat!(backstack finder folder history mode preference preview selected tab);\n"
  },
  {
    "path": "yazi-core/src/tab/mode.rs",
    "content": "use std::{collections::BTreeSet, fmt::Display, mem};\n\n#[derive(Clone, Debug, Default, Eq, PartialEq)]\npub enum Mode {\n\t#[default]\n\tNormal,\n\tSelect(usize, BTreeSet<usize>),\n\tUnset(usize, BTreeSet<usize>),\n}\n\nimpl Mode {\n\tpub fn visual_mut(&mut self) -> Option<(usize, &mut BTreeSet<usize>)> {\n\t\tmatch self {\n\t\t\tSelf::Normal => None,\n\t\t\tSelf::Select(start, indices) => Some((*start, indices)),\n\t\t\tSelf::Unset(start, indices) => Some((*start, indices)),\n\t\t}\n\t}\n\n\tpub fn take_visual(&mut self) -> Option<(usize, BTreeSet<usize>)> {\n\t\tmatch mem::take(self) {\n\t\t\tSelf::Normal => None,\n\t\t\tSelf::Select(start, indices) => Some((start, indices)),\n\t\t\tSelf::Unset(start, indices) => Some((start, indices)),\n\t\t}\n\t}\n}\n\nimpl Mode {\n\tpub fn is_select(&self) -> bool { matches!(self, Self::Select(..)) }\n\n\tpub fn is_unset(&self) -> bool { matches!(self, Self::Unset(..)) }\n\n\tpub fn is_visual(&self) -> bool { matches!(self, Self::Select(..) | Self::Unset(..)) }\n}\n\nimpl Display for Mode {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tf.write_str(match self {\n\t\t\tSelf::Normal => \"normal\",\n\t\t\tSelf::Select(..) => \"select\",\n\t\t\tSelf::Unset(..) => \"unset\",\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-core/src/tab/preference.rs",
    "content": "use yazi_config::YAZI;\nuse yazi_fs::{FilesSorter, SortBy, SortFallback};\n\n#[derive(Clone, PartialEq)]\npub struct Preference {\n\t// Display\n\tpub name:        String,\n\tpub linemode:    String,\n\tpub show_hidden: bool,\n\n\t// Sorting\n\tpub sort_by:        SortBy,\n\tpub sort_sensitive: bool,\n\tpub sort_reverse:   bool,\n\tpub sort_dir_first: bool,\n\tpub sort_translit:  bool,\n\tpub sort_fallback:  SortFallback,\n}\n\nimpl Default for Preference {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\t// Display\n\t\t\tname:        String::new(),\n\t\t\tlinemode:    YAZI.mgr.linemode.clone(),\n\t\t\tshow_hidden: YAZI.mgr.show_hidden.get(),\n\n\t\t\t// Sorting\n\t\t\tsort_by:        YAZI.mgr.sort_by.get(),\n\t\t\tsort_sensitive: YAZI.mgr.sort_sensitive.get(),\n\t\t\tsort_reverse:   YAZI.mgr.sort_reverse.get(),\n\t\t\tsort_dir_first: YAZI.mgr.sort_dir_first.get(),\n\t\t\tsort_translit:  YAZI.mgr.sort_translit.get(),\n\t\t\tsort_fallback:  YAZI.mgr.sort_fallback.get(),\n\t\t}\n\t}\n}\n\nimpl From<&Preference> for FilesSorter {\n\tfn from(value: &Preference) -> Self {\n\t\tSelf {\n\t\t\tby:        value.sort_by,\n\t\t\tsensitive: value.sort_sensitive,\n\t\t\treverse:   value.sort_reverse,\n\t\t\tdir_first: value.sort_dir_first,\n\t\t\ttranslit:  value.sort_translit,\n\t\t\tfallback:  value.sort_fallback,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-core/src/tab/preview.rs",
    "content": "use std::time::Duration;\n\nuse tokio::{pin, task::JoinHandle};\nuse tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};\nuse tokio_util::sync::CancellationToken;\nuse yazi_adapter::ADAPTOR;\nuse yazi_config::{LAYOUT, YAZI};\nuse yazi_fs::{File, Files, FilesOp, cha::Cha};\nuse yazi_macro::render;\nuse yazi_parser::mgr::PreviewLock;\nuse yazi_plugin::{external::Highlighter, isolate};\nuse yazi_shared::{pool::Symbol, url::{UrlBuf, UrlLike}};\nuse yazi_vfs::{VfsFiles, VfsFilesOp};\n\n#[derive(Default)]\npub struct Preview {\n\tpub lock: Option<PreviewLock>,\n\tpub skip: usize,\n\n\tpreviewer_ct:    Option<CancellationToken>,\n\tpub folder_lock: Option<UrlBuf>,\n\tfolder_loader:   Option<JoinHandle<()>>,\n}\n\nimpl Preview {\n\tpub fn go(&mut self, file: File, mime: Symbol<str>, force: bool) {\n\t\tif mime.is_empty() {\n\t\t\treturn; // Wait till mimetype is resolved to avoid flickering\n\t\t} else if !force && self.same_lock(&file, &mime) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet Some(previewer) = YAZI.plugin.previewer(&file, &mime) else {\n\t\t\treturn self.reset();\n\t\t};\n\n\t\tself.abort();\n\t\tself.previewer_ct = isolate::peek(&previewer.run, file, mime, self.skip);\n\t}\n\n\tpub fn go_folder(&mut self, file: File, dir: Option<Cha>, mime: Symbol<str>, force: bool) {\n\t\tif !file.url.is_internal() {\n\t\t\treturn self.go(file, mime, force);\n\t\t} else if self.folder_lock.as_ref() == Some(&file.url) {\n\t\t\treturn self.go(file, mime, force);\n\t\t}\n\n\t\tlet wd = file.url_owned();\n\t\tself.go(file, mime, force);\n\n\t\tself.folder_lock = Some(wd.clone());\n\t\tself.folder_loader.take().map(|h| h.abort());\n\t\tself.folder_loader = Some(tokio::spawn(async move {\n\t\t\tlet Some(new) = Files::assert_stale(&wd, dir.unwrap_or_default()).await else { return };\n\n\t\t\tlet rx = match Files::from_dir(&wd).await {\n\t\t\t\tOk(rx) => rx,\n\t\t\t\tErr(e) => return FilesOp::issue_error(&wd, e).await,\n\t\t\t};\n\n\t\t\tlet stream =\n\t\t\t\tUnboundedReceiverStream::new(rx).chunks_timeout(50000, Duration::from_millis(500));\n\t\t\tpin!(stream);\n\n\t\t\tlet ticket = FilesOp::prepare(&wd);\n\t\t\twhile let Some(chunk) = stream.next().await {\n\t\t\t\tFilesOp::Part(wd.clone(), chunk, ticket).emit();\n\t\t\t}\n\t\t\tFilesOp::Done(wd, new, ticket).emit();\n\t\t}));\n\t}\n\n\tpub fn abort(&mut self) {\n\t\tself.previewer_ct.take().map(|ct| ct.cancel());\n\t\tHighlighter::abort();\n\t}\n\n\tpub fn reset(&mut self) {\n\t\tself.abort();\n\t\tADAPTOR.get().image_hide().ok();\n\t\trender!(self.lock.take().is_some())\n\t}\n\n\tpub fn reset_image(&mut self) {\n\t\tself.abort();\n\t\tADAPTOR.get().image_hide().ok();\n\t}\n\n\tpub fn same_url(&self, url: &UrlBuf) -> bool { matches!(&self.lock, Some(l) if l.url == *url) }\n\n\tpub fn same_file(&self, file: &File, mime: &str) -> bool {\n\t\tself.same_url(&file.url)\n\t\t\t&& matches!(&self.lock , Some(l) if l.cha.hits(file.cha) && l.mime == mime && *l.area == LAYOUT.get().preview)\n\t}\n\n\tpub fn same_lock(&self, file: &File, mime: &str) -> bool {\n\t\tself.same_file(file, mime) && matches!(&self.lock, Some(l) if l.skip == self.skip)\n\t}\n\n\tpub fn same_folder(&self, url: &UrlBuf) -> bool { self.folder_lock.as_ref() == Some(url) }\n}\n"
  },
  {
    "path": "yazi-core/src/tab/selected.rs",
    "content": "use std::ops::Deref;\n\nuse hashbrown::HashMap;\nuse indexmap::IndexMap;\nuse yazi_fs::FilesOp;\nuse yazi_shared::{timestamp_us, url::{Url, UrlBuf, UrlBufCov, UrlCov}};\n\n#[derive(Default)]\npub struct Selected {\n\tinner:   IndexMap<UrlBufCov, u64>,\n\tparents: HashMap<UrlBufCov, usize>,\n}\n\nimpl Selected {\n\tpub fn len(&self) -> usize { self.inner.len() }\n\n\tpub fn is_empty(&self) -> bool { self.inner.is_empty() }\n\n\tpub fn values(&self) -> impl Iterator<Item = &UrlBuf> { self.inner.keys().map(Deref::deref) }\n\n\tpub fn contains<'a>(&self, url: impl Into<Url<'a>>) -> bool {\n\t\tself.inner.contains_key(&UrlCov::new(url))\n\t}\n\n\tpub fn add<'a>(&mut self, url: impl Into<Url<'a>>) -> bool { self.add_same([url]) == 1 }\n\n\tpub fn add_many<'a, I, T>(&mut self, urls: I) -> usize\n\twhere\n\t\tI: IntoIterator<Item = T>,\n\t\tT: Into<Url<'a>>,\n\t{\n\t\tlet mut grouped: IndexMap<_, Vec<_>> = Default::default();\n\t\tfor url in urls.into_iter().map(Into::into) {\n\t\t\tif let Some(p) = url.parent() {\n\t\t\t\tgrouped.entry(p).or_default().push(url);\n\t\t\t}\n\t\t}\n\t\tgrouped.into_values().map(|v| self.add_same(v)).sum()\n\t}\n\n\tfn add_same<'a, I, T>(&mut self, urls: I) -> usize\n\twhere\n\t\tI: IntoIterator<Item = T>,\n\t\tT: Into<Url<'a>>,\n\t{\n\t\t// If it has appeared as a parent\n\t\tlet urls: Vec<_> =\n\t\t\turls.into_iter().map(UrlCov::new).filter(|u| !self.parents.contains_key(u)).collect();\n\t\tif urls.is_empty() {\n\t\t\treturn 0;\n\t\t}\n\n\t\t// If it has appeared as a child\n\t\tlet mut parent = urls[0].parent();\n\t\tlet mut parents = vec![];\n\t\twhile let Some(u) = parent {\n\t\t\tif self.inner.contains_key(&UrlCov::new(u)) {\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tparent = u.parent();\n\t\t\tparents.push(u);\n\t\t}\n\n\t\tlet (now, len) = (timestamp_us(), self.inner.len());\n\t\tself.inner.extend(urls.iter().enumerate().map(|(i, u)| (u.into(), now + i as u64)));\n\n\t\tfor u in parents {\n\t\t\t*self.parents.entry_ref(&UrlCov::new(u)).or_default() += self.inner.len() - len;\n\t\t}\n\t\turls.len()\n\t}\n\n\tpub fn remove<'a>(&mut self, url: impl Into<Url<'a>> + Clone) -> bool {\n\t\tself.remove_same([url]) == 1\n\t}\n\n\tpub fn remove_many<'a, I, T>(&mut self, urls: I) -> usize\n\twhere\n\t\tI: IntoIterator<Item = T>,\n\t\tT: Into<Url<'a>>,\n\t{\n\t\tlet mut grouped: HashMap<_, Vec<_>> = Default::default();\n\t\tfor url in urls.into_iter().map(Into::into) {\n\t\t\tif let Some(p) = url.parent() {\n\t\t\t\tgrouped.entry(p).or_default().push(url);\n\t\t\t}\n\t\t}\n\n\t\tlet affected = grouped.into_values().map(|v| self.remove_same(v)).sum();\n\t\tif affected > 0 {\n\t\t\tself.inner.sort_unstable_by(|_, a, _, b| a.cmp(b));\n\t\t}\n\n\t\taffected\n\t}\n\n\tfn remove_same<'a, I, T>(&mut self, urls: I) -> usize\n\twhere\n\t\tI: IntoIterator<Item = T>,\n\t\tT: Into<Url<'a>> + Clone,\n\t{\n\t\tlet mut it = urls.into_iter().peekable();\n\t\tlet Some(first) = it.peek().cloned().map(UrlCov::new) else { return 0 };\n\n\t\tlet count = it.filter_map(|u| self.inner.swap_remove(&UrlCov::new(u))).count();\n\t\tif count == 0 {\n\t\t\treturn 0;\n\t\t}\n\n\t\t// FIXME: use UrlCov::parent() instead\n\t\tlet mut parent = first.parent();\n\t\twhile let Some(u) = parent {\n\t\t\tlet n = self.parents.get_mut(&UrlCov::new(u)).unwrap();\n\n\t\t\t*n -= count;\n\t\t\tif *n == 0 {\n\t\t\t\tself.parents.remove(&UrlCov::new(u));\n\t\t\t}\n\n\t\t\tparent = u.parent();\n\t\t}\n\t\tcount\n\t}\n\n\tpub fn clear(&mut self) {\n\t\tself.inner.clear();\n\t\tself.parents.clear();\n\t}\n\n\tpub fn apply_op(&mut self, op: &FilesOp) {\n\t\tlet (removal, addition) = op.diff_recoverable(|u| self.contains(u));\n\t\tif !removal.is_empty() {\n\t\t\tself.remove_many(&removal);\n\t\t}\n\t\tif !addition.is_empty() {\n\t\t\tself.add_many(&addition);\n\t\t}\n\t}\n}\n\n#[cfg(test)]\nmod tests {\n\tuse std::path::Path;\n\n\tuse super::*;\n\n\t#[test]\n\tfn test_insert_non_conflicting() {\n\t\tlet mut s = Selected::default();\n\n\t\tassert!(s.add(Path::new(\"/a/b\")));\n\t\tassert!(s.add(Path::new(\"/c/d\")));\n\t\tassert_eq!(s.inner.len(), 2);\n\t}\n\n\t#[test]\n\tfn test_insert_conflicting_parent() {\n\t\tlet mut s = Selected::default();\n\n\t\tassert!(s.add(Path::new(\"/a\")));\n\t\tassert!(!s.add(Path::new(\"/a/b\")));\n\t}\n\n\t#[test]\n\tfn test_insert_conflicting_child() {\n\t\tlet mut s = Selected::default();\n\n\t\tassert!(s.add(Path::new(\"/a/b/c\")));\n\t\tassert!(!s.add(Path::new(\"/a/b\")));\n\t\tassert!(s.add(Path::new(\"/a/b/d\")));\n\t}\n\n\t#[test]\n\tfn test_remove() {\n\t\tlet mut s = Selected::default();\n\n\t\tassert!(s.add(Path::new(\"/a/b\")));\n\t\tassert!(!s.remove(Path::new(\"/a/c\")));\n\t\tassert!(s.remove(Path::new(\"/a/b\")));\n\t\tassert!(!s.remove(Path::new(\"/a/b\")));\n\t\tassert!(s.inner.is_empty());\n\t\tassert!(s.parents.is_empty());\n\t}\n\n\t#[test]\n\tfn insert_many_success() {\n\t\tlet mut s = Selected::default();\n\n\t\tassert_eq!(\n\t\t\t3,\n\t\t\ts.add_same([\n\t\t\t\tPath::new(\"/parent/child1\"),\n\t\t\t\tPath::new(\"/parent/child2\"),\n\t\t\t\tPath::new(\"/parent/child3\")\n\t\t\t])\n\t\t);\n\t}\n\n\t#[test]\n\tfn insert_many_with_existing_parent_fails() {\n\t\tlet mut s = Selected::default();\n\n\t\ts.add(Path::new(\"/parent\"));\n\t\tassert_eq!(0, s.add_same([Path::new(\"/parent/child1\"), Path::new(\"/parent/child2\")]));\n\t}\n\n\t#[test]\n\tfn insert_many_with_existing_child_fails() {\n\t\tlet mut s = Selected::default();\n\n\t\ts.add(Path::new(\"/parent/child1\"));\n\t\tassert_eq!(2, s.add_same([Path::new(\"/parent/child1\"), Path::new(\"/parent/child2\")]));\n\t}\n\n\t#[test]\n\tfn insert_many_empty_urls_list() {\n\t\tlet mut s = Selected::default();\n\n\t\tassert_eq!(0, s.add_same([] as [Url; 0]));\n\t}\n\n\t#[test]\n\tfn insert_many_with_parent_as_child_of_another_url() {\n\t\tlet mut s = Selected::default();\n\n\t\ts.add(Path::new(\"/parent/child\"));\n\t\tassert_eq!(\n\t\t\t0,\n\t\t\ts.add_same([Path::new(\"/parent/child/child1\"), Path::new(\"/parent/child/child2\")])\n\t\t);\n\t}\n\t#[test]\n\tfn insert_many_with_direct_parent_fails() {\n\t\tlet mut s = Selected::default();\n\n\t\ts.add(Path::new(\"/a\"));\n\t\tassert_eq!(0, s.add_same([Path::new(\"/a/b\")]));\n\t}\n\n\t#[test]\n\tfn insert_many_with_nested_child_fails() {\n\t\tlet mut s = Selected::default();\n\n\t\ts.add(Path::new(\"/a/b\"));\n\t\tassert_eq!(0, s.add_same([Path::new(\"/a\")]));\n\t\tassert_eq!(1, s.add_same([Path::new(\"/b\"), Path::new(\"/a\")]));\n\t}\n\n\t#[test]\n\tfn insert_many_sibling_directories_success() {\n\t\tlet mut s = Selected::default();\n\n\t\tassert_eq!(2, s.add_same([Path::new(\"/a/b\"), Path::new(\"/a/c\")]));\n\t}\n\n\t#[test]\n\tfn insert_many_with_grandchild_fails() {\n\t\tlet mut s = Selected::default();\n\n\t\ts.add(Path::new(\"/a/b\"));\n\t\tassert_eq!(0, s.add_same([Path::new(\"/a/b/c\")]));\n\t}\n\n\t#[test]\n\tfn test_insert_many_with_remove() {\n\t\tlet mut s = Selected::default();\n\n\t\tlet child1 = Path::new(\"/parent/child1\");\n\t\tlet child2 = Path::new(\"/parent/child2\");\n\t\tlet child3 = Path::new(\"/parent/child3\");\n\t\tassert_eq!(3, s.add_same([child1, child2, child3]));\n\n\t\tassert!(s.remove(child1));\n\t\tassert_eq!(s.inner.len(), 2);\n\t\tassert!(!s.parents.is_empty());\n\n\t\tassert!(s.remove(child2));\n\t\tassert!(!s.parents.is_empty());\n\n\t\tassert!(s.remove(child3));\n\t\tassert!(s.inner.is_empty());\n\t\tassert!(s.parents.is_empty());\n\t}\n}\n"
  },
  {
    "path": "yazi-core/src/tab/tab.rs",
    "content": "use std::borrow::Cow;\n\nuse anyhow::Result;\nuse ratatui::layout::Rect;\nuse tokio::task::JoinHandle;\nuse yazi_config::{LAYOUT, popup::{Origin, Position}};\nuse yazi_emulator::Dimension;\nuse yazi_fs::File;\nuse yazi_shared::{Id, Ids, url::{UrlBuf, UrlLike}};\n\nuse super::{Backstack, Finder, Folder, History, Mode, Preference, Preview};\nuse crate::{spot::Spot, tab::Selected};\n\npub struct Tab {\n\tpub id:      Id,\n\tpub mode:    Mode,\n\tpub pref:    Preference,\n\tpub current: Folder,\n\tpub parent:  Option<Folder>,\n\n\tpub backstack: Backstack,\n\tpub history:   History,\n\tpub selected:  Selected,\n\n\tpub spot:    Spot,\n\tpub preview: Preview,\n\tpub finder:  Option<Finder>,\n\tpub search:  Option<JoinHandle<Result<()>>>,\n}\n\nimpl Default for Tab {\n\tfn default() -> Self {\n\t\tstatic IDS: Ids = Ids::new();\n\n\t\tSelf {\n\t\t\tid:      IDS.next(),\n\t\t\tmode:    Default::default(),\n\t\t\tpref:    Default::default(),\n\t\t\tcurrent: Default::default(),\n\t\t\tparent:  Default::default(),\n\n\t\t\tbackstack: Default::default(),\n\t\t\thistory:   Default::default(),\n\t\t\tselected:  Default::default(),\n\n\t\t\tspot:    Default::default(),\n\t\t\tpreview: Default::default(),\n\t\t\tfinder:  Default::default(),\n\t\t\tsearch:  Default::default(),\n\t\t}\n\t}\n}\n\nimpl Tab {\n\tpub fn shutdown(&mut self) {\n\t\tself.search.take().map(|h| h.abort());\n\t\tself.preview.reset();\n\t}\n}\n\nimpl Tab {\n\t// --- Current\n\t#[inline]\n\tpub fn cwd(&self) -> &UrlBuf { &self.current.url }\n\n\tpub fn name(&self) -> Cow<'_, str> {\n\t\tlet url = &self.current.url;\n\t\tif !self.pref.name.is_empty() {\n\t\t\tCow::Borrowed(&self.pref.name)\n\t\t} else if let Some(s) = url.name() {\n\t\t\ts.to_string_lossy()\n\t\t} else {\n\t\t\turl.loc().to_string_lossy()\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn hovered(&self) -> Option<&File> { self.current.hovered() }\n\n\t#[inline]\n\tpub fn hovered_mut(&mut self) -> Option<&mut File> { self.current.hovered_mut() }\n\n\tpub fn hovered_rect(&self) -> Option<Rect> {\n\t\tlet y = self.current.files.position(self.hovered()?.urn())? - self.current.offset;\n\n\t\tlet mut rect = LAYOUT.get().current;\n\t\trect.y = rect.y.saturating_sub(1) + y as u16;\n\t\trect.height = 1;\n\t\tSome(rect)\n\t}\n\n\tpub fn hovered_rect_based(&self, pos: Position) -> Rect {\n\t\tlet ws = Dimension::available().into();\n\t\tif let Some(r) = self.hovered_rect() {\n\t\t\tPosition::sticky(ws, r, pos.offset)\n\t\t} else {\n\t\t\tPosition::new(Origin::TopCenter, pos.offset).rect(ws)\n\t\t}\n\t}\n\n\tpub fn selected_or_hovered(&self) -> Box<dyn Iterator<Item = &UrlBuf> + '_> {\n\t\tif self.selected.is_empty() {\n\t\t\tBox::new(self.hovered().map(|h| &h.url).into_iter())\n\t\t} else {\n\t\t\tBox::new(self.selected.values())\n\t\t}\n\t}\n\n\tpub fn hovered_and_selected(&self) -> Box<dyn Iterator<Item = &UrlBuf> + '_> {\n\t\tlet Some(h) = self.hovered() else {\n\t\t\treturn Box::new([UrlBuf::new()].into_iter().chain(self.selected.values()));\n\t\t};\n\t\tif self.selected.is_empty() {\n\t\t\tBox::new([&h.url, &h.url].into_iter())\n\t\t} else {\n\t\t\tBox::new([&h.url].into_iter().chain(self.selected.values()))\n\t\t}\n\t}\n\n\t// --- History\n\t#[inline]\n\tpub fn hovered_folder(&self) -> Option<&Folder> {\n\t\tself.hovered().filter(|&h| h.is_dir()).and_then(|h| self.history.get(&h.url))\n\t}\n\n\t#[inline]\n\tpub fn hovered_folder_mut(&mut self) -> Option<&mut Folder> {\n\t\tself.current.hovered_mut().filter(|h| h.is_dir()).and_then(|h| self.history.get_mut(&h.url))\n\t}\n}\n"
  },
  {
    "path": "yazi-core/src/tasks/file.rs",
    "content": "use hashbrown::HashSet;\nuse tracing::debug;\nuse yazi_shared::url::{UrlBuf, UrlBufCov, UrlLike};\n\nuse super::Tasks;\nuse crate::mgr::Yanked;\n\nimpl Tasks {\n\tpub fn file_cut(&self, src: &Yanked, dest: &UrlBuf, force: bool) {\n\t\tfor u in src.iter() {\n\t\t\tlet Some(Ok(to)) = u.name().map(|n| dest.try_join(n)) else {\n\t\t\t\tdebug!(\"file_cut: cannot join {u:?} with {dest:?}\");\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tif force && *u == to {\n\t\t\t\tdebug!(\"file_cut: same file, skip {to:?}\");\n\t\t\t} else {\n\t\t\t\tself.scheduler.file_cut(u.0.clone(), to, force);\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn file_copy(&self, src: &Yanked, dest: &UrlBuf, force: bool, follow: bool) {\n\t\tfor u in src.iter() {\n\t\t\tlet Some(Ok(to)) = u.name().map(|n| dest.try_join(n)) else {\n\t\t\t\tdebug!(\"file_copy: cannot join {u:?} with {dest:?}\");\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tif force && *u == to {\n\t\t\t\tdebug!(\"file_copy: same file, skip {to:?}\");\n\t\t\t} else {\n\t\t\t\tself.scheduler.file_copy(u.0.clone(), to, force, follow);\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn file_link(&self, src: &HashSet<UrlBufCov>, dest: &UrlBuf, relative: bool, force: bool) {\n\t\tfor u in src {\n\t\t\tlet Some(Ok(to)) = u.name().map(|n| dest.try_join(n)) else {\n\t\t\t\tdebug!(\"file_link: cannot join {u:?} with {dest:?}\");\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tif force && *u == to {\n\t\t\t\tdebug!(\"file_link: same file, skip {to:?}\");\n\t\t\t} else {\n\t\t\t\tself.scheduler.file_link(u.0.clone(), to, relative, force);\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn file_hardlink(&self, src: &HashSet<UrlBufCov>, dest: &UrlBuf, force: bool, follow: bool) {\n\t\tfor u in src {\n\t\t\tlet Some(Ok(to)) = u.name().map(|n| dest.try_join(n)) else {\n\t\t\t\tdebug!(\"file_hardlink: cannot join {u:?} with {dest:?}\");\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tif force && *u == to {\n\t\t\t\tdebug!(\"file_hardlink: same file, skip {to:?}\");\n\t\t\t} else {\n\t\t\t\tself.scheduler.file_hardlink(u.0.clone(), to, force, follow);\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn file_remove(&self, targets: Vec<UrlBuf>, permanently: bool) {\n\t\tfor u in targets {\n\t\t\tif permanently {\n\t\t\t\tself.scheduler.file_delete(u);\n\t\t\t} else {\n\t\t\t\tself.scheduler.file_trash(u);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-core/src/tasks/mod.rs",
    "content": "yazi_macro::mod_flat!(file prework process tasks);\n\npub const TASKS_BORDER: u16 = 2;\npub const TASKS_PADDING: u16 = 2;\npub const TASKS_PERCENT: u16 = 80;\n"
  },
  {
    "path": "yazi-core/src/tasks/prework.rs",
    "content": "use yazi_config::{YAZI, plugin::MAX_FETCHERS};\nuse yazi_fs::{File, Files, FsHash64, SortBy};\n\nuse super::Tasks;\nuse crate::mgr::Mimetype;\n\nimpl Tasks {\n\tpub fn fetch_paged(&self, paged: &[File], mimetype: &Mimetype) {\n\t\tlet mut loaded = self.scheduler.fetch.loaded.lock();\n\t\tlet mut tasks: [Vec<_>; MAX_FETCHERS as usize] = Default::default();\n\t\tfor f in paged {\n\t\t\tlet hash = f.hash_u64();\n\t\t\tfor g in YAZI.plugin.fetchers(f, mimetype.get(&f.url).unwrap_or_default()) {\n\t\t\t\tmatch loaded.get_mut(&hash) {\n\t\t\t\t\tSome(n) if *n & (1 << g.idx) != 0 => continue,\n\t\t\t\t\tSome(n) => *n |= 1 << g.idx,\n\t\t\t\t\tNone => _ = loaded.put(hash, 1 << g.idx),\n\t\t\t\t}\n\t\t\t\ttasks[g.idx as usize].push(f.clone());\n\t\t\t}\n\t\t}\n\n\t\tdrop(loaded);\n\t\tfor (i, tasks) in tasks.into_iter().enumerate() {\n\t\t\tif !tasks.is_empty() {\n\t\t\t\tself.scheduler.fetch_paged(&YAZI.plugin.fetchers[i], tasks);\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn preload_paged(&self, paged: &[File], mimetype: &Mimetype) {\n\t\tlet mut loaded = self.scheduler.preload.loaded.lock();\n\t\tfor f in paged {\n\t\t\tlet hash = f.hash_u64();\n\t\t\tfor p in YAZI.plugin.preloaders(f, mimetype.get(&f.url).unwrap_or_default()) {\n\t\t\t\tmatch loaded.get_mut(&hash) {\n\t\t\t\t\tSome(n) if *n & (1 << p.idx) != 0 => continue,\n\t\t\t\t\tSome(n) => *n |= 1 << p.idx,\n\t\t\t\t\tNone => _ = loaded.put(hash, 1 << p.idx),\n\t\t\t\t}\n\t\t\t\tself.scheduler.preload_paged(p, f);\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn prework_sorted(&self, targets: &Files) {\n\t\tif targets.sorter().by != SortBy::Size {\n\t\t\treturn;\n\t\t}\n\n\t\tlet targets: Vec<_> = {\n\t\t\tlet loading = self.scheduler.size.sizing.read();\n\t\t\ttargets\n\t\t\t\t.iter()\n\t\t\t\t.filter(|f| {\n\t\t\t\t\tf.is_dir() && !targets.sizes.contains_key(&f.urn()) && !loading.contains(&f.url)\n\t\t\t\t})\n\t\t\t\t.map(|f| &f.url)\n\t\t\t\t.collect()\n\t\t};\n\t\tif targets.is_empty() {\n\t\t\treturn;\n\t\t}\n\n\t\tlet mut loading = self.scheduler.size.sizing.write();\n\t\tfor &target in &targets {\n\t\t\tloading.insert(target.clone());\n\t\t}\n\n\t\tself.scheduler.prework_size(targets);\n\t}\n}\n"
  },
  {
    "path": "yazi-core/src/tasks/process.rs",
    "content": "use std::mem;\n\nuse yazi_fs::Splatter;\nuse yazi_parser::tasks::ProcessOpenOpt;\n\nuse super::Tasks;\n\nimpl Tasks {\n\t// TODO: remove\n\tpub fn open_shell_compat(&self, mut opt: ProcessOpenOpt) {\n\t\tif opt.spread {\n\t\t\topt.cmd = Splatter::new(&opt.args).splat(opt.cmd);\n\t\t\tself.scheduler.process_open(opt);\n\t\t\treturn;\n\t\t}\n\t\tif opt.args.is_empty() {\n\t\t\treturn;\n\t\t}\n\t\tif opt.args.len() == 2 {\n\t\t\topt.cmd = Splatter::new(&opt.args).splat(opt.cmd);\n\t\t\tself.scheduler.process_open(opt);\n\t\t\treturn;\n\t\t}\n\t\tlet hovered = mem::take(&mut opt.args[0]);\n\t\tfor target in opt.args.into_iter().skip(1) {\n\t\t\tlet args = vec![hovered.clone(), target];\n\t\t\tself.scheduler.process_open(ProcessOpenOpt {\n\t\t\t\tcwd: opt.cwd.clone(),\n\t\t\t\tcmd: Splatter::new(&args).splat(&opt.cmd),\n\t\t\t\targs,\n\t\t\t\tblock: opt.block,\n\t\t\t\torphan: opt.orphan,\n\t\t\t\tdone: None,\n\t\t\t\tspread: opt.spread,\n\t\t\t});\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-core/src/tasks/tasks.rs",
    "content": "use std::{sync::Arc, time::Duration};\n\nuse parking_lot::Mutex;\nuse tokio::{task::JoinHandle, time::sleep};\nuse yazi_emulator::Dimension;\nuse yazi_parser::app::TaskSummary;\nuse yazi_proxy::AppProxy;\nuse yazi_scheduler::{Ongoing, Scheduler, TaskSnap};\n\nuse super::{TASKS_BORDER, TASKS_PADDING, TASKS_PERCENT};\n\npub struct Tasks {\n\tpub scheduler: Arc<Scheduler>,\n\thandle:        JoinHandle<()>,\n\n\tpub visible: bool,\n\tpub cursor:  usize,\n\tpub snaps:   Vec<TaskSnap>,\n\tpub summary: TaskSummary,\n}\n\nimpl Tasks {\n\tpub fn serve() -> Self {\n\t\tlet scheduler = Scheduler::serve();\n\t\tlet ongoing = scheduler.ongoing.clone();\n\n\t\tlet handle = tokio::spawn(async move {\n\t\t\tlet mut last = TaskSummary::default();\n\t\t\tloop {\n\t\t\t\tsleep(Duration::from_millis(500)).await;\n\n\t\t\t\tlet new = ongoing.lock().summary();\n\t\t\t\tif last != new {\n\t\t\t\t\tlast = new;\n\t\t\t\t\tAppProxy::update_progress(new);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tSelf {\n\t\t\tscheduler: Arc::new(scheduler),\n\t\t\thandle,\n\n\t\t\tvisible: false,\n\t\t\tcursor: 0,\n\t\t\tsnaps: Default::default(),\n\t\t\tsummary: Default::default(),\n\t\t}\n\t}\n\n\tpub fn shutdown(&self) {\n\t\tself.scheduler.shutdown();\n\t\tself.handle.abort();\n\t}\n\n\tpub fn limit() -> usize {\n\t\t((Dimension::available().rows * TASKS_PERCENT / 100)\n\t\t\t.saturating_sub(TASKS_BORDER + TASKS_PADDING) as usize)\n\t\t\t/ 3\n\t}\n\n\tpub fn paginate(&self) -> Vec<TaskSnap> {\n\t\tself.ongoing().lock().values().take(Self::limit()).map(Into::into).collect()\n\t}\n\n\tpub fn ongoing(&self) -> &Arc<Mutex<Ongoing>> { &self.scheduler.ongoing }\n}\n"
  },
  {
    "path": "yazi-core/src/which/mod.rs",
    "content": "yazi_macro::mod_flat!(sorter which);\n"
  },
  {
    "path": "yazi-core/src/which/sorter.rs",
    "content": "use std::{borrow::Cow, mem};\n\nuse yazi_config::{YAZI, keymap::ChordCow, which::SortBy};\nuse yazi_shared::{natsort, translit::Transliterator};\n\n#[derive(Clone, Copy, PartialEq)]\npub struct WhichSorter {\n\tpub by:        SortBy,\n\tpub sensitive: bool,\n\tpub reverse:   bool,\n\tpub translit:  bool,\n}\n\nimpl Default for WhichSorter {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tby:        YAZI.which.sort_by,\n\t\t\tsensitive: YAZI.which.sort_sensitive,\n\t\t\treverse:   YAZI.which.sort_reverse,\n\t\t\ttranslit:  YAZI.which.sort_translit,\n\t\t}\n\t}\n}\n\nimpl WhichSorter {\n\tpub fn sort(&self, items: &mut Vec<ChordCow>) {\n\t\tif self.by == SortBy::None || items.is_empty() {\n\t\t\treturn;\n\t\t}\n\n\t\tlet mut indices = Vec::with_capacity(items.len());\n\t\tlet mut entities = Vec::with_capacity(items.len());\n\t\tfor (i, ctrl) in items.iter().enumerate() {\n\t\t\tindices.push(i);\n\t\t\tentities.push(match self.by {\n\t\t\t\tSortBy::None => unreachable!(),\n\t\t\t\tSortBy::Key => Cow::Owned(ctrl.on()),\n\t\t\t\tSortBy::Desc => ctrl.desc_or_run(),\n\t\t\t});\n\t\t}\n\n\t\tindices.sort_unstable_by(|&a, &b| {\n\t\t\tlet ordering = if !self.translit {\n\t\t\t\tnatsort(entities[a].as_bytes(), entities[b].as_bytes(), !self.sensitive)\n\t\t\t} else {\n\t\t\t\tnatsort(\n\t\t\t\t\tentities[a].as_bytes().transliterate().as_bytes(),\n\t\t\t\t\tentities[b].as_bytes().transliterate().as_bytes(),\n\t\t\t\t\t!self.sensitive,\n\t\t\t\t)\n\t\t\t};\n\n\t\t\tif self.reverse { ordering.reverse() } else { ordering }\n\t\t});\n\n\t\t*items = indices.into_iter().map(|i| mem::take(&mut items[i])).collect();\n\t}\n}\n"
  },
  {
    "path": "yazi-core/src/which/which.rs",
    "content": "use tokio::sync::mpsc;\nuse yazi_config::keymap::{ChordCow, Key};\nuse yazi_macro::{emit, render_and};\n\n#[derive(Default)]\npub struct Which {\n\tpub tx:    Option<mpsc::UnboundedSender<Option<yazi_binding::ChordCow>>>,\n\tpub times: usize,\n\tpub cands: Vec<ChordCow>,\n\n\t// Active state\n\tpub active: bool,\n\tpub silent: bool,\n}\n\nimpl Which {\n\tpub fn r#type(&mut self, key: Key) -> bool {\n\t\tself.cands.retain(|c| c.on.len() > self.times && c.on[self.times] == key);\n\t\tself.times += 1;\n\n\t\tif self.cands.is_empty() {\n\t\t\tself.dismiss(None);\n\t\t} else if self.cands.len() == 1 {\n\t\t\tlet chord = self.cands.remove(0);\n\t\t\tself.dismiss(Some(chord));\n\t\t} else if let Some(i) = self.cands.iter().position(|c| c.on.len() == self.times) {\n\t\t\tlet chord = self.cands.remove(i);\n\t\t\tself.dismiss(Some(chord));\n\t\t}\n\n\t\trender_and!(true)\n\t}\n\n\tpub fn dismiss(&mut self, chord: Option<ChordCow>) {\n\t\tself.times = 0;\n\t\tself.cands.clear();\n\n\t\tself.active = false;\n\t\tself.silent = false;\n\n\t\tif let Some(tx) = self.tx.take() {\n\t\t\t_ = tx.send(chord.clone().map(Into::into));\n\t\t}\n\t\tif let Some(chord) = chord {\n\t\t\temit!(Seq(chord.into_seq()));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-dds/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-dds\"\ndescription            = \"Yazi data distribution service\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[features]\ndefault      = [ \"vendored-lua\" ]\nvendored-lua = [ \"mlua/vendored\" ]\n\n[dependencies]\nyazi-binding = { path = \"../yazi-binding\", version = \"26.2.2\" }\nyazi-boot    = { path = \"../yazi-boot\", version = \"26.2.2\" }\nyazi-fs      = { path = \"../yazi-fs\", version = \"26.2.2\" }\nyazi-macro   = { path = \"../yazi-macro\", version = \"26.2.2\" }\nyazi-parser  = { path = \"../yazi-parser\", version = \"26.2.2\" }\nyazi-shared  = { path = \"../yazi-shared\", version = \"26.2.2\" }\nyazi-widgets = { path = \"../yazi-widgets\", version = \"26.2.2\" }\n\n# External dependencies\nanyhow        = { workspace = true }\nhashbrown     = { workspace = true }\nmlua          = { workspace = true }\nordered-float = { workspace = true }\nparking_lot   = { workspace = true }\npaste         = { workspace = true }\nserde         = { workspace = true }\nserde_json    = { workspace = true }\ntokio         = { workspace = true }\ntokio-stream  = { workspace = true }\ntracing       = { workspace = true }\n\n[build-dependencies]\nvergen-gitcl = { version = \"9.1.0\", features = [ \"build\" ] }\n\n[target.\"cfg(unix)\".dependencies]\nuzers = { workspace = true }\n"
  },
  {
    "path": "yazi-dds/README.md",
    "content": "# yazi-dds\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-dds/build.rs",
    "content": "use std::error::Error;\n\nuse vergen_gitcl::{Emitter, GitclBuilder};\n\nfn main() -> Result<(), Box<dyn Error>> {\n\tEmitter::default()\n\t\t.add_instructions(&GitclBuilder::default().commit_date(true).sha(true).build()?)?\n\t\t.emit()?;\n\n\tOk(())\n}\n"
  },
  {
    "path": "yazi-dds/src/client.rs",
    "content": "use std::{mem, str::FromStr};\n\nuse anyhow::Result;\nuse hashbrown::{HashMap, HashSet};\nuse parking_lot::RwLock;\nuse serde::{Deserialize, Serialize};\nuse tokio::{io::AsyncWriteExt, select, sync::mpsc, task::JoinHandle, time};\nuse tracing::error;\nuse yazi_macro::try_format;\nuse yazi_shared::{Id, RoCell};\n\nuse crate::{ClientReader, ClientWriter, Payload, Pubsub, Server, Stream, ember::{Ember, EmberHey}};\n\npub static ID: RoCell<Id> = RoCell::new();\npub(super) static PEERS: RoCell<RwLock<HashMap<Id, Peer>>> = RoCell::new();\n\npub(super) static QUEUE_TX: RoCell<mpsc::UnboundedSender<String>> = RoCell::new();\npub(super) static QUEUE_RX: RoCell<mpsc::UnboundedReceiver<String>> = RoCell::new();\n\n#[derive(Debug)]\npub struct Client {\n\tpub(super) id:        Id,\n\tpub(super) tx:        mpsc::UnboundedSender<String>,\n\tpub(super) abilities: HashSet<String>,\n}\n\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct Peer {\n\tpub(super) abilities: HashSet<String>,\n}\n\nimpl Client {\n\t/// Connect to an existing server or start a new one.\n\tpub(super) fn serve() {\n\t\tlet mut rx = QUEUE_RX.drop();\n\t\twhile rx.try_recv().is_ok() {}\n\n\t\ttokio::spawn(async move {\n\t\t\tlet mut server = None;\n\t\t\tlet (mut lines, mut writer) = Self::connect(&mut server).await;\n\n\t\t\tloop {\n\t\t\t\tselect! {\n\t\t\t\t\tSome(payload) = rx.recv() => {\n\t\t\t\t\t\tif writer.write_all(payload.as_bytes()).await.is_err() {\n\t\t\t\t\t\t\t(lines, writer) = Self::reconnect(&mut server).await;\n\t\t\t\t\t\t\twriter.write_all(payload.as_bytes()).await.ok(); // Retry once\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tOk(next) = lines.next_line() => {\n\t\t\t\t\t\tlet Some(line) = next else {\n\t\t\t\t\t\t\t(lines, writer) = Self::reconnect(&mut server).await;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tif line.is_empty() {\n\t\t\t\t\t\t\tcontinue;  // Heartbeat, ignore\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet payload = match Payload::from_str(&line) {\n\t\t\t\t\t\t\tOk(p) => p,\n\t\t\t\t\t\t\tErr(e) => {\n\t\t\t\t\t\t\t\terror!(\"Failed to parse DDS payload:\\n{line}\\n\\nError:\\n{e}\");\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tif let Ember::Hey(hey) = &payload.body {\n\t\t\t\t\t\t\tSelf::handle_hey(hey);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tpayload.try_flush().ok();\n\t\t\t\t\t\tpayload.emit();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\tpub(super) fn push<'a>(payload: impl Into<Payload<'a>>) -> Result<()> {\n\t\tOk(QUEUE_TX.send(try_format!(\"{}\\n\", payload.into())?)?)\n\t}\n\n\tpub(super) fn able(&self, ability: &str) -> bool { self.abilities.contains(ability) }\n\n\tasync fn connect(server: &mut Option<JoinHandle<()>>) -> (ClientReader, ClientWriter) {\n\t\tlet mut first = true;\n\t\tloop {\n\t\t\tif let Ok(conn) = Stream::connect().await {\n\t\t\t\tPubsub::pub_inner_hi();\n\t\t\t\ttracing::debug!(\"Connected to existing DDS server on instance {ID}\");\n\t\t\t\treturn conn;\n\t\t\t}\n\n\t\t\tserver.take().map(|h| h.abort());\n\t\t\tmatch Server::make().await {\n\t\t\t\tOk(h) => {\n\t\t\t\t\t*server = Some(h);\n\t\t\t\t\tsuper::STATE.load_or_create().await;\n\t\t\t\t\ttracing::debug!(\"Started new DDS server on instance {ID}\");\n\t\t\t\t}\n\t\t\t\tErr(e) => {\n\t\t\t\t\ttracing::error!(\"Could not connect to or start a new DDS server on instance {ID}: {e}\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif mem::replace(&mut first, false) && server.is_some() {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttime::sleep(time::Duration::from_secs(1)).await;\n\t\t}\n\t}\n\n\tasync fn reconnect(server: &mut Option<JoinHandle<()>>) -> (ClientReader, ClientWriter) {\n\t\tPEERS.write().clear();\n\n\t\ttime::sleep(time::Duration::from_millis(500)).await;\n\t\tSelf::connect(server).await\n\t}\n\n\tfn handle_hey(body: &EmberHey) {\n\t\t*PEERS.write() = body\n\t\t\t.peers\n\t\t\t.iter()\n\t\t\t.filter(|&(id, _)| *id != *ID)\n\t\t\t.map(|(&id, peer)| (id, peer.clone()))\n\t\t\t.collect();\n\t}\n}\n\nimpl Peer {\n\tpub(super) fn new(abilities: &HashSet<String>) -> Self { Self { abilities: abilities.clone() } }\n\n\tpub fn able(&self, ability: &str) -> bool { self.abilities.contains(ability) }\n}\n"
  },
  {
    "path": "yazi-dds/src/ember/bulk.rs",
    "content": "use hashbrown::HashMap;\nuse mlua::{IntoLua, Lua, Value};\nuse serde::{Deserialize, Serialize};\nuse yazi_shared::url::{Url, UrlCow};\n\nuse super::Ember;\n\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct EmberBulk<'a> {\n\tpub changes: HashMap<UrlCow<'a>, UrlCow<'a>>,\n}\n\nimpl<'a> EmberBulk<'a> {\n\tpub fn borrowed<I>(changes: I) -> Ember<'a>\n\twhere\n\t\tI: Iterator<Item = (Url<'a>, Url<'a>)>,\n\t{\n\t\tSelf { changes: changes.map(|(from, to)| (from.into(), to.into())).collect() }.into()\n\t}\n}\n\nimpl EmberBulk<'static> {\n\tpub fn owned<'a, I>(changes: I) -> Ember<'static>\n\twhere\n\t\tI: Iterator<Item = (Url<'a>, Url<'a>)>,\n\t{\n\t\tSelf {\n\t\t\tchanges: changes.map(|(from, to)| (from.to_owned().into(), to.to_owned().into())).collect(),\n\t\t}\n\t\t.into()\n\t}\n}\n\nimpl<'a> From<EmberBulk<'a>> for Ember<'a> {\n\tfn from(value: EmberBulk<'a>) -> Self { Self::Bulk(value) }\n}\n\nimpl IntoLua for EmberBulk<'_> {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tlua\n\t\t\t.create_table_from(\n\t\t\t\tself\n\t\t\t\t\t.changes\n\t\t\t\t\t.into_iter()\n\t\t\t\t\t.map(|(from, to)| (yazi_binding::Url::new(from), yazi_binding::Url::new(to))),\n\t\t\t)?\n\t\t\t.into_lua(lua)\n\t}\n}\n"
  },
  {
    "path": "yazi-dds/src/ember/bye.rs",
    "content": "use mlua::{IntoLua, Lua, Value};\nuse serde::{Deserialize, Serialize};\n\nuse super::Ember;\n\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct EmberBye;\n\nimpl EmberBye {\n\tpub fn borrowed() -> Ember<'static> { Self.into() }\n}\n\nimpl From<EmberBye> for Ember<'_> {\n\tfn from(value: EmberBye) -> Self { Self::Bye(value) }\n}\n\nimpl IntoLua for EmberBye {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> { lua.create_table()?.into_lua(lua) }\n}\n"
  },
  {
    "path": "yazi-dds/src/ember/cd.rs",
    "content": "use std::borrow::Cow;\n\nuse mlua::{IntoLua, Lua, Value};\nuse serde::{Deserialize, Serialize};\nuse yazi_shared::{Id, url::UrlBuf};\n\nuse super::Ember;\n\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct EmberCd<'a> {\n\tpub tab: Id,\n\tpub url: Cow<'a, UrlBuf>,\n\t#[serde(skip)]\n\tdummy:   bool,\n}\n\nimpl<'a> EmberCd<'a> {\n\tpub fn borrowed(tab: Id, url: &'a UrlBuf) -> Ember<'a> {\n\t\tSelf { tab, url: url.into(), dummy: false }.into()\n\t}\n}\n\nimpl EmberCd<'static> {\n\tpub fn owned(tab: Id, _: &UrlBuf) -> Ember<'static> {\n\t\tSelf { tab, url: Default::default(), dummy: true }.into()\n\t}\n}\n\nimpl<'a> From<EmberCd<'a>> for Ember<'a> {\n\tfn from(value: EmberCd<'a>) -> Self { Self::Cd(value) }\n}\n\nimpl IntoLua for EmberCd<'_> {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tlua\n\t\t\t.create_table_from([\n\t\t\t\t(\"tab\", self.tab.get().into_lua(lua)?),\n\t\t\t\t(\"url\", Some(self.url).filter(|_| !self.dummy).map(yazi_binding::Url::new).into_lua(lua)?),\n\t\t\t])?\n\t\t\t.into_lua(lua)\n\t}\n}\n"
  },
  {
    "path": "yazi-dds/src/ember/custom.rs",
    "content": "use mlua::{IntoLua, Lua, Value};\nuse serde::Serialize;\nuse yazi_shared::data::Data;\n\nuse super::Ember;\nuse crate::Sendable;\n\n#[derive(Clone, Debug)]\npub struct EmberCustom {\n\tpub kind: String,\n\tpub data: Data,\n}\n\nimpl EmberCustom {\n\tpub fn from_str(kind: &str, data: &str) -> anyhow::Result<Ember<'static>> {\n\t\tOk(Self { kind: kind.to_owned(), data: serde_json::from_str(data)? }.into())\n\t}\n\n\tpub fn from_lua(lua: &Lua, kind: &str, data: Value) -> mlua::Result<Ember<'static>> {\n\t\tOk(Self { kind: kind.to_owned(), data: Sendable::value_to_data(lua, data)? }.into())\n\t}\n}\n\nimpl From<EmberCustom> for Ember<'_> {\n\tfn from(value: EmberCustom) -> Self { Self::Custom(value) }\n}\n\nimpl IntoLua for EmberCustom {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> { Sendable::data_to_value(lua, self.data) }\n}\n\nimpl Serialize for EmberCustom {\n\tfn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n\t\tserde::Serialize::serialize(&self.data, serializer)\n\t}\n}\n"
  },
  {
    "path": "yazi-dds/src/ember/delete.rs",
    "content": "use std::borrow::Cow;\n\nuse mlua::{IntoLua, Lua, Value};\nuse serde::{Deserialize, Serialize};\nuse yazi_shared::url::UrlBuf;\n\nuse super::Ember;\n\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct EmberDelete<'a> {\n\tpub urls: Cow<'a, Vec<UrlBuf>>,\n}\n\nimpl<'a> EmberDelete<'a> {\n\tpub fn borrowed(urls: &'a Vec<UrlBuf>) -> Ember<'a> { Self { urls: Cow::Borrowed(urls) }.into() }\n}\n\nimpl EmberDelete<'static> {\n\tpub fn owned(urls: Vec<UrlBuf>) -> Ember<'static> { Self { urls: Cow::Owned(urls) }.into() }\n}\n\nimpl<'a> From<EmberDelete<'a>> for Ember<'a> {\n\tfn from(value: EmberDelete<'a>) -> Self { Self::Delete(value) }\n}\n\nimpl IntoLua for EmberDelete<'_> {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tlet urls =\n\t\t\tlua.create_sequence_from(self.urls.into_owned().into_iter().map(yazi_binding::Url::new))?;\n\n\t\tlua.create_table_from([(\"urls\", urls)])?.into_lua(lua)\n\t}\n}\n"
  },
  {
    "path": "yazi-dds/src/ember/download.rs",
    "content": "use std::borrow::Cow;\n\nuse mlua::{IntoLua, Lua, Value};\nuse serde::{Deserialize, Serialize};\nuse yazi_shared::url::UrlBuf;\n\nuse super::Ember;\n\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct EmberDownload<'a> {\n\tpub urls: Cow<'a, Vec<UrlBuf>>,\n}\n\nimpl<'a> EmberDownload<'a> {\n\tpub fn borrowed(urls: &'a Vec<UrlBuf>) -> Ember<'a> { Self { urls: Cow::Borrowed(urls) }.into() }\n}\n\nimpl EmberDownload<'static> {\n\tpub fn owned(urls: Vec<UrlBuf>) -> Ember<'static> { Self { urls: Cow::Owned(urls) }.into() }\n}\n\nimpl<'a> From<EmberDownload<'a>> for Ember<'a> {\n\tfn from(value: EmberDownload<'a>) -> Self { Self::Download(value) }\n}\n\nimpl IntoLua for EmberDownload<'_> {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tlet urls =\n\t\t\tlua.create_sequence_from(self.urls.into_owned().into_iter().map(yazi_binding::Url::new))?;\n\n\t\tlua.create_table_from([(\"urls\", urls)])?.into_lua(lua)\n\t}\n}\n"
  },
  {
    "path": "yazi-dds/src/ember/duplicate.rs",
    "content": "use std::borrow::Cow;\n\nuse mlua::{IntoLua, Lua, Value};\nuse serde::{Deserialize, Serialize};\nuse yazi_shared::url::UrlBuf;\n\nuse super::Ember;\n\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct EmberDuplicate<'a> {\n\tpub items: Cow<'a, Vec<EmberDuplicateItem>>,\n}\n\nimpl<'a> EmberDuplicate<'a> {\n\tpub fn borrowed(items: &'a Vec<EmberDuplicateItem>) -> Ember<'a> {\n\t\tSelf { items: Cow::Borrowed(items) }.into()\n\t}\n}\n\nimpl EmberDuplicate<'static> {\n\tpub fn owned(items: Vec<EmberDuplicateItem>) -> Ember<'static> {\n\t\tSelf { items: Cow::Owned(items) }.into()\n\t}\n}\n\nimpl<'a> From<EmberDuplicate<'a>> for Ember<'a> {\n\tfn from(value: EmberDuplicate<'a>) -> Self { Self::Duplicate(value) }\n}\n\nimpl IntoLua for EmberDuplicate<'_> {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tlua.create_table_from([(\"items\", self.items.into_owned())])?.into_lua(lua)\n\t}\n}\n\n// --- Item\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct EmberDuplicateItem {\n\tpub from: UrlBuf,\n\tpub to:   UrlBuf,\n}\n\nimpl IntoLua for EmberDuplicateItem {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tlua\n\t\t\t.create_table_from([\n\t\t\t\t(\"from\", yazi_binding::Url::new(self.from)),\n\t\t\t\t(\"to\", yazi_binding::Url::new(self.to)),\n\t\t\t])?\n\t\t\t.into_lua(lua)\n\t}\n}\n"
  },
  {
    "path": "yazi-dds/src/ember/ember.rs",
    "content": "use anyhow::{Result, bail};\nuse mlua::{ExternalResult, IntoLua, Lua, Value};\nuse yazi_shared::Id;\n\nuse super::{EmberBulk, EmberBye, EmberCd, EmberCustom, EmberDelete, EmberDownload, EmberDuplicate, EmberHey, EmberHi, EmberHover, EmberLoad, EmberMount, EmberMove, EmberRename, EmberTab, EmberTrash, EmberYank};\nuse crate::Payload;\n\n#[derive(Clone, Debug)]\npub enum Ember<'a> {\n\tHi(EmberHi<'a>),\n\tHey(EmberHey),\n\tBye(EmberBye),\n\tTab(EmberTab),\n\tCd(EmberCd<'a>),\n\tLoad(EmberLoad<'a>),\n\tHover(EmberHover<'a>),\n\tRename(EmberRename<'a>),\n\tBulk(EmberBulk<'a>),\n\tYank(EmberYank<'a>),\n\tDuplicate(EmberDuplicate<'a>),\n\tMove(EmberMove<'a>),\n\tTrash(EmberTrash<'a>),\n\tDelete(EmberDelete<'a>),\n\tDownload(EmberDownload<'a>),\n\tMount(EmberMount),\n\tCustom(EmberCustom),\n}\n\nimpl Ember<'static> {\n\tpub fn from_str(kind: &str, body: &str) -> Result<Self> {\n\t\tOk(match kind {\n\t\t\t\"hi\" => Self::Hi(serde_json::from_str(body)?),\n\t\t\t\"hey\" => Self::Hey(serde_json::from_str(body)?),\n\t\t\t\"bye\" => Self::Bye(serde_json::from_str(body)?),\n\t\t\t\"tab\" => Self::Tab(serde_json::from_str(body)?),\n\t\t\t\"cd\" => Self::Cd(serde_json::from_str(body)?),\n\t\t\t\"load\" => Self::Load(serde_json::from_str(body)?),\n\t\t\t\"hover\" => Self::Hover(serde_json::from_str(body)?),\n\t\t\t\"rename\" => Self::Rename(serde_json::from_str(body)?),\n\t\t\t\"bulk\" => Self::Bulk(serde_json::from_str(body)?),\n\t\t\t\"@yank\" => Self::Yank(serde_json::from_str(body)?),\n\t\t\t\"duplicate\" => Self::Duplicate(serde_json::from_str(body)?),\n\t\t\t\"move\" => Self::Move(serde_json::from_str(body)?),\n\t\t\t\"trash\" => Self::Trash(serde_json::from_str(body)?),\n\t\t\t\"delete\" => Self::Delete(serde_json::from_str(body)?),\n\t\t\t\"download\" => Self::Download(serde_json::from_str(body)?),\n\t\t\t\"mount\" => Self::Mount(serde_json::from_str(body)?),\n\t\t\t_ => EmberCustom::from_str(kind, body)?,\n\t\t})\n\t}\n\n\tpub fn from_lua(lua: &Lua, kind: &str, value: Value) -> mlua::Result<Self> {\n\t\tSelf::validate(kind).into_lua_err()?;\n\t\tEmberCustom::from_lua(lua, kind, value)\n\t}\n\n\tpub fn validate(kind: &str) -> Result<()> {\n\t\tif matches!(\n\t\t\tkind,\n\t\t\t\"hi\"\n\t\t\t\t| \"hey\"\n\t\t\t\t| \"bye\"\n\t\t\t\t| \"tab\"\n\t\t\t\t| \"cd\"\n\t\t\t\t| \"load\"\n\t\t\t\t| \"hover\"\n\t\t\t\t| \"rename\"\n\t\t\t\t| \"bulk\"\n\t\t\t\t| \"@yank\"\n\t\t\t\t| \"duplicate\"\n\t\t\t\t| \"move\"\n\t\t\t\t| \"trash\"\n\t\t\t\t| \"delete\"\n\t\t\t\t| \"download\"\n\t\t\t\t| \"mount\"\n\t\t) || kind.starts_with(\"key-\")\n\t\t\t|| kind.starts_with(\"ind-\")\n\t\t\t|| kind.starts_with(\"emit-\")\n\t\t\t|| kind.starts_with(\"relay-\")\n\t\t{\n\t\t\tbail!(\"Cannot construct system event\");\n\t\t}\n\n\t\tlet mut it = kind.bytes().peekable();\n\t\tif it.peek() == Some(&b'@') {\n\t\t\tit.next(); // Skip `@` as it's a prefix for static messages\n\t\t}\n\t\tif !it.all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'z' | b'-')) {\n\t\t\tbail!(\"Kind `{kind}` must be in kebab-case\");\n\t\t}\n\n\t\tOk(())\n\t}\n}\n\nimpl<'a> Ember<'a> {\n\tpub fn kind(&self) -> &str {\n\t\tmatch self {\n\t\t\tSelf::Hi(_) => \"hi\",\n\t\t\tSelf::Hey(_) => \"hey\",\n\t\t\tSelf::Bye(_) => \"bye\",\n\t\t\tSelf::Tab(_) => \"tab\",\n\t\t\tSelf::Cd(_) => \"cd\",\n\t\t\tSelf::Load(_) => \"load\",\n\t\t\tSelf::Hover(_) => \"hover\",\n\t\t\tSelf::Rename(_) => \"rename\",\n\t\t\tSelf::Bulk(_) => \"bulk\",\n\t\t\tSelf::Yank(_) => \"@yank\",\n\t\t\tSelf::Duplicate(_) => \"duplicate\",\n\t\t\tSelf::Move(_) => \"move\",\n\t\t\tSelf::Trash(_) => \"trash\",\n\t\t\tSelf::Delete(_) => \"delete\",\n\t\t\tSelf::Download(_) => \"download\",\n\t\t\tSelf::Mount(_) => \"mount\",\n\t\t\tSelf::Custom(b) => b.kind.as_str(),\n\t\t}\n\t}\n\n\tpub fn with_receiver(self, receiver: Id) -> Payload<'a> {\n\t\tPayload::new(self).with_receiver(receiver)\n\t}\n}\n\nimpl<'a> IntoLua for Ember<'a> {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tmatch self {\n\t\t\tSelf::Hi(b) => b.into_lua(lua),\n\t\t\tSelf::Hey(b) => b.into_lua(lua),\n\t\t\tSelf::Bye(b) => b.into_lua(lua),\n\t\t\tSelf::Cd(b) => b.into_lua(lua),\n\t\t\tSelf::Load(b) => b.into_lua(lua),\n\t\t\tSelf::Hover(b) => b.into_lua(lua),\n\t\t\tSelf::Tab(b) => b.into_lua(lua),\n\t\t\tSelf::Rename(b) => b.into_lua(lua),\n\t\t\tSelf::Bulk(b) => b.into_lua(lua),\n\t\t\tSelf::Yank(b) => b.into_lua(lua),\n\t\t\tSelf::Duplicate(b) => b.into_lua(lua),\n\t\t\tSelf::Move(b) => b.into_lua(lua),\n\t\t\tSelf::Trash(b) => b.into_lua(lua),\n\t\t\tSelf::Delete(b) => b.into_lua(lua),\n\t\t\tSelf::Download(b) => b.into_lua(lua),\n\t\t\tSelf::Mount(b) => b.into_lua(lua),\n\t\t\tSelf::Custom(b) => b.into_lua(lua),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-dds/src/ember/hey.rs",
    "content": "use hashbrown::HashMap;\nuse mlua::{IntoLua, Lua, Value};\nuse serde::{Deserialize, Serialize};\nuse yazi_shared::{Id, SStr};\n\nuse super::{Ember, EmberHi};\nuse crate::Peer;\n\n/// Server handshake\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct EmberHey {\n\tpub peers:   HashMap<Id, Peer>,\n\tpub version: SStr,\n}\n\nimpl EmberHey {\n\tpub fn owned(peers: HashMap<Id, Peer>) -> Ember<'static> {\n\t\tSelf { peers, version: EmberHi::version().into() }.into()\n\t}\n}\n\nimpl From<EmberHey> for Ember<'_> {\n\tfn from(value: EmberHey) -> Self { Self::Hey(value) }\n}\n\nimpl IntoLua for EmberHey {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> { lua.create_table()?.into_lua(lua) }\n}\n"
  },
  {
    "path": "yazi-dds/src/ember/hi.rs",
    "content": "use std::borrow::Cow;\n\nuse hashbrown::HashSet;\nuse mlua::{IntoLua, Lua, Value};\nuse serde::{Deserialize, Serialize};\nuse yazi_shared::SStr;\n\nuse super::Ember;\n\n/// Client handshake\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct EmberHi<'a> {\n\t/// Kinds of events the client can handle\n\tpub abilities: HashSet<Cow<'a, str>>,\n\tpub version:   SStr,\n}\n\nimpl<'a> EmberHi<'a> {\n\tpub fn borrowed<I>(abilities: I) -> Ember<'a>\n\twhere\n\t\tI: IntoIterator<Item = &'a str>,\n\t{\n\t\tSelf {\n\t\t\tabilities: abilities.into_iter().map(Into::into).collect(),\n\t\t\tversion:   Self::version().into(),\n\t\t}\n\t\t.into()\n\t}\n\n\tpub fn version() -> &'static str {\n\t\tconcat!(env!(\"CARGO_PKG_VERSION\"), \" \", env!(\"VERGEN_GIT_SHA\"))\n\t}\n}\n\nimpl<'a> From<EmberHi<'a>> for Ember<'a> {\n\tfn from(value: EmberHi<'a>) -> Self { Self::Hi(value) }\n}\n\nimpl IntoLua for EmberHi<'_> {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> { lua.create_table()?.into_lua(lua) }\n}\n"
  },
  {
    "path": "yazi-dds/src/ember/hover.rs",
    "content": "use std::borrow::Cow;\n\nuse mlua::{IntoLua, Lua, Value};\nuse serde::{Deserialize, Serialize};\nuse yazi_shared::{Id, url::UrlBuf};\n\nuse super::Ember;\n\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct EmberHover<'a> {\n\tpub tab: Id,\n\tpub url: Option<Cow<'a, UrlBuf>>,\n}\n\nimpl<'a> EmberHover<'a> {\n\tpub fn borrowed(tab: Id, url: Option<&'a UrlBuf>) -> Ember<'a> {\n\t\tSelf { tab, url: url.map(Into::into) }.into()\n\t}\n}\n\nimpl EmberHover<'static> {\n\tpub fn owned(tab: Id, _: Option<&UrlBuf>) -> Ember<'static> { Self { tab, url: None }.into() }\n}\n\nimpl<'a> From<EmberHover<'a>> for Ember<'a> {\n\tfn from(value: EmberHover<'a>) -> Self { Self::Hover(value) }\n}\n\nimpl IntoLua for EmberHover<'_> {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tlua\n\t\t\t.create_table_from([\n\t\t\t\t(\"tab\", self.tab.get().into_lua(lua)?),\n\t\t\t\t(\"url\", self.url.map(yazi_binding::Url::new).into_lua(lua)?),\n\t\t\t])?\n\t\t\t.into_lua(lua)\n\t}\n}\n"
  },
  {
    "path": "yazi-dds/src/ember/load.rs",
    "content": "use std::borrow::Cow;\n\nuse mlua::{IntoLua, Lua, Value};\nuse serde::{Deserialize, Serialize};\nuse yazi_fs::FolderStage;\nuse yazi_shared::{Id, url::UrlBuf};\n\nuse super::Ember;\n\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct EmberLoad<'a> {\n\tpub tab:   Id,\n\tpub url:   Cow<'a, UrlBuf>,\n\tpub stage: Cow<'a, FolderStage>,\n}\n\nimpl<'a> EmberLoad<'a> {\n\tpub fn borrowed(tab: Id, url: &'a UrlBuf, stage: &'a FolderStage) -> Ember<'a> {\n\t\tSelf { tab, url: url.into(), stage: Cow::Borrowed(stage) }.into()\n\t}\n}\n\nimpl EmberLoad<'static> {\n\tpub fn owned(tab: Id, url: &UrlBuf, stage: &FolderStage) -> Ember<'static> {\n\t\tSelf { tab, url: url.clone().into(), stage: Cow::Owned(stage.clone()) }.into()\n\t}\n}\n\nimpl<'a> From<EmberLoad<'a>> for Ember<'a> {\n\tfn from(value: EmberLoad<'a>) -> Self { Self::Load(value) }\n}\n\nimpl IntoLua for EmberLoad<'_> {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tlua\n\t\t\t.create_table_from([\n\t\t\t\t(\"tab\", self.tab.get().into_lua(lua)?),\n\t\t\t\t(\"url\", yazi_binding::Url::new(self.url).into_lua(lua)?),\n\t\t\t\t(\"stage\", yazi_binding::FolderStage::new(self.stage.into_owned()).into_lua(lua)?),\n\t\t\t])?\n\t\t\t.into_lua(lua)\n\t}\n}\n"
  },
  {
    "path": "yazi-dds/src/ember/mod.rs",
    "content": "yazi_macro::mod_flat!(\n\tbulk bye cd custom delete download duplicate ember hey hi hover load mount r#move rename tab trash yank\n);\n"
  },
  {
    "path": "yazi-dds/src/ember/mount.rs",
    "content": "use mlua::{IntoLua, Lua, Value};\nuse serde::{Deserialize, Serialize};\n\nuse super::Ember;\n\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct EmberMount;\n\nimpl EmberMount {\n\tpub fn borrowed() -> Ember<'static> { Self.into() }\n\n\tpub fn owned() -> Ember<'static> { Self::borrowed() }\n}\n\nimpl From<EmberMount> for Ember<'_> {\n\tfn from(value: EmberMount) -> Self { Self::Mount(value) }\n}\n\nimpl IntoLua for EmberMount {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Ok(Value::Nil) }\n}\n"
  },
  {
    "path": "yazi-dds/src/ember/move.rs",
    "content": "use std::borrow::Cow;\n\nuse mlua::{IntoLua, Lua, Value};\nuse serde::{Deserialize, Serialize};\nuse yazi_shared::url::UrlBuf;\n\nuse super::Ember;\n\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct EmberMove<'a> {\n\tpub items: Cow<'a, Vec<EmberMoveItem>>,\n}\n\nimpl<'a> EmberMove<'a> {\n\tpub fn borrowed(items: &'a Vec<EmberMoveItem>) -> Ember<'a> {\n\t\tSelf { items: Cow::Borrowed(items) }.into()\n\t}\n}\n\nimpl EmberMove<'static> {\n\tpub fn owned(items: Vec<EmberMoveItem>) -> Ember<'static> {\n\t\tSelf { items: Cow::Owned(items) }.into()\n\t}\n}\n\nimpl<'a> From<EmberMove<'a>> for Ember<'a> {\n\tfn from(value: EmberMove<'a>) -> Self { Self::Move(value) }\n}\n\nimpl IntoLua for EmberMove<'_> {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tlua.create_table_from([(\"items\", self.items.into_owned())])?.into_lua(lua)\n\t}\n}\n\n// --- Item\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct EmberMoveItem {\n\tpub from: UrlBuf,\n\tpub to:   UrlBuf,\n}\n\nimpl IntoLua for EmberMoveItem {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tlua\n\t\t\t.create_table_from([\n\t\t\t\t(\"from\", yazi_binding::Url::new(self.from)),\n\t\t\t\t(\"to\", yazi_binding::Url::new(self.to)),\n\t\t\t])?\n\t\t\t.into_lua(lua)\n\t}\n}\n"
  },
  {
    "path": "yazi-dds/src/ember/rename.rs",
    "content": "use std::borrow::Cow;\n\nuse mlua::{IntoLua, Lua, Value};\nuse serde::{Deserialize, Serialize};\nuse yazi_shared::{Id, url::UrlBuf};\n\nuse super::Ember;\n\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct EmberRename<'a> {\n\tpub tab:  Id,\n\tpub from: Cow<'a, UrlBuf>,\n\tpub to:   Cow<'a, UrlBuf>,\n}\n\nimpl<'a> EmberRename<'a> {\n\tpub fn borrowed(tab: Id, from: &'a UrlBuf, to: &'a UrlBuf) -> Ember<'a> {\n\t\tSelf { tab, from: from.into(), to: to.into() }.into()\n\t}\n}\n\nimpl EmberRename<'static> {\n\tpub fn owned(tab: Id, from: &UrlBuf, to: &UrlBuf) -> Ember<'static> {\n\t\tSelf { tab, from: from.clone().into(), to: to.clone().into() }.into()\n\t}\n}\n\nimpl<'a> From<EmberRename<'a>> for Ember<'a> {\n\tfn from(value: EmberRename<'a>) -> Self { Self::Rename(value) }\n}\n\nimpl IntoLua for EmberRename<'_> {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tlua\n\t\t\t.create_table_from([\n\t\t\t\t(\"tab\", self.tab.get().into_lua(lua)?),\n\t\t\t\t(\"from\", yazi_binding::Url::new(self.from).into_lua(lua)?),\n\t\t\t\t(\"to\", yazi_binding::Url::new(self.to).into_lua(lua)?),\n\t\t\t])?\n\t\t\t.into_lua(lua)\n\t}\n}\n"
  },
  {
    "path": "yazi-dds/src/ember/tab.rs",
    "content": "use mlua::{IntoLua, Lua, Value};\nuse serde::{Deserialize, Serialize};\nuse yazi_shared::Id;\n\nuse super::Ember;\n\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct EmberTab {\n\tpub id: Id,\n}\n\nimpl EmberTab {\n\tpub fn borrowed(id: Id) -> Ember<'static> { Self { id }.into() }\n\n\tpub fn owned(id: Id) -> Ember<'static> { Self::borrowed(id) }\n}\n\nimpl From<EmberTab> for Ember<'_> {\n\tfn from(value: EmberTab) -> Self { Self::Tab(value) }\n}\n\nimpl IntoLua for EmberTab {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tlua.create_table_from([(\"idx\", self.id.get())])?.into_lua(lua)\n\t}\n}\n"
  },
  {
    "path": "yazi-dds/src/ember/trash.rs",
    "content": "use std::borrow::Cow;\n\nuse mlua::{IntoLua, Lua, Value};\nuse serde::{Deserialize, Serialize};\nuse yazi_shared::url::UrlBuf;\n\nuse super::Ember;\n\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct EmberTrash<'a> {\n\tpub urls: Cow<'a, Vec<UrlBuf>>,\n}\n\nimpl<'a> EmberTrash<'a> {\n\tpub fn borrowed(urls: &'a Vec<UrlBuf>) -> Ember<'a> { Self { urls: Cow::Borrowed(urls) }.into() }\n}\n\nimpl EmberTrash<'static> {\n\tpub fn owned(urls: Vec<UrlBuf>) -> Ember<'static> { Self { urls: Cow::Owned(urls) }.into() }\n}\n\nimpl<'a> From<EmberTrash<'a>> for Ember<'a> {\n\tfn from(value: EmberTrash<'a>) -> Self { Self::Trash(value) }\n}\n\nimpl IntoLua for EmberTrash<'_> {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tlet urls =\n\t\t\tlua.create_sequence_from(self.urls.into_owned().into_iter().map(yazi_binding::Url::new))?;\n\n\t\tlua.create_table_from([(\"urls\", urls)])?.into_lua(lua)\n\t}\n}\n"
  },
  {
    "path": "yazi-dds/src/ember/yank.rs",
    "content": "use std::borrow::Cow;\n\nuse hashbrown::HashSet;\nuse mlua::{IntoLua, Lua, Value};\nuse serde::{Deserialize, Serialize};\nuse yazi_parser::mgr::UpdateYankedOpt;\nuse yazi_shared::url::UrlBufCov;\n\nuse super::Ember;\n\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct EmberYank<'a>(UpdateYankedOpt<'a>);\n\nimpl<'a> EmberYank<'a> {\n\tpub fn borrowed(cut: bool, urls: &'a HashSet<UrlBufCov>) -> Ember<'a> {\n\t\tSelf(UpdateYankedOpt { cut, urls: Cow::Borrowed(urls) }).into()\n\t}\n}\n\nimpl EmberYank<'static> {\n\tpub fn owned(cut: bool, _: &HashSet<UrlBufCov>) -> Ember<'static> {\n\t\tSelf(UpdateYankedOpt { cut, urls: Default::default() }).into()\n\t}\n}\n\nimpl<'a> From<EmberYank<'a>> for Ember<'a> {\n\tfn from(value: EmberYank<'a>) -> Self { Self::Yank(value) }\n}\n\nimpl IntoLua for EmberYank<'_> {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> { self.0.into_lua(lua) }\n}\n"
  },
  {
    "path": "yazi-dds/src/lib.rs",
    "content": "mod macros;\n\nyazi_macro::mod_pub!(ember spark);\n\nyazi_macro::mod_flat!(client payload pubsub pump sendable server state stream);\n\npub fn init() {\n\tlet (tx, rx) = tokio::sync::mpsc::unbounded_channel();\n\n\t// Client\n\tID.init(yazi_boot::ARGS.client_id.unwrap_or(yazi_shared::Id::unique()));\n\tPEERS.with(<_>::default);\n\tQUEUE_TX.init(tx);\n\tQUEUE_RX.init(rx);\n\n\t// Server\n\tCLIENTS.with(<_>::default);\n\tSTATE.with(<_>::default);\n\n\t// Pubsub\n\tLOCAL.with(<_>::default);\n\tREMOTE.with(<_>::default);\n\n\t// Env\n\tunsafe {\n\t\tif let Some(s) = std::env::var(\"YAZI_ID\").ok().filter(|s| !s.is_empty()) {\n\t\t\tstd::env::set_var(\"YAZI_PID\", s);\n\t\t}\n\t\tstd::env::set_var(\"YAZI_ID\", ID.to_string());\n\t\tstd::env::set_var(\n\t\t\t\"YAZI_LEVEL\",\n\t\t\t(std::env::var(\"YAZI_LEVEL\").unwrap_or_default().parse().unwrap_or(0u16) + 1).to_string(),\n\t\t);\n\t}\n}\n\npub fn serve() {\n\tPump::serve();\n\tClient::serve();\n}\n\npub async fn shutdown() { Pump::shutdown().await; }\n"
  },
  {
    "path": "yazi-dds/src/macros.rs",
    "content": "#[macro_export]\nmacro_rules! spark {\n\t(mgr: $name:ident, $body:expr) => {\n\t\tpaste::paste! {\n\t\t\t$crate::spark::Spark::[<$name:camel>]($body)\n\t\t}\n\t};\n\t($layer:ident : $name:ident, $body:expr) => {\n\t\tpaste::paste! {\n\t\t\t$crate::spark::Spark::[<$layer:camel $name:camel>]($body.into())\n\t\t}\n\t};\n}\n\n#[macro_export]\nmacro_rules! try_from_spark {\n\t($opt:ty, $($($layer:ident)? : $name:ident),+) => {\n\t\timpl<'a> std::convert::TryFrom<$crate::spark::Spark<'a>> for $opt {\n\t\t\ttype Error = ();\n\n\t\t\tfn try_from(value: $crate::spark::Spark<'a>) -> Result<Self, Self::Error> {\n\t\t\t\t$(\n\t\t\t\t\ttry_from_spark!(@if $($layer)? : $name, value);\n\t\t\t\t)+\n\t\t\t\tErr(())\n\t\t\t}\n\t\t}\n\t};\n\t(@if mgr : $name:ident, $value:ident) => {\n\t\ttry_from_spark!(@if : $name, $value);\n\t};\n\t(@if $($layer:ident)? : $name:ident, $value:ident) => {\n\t\tif let paste::paste! { $crate::spark::Spark::[<$($layer:camel)* $name:camel>](opt) } = $value {\n\t\t\treturn Ok(<_>::from(opt))\n\t\t}\n\t};\n}\n"
  },
  {
    "path": "yazi-dds/src/payload.rs",
    "content": "use std::{fmt::Display, io::Write, str::FromStr};\n\nuse anyhow::{Result, anyhow};\nuse mlua::{IntoLua, Lua, Value};\nuse yazi_boot::BOOT;\nuse yazi_macro::{emit, relay};\nuse yazi_shared::{Id, event::ActionCow};\n\nuse crate::{ID, ember::Ember, spark::Spark};\n\n#[derive(Clone, Debug)]\npub struct Payload<'a> {\n\tpub receiver: Id,\n\tpub sender:   Id,\n\tpub body:     Ember<'a>,\n}\n\nimpl<'a> Payload<'a> {\n\tpub fn new(body: Ember<'a>) -> Self { Self { receiver: Id(0), sender: *ID, body } }\n\n\tpub(super) fn flush(&self) -> Result<()> {\n\t\twriteln!(std::io::stdout(), \"{self}\")?;\n\t\tOk(())\n\t}\n\n\tpub(super) fn try_flush(&self) -> Result<()> {\n\t\tlet b = if self.receiver == 0 {\n\t\t\tBOOT.remote_events.contains(self.body.kind())\n\t\t} else if let Ember::Custom(b) = &self.body {\n\t\t\tBOOT.local_events.contains(&b.kind)\n\t\t} else {\n\t\t\tfalse\n\t\t};\n\t\tif b { self.flush() } else { Ok(()) }\n\t}\n\n\tpub(super) fn with_receiver(mut self, receiver: Id) -> Self {\n\t\tself.receiver = receiver;\n\t\tself\n\t}\n\n\tpub(super) fn with_sender(mut self, sender: Id) -> Self {\n\t\tself.sender = sender;\n\t\tself\n\t}\n}\n\nimpl Payload<'static> {\n\tpub(super) fn emit(self) {\n\t\temit!(Call(relay!(app:accept_payload).with_any(\"payload\", self)));\n\t}\n}\n\nimpl FromStr for Payload<'static> {\n\ttype Err = anyhow::Error;\n\n\tfn from_str(s: &str) -> Result<Self, Self::Err> {\n\t\tlet mut parts = s.splitn(4, ',');\n\n\t\tlet kind = parts.next().ok_or_else(|| anyhow!(\"empty kind\"))?;\n\n\t\tlet receiver =\n\t\t\tparts.next().and_then(|s| s.parse().ok()).ok_or_else(|| anyhow!(\"invalid receiver\"))?;\n\n\t\tlet sender =\n\t\t\tparts.next().and_then(|s| s.parse().ok()).ok_or_else(|| anyhow!(\"invalid sender\"))?;\n\n\t\tlet body = parts.next().ok_or_else(|| anyhow!(\"empty body\"))?;\n\n\t\tOk(Self { receiver, sender, body: Ember::from_str(kind, body)? })\n\t}\n}\n\nimpl<'a> From<Ember<'a>> for Payload<'a> {\n\tfn from(value: Ember<'a>) -> Self { Self::new(value) }\n}\n\nimpl<'a> TryFrom<Spark<'a>> for Payload<'a> {\n\ttype Error = ();\n\n\tfn try_from(value: Spark<'a>) -> Result<Self, Self::Error> {\n\t\tmatch value {\n\t\t\tSpark::AppAcceptPayload(payload) => Ok(payload),\n\t\t\t_ => Err(()),\n\t\t}\n\t}\n}\n\nimpl TryFrom<ActionCow> for Payload<'_> {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\ta.take_any2(\"payload\").ok_or_else(|| anyhow!(\"Missing 'payload' in Payload\"))?\n\t}\n}\n\nimpl Display for Payload<'_> {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tlet result = match &self.body {\n\t\t\tEmber::Hi(b) => serde_json::to_string(b),\n\t\t\tEmber::Hey(b) => serde_json::to_string(b),\n\t\t\tEmber::Bye(b) => serde_json::to_string(b),\n\t\t\tEmber::Cd(b) => serde_json::to_string(b),\n\t\t\tEmber::Load(b) => serde_json::to_string(b),\n\t\t\tEmber::Hover(b) => serde_json::to_string(b),\n\t\t\tEmber::Tab(b) => serde_json::to_string(b),\n\t\t\tEmber::Rename(b) => serde_json::to_string(b),\n\t\t\tEmber::Bulk(b) => serde_json::to_string(b),\n\t\t\tEmber::Yank(b) => serde_json::to_string(b),\n\t\t\tEmber::Duplicate(b) => serde_json::to_string(b),\n\t\t\tEmber::Move(b) => serde_json::to_string(b),\n\t\t\tEmber::Trash(b) => serde_json::to_string(b),\n\t\t\tEmber::Delete(b) => serde_json::to_string(b),\n\t\t\tEmber::Download(b) => serde_json::to_string(b),\n\t\t\tEmber::Mount(b) => serde_json::to_string(b),\n\t\t\tEmber::Custom(b) => serde_json::to_string(b),\n\t\t};\n\n\t\tif let Ok(s) = result {\n\t\t\twrite!(f, \"{},{},{},{s}\", self.body.kind(), self.receiver, self.sender)\n\t\t} else {\n\t\t\tErr(std::fmt::Error)\n\t\t}\n\t}\n}\n\nimpl<'a> IntoLua for Payload<'a> {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tlua\n\t\t\t.create_table_from([\n\t\t\t\t(\"receiver\", yazi_binding::Id(self.receiver).into_lua(lua)?),\n\t\t\t\t(\"sender\", yazi_binding::Id(self.sender).into_lua(lua)?),\n\t\t\t\t(\"body\", self.body.into_lua(lua)?),\n\t\t\t])?\n\t\t\t.into_lua(lua)\n\t}\n}\n"
  },
  {
    "path": "yazi-dds/src/pubsub.rs",
    "content": "use anyhow::Result;\nuse hashbrown::{HashMap, HashSet};\nuse mlua::Function;\nuse parking_lot::RwLock;\nuse yazi_boot::BOOT;\nuse yazi_fs::FolderStage;\nuse yazi_shared::{Id, RoCell, url::{Url, UrlBuf, UrlBufCov}};\n\nuse crate::{Client, ID, PEERS, ember::{Ember, EmberBulk, EmberDuplicateItem, EmberHi, EmberMoveItem}};\n\npub static LOCAL: RoCell<RwLock<HashMap<String, HashMap<String, Function>>>> = RoCell::new();\n\npub static REMOTE: RoCell<RwLock<HashMap<String, HashMap<String, Function>>>> = RoCell::new();\n\nmacro_rules! sub {\n\t($var:ident) => {\n\t\t|plugin: &str, kind: &str, f: Function| {\n\t\t\tlet mut var = $var.write();\n\t\t\tlet Some(map) = var.get_mut(kind) else {\n\t\t\t\tvar.insert(kind.to_owned(), HashMap::from_iter([(plugin.to_owned(), f)]));\n\t\t\t\treturn true;\n\t\t\t};\n\n\t\t\tif !map.contains_key(plugin) {\n\t\t\t\tmap.insert(plugin.to_owned(), f);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tfalse\n\t\t}\n\t};\n}\n\nmacro_rules! unsub {\n\t($var:ident) => {\n\t\t|plugin: &str, kind: &str| {\n\t\t\tlet mut var = $var.write();\n\t\t\tlet Some(map) = var.get_mut(kind) else { return false };\n\n\t\t\tif map.remove(plugin).is_none() {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif map.is_empty() {\n\t\t\t\tvar.remove(kind);\n\t\t\t}\n\t\t\ttrue\n\t\t}\n\t};\n}\n\nmacro_rules! pub_after {\n\t(&impl $name:ident ($($param:ident: $param_ty:ty),*), ($($borrowed:expr),*), ($($owned:expr),*), $static:literal) => {\n\t\tpaste::paste! {\n\t\t\tpub fn [<pub_after_ $name>]($($param: $param_ty),*) -> Result<()> {\n\t\t\t\tuse crate::ember::[<Ember $name:camel>] as B;\n\n\t\t\t\tlet n = if $static { concat!(\"@\", stringify!($name)) } else { stringify!($name) };\n\t\t\t\tif BOOT.local_events.contains(n) {\n\t\t\t\t\tB::borrowed($($borrowed),*).with_receiver(*ID).flush()?;\n\t\t\t\t}\n\t\t\t\tif ($static && Self::any_remote_own(n)) || (!$static && PEERS.read().values().any(|p| p.able(n))) {\n\t\t\t\t\tClient::push(B::borrowed($($borrowed),*))?;\n\t\t\t\t}\n\t\t\t\tif LOCAL.read().contains_key(n) {\n\t\t\t\t\tSelf::r#pub(B::owned($($owned),*))?;\n\t\t\t\t}\n\t\t\t\tOk(())\n\t\t\t}\n\t\t}\n\t};\n\t($name:ident ($($param:ident: $param_ty:ty),*), ($($borrowed:expr),*)) => {\n\t\tpub_after!(&impl $name($($param: $param_ty),*), ($($borrowed),*), ($($borrowed),*), false);\n\t};\n\t($name:ident ($($param:ident: $param_ty:ty),*), ($($borrowed:expr),*), ($($owned:expr),*)) => {\n\t\tpub_after!(&impl $name($($param: $param_ty),*), ($($borrowed),*), ($($owned),*), false);\n\t};\n\t(@ $name:ident ($($param:ident: $param_ty:ty),*), ($($borrowed:expr),*)) => {\n\t\tpub_after!(&impl $name($($param: $param_ty),*), ($($borrowed),*), ($($borrowed),*), true);\n\t};\n\t(@ $name:ident ($($param:ident: $param_ty:ty),*), ($($borrowed:expr),*), ($($owned:expr),*)) => {\n\t\tpub_after!(&impl $name($($param: $param_ty),*), ($($borrowed),*), ($($owned),*), true);\n\t};\n}\n\npub struct Pubsub;\n\nimpl Pubsub {\n\tpub fn sub(plugin: &str, kind: &str, f: Function) -> bool { sub!(LOCAL)(plugin, kind, f) }\n\n\tpub fn sub_remote(plugin: &str, kind: &str, f: Function) -> bool {\n\t\tsub!(REMOTE)(plugin, kind, f) && Self::pub_inner_hi()\n\t}\n\n\tpub fn unsub(plugin: &str, kind: &str) -> bool { unsub!(LOCAL)(plugin, kind) }\n\n\tpub fn unsub_remote(plugin: &str, kind: &str) -> bool {\n\t\tunsub!(REMOTE)(plugin, kind) && Self::pub_inner_hi()\n\t}\n\n\tpub fn r#pub(body: Ember<'static>) -> Result<()> {\n\t\tlet payload = body.with_receiver(*ID);\n\t\tpayload.try_flush()?;\n\t\tpayload.emit();\n\t\tOk(())\n\t}\n\n\tpub fn pub_to(receiver: Id, body: Ember<'static>) -> Result<()> {\n\t\tif receiver == *ID {\n\t\t\treturn Self::r#pub(body);\n\t\t}\n\n\t\tlet kind = body.kind();\n\t\tif receiver == 0 && Self::any_remote_own(kind) {\n\t\t\tClient::push(body)?;\n\t\t} else if PEERS.read().get(&receiver).is_some_and(|c| c.able(kind)) {\n\t\t\tClient::push(body.with_receiver(receiver))?;\n\t\t}\n\t\tOk(())\n\t}\n\n\tpub fn pub_inner_hi() -> bool {\n\t\tlet abilities = REMOTE.read().keys().cloned().collect();\n\t\tlet abilities = BOOT.remote_events.union(&abilities).map(AsRef::as_ref);\n\n\t\t// FIXME: handle error\n\t\tClient::push(EmberHi::borrowed(abilities)).ok();\n\t\ttrue\n\t}\n\n\tpub fn pub_after_bulk<'a, I>(changes: I) -> Result<()>\n\twhere\n\t\tI: Iterator<Item = (Url<'a>, Url<'a>)> + Clone,\n\t{\n\t\tif BOOT.local_events.contains(\"bulk\") {\n\t\t\tEmberBulk::borrowed(changes.clone()).with_receiver(*ID).flush()?;\n\t\t}\n\t\tif PEERS.read().values().any(|p| p.able(\"bulk\")) {\n\t\t\tClient::push(EmberBulk::borrowed(changes.clone()))?;\n\t\t}\n\t\tif LOCAL.read().contains_key(\"bulk\") {\n\t\t\tSelf::r#pub(EmberBulk::owned(changes))?;\n\t\t}\n\t\tOk(())\n\t}\n\n\tfn any_remote_own(kind: &str) -> bool {\n\t\tREMOTE.read().contains_key(kind)  // Own remote abilities\n\t\t\t|| PEERS.read().values().any(|p| p.able(kind))  // Remote peers' abilities\n\t\t\t|| BOOT.remote_events.contains(kind) // Own abilities from the command-line argument\n\t}\n}\n\nimpl Pubsub {\n\tpub_after!(tab(idx: Id), (idx));\n\n\tpub_after!(cd(tab: Id, url: &UrlBuf), (tab, url));\n\n\tpub_after!(load(tab: Id, url: &UrlBuf, stage: &FolderStage), (tab, url, stage));\n\n\tpub_after!(hover(tab: Id, url: Option<&UrlBuf>), (tab, url));\n\n\tpub_after!(rename(tab: Id, from: &UrlBuf, to: &UrlBuf), (tab, from, to));\n\n\tpub_after!(@yank(cut: bool, urls: &HashSet<UrlBufCov>), (cut, urls));\n\n\tpub_after!(duplicate(items: Vec<EmberDuplicateItem>), (&items), (items));\n\n\tpub_after!(move(items: Vec<EmberMoveItem>), (&items), (items));\n\n\tpub_after!(trash(urls: Vec<UrlBuf>), (&urls), (urls));\n\n\tpub_after!(delete(urls: Vec<UrlBuf>), (&urls), (urls));\n\n\tpub_after!(download(urls: Vec<UrlBuf>), (&urls), (urls));\n\n\tpub_after!(mount(), ());\n}\n"
  },
  {
    "path": "yazi-dds/src/pump.rs",
    "content": "use std::time::Duration;\n\nuse tokio::{pin, select, sync::mpsc};\nuse tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};\nuse yazi_macro::err;\nuse yazi_shared::{RoCell, url::UrlBuf};\n\nuse crate::{Pubsub, ember::{EmberDuplicateItem, EmberMoveItem}};\n\nstatic DUPLICATE_TX: RoCell<mpsc::UnboundedSender<EmberDuplicateItem>> = RoCell::new();\nstatic MOVE_TX: RoCell<mpsc::UnboundedSender<EmberMoveItem>> = RoCell::new();\nstatic TRASH_TX: RoCell<mpsc::UnboundedSender<UrlBuf>> = RoCell::new();\nstatic DELETE_TX: RoCell<mpsc::UnboundedSender<UrlBuf>> = RoCell::new();\nstatic DOWNLOAD_TX: RoCell<mpsc::UnboundedSender<UrlBuf>> = RoCell::new();\nstatic SHUTDOWN_TX: RoCell<mpsc::UnboundedSender<()>> = RoCell::new();\n\npub struct Pump;\n\nimpl Pump {\n\tpub fn push_duplicate<U>(from: U, to: U)\n\twhere\n\t\tU: Into<UrlBuf>,\n\t{\n\t\tDUPLICATE_TX.send(EmberDuplicateItem { from: from.into(), to: to.into() }).ok();\n\t}\n\n\tpub fn push_move<U>(from: U, to: U)\n\twhere\n\t\tU: Into<UrlBuf>,\n\t{\n\t\tMOVE_TX.send(EmberMoveItem { from: from.into(), to: to.into() }).ok();\n\t}\n\n\tpub fn push_trash<U>(target: U)\n\twhere\n\t\tU: Into<UrlBuf>,\n\t{\n\t\tTRASH_TX.send(target.into()).ok();\n\t}\n\n\tpub fn push_delete<U>(target: U)\n\twhere\n\t\tU: Into<UrlBuf>,\n\t{\n\t\tDELETE_TX.send(target.into()).ok();\n\t}\n\n\tpub fn push_download<U>(target: U)\n\twhere\n\t\tU: Into<UrlBuf>,\n\t{\n\t\tDOWNLOAD_TX.send(target.into()).ok();\n\t}\n\n\tpub(super) fn serve() {\n\t\tlet (move_tx, move_rx) = mpsc::unbounded_channel();\n\t\tlet (duplicate_tx, duplicate_rx) = mpsc::unbounded_channel();\n\t\tlet (trash_tx, trash_rx) = mpsc::unbounded_channel();\n\t\tlet (delete_tx, delete_rx) = mpsc::unbounded_channel();\n\t\tlet (download_tx, download_rx) = mpsc::unbounded_channel();\n\t\tlet (shutdown_tx, mut shutdown_rx) = mpsc::unbounded_channel();\n\n\t\tDUPLICATE_TX.init(duplicate_tx);\n\t\tMOVE_TX.init(move_tx);\n\t\tTRASH_TX.init(trash_tx);\n\t\tDELETE_TX.init(delete_tx);\n\t\tDOWNLOAD_TX.init(download_tx);\n\t\tSHUTDOWN_TX.init(shutdown_tx);\n\n\t\ttokio::spawn(async move {\n\t\t\tlet duplicate_rx =\n\t\t\t\tUnboundedReceiverStream::new(duplicate_rx).chunks_timeout(1000, Duration::from_millis(500));\n\t\t\tlet move_rx =\n\t\t\t\tUnboundedReceiverStream::new(move_rx).chunks_timeout(1000, Duration::from_millis(500));\n\t\t\tlet trash_rx =\n\t\t\t\tUnboundedReceiverStream::new(trash_rx).chunks_timeout(1000, Duration::from_millis(500));\n\t\t\tlet delete_rx =\n\t\t\t\tUnboundedReceiverStream::new(delete_rx).chunks_timeout(1000, Duration::from_millis(500));\n\t\t\tlet download_rx =\n\t\t\t\tUnboundedReceiverStream::new(download_rx).chunks_timeout(1000, Duration::from_millis(500));\n\n\t\t\tpin!(duplicate_rx);\n\t\t\tpin!(move_rx);\n\t\t\tpin!(trash_rx);\n\t\t\tpin!(delete_rx);\n\t\t\tpin!(download_rx);\n\n\t\t\tloop {\n\t\t\t\tselect! {\n\t\t\t\t\tSome(items) = duplicate_rx.next() => err!(Pubsub::pub_after_duplicate(items)),\n\t\t\t\t\tSome(items) = move_rx.next() => err!(Pubsub::pub_after_move(items)),\n\t\t\t\t\tSome(urls) = trash_rx.next() => err!(Pubsub::pub_after_trash(urls)),\n\t\t\t\t\tSome(urls) = delete_rx.next() => err!(Pubsub::pub_after_delete(urls)),\n\t\t\t\t\tSome(urls) = download_rx.next() => err!(Pubsub::pub_after_download(urls)),\n\t\t\t\t\t_ = shutdown_rx.recv() => {\n\t\t\t\t\t\tshutdown_rx.close();\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\tpub(super) async fn shutdown() {\n\t\tSHUTDOWN_TX.send(()).ok();\n\t\tSHUTDOWN_TX.closed().await;\n\t}\n}\n"
  },
  {
    "path": "yazi-dds/src/sendable.rs",
    "content": "use std::{any::TypeId, borrow::Cow};\n\nuse hashbrown::HashMap;\nuse mlua::{ExternalError, IntoLua, Lua, MultiValue, Table, Value};\nuse ordered_float::OrderedFloat;\nuse yazi_shared::{data::{Data, DataKey}, replace_cow};\n\npub struct Sendable;\n\nimpl Sendable {\n\tpub fn value_to_data(lua: &Lua, value: Value) -> mlua::Result<Data> {\n\t\tOk(match value {\n\t\t\tValue::Nil => Data::Nil,\n\t\t\tValue::Boolean(b) => Data::Boolean(b),\n\t\t\tValue::LightUserData(_) => Err(\"light userdata is not supported\".into_lua_err())?,\n\t\t\tValue::Integer(i) => Data::Integer(i),\n\t\t\tValue::Number(n) => Data::Number(n),\n\t\t\tValue::String(b) => {\n\t\t\t\tif let Ok(s) = b.to_str() {\n\t\t\t\t\tData::String(s.to_owned().into())\n\t\t\t\t} else {\n\t\t\t\t\tData::Bytes(b.as_bytes().to_owned())\n\t\t\t\t}\n\t\t\t}\n\t\t\tValue::Table(t) => {\n\t\t\t\tlet (mut i, mut map) = (1, HashMap::with_capacity(t.raw_len()));\n\t\t\t\tfor result in t.pairs::<Value, Value>() {\n\t\t\t\t\tlet (k, v) = result?;\n\t\t\t\t\tlet k = Self::value_to_key(k)?;\n\n\t\t\t\t\tif k == DataKey::Integer(i) {\n\t\t\t\t\t\ti += 1;\n\t\t\t\t\t}\n\t\t\t\t\tmap.insert(k, Self::value_to_data(lua, v)?);\n\t\t\t\t}\n\n\t\t\t\tif map.len() == i as usize - 1 {\n\t\t\t\t\tData::List((1..i).map(|i| map.remove(&DataKey::Integer(i)).unwrap()).collect())\n\t\t\t\t} else {\n\t\t\t\t\tData::Dict(map)\n\t\t\t\t}\n\t\t\t}\n\t\t\tValue::Function(_) => Err(\"function is not supported\".into_lua_err())?,\n\t\t\tValue::Thread(_) => Err(\"thread is not supported\".into_lua_err())?,\n\t\t\tValue::UserData(ud) => match ud.type_id() {\n\t\t\t\tSome(t) if t == TypeId::of::<yazi_binding::Url>() => {\n\t\t\t\t\tData::Url(ud.take::<yazi_binding::Url>()?.into())\n\t\t\t\t}\n\t\t\t\tSome(t) if t == TypeId::of::<yazi_binding::Path>() => {\n\t\t\t\t\tData::Path(ud.take::<yazi_binding::Path>()?.into())\n\t\t\t\t}\n\t\t\t\tSome(t) if t == TypeId::of::<yazi_binding::Id>() => {\n\t\t\t\t\tData::Id(**ud.borrow::<yazi_binding::Id>()?)\n\t\t\t\t}\n\t\t\t\tSome(t) if t == TypeId::of::<yazi_fs::FilesOp>() => {\n\t\t\t\t\tData::Any(Box::new(ud.take::<yazi_fs::FilesOp>()?))\n\t\t\t\t}\n\t\t\t\tSome(t) if t == TypeId::of::<yazi_parser::mgr::UpdateYankedIter>() => {\n\t\t\t\t\tData::Any(Box::new(ud.take::<yazi_parser::mgr::UpdateYankedIter>()?.into_opt(lua)?))\n\t\t\t\t}\n\t\t\t\tSome(t) if t == TypeId::of::<yazi_binding::ChordCow>() => {\n\t\t\t\t\tData::Any(Box::new(ud.take::<yazi_binding::ChordCow>()?))\n\t\t\t\t}\n\t\t\t\t_ => Err(format!(\"unsupported userdata included: {ud:?}\").into_lua_err())?,\n\t\t\t},\n\t\t\tValue::Error(_) => Err(\"error is not supported\".into_lua_err())?,\n\t\t\tValue::Other(..) => Err(\"unknown data is not supported\".into_lua_err())?,\n\t\t})\n\t}\n\n\tpub fn data_to_value(lua: &Lua, data: Data) -> mlua::Result<Value> {\n\t\tOk(match data {\n\t\t\tData::String(Cow::Owned(s)) => Value::String(lua.create_external_string(s)?),\n\t\t\tData::List(l) => {\n\t\t\t\tlet mut vec = Vec::with_capacity(l.len());\n\t\t\t\tfor v in l.into_iter() {\n\t\t\t\t\tvec.push(Self::data_to_value(lua, v)?);\n\t\t\t\t}\n\t\t\t\tValue::Table(lua.create_sequence_from(vec)?)\n\t\t\t}\n\t\t\tData::Dict(d) => {\n\t\t\t\tlet seq_len = d.keys().filter(|&k| k.is_integer()).count();\n\t\t\t\tlet tbl = lua.create_table_with_capacity(seq_len, d.len() - seq_len)?;\n\t\t\t\tfor (k, v) in d {\n\t\t\t\t\ttbl.raw_set(Self::key_to_value(lua, k)?, Self::data_to_value(lua, v)?)?;\n\t\t\t\t}\n\t\t\t\tValue::Table(tbl)\n\t\t\t}\n\t\t\tData::Url(u) => yazi_binding::Url::new(u).into_lua(lua)?,\n\t\t\tData::Path(u) => yazi_binding::Path::new(u).into_lua(lua)?,\n\t\t\tData::Bytes(b) => Value::String(lua.create_external_string(b)?),\n\t\t\tData::Any(b) => {\n\t\t\t\tlet mut b = b.into_any();\n\t\t\t\tmacro_rules! try_cast {\n\t\t\t\t\t($f:expr) => {\n\t\t\t\t\t\tmatch b.downcast() {\n\t\t\t\t\t\t\tOk(v) => return $f(*v)?.into_lua(lua),\n\t\t\t\t\t\t\t#[allow(unused_assignments)]\n\t\t\t\t\t\t\tErr(e) => b = e,\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\ttry_cast!(|v: yazi_fs::FilesOp| lua.create_any_userdata(v));\n\t\t\t\ttry_cast!(|v: yazi_parser::mgr::UpdateYankedOpt| v.into_lua(lua));\n\t\t\t\ttry_cast!(|v: yazi_binding::ChordCow| v.into_lua(lua));\n\t\t\t\tErr(\"unsupported DataAny included\".into_lua_err())?\n\t\t\t}\n\t\t\t_ => Self::data_to_value_ref(lua, &data)?,\n\t\t})\n\t}\n\n\tpub fn data_to_value_ref(lua: &Lua, data: &Data) -> mlua::Result<Value> {\n\t\tOk(match data {\n\t\t\tData::Nil => Value::Nil,\n\t\t\tData::Boolean(b) => Value::Boolean(*b),\n\t\t\tData::Integer(i) => Value::Integer(*i),\n\t\t\tData::Number(n) => Value::Number(*n),\n\t\t\tData::String(s) => Value::String(lua.create_string(&**s)?),\n\t\t\tData::List(l) => {\n\t\t\t\tlet mut vec = Vec::with_capacity(l.len());\n\t\t\t\tfor v in l {\n\t\t\t\t\tvec.push(Self::data_to_value_ref(lua, v)?);\n\t\t\t\t}\n\t\t\t\tValue::Table(lua.create_sequence_from(vec)?)\n\t\t\t}\n\t\t\tData::Dict(d) => {\n\t\t\t\tlet seq_len = d.keys().filter(|&k| k.is_integer()).count();\n\t\t\t\tlet tbl = lua.create_table_with_capacity(seq_len, d.len() - seq_len)?;\n\t\t\t\tfor (k, v) in d {\n\t\t\t\t\ttbl.raw_set(Self::key_to_value_ref(lua, k)?, Self::data_to_value_ref(lua, v)?)?;\n\t\t\t\t}\n\t\t\t\tValue::Table(tbl)\n\t\t\t}\n\t\t\tData::Id(i) => yazi_binding::Id(*i).into_lua(lua)?,\n\t\t\tData::Url(u) => yazi_binding::Url::new(u).into_lua(lua)?,\n\t\t\tData::Path(u) => yazi_binding::Path::new(u).into_lua(lua)?,\n\t\t\tData::Bytes(b) => Value::String(lua.create_string(b)?),\n\t\t\tData::Any(b) => {\n\t\t\t\tmacro_rules! try_cast {\n\t\t\t\t\t($f:expr) => {\n\t\t\t\t\t\tif let Some(v) = b.as_any().downcast_ref() {\n\t\t\t\t\t\t\treturn $f(Clone::clone(v))?.into_lua(lua);\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\ttry_cast!(|v: yazi_fs::FilesOp| lua.create_any_userdata(v));\n\t\t\t\ttry_cast!(|v: yazi_parser::mgr::UpdateYankedOpt| v.into_lua(lua));\n\t\t\t\ttry_cast!(|v: yazi_binding::ChordCow| v.into_lua(lua));\n\t\t\t\tErr(\"unsupported DataAny included\".into_lua_err())?\n\t\t\t}\n\t\t})\n\t}\n\n\tpub fn table_to_args(lua: &Lua, t: Table) -> mlua::Result<HashMap<DataKey, Data>> {\n\t\tlet mut args = HashMap::with_capacity(t.raw_len());\n\t\tfor pair in t.pairs::<Value, Value>() {\n\t\t\tlet (k, v) = pair?;\n\t\t\tmatch k {\n\t\t\t\tValue::Integer(i) if i > 0 => {\n\t\t\t\t\targs.insert(DataKey::Integer(i - 1), Self::value_to_data(lua, v)?);\n\t\t\t\t}\n\t\t\t\tValue::String(s) => {\n\t\t\t\t\targs.insert(\n\t\t\t\t\t\tDataKey::String(Cow::Owned(s.to_str()?.replace('_', \"-\"))),\n\t\t\t\t\t\tSelf::value_to_data(lua, v)?,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\t_ => return Err(\"invalid key in Action\".into_lua_err()),\n\t\t\t}\n\t\t}\n\t\tOk(args)\n\t}\n\n\tpub fn args_to_table(lua: &Lua, args: HashMap<DataKey, Data>) -> mlua::Result<Table> {\n\t\tlet seq_len = args.keys().filter(|&k| k.is_integer()).count();\n\t\tlet tbl = lua.create_table_with_capacity(seq_len, args.len() - seq_len)?;\n\t\tfor (k, v) in args {\n\t\t\tmatch k {\n\t\t\t\tDataKey::Integer(i) => tbl.raw_set(i + 1, Self::data_to_value(lua, v)?),\n\t\t\t\tDataKey::String(s) => tbl.raw_set(replace_cow(s, \"-\", \"_\"), Self::data_to_value(lua, v)?),\n\t\t\t\t_ => Err(\"invalid key in Data\".into_lua_err()),\n\t\t\t}?;\n\t\t}\n\t\tOk(tbl)\n\t}\n\n\tpub fn args_to_table_ref(lua: &Lua, args: &HashMap<DataKey, Data>) -> mlua::Result<Table> {\n\t\tlet seq_len = args.keys().filter(|&k| k.is_integer()).count();\n\t\tlet tbl = lua.create_table_with_capacity(seq_len, args.len() - seq_len)?;\n\t\tfor (k, v) in args {\n\t\t\tmatch k {\n\t\t\t\tDataKey::Integer(i) => tbl.raw_set(i + 1, Self::data_to_value_ref(lua, v)?),\n\t\t\t\tDataKey::String(s) => {\n\t\t\t\t\ttbl.raw_set(replace_cow(&**s, \"-\", \"_\"), Self::data_to_value_ref(lua, v)?)\n\t\t\t\t}\n\t\t\t\t_ => Err(\"invalid key in Data\".into_lua_err()),\n\t\t\t}?;\n\t\t}\n\t\tOk(tbl)\n\t}\n\n\tpub fn list_to_values(lua: &Lua, data: Vec<Data>) -> mlua::Result<MultiValue> {\n\t\tdata.into_iter().map(|d| Self::data_to_value(lua, d)).collect()\n\t}\n\n\tpub fn values_to_list(lua: &Lua, values: MultiValue) -> mlua::Result<Vec<Data>> {\n\t\tvalues.into_iter().map(|v| Self::value_to_data(lua, v)).collect()\n\t}\n}\n\nimpl Sendable {\n\tfn value_to_key(value: Value) -> mlua::Result<DataKey> {\n\t\tOk(match value {\n\t\t\tValue::Nil => DataKey::Nil,\n\t\t\tValue::Boolean(b) => DataKey::Boolean(b),\n\t\t\tValue::LightUserData(_) => Err(\"light userdata is not supported\".into_lua_err())?,\n\t\t\tValue::Integer(i) => DataKey::Integer(i),\n\t\t\tValue::Number(n) => DataKey::Number(OrderedFloat(n)),\n\t\t\tValue::String(s) => {\n\t\t\t\tif let Ok(s) = s.to_str() {\n\t\t\t\t\tDataKey::String(s.to_owned().into())\n\t\t\t\t} else {\n\t\t\t\t\tDataKey::Bytes(s.as_bytes().to_owned())\n\t\t\t\t}\n\t\t\t}\n\t\t\tValue::Table(_) => Err(\"table is not supported\".into_lua_err())?,\n\t\t\tValue::Function(_) => Err(\"function is not supported\".into_lua_err())?,\n\t\t\tValue::Thread(_) => Err(\"thread is not supported\".into_lua_err())?,\n\t\t\tValue::UserData(ud) => match ud.type_id() {\n\t\t\t\tSome(t) if t == TypeId::of::<yazi_binding::Url>() => {\n\t\t\t\t\tDataKey::Url(ud.take::<yazi_binding::Url>()?.into())\n\t\t\t\t}\n\t\t\t\tSome(t) if t == TypeId::of::<yazi_binding::Path>() => {\n\t\t\t\t\tDataKey::Path(ud.take::<yazi_binding::Path>()?.into())\n\t\t\t\t}\n\t\t\t\tSome(t) if t == TypeId::of::<yazi_binding::Id>() => {\n\t\t\t\t\tDataKey::Id(**ud.borrow::<yazi_binding::Id>()?)\n\t\t\t\t}\n\t\t\t\t_ => Err(format!(\"unsupported userdata included: {ud:?}\").into_lua_err())?,\n\t\t\t},\n\t\t\tValue::Error(_) => Err(\"error is not supported\".into_lua_err())?,\n\t\t\tValue::Other(..) => Err(\"unknown data is not supported\".into_lua_err())?,\n\t\t})\n\t}\n\n\tfn key_to_value(lua: &Lua, key: DataKey) -> mlua::Result<Value> {\n\t\tmatch key {\n\t\t\tDataKey::String(Cow::Owned(s)) => lua.create_external_string(s).map(Value::String),\n\t\t\tDataKey::Url(u) => yazi_binding::Url::new(u).into_lua(lua),\n\t\t\tDataKey::Path(u) => yazi_binding::Path::new(u).into_lua(lua),\n\t\t\tDataKey::Bytes(b) => lua.create_external_string(b).map(Value::String),\n\t\t\t_ => Self::key_to_value_ref(lua, &key),\n\t\t}\n\t}\n\n\tfn key_to_value_ref(lua: &Lua, key: &DataKey) -> mlua::Result<Value> {\n\t\tOk(match key {\n\t\t\tDataKey::Nil => Value::Nil,\n\t\t\tDataKey::Boolean(b) => Value::Boolean(*b),\n\t\t\tDataKey::Integer(i) => Value::Integer(*i),\n\t\t\tDataKey::Number(n) => Value::Number(n.0),\n\t\t\tDataKey::String(s) => Value::String(lua.create_string(&**s)?),\n\t\t\tDataKey::Id(i) => yazi_binding::Id(*i).into_lua(lua)?,\n\t\t\tDataKey::Url(u) => yazi_binding::Url::new(u).into_lua(lua)?,\n\t\t\tDataKey::Path(u) => yazi_binding::Path::new(u).into_lua(lua)?,\n\t\t\tDataKey::Bytes(b) => Value::String(lua.create_string(b)?),\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-dds/src/server.rs",
    "content": "use std::{str::FromStr, time::Duration};\n\nuse anyhow::Result;\nuse hashbrown::HashMap;\nuse parking_lot::RwLock;\nuse tokio::{io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, select, sync::mpsc::{self, UnboundedReceiver}, task::JoinHandle, time};\nuse yazi_macro::try_format;\nuse yazi_shared::{Id, RoCell};\n\nuse crate::{Client, ClientWriter, Payload, Peer, STATE, Stream, ember::{Ember, EmberBye, EmberHey}};\n\npub(super) static CLIENTS: RoCell<RwLock<HashMap<Id, Client>>> = RoCell::new();\n\npub(super) struct Server;\n\nimpl Server {\n\tpub(super) async fn make() -> Result<JoinHandle<()>> {\n\t\tCLIENTS.write().clear();\n\t\tlet listener = Stream::bind().await?;\n\n\t\tOk(tokio::spawn(async move {\n\t\t\twhile let Ok((stream, _)) = listener.accept().await {\n\t\t\t\tlet (tx, mut rx) = mpsc::unbounded_channel::<String>();\n\t\t\t\tlet (reader, mut writer) = tokio::io::split(stream);\n\n\t\t\t\ttokio::spawn(async move {\n\t\t\t\t\tlet mut id = None;\n\t\t\t\t\tlet mut lines = BufReader::new(reader).lines();\n\t\t\t\t\tloop {\n\t\t\t\t\t\tselect! {\n\t\t\t\t\t\t\tSome(payload) = rx.recv() => {\n\t\t\t\t\t\t\t\tif writer.write_all(payload.as_bytes()).await.is_err() {\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t_ = time::sleep(Duration::from_secs(5)) => {\n\t\t\t\t\t\t\t\tif writer.write_u8(b'\\n').await.is_err() {\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tOk(Some(mut line)) = lines.next_line() => {\n\t\t\t\t\t\t\t\tif line.starts_with(\"hi,\") {\n\t\t\t\t\t\t\t\t\tSelf::handle_hi(line, &mut id, tx.clone());\n\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tlet Some(id) = id else { continue };\n\t\t\t\t\t\t\t\tif line.starts_with(\"bye,\") {\n\t\t\t\t\t\t\t\t\tSelf::handle_bye(id, rx, writer).await;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tlet mut parts = line.splitn(4, ',');\n\t\t\t\t\t\t\t\tlet Some(kind) = parts.next() else { continue };\n\t\t\t\t\t\t\t\tlet Some(receiver) = parts.next().and_then(|s| s.parse::<Id>().ok()) else { continue };\n\t\t\t\t\t\t\t\tlet Some(sender) = parts.next().and_then(|s| s.parse::<u64>().ok()) else { continue };\n\n\t\t\t\t\t\t\t\tlet clients = CLIENTS.read();\n\t\t\t\t\t\t\t\tlet clients: Vec<_> = if receiver == 0 {\n\t\t\t\t\t\t\t\t\tclients.values().filter(|c| c.able(kind)).collect()\n\t\t\t\t\t\t\t\t} else if let Some(c) = clients.get(&receiver).filter(|c| c.able(kind)) {\n\t\t\t\t\t\t\t\t\tvec![c]\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tvec![]\n\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t\tif clients.is_empty() {\n\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif receiver == 0 && kind.starts_with('@') {\n\t\t\t\t\t\t\t\t\tlet Some(body) = parts.next() else { continue };\n\t\t\t\t\t\t\t\t\tif !STATE.set(kind, sender, body) { continue }\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tline.push('\\n');\n\t\t\t\t\t\t\t\tclients.into_iter().for_each(|c| _ = c.tx.send(line.clone()));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse => break\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tlet mut clients = CLIENTS.write();\n\t\t\t\t\tif id.and_then(|id| clients.remove(&id)).is_some() {\n\t\t\t\t\t\tSelf::handle_hey(&clients);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t}))\n\t}\n\n\tfn handle_hi(s: String, id: &mut Option<Id>, tx: mpsc::UnboundedSender<String>) {\n\t\tlet Ok(payload) = Payload::from_str(&s) else { return };\n\t\tlet Ember::Hi(hi) = payload.body else { return };\n\n\t\tlet mut clients = CLIENTS.write();\n\t\tif let Some(old_id) = id.replace(payload.sender) {\n\t\t\tclients.remove(&old_id);\n\t\t} else if let Some(state) = &*STATE.read() {\n\t\t\tstate.values().for_each(|s| _ = tx.send(s.clone()));\n\t\t}\n\n\t\tclients.insert(payload.sender, Client {\n\t\t\tid: payload.sender,\n\t\t\ttx,\n\t\t\tabilities: hi.abilities.into_iter().map(|s| s.into_owned()).collect(),\n\t\t});\n\n\t\tSelf::handle_hey(&clients);\n\t}\n\n\tfn handle_hey(clients: &HashMap<Id, Client>) {\n\t\tlet payload = Payload::new(EmberHey::owned(\n\t\t\tclients.values().map(|c| (c.id, Peer::new(&c.abilities))).collect(),\n\t\t));\n\t\tif let Ok(s) = try_format!(\"{payload}\\n\") {\n\t\t\tclients.values().for_each(|c| _ = c.tx.send(s.clone()));\n\t\t}\n\t}\n\n\tasync fn handle_bye(id: Id, mut rx: UnboundedReceiver<String>, mut writer: ClientWriter) {\n\t\twhile let Ok(payload) = rx.try_recv() {\n\t\t\tif writer.write_all(payload.as_bytes()).await.is_err() {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tlet bye = EmberBye::borrowed().with_receiver(id).with_sender(Id(0));\n\t\tif let Ok(s) = try_format!(\"{bye}\") {\n\t\t\twriter.write_all(s.as_bytes()).await.ok();\n\t\t\twriter.flush().await.ok();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-dds/src/spark/kind.rs",
    "content": "use std::fmt::Display;\n\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]\npub enum SparkKind {\n\t// app:title\n\tIndAppTitle,\n\n\t// mgr:hidden\n\tKeyHidden,\n\tIndHidden,\n\t// mgr:sort\n\tKeySort,\n\tIndSort,\n\t// mgr:stash\n\tIndStash,\n\tRelayStash,\n\t// mgr:quit\n\tKeyQuit,\n\n\t// which:activate\n\tIndWhichActivate,\n\n\t// notify:push\n\tRelayNotifyPush,\n}\n\nimpl AsRef<str> for SparkKind {\n\tfn as_ref(&self) -> &str {\n\t\tmatch self {\n\t\t\t// app:title\n\t\t\tSelf::IndAppTitle => \"ind-app-title\",\n\n\t\t\t// mgr:hidden\n\t\t\tSelf::KeyHidden => \"key-hidden\",\n\t\t\tSelf::IndHidden => \"ind-hidden\",\n\t\t\t// mgr:sort\n\t\t\tSelf::KeySort => \"key-sort\",\n\t\t\tSelf::IndSort => \"ind-sort\",\n\t\t\t// mgr:stash\n\t\t\tSelf::IndStash => \"ind-stash\",\n\t\t\tSelf::RelayStash => \"relay-stash\",\n\t\t\t// mgr:quit\n\t\t\tSelf::KeyQuit => \"key-quit\",\n\n\t\t\t// which:activate\n\t\t\tSelf::IndWhichActivate => \"ind-which-activate\",\n\n\t\t\t// notify:push\n\t\t\tSelf::RelayNotifyPush => \"relay-notify-push\",\n\t\t}\n\t}\n}\n\nimpl Display for SparkKind {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(self.as_ref()) }\n}\n"
  },
  {
    "path": "yazi-dds/src/spark/mod.rs",
    "content": "yazi_macro::mod_flat!(kind spark);\n"
  },
  {
    "path": "yazi-dds/src/spark/spark.rs",
    "content": "use mlua::{FromLua, IntoLua, Lua, Value};\n\nuse crate::{spark::SparkKind, try_from_spark};\n\n#[derive(Debug)]\npub enum Spark<'a> {\n\t// Void\n\tVoid(yazi_parser::VoidOpt),\n\n\t// App\n\tAppAcceptPayload(crate::Payload<'a>),\n\tAppBootstrap(yazi_parser::VoidOpt),\n\tAppDeprecate(yazi_parser::app::DeprecateOpt),\n\tAppFocus(yazi_parser::VoidOpt),\n\tAppMouse(yazi_parser::app::MouseOpt),\n\tAppPlugin(yazi_parser::app::PluginOpt),\n\tAppPluginDo(yazi_parser::app::PluginOpt),\n\tAppQuit(yazi_parser::app::QuitOpt),\n\tAppReflow(yazi_parser::app::ReflowOpt),\n\tAppResize(yazi_parser::app::ReflowOpt),\n\tAppResume(yazi_parser::app::ResumeOpt),\n\tAppStop(yazi_parser::app::StopOpt),\n\tAppTitle(yazi_parser::app::TitleOpt),\n\tAppUpdateProgress(yazi_parser::app::UpdateProgressOpt),\n\n\t// Mgr\n\tArrow(yazi_parser::ArrowOpt),\n\tBack(yazi_parser::VoidOpt),\n\tBulkRename(yazi_parser::VoidOpt),\n\tCd(yazi_parser::mgr::CdOpt),\n\tClose(yazi_parser::mgr::CloseOpt),\n\tCopy(yazi_parser::mgr::CopyOpt),\n\tCreate(yazi_parser::mgr::CreateOpt),\n\tDisplace(yazi_parser::VoidOpt),\n\tDisplaceDo(yazi_parser::mgr::DisplaceDoOpt),\n\tDownload(yazi_parser::mgr::DownloadOpt),\n\tEnter(yazi_parser::VoidOpt),\n\tEscape(yazi_parser::mgr::EscapeOpt),\n\tEscapeFilter(yazi_parser::VoidOpt),\n\tEscapeFind(yazi_parser::VoidOpt),\n\tEscapeSearch(yazi_parser::VoidOpt),\n\tEscapeSelect(yazi_parser::VoidOpt),\n\tEscapeVisual(yazi_parser::VoidOpt),\n\tFilter(yazi_parser::mgr::FilterOpt),\n\tFilterDo(yazi_parser::mgr::FilterOpt),\n\tFind(yazi_parser::mgr::FindOpt),\n\tFindArrow(yazi_parser::mgr::FindArrowOpt),\n\tFindDo(yazi_parser::mgr::FindDoOpt),\n\tFollow(yazi_parser::VoidOpt),\n\tForward(yazi_parser::VoidOpt),\n\tHardlink(yazi_parser::mgr::HardlinkOpt),\n\tHidden(yazi_parser::mgr::HiddenOpt),\n\tHover(yazi_parser::mgr::HoverOpt),\n\tLeave(yazi_parser::VoidOpt),\n\tLinemode(yazi_parser::mgr::LinemodeOpt),\n\tLink(yazi_parser::mgr::LinkOpt),\n\tOpen(yazi_parser::mgr::OpenOpt),\n\tOpenDo(yazi_parser::mgr::OpenDoOpt),\n\tPaste(yazi_parser::mgr::PasteOpt),\n\tPeek(yazi_parser::mgr::PeekOpt),\n\tQuit(yazi_parser::app::QuitOpt),\n\tRefresh(yazi_parser::VoidOpt),\n\tRemove(yazi_parser::mgr::RemoveOpt),\n\tRemoveDo(yazi_parser::mgr::RemoveOpt),\n\tRename(yazi_parser::mgr::RenameOpt),\n\tReveal(yazi_parser::mgr::RevealOpt),\n\tSearch(yazi_parser::mgr::SearchOpt),\n\tSearchDo(yazi_parser::mgr::SearchOpt),\n\tSearchStop(yazi_parser::VoidOpt),\n\tSeek(yazi_parser::mgr::SeekOpt),\n\tShell(yazi_parser::mgr::ShellOpt),\n\tSort(yazi_parser::mgr::SortOpt),\n\tSpot(yazi_parser::mgr::SpotOpt),\n\tStash(yazi_parser::mgr::StashOpt),\n\tSuspend(yazi_parser::VoidOpt),\n\tTabClose(yazi_parser::mgr::TabCloseOpt),\n\tTabCreate(yazi_parser::mgr::TabCreateOpt),\n\tTabRename(yazi_parser::mgr::TabRenameOpt),\n\tTabSwap(yazi_parser::ArrowOpt),\n\tTabSwitch(yazi_parser::mgr::TabSwitchOpt),\n\tToggle(yazi_parser::mgr::ToggleOpt),\n\tToggleAll(yazi_parser::mgr::ToggleAllOpt),\n\tUnyank(yazi_parser::VoidOpt),\n\tUpdateFiles(yazi_parser::mgr::UpdateFilesOpt),\n\tUpdateMimes(yazi_parser::mgr::UpdateMimesOpt),\n\tUpdatePaged(yazi_parser::mgr::UpdatePagedOpt),\n\tUpdatePeeked(yazi_parser::mgr::UpdatePeekedOpt),\n\tUpdateSpotted(yazi_parser::mgr::UpdateSpottedOpt),\n\tUpdateYanked(yazi_parser::mgr::UpdateYankedOpt<'a>),\n\tUpload(yazi_parser::mgr::UploadOpt),\n\tVisualMode(yazi_parser::mgr::VisualModeOpt),\n\tWatch(yazi_parser::VoidOpt),\n\tYank(yazi_parser::mgr::YankOpt),\n\n\t// Cmp\n\tCmpArrow(yazi_parser::ArrowOpt),\n\tCmpClose(yazi_parser::cmp::CloseOpt),\n\tCmpShow(yazi_parser::cmp::ShowOpt),\n\tCmpTrigger(yazi_parser::cmp::TriggerOpt),\n\n\t// Confirm\n\tConfirmArrow(yazi_parser::ArrowOpt),\n\tConfirmClose(yazi_parser::confirm::CloseOpt),\n\tConfirmShow(Box<yazi_parser::confirm::ShowOpt>),\n\n\t// Help\n\tHelpArrow(yazi_parser::ArrowOpt),\n\tHelpEscape(yazi_parser::VoidOpt),\n\tHelpFilter(yazi_parser::VoidOpt),\n\tHelpToggle(yazi_parser::help::ToggleOpt),\n\n\t// Input\n\tInputBackspace(yazi_widgets::input::parser::BackspaceOpt),\n\tInputBackward(yazi_widgets::input::parser::BackwardOpt),\n\tInputClose(yazi_parser::input::CloseOpt),\n\tInputComplete(yazi_widgets::input::parser::CompleteOpt),\n\tInputDelete(yazi_widgets::input::parser::DeleteOpt),\n\tInputEscape(yazi_parser::VoidOpt),\n\tInputForward(yazi_widgets::input::parser::ForwardOpt),\n\tInputInsert(yazi_widgets::input::parser::InsertOpt),\n\tInputKill(yazi_widgets::input::parser::KillOpt),\n\tInputMove(yazi_widgets::input::parser::MoveOpt),\n\tInputPaste(yazi_widgets::input::parser::PasteOpt),\n\tInputShow(yazi_widgets::input::InputOpt),\n\n\t// Notify\n\tNotifyPush(yazi_parser::notify::PushOpt),\n\tNotifyTick(yazi_parser::notify::TickOpt),\n\n\t// Pick\n\tPickArrow(yazi_parser::ArrowOpt),\n\tPickClose(yazi_parser::pick::CloseOpt),\n\tPickShow(yazi_parser::pick::ShowOpt),\n\n\t// Spot\n\tSpotArrow(yazi_parser::ArrowOpt),\n\tSpotClose(yazi_parser::VoidOpt),\n\tSpotCopy(yazi_parser::spot::CopyOpt),\n\tSpotSwipe(yazi_parser::ArrowOpt),\n\n\t// Tasks\n\tTasksArrow(yazi_parser::ArrowOpt),\n\tTasksCancel(yazi_parser::VoidOpt),\n\tTasksClose(yazi_parser::VoidOpt),\n\tTasksInspect(yazi_parser::VoidOpt),\n\tTasksOpenShellCompat(yazi_parser::tasks::ProcessOpenOpt),\n\tTasksProcessOpen(yazi_parser::tasks::ProcessOpenOpt),\n\tTasksShow(yazi_parser::VoidOpt),\n\tTasksUpdateSucceed(yazi_parser::tasks::UpdateSucceedOpt),\n\n\t// Which\n\tWhichActivate(yazi_parser::which::ActivateOpt),\n\tWhichDismiss(yazi_parser::VoidOpt),\n}\n\nimpl<'a> Spark<'a> {\n\tpub fn from_lua(lua: &Lua, kind: SparkKind, value: Value) -> mlua::Result<Self> {\n\t\tuse SparkKind::*;\n\n\t\tOk(match kind {\n\t\t\t// app:title\n\t\t\tIndAppTitle => Self::AppTitle(<_>::from_lua(value, lua)?),\n\n\t\t\t// mgr:hidden\n\t\t\tKeyHidden => Self::Hidden(<_>::from_lua(value, lua)?),\n\t\t\tIndHidden => Self::Hidden(<_>::from_lua(value, lua)?),\n\t\t\t// mgr:sort\n\t\t\tKeySort => Self::Sort(<_>::from_lua(value, lua)?),\n\t\t\tIndSort => Self::Sort(<_>::from_lua(value, lua)?),\n\t\t\t// mgr:stash\n\t\t\tIndStash => Self::Stash(<_>::from_lua(value, lua)?),\n\t\t\tRelayStash => Self::Stash(<_>::from_lua(value, lua)?),\n\t\t\t// mgr:quit\n\t\t\tKeyQuit => Self::Quit(<_>::from_lua(value, lua)?),\n\n\t\t\t// which:activate\n\t\t\tIndWhichActivate => Self::WhichActivate(<_>::from_lua(value, lua)?),\n\n\t\t\t// notify:push\n\t\t\tRelayNotifyPush => Self::NotifyPush(<_>::from_lua(value, lua)?),\n\t\t})\n\t}\n}\n\nimpl<'a> IntoLua for Spark<'a> {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tmatch self {\n\t\t\t// Void\n\t\t\tSelf::Void(b) => b.into_lua(lua),\n\n\t\t\t// App\n\t\t\tSelf::AppAcceptPayload(b) => b.into_lua(lua),\n\t\t\tSelf::AppBootstrap(b) => b.into_lua(lua),\n\t\t\tSelf::AppDeprecate(b) => b.into_lua(lua),\n\t\t\tSelf::AppFocus(b) => b.into_lua(lua),\n\t\t\tSelf::AppMouse(b) => b.into_lua(lua),\n\t\t\tSelf::AppPlugin(b) => b.into_lua(lua),\n\t\t\tSelf::AppPluginDo(b) => b.into_lua(lua),\n\t\t\tSelf::AppQuit(b) => b.into_lua(lua),\n\t\t\tSelf::AppReflow(b) => b.into_lua(lua),\n\t\t\tSelf::AppResize(b) => b.into_lua(lua),\n\t\t\tSelf::AppResume(b) => b.into_lua(lua),\n\t\t\tSelf::AppStop(b) => b.into_lua(lua),\n\t\t\tSelf::AppTitle(b) => b.into_lua(lua),\n\t\t\tSelf::AppUpdateProgress(b) => b.into_lua(lua),\n\n\t\t\t// Mgr\n\t\t\tSelf::Arrow(b) => b.into_lua(lua),\n\t\t\tSelf::Back(b) => b.into_lua(lua),\n\t\t\tSelf::BulkRename(b) => b.into_lua(lua),\n\t\t\tSelf::Cd(b) => b.into_lua(lua),\n\t\t\tSelf::Close(b) => b.into_lua(lua),\n\t\t\tSelf::Copy(b) => b.into_lua(lua),\n\t\t\tSelf::Create(b) => b.into_lua(lua),\n\t\t\tSelf::Displace(b) => b.into_lua(lua),\n\t\t\tSelf::DisplaceDo(b) => b.into_lua(lua),\n\t\t\tSelf::Download(b) => b.into_lua(lua),\n\t\t\tSelf::Enter(b) => b.into_lua(lua),\n\t\t\tSelf::Escape(b) => b.into_lua(lua),\n\t\t\tSelf::EscapeFilter(b) => b.into_lua(lua),\n\t\t\tSelf::EscapeFind(b) => b.into_lua(lua),\n\t\t\tSelf::EscapeSearch(b) => b.into_lua(lua),\n\t\t\tSelf::EscapeSelect(b) => b.into_lua(lua),\n\t\t\tSelf::EscapeVisual(b) => b.into_lua(lua),\n\t\t\tSelf::Filter(b) => b.into_lua(lua),\n\t\t\tSelf::FilterDo(b) => b.into_lua(lua),\n\t\t\tSelf::Find(b) => b.into_lua(lua),\n\t\t\tSelf::FindArrow(b) => b.into_lua(lua),\n\t\t\tSelf::FindDo(b) => b.into_lua(lua),\n\t\t\tSelf::Follow(b) => b.into_lua(lua),\n\t\t\tSelf::Forward(b) => b.into_lua(lua),\n\t\t\tSelf::Hardlink(b) => b.into_lua(lua),\n\t\t\tSelf::Hidden(b) => b.into_lua(lua),\n\t\t\tSelf::Hover(b) => b.into_lua(lua),\n\t\t\tSelf::Leave(b) => b.into_lua(lua),\n\t\t\tSelf::Linemode(b) => b.into_lua(lua),\n\t\t\tSelf::Link(b) => b.into_lua(lua),\n\t\t\tSelf::Open(b) => b.into_lua(lua),\n\t\t\tSelf::OpenDo(b) => b.into_lua(lua),\n\t\t\tSelf::Paste(b) => b.into_lua(lua),\n\t\t\tSelf::Peek(b) => b.into_lua(lua),\n\t\t\tSelf::Quit(b) => b.into_lua(lua),\n\t\t\tSelf::Refresh(b) => b.into_lua(lua),\n\t\t\tSelf::Remove(b) => b.into_lua(lua),\n\t\t\tSelf::RemoveDo(b) => b.into_lua(lua),\n\t\t\tSelf::Rename(b) => b.into_lua(lua),\n\t\t\tSelf::Reveal(b) => b.into_lua(lua),\n\t\t\tSelf::Search(b) => b.into_lua(lua),\n\t\t\tSelf::SearchDo(b) => b.into_lua(lua),\n\t\t\tSelf::SearchStop(b) => b.into_lua(lua),\n\t\t\tSelf::Seek(b) => b.into_lua(lua),\n\t\t\tSelf::Shell(b) => b.into_lua(lua),\n\t\t\tSelf::Sort(b) => b.into_lua(lua),\n\t\t\tSelf::Spot(b) => b.into_lua(lua),\n\t\t\tSelf::Stash(b) => b.into_lua(lua),\n\t\t\tSelf::Suspend(b) => b.into_lua(lua),\n\t\t\tSelf::TabClose(b) => b.into_lua(lua),\n\t\t\tSelf::TabCreate(b) => b.into_lua(lua),\n\t\t\tSelf::TabRename(b) => b.into_lua(lua),\n\t\t\tSelf::TabSwap(b) => b.into_lua(lua),\n\t\t\tSelf::TabSwitch(b) => b.into_lua(lua),\n\t\t\tSelf::Toggle(b) => b.into_lua(lua),\n\t\t\tSelf::ToggleAll(b) => b.into_lua(lua),\n\t\t\tSelf::Unyank(b) => b.into_lua(lua),\n\t\t\tSelf::UpdateFiles(b) => b.into_lua(lua),\n\t\t\tSelf::UpdateMimes(b) => b.into_lua(lua),\n\t\t\tSelf::UpdatePaged(b) => b.into_lua(lua),\n\t\t\tSelf::UpdatePeeked(b) => b.into_lua(lua),\n\t\t\tSelf::UpdateSpotted(b) => b.into_lua(lua),\n\t\t\tSelf::UpdateYanked(b) => b.into_lua(lua),\n\t\t\tSelf::Upload(b) => b.into_lua(lua),\n\t\t\tSelf::VisualMode(b) => b.into_lua(lua),\n\t\t\tSelf::Watch(b) => b.into_lua(lua),\n\t\t\tSelf::Yank(b) => b.into_lua(lua),\n\n\t\t\t// Cmp\n\t\t\tSelf::CmpArrow(b) => b.into_lua(lua),\n\t\t\tSelf::CmpClose(b) => b.into_lua(lua),\n\t\t\tSelf::CmpShow(b) => b.into_lua(lua),\n\t\t\tSelf::CmpTrigger(b) => b.into_lua(lua),\n\n\t\t\t// Confirm\n\t\t\tSelf::ConfirmArrow(b) => b.into_lua(lua),\n\t\t\tSelf::ConfirmClose(b) => b.into_lua(lua),\n\t\t\tSelf::ConfirmShow(b) => b.into_lua(lua),\n\n\t\t\t// Help\n\t\t\tSelf::HelpArrow(b) => b.into_lua(lua),\n\t\t\tSelf::HelpEscape(b) => b.into_lua(lua),\n\t\t\tSelf::HelpFilter(b) => b.into_lua(lua),\n\t\t\tSelf::HelpToggle(b) => b.into_lua(lua),\n\n\t\t\t// Input\n\t\t\tSelf::InputBackspace(b) => b.into_lua(lua),\n\t\t\tSelf::InputBackward(b) => b.into_lua(lua),\n\t\t\tSelf::InputClose(b) => b.into_lua(lua),\n\t\t\tSelf::InputComplete(b) => b.into_lua(lua),\n\t\t\tSelf::InputDelete(b) => b.into_lua(lua),\n\t\t\tSelf::InputEscape(b) => b.into_lua(lua),\n\t\t\tSelf::InputForward(b) => b.into_lua(lua),\n\t\t\tSelf::InputInsert(b) => b.into_lua(lua),\n\t\t\tSelf::InputKill(b) => b.into_lua(lua),\n\t\t\tSelf::InputMove(b) => b.into_lua(lua),\n\t\t\tSelf::InputPaste(b) => b.into_lua(lua),\n\t\t\tSelf::InputShow(b) => b.into_lua(lua),\n\n\t\t\t// Notify\n\t\t\tSelf::NotifyPush(b) => b.into_lua(lua),\n\t\t\tSelf::NotifyTick(b) => b.into_lua(lua),\n\n\t\t\t// Pick\n\t\t\tSelf::PickArrow(b) => b.into_lua(lua),\n\t\t\tSelf::PickClose(b) => b.into_lua(lua),\n\t\t\tSelf::PickShow(b) => b.into_lua(lua),\n\n\t\t\t// Spot\n\t\t\tSelf::SpotArrow(b) => b.into_lua(lua),\n\t\t\tSelf::SpotClose(b) => b.into_lua(lua),\n\t\t\tSelf::SpotCopy(b) => b.into_lua(lua),\n\t\t\tSelf::SpotSwipe(b) => b.into_lua(lua),\n\n\t\t\t// Tasks\n\t\t\tSelf::TasksArrow(b) => b.into_lua(lua),\n\t\t\tSelf::TasksCancel(b) => b.into_lua(lua),\n\t\t\tSelf::TasksClose(b) => b.into_lua(lua),\n\t\t\tSelf::TasksInspect(b) => b.into_lua(lua),\n\t\t\tSelf::TasksOpenShellCompat(b) => b.into_lua(lua),\n\t\t\tSelf::TasksProcessOpen(b) => b.into_lua(lua),\n\t\t\tSelf::TasksShow(b) => b.into_lua(lua),\n\t\t\tSelf::TasksUpdateSucceed(b) => b.into_lua(lua),\n\n\t\t\t// Which\n\t\t\tSelf::WhichActivate(b) => b.into_lua(lua),\n\t\t\tSelf::WhichDismiss(b) => b.into_lua(lua),\n\t\t}\n\t}\n}\n\ntry_from_spark!(\n\tyazi_parser::VoidOpt,\n\tapp:bootstrap,\n\tapp:focus,\n\tmgr:back,\n\tmgr:bulk_rename,\n\tmgr:enter,\n\tmgr:escape_filter,\n\tmgr:escape_find,\n\tmgr:escape_search,\n\tmgr:escape_select,\n\tmgr:escape_visual,\n\tmgr:follow,\n\tmgr:forward,\n\tmgr:leave,\n\tmgr:refresh,\n\tmgr:search_stop,\n\tmgr:suspend,\n\tmgr:unyank,\n\tmgr:watch,\n\twhich:dismiss\n);\n\n// App\ntry_from_spark!(yazi_parser::ArrowOpt, mgr:arrow, mgr:tab_swap);\ntry_from_spark!(yazi_parser::app::DeprecateOpt, app:deprecate);\ntry_from_spark!(yazi_parser::app::MouseOpt, app:mouse);\ntry_from_spark!(yazi_parser::app::PluginOpt, app:plugin, app:plugin_do);\ntry_from_spark!(yazi_parser::app::QuitOpt, app:quit, mgr:quit);\ntry_from_spark!(yazi_parser::app::ReflowOpt, app:reflow, app:resize);\ntry_from_spark!(yazi_parser::app::ResumeOpt, app:resume);\ntry_from_spark!(yazi_parser::app::StopOpt, app:stop);\ntry_from_spark!(yazi_parser::app::TitleOpt, app:title);\ntry_from_spark!(yazi_parser::app::UpdateProgressOpt, app:update_progress);\ntry_from_spark!(yazi_parser::cmp::CloseOpt, cmp:close);\ntry_from_spark!(yazi_parser::cmp::ShowOpt, cmp:show);\ntry_from_spark!(yazi_parser::cmp::TriggerOpt, cmp:trigger);\ntry_from_spark!(yazi_parser::confirm::CloseOpt, confirm:close);\ntry_from_spark!(yazi_parser::confirm::ShowOpt, confirm:show);\ntry_from_spark!(yazi_parser::help::ToggleOpt, help:toggle);\ntry_from_spark!(yazi_parser::input::CloseOpt, input:close);\ntry_from_spark!(yazi_widgets::input::InputOpt, input:show);\ntry_from_spark!(yazi_parser::mgr::CdOpt, mgr:cd);\ntry_from_spark!(yazi_parser::mgr::CloseOpt, mgr:close);\ntry_from_spark!(yazi_parser::mgr::CopyOpt, mgr:copy);\ntry_from_spark!(yazi_parser::mgr::CreateOpt, mgr:create);\ntry_from_spark!(yazi_parser::mgr::DisplaceDoOpt, mgr:displace_do);\ntry_from_spark!(yazi_parser::mgr::DownloadOpt, mgr:download);\ntry_from_spark!(yazi_parser::mgr::EscapeOpt, mgr:escape);\ntry_from_spark!(yazi_parser::mgr::FilterOpt, mgr:filter, mgr:filter_do);\ntry_from_spark!(yazi_parser::mgr::FindArrowOpt, mgr:find_arrow);\ntry_from_spark!(yazi_parser::mgr::FindDoOpt, mgr:find_do);\ntry_from_spark!(yazi_parser::mgr::FindOpt, mgr:find);\ntry_from_spark!(yazi_parser::mgr::HardlinkOpt, mgr:hardlink);\ntry_from_spark!(yazi_parser::mgr::HiddenOpt, mgr:hidden);\ntry_from_spark!(yazi_parser::mgr::HoverOpt, mgr:hover);\ntry_from_spark!(yazi_parser::mgr::LinemodeOpt, mgr:linemode);\ntry_from_spark!(yazi_parser::mgr::LinkOpt, mgr:link);\ntry_from_spark!(yazi_parser::mgr::OpenDoOpt, mgr:open_do);\ntry_from_spark!(yazi_parser::mgr::OpenOpt, mgr:open);\ntry_from_spark!(yazi_parser::mgr::PasteOpt, mgr:paste);\ntry_from_spark!(yazi_parser::mgr::PeekOpt, mgr:peek);\ntry_from_spark!(yazi_parser::mgr::RemoveOpt, mgr:remove, mgr:remove_do);\ntry_from_spark!(yazi_parser::mgr::RenameOpt, mgr:rename);\ntry_from_spark!(yazi_parser::mgr::RevealOpt, mgr:reveal);\ntry_from_spark!(yazi_parser::mgr::SearchOpt, mgr:search, mgr:search_do);\ntry_from_spark!(yazi_parser::mgr::SeekOpt, mgr:seek);\ntry_from_spark!(yazi_parser::mgr::ShellOpt, mgr:shell);\ntry_from_spark!(yazi_parser::mgr::SortOpt, mgr:sort);\ntry_from_spark!(yazi_parser::mgr::SpotOpt, mgr:spot);\ntry_from_spark!(yazi_parser::mgr::StashOpt, mgr:stash);\ntry_from_spark!(yazi_parser::mgr::TabCloseOpt, mgr:tab_close);\ntry_from_spark!(yazi_parser::mgr::TabCreateOpt, mgr:tab_create);\ntry_from_spark!(yazi_parser::mgr::TabRenameOpt, mgr:tab_rename);\ntry_from_spark!(yazi_parser::mgr::TabSwitchOpt, mgr:tab_switch);\ntry_from_spark!(yazi_parser::mgr::ToggleAllOpt, mgr:toggle_all);\ntry_from_spark!(yazi_parser::mgr::ToggleOpt, mgr:toggle);\ntry_from_spark!(yazi_parser::mgr::UpdateFilesOpt, mgr:update_files);\ntry_from_spark!(yazi_parser::mgr::UpdateMimesOpt, mgr:update_mimes);\ntry_from_spark!(yazi_parser::mgr::UpdatePagedOpt, mgr:update_paged);\ntry_from_spark!(yazi_parser::mgr::UpdatePeekedOpt, mgr:update_peeked);\ntry_from_spark!(yazi_parser::mgr::UpdateSpottedOpt, mgr:update_spotted);\ntry_from_spark!(yazi_parser::mgr::UpdateYankedOpt<'a>, mgr:update_yanked);\ntry_from_spark!(yazi_parser::mgr::UploadOpt, mgr:upload);\ntry_from_spark!(yazi_parser::mgr::VisualModeOpt, mgr:visual_mode);\ntry_from_spark!(yazi_parser::mgr::YankOpt, mgr:yank);\ntry_from_spark!(yazi_parser::notify::PushOpt, notify:push);\ntry_from_spark!(yazi_parser::notify::TickOpt, notify:tick);\ntry_from_spark!(yazi_parser::pick::CloseOpt, pick:close);\ntry_from_spark!(yazi_parser::pick::ShowOpt, pick:show);\ntry_from_spark!(yazi_parser::spot::CopyOpt, spot:copy);\ntry_from_spark!(yazi_parser::tasks::ProcessOpenOpt, tasks:process_open);\ntry_from_spark!(yazi_parser::tasks::UpdateSucceedOpt, tasks:update_succeed);\ntry_from_spark!(yazi_parser::which::ActivateOpt, which:activate);\ntry_from_spark!(yazi_widgets::input::parser::BackspaceOpt, input:backspace);\ntry_from_spark!(yazi_widgets::input::parser::BackwardOpt, input:backward);\ntry_from_spark!(yazi_widgets::input::parser::CompleteOpt, input:complete);\ntry_from_spark!(yazi_widgets::input::parser::DeleteOpt, input:delete);\ntry_from_spark!(yazi_widgets::input::parser::ForwardOpt, input:forward);\ntry_from_spark!(yazi_widgets::input::parser::InsertOpt, input:insert);\ntry_from_spark!(yazi_widgets::input::parser::KillOpt, input:kill);\ntry_from_spark!(yazi_widgets::input::parser::MoveOpt, input:move);\ntry_from_spark!(yazi_widgets::input::parser::PasteOpt, input:paste);\n"
  },
  {
    "path": "yazi-dds/src/state.rs",
    "content": "use std::{mem, ops::Deref, sync::atomic::{AtomicU64, Ordering}};\n\nuse anyhow::Result;\nuse hashbrown::HashMap;\nuse parking_lot::RwLock;\nuse tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter};\nuse yazi_boot::BOOT;\nuse yazi_fs::provider::{FileBuilder, Provider, local::{Gate, Local}};\nuse yazi_shared::{RoCell, timestamp_us};\n\nuse crate::CLIENTS;\n\npub static STATE: RoCell<State> = RoCell::new();\n\n#[derive(Default)]\npub struct State {\n\tinner: RwLock<Option<HashMap<String, String>>>,\n\tlast:  AtomicU64,\n}\n\nimpl Deref for State {\n\ttype Target = RwLock<Option<HashMap<String, String>>>;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl State {\n\tpub fn set(&self, kind: &str, sender: u64, body: &str) -> bool {\n\t\tlet Some(inner) = &mut *self.inner.write() else { return false };\n\n\t\tif body == \"null\" {\n\t\t\treturn inner\n\t\t\t\t.remove(kind)\n\t\t\t\t.map(|_| self.last.store(timestamp_us(), Ordering::Relaxed))\n\t\t\t\t.is_some();\n\t\t}\n\n\t\tlet value = format!(\"{kind},0,{sender},{body}\\n\");\n\t\tif inner.get(kind).is_some_and(|s| *s == value) {\n\t\t\treturn false;\n\t\t}\n\n\t\tinner.insert(kind.to_owned(), value);\n\t\tself.last.store(timestamp_us(), Ordering::Relaxed);\n\t\ttrue\n\t}\n\n\tpub async fn load_or_create(&self) {\n\t\tif self.load().await.is_err() {\n\t\t\tself.inner.write().replace(Default::default());\n\t\t\tself.last.store(timestamp_us(), Ordering::Relaxed);\n\t\t}\n\t}\n\n\tpub async fn drain(&self) -> Result<()> {\n\t\tlet Some(inner) = self.inner.write().take() else { return Ok(()) };\n\t\tif self.skip().await.unwrap_or(false) {\n\t\t\treturn Ok(());\n\t\t}\n\n\t\tLocal::regular(&BOOT.state_dir).create_dir_all().await?;\n\t\tlet mut buf = BufWriter::new(\n\t\t\tGate::default()\n\t\t\t\t.write(true)\n\t\t\t\t.create(true)\n\t\t\t\t.truncate(true)\n\t\t\t\t.open(BOOT.state_dir.join(\".dds\"))\n\t\t\t\t.await?,\n\t\t);\n\n\t\tlet mut state = inner.into_iter().collect::<Vec<_>>();\n\t\tstate.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));\n\t\tfor (_, v) in state {\n\t\t\tbuf.write_all(v.as_bytes()).await?;\n\t\t}\n\n\t\tbuf.flush().await?;\n\t\tOk(())\n\t}\n\n\tasync fn load(&self) -> Result<()> {\n\t\tlet mut file = BufReader::new(Local::regular(&BOOT.state_dir.join(\".dds\")).open().await?);\n\t\tlet mut buf = String::new();\n\n\t\tlet mut inner = HashMap::new();\n\t\twhile file.read_line(&mut buf).await? > 0 {\n\t\t\tlet line = mem::take(&mut buf);\n\t\t\tlet mut parts = line.splitn(4, ',');\n\n\t\t\tlet Some(kind) = parts.next() else { continue };\n\t\t\tlet Some(_) = parts.next() else { continue };\n\t\t\tinner.insert(kind.to_owned(), line);\n\t\t}\n\n\t\tlet clients = CLIENTS.read();\n\t\tfor payload in inner.values() {\n\t\t\tclients.values().for_each(|c| _ = c.tx.send(payload.clone()));\n\t\t}\n\n\t\tself.inner.write().replace(inner);\n\t\tself.last.store(timestamp_us(), Ordering::Relaxed);\n\t\tOk(())\n\t}\n\n\tasync fn skip(&self) -> Result<bool> {\n\t\tlet cha = Local::regular(&BOOT.state_dir.join(\".dds\")).symlink_metadata().await?;\n\t\tlet modified = cha.mtime_dur()?.as_micros();\n\t\tOk(modified >= self.last.load(Ordering::Relaxed) as u128)\n\t}\n}\n"
  },
  {
    "path": "yazi-dds/src/stream.rs",
    "content": "use tokio::io::{BufReader, Lines, ReadHalf, WriteHalf};\n\npub struct Stream;\n\nuse tokio::io::AsyncBufReadExt;\n\n#[cfg(unix)]\npub type ClientReader = Lines<BufReader<ReadHalf<tokio::net::UnixStream>>>;\n#[cfg(not(unix))]\npub type ClientReader = Lines<BufReader<ReadHalf<tokio::net::TcpStream>>>;\n\n#[cfg(unix)]\npub(super) type ClientWriter = WriteHalf<tokio::net::UnixStream>;\n#[cfg(not(unix))]\npub(super) type ClientWriter = WriteHalf<tokio::net::TcpStream>;\n\n#[cfg(unix)]\npub(super) type ServerListener = tokio::net::UnixListener;\n#[cfg(not(unix))]\npub(super) type ServerListener = tokio::net::TcpListener;\n\nimpl Stream {\n\t#[cfg(unix)]\n\tpub async fn connect() -> std::io::Result<(ClientReader, ClientWriter)> {\n\t\tlet stream = tokio::net::UnixStream::connect(Self::socket_file()).await?;\n\t\tlet (reader, writer) = tokio::io::split(stream);\n\t\tOk((BufReader::new(reader).lines(), writer))\n\t}\n\n\t#[cfg(not(unix))]\n\tpub async fn connect() -> std::io::Result<(ClientReader, ClientWriter)> {\n\t\tlet stream = tokio::net::TcpStream::connect(\"127.0.0.1:33581\").await?;\n\t\tlet (reader, writer) = tokio::io::split(stream);\n\t\tOk((BufReader::new(reader).lines(), writer))\n\t}\n\n\t#[cfg(unix)]\n\tpub(super) async fn bind() -> std::io::Result<ServerListener> {\n\t\tuse yazi_fs::provider::Provider;\n\n\t\tlet p = Self::socket_file();\n\n\t\tyazi_fs::provider::local::Local::regular(&p).remove_file().await.ok();\n\t\ttokio::net::UnixListener::bind(p)\n\t}\n\n\t#[cfg(not(unix))]\n\tpub(super) async fn bind() -> std::io::Result<ServerListener> {\n\t\ttokio::net::TcpListener::bind(\"127.0.0.1:33581\").await\n\t}\n\n\t#[cfg(unix)]\n\tfn socket_file() -> std::path::PathBuf {\n\t\tuse std::env::temp_dir;\n\n\t\tuse uzers::Users;\n\t\tuse yazi_shared::USERS_CACHE;\n\n\t\ttemp_dir().join(format!(\".yazi_dds-{}.sock\", USERS_CACHE.get_current_uid()))\n\t}\n}\n"
  },
  {
    "path": "yazi-emulator/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-emulator\"\ndescription            = \"Yazi terminal emulator database\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[dependencies]\nyazi-macro  = { path = \"../yazi-macro\", version = \"26.2.2\" }\nyazi-shared = { path = \"../yazi-shared\", version = \"26.2.2\" }\nyazi-tty    = { path = \"../yazi-tty\", version = \"26.2.2\" }\n\n# External dependencies\nanyhow     = { workspace = true }\ncrossterm  = { workspace = true }\neither     = { workspace = true }\nscopeguard = { workspace = true }\ntokio      = { workspace = true }\ntracing    = { workspace = true }\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\ncrossterm = { workspace = true, features = [ \"use-dev-tty\", \"libc\" ] }\n"
  },
  {
    "path": "yazi-emulator/README.md",
    "content": "# yazi-emulator\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-emulator/src/brand.rs",
    "content": "use tracing::debug;\nuse yazi_shared::env_exists;\n\nuse crate::Mux;\n\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]\npub enum Brand {\n\tKitty,\n\tKonsole,\n\tIterm2,\n\tWezTerm,\n\tFoot,\n\tGhostty,\n\tMicrosoft,\n\tWarp,\n\tRio,\n\tBlackBox,\n\tVSCode,\n\tTabby,\n\tHyper,\n\tMintty,\n\tTmux,\n\tVTerm,\n\tApple,\n\tUrxvt,\n\tBobcat,\n}\n\nimpl Brand {\n\tpub(super) fn from_csi(resp: &str) -> Option<Self> {\n\t\tlet names = [\n\t\t\t(\"kitty\", Self::Kitty),\n\t\t\t(\"Konsole\", Self::Konsole),\n\t\t\t(\"iTerm2\", Self::Iterm2),\n\t\t\t(\"WezTerm\", Self::WezTerm),\n\t\t\t(\"foot\", Self::Foot),\n\t\t\t(\"ghostty\", Self::Ghostty),\n\t\t\t(\"Warp\", Self::Warp),\n\t\t\t(\"tmux \", Self::Tmux),\n\t\t\t(\"libvterm\", Self::VTerm),\n\t\t\t(\"Bobcat\", Self::Bobcat),\n\t\t];\n\t\tnames.into_iter().find(|&(n, _)| resp.contains(n)).map(|(_, b)| b)\n\t}\n\n\tpub fn from_env() -> Option<Self> {\n\t\tlet (term, program) = Self::env();\n\t\tlet vars = [\n\t\t\t(\"KITTY_WINDOW_ID\", Self::Kitty),\n\t\t\t(\"KONSOLE_VERSION\", Self::Konsole),\n\t\t\t(\"ITERM_SESSION_ID\", Self::Iterm2),\n\t\t\t(\"WEZTERM_EXECUTABLE\", Self::WezTerm),\n\t\t\t(\"GHOSTTY_RESOURCES_DIR\", Self::Ghostty),\n\t\t\t(\"WT_Session\", Self::Microsoft),\n\t\t\t(\"WARP_HONOR_PS1\", Self::Warp),\n\t\t\t(\"VSCODE_INJECTION\", Self::VSCode),\n\t\t\t(\"TABBY_CONFIG_DIRECTORY\", Self::Tabby),\n\t\t];\n\n\t\tmatch term.as_str() {\n\t\t\t\"xterm-kitty\" => return Some(Self::Kitty),\n\t\t\t\"foot\" => return Some(Self::Foot),\n\t\t\t\"foot-extra\" => return Some(Self::Foot),\n\t\t\t\"xterm-ghostty\" => return Some(Self::Ghostty),\n\t\t\t\"rio\" => return Some(Self::Rio),\n\t\t\t\"rxvt-unicode-256color\" => return Some(Self::Urxvt),\n\t\t\t_ => {}\n\t\t}\n\t\tmatch program.as_str() {\n\t\t\t\"iTerm.app\" => return Some(Self::Iterm2),\n\t\t\t\"WezTerm\" => return Some(Self::WezTerm),\n\t\t\t\"ghostty\" => return Some(Self::Ghostty),\n\t\t\t\"WarpTerminal\" => return Some(Self::Warp),\n\t\t\t\"rio\" => return Some(Self::Rio),\n\t\t\t\"BlackBox\" => return Some(Self::BlackBox),\n\t\t\t\"vscode\" => return Some(Self::VSCode),\n\t\t\t\"Tabby\" => return Some(Self::Tabby),\n\t\t\t\"Hyper\" => return Some(Self::Hyper),\n\t\t\t\"mintty\" => return Some(Self::Mintty),\n\t\t\t\"Apple_Terminal\" => return Some(Self::Apple),\n\t\t\t_ => {}\n\t\t}\n\t\tif let Some((var, brand)) = vars.into_iter().find(|&(s, _)| env_exists(s)) {\n\t\t\tdebug!(\"Detected special environment variable: {var}\");\n\t\t\treturn Some(brand);\n\t\t}\n\n\t\tNone\n\t}\n\n\tfn env() -> (String, String) {\n\t\tlet (term, program) = Mux::term_program();\n\t\t(\n\t\t\tterm.unwrap_or(std::env::var(\"TERM\").unwrap_or_default()),\n\t\t\tprogram.unwrap_or(std::env::var(\"TERM_PROGRAM\").unwrap_or_default()),\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "yazi-emulator/src/dimension.rs",
    "content": "use std::mem;\n\nuse crossterm::terminal::WindowSize;\n\nuse crate::EMULATOR;\n\n#[derive(Clone, Copy, Debug)]\npub struct Dimension {\n\tpub rows:    u16,\n\tpub columns: u16,\n\tpub width:   u16,\n\tpub height:  u16,\n}\n\nimpl From<WindowSize> for Dimension {\n\tfn from(s: WindowSize) -> Self {\n\t\tSelf { rows: s.rows, columns: s.columns, width: s.width, height: s.height }\n\t}\n}\n\nimpl From<Dimension> for WindowSize {\n\tfn from(d: Dimension) -> Self {\n\t\tSelf { rows: d.rows, columns: d.columns, width: d.width, height: d.height }\n\t}\n}\n\nimpl Dimension {\n\tpub fn available() -> Self {\n\t\tlet mut size = WindowSize { rows: 0, columns: 0, width: 0, height: 0 };\n\t\tif let Ok(s) = crossterm::terminal::window_size() {\n\t\t\t_ = mem::replace(&mut size, s);\n\t\t}\n\n\t\tif (size.columns == 0 || size.rows == 0)\n\t\t\t&& let Ok((cols, rows)) = crossterm::terminal::size()\n\t\t{\n\t\t\tsize.columns = cols;\n\t\t\tsize.rows = rows;\n\t\t}\n\n\t\tsize.into()\n\t}\n\n\tpub fn ratio(self) -> Option<(f64, f64)> {\n\t\tif self.rows == 0 || self.columns == 0 || self.width == 0 || self.height == 0 {\n\t\t\tNone\n\t\t} else {\n\t\t\tSome((self.width as f64 / self.columns as f64, self.height as f64 / self.rows as f64))\n\t\t}\n\t}\n\n\tpub fn cell_size() -> Option<(f64, f64)> {\n\t\tlet emu = &*EMULATOR;\n\t\tSome(if emu.force_16t {\n\t\t\t(emu.csi_16t.0 as f64, emu.csi_16t.1 as f64)\n\t\t} else if let Some(r) = Self::available().ratio() {\n\t\t\tr\n\t\t} else if emu.csi_16t.0 != 0 && emu.csi_16t.1 != 0 {\n\t\t\t(emu.csi_16t.0 as f64, emu.csi_16t.1 as f64)\n\t\t} else {\n\t\t\tNone?\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-emulator/src/emulator.rs",
    "content": "use std::{io::BufWriter, time::Duration};\n\nuse anyhow::Result;\nuse crossterm::{cursor::{RestorePosition, SavePosition}, execute, style::Print, terminal::{disable_raw_mode, enable_raw_mode}};\nuse either::Either;\nuse scopeguard::defer;\nuse tokio::time::sleep;\nuse tracing::{debug, error, warn};\nuse yazi_shared::RoCell;\nuse yazi_tty::{Handle, TTY};\n\nuse crate::{Brand, Dimension, Mux, TMUX, Unknown};\n\npub static EMULATOR: RoCell<Emulator> = RoCell::new();\n\n#[derive(Clone, Debug)]\npub struct Emulator {\n\tpub kind:      Either<Brand, Unknown>,\n\tpub version:   String,\n\tpub light:     bool,\n\tpub csi_16t:   (u16, u16),\n\tpub force_16t: bool,\n}\n\nimpl Default for Emulator {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tkind:      Either::Right(Unknown::default()),\n\t\t\tversion:   String::new(),\n\t\t\tlight:     false,\n\t\t\tcsi_16t:   (0, 0),\n\t\t\tforce_16t: false,\n\t\t}\n\t}\n}\n\nimpl Emulator {\n\tpub fn detect() -> Result<Self> {\n\t\tdefer! { disable_raw_mode().ok(); }\n\t\tenable_raw_mode()?;\n\n\t\tlet resort = Brand::from_env();\n\t\tlet kgp_seq = if resort.is_none() {\n\t\t\tMux::csi(\"\\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\\x1b\\\\\")\n\t\t} else {\n\t\t\t\"\".into()\n\t\t};\n\n\t\texecute!(\n\t\t\tTTY.writer(),\n\t\t\tSavePosition,\n\t\t\tPrint(kgp_seq),             // Detect KGP\n\t\t\tPrint(Mux::csi(\"\\x1b[>q\")), // Request terminal version\n\t\t\tPrint(\"\\x1b[16t\"),          // Request cell size\n\t\t\tPrint(\"\\x1b]11;?\\x07\"),     // Request background color\n\t\t\tPrint(Mux::csi(\"\\x1b[0c\")), // Request device attributes\n\t\t\tRestorePosition\n\t\t)?;\n\n\t\tlet resp = Self::read_until_da1();\n\t\tMux::tmux_drain()?;\n\n\t\tlet kind = if let Some(b) = Brand::from_csi(&resp).or(resort) {\n\t\t\tEither::Left(b)\n\t\t} else {\n\t\t\tEither::Right(Unknown {\n\t\t\t\tkgp:   resp.contains(\"\\x1b_Gi=31;OK\"),\n\t\t\t\tsixel: [\"?4;\", \"?4c\", \";4;\", \";4c\"].iter().any(|s| resp.contains(s)),\n\t\t\t})\n\t\t};\n\n\t\tlet csi_16t = Self::csi_16t(&resp).unwrap_or_default();\n\t\tOk(Self {\n\t\t\tkind,\n\t\t\tversion: Self::csi_gt_q(&resp).unwrap_or_default(),\n\t\t\tlight: Self::light_bg(&resp).unwrap_or_default(),\n\t\t\tcsi_16t,\n\t\t\tforce_16t: Self::force_16t(csi_16t),\n\t\t})\n\t}\n\n\tpub fn move_lock<F, T>((x, y): (u16, u16), cb: F) -> Result<T>\n\twhere\n\t\tF: FnOnce(&mut BufWriter<Handle>) -> Result<T>,\n\t{\n\t\tuse std::{io::Write, thread, time::Duration};\n\n\t\tuse crossterm::{cursor::{Hide, MoveTo, Show}, queue};\n\n\t\tlet mut w = TTY.lockout();\n\n\t\t// I really don't want to add this,\n\t\t// But tmux and ConPTY sometimes cause the cursor position to get out of sync.\n\t\tif TMUX.get() || cfg!(windows) {\n\t\t\texecute!(w, SavePosition, MoveTo(x, y), Show)?;\n\t\t\texecute!(w, MoveTo(x, y), Show)?;\n\t\t\texecute!(w, MoveTo(x, y), Show)?;\n\t\t\tthread::sleep(Duration::from_millis(1));\n\t\t} else {\n\t\t\tqueue!(w, SavePosition, MoveTo(x, y))?;\n\t\t}\n\n\t\tlet result = cb(&mut w);\n\t\tif TMUX.get() || cfg!(windows) {\n\t\t\tqueue!(w, Hide, RestorePosition)?;\n\t\t} else {\n\t\t\tqueue!(w, RestorePosition)?;\n\t\t}\n\n\t\tw.flush()?;\n\t\tresult\n\t}\n\n\tpub fn read_until_da1() -> String {\n\t\tlet now = std::time::Instant::now();\n\t\tlet h = tokio::spawn(Self::error_to_user());\n\n\t\tlet (buf, result) = TTY.read_until(Duration::from_millis(1000), |b, buf| {\n\t\t\tb == b'c'\n\t\t\t\t&& buf.contains(&0x1b)\n\t\t\t\t&& buf.rsplitn(2, |&b| b == 0x1b).next().is_some_and(|s| s.starts_with(b\"[?\"))\n\t\t});\n\n\t\th.abort();\n\t\tmatch result {\n\t\t\tOk(()) => debug!(\"Terminal responded to DA1 in {:?}: {buf:?}\", now.elapsed()),\n\t\t\tErr(e) => {\n\t\t\t\terror!(\"Terminal failed to respond to DA1 in {:?}: {buf:?}, error: {e:?}\", now.elapsed())\n\t\t\t}\n\t\t}\n\n\t\tString::from_utf8_lossy(&buf).into_owned()\n\t}\n\n\tpub fn read_until_dsr() -> String {\n\t\tlet now = std::time::Instant::now();\n\t\tlet (buf, result) = TTY.read_until(Duration::from_millis(200), |b, buf| {\n\t\t\tb == b'n' && (buf.ends_with(b\"\\x1b[0n\") || buf.ends_with(b\"\\x1b[3n\"))\n\t\t});\n\n\t\tmatch result {\n\t\t\tOk(()) => debug!(\"Terminal responded to DSR in {:?}: {buf:?}\", now.elapsed()),\n\t\t\tErr(e) => {\n\t\t\t\terror!(\"Terminal failed to respond to DSR in {:?}: {buf:?}, error: {e:?}\", now.elapsed())\n\t\t\t}\n\t\t}\n\t\tString::from_utf8_lossy(&buf).into_owned()\n\t}\n\n\tasync fn error_to_user() {\n\t\tuse crossterm::style::{Attribute, Color, Print, ResetColor, SetAttributes, SetForegroundColor};\n\n\t\tsleep(Duration::from_millis(400)).await;\n\t\t_ = crossterm::execute!(\n\t\t\tstd::io::stderr(),\n\t\t\tSetForegroundColor(Color::Red),\n\t\t\tSetAttributes(Attribute::Bold.into()),\n\t\t\tPrint(\"\\r\\nTerminal response timeout: \"),\n\t\t\tResetColor,\n\t\t\tSetAttributes(Attribute::Reset.into()),\n\t\t\t//\n\t\t\tPrint(\"The request sent by Yazi didn't receive a correct response.\\r\\n\"),\n\t\t\tPrint(\n\t\t\t\t\"Please check your terminal environment as per: https://yazi-rs.github.io/docs/faq#trt\\r\\n\"\n\t\t\t),\n\t\t);\n\t}\n\n\tfn csi_16t(resp: &str) -> Option<(u16, u16)> {\n\t\tlet b = resp.split_once(\"\\x1b[6;\")?.1.as_bytes();\n\n\t\tlet h: Vec<_> = b.iter().copied().take_while(|&c| c.is_ascii_digit()).collect();\n\t\tb.get(h.len()).filter(|&&c| c == b';')?;\n\n\t\tlet w: Vec<_> = b[h.len() + 1..].iter().copied().take_while(|&c| c.is_ascii_digit()).collect();\n\t\tb.get(h.len() + 1 + w.len()).filter(|&&c| c == b't')?;\n\n\t\tlet (w, h) = unsafe { (String::from_utf8_unchecked(w), String::from_utf8_unchecked(h)) };\n\t\tSome((w.parse().ok()?, h.parse().ok()?))\n\t}\n\n\tfn csi_gt_q(resp: &str) -> Option<String> {\n\t\tlet (_, s) = resp.split_once(\"\\x1bP>|\")?;\n\t\tSome(s[..s.find(\"\\x1b\\\\\")?].to_owned())\n\t}\n\n\tfn light_bg(resp: &str) -> Result<bool> {\n\t\tmatch resp.split_once(\"]11;rgb:\") {\n\t\t\tSome((_, s)) if s.len() >= 14 => {\n\t\t\t\tlet r = u8::from_str_radix(&s[0..2], 16)? as f32;\n\t\t\t\tlet g = u8::from_str_radix(&s[5..7], 16)? as f32;\n\t\t\t\tlet b = u8::from_str_radix(&s[10..12], 16)? as f32;\n\t\t\t\tlet luma = r * 0.2627 / 256.0 + g * 0.6780 / 256.0 + b * 0.0593 / 256.0;\n\t\t\t\tdebug!(\"Detected background color: {} (luma = {luma:.2})\", &s[..14]);\n\t\t\t\tOk(luma > 0.6)\n\t\t\t}\n\t\t\t_ => {\n\t\t\t\twarn!(\"Failed to detect background color: {resp:?}\");\n\t\t\t\tOk(false)\n\t\t\t}\n\t\t}\n\t}\n\n\tfn force_16t((w, h): (u16, u16)) -> bool {\n\t\tif w == 0 || h == 0 {\n\t\t\treturn false;\n\t\t}\n\n\t\tDimension::available()\n\t\t\t.ratio()\n\t\t\t.is_none_or(|(rw, rh)| rw.floor() as u16 != w || rh.floor() as u16 != h)\n\t}\n}\n"
  },
  {
    "path": "yazi-emulator/src/lib.rs",
    "content": "yazi_macro::mod_flat!(brand dimension emulator mux unknown);\n"
  },
  {
    "path": "yazi-emulator/src/mux.rs",
    "content": "use std::borrow::Cow;\n\nuse anyhow::Result;\nuse tracing::error;\nuse yazi_macro::time;\nuse yazi_shared::SyncCell;\nuse yazi_tty::TTY;\n\nuse crate::Emulator;\n\npub static TMUX: SyncCell<bool> = SyncCell::new(false);\npub static ESCAPE: SyncCell<&'static str> = SyncCell::new(\"\\x1b\");\npub static START: SyncCell<&'static str> = SyncCell::new(\"\\x1b\");\npub static CLOSE: SyncCell<&'static str> = SyncCell::new(\"\");\n\npub struct Mux;\n\nimpl Mux {\n\tpub fn csi(s: &str) -> Cow<'_, str> {\n\t\tif TMUX.get() {\n\t\t\tCow::Owned(format!(\n\t\t\t\t\"{START}{}{CLOSE}\",\n\t\t\t\ts.trim_start_matches('\\x1b').replace('\\x1b', ESCAPE.get()),\n\t\t\t))\n\t\t} else {\n\t\t\tCow::Borrowed(s)\n\t\t}\n\t}\n\n\tpub fn tmux_passthrough() {\n\t\tlet output = time!(\n\t\t\t\"Running `tmux set -p allow-passthrough on`\",\n\t\t\tstd::process::Command::new(\"tmux\")\n\t\t\t\t.args([\"set\", \"-p\", \"allow-passthrough\", \"on\"])\n\t\t\t\t.stdin(std::process::Stdio::null())\n\t\t\t\t.stdout(std::process::Stdio::null())\n\t\t\t\t.stderr(std::process::Stdio::piped())\n\t\t\t\t.spawn()\n\t\t\t\t.and_then(|c| c.wait_with_output())\n\t\t);\n\n\t\tmatch output {\n\t\t\tOk(o) if o.status.success() => {}\n\t\t\tOk(o) => {\n\t\t\t\terror!(\n\t\t\t\t\t\"Running `tmux set -p allow-passthrough on` failed: {:?}, {}\",\n\t\t\t\t\to.status,\n\t\t\t\t\tString::from_utf8_lossy(&o.stderr)\n\t\t\t\t);\n\t\t\t}\n\t\t\tErr(e) => {\n\t\t\t\terror!(\"Failed to spawn `tmux set -p allow-passthrough on`: {e}\");\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn tmux_drain() -> Result<()> {\n\t\tif TMUX.get() {\n\t\t\tcrossterm::execute!(TTY.writer(), crossterm::style::Print(Self::csi(\"\\x1b[5n\")))?;\n\t\t\t_ = Emulator::read_until_dsr();\n\t\t}\n\t\tOk(())\n\t}\n\n\tpub fn tmux_sixel_flag() -> &'static str {\n\t\tlet stdout = std::process::Command::new(\"tmux\")\n\t\t\t.args([\"-LwU0dju1is5\", \"-f/dev/null\", \"start\", \";\", \"display\", \"-p\", \"#{sixel_support}\"])\n\t\t\t.output()\n\t\t\t.ok()\n\t\t\t.and_then(|o| String::from_utf8(o.stdout).ok())\n\t\t\t.unwrap_or_default();\n\n\t\tmatch stdout.trim() {\n\t\t\t\"1\" => \"Supported\",\n\t\t\t\"0\" => \"Unsupported\",\n\t\t\t_ => \"Unknown\",\n\t\t}\n\t}\n\n\tpub(super) fn term_program() -> (Option<String>, Option<String>) {\n\t\tlet (mut term, mut program) = (None, None);\n\t\tif !TMUX.get() {\n\t\t\treturn (term, program);\n\t\t}\n\n\t\tlet Ok(output) = time!(\n\t\t\t\"Running `tmux show-environment`\",\n\t\t\tstd::process::Command::new(\"tmux\").arg(\"show-environment\").output()\n\t\t) else {\n\t\t\treturn (term, program);\n\t\t};\n\n\t\tfor line in String::from_utf8_lossy(&output.stdout).lines() {\n\t\t\tif let Some((k, v)) = line.trim().split_once('=') {\n\t\t\t\tmatch k {\n\t\t\t\t\t\"TERM\" => term = Some(v.to_owned()),\n\t\t\t\t\t\"TERM_PROGRAM\" => program = Some(v.to_owned()),\n\t\t\t\t\t_ => continue,\n\t\t\t\t}\n\t\t\t}\n\t\t\tif term.is_some() && program.is_some() {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\t(term, program)\n\t}\n}\n"
  },
  {
    "path": "yazi-emulator/src/unknown.rs",
    "content": "#[derive(Clone, Copy, Debug, Default)]\npub struct Unknown {\n\tpub kgp:   bool,\n\tpub sixel: bool,\n}\n"
  },
  {
    "path": "yazi-ffi/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-ffi\"\ndescription            = \"Yazi foreign function interface\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[dependencies]\nyazi-macro = { path = \"../yazi-macro\", version = \"26.2.2\" }\n\n# External dependencies\nanyhow = { workspace = true }\n\n[target.\"cfg(unix)\".dependencies]\nlibc = { workspace = true }\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\ncore-foundation-sys = { workspace = true }\nobjc2               = { workspace = true }\n"
  },
  {
    "path": "yazi-ffi/README.md",
    "content": "# yazi-ffi\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-ffi/src/cf_dict.rs",
    "content": "use std::{ffi::{CStr, OsStr, OsString, c_char, c_void}, mem::ManuallyDrop, os::unix::ffi::OsStrExt, path::PathBuf};\n\nuse anyhow::{Result, bail};\nuse core_foundation_sys::{base::{CFRelease, TCFTypeRef}, dictionary::{CFDictionaryGetValueIfPresent, CFDictionaryRef}, string::CFStringRef};\nuse objc2::{msg_send, runtime::AnyObject};\n\nuse super::cf_string::CFString;\n\npub struct CFDict(CFDictionaryRef);\n\nimpl CFDict {\n\tpub fn take(dict: CFDictionaryRef) -> Result<Self> {\n\t\tif dict.is_null() {\n\t\t\tbail!(\"Cannot take a null pointer\");\n\t\t}\n\t\tOk(Self(dict))\n\t}\n\n\tpub fn value(&self, key: &str) -> Result<*const c_void> {\n\t\tlet key_ = CFString::new(key)?;\n\t\tlet mut value = std::ptr::null();\n\t\tif unsafe { CFDictionaryGetValueIfPresent(self.0, key_.as_void_ptr(), &mut value) } == 0\n\t\t\t|| value.is_null()\n\t\t{\n\t\t\tbail!(\"Cannot get the value for the key `{key}`\");\n\t\t}\n\t\tOk(value)\n\t}\n\n\tpub fn bool(&self, key: &str) -> Result<bool> {\n\t\tlet value = self.value(key)?;\n\t\t#[allow(unexpected_cfgs)]\n\t\tOk(unsafe { msg_send![value as *const AnyObject, boolValue] })\n\t}\n\n\tpub fn integer(&self, key: &str) -> Result<i64> {\n\t\tlet value = self.value(key)?;\n\t\t#[allow(unexpected_cfgs)]\n\t\tOk(unsafe { msg_send![value as *const AnyObject, longLongValue] })\n\t}\n\n\tpub fn os_string(&self, key: &str) -> Result<OsString> {\n\t\tManuallyDrop::new(CFString(self.value(key)? as CFStringRef)).os_string()\n\t}\n\n\tpub fn path_buf(&self, key: &str) -> Result<PathBuf> {\n\t\tlet url = self.value(key)? as *const AnyObject;\n\t\t#[allow(unexpected_cfgs)]\n\t\tlet cstr: *const c_char = unsafe {\n\t\t\tlet nss: *const AnyObject = msg_send![url, path];\n\t\t\tmsg_send![nss, UTF8String]\n\t\t};\n\t\tOk(OsStr::from_bytes(unsafe { CStr::from_ptr(cstr) }.to_bytes()).into())\n\t}\n}\n\nimpl Drop for CFDict {\n\tfn drop(&mut self) { unsafe { CFRelease(self.0 as _) } }\n}\n"
  },
  {
    "path": "yazi-ffi/src/cf_string.rs",
    "content": "use std::{ffi::OsString, ops::Deref, os::unix::ffi::OsStringExt};\n\nuse anyhow::{Result, bail};\nuse core_foundation_sys::{base::{CFRelease, kCFAllocatorDefault, kCFAllocatorNull}, string::{CFStringCreateWithBytesNoCopy, CFStringGetCString, CFStringGetLength, CFStringGetMaximumSizeForEncoding, CFStringRef, kCFStringEncodingUTF8}};\nuse libc::strlen;\n\npub struct CFString(pub(super) CFStringRef);\n\nimpl CFString {\n\tpub fn new(s: &str) -> Result<Self> {\n\t\tlet key = unsafe {\n\t\t\tCFStringCreateWithBytesNoCopy(\n\t\t\t\tkCFAllocatorDefault,\n\t\t\t\ts.as_ptr(),\n\t\t\t\ts.len() as _,\n\t\t\t\tkCFStringEncodingUTF8,\n\t\t\t\tfalse as _,\n\t\t\t\tkCFAllocatorNull,\n\t\t\t)\n\t\t};\n\t\tif key.is_null() {\n\t\t\tbail!(\"Allocation failed while creating CFString\");\n\t\t}\n\t\tOk(Self(key))\n\t}\n\n\tpub fn len(&self) -> usize { unsafe { CFStringGetLength(self.0) as _ } }\n\n\tpub fn is_empty(&self) -> bool { self.len() == 0 }\n\n\tpub fn os_string(&self) -> Result<OsString> {\n\t\tlet len = self.len();\n\t\tlet capacity =\n\t\t\tunsafe { CFStringGetMaximumSizeForEncoding(len as _, kCFStringEncodingUTF8) } + 1;\n\n\t\tlet mut out: Vec<u8> = Vec::with_capacity(capacity as usize);\n\t\tlet result = unsafe {\n\t\t\tCFStringGetCString(self.0, out.as_mut_ptr().cast(), capacity, kCFStringEncodingUTF8)\n\t\t};\n\t\tif result == 0 {\n\t\t\tbail!(\"Failed to get the C string from CFString\");\n\t\t}\n\n\t\tunsafe { out.set_len(strlen(out.as_ptr().cast())) };\n\t\tout.shrink_to_fit();\n\t\tOk(OsString::from_vec(out))\n\t}\n}\n\nimpl Deref for CFString {\n\ttype Target = CFStringRef;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl Drop for CFString {\n\tfn drop(&mut self) { unsafe { CFRelease(self.0 as _) }; }\n}\n"
  },
  {
    "path": "yazi-ffi/src/disk_arbitration.rs",
    "content": "use std::ffi::{c_char, c_void};\n\nuse core_foundation_sys::{array::CFArrayRef, base::CFAllocatorRef, dictionary::CFDictionaryRef, runloop::CFRunLoopRef, string::CFStringRef};\n\n#[link(name = \"DiskArbitration\", kind = \"framework\")]\nunsafe extern \"C\" {\n\tpub fn DASessionCreate(allocator: CFAllocatorRef) -> *const c_void;\n\n\tpub fn DADiskCreateFromBSDName(\n\t\tallocator: CFAllocatorRef,\n\t\tsession: *const c_void,\n\t\tpath: *const c_char,\n\t) -> *const c_void;\n\n\tpub fn DADiskCopyDescription(disk: *const c_void) -> CFDictionaryRef;\n\n\tpub fn DARegisterDiskAppearedCallback(\n\t\tsession: *const c_void,\n\t\tr#match: CFDictionaryRef,\n\t\tcallback: extern \"C\" fn(disk: *const c_void, context: *mut c_void),\n\t\tcontext: *mut c_void,\n\t);\n\n\tpub fn DARegisterDiskDescriptionChangedCallback(\n\t\tsession: *const c_void,\n\t\tr#match: CFDictionaryRef,\n\t\twatch: CFArrayRef,\n\t\tcallback: extern \"C\" fn(disk: *const c_void, keys: CFArrayRef, context: *mut c_void),\n\t\tcontext: *mut c_void,\n\t);\n\n\tpub fn DARegisterDiskDisappearedCallback(\n\t\tsession: *const c_void,\n\t\tr#match: CFDictionaryRef,\n\t\tcallback: extern \"C\" fn(disk: *const c_void, context: *mut c_void),\n\t\tcontext: *mut c_void,\n\t);\n\n\tpub fn DASessionScheduleWithRunLoop(\n\t\tsession: *const c_void,\n\t\trunLoop: CFRunLoopRef,\n\t\trunLoopMode: CFStringRef,\n\t);\n}\n"
  },
  {
    "path": "yazi-ffi/src/io_kit.rs",
    "content": "use std::ffi::c_char;\n\nuse core_foundation_sys::{base::{CFAllocatorRef, CFTypeRef, mach_port_t}, dictionary::CFMutableDictionaryRef, string::CFStringRef};\nuse libc::kern_return_t;\n\n#[link(name = \"IOKit\", kind = \"framework\")]\nunsafe extern \"C\" {\n\tpub fn IOServiceGetMatchingServices(\n\t\tmainPort: mach_port_t,\n\t\tmatching: CFMutableDictionaryRef,\n\t\texisting: *mut mach_port_t,\n\t) -> kern_return_t;\n\n\tpub fn IOServiceMatching(a: *const c_char) -> CFMutableDictionaryRef;\n\n\tpub fn IOIteratorNext(iterator: mach_port_t) -> mach_port_t;\n\n\tpub fn IORegistryEntryCreateCFProperty(\n\t\tentry: mach_port_t,\n\t\tkey: CFStringRef,\n\t\tallocator: CFAllocatorRef,\n\t\toptions: u32,\n\t) -> CFTypeRef;\n\n\tpub fn IOObjectRelease(obj: mach_port_t) -> kern_return_t;\n}\n"
  },
  {
    "path": "yazi-ffi/src/lib.rs",
    "content": "#[cfg(target_os = \"macos\")]\nyazi_macro::mod_flat!(cf_dict cf_string disk_arbitration io_kit);\n"
  },
  {
    "path": "yazi-fm/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-fm\"\ndescription            = \"Yazi file manager\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[profile.release]\ncodegen-units = 1\nlto           = true\npanic         = \"abort\"\nstrip         = true\n\n[profile.release-windows]\ninherits = \"release\"\npanic    = \"unwind\"\n\n[features]\ndefault      = [ \"vendored-lua\" ]\nvendored-lua = [ \"mlua/vendored\" ]\n\n[dependencies]\nyazi-actor    = { path = \"../yazi-actor\", version = \"26.2.2\" }\nyazi-adapter  = { path = \"../yazi-adapter\", version = \"26.2.2\" }\nyazi-binding  = { path = \"../yazi-binding\", version = \"26.2.2\" }\nyazi-boot     = { path = \"../yazi-boot\", version = \"26.2.2\" }\nyazi-config   = { path = \"../yazi-config\", version = \"26.2.2\" }\nyazi-core     = { path = \"../yazi-core\", version = \"26.2.2\" }\nyazi-dds      = { path = \"../yazi-dds\", version = \"26.2.2\" }\nyazi-emulator = { path = \"../yazi-emulator\", version = \"26.2.2\" }\nyazi-fs       = { path = \"../yazi-fs\", version = \"26.2.2\" }\nyazi-macro    = { path = \"../yazi-macro\", version = \"26.2.2\" }\nyazi-parser   = { path = \"../yazi-parser\", version = \"26.2.2\" }\nyazi-plugin   = { path = \"../yazi-plugin\", version = \"26.2.2\" }\nyazi-proxy    = { path = \"../yazi-proxy\", version = \"26.2.2\" }\nyazi-shared   = { path = \"../yazi-shared\", version = \"26.2.2\" }\nyazi-term     = { path = \"../yazi-term\", version = \"26.2.2\" }\nyazi-tty      = { path = \"../yazi-tty\", version = \"26.2.2\" }\nyazi-vfs      = { path = \"../yazi-vfs\", version = \"26.2.2\" }\nyazi-watcher  = { path = \"../yazi-watcher\", version = \"26.2.2\" }\nyazi-widgets  = { path = \"../yazi-widgets\", version = \"26.2.2\" }\n\n# External dependencies\nanyhow             = { workspace = true }\nbetter-panic       = \"0.3.0\"\ncrossterm          = { workspace = true }\nfdlimit            = \"0.3.0\"\nfutures            = { workspace = true }\nmlua               = { workspace = true }\npaste              = { workspace = true }\nratatui            = { workspace = true }\nscopeguard         = { workspace = true }\ntokio              = { workspace = true }\ntokio-stream       = { workspace = true }\ntracing            = { workspace = true }\ntracing-appender   = \"0.2.4\"\ntracing-subscriber = { version = \"0.3.23\", features = [ \"env-filter\" ] }\n\n[target.\"cfg(unix)\".dependencies]\nlibc              = { workspace = true }\nsignal-hook-tokio = { version = \"0.4.0\", features = [ \"futures-v0_3\" ] }\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\ncrossterm = { workspace = true, features = [ \"use-dev-tty\", \"libc\" ] }\n\n[target.'cfg(all(not(target_os = \"macos\"), not(target_os = \"windows\")))'.dependencies]\ntikv-jemallocator = \"0.6.1\"\n\n[[bin]]\nname = \"yazi\"\npath = \"src/main.rs\"\n\n[package.metadata.binstall]\npkg-url = \"{repo}/releases/download/v{version}/yazi-{target}{archive-suffix}\"\nbin-dir = \"yazi-{target}/{bin}{binary-ext}\"\n"
  },
  {
    "path": "yazi-fm/README.md",
    "content": "# yazi-fm\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-fm/build.rs",
    "content": "use std::{env, error::Error};\n\nfn main() -> Result<(), Box<dyn Error>> {\n\tlet dir = env::var(\"OUT_DIR\").unwrap();\n\n\t// cargo build\n\t//   C:\\Users\\Ika\\Desktop\\yazi\\target\\release\\build\\yazi-fm-cfc94820f71daa30\\out\n\t// cargo install\n\t//   C:\\Users\\Ika\\AppData\\Local\\Temp\\cargo-installTFU8cj\\release\\build\\\n\t// yazi-fm-45dffef2500eecd0\\out\n\n\tif dir.contains(r\"\\release\\build\\yazi-fm-\") {\n\t\tpanic!(\n\t\t\t\"Unwinding must be enabled for Windows. Please use `cargo build --profile release-windows --locked` instead to build Yazi.\"\n\t\t);\n\t}\n\n\tlet manifest = env::var_os(\"CARGO_MANIFEST_DIR\").unwrap().to_string_lossy().replace(r\"\\\", \"/\");\n\tif env::var_os(\"YAZI_CRATE_BUILD\").is_none()\n\t\t&& (manifest.contains(\"/git/checkouts/yazi-\")\n\t\t\t|| manifest.contains(\"/registry/src/index.crates.io-\"))\n\t{\n\t\tpanic!(\n\t\t\t\"Due to Cargo's limitations, the `yazi-fm` and `yazi-cli` crates on crates.io must be built with `cargo install --force yazi-build`\"\n\t\t);\n\t}\n\n\tOk(())\n}\n"
  },
  {
    "path": "yazi-fm/src/app/app.rs",
    "content": "use std::{sync::atomic::Ordering, time::{Duration, Instant}};\n\nuse anyhow::Result;\nuse tokio::{select, time::sleep};\nuse yazi_actor::Ctx;\nuse yazi_core::Core;\nuse yazi_macro::act;\nuse yazi_shared::{data::Data, event::{Event, NEED_RENDER}};\nuse yazi_term::Term;\n\nuse crate::{Dispatcher, Signals};\n\npub(crate) struct App {\n\tpub(crate) core:    Core,\n\tpub(crate) term:    Option<Term>,\n\tpub(crate) signals: Signals,\n}\n\nimpl App {\n\tpub(crate) async fn serve() -> Result<()> {\n\t\tlet term = Term::start()?;\n\t\tlet (mut rx, signals) = (Event::take(), Signals::start()?);\n\n\t\tlet mut app = Self { core: Core::make(), term: Some(term), signals };\n\t\tapp.bootstrap()?;\n\n\t\tlet mut events = Vec::with_capacity(50);\n\t\tlet (mut timeout, mut need_render, mut last_render) = (None, 0, Instant::now());\n\t\tmacro_rules! drain_events {\n\t\t\t() => {\n\t\t\t\tfor event in events.drain(..) {\n\t\t\t\t\tDispatcher::new(&mut app).dispatch(event);\n\n\t\t\t\t\tneed_render = NEED_RENDER.load(Ordering::Relaxed);\n\t\t\t\t\tif need_render == 0 {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\ttimeout = Duration::from_millis(10).checked_sub(last_render.elapsed());\n\t\t\t\t\tif timeout.is_none() {\n\t\t\t\t\t\tapp.render(need_render == 2)?;\n\t\t\t\t\t\tlast_render = Instant::now();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\tloop {\n\t\t\tif let Some(t) = timeout.take() {\n\t\t\t\tselect! {\n\t\t\t\t\t_ = sleep(t) => {\n\t\t\t\t\t\tapp.render(need_render == 2)?;\n\t\t\t\t\t\tlast_render = Instant::now();\n\t\t\t\t\t}\n\t\t\t\t\tn = rx.recv_many(&mut events, 50) => {\n\t\t\t\t\t\tif n == 0 { break }\n\t\t\t\t\t\tdrain_events!();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if rx.recv_many(&mut events, 50).await != 0 {\n\t\t\t\tdrain_events!();\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tOk(())\n\t}\n\n\tfn bootstrap(&mut self) -> anyhow::Result<Data> {\n\t\tlet cx = &mut Ctx::active(&mut self.core, &mut self.term);\n\t\tact!(app:bootstrap, cx)?;\n\n\t\tself.render(false)\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/app/mod.rs",
    "content": "yazi_macro::mod_flat!(app render);\n"
  },
  {
    "path": "yazi-fm/src/app/render.rs",
    "content": "use std::sync::atomic::{AtomicU8, Ordering};\n\nuse anyhow::Result;\nuse crossterm::{cursor::{MoveTo, SetCursorStyle, Show}, execute, queue, terminal::{BeginSynchronizedUpdate, EndSynchronizedUpdate}};\nuse ratatui::{CompletedFrame, backend::{Backend, CrosstermBackend}, buffer::Buffer, layout::Position};\nuse yazi_actor::{Ctx, lives::Lives};\nuse yazi_binding::runtime_scope;\nuse yazi_config::LAYOUT;\nuse yazi_macro::{act, succ};\nuse yazi_plugin::LUA;\nuse yazi_shared::{data::Data, event::NEED_RENDER};\nuse yazi_tty::TTY;\nuse yazi_widgets::COLLISION;\n\nuse crate::{app::App, root::Root};\n\nimpl App {\n\tpub(crate) fn render(&mut self, partial: bool) -> Result<Data> {\n\t\tNEED_RENDER.store(0, Ordering::Relaxed);\n\t\tlet Some(term) = &mut self.term else { succ!() };\n\n\t\tif partial {\n\t\t\treturn self.render_partially();\n\t\t}\n\n\t\tSelf::routine(true, None);\n\t\tlet _guard = scopeguard::guard(self.core.cursor(), |c| Self::routine(false, c));\n\n\t\tlet collision = COLLISION.swap(false, Ordering::Relaxed);\n\t\tlet preview_rect = LAYOUT.get().preview;\n\t\tlet frame = term.draw(|f| {\n\t\t\t_ = Lives::scope(&self.core, || {\n\t\t\t\truntime_scope!(LUA, \"root\", Ok(f.render_widget(Root::new(&self.core), f.area())))\n\t\t\t});\n\t\t})?;\n\n\t\tif COLLISION.load(Ordering::Relaxed) {\n\t\t\tSelf::patch(frame);\n\t\t}\n\t\tif !self.core.notify.messages.is_empty() {\n\t\t\tself.render_partially()?;\n\t\t}\n\n\t\tlet cx = &mut Ctx::active(&mut self.core, &mut self.term);\n\t\tif collision && !COLLISION.load(Ordering::Relaxed) {\n\t\t\tact!(mgr:peek, cx, true)?; // Reload preview if collision is resolved\n\t\t} else if preview_rect != LAYOUT.get().preview {\n\t\t\tact!(mgr:peek, cx)?; // Reload preview if layout changed\n\t\t}\n\t\tsucc!();\n\t}\n\n\tpub(crate) fn render_partially(&mut self) -> Result<Data> {\n\t\tlet Some(term) = &mut self.term else { succ!() };\n\t\tif !term.can_partial() {\n\t\t\treturn self.render(false);\n\t\t}\n\n\t\tSelf::routine(true, None);\n\t\tlet _guard = scopeguard::guard(self.core.cursor(), |c| Self::routine(false, c));\n\n\t\tlet frame = term.draw_partial(|f| {\n\t\t\t_ = Lives::scope(&self.core, || {\n\t\t\t\truntime_scope!(LUA, \"root\", {\n\t\t\t\t\tf.render_widget(crate::tasks::Progress::new(&self.core), f.area());\n\t\t\t\t\tf.render_widget(crate::notify::Notify::new(&self.core), f.area());\n\t\t\t\t\tOk(())\n\t\t\t\t})\n\t\t\t});\n\t\t})?;\n\n\t\tif COLLISION.load(Ordering::Relaxed) {\n\t\t\tSelf::patch(frame);\n\t\t}\n\t\tsucc!();\n\t}\n\n\t#[inline]\n\tfn patch(frame: CompletedFrame) {\n\t\tlet mut new = Buffer::empty(frame.area);\n\t\tfor y in new.area.top()..new.area.bottom() {\n\t\t\tfor x in new.area.left()..new.area.right() {\n\t\t\t\tlet cell = &frame.buffer[(x, y)];\n\t\t\t\tif cell.skip {\n\t\t\t\t\tnew[(x, y)] = cell.clone();\n\t\t\t\t}\n\t\t\t\tnew[(x, y)].set_skip(!cell.skip);\n\t\t\t}\n\t\t}\n\n\t\tlet patches = frame.buffer.diff(&new);\n\t\tCrosstermBackend::new(&mut *TTY.lockout()).draw(patches.into_iter()).ok();\n\t}\n\n\tfn routine(push: bool, cursor: Option<(Position, SetCursorStyle)>) {\n\t\tstatic COUNT: AtomicU8 = AtomicU8::new(0);\n\t\tif push && COUNT.fetch_add(1, Ordering::Relaxed) != 0 {\n\t\t\treturn;\n\t\t} else if !push && COUNT.fetch_sub(1, Ordering::Relaxed) != 1 {\n\t\t\treturn;\n\t\t}\n\n\t\t_ = if push {\n\t\t\tqueue!(TTY.writer(), BeginSynchronizedUpdate)\n\t\t} else if let Some((Position { x, y }, shape)) = cursor {\n\t\t\texecute!(TTY.writer(), shape, MoveTo(x, y), Show, EndSynchronizedUpdate)\n\t\t} else {\n\t\t\texecute!(TTY.writer(), EndSynchronizedUpdate)\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/cmp/cmp.rs",
    "content": "use std::path::MAIN_SEPARATOR_STR;\n\nuse ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, List, ListItem, Widget}};\nuse yazi_config::{THEME, popup::{Offset, Position}};\nuse yazi_core::Core;\nuse yazi_emulator::Dimension;\nuse yazi_shared::strand::StrandLike;\n\npub(crate) struct Cmp<'a> {\n\tcore: &'a Core,\n}\n\nimpl<'a> Cmp<'a> {\n\tpub(crate) fn new(core: &'a Core) -> Self { Self { core } }\n}\n\nimpl Widget for Cmp<'_> {\n\tfn render(self, rect: Rect, buf: &mut Buffer) {\n\t\tlet items: Vec<_> = self\n\t\t\t.core\n\t\t\t.cmp\n\t\t\t.window()\n\t\t\t.iter()\n\t\t\t.enumerate()\n\t\t\t.map(|(i, x)| {\n\t\t\t\tlet icon = if x.is_dir { &THEME.cmp.icon_folder } else { &THEME.cmp.icon_file };\n\t\t\t\tlet slash = if x.is_dir { MAIN_SEPARATOR_STR } else { \"\" };\n\n\t\t\t\tlet mut item = ListItem::new(format!(\" {icon} {}{slash}\", x.name.display()));\n\t\t\t\tif i == self.core.cmp.rel_cursor() {\n\t\t\t\t\titem = item.style(THEME.cmp.active);\n\t\t\t\t} else {\n\t\t\t\t\titem = item.style(THEME.cmp.inactive);\n\t\t\t\t}\n\n\t\t\t\titem\n\t\t\t})\n\t\t\t.collect();\n\n\t\tlet input_area = self.core.mgr.area(self.core.input.position);\n\t\tlet mut area = Position::sticky(Dimension::available().into(), input_area, Offset {\n\t\t\tx:      1,\n\t\t\ty:      0,\n\t\t\twidth:  input_area.width.saturating_sub(2),\n\t\t\theight: items.len() as u16 + 2,\n\t\t});\n\n\t\tif area.y > input_area.y {\n\t\t\tarea.y = area.y.saturating_sub(1);\n\t\t} else {\n\t\t\tarea.y = rect.height.min(area.y + 1);\n\t\t\tarea.height = rect.height.saturating_sub(area.y).min(area.height);\n\t\t}\n\n\t\tyazi_widgets::Clear.render(area, buf);\n\t\tList::new(items)\n\t\t\t.block(Block::bordered().border_type(BorderType::Rounded).border_style(THEME.cmp.border))\n\t\t\t.render(area, buf);\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/cmp/mod.rs",
    "content": "yazi_macro::mod_flat!(cmp);\n"
  },
  {
    "path": "yazi-fm/src/confirm/body.rs",
    "content": "use ratatui::{buffer::Buffer, layout::{Margin, Rect}, style::Styled, widgets::{Block, Borders, Widget}};\nuse yazi_config::THEME;\nuse yazi_core::Core;\n\npub(crate) struct Body<'a> {\n\tcore:   &'a Core,\n\tborder: bool,\n}\n\nimpl<'a> Body<'a> {\n\tpub(crate) fn new(core: &'a Core, border: bool) -> Self { Self { core, border } }\n}\n\nimpl Widget for Body<'_> {\n\tfn render(self, area: Rect, buf: &mut Buffer) {\n\t\tlet confirm = &self.core.confirm;\n\n\t\t// Inner area\n\t\tlet inner = area.inner(Margin::new(1, 0));\n\n\t\t// Border\n\t\tlet block = if self.border {\n\t\t\tBlock::new().borders(Borders::BOTTOM).border_style(THEME.confirm.border)\n\t\t} else {\n\t\t\tBlock::new()\n\t\t};\n\n\t\tconfirm\n\t\t\t.body\n\t\t\t.clone()\n\t\t\t.alignment(ratatui::layout::Alignment::Center)\n\t\t\t.block(block)\n\t\t\t.style(THEME.confirm.body.derive(Styled::style(&confirm.body)))\n\t\t\t.render(inner, buf);\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/confirm/buttons.rs",
    "content": "use ratatui::{buffer::Buffer, layout::{Constraint, Rect}, text::Span, widgets::{Paragraph, Widget}};\nuse yazi_config::THEME;\n\npub(crate) struct Buttons;\n\nimpl Widget for Buttons {\n\tfn render(self, area: Rect, buf: &mut Buffer) {\n\t\tlet chunks =\n\t\t\tratatui::layout::Layout::horizontal([Constraint::Fill(1), Constraint::Fill(1)]).split(area);\n\n\t\tParagraph::new(Span::raw(&THEME.confirm.btn_labels[0]).style(THEME.confirm.btn_yes))\n\t\t\t.centered()\n\t\t\t.render(chunks[0], buf);\n\t\tParagraph::new(Span::raw(&THEME.confirm.btn_labels[1]).style(THEME.confirm.btn_no))\n\t\t\t.centered()\n\t\t\t.render(chunks[1], buf);\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/confirm/confirm.rs",
    "content": "use ratatui::{buffer::Buffer, layout::{Alignment, Constraint, Layout, Margin, Rect}, widgets::{Block, BorderType, Widget}};\nuse yazi_config::THEME;\nuse yazi_core::Core;\n\npub(crate) struct Confirm<'a> {\n\tcore: &'a Core,\n}\n\nimpl<'a> Confirm<'a> {\n\tpub(crate) fn new(core: &'a Core) -> Self { Self { core } }\n}\n\nimpl Widget for Confirm<'_> {\n\tfn render(self, _: Rect, buf: &mut Buffer) {\n\t\tlet confirm = &self.core.confirm;\n\t\tlet area = self.core.mgr.area(confirm.position);\n\n\t\tyazi_widgets::Clear.render(area, buf);\n\n\t\tBlock::bordered()\n\t\t\t.border_type(BorderType::Rounded)\n\t\t\t.border_style(THEME.confirm.border)\n\t\t\t.title(confirm.title.clone().style(THEME.confirm.title.derive(confirm.title.style)))\n\t\t\t.title_alignment(Alignment::Center)\n\t\t\t.render(area, buf);\n\n\t\tlet body_border = confirm.list.line_count(area.width) != 0;\n\t\tlet body_height = confirm.body.line_count(area.width) as u16;\n\n\t\tlet chunks = Layout::vertical([\n\t\t\tConstraint::Length(if body_height == 0 {\n\t\t\t\t0\n\t\t\t} else {\n\t\t\t\tbody_height.saturating_add(body_border as u16)\n\t\t\t}),\n\t\t\tConstraint::Fill(1),\n\t\t\tConstraint::Length(1),\n\t\t])\n\t\t.split(area.inner(Margin::new(0, 1)));\n\n\t\tsuper::Body::new(self.core, body_border).render(chunks[0], buf);\n\t\tsuper::List::new(self.core).render(chunks[1], buf);\n\t\tsuper::Buttons.render(chunks[2], buf);\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/confirm/list.rs",
    "content": "use ratatui::{buffer::Buffer, layout::{Margin, Rect}, widgets::{Block, Borders, Scrollbar, ScrollbarOrientation, ScrollbarState, StatefulWidget, Widget, Wrap}};\nuse yazi_config::THEME;\nuse yazi_core::Core;\n\npub(crate) struct List<'a> {\n\tcore: &'a Core,\n}\n\nimpl<'a> List<'a> {\n\tpub(crate) fn new(core: &'a Core) -> Self { Self { core } }\n}\n\nimpl Widget for List<'_> {\n\tfn render(self, mut area: Rect, buf: &mut Buffer) {\n\t\t// List content area\n\t\tlet inner = area.inner(Margin::new(2, 0));\n\n\t\t// Bottom border\n\t\tlet block = Block::new().borders(Borders::BOTTOM).border_style(THEME.confirm.border);\n\t\tblock.clone().render(area.inner(Margin::new(1, 0)), buf);\n\n\t\tlet list = self\n\t\t\t.core\n\t\t\t.confirm\n\t\t\t.list\n\t\t\t.clone()\n\t\t\t.scroll((self.core.confirm.offset as u16, 0))\n\t\t\t.block(block)\n\t\t\t.style(THEME.confirm.list)\n\t\t\t.wrap(Wrap { trim: false });\n\n\t\t// Vertical scrollbar\n\t\tlet lines = list.line_count(inner.width);\n\t\tif lines >= inner.height as usize {\n\t\t\tarea.height = area.height.saturating_sub(1);\n\t\t\tScrollbar::new(ScrollbarOrientation::VerticalRight).render(\n\t\t\t\tarea,\n\t\t\t\tbuf,\n\t\t\t\t&mut ScrollbarState::new(lines).position(self.core.confirm.offset),\n\t\t\t);\n\t\t}\n\n\t\tlist.render(inner, buf);\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/confirm/mod.rs",
    "content": "yazi_macro::mod_flat!(buttons confirm body list);\n"
  },
  {
    "path": "yazi-fm/src/dispatcher.rs",
    "content": "use std::sync::atomic::Ordering;\n\nuse anyhow::Result;\nuse crossterm::event::{KeyEvent, MouseEvent};\nuse tokio::sync::mpsc;\nuse tracing::warn;\nuse yazi_actor::Ctx;\nuse yazi_config::keymap::Key;\nuse yazi_macro::{act, emit};\nuse yazi_shared::{data::Data, event::{ActionCow, Event, NEED_RENDER}};\nuse yazi_widgets::input::InputMode;\n\nuse crate::{Executor, Router, app::App};\n\npub(super) struct Dispatcher<'a> {\n\tapp: &'a mut App,\n}\n\nimpl<'a> Dispatcher<'a> {\n\t#[inline]\n\tpub(super) fn new(app: &'a mut App) -> Self { Self { app } }\n\n\t#[inline]\n\tpub(super) fn dispatch(&mut self, event: Event) {\n\t\tlet result = match event {\n\t\t\tEvent::Call(action) => self.dispatch_call(action),\n\t\t\tEvent::Seq(actions) => self.dispatch_seq(actions),\n\t\t\tEvent::Render(partial) => self.dispatch_render(partial),\n\t\t\tEvent::Key(key) => self.dispatch_key(key),\n\t\t\tEvent::Mouse(mouse) => self.dispatch_mouse(mouse),\n\t\t\tEvent::Resize => self.dispatch_resize(),\n\t\t\tEvent::Focus => self.dispatch_focus(),\n\t\t\tEvent::Paste(str) => self.dispatch_paste(str),\n\t\t};\n\n\t\tif let Err(e) = &result {\n\t\t\twarn!(\"Event dispatch error: {e:?}\");\n\t\t}\n\t}\n\n\tfn dispatch_call(&mut self, action: ActionCow) -> Result<()> {\n\t\tlet tx = action.any::<mpsc::UnboundedSender<Result<Data>>>(\"__reply\").cloned();\n\t\tlet result = Executor::new(self.app).execute(action);\n\n\t\tif let Err(e) = &result {\n\t\t\twarn!(\"Call dispatch error: {e:?}\");\n\t\t}\n\t\tif let Some(tx) = tx {\n\t\t\ttx.send(result).ok();\n\t\t}\n\t\tOk(())\n\t}\n\n\t#[inline]\n\tfn dispatch_seq(&mut self, mut actions: Vec<ActionCow>) -> Result<()> {\n\t\tif let Some(last) = actions.pop() {\n\t\t\tself.dispatch_call(last)?;\n\t\t}\n\t\tif !actions.is_empty() {\n\t\t\temit!(Seq(actions));\n\t\t}\n\t\tOk(())\n\t}\n\n\t#[inline]\n\tfn dispatch_render(&mut self, partial: bool) -> Result<()> {\n\t\tif partial {\n\t\t\t_ = NEED_RENDER.compare_exchange(0, 2, Ordering::Relaxed, Ordering::Relaxed);\n\t\t} else {\n\t\t\tNEED_RENDER.store(1, Ordering::Relaxed);\n\t\t}\n\t\tOk(())\n\t}\n\n\t#[inline]\n\tfn dispatch_key(&mut self, key: KeyEvent) -> Result<()> {\n\t\tRouter::new(self.app).route(Key::from(key))?;\n\t\tOk(())\n\t}\n\n\t#[inline]\n\tfn dispatch_mouse(&mut self, mouse: MouseEvent) -> Result<()> {\n\t\tlet cx = &mut Ctx::active(&mut self.app.core, &mut self.app.term);\n\t\tact!(app:mouse, cx, mouse).map(|_| ())\n\t}\n\n\t#[inline]\n\tfn dispatch_resize(&mut self) -> Result<()> {\n\t\tlet cx = &mut Ctx::active(&mut self.app.core, &mut self.app.term);\n\t\tact!(app:resize, cx, crate::Root::reflow as fn(_) -> _).map(|_| ())\n\t}\n\n\t#[inline]\n\tfn dispatch_focus(&mut self) -> Result<()> {\n\t\tlet cx = &mut Ctx::active(&mut self.app.core, &mut self.app.term);\n\t\tact!(app:focus, cx).map(|_| ())\n\t}\n\n\t#[inline]\n\tfn dispatch_paste(&mut self, str: String) -> Result<()> {\n\t\tif self.app.core.input.visible {\n\t\t\tlet input = &mut self.app.core.input;\n\t\t\tif input.mode() == InputMode::Insert {\n\t\t\t\tinput.type_str(&str)?;\n\t\t\t} else if input.mode() == InputMode::Replace {\n\t\t\t\tinput.replace_str(&str)?;\n\t\t\t}\n\t\t}\n\t\tOk(())\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/executor.rs",
    "content": "use anyhow::{Context, Result};\nuse yazi_actor::Ctx;\nuse yazi_macro::{act, succ};\nuse yazi_shared::{Layer, data::Data, event::ActionCow};\nuse yazi_widgets::input::InputMode;\n\nuse crate::app::App;\n\npub(super) struct Executor<'a> {\n\tapp: &'a mut App,\n}\n\nimpl<'a> Executor<'a> {\n\t#[inline]\n\tpub(super) fn new(app: &'a mut App) -> Self { Self { app } }\n\n\tpub(super) fn execute(&mut self, action: ActionCow) -> Result<Data> {\n\t\tmatch action.layer {\n\t\t\tLayer::App => self.app(action),\n\t\t\tLayer::Mgr => self.mgr(action),\n\t\t\tLayer::Tasks => self.tasks(action),\n\t\t\tLayer::Spot => self.spot(action),\n\t\t\tLayer::Pick => self.pick(action),\n\t\t\tLayer::Input => self.input(action),\n\t\t\tLayer::Confirm => self.confirm(action),\n\t\t\tLayer::Help => self.help(action),\n\t\t\tLayer::Cmp => self.cmp(action),\n\t\t\tLayer::Which => self.which(action),\n\t\t\tLayer::Notify => self.notify(action),\n\t\t}\n\t}\n\n\tfn app(&mut self, mut action: ActionCow) -> Result<Data> {\n\t\tlet cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;\n\n\t\tmacro_rules! on {\n\t\t\t($name:ident) => {\n\t\t\t\tif action.name == stringify!($name) {\n\t\t\t\t\treturn act!(app:$name, cx, action);\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\ton!(accept_payload);\n\t\ton!(plugin);\n\t\ton!(plugin_do);\n\t\ton!(update_progress);\n\t\ton!(deprecate);\n\t\ton!(quit);\n\n\t\tmatch &*action.name {\n\t\t\t\"resize\" => act!(app:resize, cx, crate::Root::reflow as fn(_) -> _),\n\t\t\t\"resume\" => act!(app:resume, cx, yazi_parser::app::ResumeOpt {\n\t\t\t\ttx: self.app.signals.tx.clone(),\n\t\t\t\ttoken: action.take_any(\"token\").context(\"Invalid 'token' in ResumeOpt\")?,\n\t\t\t\treflow: crate::Root::reflow,\n\t\t\t}),\n\t\t\t\"stop\" => act!(app:stop, cx, yazi_parser::app::StopOpt {\n\t\t\t\ttx: self.app.signals.tx.clone(),\n\t\t\t\ttoken: action.take_any(\"token\").context(\"Invalid 'token' in StopOpt\")?,\n\t\t\t}),\n\t\t\t_ => succ!(),\n\t\t}\n\t}\n\n\tfn mgr(&mut self, action: ActionCow) -> Result<Data> {\n\t\tlet cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;\n\n\t\tmacro_rules! on {\n\t\t\t($name:ident) => {\n\t\t\t\tif action.name == stringify!($name) {\n\t\t\t\t\treturn act!(mgr:$name, cx, action)\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\ton!(cd);\n\t\ton!(update_yanked);\n\n\t\ton!(update_files);\n\t\ton!(update_mimes);\n\t\ton!(update_paged);\n\t\ton!(watch);\n\t\ton!(peek);\n\t\ton!(seek);\n\t\ton!(spot);\n\t\ton!(refresh);\n\t\ton!(quit);\n\t\ton!(close);\n\t\ton!(suspend);\n\t\ton!(escape);\n\t\ton!(update_peeked);\n\t\ton!(update_spotted);\n\n\t\t// Navigation\n\t\ton!(arrow);\n\t\ton!(leave);\n\t\ton!(enter);\n\t\ton!(back);\n\t\ton!(forward);\n\t\ton!(reveal);\n\t\ton!(follow);\n\n\t\t// Toggle\n\t\ton!(toggle);\n\t\ton!(toggle_all);\n\t\ton!(visual_mode);\n\n\t\t// Operation\n\t\ton!(open);\n\t\ton!(open_do);\n\t\ton!(yank);\n\t\ton!(unyank);\n\t\ton!(paste);\n\t\ton!(link);\n\t\ton!(hardlink);\n\t\ton!(remove);\n\t\ton!(remove_do);\n\t\ton!(create);\n\t\ton!(rename);\n\t\ton!(copy);\n\t\ton!(shell);\n\t\ton!(hidden);\n\t\ton!(linemode);\n\t\ton!(search);\n\t\ton!(search_do);\n\t\ton!(bulk_rename);\n\n\t\t// Filter\n\t\ton!(filter);\n\t\ton!(filter_do);\n\n\t\t// Find\n\t\ton!(find);\n\t\ton!(find_do);\n\t\ton!(find_arrow);\n\n\t\t// Sorting\n\t\ton!(sort);\n\n\t\t// Tabs\n\t\ton!(tab_create);\n\t\ton!(tab_rename);\n\t\ton!(tab_close);\n\t\ton!(tab_switch);\n\t\ton!(tab_swap);\n\n\t\t// VFS\n\t\ton!(download);\n\t\ton!(upload);\n\t\ton!(displace_do);\n\n\t\tmatch action.name.as_ref() {\n\t\t\t// Help\n\t\t\t\"help\" => act!(help:toggle, cx, Layer::Mgr),\n\t\t\t// Plugin\n\t\t\t\"plugin\" => act!(app:plugin, cx, action),\n\t\t\t_ => succ!(),\n\t\t}\n\t}\n\n\tfn tasks(&mut self, action: ActionCow) -> Result<Data> {\n\t\tlet cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;\n\n\t\tmacro_rules! on {\n\t\t\t($name:ident) => {\n\t\t\t\tif action.name == stringify!($name) {\n\t\t\t\t\treturn act!(tasks:$name, cx, action);\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\ton!(update_succeed);\n\n\t\ton!(show);\n\t\ton!(close);\n\t\ton!(arrow);\n\t\ton!(inspect);\n\t\ton!(cancel);\n\t\ton!(process_open);\n\t\ton!(open_shell_compat);\n\n\t\tmatch action.name.as_ref() {\n\t\t\t// Help\n\t\t\t\"help\" => act!(help:toggle, cx, Layer::Tasks),\n\t\t\t// Plugin\n\t\t\t\"plugin\" => act!(app:plugin, cx, action),\n\t\t\t_ => succ!(),\n\t\t}\n\t}\n\n\tfn spot(&mut self, action: ActionCow) -> Result<Data> {\n\t\tlet cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;\n\n\t\tmacro_rules! on {\n\t\t\t($name:ident) => {\n\t\t\t\tif action.name == stringify!($name) {\n\t\t\t\t\treturn act!(spot:$name, cx, action);\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\ton!(arrow);\n\t\ton!(close);\n\t\ton!(swipe);\n\t\ton!(copy);\n\n\t\tmatch action.name.as_ref() {\n\t\t\t// Help\n\t\t\t\"help\" => act!(help:toggle, cx, Layer::Spot),\n\t\t\t// Plugin\n\t\t\t\"plugin\" => act!(app:plugin, cx, action),\n\t\t\t_ => succ!(),\n\t\t}\n\t}\n\n\tfn pick(&mut self, action: ActionCow) -> Result<Data> {\n\t\tlet cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;\n\n\t\tmacro_rules! on {\n\t\t\t($name:ident) => {\n\t\t\t\tif action.name == stringify!($name) {\n\t\t\t\t\treturn act!(pick:$name, cx, action);\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\ton!(show);\n\t\ton!(close);\n\t\ton!(arrow);\n\n\t\tmatch action.name.as_ref() {\n\t\t\t// Help\n\t\t\t\"help\" => act!(help:toggle, cx, Layer::Pick),\n\t\t\t// Plugin\n\t\t\t\"plugin\" => act!(app:plugin, cx, action),\n\t\t\t_ => succ!(),\n\t\t}\n\t}\n\n\tfn input(&mut self, action: ActionCow) -> Result<Data> {\n\t\tlet mode = self.app.core.input.mode();\n\t\tlet cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;\n\n\t\tmacro_rules! on {\n\t\t\t($name:ident) => {\n\t\t\t\tif action.name == stringify!($name) {\n\t\t\t\t\treturn act!(input:$name, cx, action);\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\ton!(escape);\n\t\ton!(show);\n\t\ton!(close);\n\n\t\tmatch mode {\n\t\t\tInputMode::Normal => {\n\t\t\t\tmatch action.name.as_ref() {\n\t\t\t\t\t// Help\n\t\t\t\t\t\"help\" => return act!(help:toggle, cx, Layer::Input),\n\t\t\t\t\t// Plugin\n\t\t\t\t\t\"plugin\" => return act!(app:plugin, cx, action),\n\t\t\t\t\t_ => {}\n\t\t\t\t}\n\t\t\t}\n\t\t\tInputMode::Insert => {\n\t\t\t\ton!(complete);\n\t\t\t}\n\t\t\tInputMode::Replace => {}\n\t\t};\n\n\t\tself.app.core.input.execute(action)\n\t}\n\n\tfn confirm(&mut self, action: ActionCow) -> Result<Data> {\n\t\tlet cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;\n\n\t\tmacro_rules! on {\n\t\t\t($name:ident) => {\n\t\t\t\tif action.name == stringify!($name) {\n\t\t\t\t\treturn act!(confirm:$name, cx, action);\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\ton!(arrow);\n\t\ton!(show);\n\t\ton!(close);\n\n\t\tsucc!();\n\t}\n\n\tfn help(&mut self, action: ActionCow) -> Result<Data> {\n\t\tlet cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;\n\n\t\tmacro_rules! on {\n\t\t\t($name:ident) => {\n\t\t\t\tif action.name == stringify!($name) {\n\t\t\t\t\treturn act!(help:$name, cx, action);\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\ton!(escape);\n\t\ton!(arrow);\n\t\ton!(filter);\n\n\t\tmatch action.name.as_ref() {\n\t\t\t// Help\n\t\t\t\"close\" => act!(help:toggle, cx, Layer::Help),\n\t\t\t// Plugin\n\t\t\t\"plugin\" => act!(app:plugin, cx, action),\n\t\t\t_ => succ!(),\n\t\t}\n\t}\n\n\tfn cmp(&mut self, action: ActionCow) -> Result<Data> {\n\t\tlet cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;\n\n\t\tmacro_rules! on {\n\t\t\t($name:ident) => {\n\t\t\t\tif action.name == stringify!($name) {\n\t\t\t\t\treturn act!(cmp:$name, cx, action);\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\ton!(trigger);\n\t\ton!(show);\n\t\ton!(close);\n\t\ton!(arrow);\n\n\t\tmatch action.name.as_ref() {\n\t\t\t// Help\n\t\t\t\"help\" => act!(help:toggle, cx, Layer::Cmp),\n\t\t\t// Plugin\n\t\t\t\"plugin\" => act!(app:plugin, cx, action),\n\t\t\t_ => succ!(),\n\t\t}\n\t}\n\n\tfn which(&mut self, action: ActionCow) -> Result<Data> {\n\t\tlet cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;\n\n\t\tmacro_rules! on {\n\t\t\t($name:ident) => {\n\t\t\t\tif action.name == stringify!($name) {\n\t\t\t\t\treturn act!(which:$name, cx, action);\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\ton!(activate);\n\t\ton!(dismiss);\n\n\t\tsucc!();\n\t}\n\n\tfn notify(&mut self, action: ActionCow) -> Result<Data> {\n\t\tlet cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;\n\n\t\tmacro_rules! on {\n\t\t\t($name:ident) => {\n\t\t\t\tif action.name == stringify!($name) {\n\t\t\t\t\treturn act!(notify:$name, cx, action);\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\ton!(push);\n\t\ton!(tick);\n\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/help/bindings.rs",
    "content": "use ratatui::{buffer::Buffer, layout::{self, Constraint, Rect}, widgets::{List, ListItem, Widget}};\nuse yazi_config::THEME;\nuse yazi_core::Core;\n\npub(super) struct Bindings<'a> {\n\tcore: &'a Core,\n}\n\nimpl<'a> Bindings<'a> {\n\tpub(super) fn new(core: &'a Core) -> Self { Self { core } }\n}\n\nimpl Widget for Bindings<'_> {\n\tfn render(self, area: Rect, buf: &mut Buffer) {\n\t\tlet bindings = &self.core.help.window();\n\t\tif bindings.is_empty() {\n\t\t\treturn;\n\t\t}\n\n\t\t// On\n\t\tlet col1: Vec<_> =\n\t\t\tbindings.iter().map(|c| ListItem::new(c.on()).style(THEME.help.on)).collect();\n\n\t\t// Run\n\t\tlet col2: Vec<_> =\n\t\t\tbindings.iter().map(|c| ListItem::new(c.run()).style(THEME.help.run)).collect();\n\n\t\t// Desc\n\t\tlet col3: Vec<_> = bindings\n\t\t\t.iter()\n\t\t\t.map(|c| ListItem::new(c.desc().unwrap_or(\"-\".into())).style(THEME.help.desc))\n\t\t\t.collect();\n\n\t\tlet chunks = layout::Layout::horizontal([\n\t\t\tConstraint::Ratio(2, 10),\n\t\t\tConstraint::Ratio(3, 10),\n\t\t\tConstraint::Ratio(5, 10),\n\t\t])\n\t\t.split(area);\n\n\t\tlet cursor = self.core.help.rel_cursor() as u16;\n\t\tbuf.set_style(\n\t\t\tRect { x: area.x, y: area.y + cursor, width: area.width, height: 1 },\n\t\t\tTHEME.help.hovered,\n\t\t);\n\n\t\tList::new(col1).render(chunks[0], buf);\n\t\tList::new(col2).render(chunks[1], buf);\n\t\tList::new(col3).render(chunks[2], buf);\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/help/help.rs",
    "content": "use ratatui::{buffer::Buffer, layout::{self, Constraint, Rect}, text::Line, widgets::Widget};\nuse yazi_config::{KEYMAP, THEME};\nuse yazi_core::Core;\n\nuse super::Bindings;\n\npub(crate) struct Help<'a> {\n\tcore: &'a Core,\n}\n\nimpl<'a> Help<'a> {\n\tpub fn new(core: &'a Core) -> Self { Self { core } }\n\n\tfn tips() -> String {\n\t\tmatch KEYMAP.help.iter().find(|&c| c.run.iter().any(|c| c.name == \"filter\")) {\n\t\t\tSome(c) => format!(\" (Press `{}` to filter)\", c.on()),\n\t\t\tNone => String::new(),\n\t\t}\n\t}\n}\n\nimpl Widget for Help<'_> {\n\tfn render(self, area: Rect, buf: &mut Buffer) {\n\t\tlet help = &self.core.help;\n\t\tyazi_widgets::Clear.render(area, buf);\n\n\t\tlet chunks = layout::Layout::vertical([Constraint::Fill(1), Constraint::Length(1)]).split(area);\n\t\tLine::styled(\n\t\t\thelp.keyword().unwrap_or_else(|| format!(\"{}.help{}\", help.layer, Self::tips())),\n\t\t\tTHEME.help.footer,\n\t\t)\n\t\t.render(chunks[1], buf);\n\n\t\tBindings::new(self.core).render(chunks[0], buf);\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/help/mod.rs",
    "content": "yazi_macro::mod_flat!(bindings help);\n"
  },
  {
    "path": "yazi-fm/src/input/input.rs",
    "content": "use ratatui::{buffer::Buffer, layout::{Margin, Rect}, text::Line, widgets::{Block, BorderType, Widget}};\nuse yazi_config::THEME;\nuse yazi_core::Core;\n\npub(crate) struct Input<'a> {\n\tcore: &'a Core,\n}\n\nimpl<'a> Input<'a> {\n\tpub(crate) fn new(core: &'a Core) -> Self { Self { core } }\n}\n\nimpl Widget for Input<'_> {\n\tfn render(self, _: Rect, buf: &mut Buffer) {\n\t\tlet input = &self.core.input;\n\t\tlet area = self.core.mgr.area(input.position);\n\n\t\tyazi_widgets::Clear.render(area, buf);\n\n\t\tBlock::bordered()\n\t\t\t.border_type(BorderType::Rounded)\n\t\t\t.border_style(THEME.input.border)\n\t\t\t.title(Line::styled(&input.title, THEME.input.title))\n\t\t\t.render(area, buf);\n\n\t\tinput.render(area.inner(Margin::new(1, 1)), buf);\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/input/mod.rs",
    "content": "yazi_macro::mod_flat!(input);\n"
  },
  {
    "path": "yazi-fm/src/logs.rs",
    "content": "use std::fs::File;\n\nuse anyhow::Context;\nuse crossterm::style::{Color, Print, ResetColor, SetForegroundColor};\nuse tracing_appender::non_blocking::WorkerGuard;\nuse tracing_subscriber::EnvFilter;\nuse yazi_fs::Xdg;\nuse yazi_shared::{LOG_LEVEL, RoCell};\n\nstatic _GUARD: RoCell<WorkerGuard> = RoCell::new();\n\npub(super) struct Logs;\n\nimpl Logs {\n\tpub(super) fn start() -> anyhow::Result<()> {\n\t\tlet level = LOG_LEVEL.get();\n\t\tif level.is_none() {\n\t\t\treturn Ok(());\n\t\t}\n\n\t\tlet state_dir = Xdg::state_dir();\n\t\tstd::fs::create_dir_all(&state_dir)\n\t\t\t.with_context(|| format!(\"failed to create state directory: {state_dir:?}\"))?;\n\n\t\tlet log_path = state_dir.join(\"yazi.log\");\n\t\tlet log_file = File::create(&log_path)\n\t\t\t.with_context(|| format!(\"failed to create log file: {log_path:?}\"))?;\n\n\t\tlet (non_blocking, guard) = tracing_appender::non_blocking(log_file);\n\t\ttracing_subscriber::fmt()\n\t\t\t.pretty()\n\t\t\t.with_env_filter(EnvFilter::new(level))\n\t\t\t.with_writer(non_blocking)\n\t\t\t.with_ansi(cfg!(debug_assertions))\n\t\t\t.init();\n\n\t\t_GUARD.init(guard);\n\t\tOk(crossterm::execute!(\n\t\t\tstd::io::stderr(),\n\t\t\tSetForegroundColor(Color::Yellow),\n\t\t\tPrint(format!(\"Running with log level `{level}`, logs are written to {log_path:?}\\n\")),\n\t\t\tResetColor\n\t\t)?)\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/main.rs",
    "content": "#[cfg(all(not(target_os = \"macos\"), not(target_os = \"windows\")))]\n#[global_allocator]\nstatic GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;\n\nyazi_macro::mod_pub!(app cmp confirm help input mgr notify pick spot tasks which);\n\nyazi_macro::mod_flat!(dispatcher executor logs panic root router signals);\n\n#[tokio::main]\nasync fn main() -> anyhow::Result<()> {\n\tPanic::install();\n\tyazi_shared::init();\n\n\tLogs::start()?;\n\t_ = fdlimit::raise_fd_limit();\n\n\tyazi_tty::init();\n\n\tyazi_term::init();\n\n\tyazi_fs::init();\n\n\tyazi_config::init()?;\n\n\tyazi_vfs::init();\n\n\tyazi_adapter::init()?;\n\n\tyazi_boot::init();\n\n\tyazi_dds::init();\n\n\tyazi_widgets::init();\n\n\tyazi_watcher::init();\n\n\tyazi_plugin::init()?;\n\n\tyazi_dds::serve();\n\n\tyazi_shared::LOCAL_SET.run_until(app::App::serve()).await\n}\n"
  },
  {
    "path": "yazi-fm/src/mgr/mod.rs",
    "content": "yazi_macro::mod_flat!(modal preview);\n"
  },
  {
    "path": "yazi-fm/src/mgr/modal.rs",
    "content": "use mlua::{ObjectLike, Table};\nuse ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};\nuse tracing::error;\nuse yazi_binding::elements::render_once;\nuse yazi_core::Core;\nuse yazi_plugin::LUA;\n\npub(crate) struct Modal<'a> {\n\tcore: &'a Core,\n}\n\nimpl<'a> Modal<'a> {\n\t#[inline]\n\tpub(crate) fn new(core: &'a Core) -> Self { Self { core } }\n}\n\nimpl Widget for Modal<'_> {\n\tfn render(self, area: Rect, buf: &mut Buffer) {\n\t\tlet mut f = || {\n\t\t\tlet area = yazi_binding::elements::Rect::from(area);\n\t\t\tlet root = LUA.globals().raw_get::<Table>(\"Modal\")?.call_method::<Table>(\"new\", area)?;\n\n\t\t\trender_once(root.call_method(\"children_redraw\", ())?, buf, |p| self.core.mgr.area(p));\n\t\t\tOk::<_, mlua::Error>(())\n\t\t};\n\t\tif let Err(e) = f() {\n\t\t\terror!(\"Failed to redraw the `Modal` component:\\n{e}\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/mgr/preview.rs",
    "content": "use ratatui::{buffer::Buffer, widgets::Widget};\nuse yazi_config::LAYOUT;\nuse yazi_core::Core;\n\npub(crate) struct Preview<'a> {\n\tcore: &'a Core,\n}\n\nimpl<'a> Preview<'a> {\n\t#[inline]\n\tpub(crate) fn new(core: &'a Core) -> Self { Self { core } }\n}\n\nimpl Widget for Preview<'_> {\n\tfn render(self, win: ratatui::layout::Rect, buf: &mut Buffer) {\n\t\tlet Some(lock) = &self.core.active().preview.lock else {\n\t\t\treturn;\n\t\t};\n\n\t\tif *lock.area != LAYOUT.get().preview {\n\t\t\treturn;\n\t\t}\n\n\t\tfor w in &lock.data {\n\t\t\tlet rect = w.area().transform(|p| self.core.mgr.area(p));\n\t\t\tif rect.intersection(win) == rect {\n\t\t\t\tw.render(rect, buf);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/notify/mod.rs",
    "content": "yazi_macro::mod_flat!(notify);\n"
  },
  {
    "path": "yazi-fm/src/notify/notify.rs",
    "content": "use ratatui::{buffer::Buffer, layout::{self, Constraint, Offset, Rect}, widgets::{Block, BorderType, Paragraph, Widget, Wrap}};\nuse yazi_core::{Core, notify::Message};\n\npub(crate) struct Notify<'a> {\n\tcore: &'a Core,\n}\n\nimpl<'a> Notify<'a> {\n\tpub(crate) fn new(core: &'a Core) -> Self { Self { core } }\n\n\tfn tiles<'m>(area: Rect, messages: impl Iterator<Item = &'m Message> + Clone) -> Vec<Rect> {\n\t\tlayout::Layout::vertical(\n\t\t\t[Constraint::Fill(1)]\n\t\t\t\t.into_iter()\n\t\t\t\t.chain(messages.clone().map(|m| Constraint::Length(m.height(area.width) as u16))),\n\t\t)\n\t\t.spacing(1)\n\t\t.split(area)\n\t\t.iter()\n\t\t.skip(1)\n\t\t.zip(messages)\n\t\t.map(|(&(mut r), m)| {\n\t\t\tif r.width > m.max_width as u16 {\n\t\t\t\tr.x = r.x.saturating_add(r.width - m.max_width as u16);\n\t\t\t\tr.width = m.max_width as u16;\n\t\t\t}\n\t\t\tr\n\t\t})\n\t\t.collect()\n\t}\n}\n\nimpl Widget for Notify<'_> {\n\tfn render(self, area: Rect, buf: &mut Buffer) {\n\t\tlet notify = &self.core.notify;\n\t\tlet available = yazi_core::notify::Notify::available(area);\n\n\t\tlet messages = notify.messages.iter().take(notify.limit(available)).rev();\n\t\tlet tiles = Self::tiles(available, messages.clone());\n\n\t\tfor (i, m) in messages.enumerate() {\n\t\t\tlet mut rect =\n\t\t\t\ttiles[i].offset(Offset { x: (100 - m.percent) as i32 * tiles[i].width as i32 / 100, y: 0 });\n\t\t\trect.width -= rect.x - tiles[i].x;\n\n\t\t\tyazi_widgets::Clear.render(rect, buf);\n\t\t\tParagraph::new(m.content.as_str())\n\t\t\t\t.wrap(Wrap { trim: false })\n\t\t\t\t.block(\n\t\t\t\t\tBlock::bordered()\n\t\t\t\t\t\t.border_type(BorderType::Rounded)\n\t\t\t\t\t\t.title(format!(\"{} {}\", m.level.icon(), m.title))\n\t\t\t\t\t\t.title_style(m.level.style())\n\t\t\t\t\t\t.border_style(m.level.style()),\n\t\t\t\t)\n\t\t\t\t.render(rect, buf);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/panic.rs",
    "content": "use yazi_term::Term;\n\npub(super) struct Panic;\n\nimpl Panic {\n\tpub(super) fn install() {\n\t\tbetter_panic::install();\n\n\t\tlet hook = std::panic::take_hook();\n\t\tstd::panic::set_hook(Box::new(move |info| {\n\t\t\tTerm::goodbye(|| {\n\t\t\t\thook(info);\n\t\t\t\t1\n\t\t\t});\n\t\t}));\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/pick/list.rs",
    "content": "use ratatui::{buffer::Buffer, layout::{Margin, Rect}, widgets::{ListItem, Scrollbar, ScrollbarOrientation, ScrollbarState, StatefulWidget, Widget}};\nuse yazi_config::THEME;\nuse yazi_core::Core;\nuse yazi_widgets::Scrollable;\n\npub(crate) struct List<'a> {\n\tcore: &'a Core,\n}\n\nimpl<'a> List<'a> {\n\tpub(crate) fn new(core: &'a Core) -> Self { Self { core } }\n}\n\nimpl Widget for List<'_> {\n\tfn render(self, area: Rect, buf: &mut Buffer) {\n\t\tlet pick = &self.core.pick;\n\n\t\t// Vertical scrollbar\n\t\tif pick.total() > pick.limit() {\n\t\t\tScrollbar::new(ScrollbarOrientation::VerticalRight).render(\n\t\t\t\tarea,\n\t\t\t\tbuf,\n\t\t\t\t&mut ScrollbarState::new(pick.total()).position(pick.cursor),\n\t\t\t);\n\t\t}\n\n\t\t// List content\n\t\tlet inner = area.inner(Margin::new(1, 0));\n\t\tlet items = pick.window().map(|(i, v)| {\n\t\t\tif i == pick.cursor {\n\t\t\t\tListItem::new(format!(\" {v}\")).style(THEME.pick.active)\n\t\t\t} else {\n\t\t\t\tListItem::new(format!(\"  {v}\")).style(THEME.pick.inactive)\n\t\t\t}\n\t\t});\n\t\tWidget::render(ratatui::widgets::List::new(items), inner, buf);\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/pick/mod.rs",
    "content": "yazi_macro::mod_flat!(list pick);\n"
  },
  {
    "path": "yazi-fm/src/pick/pick.rs",
    "content": "use ratatui::{buffer::Buffer, layout::{Margin, Rect}, widgets::{Block, BorderType, Widget}};\nuse yazi_config::THEME;\nuse yazi_core::Core;\n\nuse crate::pick::List;\n\npub(crate) struct Pick<'a> {\n\tcore: &'a Core,\n}\n\nimpl<'a> Pick<'a> {\n\tpub(crate) fn new(core: &'a Core) -> Self { Self { core } }\n}\n\nimpl Widget for Pick<'_> {\n\tfn render(self, _: Rect, buf: &mut Buffer) {\n\t\tlet pick = &self.core.pick;\n\t\tlet area = self.core.mgr.area(pick.position);\n\n\t\tyazi_widgets::Clear.render(area, buf);\n\n\t\tBlock::bordered()\n\t\t\t.title(pick.title())\n\t\t\t.border_type(BorderType::Rounded)\n\t\t\t.border_style(THEME.pick.border)\n\t\t\t.render(area, buf);\n\n\t\tList::new(self.core).render(area.inner(Margin::new(0, 1)), buf);\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/root.rs",
    "content": "use mlua::{ObjectLike, Table};\nuse ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};\nuse tracing::error;\nuse yazi_binding::elements::render_once;\nuse yazi_core::Core;\nuse yazi_plugin::LUA;\n\nuse super::{cmp, confirm, help, input, mgr, pick, spot, tasks, which};\n\npub(super) struct Root<'a> {\n\tcore: &'a Core,\n}\n\nimpl<'a> Root<'a> {\n\tpub(super) fn new(core: &'a Core) -> Self { Self { core } }\n\n\tpub(super) fn reflow(area: Rect) -> mlua::Result<Table> {\n\t\tlet area = yazi_binding::elements::Rect::from(area);\n\t\tlet root = LUA.globals().raw_get::<Table>(\"Root\")?.call_method::<Table>(\"new\", area)?;\n\t\troot.call_method(\"reflow\", ())\n\t}\n}\n\nimpl Widget for Root<'_> {\n\tfn render(self, area: Rect, buf: &mut Buffer) {\n\t\tlet mut f = || {\n\t\t\tlet area = yazi_binding::elements::Rect::from(area);\n\t\t\tlet root = LUA.globals().raw_get::<Table>(\"Root\")?.call_method::<Table>(\"new\", area)?;\n\n\t\t\trender_once(root.call_method(\"redraw\", ())?, buf, |p| self.core.mgr.area(p));\n\t\t\tOk::<_, mlua::Error>(())\n\t\t};\n\t\tif let Err(e) = f() {\n\t\t\terror!(\"Failed to redraw the `Root` component:\\n{e}\");\n\t\t}\n\n\t\tmgr::Preview::new(self.core).render(area, buf);\n\t\tmgr::Modal::new(self.core).render(area, buf);\n\n\t\tif self.core.tasks.visible {\n\t\t\ttasks::Tasks::new(self.core).render(area, buf);\n\t\t}\n\n\t\tif self.core.active().spot.visible() {\n\t\t\tspot::Spot::new(self.core).render(area, buf);\n\t\t}\n\n\t\tif self.core.pick.visible {\n\t\t\tpick::Pick::new(self.core).render(area, buf);\n\t\t}\n\n\t\tif self.core.input.visible {\n\t\t\tinput::Input::new(self.core).render(area, buf);\n\t\t}\n\n\t\tif self.core.confirm.visible {\n\t\t\tconfirm::Confirm::new(self.core).render(area, buf);\n\t\t}\n\n\t\tif self.core.help.visible {\n\t\t\thelp::Help::new(self.core).render(area, buf);\n\t\t}\n\n\t\tif self.core.cmp.visible {\n\t\t\tcmp::Cmp::new(self.core).render(area, buf);\n\t\t}\n\n\t\tif self.core.which.active {\n\t\t\twhich::Which::new(self.core).render(area, buf);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/router.rs",
    "content": "use anyhow::Result;\nuse yazi_actor::Ctx;\nuse yazi_config::{KEYMAP, keymap::{Chord, ChordCow, Key}};\nuse yazi_macro::{act, emit};\nuse yazi_shared::Layer;\n\nuse crate::app::App;\n\npub(super) struct Router<'a> {\n\tapp: &'a mut App,\n}\n\nimpl<'a> Router<'a> {\n\tpub(super) fn new(app: &'a mut App) -> Self { Self { app } }\n\n\tpub(super) fn route(&mut self, key: Key) -> Result<bool> {\n\t\tlet core = &mut self.app.core;\n\t\tlet layer = core.layer();\n\n\t\tif core.help.visible && core.help.r#type(&key)? {\n\t\t\treturn Ok(true);\n\t\t}\n\t\tif core.input.visible && core.input.r#type(&key)? {\n\t\t\treturn Ok(true);\n\t\t}\n\n\t\tuse Layer as L;\n\t\tOk(match layer {\n\t\t\tL::App | L::Notify => unreachable!(),\n\t\t\tL::Mgr | L::Tasks | L::Spot | L::Pick | L::Input | L::Confirm | L::Help => {\n\t\t\t\tself.matches(layer, key)\n\t\t\t}\n\t\t\tL::Cmp => self.matches(L::Cmp, key) || self.matches(L::Input, key),\n\t\t\tL::Which => core.which.r#type(key),\n\t\t})\n\t}\n\n\tfn matches(&mut self, layer: Layer, key: Key) -> bool {\n\t\tfor chord @ Chord { on, .. } in KEYMAP.get(layer) {\n\t\t\tif on.is_empty() || on[0] != key {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif on.len() > 1 {\n\t\t\t\tlet cx = &mut Ctx::active(&mut self.app.core, &mut self.app.term);\n\t\t\t\tact!(which:activate, cx, (layer, key)).ok();\n\t\t\t} else {\n\t\t\t\temit!(Seq(ChordCow::from(chord).into_seq()));\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\tfalse\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/signals.rs",
    "content": "use anyhow::Result;\nuse crossterm::event::{Event as CrosstermEvent, EventStream, KeyEvent, KeyEventKind};\nuse futures::StreamExt;\nuse tokio::{select, sync::mpsc};\nuse yazi_config::YAZI;\nuse yazi_shared::{CompletionToken, event::Event};\n\npub(super) struct Signals {\n\tpub(super) tx: mpsc::UnboundedSender<(bool, CompletionToken)>,\n}\n\nimpl Signals {\n\tpub(super) fn start() -> Result<Self> {\n\t\tlet (tx, rx) = mpsc::unbounded_channel();\n\t\tSelf::spawn(rx)?;\n\n\t\tOk(Self { tx })\n\t}\n\n\t#[cfg(unix)]\n\tfn handle_sys(n: libc::c_int) -> bool {\n\t\tuse libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGSTOP, SIGTERM, SIGTSTP};\n\t\tuse tracing::error;\n\t\tuse yazi_proxy::AppProxy;\n\t\tuse yazi_term::YIELD_TO_SUBPROCESS;\n\n\t\tmatch n {\n\t\t\tSIGINT => { /* ignored */ }\n\t\t\tSIGQUIT | SIGHUP | SIGTERM => {\n\t\t\t\tAppProxy::quit(Default::default());\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tSIGTSTP => {\n\t\t\t\ttokio::spawn(async move {\n\t\t\t\t\tAppProxy::stop().await;\n\t\t\t\t\tif unsafe { libc::kill(0, SIGSTOP) } != 0 {\n\t\t\t\t\t\terror!(\"Failed to stop the process:\\n{}\", std::io::Error::last_os_error());\n\t\t\t\t\t\tAppProxy::quit(Default::default());\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\tSIGCONT if YIELD_TO_SUBPROCESS.try_acquire().is_ok() => _ = tokio::spawn(AppProxy::resume()),\n\t\t\t_ => {}\n\t\t}\n\t\ttrue\n\t}\n\n\t#[cfg(windows)]\n\t#[inline]\n\tfn handle_sys(_: ()) -> bool { unreachable!() }\n\n\tfn handle_term(event: CrosstermEvent) {\n\t\tmatch event {\n\t\t\tCrosstermEvent::Key(key @ KeyEvent { kind: KeyEventKind::Press, .. }) => {\n\t\t\t\tEvent::Key(key).emit()\n\t\t\t}\n\t\t\tCrosstermEvent::Mouse(mouse) if YAZI.mgr.mouse_events.get().contains(mouse.kind.into()) => {\n\t\t\t\tEvent::Mouse(mouse).emit()\n\t\t\t}\n\t\t\tCrosstermEvent::Resize(..) => Event::Resize.emit(),\n\t\t\tCrosstermEvent::FocusGained => Event::Focus.emit(),\n\t\t\tCrosstermEvent::Paste(str) => Event::Paste(str).emit(),\n\t\t\t_ => {}\n\t\t}\n\t}\n\n\tfn spawn(mut rx: mpsc::UnboundedReceiver<(bool, CompletionToken)>) -> Result<()> {\n\t\t#[cfg(unix)]\n\t\tuse libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGTSTP};\n\n\t\t#[cfg(unix)]\n\t\tlet mut sys = signal_hook_tokio::Signals::new([\n\t\t\t// Interrupt signals (Ctrl-C, Ctrl-\\)\n\t\t\tSIGINT, SIGQUIT, //\n\t\t\t// Hangup signal (Terminal closed)\n\t\t\tSIGHUP, //\n\t\t\t// Termination signal (kill)\n\t\t\tSIGTERM, //\n\t\t\t// Job control signals (Ctrl-Z, fg/bg)\n\t\t\tSIGTSTP, SIGCONT,\n\t\t])?;\n\t\t#[cfg(windows)]\n\t\tlet mut sys = tokio_stream::empty();\n\n\t\tlet mut term = Some(EventStream::new());\n\n\t\ttokio::spawn(async move {\n\t\t\tloop {\n\t\t\t\tif let Some(t) = &mut term {\n\t\t\t\t\tselect! {\n\t\t\t\t\t\tbiased;\n\t\t\t\t\t\tSome((state, token)) = rx.recv() => {\n\t\t\t\t\t\t\tterm = term.filter(|_| state);\n\t\t\t\t\t\t\ttoken.complete(true);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSome(n) = sys.next() => if !Self::handle_sys(n) { return },\n\t\t\t\t\t\tSome(Ok(e)) = t.next() => Self::handle_term(e)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tselect! {\n\t\t\t\t\t\tbiased;\n\t\t\t\t\t\tSome((state, token)) = rx.recv() => {\n\t\t\t\t\t\t\tterm = state.then(EventStream::new);\n\t\t\t\t\t\t\ttoken.complete(true);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSome(n) = sys.next() => if !Self::handle_sys(n) { return },\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tOk(())\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/spot/mod.rs",
    "content": "yazi_macro::mod_flat!(spot);\n"
  },
  {
    "path": "yazi-fm/src/spot/spot.rs",
    "content": "use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};\nuse yazi_core::Core;\n\npub(crate) struct Spot<'a> {\n\tcore: &'a Core,\n}\n\nimpl<'a> Spot<'a> {\n\tpub(crate) fn new(core: &'a Core) -> Self { Self { core } }\n}\n\nimpl Widget for Spot<'_> {\n\tfn render(self, win: Rect, buf: &mut Buffer) {\n\t\tlet Some(lock) = &self.core.active().spot.lock else {\n\t\t\treturn;\n\t\t};\n\n\t\tfor w in &lock.data {\n\t\t\tlet rect = w.area().transform(|p| self.core.mgr.area(p));\n\t\t\tif rect.intersection(win) == rect {\n\t\t\t\tw.render(rect, buf);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/tasks/list.rs",
    "content": "use mlua::{ObjectLike, Table};\nuse ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};\nuse tracing::error;\nuse yazi_binding::elements::render_once;\nuse yazi_core::Core;\nuse yazi_plugin::LUA;\n\npub(crate) struct List<'a> {\n\tcore: &'a Core,\n}\n\nimpl<'a> List<'a> {\n\t#[inline]\n\tpub(crate) fn new(core: &'a Core) -> Self { Self { core } }\n}\n\nimpl Widget for List<'_> {\n\tfn render(self, area: Rect, buf: &mut Buffer) {\n\t\tlet mut f = || {\n\t\t\tlet area = yazi_binding::elements::Rect::from(area);\n\t\t\tlet root = LUA.globals().raw_get::<Table>(\"Tasks\")?.call_method::<Table>(\"new\", area)?;\n\n\t\t\trender_once(root.call_method(\"redraw\", ())?, buf, |p| self.core.mgr.area(p));\n\t\t\tOk::<_, mlua::Error>(())\n\t\t};\n\t\tif let Err(e) = f() {\n\t\t\terror!(\"Failed to redraw the `Tasks` component:\\n{e}\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/tasks/mod.rs",
    "content": "yazi_macro::mod_flat!(list progress tasks);\n"
  },
  {
    "path": "yazi-fm/src/tasks/progress.rs",
    "content": "use mlua::{ObjectLike, Table};\nuse ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};\nuse tracing::error;\nuse yazi_binding::elements::render_once;\nuse yazi_config::LAYOUT;\nuse yazi_core::Core;\nuse yazi_plugin::LUA;\n\npub(crate) struct Progress<'a> {\n\tcore: &'a Core,\n}\n\nimpl<'a> Progress<'a> {\n\tpub(crate) fn new(core: &'a Core) -> Self { Self { core } }\n}\n\nimpl Widget for Progress<'_> {\n\tfn render(self, _: Rect, buf: &mut Buffer) {\n\t\tlet mut f = || {\n\t\t\tlet area = yazi_binding::elements::Rect::from(LAYOUT.get().progress);\n\t\t\tlet progress =\n\t\t\t\tLUA.globals().raw_get::<Table>(\"Progress\")?.call_method::<Table>(\"use\", area)?;\n\n\t\t\trender_once(progress.call_method(\"redraw\", ())?, buf, |p| self.core.mgr.area(p));\n\t\t\tOk::<_, mlua::Error>(())\n\t\t};\n\t\tif let Err(e) = f() {\n\t\t\terror!(\"Failed to redraw the `Progress` component:\\n{e}\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/tasks/tasks.rs",
    "content": "use ratatui::{buffer::Buffer, layout::{self, Alignment, Constraint, Rect}, text::Line, widgets::{Block, BorderType, Widget}};\nuse yazi_config::THEME;\nuse yazi_core::{Core, tasks::TASKS_PERCENT};\n\nuse crate::tasks::List;\n\npub(crate) struct Tasks<'a> {\n\tcore: &'a Core,\n}\n\nimpl<'a> Tasks<'a> {\n\tpub(crate) fn new(core: &'a Core) -> Self { Self { core } }\n\n\tpub(super) fn area(area: Rect) -> Rect {\n\t\tlet chunk = layout::Layout::vertical([\n\t\t\tConstraint::Percentage((100 - TASKS_PERCENT) / 2),\n\t\t\tConstraint::Percentage(TASKS_PERCENT),\n\t\t\tConstraint::Percentage((100 - TASKS_PERCENT) / 2),\n\t\t])\n\t\t.split(area)[1];\n\n\t\tlayout::Layout::horizontal([\n\t\t\tConstraint::Percentage((100 - TASKS_PERCENT) / 2),\n\t\t\tConstraint::Percentage(TASKS_PERCENT),\n\t\t\tConstraint::Percentage((100 - TASKS_PERCENT) / 2),\n\t\t])\n\t\t.split(chunk)[1]\n\t}\n}\n\nimpl Widget for Tasks<'_> {\n\tfn render(self, area: Rect, buf: &mut Buffer) {\n\t\tlet area = Self::area(area);\n\n\t\tyazi_widgets::Clear.render(area, buf);\n\n\t\tlet block = Block::bordered()\n\t\t\t.title(Line::styled(\"Tasks\", THEME.tasks.title))\n\t\t\t.title_alignment(Alignment::Center)\n\t\t\t.border_type(BorderType::Rounded)\n\t\t\t.border_style(THEME.tasks.border);\n\t\t(&block).render(area, buf);\n\n\t\tList::new(self.core).render(block.inner(area), buf);\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/which/cand.rs",
    "content": "use ratatui::{buffer::Buffer, layout::Rect, text::{Line, Span}, widgets::Widget};\nuse yazi_config::{THEME, keymap::Chord};\n\npub(super) struct Cand<'a> {\n\tcand:  &'a Chord,\n\ttimes: usize,\n}\n\nimpl<'a> Cand<'a> {\n\tpub(super) fn new(cand: &'a Chord, times: usize) -> Self { Self { times, cand } }\n\n\tfn keys(&self) -> Vec<String> {\n\t\tself.cand.on[self.times..].iter().map(ToString::to_string).collect()\n\t}\n}\n\nimpl Widget for Cand<'_> {\n\tfn render(self, area: Rect, buf: &mut Buffer) {\n\t\tlet keys = self.keys();\n\t\tlet mut spans = Vec::with_capacity(10);\n\n\t\t// Padding\n\t\tspans.push(Span::raw(\" \".repeat(10usize.saturating_sub(keys.join(\"\").len()))));\n\n\t\t// First key\n\t\tspans.push(Span::styled(keys[0].clone(), THEME.which.cand));\n\n\t\t// Rest keys\n\t\tspans.extend(keys.iter().skip(1).map(|k| Span::styled(k, THEME.which.rest)));\n\n\t\t// Separator\n\t\tspans.push(Span::styled(&THEME.which.separator, THEME.which.separator_style));\n\n\t\t// Description\n\t\tspans.push(Span::styled(self.cand.desc_or_run(), THEME.which.desc));\n\n\t\tLine::from(spans).render(area, buf);\n\t}\n}\n"
  },
  {
    "path": "yazi-fm/src/which/mod.rs",
    "content": "yazi_macro::mod_flat!(cand which);\n"
  },
  {
    "path": "yazi-fm/src/which/which.rs",
    "content": "use ratatui::{buffer::Buffer, layout, layout::{Constraint, Rect}, widgets::{Block, Widget}};\nuse yazi_config::THEME;\nuse yazi_core::Core;\n\nuse super::Cand;\n\nconst PADDING_X: u16 = 1;\nconst PADDING_Y: u16 = 1;\n\npub(crate) struct Which<'a> {\n\tcore: &'a Core,\n}\n\nimpl<'a> Which<'a> {\n\tpub(crate) fn new(core: &'a Core) -> Self { Self { core } }\n}\n\nimpl Widget for Which<'_> {\n\tfn render(self, area: Rect, buf: &mut Buffer) {\n\t\tlet which = &self.core.which;\n\t\tif which.silent {\n\t\t\treturn;\n\t\t}\n\n\t\tlet cols = THEME.which.cols as usize;\n\t\tlet height = area.height.min(which.cands.len().div_ceil(cols) as u16 + PADDING_Y * 2);\n\t\tlet area = Rect {\n\t\t\tx: PADDING_X.min(area.width),\n\t\t\ty: area.height.saturating_sub(height + PADDING_Y * 2),\n\t\t\twidth: area.width.saturating_sub(PADDING_X * 2),\n\t\t\theight,\n\t\t};\n\n\t\t// Don't render if there's no space\n\t\tif area.height <= PADDING_Y * 2 {\n\t\t\treturn;\n\t\t}\n\n\t\tlet chunks = {\n\t\t\tuse Constraint::*;\n\t\t\tlayout::Layout::horizontal(match cols {\n\t\t\t\t1 => &[Ratio(1, 1)][..],\n\t\t\t\t2 => &[Ratio(1, 2), Ratio(1, 2)],\n\t\t\t\t_ => &[Ratio(1, 3), Ratio(1, 3), Ratio(1, 3)],\n\t\t\t})\n\t\t\t.split(area)\n\t\t};\n\n\t\tyazi_widgets::Clear.render(area, buf);\n\t\tBlock::new().style(THEME.which.mask).render(area, buf);\n\n\t\tfor y in 0..area.height {\n\t\t\tfor (x, chunk) in chunks.iter().enumerate() {\n\t\t\t\tlet Some(cand) = which.cands.get(y as usize * cols + x) else {\n\t\t\t\t\tbreak;\n\t\t\t\t};\n\n\t\t\t\tCand::new(cand, which.times).render(Rect { y: chunk.y + y + 1, height: 1, ..*chunk }, buf);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-fs\"\ndescription            = \"Yazi file system\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[dependencies]\nyazi-ffi    = { path = \"../yazi-ffi\", version = \"26.2.2\" }\nyazi-macro  = { path = \"../yazi-macro\", version = \"26.2.2\" }\nyazi-shared = { path = \"../yazi-shared\", version = \"26.2.2\" }\nyazi-shim   = { path = \"../yazi-shim\", version = \"26.2.2\" }\n\n# External dependencies\nanyhow           = { workspace = true }\narc-swap         = \"1.8.2\"\nbitflags         = { workspace = true }\ndirs             = { workspace = true }\neither           = { workspace = true }\nfoldhash         = { workspace = true }\nhashbrown        = { workspace = true }\nparking_lot      = { workspace = true }\npercent-encoding = { workspace = true }\nrand             = { workspace = true }\nregex            = { workspace = true }\nscopeguard       = { workspace = true }\nserde            = { workspace = true }\ntokio            = { workspace = true }\ntracing          = { workspace = true }\ntyped-path       = { workspace = true }\n\n[target.\"cfg(unix)\".dependencies]\nlibc  = { workspace = true }\nuzers = { workspace = true }\n\n[target.'cfg(windows)'.dependencies]\nwindows-sys = { version = \"0.61.2\", features = [ \"Win32_Storage_FileSystem\" ] }\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\ncore-foundation-sys = { workspace = true }\nobjc2               = { workspace = true }\n\n[target.'cfg(not(target_os = \"android\"))'.dependencies]\ntrash = \"5.2.5\"\n"
  },
  {
    "path": "yazi-fs/README.md",
    "content": "# yazi-fs\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-fs/src/cha/cha.rs",
    "content": "use std::{fs::Metadata, ops::Deref, time::{Duration, SystemTime, UNIX_EPOCH}};\n\nuse anyhow::bail;\nuse yazi_macro::{unix_either, win_either};\nuse yazi_shared::{strand::AsStrand, url::AsUrl};\n\nuse super::ChaKind;\nuse crate::cha::{ChaMode, ChaType};\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub struct Cha {\n\tpub kind:  ChaKind,\n\tpub mode:  ChaMode,\n\tpub len:   u64,\n\tpub atime: Option<SystemTime>,\n\tpub btime: Option<SystemTime>,\n\tpub ctime: Option<SystemTime>,\n\tpub mtime: Option<SystemTime>,\n\tpub dev:   u64,\n\tpub uid:   u32,\n\tpub gid:   u32,\n\tpub nlink: u64,\n}\n\nimpl Deref for Cha {\n\ttype Target = ChaMode;\n\n\tfn deref(&self) -> &Self::Target { &self.mode }\n}\n\nimpl Default for Cha {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tkind:  ChaKind::DUMMY,\n\t\t\tmode:  ChaMode::empty(),\n\t\t\tlen:   0,\n\t\t\tatime: None,\n\t\t\tbtime: None,\n\t\t\tctime: None,\n\t\t\tmtime: None,\n\t\t\tdev:   0,\n\t\t\tuid:   0,\n\t\t\tgid:   0,\n\t\t\tnlink: 0,\n\t\t}\n\t}\n}\n\nimpl Cha {\n\t#[inline]\n\tpub fn new<T>(name: T, meta: Metadata) -> Self\n\twhere\n\t\tT: AsStrand,\n\t{\n\t\tSelf::from_bare(&meta).attach(ChaKind::hidden(name, &meta))\n\t}\n\n\tpub fn from_dummy<U>(_url: U, r#type: Option<ChaType>) -> Self\n\twhere\n\t\tU: AsUrl,\n\t{\n\t\tlet mut kind = ChaKind::DUMMY;\n\t\tlet mode = r#type.map(ChaMode::from_bare).unwrap_or_default();\n\n\t\t#[cfg(unix)]\n\t\tif _url.as_url().urn().is_hidden() {\n\t\t\tkind |= ChaKind::HIDDEN;\n\t\t}\n\n\t\tSelf { kind, mode, ..Default::default() }\n\t}\n\n\tfn from_bare(m: &Metadata) -> Self {\n\t\t#[cfg(unix)]\n\t\tuse std::os::unix::fs::MetadataExt;\n\n\t\t#[cfg(unix)]\n\t\tlet mode = {\n\t\t\tuse std::os::unix::fs::PermissionsExt;\n\t\t\tChaMode::from_bits_retain(m.permissions().mode() as u16)\n\t\t};\n\n\t\t#[cfg(windows)]\n\t\tlet mode = {\n\t\t\tif m.is_file() {\n\t\t\t\tChaMode::T_FILE\n\t\t\t} else if m.is_dir() {\n\t\t\t\tChaMode::T_DIR\n\t\t\t} else if m.is_symlink() {\n\t\t\t\tChaMode::T_LINK\n\t\t\t} else {\n\t\t\t\tChaMode::empty()\n\t\t\t}\n\t\t};\n\n\t\tSelf {\n\t\t\tkind: ChaKind::empty(),\n\t\t\tmode,\n\t\t\tlen: m.len(),\n\t\t\tatime: m.accessed().ok(),\n\t\t\tbtime: m.created().ok(),\n\t\t\tctime: unix_either!(\n\t\t\t\tUNIX_EPOCH.checked_add(Duration::new(m.ctime() as u64, m.ctime_nsec() as u32)),\n\t\t\t\tNone\n\t\t\t),\n\t\t\tmtime: m.modified().ok(),\n\t\t\tdev: unix_either!(m.dev(), 0) as _,\n\t\t\tuid: unix_either!(m.uid(), 0) as _,\n\t\t\tgid: unix_either!(m.gid(), 0) as _,\n\t\t\tnlink: unix_either!(m.nlink(), 0) as _,\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn hits(self, c: Self) -> bool {\n\t\tself.len == c.len\n\t\t\t&& self.mtime == c.mtime\n\t\t\t&& self.ctime == c.ctime\n\t\t\t&& self.btime == c.btime\n\t\t\t&& self.kind == c.kind\n\t\t\t&& self.mode == c.mode\n\t}\n\n\t#[inline]\n\tpub fn attach(mut self, kind: ChaKind) -> Self {\n\t\tself.kind |= kind;\n\t\tself\n\t}\n}\n\nimpl Cha {\n\t#[inline]\n\tpub fn is_link(self) -> bool {\n\t\tself.kind.contains(ChaKind::FOLLOW) || *self.mode == ChaType::Link\n\t}\n\n\t#[inline]\n\tpub fn is_orphan(self) -> bool {\n\t\t*self.mode == ChaType::Link && self.kind.contains(ChaKind::FOLLOW)\n\t}\n\n\t#[inline]\n\tpub const fn is_hidden(self) -> bool {\n\t\twin_either!(\n\t\t\tself.kind.contains(ChaKind::HIDDEN) || self.kind.contains(ChaKind::SYSTEM),\n\t\t\tself.kind.contains(ChaKind::HIDDEN)\n\t\t)\n\t}\n\n\t#[inline]\n\tpub const fn is_dummy(self) -> bool { self.kind.contains(ChaKind::DUMMY) }\n\n\tpub fn atime_dur(self) -> anyhow::Result<Duration> {\n\t\tif let Some(atime) = self.atime {\n\t\t\tOk(atime.duration_since(UNIX_EPOCH)?)\n\t\t} else {\n\t\t\tbail!(\"atime not available\");\n\t\t}\n\t}\n\n\tpub fn btime_dur(self) -> anyhow::Result<Duration> {\n\t\tif let Some(btime) = self.btime {\n\t\t\tOk(btime.duration_since(UNIX_EPOCH)?)\n\t\t} else {\n\t\t\tbail!(\"btime not available\");\n\t\t}\n\t}\n\n\tpub fn ctime_dur(self) -> anyhow::Result<Duration> {\n\t\tif let Some(ctime) = self.ctime {\n\t\t\tOk(ctime.duration_since(UNIX_EPOCH)?)\n\t\t} else {\n\t\t\tbail!(\"ctime not available\");\n\t\t}\n\t}\n\n\tpub fn mtime_dur(self) -> anyhow::Result<Duration> {\n\t\tif let Some(mtime) = self.mtime {\n\t\t\tOk(mtime.duration_since(UNIX_EPOCH)?)\n\t\t} else {\n\t\t\tbail!(\"mtime not available\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/cha/kind.rs",
    "content": "use std::fs::Metadata;\n\nuse bitflags::bitflags;\nuse yazi_shared::strand::AsStrand;\n\nbitflags! {\n\t#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]\n\tpub struct ChaKind: u8 {\n\t\tconst FOLLOW = 0b0000_0001;\n\t\tconst HIDDEN = 0b0000_0010;\n\t\tconst SYSTEM = 0b0000_0100;\n\t\tconst DUMMY  = 0b0000_1000;\n\t}\n}\n\nimpl ChaKind {\n\t#[inline]\n\tpub(super) fn hidden<T>(_name: T, _meta: &Metadata) -> Self\n\twhere\n\t\tT: AsStrand,\n\t{\n\t\tlet mut me = Self::empty();\n\n\t\t#[cfg(unix)]\n\t\t{\n\t\t\tif _name.as_strand().starts_with(\".\") {\n\t\t\t\tme |= Self::HIDDEN;\n\t\t\t}\n\t\t}\n\t\t#[cfg(windows)]\n\t\t{\n\t\t\tuse std::os::windows::fs::MetadataExt;\n\n\t\t\tuse windows_sys::Win32::Storage::FileSystem::{FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_SYSTEM};\n\t\t\tif _meta.file_attributes() & FILE_ATTRIBUTE_HIDDEN != 0 {\n\t\t\t\tme |= Self::HIDDEN;\n\t\t\t}\n\t\t\tif _meta.file_attributes() & FILE_ATTRIBUTE_SYSTEM != 0 {\n\t\t\t\tme |= Self::SYSTEM;\n\t\t\t}\n\t\t}\n\n\t\tme\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/cha/mod.rs",
    "content": "yazi_macro::mod_flat!(cha kind mode r#type);\n"
  },
  {
    "path": "yazi-fs/src/cha/mode.rs",
    "content": "use std::ops::Deref;\n\nuse anyhow::{anyhow, bail};\nuse bitflags::bitflags;\n\nuse crate::cha::ChaType;\n\nbitflags! {\n\t#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]\n\tpub struct ChaMode: u16 {\n\t\t// File type\n\t\tconst T_MASK   = 0b1111_0000_0000_0000;\n\t\tconst T_SOCK   = 0b1100_0000_0000_0000;\n\t\tconst T_LINK   = 0b1010_0000_0000_0000;\n\t\tconst T_FILE   = 0b1000_0000_0000_0000;\n\t\tconst T_BLOCK  = 0b0110_0000_0000_0000;\n\t\tconst T_DIR    = 0b0100_0000_0000_0000;\n\t\tconst T_CHAR   = 0b0010_0000_0000_0000;\n\t\tconst T_FIFO   = 0b0001_0000_0000_0000;\n\t\t// Special\n\t\tconst S_SUID   = 0b0000_1000_0000_0000;\n\t\tconst S_SGID   = 0b0000_0100_0000_0000;\n\t\tconst S_STICKY = 0b0000_0010_0000_0000;\n\t\t// User\n\t\tconst U_MASK   = 0b0000_0001_1100_0000;\n\t\tconst U_READ   = 0b0000_0001_0000_0000;\n\t\tconst U_WRITE  = 0b0000_0000_1000_0000;\n\t\tconst U_EXEC   = 0b0000_0000_0100_0000;\n\t\t// Group\n\t\tconst G_MASK   = 0b0000_0000_0011_1000;\n\t\tconst G_READ   = 0b0000_0000_0010_0000;\n\t\tconst G_WRITE  = 0b0000_0000_0001_0000;\n\t\tconst G_EXEC   = 0b0000_0000_0000_1000;\n\t\t// Others\n\t\tconst O_MASK   = 0b0000_0000_0000_0111;\n\t\tconst O_READ   = 0b0000_0000_0000_0100;\n\t\tconst O_WRITE  = 0b0000_0000_0000_0010;\n\t\tconst O_EXEC   = 0b0000_0000_0000_0001;\n\t}\n}\n\nimpl Deref for ChaMode {\n\ttype Target = ChaType;\n\n\t#[inline]\n\tfn deref(&self) -> &Self::Target {\n\t\tmatch *self & Self::T_MASK {\n\t\t\tSelf::T_FILE => &ChaType::File,\n\t\t\tSelf::T_DIR => &ChaType::Dir,\n\t\t\tSelf::T_LINK => &ChaType::Link,\n\t\t\tSelf::T_BLOCK => &ChaType::Block,\n\t\t\tSelf::T_CHAR => &ChaType::Char,\n\t\t\tSelf::T_SOCK => &ChaType::Sock,\n\t\t\tSelf::T_FIFO => &ChaType::FIFO,\n\t\t\t_ => &ChaType::Unknown,\n\t\t}\n\t}\n}\n\nimpl TryFrom<u16> for ChaMode {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(value: u16) -> Result<Self, Self::Error> {\n\t\tlet me = Self::from_bits(value).ok_or_else(|| anyhow!(\"invalid file mode: {value:04o}\"))?;\n\t\tmatch me & Self::T_MASK {\n\t\t\tSelf::T_FILE\n\t\t\t| Self::T_DIR\n\t\t\t| Self::T_LINK\n\t\t\t| Self::T_BLOCK\n\t\t\t| Self::T_CHAR\n\t\t\t| Self::T_SOCK\n\t\t\t| Self::T_FIFO => Ok(me),\n\t\t\t_ => bail!(\"invalid file type: {value:04o}\"),\n\t\t}\n\t}\n}\n\n#[cfg(unix)]\nimpl From<ChaMode> for std::fs::Permissions {\n\tfn from(value: ChaMode) -> Self {\n\t\tuse std::os::unix::fs::PermissionsExt;\n\n\t\tSelf::from_mode(value.bits() as _)\n\t}\n}\n\nimpl ChaMode {\n\t// Convert a file mode to a string representation\n\t#[cfg(unix)]\n\t#[allow(clippy::collapsible_else_if)]\n\tpub fn permissions(self, dummy: bool) -> [u8; 10] {\n\t\tlet mut s = *b\"-?????????\";\n\n\t\t// File type\n\t\ts[0] = match *self {\n\t\t\tChaType::Dir => b'd',\n\t\t\tChaType::Link => b'l',\n\t\t\tChaType::Block => b'b',\n\t\t\tChaType::Char => b'c',\n\t\t\tChaType::Sock => b's',\n\t\t\tChaType::FIFO => b'p',\n\t\t\t_ => b'-',\n\t\t};\n\t\tif dummy {\n\t\t\treturn s;\n\t\t}\n\n\t\t// User\n\t\ts[1] = if self.contains(Self::U_READ) { b'r' } else { b'-' };\n\t\ts[2] = if self.contains(Self::U_WRITE) { b'w' } else { b'-' };\n\t\ts[3] = if self.contains(Self::U_EXEC) {\n\t\t\tif self.contains(Self::S_SUID) { b's' } else { b'x' }\n\t\t} else {\n\t\t\tif self.contains(Self::S_SUID) { b'S' } else { b'-' }\n\t\t};\n\n\t\t// Group\n\t\ts[4] = if self.contains(Self::G_READ) { b'r' } else { b'-' };\n\t\ts[5] = if self.contains(Self::G_WRITE) { b'w' } else { b'-' };\n\t\ts[6] = if self.contains(Self::G_EXEC) {\n\t\t\tif self.contains(Self::S_SGID) { b's' } else { b'x' }\n\t\t} else {\n\t\t\tif self.contains(Self::S_SGID) { b'S' } else { b'-' }\n\t\t};\n\n\t\t// Others\n\t\ts[7] = if self.contains(Self::O_READ) { b'r' } else { b'-' };\n\t\ts[8] = if self.contains(Self::O_WRITE) { b'w' } else { b'-' };\n\t\ts[9] = if self.contains(Self::O_EXEC) {\n\t\t\tif self.contains(Self::S_STICKY) { b't' } else { b'x' }\n\t\t} else {\n\t\t\tif self.contains(Self::S_STICKY) { b'T' } else { b'-' }\n\t\t};\n\n\t\ts\n\t}\n\n\tpub(super) fn from_bare(r#type: ChaType) -> Self {\n\t\tmatch r#type {\n\t\t\tChaType::File => Self::T_FILE,\n\t\t\tChaType::Dir => Self::T_DIR,\n\t\t\tChaType::Link => Self::T_LINK,\n\t\t\tChaType::Block => Self::T_BLOCK,\n\t\t\tChaType::Char => Self::T_CHAR,\n\t\t\tChaType::Sock => Self::T_SOCK,\n\t\t\tChaType::FIFO => Self::T_FIFO,\n\t\t\tChaType::Unknown => Self::empty(),\n\t\t}\n\t}\n}\n\nimpl ChaMode {\n\t// TODO: deprecate\n\t#[inline]\n\tpub const fn is_exec(self) -> bool { self.contains(Self::U_EXEC) }\n\n\t#[inline]\n\tpub const fn is_sticky(self) -> bool { self.contains(Self::S_STICKY) }\n}\n"
  },
  {
    "path": "yazi-fs/src/cha/type.rs",
    "content": "use std::fs::FileType;\n\nuse crate::cha::ChaMode;\n\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]\npub enum ChaType {\n\tFile,\n\tDir,\n\tLink,\n\tBlock,\n\tChar,\n\tSock,\n\tFIFO,\n\tUnknown,\n}\n\nimpl From<ChaMode> for ChaType {\n\tfn from(value: ChaMode) -> Self { *value }\n}\n\nimpl From<FileType> for ChaType {\n\tfn from(value: FileType) -> Self {\n\t\t#[cfg(unix)]\n\t\t{\n\t\t\tuse std::os::unix::fs::FileTypeExt;\n\t\t\tif value.is_file() {\n\t\t\t\tSelf::File\n\t\t\t} else if value.is_dir() {\n\t\t\t\tSelf::Dir\n\t\t\t} else if value.is_symlink() {\n\t\t\t\tSelf::Link\n\t\t\t} else if value.is_block_device() {\n\t\t\t\tSelf::Block\n\t\t\t} else if value.is_char_device() {\n\t\t\t\tSelf::Char\n\t\t\t} else if value.is_socket() {\n\t\t\t\tSelf::Sock\n\t\t\t} else if value.is_fifo() {\n\t\t\t\tSelf::FIFO\n\t\t\t} else {\n\t\t\t\tSelf::Unknown\n\t\t\t}\n\t\t}\n\t\t#[cfg(windows)]\n\t\t{\n\t\t\tif value.is_file() {\n\t\t\t\tSelf::File\n\t\t\t} else if value.is_dir() {\n\t\t\t\tSelf::Dir\n\t\t\t} else if value.is_symlink() {\n\t\t\t\tSelf::Link\n\t\t\t} else {\n\t\t\t\tSelf::Unknown\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl ChaType {\n\t#[inline]\n\tpub fn is_file(self) -> bool { self == Self::File }\n\n\t#[inline]\n\tpub fn is_dir(self) -> bool { self == Self::Dir }\n\n\t#[inline]\n\tpub fn is_block(self) -> bool { self == Self::Block }\n\n\t#[inline]\n\tpub fn is_char(self) -> bool { self == Self::Char }\n\n\t#[inline]\n\tpub fn is_sock(self) -> bool { self == Self::Sock }\n\n\t#[inline]\n\tpub fn is_fifo(self) -> bool { self == Self::FIFO }\n}\n"
  },
  {
    "path": "yazi-fs/src/cwd.rs",
    "content": "use std::{borrow::Cow, env::{current_dir, set_current_dir}, ops::Deref, path::{Path, PathBuf}, sync::{Arc, atomic::{AtomicBool, Ordering}}};\n\nuse arc_swap::ArcSwap;\nuse yazi_shared::{RoCell, url::{AsUrl, Url, UrlBuf, UrlLike}};\n\nuse crate::{FsUrl, Xdg};\n\npub static CWD: RoCell<Cwd> = RoCell::new();\n\npub struct Cwd(ArcSwap<UrlBuf>);\n\nimpl Deref for Cwd {\n\ttype Target = ArcSwap<UrlBuf>;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl Default for Cwd {\n\tfn default() -> Self {\n\t\tlet p = std::env::var_os(\"PWD\")\n\t\t\t.map(PathBuf::from)\n\t\t\t.filter(|p| p.is_absolute())\n\t\t\t.or_else(|| current_dir().ok())\n\t\t\t.expect(\"failed to get current working directory\");\n\n\t\tSelf(ArcSwap::new(Arc::new(UrlBuf::from(p))))\n\t}\n}\n\nimpl Cwd {\n\tpub fn path(&self) -> PathBuf { self.0.load().as_url().unified_path().into_owned() }\n\n\tpub fn set(&self, url: &UrlBuf, callback: fn()) -> bool {\n\t\tif !url.is_absolute() {\n\t\t\treturn false;\n\t\t} else if self.0.load().as_ref() == url {\n\t\t\treturn false;\n\t\t}\n\n\t\tself.0.store(Arc::new(url.clone()));\n\t\tSelf::sync_cwd(callback);\n\n\t\ttrue\n\t}\n\n\tpub fn ensure(url: Url) -> Cow<Path> {\n\t\tuse std::{io::ErrorKind::{AlreadyExists, NotADirectory, NotFound}, path::Component as C};\n\n\t\tlet Some(cache) = url.cache() else {\n\t\t\treturn url.unified_path();\n\t\t};\n\n\t\tif !matches!(std::fs::create_dir_all(&cache), Err(e) if e.kind() == NotADirectory || e.kind() == AlreadyExists)\n\t\t{\n\t\t\treturn cache.into();\n\t\t}\n\n\t\tlet latter = cache.strip_prefix(Xdg::cache_dir()).expect(\"under cache dir\");\n\t\tlet mut it = latter.components().peekable();\n\t\twhile it.peek() == Some(&C::CurDir) {\n\t\t\tit.next().unwrap();\n\t\t}\n\n\t\tlet mut count = 0;\n\t\tfor c in it {\n\t\t\tmatch c {\n\t\t\t\tC::Prefix(_) | C::RootDir | C::ParentDir => return cache.into(),\n\t\t\t\tC::CurDir | C::Normal(_) => count += 1,\n\t\t\t}\n\t\t}\n\n\t\tfor n in (0..count).rev() {\n\t\t\tlet mut it = cache.components();\n\t\t\tfor _ in 0..n {\n\t\t\t\tit.next_back().unwrap();\n\t\t\t}\n\t\t\tmatch std::fs::remove_file(it.as_path()) {\n\t\t\t\tOk(_) => break,\n\t\t\t\tErr(e) if e.kind() == NotFound => break,\n\t\t\t\tErr(_) => {}\n\t\t\t}\n\t\t}\n\n\t\tstd::fs::create_dir_all(&cache).ok();\n\t\tcache.into()\n\t}\n\n\tfn sync_cwd(callback: fn()) {\n\t\tstatic SYNCING: AtomicBool = AtomicBool::new(false);\n\t\tif SYNCING.swap(true, Ordering::Relaxed) {\n\t\t\treturn;\n\t\t}\n\n\t\ttokio::task::spawn_blocking(move || {\n\t\t\tlet cwd = CWD.load();\n\t\t\tlet path = Self::ensure(cwd.as_url());\n\n\t\t\tset_current_dir(&path).ok();\n\t\t\tlet cur = current_dir().unwrap_or_default();\n\n\t\t\tunsafe { std::env::set_var(\"PWD\", path.as_ref()) }\n\t\t\tSYNCING.store(false, Ordering::Relaxed);\n\n\t\t\tlet cwd = CWD.load();\n\t\t\tlet path = Self::ensure(cwd.as_url());\n\t\t\tif cur != path {\n\t\t\t\tset_current_dir(&path).ok();\n\t\t\t\tunsafe { std::env::set_var(\"PWD\", path.as_ref()) }\n\t\t\t}\n\n\t\t\tcallback();\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/error/error.rs",
    "content": "use std::{fmt, io, sync::Arc};\n\nuse anyhow::Result;\n\nuse crate::error::{kind_from_str, kind_to_str};\n\n#[derive(Clone, Debug, Eq, PartialEq)]\npub enum Error {\n\tKind(io::ErrorKind),\n\tRaw(i32),\n\tCustom { kind: io::ErrorKind, code: Option<i32>, message: Arc<str> },\n}\n\nimpl From<io::Error> for Error {\n\tfn from(err: io::Error) -> Self {\n\t\tif err.get_ref().is_some() {\n\t\t\tSelf::Custom {\n\t\t\t\tkind:    err.kind(),\n\t\t\t\tcode:    err.raw_os_error(),\n\t\t\t\tmessage: err.to_string().into(),\n\t\t\t}\n\t\t} else if let Some(code) = err.raw_os_error() {\n\t\t\tSelf::Raw(code)\n\t\t} else {\n\t\t\tSelf::Kind(err.kind())\n\t\t}\n\t}\n}\n\nimpl From<io::ErrorKind> for Error {\n\tfn from(kind: io::ErrorKind) -> Self { Self::Kind(kind) }\n}\n\nimpl fmt::Display for Error {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tmatch self {\n\t\t\tSelf::Kind(kind) => io::Error::from(*kind).fmt(f),\n\t\t\tSelf::Raw(code) => io::Error::from_raw_os_error(*code).fmt(f),\n\t\t\tSelf::Custom { message, .. } => write!(f, \"{message}\"),\n\t\t}\n\t}\n}\n\nimpl Error {\n\tpub fn custom(kind: &str, code: Option<i32>, message: &str) -> Result<Self> {\n\t\tOk(Self::Custom { kind: kind_from_str(kind)?, code, message: message.into() })\n\t}\n\n\tpub fn kind(&self) -> io::ErrorKind {\n\t\tmatch self {\n\t\t\tSelf::Kind(kind) => *kind,\n\t\t\tSelf::Raw(code) => io::Error::from_raw_os_error(*code).kind(),\n\t\t\tSelf::Custom { kind, .. } => *kind,\n\t\t}\n\t}\n\n\tpub fn kind_str(&self) -> &'static str { kind_to_str(self.kind()) }\n\n\tpub fn raw_os_error(&self) -> Option<i32> {\n\t\tmatch self {\n\t\t\tSelf::Kind(_) => None,\n\t\t\tSelf::Raw(code) => Some(*code),\n\t\t\tSelf::Custom { code, .. } => *code,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/error/mod.rs",
    "content": "yazi_macro::mod_flat!(error serde);\n"
  },
  {
    "path": "yazi-fs/src/error/serde.rs",
    "content": "use std::io;\n\nuse anyhow::{Result, bail};\nuse serde::{Deserialize, Deserializer, Serialize, Serializer};\n\nuse super::Error;\n\npub(super) fn kind_to_str(kind: io::ErrorKind) -> &'static str {\n\tuse std::io::ErrorKind as K;\n\tmatch kind {\n\t\tK::NotFound => \"NotFound\",\n\t\tK::PermissionDenied => \"PermissionDenied\",\n\t\tK::ConnectionRefused => \"ConnectionRefused\",\n\t\tK::ConnectionReset => \"ConnectionReset\",\n\t\tK::HostUnreachable => \"HostUnreachable\",\n\t\tK::NetworkUnreachable => \"NetworkUnreachable\",\n\t\tK::ConnectionAborted => \"ConnectionAborted\",\n\t\tK::NotConnected => \"NotConnected\",\n\t\tK::AddrInUse => \"AddrInUse\",\n\t\tK::AddrNotAvailable => \"AddrNotAvailable\",\n\t\tK::NetworkDown => \"NetworkDown\",\n\t\tK::BrokenPipe => \"BrokenPipe\",\n\t\tK::AlreadyExists => \"AlreadyExists\",\n\t\tK::WouldBlock => \"WouldBlock\",\n\t\tK::NotADirectory => \"NotADirectory\",\n\t\tK::IsADirectory => \"IsADirectory\",\n\t\tK::DirectoryNotEmpty => \"DirectoryNotEmpty\",\n\t\tK::ReadOnlyFilesystem => \"ReadOnlyFilesystem\",\n\t\t// K::FilesystemLoop => \"FilesystemLoop\",\n\t\tK::StaleNetworkFileHandle => \"StaleNetworkFileHandle\",\n\t\tK::InvalidInput => \"InvalidInput\",\n\t\tK::InvalidData => \"InvalidData\",\n\t\tK::TimedOut => \"TimedOut\",\n\t\tK::WriteZero => \"WriteZero\",\n\t\tK::StorageFull => \"StorageFull\",\n\t\tK::NotSeekable => \"NotSeekable\",\n\t\tK::QuotaExceeded => \"QuotaExceeded\",\n\t\tK::FileTooLarge => \"FileTooLarge\",\n\t\tK::ResourceBusy => \"ResourceBusy\",\n\t\tK::ExecutableFileBusy => \"ExecutableFileBusy\",\n\t\tK::Deadlock => \"Deadlock\",\n\t\tK::CrossesDevices => \"CrossesDevices\",\n\t\tK::TooManyLinks => \"TooManyLinks\",\n\t\tK::InvalidFilename => \"InvalidFilename\",\n\t\tK::ArgumentListTooLong => \"ArgumentListTooLong\",\n\t\tK::Interrupted => \"Interrupted\",\n\t\tK::Unsupported => \"Unsupported\",\n\t\tK::UnexpectedEof => \"UnexpectedEof\",\n\t\tK::OutOfMemory => \"OutOfMemory\",\n\t\t// K::InProgress => \"InProgress\",\n\t\tK::Other => \"Other\",\n\t\t_ => \"Other\",\n\t}\n}\n\npub(super) fn kind_from_str(s: &str) -> Result<io::ErrorKind> {\n\tuse std::io::ErrorKind as K;\n\tOk(match s {\n\t\t\"NotFound\" => K::NotFound,\n\t\t\"PermissionDenied\" => K::PermissionDenied,\n\t\t\"ConnectionRefused\" => K::ConnectionRefused,\n\t\t\"ConnectionReset\" => K::ConnectionReset,\n\t\t\"HostUnreachable\" => K::HostUnreachable,\n\t\t\"NetworkUnreachable\" => K::NetworkUnreachable,\n\t\t\"ConnectionAborted\" => K::ConnectionAborted,\n\t\t\"NotConnected\" => K::NotConnected,\n\t\t\"AddrInUse\" => K::AddrInUse,\n\t\t\"AddrNotAvailable\" => K::AddrNotAvailable,\n\t\t\"NetworkDown\" => K::NetworkDown,\n\t\t\"BrokenPipe\" => K::BrokenPipe,\n\t\t\"AlreadyExists\" => K::AlreadyExists,\n\t\t\"WouldBlock\" => K::WouldBlock,\n\t\t\"NotADirectory\" => K::NotADirectory,\n\t\t\"IsADirectory\" => K::IsADirectory,\n\t\t\"DirectoryNotEmpty\" => K::DirectoryNotEmpty,\n\t\t\"ReadOnlyFilesystem\" => K::ReadOnlyFilesystem,\n\t\t// \"FilesystemLoop\" => K::FilesystemLoop,\n\t\t\"StaleNetworkFileHandle\" => K::StaleNetworkFileHandle,\n\t\t\"InvalidInput\" => K::InvalidInput,\n\t\t\"InvalidData\" => K::InvalidData,\n\t\t\"TimedOut\" => K::TimedOut,\n\t\t\"WriteZero\" => K::WriteZero,\n\t\t\"StorageFull\" => K::StorageFull,\n\t\t\"NotSeekable\" => K::NotSeekable,\n\t\t\"QuotaExceeded\" => K::QuotaExceeded,\n\t\t\"FileTooLarge\" => K::FileTooLarge,\n\t\t\"ResourceBusy\" => K::ResourceBusy,\n\t\t\"ExecutableFileBusy\" => K::ExecutableFileBusy,\n\t\t\"Deadlock\" => K::Deadlock,\n\t\t\"CrossesDevices\" => K::CrossesDevices,\n\t\t\"TooManyLinks\" => K::TooManyLinks,\n\t\t\"InvalidFilename\" => K::InvalidFilename,\n\t\t\"ArgumentListTooLong\" => K::ArgumentListTooLong,\n\t\t\"Interrupted\" => K::Interrupted,\n\t\t\"Unsupported\" => K::Unsupported,\n\t\t\"UnexpectedEof\" => K::UnexpectedEof,\n\t\t\"OutOfMemory\" => K::OutOfMemory,\n\t\t// \"InProgress\" => K::InProgress,\n\t\t\"Other\" => K::Other,\n\t\t_ => bail!(\"unknown error kind: {s}\"),\n\t})\n}\n\nimpl Serialize for Error {\n\tfn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n\twhere\n\t\tS: Serializer,\n\t{\n\t\t#[derive(Serialize)]\n\t\t#[serde(tag = \"type\", rename_all = \"kebab-case\")]\n\t\tenum Shadow<'a> {\n\t\t\tKind { kind: &'a str },\n\t\t\tRaw { code: i32 },\n\t\t\tDyn { kind: &'a str, code: Option<i32>, message: &'a str },\n\t\t}\n\n\t\tmatch self {\n\t\t\tSelf::Kind(kind) => Shadow::Kind { kind: kind_to_str(*kind) }.serialize(serializer),\n\t\t\tSelf::Raw(code) => Shadow::Raw { code: *code }.serialize(serializer),\n\t\t\tSelf::Custom { kind, code, message } => {\n\t\t\t\tShadow::Dyn { kind: kind_to_str(*kind), code: *code, message }.serialize(serializer)\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl<'de> Deserialize<'de> for Error {\n\tfn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n\twhere\n\t\tD: Deserializer<'de>,\n\t{\n\t\t#[derive(Deserialize)]\n\t\t#[serde(tag = \"type\", rename_all = \"kebab-case\")]\n\t\tenum Shadow {\n\t\t\tKind { kind: String },\n\t\t\tRaw { code: i32 },\n\t\t\tDyn { kind: String, code: Option<i32>, message: String },\n\t\t}\n\n\t\tlet shadow = Shadow::deserialize(deserializer)?;\n\t\tOk(match shadow {\n\t\t\tShadow::Kind { kind } => Self::Kind(kind_from_str(&kind).map_err(serde::de::Error::custom)?),\n\t\t\tShadow::Raw { code } => Self::Raw(code),\n\t\t\tShadow::Dyn { kind, code, message } => {\n\t\t\t\tif !message.is_empty() {\n\t\t\t\t\tSelf::Custom {\n\t\t\t\t\t\tkind: kind_from_str(&kind).map_err(serde::de::Error::custom)?,\n\t\t\t\t\t\tcode,\n\t\t\t\t\t\tmessage: message.into(),\n\t\t\t\t\t}\n\t\t\t\t} else if let Some(code) = code {\n\t\t\t\t\tSelf::Raw(code)\n\t\t\t\t} else {\n\t\t\t\t\tSelf::Kind(kind_from_str(&kind).map_err(serde::de::Error::custom)?)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/file.rs",
    "content": "use std::{hash::{Hash, Hasher}, ops::Deref, path::Path};\n\nuse yazi_shared::{path::{PathBufDyn, PathDyn}, strand::Strand, url::{UrlBuf, UrlLike}};\n\nuse crate::cha::{Cha, ChaType};\n\n#[derive(Clone, Debug, Default)]\npub struct File {\n\tpub url:     UrlBuf,\n\tpub cha:     Cha,\n\tpub link_to: Option<PathBufDyn>,\n}\n\nimpl Deref for File {\n\ttype Target = Cha;\n\n\tfn deref(&self) -> &Self::Target { &self.cha }\n}\n\nimpl From<&Self> for File {\n\tfn from(value: &Self) -> Self { value.clone() }\n}\n\nimpl File {\n\t#[inline]\n\tpub fn from_dummy(url: impl Into<UrlBuf>, r#type: Option<ChaType>) -> Self {\n\t\tlet url = url.into();\n\t\tlet cha = Cha::from_dummy(&url, r#type);\n\t\tSelf { url, cha, link_to: None }\n\t}\n\n\t#[inline]\n\tpub fn chdir(&self, wd: &Path) -> Self {\n\t\tSelf { url: self.url.rebase(wd), cha: self.cha, link_to: self.link_to.clone() }\n\t}\n}\n\nimpl File {\n\t// --- Url\n\t#[inline]\n\tpub fn url_owned(&self) -> UrlBuf { self.url.clone() }\n\n\t#[inline]\n\tpub fn uri(&self) -> PathDyn<'_> { self.url.uri() }\n\n\t#[inline]\n\tpub fn urn(&self) -> PathDyn<'_> { self.url.urn() }\n\n\t#[inline]\n\tpub fn name(&self) -> Option<Strand<'_>> { self.url.name() }\n\n\t#[inline]\n\tpub fn stem(&self) -> Option<Strand<'_>> { self.url.stem() }\n}\n\nimpl Hash for File {\n\tfn hash<H: Hasher>(&self, state: &mut H) {\n\t\tself.url.hash(state);\n\t\tself.cha.len.hash(state);\n\t\tself.cha.btime.hash(state);\n\t\tself.cha.ctime.hash(state);\n\t\tself.cha.mtime.hash(state);\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/files.rs",
    "content": "use std::{mem, ops::{Deref, DerefMut, Not}};\n\nuse hashbrown::{HashMap, HashSet};\nuse yazi_shared::{Id, path::{PathBufDyn, PathDyn}};\n\nuse super::{FilesSorter, Filter};\nuse crate::{FILES_TICKET, File, SortBy};\n\n#[derive(Default)]\npub struct Files {\n\thidden:       Vec<File>,\n\titems:        Vec<File>,\n\tticket:       Id,\n\tversion:      u64,\n\tpub revision: u64,\n\n\tpub sizes: HashMap<PathBufDyn, u64>,\n\n\tsorter:      FilesSorter,\n\tfilter:      Option<Filter>,\n\tshow_hidden: bool,\n}\n\nimpl Deref for Files {\n\ttype Target = Vec<File>;\n\n\tfn deref(&self) -> &Self::Target { &self.items }\n}\n\nimpl DerefMut for Files {\n\tfn deref_mut(&mut self) -> &mut Self::Target { &mut self.items }\n}\n\nimpl Files {\n\tpub fn new(show_hidden: bool) -> Self { Self { show_hidden, ..Default::default() } }\n\n\tpub fn update_full(&mut self, files: Vec<File>) {\n\t\tself.ticket = FILES_TICKET.next();\n\n\t\tlet (hidden, items) = self.split_files(files);\n\t\tif !(items.is_empty() && self.items.is_empty()) {\n\t\t\tself.revision += 1;\n\t\t}\n\n\t\t(self.hidden, self.items) = (hidden, items);\n\t}\n\n\tpub fn update_part(&mut self, files: Vec<File>, ticket: Id) {\n\t\tif !files.is_empty() {\n\t\t\tif ticket != self.ticket {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet (hidden, items) = self.split_files(files);\n\t\t\tif !items.is_empty() {\n\t\t\t\tself.revision += 1;\n\t\t\t}\n\n\t\t\tself.hidden.extend(hidden);\n\t\t\tself.items.extend(items);\n\t\t\treturn;\n\t\t}\n\n\t\tself.ticket = ticket;\n\t\tself.hidden.clear();\n\t\tif !self.items.is_empty() {\n\t\t\tself.revision += 1;\n\t\t\tself.items.clear();\n\t\t}\n\t}\n\n\tpub fn update_size(&mut self, mut sizes: HashMap<PathBufDyn, u64>) {\n\t\tif sizes.len() <= 50 {\n\t\t\tsizes.retain(|k, v| self.sizes.get(k) != Some(v));\n\t\t}\n\n\t\tif sizes.is_empty() {\n\t\t\treturn;\n\t\t}\n\n\t\tif self.sorter.by == SortBy::Size {\n\t\t\tself.revision += 1;\n\t\t}\n\t\tself.sizes.extend(sizes);\n\t}\n\n\tpub fn update_ioerr(&mut self) {\n\t\tself.ticket = FILES_TICKET.next();\n\t\tself.hidden.clear();\n\t\tself.items.clear();\n\t}\n\n\tpub fn update_creating(&mut self, files: Vec<File>) {\n\t\tif files.is_empty() {\n\t\t\treturn;\n\t\t}\n\n\t\tmacro_rules! go {\n\t\t\t($dist:expr, $src:expr, $inc:literal) => {\n\t\t\t\tlet mut todo: HashMap<_, _> = $src.into_iter().map(|f| (f.urn().to_owned(), f)).collect();\n\t\t\t\tfor f in &$dist {\n\t\t\t\t\tif todo.remove(&f.urn()).is_some() && todo.is_empty() {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif !todo.is_empty() {\n\t\t\t\t\tself.revision += $inc;\n\t\t\t\t\t$dist.extend(todo.into_values());\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\tlet (hidden, items) = self.split_files(files);\n\t\tif !items.is_empty() {\n\t\t\tgo!(self.items, items, 1);\n\t\t}\n\t\tif !hidden.is_empty() {\n\t\t\tgo!(self.hidden, hidden, 0);\n\t\t}\n\t}\n\n\t#[cfg(unix)]\n\tpub fn update_deleting(&mut self, urns: HashSet<PathBufDyn>) -> Vec<usize> {\n\t\tuse yazi_shared::path::PathLike;\n\t\tif urns.is_empty() {\n\t\t\treturn vec![];\n\t\t}\n\n\t\tlet (mut hidden, mut items) = if let Some(filter) = &self.filter {\n\t\t\turns.into_iter().partition(|u| (!self.show_hidden && u.is_hidden()) || !filter.matches(u))\n\t\t} else if self.show_hidden {\n\t\t\t(HashSet::new(), urns)\n\t\t} else {\n\t\t\turns.into_iter().partition(|u| u.is_hidden())\n\t\t};\n\n\t\tlet mut deleted = Vec::with_capacity(items.len());\n\t\tif !items.is_empty() {\n\t\t\tlet mut i = 0;\n\t\t\tself.items.retain(|f| {\n\t\t\t\tlet b = items.remove(&f.urn());\n\t\t\t\tif b {\n\t\t\t\t\tdeleted.push(i);\n\t\t\t\t}\n\t\t\t\ti += 1;\n\t\t\t\t!b\n\t\t\t});\n\t\t}\n\t\tif !hidden.is_empty() {\n\t\t\tself.hidden.retain(|f| !hidden.remove(&f.urn()));\n\t\t}\n\n\t\tself.revision += deleted.is_empty().not() as u64;\n\t\tdeleted\n\t}\n\n\t#[cfg(windows)]\n\tpub fn update_deleting(&mut self, mut urns: HashSet<PathBufDyn>) -> Vec<usize> {\n\t\tlet mut deleted = Vec::with_capacity(urns.len());\n\t\tif !urns.is_empty() {\n\t\t\tlet mut i = 0;\n\t\t\tself.items.retain(|f| {\n\t\t\t\tlet b = urns.remove(&f.urn());\n\t\t\t\tif b {\n\t\t\t\t\tdeleted.push(i)\n\t\t\t\t}\n\t\t\t\ti += 1;\n\t\t\t\t!b\n\t\t\t});\n\t\t}\n\t\tif !urns.is_empty() {\n\t\t\tself.hidden.retain(|f| !urns.remove(&f.urn()));\n\t\t}\n\n\t\tself.revision += deleted.is_empty().not() as u64;\n\t\tdeleted\n\t}\n\n\tpub fn update_updating(\n\t\t&mut self,\n\t\tfiles: HashMap<PathBufDyn, File>,\n\t) -> (HashMap<PathBufDyn, File>, HashMap<PathBufDyn, File>) {\n\t\tif files.is_empty() {\n\t\t\treturn Default::default();\n\t\t}\n\n\t\tmacro_rules! go {\n\t\t\t($dist:expr, $src:expr, $inc:literal) => {\n\t\t\t\tlet mut b = true;\n\t\t\t\tfor i in 0..$dist.len() {\n\t\t\t\t\tif let Some(f) = $src.remove(&$dist[i].urn()) {\n\t\t\t\t\t\tb = b && $dist[i].cha.hits(f.cha);\n\t\t\t\t\t\tb = b && $dist[i].urn() == f.urn();\n\n\t\t\t\t\t\t$dist[i] = f;\n\t\t\t\t\t\tif $src.is_empty() {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tself.revision += if b { 0 } else { $inc };\n\t\t\t};\n\t\t}\n\n\t\tlet (mut hidden, mut items) = if let Some(filter) = &self.filter {\n\t\t\tfiles\n\t\t\t\t.into_iter()\n\t\t\t\t.partition(|(_, f)| (f.is_hidden() && !self.show_hidden) || !filter.matches(f.urn()))\n\t\t} else if self.show_hidden {\n\t\t\t(HashMap::new(), files)\n\t\t} else {\n\t\t\tfiles.into_iter().partition(|(_, f)| f.is_hidden())\n\t\t};\n\n\t\tif !items.is_empty() {\n\t\t\tgo!(self.items, items, 1);\n\t\t}\n\t\tif !hidden.is_empty() {\n\t\t\tgo!(self.hidden, hidden, 0);\n\t\t}\n\t\t(hidden, items)\n\t}\n\n\tpub fn update_upserting(&mut self, files: HashMap<PathBufDyn, File>) {\n\t\tif files.is_empty() {\n\t\t\treturn;\n\t\t}\n\n\t\tself.update_deleting(\n\t\t\tfiles.iter().filter(|&(u, f)| u != f.urn()).map(|(_, f)| f.urn().into()).collect(),\n\t\t);\n\n\t\tlet (hidden, items) = self.update_updating(files);\n\t\tif hidden.is_empty() && items.is_empty() {\n\t\t\treturn;\n\t\t}\n\n\t\tif !hidden.is_empty() {\n\t\t\tself.hidden.extend(hidden.into_values());\n\t\t}\n\t\tif !items.is_empty() {\n\t\t\tself.revision += 1;\n\t\t\tself.items.extend(items.into_values());\n\t\t}\n\t}\n\n\tpub fn catchup_revision(&mut self) -> bool {\n\t\tif self.version == self.revision {\n\t\t\treturn false;\n\t\t}\n\n\t\tself.version = self.revision;\n\t\tself.sorter.sort(&mut self.items, &self.sizes);\n\t\ttrue\n\t}\n\n\tfn split_files(&self, files: impl IntoIterator<Item = File>) -> (Vec<File>, Vec<File>) {\n\t\tif let Some(filter) = &self.filter {\n\t\t\tfiles\n\t\t\t\t.into_iter()\n\t\t\t\t.partition(|f| (f.is_hidden() && !self.show_hidden) || !filter.matches(f.urn()))\n\t\t} else if self.show_hidden {\n\t\t\t(vec![], files.into_iter().collect())\n\t\t} else {\n\t\t\tfiles.into_iter().partition(|f| f.is_hidden())\n\t\t}\n\t}\n}\n\nimpl Files {\n\t// --- Items\n\t#[inline]\n\tpub fn position(&self, urn: PathDyn) -> Option<usize> { self.iter().position(|f| urn == f.urn()) }\n\n\t// --- Ticket\n\t#[inline]\n\tpub fn ticket(&self) -> Id { self.ticket }\n\n\t// --- Sorter\n\t#[inline]\n\tpub fn sorter(&self) -> &FilesSorter { &self.sorter }\n\n\tpub fn set_sorter(&mut self, sorter: FilesSorter) {\n\t\tif self.sorter != sorter {\n\t\t\tself.sorter = sorter;\n\t\t\tself.revision += 1;\n\t\t}\n\t}\n\n\t// --- Filter\n\t#[inline]\n\tpub fn filter(&self) -> Option<&Filter> { self.filter.as_ref() }\n\n\tpub fn set_filter(&mut self, filter: Option<Filter>) -> bool {\n\t\tif self.filter == filter {\n\t\t\treturn false;\n\t\t}\n\n\t\tself.filter = filter;\n\t\tif self.filter.is_none() {\n\t\t\tlet take = mem::take(&mut self.hidden);\n\t\t\tlet (hidden, items) = self.split_files(take);\n\n\t\t\tself.hidden = hidden;\n\t\t\tif !items.is_empty() {\n\t\t\t\tself.items.extend(items);\n\t\t\t\tself.sorter.sort(&mut self.items, &self.sizes);\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\tlet it = mem::take(&mut self.items).into_iter().chain(mem::take(&mut self.hidden));\n\t\t(self.hidden, self.items) = self.split_files(it);\n\t\tself.sorter.sort(&mut self.items, &self.sizes);\n\t\ttrue\n\t}\n\n\t// --- Show hidden\n\tpub fn set_show_hidden(&mut self, state: bool) {\n\t\tif self.show_hidden == state {\n\t\t\treturn;\n\t\t}\n\n\t\tself.show_hidden = state;\n\t\tif self.show_hidden && self.hidden.is_empty() {\n\t\t\treturn;\n\t\t} else if !self.show_hidden && self.items.is_empty() {\n\t\t\treturn;\n\t\t}\n\n\t\tlet take =\n\t\t\tif self.show_hidden { mem::take(&mut self.hidden) } else { mem::take(&mut self.items) };\n\t\tlet (hidden, items) = self.split_files(take);\n\n\t\tself.hidden.extend(hidden);\n\t\tif !items.is_empty() {\n\t\t\tself.revision += 1;\n\t\t\tself.items.extend(items);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/filter.rs",
    "content": "use std::{fmt::Display, ops::Range};\n\nuse anyhow::Result;\nuse regex::bytes::{Regex, RegexBuilder};\nuse yazi_shared::{event::Action, strand::AsStrand};\n\npub struct Filter {\n\traw:   String,\n\tregex: Regex,\n}\n\nimpl Filter {\n\tpub fn new(s: &str, case: FilterCase) -> Result<Self> {\n\t\tlet regex = match case {\n\t\t\tFilterCase::Smart => {\n\t\t\t\tlet uppercase = s.chars().any(|c| c.is_uppercase());\n\t\t\t\tRegexBuilder::new(s).case_insensitive(!uppercase).build()?\n\t\t\t}\n\t\t\tFilterCase::Sensitive => Regex::new(s)?,\n\t\t\tFilterCase::Insensitive => RegexBuilder::new(s).case_insensitive(true).build()?,\n\t\t};\n\t\tOk(Self { raw: s.to_owned(), regex })\n\t}\n\n\t#[inline]\n\t#[allow(private_bounds)]\n\tpub fn matches<T>(&self, name: T) -> bool\n\twhere\n\t\tT: AsStrand,\n\t{\n\t\tself.regex.is_match(name.as_strand().encoded_bytes())\n\t}\n\n\t#[inline]\n\tpub fn highlighted(&self, name: impl AsStrand) -> Option<Vec<Range<usize>>> {\n\t\tself.regex.find(name.as_strand().encoded_bytes()).map(|m| vec![m.range()])\n\t}\n}\n\nimpl PartialEq for Filter {\n\tfn eq(&self, other: &Self) -> bool { self.raw == other.raw }\n}\n\nimpl Display for Filter {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.raw) }\n}\n\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]\npub enum FilterCase {\n\tSmart,\n\t#[default]\n\tSensitive,\n\tInsensitive,\n}\n\nimpl From<&Action> for FilterCase {\n\tfn from(a: &Action) -> Self {\n\t\tmatch (a.bool(\"smart\"), a.bool(\"insensitive\")) {\n\t\t\t(true, _) => Self::Smart,\n\t\t\t(_, false) => Self::Sensitive,\n\t\t\t(_, true) => Self::Insensitive,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/fns.rs",
    "content": "use tokio::io;\nuse yazi_shared::url::{Component, UrlBuf, UrlLike};\n\n#[inline]\npub fn ok_or_not_found<T: Default>(result: io::Result<T>) -> io::Result<T> {\n\tmatch result {\n\t\tOk(t) => Ok(t),\n\t\tErr(e) if e.kind() == io::ErrorKind::NotFound => Ok(T::default()),\n\t\tErr(_) => result,\n\t}\n}\n\n// Find the max common root in a list of urls\n// e.g. /a/b/c, /a/b/d       -> /a/b\n//      /aa/bb/cc, /aa/dd/ee -> /aa\npub fn max_common_root(urls: &[UrlBuf]) -> usize {\n\tif urls.is_empty() {\n\t\treturn 0;\n\t} else if urls.len() == 1 {\n\t\treturn urls[0].components().count() - 1;\n\t}\n\n\tlet mut it = urls.iter().map(|u| u.parent());\n\tlet Some(first) = it.next().unwrap() else {\n\t\treturn 0; // The first URL has no parent\n\t};\n\n\tlet mut common = first.components().count();\n\tfor parent in it {\n\t\tlet Some(parent) = parent else {\n\t\t\treturn 0; // One of the URLs has no parent\n\t\t};\n\n\t\tcommon = first\n\t\t\t.components()\n\t\t\t.zip(parent.components())\n\t\t\t.take_while(|(a, b)| match (a, b) {\n\t\t\t\t(Component::Scheme(a), Component::Scheme(b)) => a.covariant(*b),\n\t\t\t\t(a, b) => a == b,\n\t\t\t})\n\t\t\t.count()\n\t\t\t.min(common);\n\n\t\tif common == 0 {\n\t\t\tbreak; // No common root found\n\t\t}\n\t}\n\n\tcommon\n}\n\n#[cfg(unix)]\n#[test]\nfn test_max_common_root() {\n\tfn assert(input: &[&str], expected: &str) {\n\t\tuse std::{ffi::OsStr, str::FromStr};\n\t\tlet urls: Vec<_> =\n\t\t\tinput.iter().copied().map(UrlBuf::from_str).collect::<Result<_, _>>().unwrap();\n\n\t\tlet mut comp = urls[0].components();\n\t\tfor _ in 0..comp.clone().count() - max_common_root(&urls) {\n\t\t\tcomp.next_back();\n\t\t}\n\t\tassert_eq!(comp.os_str(), OsStr::new(expected));\n\t}\n\n\tassert_eq!(max_common_root(&[]), 0);\n\tassert(&[\"\"], \"\");\n\tassert(&[\"a\"], \"\");\n\n\tassert(&[\"/a\"], \"/\");\n\tassert(&[\"/a/b\"], \"/a\");\n\tassert(&[\"/a/b/c\", \"/a/b/d\"], \"/a/b\");\n\tassert(&[\"/aa/bb/cc\", \"/aa/dd/ee\"], \"/aa\");\n\tassert(&[\"/aa/bb/cc\", \"/aa/bb/cc/dd/ee\", \"/aa/bb/cc/ff\"], \"/aa/bb\");\n}\n"
  },
  {
    "path": "yazi-fs/src/hash.rs",
    "content": "use std::hash::{BuildHasher, Hash};\n\nuse yazi_shared::url::AsUrl;\nuse yazi_shim::Twox128;\n\nuse crate::{File, cha::Cha};\n\npub trait FsHash64 {\n\tfn hash_u64(&self) -> u64;\n}\n\nimpl FsHash64 for File {\n\tfn hash_u64(&self) -> u64 { foldhash::fast::FixedState::default().hash_one(self) }\n}\n\nimpl<T: AsUrl> FsHash64 for T {\n\tfn hash_u64(&self) -> u64 { foldhash::fast::FixedState::default().hash_one(self.as_url()) }\n}\n\n// Hash128\npub trait FsHash128 {\n\tfn hash_u128(&self) -> u128;\n}\n\nimpl FsHash128 for Cha {\n\tfn hash_u128(&self) -> u128 {\n\t\tlet mut h = Twox128::default();\n\n\t\tself.len.hash(&mut h);\n\t\tself.btime_dur().ok().map(|d| d.as_nanos()).hash(&mut h);\n\t\tself.mtime_dur().ok().map(|d| d.as_nanos()).hash(&mut h);\n\n\t\th.finish_128()\n\t}\n}\n\nimpl<T: AsUrl> FsHash128 for T {\n\tfn hash_u128(&self) -> u128 {\n\t\tlet mut h = Twox128::default();\n\t\tself.as_url().hash(&mut h);\n\t\th.finish_128()\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/lib.rs",
    "content": "yazi_macro::mod_pub!(cha error mounts path provider);\n\nyazi_macro::mod_flat!(cwd file files filter fns hash op scheme sorter sorting splatter stage url xdg);\n\npub fn init() {\n\tCWD.init(<_>::default());\n\n\tmounts::init();\n}\n"
  },
  {
    "path": "yazi-fs/src/mounts/linux.rs",
    "content": "use std::{borrow::Cow, ffi::{OsStr, OsString}, os::{fd::AsFd, unix::{ffi::{OsStrExt, OsStringExt}, fs::MetadataExt}}, time::Duration};\n\nuse anyhow::{Context, Result};\nuse hashbrown::{HashMap, HashSet};\nuse tokio::{io::{Interest, unix::AsyncFd}, time::sleep};\nuse tracing::error;\nuse yazi_shared::{natsort, replace_cow, replace_vec_cow};\n\nuse super::{Locked, Partition, Partitions};\n\nimpl Partitions {\n\tpub fn monitor<F>(me: &'static Locked, cb: F)\n\twhere\n\t\tF: Fn() + Copy + Send + 'static,\n\t{\n\t\tasync fn wait_mounts(me: &'static Locked, cb: impl Fn()) -> Result<()> {\n\t\t\tlet f = std::fs::File::open(\"/proc/mounts\")?;\n\t\t\tlet fd = AsyncFd::with_interest(f.as_fd(), Interest::READABLE)?;\n\t\t\tloop {\n\t\t\t\tlet mut guard = fd.readable().await?;\n\t\t\t\tguard.clear_ready();\n\t\t\t\tPartitions::update(me).await;\n\t\t\t\tcb();\n\t\t\t}\n\t\t}\n\n\t\tasync fn wait_partitions(me: &'static Locked, cb: impl Fn()) -> Result<()> {\n\t\t\tloop {\n\t\t\t\tlet partitions = Partitions::partitions()?;\n\t\t\t\tif me.read().linux_cache == partitions {\n\t\t\t\t\tsleep(Duration::from_secs(3)).await;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tme.write().linux_cache = partitions;\n\t\t\t\tPartitions::update(me).await;\n\n\t\t\t\tcb();\n\t\t\t\tsleep(Duration::from_secs(3)).await;\n\t\t\t}\n\t\t}\n\n\t\ttokio::spawn(async move {\n\t\t\tloop {\n\t\t\t\tif let Err(e) = wait_mounts(me, cb).await {\n\t\t\t\t\terror!(\"Error encountered while monitoring /proc/mounts: {e:?}\");\n\t\t\t\t}\n\t\t\t\tsleep(Duration::from_secs(5)).await;\n\t\t\t}\n\t\t});\n\n\t\ttokio::spawn(async move {\n\t\t\tloop {\n\t\t\t\tif let Err(e) = wait_partitions(me, cb).await {\n\t\t\t\t\terror!(\"Error encountered while monitoring /proc/partitions: {e:?}\");\n\t\t\t\t}\n\t\t\t\tsleep(Duration::from_secs(5)).await;\n\t\t\t}\n\t\t});\n\t}\n\n\tfn partitions() -> Result<HashSet<String>> {\n\t\tlet mut set = HashSet::new();\n\t\tlet s = std::fs::read_to_string(\"/proc/partitions\")?;\n\t\tfor line in s.lines().skip(2) {\n\t\t\tlet mut it = line.split_whitespace();\n\t\t\tlet Some(Ok(_major)) = it.next().map(|s| s.parse::<u16>()) else { continue };\n\t\t\tlet Some(Ok(_minor)) = it.next().map(|s| s.parse::<u16>()) else { continue };\n\t\t\tlet Some(Ok(_blocks)) = it.next().map(|s| s.parse::<u32>()) else { continue };\n\t\t\tif let Some(name) = it.next() {\n\t\t\t\tset.insert(Self::unmangle_octal(name).into_owned());\n\t\t\t}\n\t\t}\n\t\tOk(set)\n\t}\n\n\tasync fn update(me: &'static Locked) {\n\t\t_ = tokio::task::spawn_blocking(move || {\n\t\t\tlet mut guard = me.write();\n\t\t\tmatch Self::all(&guard) {\n\t\t\t\tOk(new) => guard.inner = new,\n\t\t\t\tErr(e) => error!(\"Error encountered while updating mount points: {e:?}\"),\n\t\t\t};\n\t\t})\n\t\t.await;\n\t}\n\n\tfn all(&self) -> Result<Vec<Partition>> {\n\t\tlet mut mounts = Self::mounts().context(\"Parsing /proc/mounts\")?;\n\t\t{\n\t\t\tlet set = &self.linux_cache;\n\t\t\tlet mut set: HashSet<&OsStr> = set.iter().map(AsRef::as_ref).collect();\n\t\t\tmounts.iter().filter_map(|p| p.dev_name(true)).for_each(|s| _ = set.remove(s));\n\t\t\tmounts.extend(set.into_iter().map(Partition::new));\n\t\t\tmounts.sort_unstable_by(|a, b| natsort(a.src.as_bytes(), b.src.as_bytes(), false));\n\t\t};\n\n\t\tlet mut removable: HashMap<OsString, Option<bool>> =\n\t\t\tmounts.iter().filter_map(|p| p.dev_name(false)).map(|s| (s.to_owned(), None)).collect();\n\t\tfor (s, b) in &mut removable {\n\t\t\tmatch std::fs::read(format!(\"/sys/block/{}/removable\", s.to_string_lossy()))\n\t\t\t\t.unwrap_or_default()\n\t\t\t\t.trim_ascii()\n\t\t\t{\n\t\t\t\tb\"0\" => *b = Some(false),\n\t\t\t\tb\"1\" => *b = Some(true),\n\t\t\t\t_ => (),\n\t\t\t}\n\t\t}\n\n\t\tlet labels = Self::labels();\n\t\tfor mount in &mut mounts {\n\t\t\tif !mount.src.as_bytes().starts_with(b\"/dev/\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif let Ok(meta) = std::fs::metadata(&mount.src) {\n\t\t\t\tmount.rdev = Some(meta.rdev() as _);\n\t\t\t\tmount.label = labels.get(&(meta.dev(), meta.ino())).cloned();\n\t\t\t\t// TODO: mount.external\n\t\t\t\tmount.removable = mount.dev_name(false).and_then(|s| removable.get(s).copied()).flatten();\n\t\t\t}\n\t\t}\n\t\tOk(mounts)\n\t}\n\n\tfn mounts() -> Result<Vec<Partition>> {\n\t\tlet mut vec = vec![];\n\t\tlet s = std::fs::read_to_string(\"/proc/mounts\")?;\n\t\tfor line in s.lines() {\n\t\t\tlet mut it = line.split_whitespace();\n\t\t\tlet Some(src) = it.next() else { continue };\n\t\t\tlet Some(dist) = it.next() else { continue };\n\t\t\tlet Some(fstype) = it.next() else { continue };\n\t\t\tvec.push(Partition {\n\t\t\t\tsrc: Self::unmangle_octal(src).into_owned().into(),\n\t\t\t\tdist: Some(Self::unmangle_octal(dist).into_owned().into()),\n\t\t\t\tfstype: Some(Self::unmangle_octal(fstype).into_owned().into()),\n\t\t\t\t..Default::default()\n\t\t\t});\n\t\t}\n\t\tOk(vec)\n\t}\n\n\tfn labels() -> HashMap<(u64, u64), OsString> {\n\t\tlet mut map = HashMap::new();\n\t\tlet Ok(it) = std::fs::read_dir(\"/dev/disk/by-label\") else {\n\t\t\terror!(\"Cannot read /dev/disk/by-label\");\n\t\t\treturn map;\n\t\t};\n\n\t\tfor entry in it.flatten() {\n\t\t\tlet Ok(meta) = std::fs::metadata(entry.path()) else { continue };\n\t\t\tlet name = entry.file_name();\n\t\t\tmap.insert(\n\t\t\t\t(meta.dev(), meta.ino()),\n\t\t\t\tmatch replace_vec_cow(name.as_bytes(), br\"\\x20\", b\" \") {\n\t\t\t\t\tCow::Borrowed(_) => name,\n\t\t\t\t\tCow::Owned(v) => OsString::from_vec(v),\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tmap\n\t}\n\n\t// Unmangle '\\t', '\\n', ' ', '#', and r'\\'\n\t// https://elixir.bootlin.com/linux/v6.13-rc3/source/fs/proc_namespace.c#L89\n\tfn unmangle_octal(s: &str) -> Cow<'_, str> {\n\t\tlet mut s = Cow::Borrowed(s);\n\t\tfor (a, b) in\n\t\t\t[(r\"\\011\", \"\\t\"), (r\"\\012\", \"\\n\"), (r\"\\040\", \" \"), (r\"\\043\", \"#\"), (r\"\\134\", r\"\\\")]\n\t\t{\n\t\t\ts = replace_cow(s, a, b);\n\t\t}\n\t\ts\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/mounts/macos.rs",
    "content": "use std::{ffi::{CStr, CString, OsString, c_void}, mem, os::unix::{ffi::OsStringExt, fs::MetadataExt}};\n\nuse anyhow::{Result, bail};\nuse core_foundation_sys::{array::CFArrayRef, base::{CFRelease, kCFAllocatorDefault}, runloop::{CFRunLoopGetCurrent, CFRunLoopRun, kCFRunLoopDefaultMode}};\nuse libc::{c_char, mach_port_t};\nuse objc2::{msg_send, runtime::AnyObject};\nuse scopeguard::defer;\nuse tracing::error;\nuse yazi_ffi::{CFDict, CFString, DADiskCopyDescription, DADiskCreateFromBSDName, DARegisterDiskAppearedCallback, DARegisterDiskDescriptionChangedCallback, DARegisterDiskDisappearedCallback, DASessionCreate, DASessionScheduleWithRunLoop, IOIteratorNext, IOObjectRelease, IORegistryEntryCreateCFProperty, IOServiceGetMatchingServices, IOServiceMatching};\nuse yazi_shared::natsort;\n\nuse super::{Locked, Partition, Partitions};\n\nimpl Partitions {\n\tpub fn monitor<F>(me: &'static Locked, cb: F)\n\twhere\n\t\tF: Fn() + Copy + Send + 'static,\n\t{\n\t\tlet rt = tokio::runtime::Handle::current();\n\t\tstd::thread::spawn(move || {\n\t\t\tlet session = unsafe { DASessionCreate(kCFAllocatorDefault) };\n\t\t\tif session.is_null() {\n\t\t\t\treturn error!(\"Cannot create a disk arbitration session\");\n\t\t\t}\n\t\t\tdefer! { unsafe { CFRelease(session) } };\n\n\t\t\textern \"C\" fn on_appeared(_disk: *const c_void, context: *mut c_void) {\n\t\t\t\tlet boxed = context as *mut Box<dyn Fn()>;\n\t\t\t\tunsafe { (*boxed)() }\n\t\t\t}\n\n\t\t\textern \"C\" fn on_changed(_disk: *const c_void, _keys: CFArrayRef, context: *mut c_void) {\n\t\t\t\tlet boxed = context as *mut Box<dyn Fn()>;\n\t\t\t\tunsafe { (*boxed)() }\n\t\t\t}\n\n\t\t\textern \"C\" fn on_disappeared(_disk: *const c_void, context: *mut c_void) {\n\t\t\t\tlet boxed = context as *mut Box<dyn Fn()>;\n\t\t\t\tunsafe { (*boxed)() }\n\t\t\t}\n\n\t\t\tlet create_context = || {\n\t\t\t\tlet rt = rt.clone();\n\t\t\t\tlet boxed: Box<dyn Fn()> = Box::new(move || {\n\t\t\t\t\tif mem::replace(&mut me.write().need_update, true) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tSelf::update(me, cb, &rt);\n\t\t\t\t});\n\t\t\t\tBox::into_raw(Box::new(boxed)) as *mut c_void\n\t\t\t};\n\n\t\t\tunsafe {\n\t\t\t\tDARegisterDiskAppearedCallback(session, std::ptr::null(), on_appeared, create_context());\n\t\t\t\tDARegisterDiskDescriptionChangedCallback(\n\t\t\t\t\tsession,\n\t\t\t\t\tstd::ptr::null(),\n\t\t\t\t\tstd::ptr::null(),\n\t\t\t\t\ton_changed,\n\t\t\t\t\tcreate_context(),\n\t\t\t\t);\n\t\t\t\tDARegisterDiskDisappearedCallback(\n\t\t\t\t\tsession,\n\t\t\t\t\tstd::ptr::null(),\n\t\t\t\t\ton_disappeared,\n\t\t\t\t\tcreate_context(),\n\t\t\t\t);\n\t\t\t\tDASessionScheduleWithRunLoop(session, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);\n\t\t\t\tCFRunLoopRun();\n\t\t\t}\n\t\t});\n\t}\n\n\tfn update<F>(me: &'static Locked, cb: F, rt: &tokio::runtime::Handle)\n\twhere\n\t\tF: Fn() + Send + 'static,\n\t{\n\t\t_ = rt.spawn_blocking(move || {\n\t\t\tlet result = Self::all_names().and_then(Self::all_partitions);\n\t\t\tif let Err(ref e) = result {\n\t\t\t\terror!(\"Error encountered while updating mount points: {e:?}\");\n\t\t\t}\n\n\t\t\tlet mut guard = me.write();\n\t\t\tif let Ok(new) = result {\n\t\t\t\tguard.inner = new;\n\t\t\t}\n\t\t\tguard.need_update = false;\n\n\t\t\tdrop(guard);\n\t\t\tcb();\n\t\t});\n\t}\n\n\tfn all_partitions(names: Vec<CString>) -> Result<Vec<Partition>> {\n\t\tlet session = unsafe { DASessionCreate(kCFAllocatorDefault) };\n\t\tif session.is_null() {\n\t\t\tbail!(\"Cannot create a disk arbitration session\");\n\t\t}\n\t\tdefer! { unsafe { CFRelease(session) } };\n\n\t\tlet mut disks = Vec::with_capacity(names.len());\n\t\tfor name in names {\n\t\t\tlet disk = unsafe { DADiskCreateFromBSDName(kCFAllocatorDefault, session, name.as_ptr()) };\n\t\t\tif disk.is_null() {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tdefer! { unsafe { CFRelease(disk) } };\n\t\t\tlet Ok(dict) = CFDict::take(unsafe { DADiskCopyDescription(disk) }) else {\n\t\t\t\tcontinue;\n\t\t\t};\n\n\t\t\tlet partition = Partition::new(&OsString::from_vec(name.into_bytes()));\n\t\t\tlet rdev = std::fs::metadata(&partition.src).map(|m| m.rdev() as _).ok();\n\t\t\tdisks.push(Partition {\n\t\t\t\tdist: dict.path_buf(\"DAVolumePath\").ok(),\n\t\t\t\trdev,\n\t\t\t\tfstype: dict.os_string(\"DAVolumeKind\").ok(),\n\t\t\t\tlabel: dict.os_string(\"DAVolumeName\").ok(),\n\t\t\t\tcapacity: dict.integer(\"DAMediaSize\").unwrap_or_default() as u64,\n\t\t\t\texternal: dict.bool(\"DADeviceInternal\").ok().map(|b| !b),\n\t\t\t\tremovable: dict.bool(\"DAMediaRemovable\").ok(),\n\t\t\t\t..partition\n\t\t\t});\n\t\t}\n\n\t\tOk(disks)\n\t}\n\n\tfn all_names() -> Result<Vec<CString>> {\n\t\tlet mut iterator: mach_port_t = 0;\n\t\tlet result = unsafe {\n\t\t\tIOServiceGetMatchingServices(0, IOServiceMatching(c\"IOService\".as_ptr()), &mut iterator)\n\t\t};\n\n\t\tif result != 0 {\n\t\t\tbail!(\"Cannot get the IO matching services\");\n\t\t}\n\t\tdefer! { unsafe { IOObjectRelease(iterator); } };\n\n\t\tlet mut names = vec![];\n\t\tloop {\n\t\t\tlet service = unsafe { IOIteratorNext(iterator) };\n\t\t\tif service == 0 {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefer! { unsafe { IOObjectRelease(service); } };\n\t\t\tif let Some(name) = Self::bsd_name(service).ok().filter(|s| s.as_bytes().starts_with(b\"disk\"))\n\t\t\t{\n\t\t\t\tnames.push(name);\n\t\t\t}\n\t\t}\n\n\t\tnames.sort_unstable_by(|a, b| natsort(a.as_bytes(), b.as_bytes(), false));\n\t\tOk(names)\n\t}\n\n\tfn bsd_name(service: mach_port_t) -> Result<CString> {\n\t\tlet key = CFString::new(\"BSD Name\")?;\n\t\tlet property =\n\t\t\tunsafe { IORegistryEntryCreateCFProperty(service, *key, kCFAllocatorDefault, 1) };\n\t\tif property.is_null() {\n\t\t\tbail!(\"Cannot get the name property\");\n\t\t}\n\t\tdefer! { unsafe { CFRelease(property) } };\n\n\t\t#[allow(unexpected_cfgs)]\n\t\tlet cstr: *const c_char = unsafe { msg_send![property as *const AnyObject, UTF8String] };\n\t\tOk(if cstr.is_null() {\n\t\t\tbail!(\"Invalid value for the name property\");\n\t\t} else {\n\t\t\tCString::from(unsafe { CStr::from_ptr(cstr) })\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/mounts/mod.rs",
    "content": "yazi_macro::mod_flat!(partition partitions);\n\n#[cfg(target_os = \"linux\")]\nyazi_macro::mod_flat!(linux);\n\n#[cfg(target_os = \"macos\")]\nyazi_macro::mod_flat!(macos);\n\npub(super) fn init() { PARTITIONS.init(<_>::default()); }\n"
  },
  {
    "path": "yazi-fs/src/mounts/partition.rs",
    "content": "use std::{ffi::OsString, path::PathBuf};\n\n#[derive(Debug, Default)]\npub struct Partition {\n\tpub src:       OsString,\n\tpub dist:      Option<PathBuf>,\n\t#[cfg(unix)]\n\tpub rdev:      Option<u64>,\n\tpub label:     Option<OsString>,\n\tpub fstype:    Option<OsString>,\n\tpub capacity:  u64,\n\tpub external:  Option<bool>,\n\tpub removable: Option<bool>,\n}\n\nimpl Partition {\n\t// Match mount types that do not update directory mtime on changes,\n\t// and should be refreshed frequently.\n\tpub fn timeless(&self) -> bool {\n\t\tlet b: &[u8] = self.fstype.as_ref().map_or(b\"\", |s| s.as_encoded_bytes());\n\t\tmatches!(b, b\"exfat\")\n\t}\n\n\t// Match mount types that do not reliably emit change notifications,\n\t// and should be polled for changes.\n\tpub fn soundless(&self) -> bool {\n\t\tlet b: &[u8] = self.fstype.as_ref().map_or(b\"\", |s| s.as_encoded_bytes());\n\t\tmatches!(b, b\"fuse.rclone\")\n\t}\n\n\t#[rustfmt::skip]\n\tpub fn systemic(&self) -> bool {\n\t\tlet _b: &[u8] = self.fstype.as_ref().map_or(b\"\", |s| s.as_encoded_bytes());\n\t\t#[cfg(target_os = \"linux\")]\n\t\t{\n\t\t\tmatches!(_b, b\"autofs\" | b\"binfmt_misc\" | b\"bpf\" | b\"cgroup2\" | b\"configfs\" | b\"debugfs\" | b\"devpts\" | b\"devtmpfs\" | b\"fuse.gvfsd-fuse\" | b\"fusectl\" | b\"hugetlbfs\" | b\"mqueue\" | b\"proc\" | b\"pstore\" | b\"ramfs\" | b\"securityfs\" | b\"sysfs\" | b\"tmpfs\" | b\"tracefs\")\n\t\t}\n\t\t#[cfg(target_os = \"macos\")]\n\t\t{\n\t\t\t_b.is_empty()\n\t\t}\n\t\t#[cfg(not(any(target_os = \"linux\", target_os = \"macos\")))]\n\t\t{\n\t\t\tfalse\n\t\t}\n\t}\n}\n\nimpl Partition {\n\t#[cfg(any(target_os = \"linux\", target_os = \"macos\"))]\n\tpub(super) fn new(name: &std::ffi::OsStr) -> Self {\n\t\tSelf { src: std::path::Path::new(\"/dev/\").join(name).into(), ..Default::default() }\n\t}\n\n\t#[cfg(target_os = \"linux\")]\n\tpub(super) fn dev_name(&self, full: bool) -> Option<&std::ffi::OsStr> {\n\t\tuse std::os::unix::ffi::OsStrExt;\n\n\t\tlet s = std::path::Path::new(&self.src).strip_prefix(\"/dev/\").ok()?.as_os_str();\n\t\tif full {\n\t\t\treturn Some(s);\n\t\t}\n\n\t\tlet b = s.as_bytes();\n\t\tif b.len() < 3 {\n\t\t\tNone\n\t\t} else if b.starts_with(b\"sd\") || b.starts_with(b\"hd\") || b.starts_with(b\"vd\") {\n\t\t\tSome(std::ffi::OsStr::from_bytes(&b[..3]))\n\t\t} else if b.starts_with(b\"nvme\") || b.starts_with(b\"mmcblk\") {\n\t\t\tlet n = b.iter().position(|&b| b == b'p').unwrap_or(b.len());\n\t\t\tSome(std::ffi::OsStr::from_bytes(&b[..n]))\n\t\t} else {\n\t\t\tNone\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/mounts/partitions.rs",
    "content": "use std::ops::Deref;\n\nuse parking_lot::RwLock;\nuse yazi_shared::RoCell;\n\nuse super::Partition;\nuse crate::cha::Cha;\n\npub(super) type Locked = RwLock<Partitions>;\n\npub static PARTITIONS: RoCell<Locked> = RoCell::new();\n\n#[derive(Default)]\npub struct Partitions {\n\tpub(super) inner:       Vec<Partition>,\n\t#[cfg(target_os = \"linux\")]\n\tpub(super) linux_cache: hashbrown::HashSet<String>,\n\t#[cfg(target_os = \"macos\")]\n\tpub(super) need_update: bool,\n}\n\nimpl Deref for Partitions {\n\ttype Target = Vec<Partition>;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl Partitions {\n\t#[cfg(unix)]\n\tpub fn by_dev(&self, dev: u64) -> Option<&Partition> {\n\t\tself.inner.iter().find(|p| p.rdev == Some(dev))\n\t}\n\n\tpub fn timeless(&self, _cha: Cha) -> bool {\n\t\t#[cfg(any(target_os = \"linux\", target_os = \"macos\"))]\n\t\t{\n\t\t\tself.by_dev(_cha.dev).is_some_and(|p| p.timeless())\n\t\t}\n\t\t#[cfg(not(any(target_os = \"linux\", target_os = \"macos\")))]\n\t\t{\n\t\t\t// For now, assume other targets update directory mtime correctly\n\t\t\tfalse\n\t\t}\n\t}\n\n\tpub fn soundless(&self, _cha: Cha) -> bool {\n\t\t#[cfg(any(target_os = \"linux\", target_os = \"macos\"))]\n\t\t{\n\t\t\tself.by_dev(_cha.dev).is_some_and(|p| p.soundless())\n\t\t}\n\t\t#[cfg(not(any(target_os = \"linux\", target_os = \"macos\")))]\n\t\t{\n\t\t\t// For now, assume other targets emit change notifications correctly\n\t\t\tfalse\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/op.rs",
    "content": "use std::path::Path;\n\nuse hashbrown::{HashMap, HashSet};\nuse yazi_macro::relay;\nuse yazi_shared::{Id, Ids, path::PathBufDyn, url::{UrlBuf, UrlLike}};\n\nuse super::File;\nuse crate::{cha::Cha, error::Error};\n\npub static FILES_TICKET: Ids = Ids::new();\n\n#[derive(Clone, Debug)]\npub enum FilesOp {\n\tFull(UrlBuf, Vec<File>, Cha),\n\tPart(UrlBuf, Vec<File>, Id),\n\tDone(UrlBuf, Cha, Id),\n\tSize(UrlBuf, HashMap<PathBufDyn, u64>),\n\tIOErr(UrlBuf, Error),\n\n\tCreating(UrlBuf, Vec<File>),\n\tDeleting(UrlBuf, HashSet<PathBufDyn>),\n\tUpdating(UrlBuf, HashMap<PathBufDyn, File>),\n\tUpserting(UrlBuf, HashMap<PathBufDyn, File>),\n}\n\nimpl FilesOp {\n\t#[inline]\n\tpub fn cwd(&self) -> &UrlBuf {\n\t\tmatch self {\n\t\t\tSelf::Full(u, ..) => u,\n\t\t\tSelf::Part(u, ..) => u,\n\t\t\tSelf::Done(u, ..) => u,\n\t\t\tSelf::Size(u, _) => u,\n\t\t\tSelf::IOErr(u, _) => u,\n\n\t\t\tSelf::Creating(u, _) => u,\n\t\t\tSelf::Deleting(u, _) => u,\n\t\t\tSelf::Updating(u, _) => u,\n\t\t\tSelf::Upserting(u, _) => u,\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn emit(self) {\n\t\tyazi_shared::event::Event::Call(relay!(mgr:update_files).with_any(\"op\", self).into()).emit();\n\t}\n\n\tpub fn prepare(cwd: &UrlBuf) -> Id {\n\t\tlet ticket = FILES_TICKET.next();\n\t\tSelf::Part(cwd.clone(), vec![], ticket).emit();\n\t\tticket\n\t}\n\n\tpub fn rename(map: HashMap<UrlBuf, File>) {\n\t\tlet mut parents: HashMap<_, (HashSet<_>, HashMap<_, _>)> = Default::default();\n\t\tfor (o, n) in map {\n\t\t\tlet Some(o_p) = o.parent() else { continue };\n\t\t\tlet Some(n_p) = n.url.parent() else { continue };\n\t\t\tif o_p == n_p {\n\t\t\t\tparents.entry_ref(&o_p).or_default().1.insert(o.urn().into(), n);\n\t\t\t} else {\n\t\t\t\tparents.entry_ref(&o_p).or_default().0.insert(o.urn().into());\n\t\t\t\tparents.entry_ref(&n_p).or_default().1.insert(n.urn().into(), n);\n\t\t\t}\n\t\t}\n\t\tfor (p, (o, n)) in parents {\n\t\t\tmatch (o.is_empty(), n.is_empty()) {\n\t\t\t\t(true, true) => unreachable!(),\n\t\t\t\t(true, false) => Self::Upserting(p, n).emit(),\n\t\t\t\t(false, true) => Self::Deleting(p, o).emit(),\n\t\t\t\t(false, false) => {\n\t\t\t\t\tSelf::Deleting(p.clone(), o).emit();\n\t\t\t\t\tSelf::Upserting(p, n).emit();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn mutate(ops: Vec<Self>) {\n\t\tlet mut parents: HashMap<_, (HashMap<_, _>, HashSet<_>)> = Default::default();\n\t\tfor op in ops {\n\t\t\tmatch op {\n\t\t\t\tSelf::Upserting(p, map) => parents.entry(p).or_default().0.extend(map),\n\t\t\t\tSelf::Deleting(p, urns) => parents.entry(p).or_default().1.extend(urns),\n\t\t\t\t_ => unreachable!(),\n\t\t\t}\n\t\t}\n\t\tfor (p, (u, d)) in parents {\n\t\t\tmatch (u.is_empty(), d.is_empty()) {\n\t\t\t\t(true, true) => unreachable!(),\n\t\t\t\t(true, false) => Self::Deleting(p, d).emit(),\n\t\t\t\t(false, true) => Self::Upserting(p, u).emit(),\n\t\t\t\t(false, false) => {\n\t\t\t\t\tSelf::Deleting(p.clone(), d).emit();\n\t\t\t\t\tSelf::Upserting(p, u).emit();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpub fn chdir(&self, wd: &Path) -> Self {\n\t\tmacro_rules! files {\n\t\t\t($files:expr) => {{ $files.iter().map(|file| file.chdir(wd)).collect() }};\n\t\t}\n\t\tmacro_rules! map {\n\t\t\t($map:expr) => {{ $map.iter().map(|(urn, file)| (urn.clone(), file.chdir(wd))).collect() }};\n\t\t}\n\n\t\tlet w = UrlBuf::from(wd);\n\t\tmatch self {\n\t\t\tSelf::Full(_, files, cha) => Self::Full(w, files!(files), *cha),\n\t\t\tSelf::Part(_, files, ticket) => Self::Part(w, files!(files), *ticket),\n\t\t\tSelf::Done(_, cha, ticket) => Self::Done(w, *cha, *ticket),\n\t\t\tSelf::Size(_, map) => Self::Size(w, map.iter().map(|(urn, &s)| (urn.clone(), s)).collect()),\n\t\t\tSelf::IOErr(_, err) => Self::IOErr(w, err.clone()),\n\n\t\t\tSelf::Creating(_, files) => Self::Creating(w, files!(files)),\n\t\t\tSelf::Deleting(_, urns) => Self::Deleting(w, urns.clone()),\n\t\t\tSelf::Updating(_, map) => Self::Updating(w, map!(map)),\n\t\t\tSelf::Upserting(_, map) => Self::Upserting(w, map!(map)),\n\t\t}\n\t}\n\n\tpub fn diff_recoverable(&self, contains: impl Fn(&UrlBuf) -> bool) -> (Vec<UrlBuf>, Vec<UrlBuf>) {\n\t\tmatch self {\n\t\t\tSelf::Deleting(cwd, urns) => {\n\t\t\t\t(urns.iter().filter_map(|u| cwd.try_join(u).ok()).collect(), vec![])\n\t\t\t}\n\t\t\tSelf::Updating(cwd, urns) | Self::Upserting(cwd, urns) => urns\n\t\t\t\t.iter()\n\t\t\t\t.filter(|&(u, f)| u != f.urn())\n\t\t\t\t.filter_map(|(u, f)| cwd.try_join(u).ok().map(|u| (u, f)))\n\t\t\t\t.filter(|(u, _)| contains(u))\n\t\t\t\t.map(|(u, f)| (u, f.url_owned()))\n\t\t\t\t.unzip(),\n\t\t\t_ => (vec![], vec![]),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/path/clean.rs",
    "content": "use yazi_shared::{path::{PathBufDyn, PathDyn}, url::{UrlBuf, UrlCow, UrlLike}};\n\npub fn clean_url<'a>(url: impl Into<UrlCow<'a>>) -> UrlBuf {\n\tlet cow: UrlCow = url.into();\n\tlet (path, uri, urn) = clean_path_impl(\n\t\tcow.loc(),\n\t\tcow.base().components().count() - 1,\n\t\tcow.trail().components().count() - 1,\n\t);\n\n\tlet scheme = cow.into_scheme().into_owned().with_ports(uri, urn);\n\t(scheme, path).try_into().expect(\"UrlBuf from cleaned path\")\n}\n\nfn clean_path_impl(path: PathDyn, base: usize, trail: usize) -> (PathBufDyn, usize, usize) {\n\tuse yazi_shared::path::Component::*;\n\n\tlet mut out = vec![];\n\tlet mut uri_count = 0;\n\tlet mut urn_count = 0;\n\n\tmacro_rules! push {\n\t\t($i:ident, $c:ident) => {{\n\t\t\tout.push(($i, $c));\n\t\t\tif $i >= base {\n\t\t\t\turi_count += 1;\n\t\t\t}\n\t\t\tif $i >= trail {\n\t\t\t\turn_count += 1;\n\t\t\t}\n\t\t}};\n\t}\n\n\tmacro_rules! pop {\n\t\t() => {{\n\t\t\tif let Some((i, _)) = out.pop() {\n\t\t\t\tif i >= base {\n\t\t\t\t\turi_count -= 1;\n\t\t\t\t}\n\t\t\t\tif i >= trail {\n\t\t\t\t\turn_count -= 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}};\n\t}\n\n\tfor (i, c) in path.components().enumerate() {\n\t\tmatch c {\n\t\t\tCurDir => {}\n\t\t\tParentDir => match out.last().map(|(_, c)| c) {\n\t\t\t\tSome(RootDir) => {}\n\t\t\t\tSome(Normal(_)) => pop!(),\n\t\t\t\tNone | Some(CurDir) | Some(ParentDir) | Some(Prefix(_)) => push!(i, c),\n\t\t\t},\n\t\t\tc => push!(i, c),\n\t\t}\n\t}\n\n\tlet kind = path.kind();\n\tlet path = if out.is_empty() {\n\t\tPathBufDyn::with_str(kind, \".\")\n\t} else {\n\t\tPathBufDyn::from_components(kind, out.into_iter().map(|(_, c)| c))\n\t\t\t.expect(\"components with same kind\")\n\t};\n\n\t(path, uri_count, urn_count)\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\n\t#[test]\n\tfn test_clean_url() -> anyhow::Result<()> {\n\t\tyazi_shared::init_tests();\n\t\tlet cases = [\n\t\t\t// CurDir\n\t\t\t(\"archive://:3//./tmp/test.zip/foo/bar\", \"archive://:3//tmp/test.zip/foo/bar\"),\n\t\t\t(\"archive://:3//tmp/./test.zip/foo/bar\", \"archive://:3//tmp/test.zip/foo/bar\"),\n\t\t\t(\"archive://:3//tmp/./test.zip/./foo/bar\", \"archive://:3//tmp/test.zip/foo/bar\"),\n\t\t\t(\"archive://:3//tmp/./test.zip/./foo/./bar/.\", \"archive://:3//tmp/test.zip/foo/bar\"),\n\t\t\t// ParentDir\n\t\t\t(\"archive://:3:2//../../tmp/test.zip/foo/bar\", \"archive://:3:2//tmp/test.zip/foo/bar\"),\n\t\t\t(\"archive://:3:2//tmp/../../test.zip/foo/bar\", \"archive://:3:2//test.zip/foo/bar\"),\n\t\t\t(\"archive://:4:2//tmp/test.zip/../../foo/bar\", \"archive://:2:2//foo/bar\"),\n\t\t\t(\"archive://:5:2//tmp/test.zip/../../foo/bar\", \"archive://:2:2//foo/bar\"),\n\t\t\t(\"archive://:4:4//tmp/test.zip/foo/bar/../../\", \"archive:////tmp/test.zip\"),\n\t\t\t(\"archive://:5:4//tmp/test.zip/foo/bar/../../\", \"archive://:1//tmp/test.zip\"),\n\t\t\t(\"archive://:4:4//tmp/test.zip/foo/bar/../../../\", \"archive:////tmp\"),\n\t\t\t(\"sftp://test//root/.config/yazi/../../Downloads\", \"sftp://test//root/Downloads\"),\n\t\t];\n\n\t\tfor (input, expected) in cases {\n\t\t\tlet input: UrlBuf = input.parse()?;\n\t\t\t#[cfg(unix)]\n\t\t\tassert_eq!(format!(\"{:?}\", clean_url(input)), expected);\n\t\t\t#[cfg(windows)]\n\t\t\tassert_eq!(format!(\"{:?}\", clean_url(input)).replace(r\"\\\", \"/\"), expected.replace(r\"\\\", \"/\"));\n\t\t}\n\t\tOk(())\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/path/expand.rs",
    "content": "use std::borrow::Cow;\n\nuse yazi_shared::{loc::LocBuf, path::{PathBufDyn, PathCow, PathKind, PathLike}, pool::InternStr, url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike}, wtf8::FromWtf8Vec};\n\n#[inline]\npub fn expand_url<'a>(url: impl Into<UrlCow<'a>>) -> UrlCow<'a> { expand_url_impl(url.into()) }\n\nfn expand_url_impl(url: UrlCow) -> UrlCow {\n\tlet (o_base, o_rest, o_urn) = url.triple();\n\n\tlet n_base = expand_variables(o_base.into());\n\tlet n_rest = expand_variables(o_rest.into());\n\tlet n_urn = expand_variables(o_urn.into());\n\tif n_base.is_borrowed() && n_rest.is_borrowed() && n_urn.is_borrowed() {\n\t\treturn url;\n\t}\n\n\tlet rest_diff = n_rest.components().count() as isize - o_rest.components().count() as isize;\n\tlet urn_diff = n_urn.components().count() as isize - o_urn.components().count() as isize;\n\n\tlet uri_count = url.uri().components().count() as isize;\n\tlet urn_count = url.urn().components().count() as isize;\n\n\tlet mut path = PathBufDyn::with_capacity(url.kind(), n_base.len() + n_rest.len() + n_urn.len());\n\tpath.try_extend([n_base, n_rest, n_urn]).expect(\"extend original parts should not fail\");\n\n\tlet uri = (uri_count + rest_diff + urn_diff) as usize;\n\tlet urn = (urn_count + urn_diff) as usize;\n\n\tmatch url.as_url() {\n\t\tUrl::Regular(_) => UrlBuf::Regular(\n\t\t\tLocBuf::<std::path::PathBuf>::with(path.into_os().unwrap(), uri, urn).unwrap(),\n\t\t),\n\t\tUrl::Search { domain, .. } => UrlBuf::Search {\n\t\t\tloc:    LocBuf::<std::path::PathBuf>::with(path.into_os().unwrap(), uri, urn).unwrap(),\n\t\t\tdomain: domain.intern(),\n\t\t},\n\t\tUrl::Archive { domain, .. } => UrlBuf::Archive {\n\t\t\tloc:    LocBuf::<std::path::PathBuf>::with(path.into_os().unwrap(), uri, urn).unwrap(),\n\t\t\tdomain: domain.intern(),\n\t\t},\n\t\tUrl::Sftp { domain, .. } => UrlBuf::Sftp {\n\t\t\tloc:    LocBuf::<typed_path::UnixPathBuf>::with(path.into_unix().unwrap(), uri, urn).unwrap(),\n\t\t\tdomain: domain.intern(),\n\t\t},\n\t}\n\t.into()\n}\n\nfn expand_variables(p: PathCow) -> PathCow {\n\t// ${HOME} or $HOME\n\t#[cfg(unix)]\n\tlet re = regex::bytes::Regex::new(r\"\\$(?:\\{([^}]+)\\}|([a-zA-Z\\d_]+))\").unwrap();\n\n\t// %USERPROFILE%\n\t#[cfg(windows)]\n\tlet re = regex::bytes::Regex::new(r\"%([^%]+)%\").unwrap();\n\n\tlet b = p.encoded_bytes();\n\tlet b = re.replace_all(b, |caps: &regex::bytes::Captures| {\n\t\tlet name = caps.get(2).or_else(|| caps.get(1)).unwrap();\n\t\tstr::from_utf8(name.as_bytes())\n\t\t\t.ok()\n\t\t\t.and_then(std::env::var_os)\n\t\t\t.map_or_else(|| caps.get(0).unwrap().as_bytes().to_owned(), |s| s.into_encoded_bytes())\n\t});\n\n\tmatch (b, p.kind()) {\n\t\t(Cow::Borrowed(_), _) => p,\n\t\t(Cow::Owned(b), PathKind::Os) => {\n\t\t\tPathBufDyn::Os(std::path::PathBuf::from_wtf8_vec(b).expect(\"valid WTF-8 path\")).into()\n\t\t}\n\t\t(Cow::Owned(b), PathKind::Unix) => PathBufDyn::Unix(b.into()).into(),\n\t}\n}\n\n#[cfg(test)]\nmod tests {\n\tuse anyhow::Result;\n\n\tuse super::*;\n\n\t#[cfg(unix)]\n\t#[test]\n\tfn test_expand_url() -> Result<()> {\n\t\tyazi_shared::init_tests();\n\t\tunsafe {\n\t\t\tstd::env::set_var(\"FOO\", \"foo\");\n\t\t\tstd::env::set_var(\"BAR_BAZ\", \"bar/baz\");\n\t\t\tstd::env::set_var(\"BAR/BAZ\", \"bar_baz\");\n\t\t\tstd::env::set_var(\"EM/PT/Y\", \"\");\n\t\t}\n\n\t\tlet cases = [\n\t\t\t// Zero extra component expanded\n\t\t\t(\"archive:////tmp/test.zip/$FOO/bar\", \"archive:////tmp/test.zip/foo/bar\"),\n\t\t\t(\"archive://:1//tmp/test.zip/$FOO/bar\", \"archive://:1//tmp/test.zip/foo/bar\"),\n\t\t\t(\"archive://:2//tmp/test.zip/bar/$FOO\", \"archive://:2//tmp/test.zip/bar/foo\"),\n\t\t\t(\"archive://:3//tmp/test.zip/$FOO/bar\", \"archive://:3//tmp/test.zip/foo/bar\"),\n\t\t\t(\"archive://:3:1//tmp/test.zip/bar/$FOO\", \"archive://:3:1//tmp/test.zip/bar/foo\"),\n\t\t\t(\"archive://:3:2//tmp/test.zip/$FOO/bar\", \"archive://:3:2//tmp/test.zip/foo/bar\"),\n\t\t\t(\"archive://:3:3//tmp/test.zip/bar/$FOO\", \"archive://:3:3//tmp/test.zip/bar/foo\"),\n\t\t\t// +1 component\n\t\t\t(\"archive:////tmp/test.zip/$BAR_BAZ\", \"archive:////tmp/test.zip/bar/baz\"),\n\t\t\t(\"archive://:1//tmp/test.zip/$BAR_BAZ\", \"archive://:2//tmp/test.zip/bar/baz\"),\n\t\t\t(\"archive://:2//$BAR_BAZ/tmp/test.zip\", \"archive://:2//bar/baz/tmp/test.zip\"),\n\t\t\t(\"archive://:2:1//tmp/test.zip/$BAR_BAZ\", \"archive://:3:2//tmp/test.zip/bar/baz\"),\n\t\t\t(\"archive://:2:2//tmp/$BAR_BAZ/test.zip\", \"archive://:3:3//tmp/bar/baz/test.zip\"),\n\t\t\t(\"archive://:2:2//$BAR_BAZ/tmp/test.zip\", \"archive://:2:2//bar/baz/tmp/test.zip\"),\n\t\t\t// -1 component\n\t\t\t(\"archive:////tmp/test.zip/${BAR/BAZ}\", \"archive:////tmp/test.zip/bar_baz\"),\n\t\t\t(\"archive://:1//tmp/test.zip/${BAR/BAZ}\", \"archive://:1//tmp/test.zip/${BAR/BAZ}\"),\n\t\t\t(\"archive://:1//tmp/${BAR/BAZ}/test.zip\", \"archive://:1//tmp/bar_baz/test.zip\"),\n\t\t\t(\"archive://:2//tmp/test.zip/${BAR/BAZ}\", \"archive://:1//tmp/test.zip/bar_baz\"),\n\t\t\t(\"archive://:2//tmp/${BAR/BAZ}/test.zip\", \"archive://:2//tmp/${BAR/BAZ}/test.zip\"),\n\t\t\t(\"archive://:2:1//tmp/test.zip/${BAR/BAZ}\", \"archive://:2:1//tmp/test.zip/${BAR/BAZ}\"),\n\t\t\t(\"archive://:2:1//tmp/${BAR/BAZ}/test.zip\", \"archive://:2:1//tmp/${BAR/BAZ}/test.zip\"),\n\t\t\t(\"archive://:2:1//${BAR/BAZ}/tmp/test.zip\", \"archive://:2:1//bar_baz/tmp/test.zip\"),\n\t\t\t(\"archive://:3:2//tmp/test.zip/${BAR/BAZ}\", \"archive://:2:1//tmp/test.zip/bar_baz\"),\n\t\t\t(\"archive://:3:2//tmp/${BAR/BAZ}/test.zip\", \"archive://:3:2//tmp/${BAR/BAZ}/test.zip\"),\n\t\t\t(\"archive://:3:3//tmp/test.zip/${BAR/BAZ}\", \"archive://:2:2//tmp/test.zip/bar_baz\"),\n\t\t\t(\"archive://:3:3//tmp/${BAR/BAZ}/test.zip\", \"archive://:2:2//tmp/bar_baz/test.zip\"),\n\t\t\t// Zeros all components\n\t\t\t(\"archive:////${EM/PT/Y}\", \"archive:////\"),\n\t\t\t(\"archive://:1//${EM/PT/Y}\", \"archive://:1//${EM/PT/Y}\"),\n\t\t\t(\"archive://:2//${EM/PT/Y}\", \"archive://:2//${EM/PT/Y}\"),\n\t\t\t(\"archive://:3//${EM/PT/Y}\", \"archive:////\"),\n\t\t\t(\"archive://:4//${EM/PT/Y}\", \"archive://:1//\"),\n\t\t];\n\n\t\tfor (input, expected) in cases {\n\t\t\tlet u: UrlBuf = input.parse()?;\n\t\t\tassert_eq!(format!(\"{:?}\", expand_url(u).as_url()), expected);\n\t\t}\n\n\t\tOk(())\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/path/mod.rs",
    "content": "yazi_macro::mod_flat!(clean expand path percent relative);\n"
  },
  {
    "path": "yazi-fs/src/path/path.rs",
    "content": "use yazi_shared::{strand::StrandCow, url::{UrlBuf, UrlLike}};\n\npub fn skip_url(url: &UrlBuf, n: usize) -> StrandCow<'_> {\n\tlet mut it = url.components();\n\tfor _ in 0..n {\n\t\tif it.next().is_none() {\n\t\t\treturn StrandCow::default();\n\t\t}\n\t}\n\tit.strand()\n}\n"
  },
  {
    "path": "yazi-fs/src/path/percent.rs",
    "content": "use std::{borrow::Cow, path::{Path, PathBuf}};\n\nuse anyhow::Result;\nuse percent_encoding::{AsciiSet, CONTROLS, percent_decode, percent_encode};\nuse yazi_shared::path::{PathCow, PathDyn, PathKind};\n\nconst SET: &AsciiSet =\n\t&CONTROLS.add(b'\"').add(b'*').add(b':').add(b'<').add(b'>').add(b'?').add(b'\\\\').add(b'|');\n\npub trait PercentEncoding<'a> {\n\tfn percent_encode(self) -> Cow<'a, Path>;\n\n\tfn percent_decode<K>(self, kind: K) -> Result<PathCow<'a>>\n\twhere\n\t\tK: Into<PathKind>;\n}\n\nimpl<'a> PercentEncoding<'a> for PathDyn<'a> {\n\tfn percent_encode(self) -> Cow<'a, Path> {\n\t\tmatch percent_encode(self.encoded_bytes(), SET).into() {\n\t\t\tCow::Borrowed(s) => Path::new(s).into(),\n\t\t\tCow::Owned(s) => PathBuf::from(s).into(),\n\t\t}\n\t}\n\n\tfn percent_decode<K>(self, kind: K) -> Result<PathCow<'a>>\n\twhere\n\t\tK: Into<PathKind>,\n\t{\n\t\tPathCow::with(kind, percent_decode(self.encoded_bytes()))\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/path/relative.rs",
    "content": "use anyhow::{Result, bail};\nuse yazi_shared::path::{PathBufDyn, PathCow, PathDyn, PathLike};\n\npub fn path_relative_to<'a, 'b, P, Q>(from: P, to: Q) -> Result<PathCow<'b>>\nwhere\n\tP: Into<PathCow<'a>>,\n\tQ: Into<PathCow<'b>>,\n{\n\tpath_relative_to_impl(from.into(), to.into())\n}\n\nfn path_relative_to_impl<'a>(from: PathCow<'_>, to: PathCow<'a>) -> Result<PathCow<'a>> {\n\tuse yazi_shared::path::Component::*;\n\n\tif from.is_absolute() != to.is_absolute() {\n\t\treturn if to.is_absolute() {\n\t\t\tOk(to)\n\t\t} else {\n\t\t\tbail!(\"Paths must be both absolute or both relative: {from:?} and {to:?}\");\n\t\t};\n\t}\n\n\tif from == to {\n\t\treturn Ok(PathDyn::with_str(from.kind(), \".\").into());\n\t}\n\n\tlet (mut f_it, mut t_it) = (from.components(), to.components());\n\tlet (f_head, t_head) = loop {\n\t\tmatch (f_it.next(), t_it.next()) {\n\t\t\t(Some(RootDir), Some(RootDir)) => {}\n\t\t\t(Some(Prefix(a)), Some(Prefix(b))) if a == b => {}\n\t\t\t(Some(Prefix(_) | RootDir), _) | (_, Some(Prefix(_) | RootDir)) => {\n\t\t\t\treturn Ok(to);\n\t\t\t}\n\t\t\t(None, None) => break (None, None),\n\t\t\t(a, b) if a != b => break (a, b),\n\t\t\t_ => (),\n\t\t}\n\t};\n\n\tlet dots = f_head.into_iter().chain(f_it).map(|_| ParentDir);\n\tlet rest = t_head.into_iter().chain(t_it);\n\n\tlet buf = PathBufDyn::from_components(from.kind(), dots.chain(rest))?;\n\tOk(buf.into())\n}\n\n#[cfg(test)]\nmod tests {\n\tuse yazi_shared::path::PathDyn;\n\n\tuse super::*;\n\n\t#[test]\n\tfn test_path_relative_to() {\n\t\tyazi_shared::init_tests();\n\n\t\t#[cfg(unix)]\n\t\tlet cases = [\n\t\t\t// Same paths\n\t\t\t(\"\", \"\", \".\"),\n\t\t\t(\".\", \".\", \".\"),\n\t\t\t(\"/\", \"/\", \".\"),\n\t\t\t(\"/a\", \"/a\", \".\"),\n\t\t\t// Relative paths\n\t\t\t(\"foo\", \"bar\", \"../bar\"),\n\t\t\t// Absolute paths\n\t\t\t(\"/a/b\", \"/a/b/c\", \"c\"),\n\t\t\t(\"/a/b/c\", \"/a/b\", \"..\"),\n\t\t\t(\"/a/b/d\", \"/a/b/c\", \"../c\"),\n\t\t\t(\"/a/b/c\", \"/a\", \"../..\"),\n\t\t\t(\"/a/b/c/\", \"/a/d/\", \"../../d\"),\n\t\t\t(\"/a/b/b\", \"/a/a/b\", \"../../a/b\"),\n\t\t];\n\n\t\t#[cfg(windows)]\n\t\tlet cases = [\n\t\t\t(r\"C:\\a\\b\", r\"C:\\a\\b\\c\", \"c\"),\n\t\t\t(r\"C:\\a\\b\\c\", r\"C:\\a\\b\", r\"..\"),\n\t\t\t(r\"C:\\a\\b\\d\", r\"C:\\a\\b\\c\", r\"..\\c\"),\n\t\t\t(r\"C:\\a\\b\\c\", r\"C:\\a\", r\"..\\..\"),\n\t\t\t(r\"C:\\a\\b\\b\", r\"C:\\a\\a\\b\", r\"..\\..\\a\\b\"),\n\t\t];\n\n\t\tfor (from, to, expected) in cases {\n\t\t\tlet from = PathDyn::Os(from.as_ref());\n\t\t\tlet to = PathDyn::Os(to.as_ref());\n\t\t\tassert_eq!(path_relative_to(from, to).unwrap().to_str().unwrap(), expected);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/provider/attrs.rs",
    "content": "use std::time::{Duration, SystemTime, UNIX_EPOCH};\n\nuse crate::cha::{Cha, ChaMode};\n\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]\npub struct Attrs {\n\tpub mode:  Option<ChaMode>,\n\tpub atime: Option<SystemTime>,\n\tpub btime: Option<SystemTime>,\n\tpub mtime: Option<SystemTime>,\n}\n\nimpl From<Cha> for Attrs {\n\tfn from(value: Cha) -> Self {\n\t\tSelf { mode: Some(value.mode), atime: value.atime, btime: value.btime, mtime: value.mtime }\n\t}\n}\n\nimpl TryFrom<Attrs> for std::fs::FileTimes {\n\ttype Error = ();\n\n\tfn try_from(value: Attrs) -> Result<Self, Self::Error> {\n\t\tif !value.has_times() {\n\t\t\treturn Err(());\n\t\t}\n\n\t\tlet mut t = Self::new();\n\t\tif let Some(atime) = value.atime {\n\t\t\tt = t.set_accessed(atime);\n\t\t}\n\n\t\t#[cfg(target_os = \"macos\")]\n\t\tif let Some(btime) = value.btime {\n\t\t\tuse std::os::macos::fs::FileTimesExt;\n\t\t\tt = t.set_created(btime);\n\t\t}\n\n\t\t#[cfg(windows)]\n\t\tif let Some(btime) = value.btime {\n\t\t\tuse std::os::windows::fs::FileTimesExt;\n\t\t\tt = t.set_created(btime);\n\t\t}\n\n\t\tif let Some(mtime) = value.mtime {\n\t\t\tt = t.set_modified(mtime);\n\t\t}\n\n\t\tOk(t)\n\t}\n}\n\nimpl TryFrom<Attrs> for std::fs::Permissions {\n\ttype Error = ();\n\n\tfn try_from(value: Attrs) -> Result<Self, Self::Error> {\n\t\t#[cfg(unix)]\n\t\tif let Some(mode) = value.mode {\n\t\t\treturn Ok(mode.into());\n\t\t}\n\n\t\tErr(())\n\t}\n}\n\nimpl Attrs {\n\tpub fn has_times(self) -> bool {\n\t\tself.atime.is_some() || self.btime.is_some() || self.mtime.is_some()\n\t}\n\n\tpub fn atime_dur(self) -> Option<Duration> { self.atime?.duration_since(UNIX_EPOCH).ok() }\n\n\tpub fn btime_dur(self) -> Option<Duration> { self.btime?.duration_since(UNIX_EPOCH).ok() }\n\n\tpub fn mtime_dur(self) -> Option<Duration> { self.mtime?.duration_since(UNIX_EPOCH).ok() }\n}\n"
  },
  {
    "path": "yazi-fs/src/provider/capabilities.rs",
    "content": "#[derive(Clone, Copy, Debug)]\npub struct Capabilities {\n\tpub symlink: bool,\n}\n"
  },
  {
    "path": "yazi-fs/src/provider/local/absolute.rs",
    "content": "use std::path::PathBuf;\n\nuse yazi_shared::{loc::LocBuf, pool::InternStr, url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike}};\n\nuse crate::CWD;\n\npub fn try_absolute<'a, U>(url: U) -> Option<UrlCow<'a>>\nwhere\n\tU: Into<UrlCow<'a>>,\n{\n\ttry_absolute_impl(url.into())\n}\n\nfn try_absolute_impl<'a>(url: UrlCow<'a>) -> Option<UrlCow<'a>> {\n\tif url.kind().is_virtual() {\n\t\treturn None;\n\t}\n\n\tlet path = url.loc().as_os().expect(\"must be a local path\");\n\tlet b = path.as_os_str().as_encoded_bytes();\n\n\tlet loc = if cfg!(windows) && b.len() == 2 && b[1] == b':' && b[0].is_ascii_alphabetic() {\n\t\tLocBuf::<PathBuf>::with(\n\t\t\tformat!(r\"{}:\\\", b[0].to_ascii_uppercase() as char).into(),\n\t\t\tif url.has_base() { 0 } else { 2 },\n\t\t\tif url.has_trail() { 0 } else { 2 },\n\t\t)\n\t\t.expect(\"Loc from drive letter\")\n\t} else if let Ok(rest) = path.strip_prefix(\"~/\")\n\t\t&& let Some(home) = dirs::home_dir()\n\t\t&& home.is_absolute()\n\t{\n\t\tlet add = home.components().count() - 1; // Home root (\"~\") has offset by the absolute root (\"/\")\n\t\tLocBuf::<PathBuf>::with(\n\t\t\thome.join(rest),\n\t\t\turl.uri().components().count() + if url.has_base() { 0 } else { add },\n\t\t\turl.urn().components().count() + if url.has_trail() { 0 } else { add },\n\t\t)\n\t\t.expect(\"Loc from home directory\")\n\t} else if !url.is_absolute() {\n\t\tLocBuf::<PathBuf>::with(\n\t\t\tCWD.path().join(path),\n\t\t\turl.uri().components().count(),\n\t\t\turl.urn().components().count(),\n\t\t)\n\t\t.expect(\"Loc from relative path\")\n\t} else {\n\t\treturn Some(url);\n\t};\n\n\tSome(match url.as_url() {\n\t\tUrl::Regular(_) => UrlBuf::Regular(loc).into(),\n\t\tUrl::Search { domain, .. } => UrlBuf::Search { loc, domain: domain.intern() }.into(),\n\t\tUrl::Archive { .. } | Url::Sftp { .. } => None?,\n\t})\n}\n"
  },
  {
    "path": "yazi-fs/src/provider/local/calculator.rs",
    "content": "use std::{collections::VecDeque, future::poll_fn, io, mem, path::{Path, PathBuf}, pin::Pin, task::{Poll, ready}, time::{Duration, Instant}};\n\nuse either::Either;\nuse tokio::task::JoinHandle;\n\nuse crate::cha::Cha;\n\ntype Task = Either<PathBuf, std::fs::ReadDir>;\n\npub enum SizeCalculator {\n\tIdle((VecDeque<Task>, Option<u64>), Cha),\n\tPending(JoinHandle<(VecDeque<Task>, Option<u64>)>, Cha),\n}\n\nimpl SizeCalculator {\n\tpub async fn new(path: &Path) -> io::Result<Self> {\n\t\tlet p = path.to_owned();\n\t\ttokio::task::spawn_blocking(move || {\n\t\t\tlet cha = Cha::new(p.file_name().unwrap_or_default(), std::fs::symlink_metadata(&p)?);\n\t\t\tif !cha.is_dir() {\n\t\t\t\treturn Ok(Self::Idle((VecDeque::new(), Some(cha.len)), cha));\n\t\t\t}\n\n\t\t\tlet mut buf = VecDeque::from([Either::Right(std::fs::read_dir(&p)?)]);\n\t\t\tlet size = Self::next_chunk(&mut buf);\n\t\t\tOk(Self::Idle((buf, size), cha))\n\t\t})\n\t\t.await?\n\t}\n\n\tpub fn cha(&self) -> Cha {\n\t\tmatch *self {\n\t\t\tSelf::Idle(_, cha) | Self::Pending(_, cha) => cha,\n\t\t}\n\t}\n\n\tpub async fn total(path: &Path) -> io::Result<u64> {\n\t\tlet mut it = Self::new(path).await?;\n\t\tlet mut total = 0;\n\t\twhile let Some(n) = it.next().await? {\n\t\t\ttotal += n;\n\t\t}\n\t\tOk(total)\n\t}\n\n\tpub async fn next(&mut self) -> io::Result<Option<u64>> {\n\t\tpoll_fn(|cx| {\n\t\t\tloop {\n\t\t\t\tmatch self {\n\t\t\t\t\tSelf::Idle((buf, size), cha) => {\n\t\t\t\t\t\tif let Some(s) = size.take() {\n\t\t\t\t\t\t\treturn Poll::Ready(Ok(Some(s)));\n\t\t\t\t\t\t} else if buf.is_empty() {\n\t\t\t\t\t\t\treturn Poll::Ready(Ok(None));\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet mut buf = mem::take(buf);\n\t\t\t\t\t\t*self = Self::Pending(\n\t\t\t\t\t\t\ttokio::task::spawn_blocking(move || {\n\t\t\t\t\t\t\t\tlet size = Self::next_chunk(&mut buf);\n\t\t\t\t\t\t\t\t(buf, size)\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t*cha,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tSelf::Pending(handle, cha) => {\n\t\t\t\t\t\t*self = Self::Idle(ready!(Pin::new(handle).poll(cx))?, *cha);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.await\n\t}\n\n\tfn next_chunk(buf: &mut VecDeque<Either<PathBuf, std::fs::ReadDir>>) -> Option<u64> {\n\t\tlet (mut i, mut size, now) = (0, 0, Instant::now());\n\t\tmacro_rules! pop_and_continue {\n\t\t\t() => {{\n\t\t\t\tbuf.pop_front();\n\t\t\t\tif buf.is_empty() {\n\t\t\t\t\treturn Some(size);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}};\n\t\t}\n\n\t\twhile i < 5000 && now.elapsed() < Duration::from_millis(50) {\n\t\t\ti += 1;\n\t\t\tlet front = buf.front_mut()?;\n\n\t\t\tif let Either::Left(p) = front {\n\t\t\t\t*front = match std::fs::read_dir(p) {\n\t\t\t\t\tOk(it) => Either::Right(it),\n\t\t\t\t\tErr(_) => pop_and_continue!(),\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tlet Some(next) = front.as_mut().right()?.next() else {\n\t\t\t\tpop_and_continue!();\n\t\t\t};\n\n\t\t\tlet Ok(ent) = next else { continue };\n\t\t\tlet Ok(ft) = ent.file_type() else { continue };\n\t\t\tif ft.is_dir() {\n\t\t\t\tbuf.push_back(Either::Left(ent.path()));\n\t\t\t} else if let Ok(meta) = ent.metadata() {\n\t\t\t\tsize += meta.len();\n\t\t\t}\n\t\t}\n\t\tSome(size)\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/provider/local/casefold.rs",
    "content": "use std::{io, path::{Path, PathBuf}};\n\npub async fn match_name_case(path: impl AsRef<Path>) -> bool {\n\tlet path = path.as_ref();\n\tcasefold(path).await.is_ok_and(|p| p.file_name() == path.file_name())\n}\n\npub(super) async fn casefold(path: impl AsRef<Path>) -> io::Result<PathBuf> {\n\tlet path = path.as_ref().to_owned();\n\ttokio::task::spawn_blocking(move || casefold_impl(path)).await?\n}\n\n#[cfg(any(target_os = \"macos\", target_os = \"windows\", target_os = \"freebsd\"))]\nfn casefold_impl(path: PathBuf) -> io::Result<PathBuf> {\n\tlet mut it = path.components();\n\tlet mut parts = vec![];\n\tloop {\n\t\tlet p = it.as_path();\n\t\tlet q = final_path(p)?;\n\t\tif p != q {\n\t\t\tparts.push(q);\n\t\t} else if parts.is_empty() {\n\t\t\treturn Ok(q);\n\t\t} else {\n\t\t\tbreak;\n\t\t}\n\t\tif it.next_back().is_none() {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tlet mut buf = it.as_path().to_path_buf();\n\tfor p in parts.into_iter().rev() {\n\t\tif let Some(name) = p.file_name() {\n\t\t\tbuf.push(name);\n\t\t} else {\n\t\t\treturn Err(io::Error::other(\"Cannot get filename\"));\n\t\t}\n\t}\n\tOk(buf)\n}\n\n#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\nfn casefold_impl(path: PathBuf) -> io::Result<PathBuf> {\n\tuse std::{ffi::{CString, OsStr, OsString}, fs::File, os::{fd::{AsRawFd, FromRawFd}, unix::{ffi::{OsStrExt, OsStringExt}, fs::MetadataExt}}};\n\n\tuse libc::{O_NOFOLLOW, O_PATH};\n\n\tlet cstr = CString::new(path.into_os_string().into_vec())?;\n\tlet path = Path::new(OsStr::from_bytes(cstr.as_bytes()));\n\tlet Some((parent, name)) = path.parent().zip(path.file_name()) else {\n\t\treturn Ok(PathBuf::from(OsString::from_vec(cstr.into_bytes())));\n\t};\n\n\tlet file = match unsafe { libc::open(cstr.as_ptr(), O_PATH | O_NOFOLLOW) } {\n\t\tret if ret < 0 => return Err(io::Error::last_os_error()),\n\t\tret => unsafe { File::from_raw_fd(ret) },\n\t};\n\n\t// Fast path: if the `/proc/self/fd/N` matches\n\tif let Some(p) = try_from_fd(file.as_raw_fd(), path) {\n\t\treturn Ok(p);\n\t}\n\n\t// Fast path: if the file isn't a symlink\n\tlet meta = file.metadata()?;\n\tif !meta.is_symlink()\n\t\t&& let Some(n) = path.canonicalize()?.file_name()\n\t{\n\t\treturn Ok(parent.join(n));\n\t}\n\n\t// Fallback: scan the directory for matching inodes\n\tlet mut names = vec![];\n\tfor entry in std::fs::read_dir(parent)? {\n\t\tlet entry = entry?;\n\t\tlet n = entry.file_name(); // TODO: use `file_name_ref()` when stabilized\n\n\t\tif n == name {\n\t\t\treturn Ok(PathBuf::from(OsString::from_vec(cstr.into_bytes())));\n\t\t} else if let m = entry.metadata()?\n\t\t\t&& m.ino() == meta.ino()\n\t\t\t&& m.dev() == meta.dev()\n\t\t{\n\t\t\tnames.push(n);\n\t\t}\n\t}\n\n\tif names.len() == 1 {\n\t\t// No hardlink that shares the same inode\n\t\tOk(parent.join(&names[0]))\n\t} else if let mut it = names.iter().enumerate().filter(|&(_, n)| n.eq_ignore_ascii_case(name))\n\t\t&& let Some((i, _)) = it.next()\n\t\t&& it.next().is_none()\n\t{\n\t\t// Case-insensitive match\n\t\tOk(parent.join(&names[i]))\n\t} else {\n\t\tErr(io::Error::from(io::ErrorKind::NotFound))\n\t}\n}\n\n#[cfg(any(target_os = \"netbsd\", target_os = \"openbsd\"))]\n#[allow(irrefutable_let_patterns)]\nfn casefold_impl(path: PathBuf) -> io::Result<PathBuf> {\n\tuse std::{ffi::{CString, OsStr, OsString}, fs::File, os::{fd::{AsRawFd, FromRawFd}, unix::{ffi::{OsStrExt, OsStringExt}, fs::MetadataExt}}};\n\n\tuse libc::{O_NOFOLLOW, O_RDONLY};\n\n\tlet cstr = CString::new(path.into_os_string().into_vec())?;\n\tlet path = Path::new(OsStr::from_bytes(cstr.as_bytes()));\n\tlet Some((parent, name)) = path.parent().zip(path.file_name()) else {\n\t\treturn Ok(PathBuf::from(OsString::from_vec(cstr.into_bytes())));\n\t};\n\n\t// Fast path: if it's not a symlink\n\tif let fd = unsafe { libc::open(cstr.as_ptr(), O_RDONLY | O_NOFOLLOW) }\n\t\t&& fd >= 0\n\t\t&& let file = unsafe { File::from_raw_fd(fd) }\n\t{\n\t\tif let Some(p) = try_from_fd(file.as_raw_fd(), path) {\n\t\t\treturn Ok(p);\n\t\t} else if let Some(n) = path.canonicalize()?.file_name() {\n\t\t\treturn Ok(parent.join(n));\n\t\t} else {\n\t\t\treturn Err(io::Error::other(\"Cannot get filename\"));\n\t\t}\n\t};\n\n\t// Fallback: scan the directory for matching inodes\n\tlet (meta, mut names) = (std::fs::symlink_metadata(path)?, vec![]);\n\tfor entry in std::fs::read_dir(parent)? {\n\t\tlet entry = entry?;\n\t\tlet n = entry.file_name(); // TODO: use `file_name_ref()` when stabilized\n\n\t\tif n == name {\n\t\t\treturn Ok(PathBuf::from(OsString::from_vec(cstr.into_bytes())));\n\t\t} else if let m = entry.metadata()?\n\t\t\t&& m.ino() == meta.ino()\n\t\t\t&& m.dev() == meta.dev()\n\t\t{\n\t\t\tnames.push(n);\n\t\t}\n\t}\n\n\tif names.len() == 1 {\n\t\t// No hardlink that shares the same inode\n\t\tOk(parent.join(&names[0]))\n\t} else if let mut it = names.iter().enumerate().filter(|&(_, n)| n.eq_ignore_ascii_case(name))\n\t\t&& let Some((i, _)) = it.next()\n\t\t&& it.next().is_none()\n\t{\n\t\t// Case-insensitive match\n\t\tOk(parent.join(&names[i]))\n\t} else {\n\t\tErr(io::Error::from(io::ErrorKind::NotFound))\n\t}\n}\n\n#[cfg(any(target_os = \"macos\", target_os = \"freebsd\"))]\nfn final_path(path: &Path) -> io::Result<PathBuf> {\n\tuse std::{ffi::{CStr, CString, OsString}, os::{fd::{AsRawFd, FromRawFd, OwnedFd}, unix::ffi::{OsStrExt, OsStringExt}}};\n\n\tuse libc::{F_GETPATH, O_RDONLY, O_SYMLINK, PATH_MAX};\n\n\tlet cstr = CString::new(path.as_os_str().as_bytes())?;\n\tlet fd = match unsafe { libc::open(cstr.as_ptr(), O_RDONLY | O_SYMLINK) } {\n\t\tret if ret < 0 => return Err(io::Error::last_os_error()),\n\t\tret => unsafe { OwnedFd::from_raw_fd(ret) },\n\t};\n\n\tlet mut buf = [0u8; PATH_MAX as usize];\n\tif unsafe { libc::fcntl(fd.as_raw_fd(), F_GETPATH, buf.as_mut_ptr()) } < 0 {\n\t\treturn Err(io::Error::last_os_error());\n\t}\n\n\tlet cstr = unsafe { CStr::from_ptr(buf.as_ptr() as *const i8) };\n\tOk(OsString::from_vec(cstr.to_bytes().to_vec()).into())\n}\n\n#[cfg(target_os = \"windows\")]\nfn final_path(path: &Path) -> io::Result<PathBuf> {\n\tuse std::{ffi::OsString, fs::File, mem, os::windows::{ffi::{OsStrExt, OsStringExt}, fs::OpenOptionsExt, io::AsRawHandle}};\n\n\tuse either::Either;\n\tuse windows_sys::Win32::{Foundation::{HANDLE, INVALID_HANDLE_VALUE}, Storage::FileSystem::{FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FindClose, FindFirstFileW, GetFinalPathNameByHandleW, VOLUME_NAME_DOS, WIN32_FIND_DATAW}};\n\n\tfn by_handle(file: &File, buf: &mut [u16]) -> io::Result<Either<PathBuf, u32>> {\n\t\tlet len = unsafe {\n\t\t\tGetFinalPathNameByHandleW(\n\t\t\t\tfile.as_raw_handle() as HANDLE,\n\t\t\t\tbuf.as_mut_ptr(),\n\t\t\t\tbuf.len() as u32,\n\t\t\t\tVOLUME_NAME_DOS,\n\t\t\t)\n\t\t};\n\n\t\tOk(if len == 0 {\n\t\t\tErr(io::Error::last_os_error())?\n\t\t} else if len as usize > buf.len() {\n\t\t\tEither::Right(len)\n\t\t} else if buf.starts_with(&[92, 92, 63, 92]) {\n\t\t\tEither::Left(PathBuf::from(OsString::from_wide(&buf[4..len as usize])))\n\t\t} else {\n\t\t\tEither::Left(PathBuf::from(OsString::from_wide(&buf[0..len as usize])))\n\t\t})\n\t}\n\n\tfn by_find(path: &Path) -> io::Result<PathBuf> {\n\t\tlet Some(parent) = path.parent() else {\n\t\t\treturn Ok(path.to_path_buf());\n\t\t};\n\n\t\tlet wide: Vec<u16> = path.as_os_str().encode_wide().chain([0]).collect();\n\t\tlet mut data = unsafe { mem::zeroed::<WIN32_FIND_DATAW>() };\n\t\tmatch unsafe { FindFirstFileW(wide.as_ptr(), &mut data) } {\n\t\t\tINVALID_HANDLE_VALUE => return Err(io::Error::last_os_error()),\n\t\t\thandle => _ = unsafe { FindClose(handle) },\n\t\t}\n\n\t\tlet name = data.cFileName.split(|&c| c == 0).next().unwrap_or(&data.cFileName);\n\t\tOk(parent.join(OsString::from_wide(name)))\n\t}\n\n\tlet file = std::fs::OpenOptions::new()\n\t\t.access_mode(0)\n\t\t.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT)\n\t\t.open(path)?;\n\n\tmatch by_handle(&file, &mut [0u16; 512]) {\n\t\tOk(Either::Left(path)) => Ok(path),\n\t\tOk(Either::Right(len)) => match by_handle(&file, &mut vec![0u16; len as usize])? {\n\t\t\tEither::Left(path) => Ok(path),\n\t\t\tEither::Right(_) => Err(io::Error::new(io::ErrorKind::InvalidData, \"path too long\")),\n\t\t},\n\t\t// Fallback for paths that GetFinalPathNameByHandleW cannot handle,\n\t\t// such as those on DefineDosDeviceW-created devices (error 1005).\n\t\tErr(_) => by_find(path),\n\t}\n}\n\n#[cfg(any(\n\ttarget_os = \"linux\",\n\ttarget_os = \"android\",\n\ttarget_os = \"netbsd\",\n\ttarget_os = \"openbsd\"\n))]\nfn try_from_fd(fd: std::os::fd::RawFd, needle: &Path) -> Option<PathBuf> {\n\tuse std::{ffi::OsString, os::unix::ffi::{OsStrExt, OsStringExt}};\n\n\t#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n\tlet cand = format!(\"/proc/self/fd/{fd}\");\n\t#[cfg(target_os = \"netbsd\")]\n\tlet cand = format!(\"/proc/curproc/fd/{fd}\");\n\t#[cfg(target_os = \"openbsd\")]\n\tlet cand = format!(\"/dev/fd/{fd}\");\n\n\tif let Ok(p) = std::fs::read_link(cand)\n\t\t&& let needle = needle.as_os_str()\n\t\t&& let Some(b) = p.as_os_str().as_bytes().get(..needle.len())\n\t\t&& b.eq_ignore_ascii_case(needle.as_bytes())\n\t{\n\t\tlet mut b = p.into_os_string().into_vec();\n\t\tb.truncate(needle.len());\n\t\treturn Some(PathBuf::from(OsString::from_vec(b)));\n\t}\n\n\tNone\n}\n"
  },
  {
    "path": "yazi-fs/src/provider/local/copier.rs",
    "content": "use std::{io, path::PathBuf};\n\nuse tokio::{select, sync::{mpsc, oneshot}};\n\nuse crate::provider::Attrs;\n\npub(super) async fn copy_impl(from: PathBuf, to: PathBuf, attrs: Attrs) -> io::Result<u64> {\n\t#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\n\t{\n\t\tuse std::os::unix::fs::OpenOptionsExt;\n\n\t\ttokio::task::spawn_blocking(move || {\n\t\t\tlet mut opts = std::fs::OpenOptions::new();\n\t\t\tif let Some(mode) = attrs.mode {\n\t\t\t\topts.mode(mode.bits() as _);\n\t\t\t}\n\n\t\t\tlet mut reader = std::fs::File::open(from)?;\n\t\t\tlet mut writer = opts.write(true).create(true).truncate(true).open(to)?;\n\t\t\tlet written = std::io::copy(&mut reader, &mut writer)?;\n\n\t\t\tif let Some(mode) = attrs.mode {\n\t\t\t\twriter.set_permissions(mode.into()).ok();\n\t\t\t}\n\t\t\tif let Ok(times) = attrs.try_into() {\n\t\t\t\twriter.set_times(times).ok();\n\t\t\t}\n\n\t\t\tOk(written)\n\t\t})\n\t\t.await?\n\t}\n\n\t#[cfg(not(any(target_os = \"linux\", target_os = \"android\")))]\n\t{\n\t\ttokio::task::spawn_blocking(move || {\n\t\t\tlet written = std::fs::copy(from, &to)?;\n\n\t\t\tif let Ok(times) = attrs.try_into()\n\t\t\t\t&& let Ok(file) = std::fs::File::options().write(true).open(to)\n\t\t\t{\n\t\t\t\tfile.set_times(times).ok();\n\t\t\t}\n\n\t\t\tOk(written)\n\t\t})\n\t\t.await?\n\t}\n}\n\npub(super) fn copy_with_progress_impl(\n\tfrom: PathBuf,\n\tto: PathBuf,\n\tattrs: Attrs,\n) -> mpsc::Receiver<Result<u64, io::Error>> {\n\tlet (prog_tx, prog_rx) = mpsc::channel(10);\n\tlet (done_tx, mut done_rx) = oneshot::channel();\n\n\ttokio::spawn({\n\t\tlet to = to.clone();\n\t\tasync move {\n\t\t\tdone_tx.send(copy_impl(from, to, attrs).await).ok();\n\t\t}\n\t});\n\n\ttokio::spawn({\n\t\tlet prog_tx = prog_tx.clone();\n\t\tasync move {\n\t\t\tlet mut last = 0;\n\t\t\tlet mut done = None;\n\t\t\tloop {\n\t\t\t\tselect! {\n\t\t\t\t\tres = &mut done_rx => done = Some(res.unwrap()),\n\t\t\t\t\t_ = prog_tx.closed() => break,\n\t\t\t\t\t_ = tokio::time::sleep(std::time::Duration::from_secs(3)) => {},\n\t\t\t\t}\n\n\t\t\t\tmatch done {\n\t\t\t\t\tSome(Ok(len)) => {\n\t\t\t\t\t\tif len > last {\n\t\t\t\t\t\t\tprog_tx.send(Ok(len - last)).await.ok();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tprog_tx.send(Ok(0)).await.ok();\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tSome(Err(e)) => {\n\t\t\t\t\t\tprog_tx.send(Err(e)).await.ok();\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tNone => {}\n\t\t\t\t}\n\n\t\t\t\tlet len = tokio::fs::symlink_metadata(&to).await.map(|m| m.len()).unwrap_or(0);\n\t\t\t\tif len > last {\n\t\t\t\t\tprog_tx.send(Ok(len - last)).await.ok();\n\t\t\t\t\tlast = len;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t});\n\n\tprog_rx\n}\n"
  },
  {
    "path": "yazi-fs/src/provider/local/dir_entry.rs",
    "content": "use std::{io, sync::Arc};\n\nuse yazi_shared::{path::PathBufDyn, strand::StrandCow, url::{UrlBuf, UrlLike}};\n\nuse crate::{cha::{Cha, ChaType}, provider::FileHolder};\n\npub enum DirEntry {\n\tRegular(tokio::fs::DirEntry),\n\tOthers { entry: tokio::fs::DirEntry, dir: Arc<UrlBuf> },\n}\n\nimpl FileHolder for DirEntry {\n\tasync fn file_type(&self) -> io::Result<ChaType> {\n\t\tmatch self {\n\t\t\tSelf::Regular(entry) | Self::Others { entry, .. } => entry.file_type().await.map(Into::into),\n\t\t}\n\t}\n\n\tasync fn metadata(&self) -> io::Result<Cha> {\n\t\tlet meta = match self {\n\t\t\tSelf::Regular(entry) | Self::Others { entry, .. } => entry.metadata().await?,\n\t\t};\n\n\t\tOk(Cha::new(self.name(), meta)) // TODO: use `file_name_os_str` when stabilized\n\t}\n\n\tfn name(&self) -> StrandCow<'_> {\n\t\tmatch self {\n\t\t\tSelf::Regular(entry) | Self::Others { entry, .. } => entry.file_name().into(),\n\t\t}\n\t}\n\n\tfn path(&self) -> PathBufDyn {\n\t\tmatch self {\n\t\t\tSelf::Regular(entry) | Self::Others { entry, .. } => entry.path().into(),\n\t\t}\n\t}\n\n\tfn url(&self) -> UrlBuf {\n\t\tmatch self {\n\t\t\tSelf::Regular(entry) => entry.path().into(),\n\t\t\tSelf::Others { entry, dir } => {\n\t\t\t\tdir.try_join(entry.file_name()).expect(\"entry name is a valid component of the local URL\")\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/provider/local/gate.rs",
    "content": "use std::io;\n\nuse yazi_shared::url::AsUrl;\n\nuse crate::provider::{Attrs, FileBuilder};\n\n#[derive(Default)]\npub struct Gate(tokio::fs::OpenOptions);\n\nimpl FileBuilder for Gate {\n\ttype File = tokio::fs::File;\n\n\tfn append(&mut self, append: bool) -> &mut Self {\n\t\tself.0.append(append);\n\t\tself\n\t}\n\n\tfn attrs(&mut self, _attrs: Attrs) -> &mut Self {\n\t\t#[cfg(unix)]\n\t\tif let Some(mode) = _attrs.mode {\n\t\t\tself.0.mode(mode.bits() as _);\n\t\t}\n\t\tself\n\t}\n\n\tfn create(&mut self, create: bool) -> &mut Self {\n\t\tself.0.create(create);\n\t\tself\n\t}\n\n\tfn create_new(&mut self, create_new: bool) -> &mut Self {\n\t\tself.0.create_new(create_new);\n\t\tself\n\t}\n\n\tasync fn open<U>(&self, url: U) -> io::Result<Self::File>\n\twhere\n\t\tU: AsUrl,\n\t{\n\t\tlet url = url.as_url();\n\t\tif let Some(path) = url.as_local() {\n\t\t\tself.0.open(path).await\n\t\t} else {\n\t\t\tErr(io::Error::new(io::ErrorKind::InvalidInput, format!(\"Not a local URL: {url:?}\")))\n\t\t}\n\t}\n\n\tfn read(&mut self, read: bool) -> &mut Self {\n\t\tself.0.read(read);\n\t\tself\n\t}\n\n\tfn truncate(&mut self, truncate: bool) -> &mut Self {\n\t\tself.0.truncate(truncate);\n\t\tself\n\t}\n\n\tfn write(&mut self, write: bool) -> &mut Self {\n\t\tself.0.write(write);\n\t\tself\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/provider/local/identical.rs",
    "content": "use std::{io, path::{Path, PathBuf}};\n\n#[inline]\npub async fn identical<P, Q>(a: P, b: Q) -> io::Result<bool>\nwhere\n\tP: AsRef<Path>,\n\tQ: AsRef<Path>,\n{\n\tlet (a, b) = (a.as_ref().to_owned(), b.as_ref().to_owned());\n\ttokio::task::spawn_blocking(move || identical_impl(a, b)).await?\n}\n\n#[cfg(unix)]\nfn identical_impl(a: PathBuf, b: PathBuf) -> io::Result<bool> {\n\tuse std::os::unix::fs::MetadataExt;\n\n\tlet (a_, b_) = (std::fs::symlink_metadata(&a)?, std::fs::symlink_metadata(&b)?);\n\tOk(\n\t\ta_.ino() == b_.ino()\n\t\t\t&& a_.dev() == b_.dev()\n\t\t\t&& std::fs::canonicalize(a)? == std::fs::canonicalize(b)?,\n\t)\n}\n\n#[cfg(windows)]\nfn identical_impl(a: PathBuf, b: PathBuf) -> io::Result<bool> {\n\tuse std::{fs::OpenOptions, mem, os::windows::{fs::OpenOptionsExt, io::AsRawHandle}};\n\n\tuse windows_sys::Win32::{Foundation::HANDLE, Storage::FileSystem::{FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_ID_INFO, FileIdInfo, GetFileInformationByHandleEx}};\n\n\tfn file_id(path: PathBuf) -> io::Result<(u64, u128)> {\n\t\tlet file = OpenOptions::new()\n\t\t\t.access_mode(0)\n\t\t\t.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT)\n\t\t\t.open(path)?;\n\n\t\tlet mut info: FILE_ID_INFO = unsafe { mem::zeroed() };\n\t\tlet ret = unsafe {\n\t\t\tGetFileInformationByHandleEx(\n\t\t\t\tfile.as_raw_handle() as HANDLE,\n\t\t\t\tFileIdInfo,\n\t\t\t\t&mut info as *mut FILE_ID_INFO as _,\n\t\t\t\tmem::size_of::<FILE_ID_INFO>() as u32,\n\t\t\t)\n\t\t};\n\n\t\tif ret == 0 {\n\t\t\tErr(io::Error::last_os_error())\n\t\t} else {\n\t\t\tOk((info.VolumeSerialNumber, u128::from_le_bytes(info.FileId.Identifier)))\n\t\t}\n\t}\n\n\tOk(file_id(a)? == file_id(b)?)\n}\n"
  },
  {
    "path": "yazi-fs/src/provider/local/local.rs",
    "content": "use std::{io, path::Path, sync::Arc};\n\nuse tokio::sync::mpsc;\nuse yazi_shared::{path::{AsPath, PathBufDyn}, scheme::SchemeKind, strand::AsStrand, url::{Url, UrlBuf, UrlCow}};\n\nuse crate::{cha::Cha, provider::{Attrs, Capabilities, Provider}};\n\n#[derive(Clone)]\npub struct Local<'a> {\n\turl:  Url<'a>,\n\tpath: &'a Path,\n}\n\nimpl<'a> Provider for Local<'a> {\n\ttype File = tokio::fs::File;\n\ttype Gate = super::Gate;\n\ttype Me<'b> = Local<'b>;\n\ttype ReadDir = super::ReadDir;\n\ttype UrlCow = UrlCow<'a>;\n\n\tasync fn absolute(&self) -> io::Result<Self::UrlCow> {\n\t\tsuper::try_absolute(self.url)\n\t\t\t.ok_or_else(|| io::Error::other(\"Cannot get absolute path for local URL\"))\n\t}\n\n\t#[inline]\n\tasync fn canonicalize(&self) -> io::Result<UrlBuf> {\n\t\ttokio::fs::canonicalize(self.path).await.map(Into::into)\n\t}\n\n\t#[inline]\n\tfn capabilities(&self) -> Capabilities { Capabilities { symlink: true } }\n\n\tasync fn casefold(&self) -> io::Result<UrlBuf> {\n\t\tsuper::casefold(self.path).await.map(Into::into)\n\t}\n\n\t#[inline]\n\tasync fn copy<P>(&self, to: P, attrs: Attrs) -> io::Result<u64>\n\twhere\n\t\tP: AsPath,\n\t{\n\t\tlet to = to.as_path().to_os_owned()?;\n\t\tlet from = self.path.to_owned();\n\t\tsuper::copy_impl(from, to, attrs).await\n\t}\n\n\tfn copy_with_progress<P, A>(&self, to: P, attrs: A) -> io::Result<mpsc::Receiver<io::Result<u64>>>\n\twhere\n\t\tP: AsPath,\n\t\tA: Into<Attrs>,\n\t{\n\t\tlet to = to.as_path().to_os_owned()?;\n\t\tlet from = self.path.to_owned();\n\t\tOk(super::copy_with_progress_impl(from, to, attrs.into()))\n\t}\n\n\t#[inline]\n\tasync fn create_dir(&self) -> io::Result<()> { tokio::fs::create_dir(self.path).await }\n\n\t#[inline]\n\tasync fn create_dir_all(&self) -> io::Result<()> { tokio::fs::create_dir_all(self.path).await }\n\n\t#[inline]\n\tasync fn hard_link<P>(&self, to: P) -> io::Result<()>\n\twhere\n\t\tP: AsPath,\n\t{\n\t\tlet to = to.as_path().as_os()?;\n\n\t\ttokio::fs::hard_link(self.path, to).await\n\t}\n\n\t#[inline]\n\tasync fn metadata(&self) -> io::Result<Cha> {\n\t\tOk(Cha::new(self.path.file_name().unwrap_or_default(), tokio::fs::metadata(self.path).await?))\n\t}\n\n\t#[inline]\n\tasync fn new<'b>(url: Url<'b>) -> io::Result<Self::Me<'b>> {\n\t\tmatch url {\n\t\t\tUrl::Regular(loc) | Url::Search { loc, .. } => Ok(Self::Me { url, path: loc.as_inner() }),\n\t\t\tUrl::Archive { .. } | Url::Sftp { .. } => {\n\t\t\t\tErr(io::Error::new(io::ErrorKind::InvalidInput, format!(\"Not a local URL: {url:?}\")))\n\t\t\t}\n\t\t}\n\t}\n\n\t#[inline]\n\tasync fn read_dir(self) -> io::Result<Self::ReadDir> {\n\t\tOk(match self.url.kind() {\n\t\t\tSchemeKind::Regular => Self::ReadDir::Regular(tokio::fs::read_dir(self.path).await?),\n\t\t\tSchemeKind::Search => Self::ReadDir::Others {\n\t\t\t\treader: tokio::fs::read_dir(self.path).await?,\n\t\t\t\tdir:    Arc::new(self.url.to_owned()),\n\t\t\t},\n\t\t\tSchemeKind::Archive | SchemeKind::Sftp => Err(io::Error::new(\n\t\t\t\tio::ErrorKind::InvalidInput,\n\t\t\t\tformat!(\"Not a local URL: {:?}\", self.url),\n\t\t\t))?,\n\t\t})\n\t}\n\n\t#[inline]\n\tasync fn read_link(&self) -> io::Result<PathBufDyn> {\n\t\tOk(tokio::fs::read_link(self.path).await?.into())\n\t}\n\n\t#[inline]\n\tasync fn remove_dir(&self) -> io::Result<()> { tokio::fs::remove_dir(self.path).await }\n\n\t#[inline]\n\tasync fn remove_dir_all(&self) -> io::Result<()> { tokio::fs::remove_dir_all(self.path).await }\n\n\t#[inline]\n\tasync fn remove_file(&self) -> io::Result<()> { tokio::fs::remove_file(self.path).await }\n\n\t#[inline]\n\tasync fn rename<P>(&self, to: P) -> io::Result<()>\n\twhere\n\t\tP: AsPath,\n\t{\n\t\tlet to = to.as_path().as_os()?;\n\n\t\ttokio::fs::rename(self.path, to).await\n\t}\n\n\t#[inline]\n\tasync fn symlink<S, F>(&self, original: S, _is_dir: F) -> io::Result<()>\n\twhere\n\t\tS: AsStrand,\n\t\tF: AsyncFnOnce() -> io::Result<bool>,\n\t{\n\t\t#[cfg(unix)]\n\t\t{\n\t\t\tlet original = original.as_strand().as_os()?;\n\t\t\ttokio::fs::symlink(original, self.path).await\n\t\t}\n\t\t#[cfg(windows)]\n\t\tif _is_dir().await? {\n\t\t\tself.symlink_dir(original).await\n\t\t} else {\n\t\t\tself.symlink_file(original).await\n\t\t}\n\t}\n\n\t#[inline]\n\tasync fn symlink_dir<S>(&self, original: S) -> io::Result<()>\n\twhere\n\t\tS: AsStrand,\n\t{\n\t\tlet original = original.as_strand().as_os()?;\n\n\t\t#[cfg(unix)]\n\t\t{\n\t\t\ttokio::fs::symlink(original, self.path).await\n\t\t}\n\t\t#[cfg(windows)]\n\t\t{\n\t\t\ttokio::fs::symlink_dir(original, self.path).await\n\t\t}\n\t}\n\n\t#[inline]\n\tasync fn symlink_file<S>(&self, original: S) -> io::Result<()>\n\twhere\n\t\tS: AsStrand,\n\t{\n\t\tlet original = original.as_strand().as_os()?;\n\n\t\t#[cfg(unix)]\n\t\t{\n\t\t\ttokio::fs::symlink(original, self.path).await\n\t\t}\n\t\t#[cfg(windows)]\n\t\t{\n\t\t\ttokio::fs::symlink_file(original, self.path).await\n\t\t}\n\t}\n\n\t#[inline]\n\tasync fn symlink_metadata(&self) -> io::Result<Cha> {\n\t\tOk(Cha::new(\n\t\t\tself.path.file_name().unwrap_or_default(),\n\t\t\ttokio::fs::symlink_metadata(self.path).await?,\n\t\t))\n\t}\n\n\tasync fn trash(&self) -> io::Result<()> {\n\t\tlet path = self.path.to_owned();\n\t\ttokio::task::spawn_blocking(move || {\n\t\t\t#[cfg(target_os = \"android\")]\n\t\t\t{\n\t\t\t\tErr(io::Error::new(io::ErrorKind::Unsupported, \"Unsupported OS for trash operation\"))\n\t\t\t}\n\t\t\t#[cfg(target_os = \"macos\")]\n\t\t\t{\n\t\t\t\tuse trash::{TrashContext, macos::{DeleteMethod, TrashContextExtMacos}};\n\t\t\t\tlet mut ctx = TrashContext::default();\n\t\t\t\tctx.set_delete_method(DeleteMethod::NsFileManager);\n\t\t\t\tctx.delete(path).map_err(io::Error::other)\n\t\t\t}\n\t\t\t#[cfg(all(not(target_os = \"macos\"), not(target_os = \"android\")))]\n\t\t\t{\n\t\t\t\ttrash::delete(path).map_err(io::Error::other)\n\t\t\t}\n\t\t})\n\t\t.await?\n\t}\n\n\t#[inline]\n\tfn url(&self) -> Url<'_> { self.url }\n\n\t#[inline]\n\tasync fn write<C>(&self, contents: C) -> io::Result<()>\n\twhere\n\t\tC: AsRef<[u8]>,\n\t{\n\t\ttokio::fs::write(self.path, contents).await\n\t}\n}\n\nimpl<'a> Local<'a> {\n\t#[inline]\n\tpub async fn read(&self) -> io::Result<Vec<u8>> { tokio::fs::read(self.path).await }\n\n\t#[inline]\n\tpub async fn read_to_string(&self) -> io::Result<String> {\n\t\ttokio::fs::read_to_string(self.path).await\n\t}\n\n\t#[inline]\n\tpub fn regular<P>(path: &'a P) -> Self\n\twhere\n\t\tP: ?Sized + AsRef<Path>,\n\t{\n\t\tSelf { url: Url::regular(path), path: path.as_ref() }\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/provider/local/mod.rs",
    "content": "yazi_macro::mod_flat!(absolute calculator casefold copier dir_entry gate identical local read_dir);\n"
  },
  {
    "path": "yazi-fs/src/provider/local/read_dir.rs",
    "content": "use std::{io, sync::Arc};\n\nuse yazi_shared::url::UrlBuf;\n\nuse crate::provider::DirReader;\n\npub enum ReadDir {\n\tRegular(tokio::fs::ReadDir),\n\tOthers { reader: tokio::fs::ReadDir, dir: Arc<UrlBuf> },\n}\n\nimpl DirReader for ReadDir {\n\ttype Entry = super::DirEntry;\n\n\tasync fn next(&mut self) -> io::Result<Option<Self::Entry>> {\n\t\tOk(match self {\n\t\t\tSelf::Regular(reader) => reader.next_entry().await?.map(Self::Entry::Regular),\n\t\t\tSelf::Others { reader, dir } => {\n\t\t\t\treader.next_entry().await?.map(|entry| Self::Entry::Others { entry, dir: dir.clone() })\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/provider/mod.rs",
    "content": "yazi_macro::mod_pub!(local);\n\nyazi_macro::mod_flat!(attrs capabilities traits);\n"
  },
  {
    "path": "yazi-fs/src/provider/traits.rs",
    "content": "use std::io;\n\nuse tokio::{io::{AsyncRead, AsyncSeek, AsyncWrite, AsyncWriteExt}, sync::mpsc};\nuse yazi_macro::ok_or_not_found;\nuse yazi_shared::{path::{AsPath, PathBufDyn}, strand::{AsStrand, StrandCow}, url::{AsUrl, Url, UrlBuf}};\n\nuse crate::{cha::{Cha, ChaType}, provider::{Attrs, Capabilities}};\n\npub trait Provider: Sized {\n\ttype File: AsyncRead + AsyncSeek + AsyncWrite + Unpin;\n\ttype Gate: FileBuilder<File = Self::File>;\n\ttype ReadDir: DirReader + 'static;\n\ttype UrlCow;\n\ttype Me<'a>: Provider;\n\n\tfn absolute(&self) -> impl Future<Output = io::Result<Self::UrlCow>>;\n\n\tfn canonicalize(&self) -> impl Future<Output = io::Result<UrlBuf>>;\n\n\tfn capabilities(&self) -> Capabilities;\n\n\tfn casefold(&self) -> impl Future<Output = io::Result<UrlBuf>>;\n\n\tfn copy<P>(&self, to: P, attrs: Attrs) -> impl Future<Output = io::Result<u64>>\n\twhere\n\t\tP: AsPath;\n\n\tfn copy_with_progress<P, A>(\n\t\t&self,\n\t\tto: P,\n\t\tattrs: A,\n\t) -> io::Result<mpsc::Receiver<io::Result<u64>>>\n\twhere\n\t\tP: AsPath,\n\t\tA: Into<Attrs>;\n\n\tfn create(&self) -> impl Future<Output = io::Result<Self::File>> {\n\t\tasync move { self.gate().write(true).create(true).truncate(true).open(self.url()).await }\n\t}\n\n\tfn create_dir(&self) -> impl Future<Output = io::Result<()>>;\n\n\tfn create_dir_all(&self) -> impl Future<Output = io::Result<()>> {\n\t\tasync move {\n\t\t\tlet mut url = self.url();\n\t\t\tif url.loc().is_empty() {\n\t\t\t\treturn Ok(());\n\t\t\t}\n\n\t\t\tlet mut stack = Vec::new();\n\t\t\tloop {\n\t\t\t\tmatch Self::new(url).await?.create_dir().await {\n\t\t\t\t\tOk(()) => break,\n\t\t\t\t\tErr(e) if e.kind() == io::ErrorKind::NotFound => {\n\t\t\t\t\t\tif let Some(parent) = url.parent() {\n\t\t\t\t\t\t\tstack.push(url);\n\t\t\t\t\t\t\turl = parent;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn Err(io::Error::other(\"failed to create whole tree\"));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tErr(_) if Self::new(url).await?.metadata().await.is_ok_and(|m| m.is_dir()) => break,\n\t\t\t\t\tErr(e) => return Err(e),\n\t\t\t\t}\n\t\t\t}\n\n\t\t\twhile let Some(u) = stack.pop() {\n\t\t\t\tmatch Self::new(u).await?.create_dir().await {\n\t\t\t\t\tOk(()) => {}\n\t\t\t\t\tErr(_) if Self::new(u).await?.metadata().await.is_ok_and(|m| m.is_dir()) => {}\n\t\t\t\t\tErr(e) => return Err(e),\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tOk(())\n\t\t}\n\t}\n\n\tfn create_new(&self) -> impl Future<Output = io::Result<Self::File>> {\n\t\tasync move { self.gate().write(true).create_new(true).open(self.url()).await }\n\t}\n\n\tfn gate(&self) -> Self::Gate { Self::Gate::default() }\n\n\tfn hard_link<P>(&self, to: P) -> impl Future<Output = io::Result<()>>\n\twhere\n\t\tP: AsPath;\n\n\tfn metadata(&self) -> impl Future<Output = io::Result<Cha>>;\n\n\tfn new<'a>(url: Url<'a>) -> impl Future<Output = io::Result<Self::Me<'a>>>;\n\n\tfn open(&self) -> impl Future<Output = io::Result<Self::File>> {\n\t\tasync move { self.gate().read(true).open(self.url()).await }\n\t}\n\n\tfn read_dir(self) -> impl Future<Output = io::Result<Self::ReadDir>>;\n\n\tfn read_link(&self) -> impl Future<Output = io::Result<PathBufDyn>>;\n\n\tfn remove_dir(&self) -> impl Future<Output = io::Result<()>>;\n\n\tfn remove_dir_all(&self) -> impl Future<Output = io::Result<()>> {\n\t\tasync fn remove_dir_all_impl<P>(url: Url<'_>) -> io::Result<()>\n\t\twhere\n\t\t\tP: Provider,\n\t\t{\n\t\t\tlet mut it = ok_or_not_found!(P::new(url).await?.read_dir().await, return Ok(()));\n\t\t\twhile let Some(child) = it.next().await? {\n\t\t\t\tlet ft = ok_or_not_found!(child.file_type().await, continue);\n\t\t\t\tlet result = if ft.is_dir() {\n\t\t\t\t\tBox::pin(remove_dir_all_impl::<P>(child.url().as_url())).await\n\t\t\t\t} else {\n\t\t\t\t\tP::new(child.url().as_url()).await?.remove_file().await\n\t\t\t\t};\n\n\t\t\t\t() = ok_or_not_found!(result);\n\t\t\t}\n\n\t\t\tOk(ok_or_not_found!(P::new(url).await?.remove_dir().await))\n\t\t}\n\n\t\tasync move {\n\t\t\tlet cha = ok_or_not_found!(self.symlink_metadata().await, return Ok(()));\n\t\t\tif cha.is_link() {\n\t\t\t\tself.remove_file().await\n\t\t\t} else {\n\t\t\t\tremove_dir_all_impl::<Self>(self.url()).await\n\t\t\t}\n\t\t}\n\t}\n\n\tfn remove_dir_clean(&self) -> impl Future<Output = io::Result<()>> {\n\t\tlet root = self.url().to_owned();\n\n\t\tasync move {\n\t\t\tlet mut stack = vec![(root, false)];\n\t\t\tlet mut result = Ok(());\n\n\t\t\twhile let Some((dir, visited)) = stack.pop() {\n\t\t\t\tlet Ok(provider) = Self::new(dir.as_url()).await else {\n\t\t\t\t\tcontinue;\n\t\t\t\t};\n\n\t\t\t\tif visited {\n\t\t\t\t\tresult = provider.remove_dir().await;\n\t\t\t\t} else if let Ok(mut it) = provider.read_dir().await {\n\t\t\t\t\tstack.push((dir, true));\n\t\t\t\t\twhile let Ok(Some(ent)) = it.next().await {\n\t\t\t\t\t\tif ent.file_type().await.is_ok_and(|t| t.is_dir()) {\n\t\t\t\t\t\t\tstack.push((ent.url(), false));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tresult\n\t\t}\n\t}\n\n\tfn remove_file(&self) -> impl Future<Output = io::Result<()>>;\n\n\tfn rename<P>(&self, to: P) -> impl Future<Output = io::Result<()>>\n\twhere\n\t\tP: AsPath;\n\n\tfn symlink<S, F>(&self, original: S, _is_dir: F) -> impl Future<Output = io::Result<()>>\n\twhere\n\t\tS: AsStrand,\n\t\tF: AsyncFnOnce() -> io::Result<bool>;\n\n\tfn symlink_dir<S>(&self, original: S) -> impl Future<Output = io::Result<()>>\n\twhere\n\t\tS: AsStrand,\n\t{\n\t\tself.symlink(original, async || Ok(true))\n\t}\n\n\tfn symlink_file<S>(&self, original: S) -> impl Future<Output = io::Result<()>>\n\twhere\n\t\tS: AsStrand,\n\t{\n\t\tself.symlink(original, async || Ok(false))\n\t}\n\n\tfn symlink_metadata(&self) -> impl Future<Output = io::Result<Cha>>;\n\n\tfn trash(&self) -> impl Future<Output = io::Result<()>>;\n\n\tfn url(&self) -> Url<'_>;\n\n\tfn write<C>(&self, contents: C) -> impl Future<Output = io::Result<()>>\n\twhere\n\t\tC: AsRef<[u8]>,\n\t{\n\t\tasync move { self.create().await?.write_all(contents.as_ref()).await }\n\t}\n}\n\n// --- DirReader\npub trait DirReader {\n\ttype Entry: FileHolder;\n\n\t#[must_use]\n\tfn next(&mut self) -> impl Future<Output = io::Result<Option<Self::Entry>>>;\n}\n\n// --- FileHolder\npub trait FileHolder {\n\t#[must_use]\n\tfn file_type(&self) -> impl Future<Output = io::Result<ChaType>>;\n\n\t#[must_use]\n\tfn metadata(&self) -> impl Future<Output = io::Result<Cha>>;\n\n\t#[must_use]\n\tfn name(&self) -> StrandCow<'_>;\n\n\t#[must_use]\n\tfn path(&self) -> PathBufDyn;\n\n\t#[must_use]\n\tfn url(&self) -> UrlBuf;\n}\n\n// --- FileBuilder\npub trait FileBuilder: Sized + Default {\n\ttype File: AsyncRead + AsyncSeek + AsyncWrite + Unpin;\n\n\tfn append(&mut self, append: bool) -> &mut Self;\n\n\tfn attrs(&mut self, attrs: Attrs) -> &mut Self;\n\n\tfn create(&mut self, create: bool) -> &mut Self;\n\n\tfn create_new(&mut self, create_new: bool) -> &mut Self;\n\n\tfn open<U>(&self, url: U) -> impl Future<Output = io::Result<Self::File>>\n\twhere\n\t\tU: AsUrl;\n\n\tfn read(&mut self, read: bool) -> &mut Self;\n\n\tfn truncate(&mut self, truncate: bool) -> &mut Self;\n\n\tfn write(&mut self, write: bool) -> &mut Self;\n}\n"
  },
  {
    "path": "yazi-fs/src/scheme.rs",
    "content": "use std::path::PathBuf;\n\nuse yazi_shared::scheme::{AsScheme, Scheme, SchemeRef};\n\nuse crate::Xdg;\n\npub trait FsScheme {\n\tfn cache(&self) -> Option<PathBuf>;\n}\n\nimpl FsScheme for SchemeRef<'_> {\n\tfn cache(&self) -> Option<PathBuf> {\n\t\tmatch self {\n\t\t\tSelf::Regular { .. } | Self::Search { .. } => None,\n\t\t\tSelf::Archive { domain, .. } => Some(\n\t\t\t\tXdg::cache_dir().join(format!(\"archive-{}\", yazi_shared::scheme::Encode::domain(domain))),\n\t\t\t),\n\t\t\tSelf::Sftp { domain, .. } => {\n\t\t\t\tSome(Xdg::cache_dir().join(format!(\"sftp-{}\", yazi_shared::scheme::Encode::domain(domain))))\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl FsScheme for Scheme {\n\tfn cache(&self) -> Option<PathBuf> { self.as_scheme().cache() }\n}\n"
  },
  {
    "path": "yazi-fs/src/sorter.rs",
    "content": "use std::cmp::Ordering;\n\nuse hashbrown::HashMap;\nuse rand::{RngCore, SeedableRng, rngs::SmallRng};\nuse yazi_shared::{natsort, path::PathBufDyn, translit::Transliterator, url::UrlLike};\n\nuse crate::{File, SortBy, SortFallback};\n\n#[derive(Clone, Copy, Debug, Default, PartialEq)]\npub struct FilesSorter {\n\tpub by:        SortBy,\n\tpub sensitive: bool,\n\tpub reverse:   bool,\n\tpub dir_first: bool,\n\tpub translit:  bool,\n\tpub fallback:  SortFallback,\n}\n\nimpl FilesSorter {\n\tpub(super) fn sort(&self, items: &mut [File], sizes: &HashMap<PathBufDyn, u64>) {\n\t\tif items.is_empty() {\n\t\t\treturn;\n\t\t}\n\n\t\tmacro_rules! promote {\n\t\t\t($a:ident, $b:ident) => {\n\t\t\t\tif self.dir_first {\n\t\t\t\t\tmatch $b.is_dir().cmp(&$a.is_dir()) {\n\t\t\t\t\t\tOrdering::Equal => {}\n\t\t\t\t\t\tnot_eq => return not_eq,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\tmatch self.by {\n\t\t\tSortBy::None => {}\n\t\t\tSortBy::Mtime => items.sort_unstable_by(|a, b| {\n\t\t\t\tpromote!(a, b);\n\t\t\t\tself.fallback(a, b, self.cmp(a.mtime, b.mtime))\n\t\t\t}),\n\t\t\tSortBy::Btime => items.sort_unstable_by(|a, b| {\n\t\t\t\tpromote!(a, b);\n\t\t\t\tself.fallback(a, b, self.cmp(a.btime, b.btime))\n\t\t\t}),\n\t\t\tSortBy::Extension => items.sort_unstable_by(|a, b| {\n\t\t\t\tpromote!(a, b);\n\t\t\t\tlet aa = a.url.ext().filter(|_| a.is_file());\n\t\t\t\tlet bb = b.url.ext().filter(|_| b.is_file());\n\t\t\t\tlet ord = if self.sensitive {\n\t\t\t\t\tself.cmp(aa, bb)\n\t\t\t\t} else {\n\t\t\t\t\tself.cmp_insensitive(\n\t\t\t\t\t\taa.map_or(&[], |s| s.encoded_bytes()),\n\t\t\t\t\t\tbb.map_or(&[], |s| s.encoded_bytes()),\n\t\t\t\t\t)\n\t\t\t\t};\n\t\t\t\tself.fallback(a, b, ord)\n\t\t\t}),\n\t\t\tSortBy::Alphabetical => items.sort_unstable_by(|a, b| {\n\t\t\t\tpromote!(a, b);\n\t\t\t\tself.fallback(a, b, self.sort_alphabetically(a, b))\n\t\t\t}),\n\t\t\tSortBy::Natural => items.sort_unstable_by(|a, b| {\n\t\t\t\tpromote!(a, b);\n\t\t\t\tself.fallback(a, b, self.sort_naturally(a, b))\n\t\t\t}),\n\t\t\tSortBy::Size => items.sort_unstable_by(|a, b| {\n\t\t\t\tpromote!(a, b);\n\t\t\t\tlet aa = if a.is_dir() { sizes.get(&a.urn()).copied() } else { None };\n\t\t\t\tlet bb = if b.is_dir() { sizes.get(&b.urn()).copied() } else { None };\n\t\t\t\tself.fallback(a, b, self.cmp(aa.unwrap_or(a.len), bb.unwrap_or(b.len)))\n\t\t\t}),\n\t\t\tSortBy::Random => {\n\t\t\t\tlet mut rng = SmallRng::from_os_rng();\n\t\t\t\titems.sort_unstable_by(|a, b| {\n\t\t\t\t\tpromote!(a, b);\n\t\t\t\t\tself.cmp(rng.next_u64(), rng.next_u64())\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\t#[inline(always)]\n\tfn sort_alphabetically(&self, a: &File, b: &File) -> Ordering {\n\t\tif self.sensitive {\n\t\t\tself.cmp(a.urn().encoded_bytes(), b.urn().encoded_bytes())\n\t\t} else {\n\t\t\tself.cmp_insensitive(a.urn().encoded_bytes(), b.urn().encoded_bytes())\n\t\t}\n\t}\n\n\t#[inline(always)]\n\tfn sort_naturally(&self, a: &File, b: &File) -> Ordering {\n\t\tlet ordering = if self.translit {\n\t\t\tnatsort(\n\t\t\t\ta.urn().encoded_bytes().transliterate().as_bytes(),\n\t\t\t\tb.urn().encoded_bytes().transliterate().as_bytes(),\n\t\t\t\t!self.sensitive,\n\t\t\t)\n\t\t} else {\n\t\t\tnatsort(a.urn().encoded_bytes(), b.urn().encoded_bytes(), !self.sensitive)\n\t\t};\n\n\t\tif self.reverse { ordering.reverse() } else { ordering }\n\t}\n\n\t#[inline(always)]\n\tfn fallback(&self, a: &File, b: &File, ord: Ordering) -> Ordering {\n\t\tif ord != Ordering::Equal {\n\t\t\treturn ord;\n\t\t}\n\n\t\tmatch self.fallback {\n\t\t\tSortFallback::Alphabetical => self.cmp(a.urn().encoded_bytes(), b.urn().encoded_bytes()),\n\t\t\tSortFallback::Natural => {\n\t\t\t\tlet ord = natsort(a.urn().encoded_bytes(), b.urn().encoded_bytes(), false);\n\t\t\t\tif self.reverse { ord.reverse() } else { ord }\n\t\t\t}\n\t\t}\n\t}\n\n\t#[inline(always)]\n\tfn cmp<T: Ord>(&self, a: T, b: T) -> Ordering { if self.reverse { b.cmp(&a) } else { a.cmp(&b) } }\n\n\t#[inline(always)]\n\tfn cmp_insensitive(&self, a: &[u8], b: &[u8]) -> Ordering {\n\t\tlet l = a.len().min(b.len());\n\t\tlet (lhs, rhs) = if self.reverse { (&b[..l], &a[..l]) } else { (&a[..l], &b[..l]) };\n\n\t\tfor i in 0..l {\n\t\t\tmatch lhs[i].to_ascii_lowercase().cmp(&rhs[i].to_ascii_lowercase()) {\n\t\t\t\tOrdering::Equal => {}\n\t\t\t\tnot_eq => return not_eq,\n\t\t\t}\n\t\t}\n\n\t\tif self.reverse { b.len().cmp(&a.len()) } else { a.len().cmp(&b.len()) }\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/sorting.rs",
    "content": "use std::{fmt::Display, str::FromStr};\n\nuse serde::{Deserialize, Serialize};\n\n// --- by\n#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]\n#[serde(rename_all = \"kebab-case\")]\npub enum SortBy {\n\t#[default]\n\tNone,\n\tMtime,\n\tBtime,\n\tExtension,\n\tAlphabetical,\n\tNatural,\n\tSize,\n\tRandom,\n}\n\nimpl FromStr for SortBy {\n\ttype Err = serde::de::value::Error;\n\n\tfn from_str(s: &str) -> Result<Self, Self::Err> {\n\t\tSelf::deserialize(serde::de::value::StrDeserializer::new(s))\n\t}\n}\n\nimpl Display for SortBy {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tf.write_str(match self {\n\t\t\tSelf::None => \"none\",\n\t\t\tSelf::Mtime => \"mtime\",\n\t\t\tSelf::Btime => \"btime\",\n\t\t\tSelf::Extension => \"extension\",\n\t\t\tSelf::Alphabetical => \"alphabetical\",\n\t\t\tSelf::Natural => \"natural\",\n\t\t\tSelf::Size => \"size\",\n\t\t\tSelf::Random => \"random\",\n\t\t})\n\t}\n}\n\n// --- fallback\n#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]\n#[serde(rename_all = \"kebab-case\")]\npub enum SortFallback {\n\t#[default]\n\tAlphabetical,\n\tNatural,\n}\n\nimpl FromStr for SortFallback {\n\ttype Err = serde::de::value::Error;\n\n\tfn from_str(s: &str) -> Result<Self, Self::Err> {\n\t\tSelf::deserialize(serde::de::value::StrDeserializer::new(s))\n\t}\n}\n\nimpl Display for SortFallback {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tf.write_str(match self {\n\t\t\tSelf::Alphabetical => \"alphabetical\",\n\t\t\tSelf::Natural => \"natural\",\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/splatter.rs",
    "content": "#[cfg(unix)]\nuse std::os::unix::ffi::{OsStrExt, OsStringExt};\n#[cfg(windows)]\nuse std::os::windows::ffi::{OsStrExt, OsStringExt};\nuse std::{cell::Cell, ffi::{OsStr, OsString}, iter::{self, Peekable}, mem};\n\nuse yazi_shared::url::{AsUrl, Url, UrlCow};\n\nuse crate::FsUrl;\n\n#[cfg(unix)]\ntype Iter<'a> = Peekable<std::iter::Copied<std::slice::Iter<'a, u8>>>;\n#[cfg(windows)]\ntype Iter<'a> = Peekable<std::os::windows::ffi::EncodeWide<'a>>;\n\n#[cfg(unix)]\ntype Buf = Vec<u8>;\n#[cfg(windows)]\ntype Buf = Vec<u16>;\n\n#[derive(Clone, Copy)]\npub struct Splatter<T> {\n\tsrc: T,\n\ttab: usize,\n}\n\npub trait Splatable {\n\tfn tab(&self) -> usize;\n\n\tfn selected(&self, tab: usize, idx: Option<usize>) -> impl Iterator<Item = Url<'_>>;\n\n\tfn hovered(&self, tab: usize) -> Option<Url<'_>>;\n\n\tfn yanked(&self) -> impl Iterator<Item = Url<'_>>;\n}\n\n#[cfg(unix)]\nfn b2c(b: u8) -> Option<char> { Some(b as char) }\n#[cfg(windows)]\nfn b2c(b: u16) -> Option<char> { char::from_u32(b as u32) }\n\nfn cue(buf: &mut Buf, s: impl AsRef<OsStr>) {\n\t#[cfg(unix)]\n\tbuf.extend(yazi_shared::shell::escape_os_str(s.as_ref()).as_bytes());\n\t#[cfg(windows)]\n\tbuf.extend(yazi_shared::shell::escape_os_str(s.as_ref()).encode_wide());\n}\n\nimpl<T> Splatter<T>\nwhere\n\tT: Splatable,\n{\n\tpub fn new(src: T) -> Self { Self { tab: src.tab() + 1, src } }\n\n\tpub fn splat(mut self, cmd: impl AsRef<OsStr>) -> OsString {\n\t\t#[cfg(unix)]\n\t\tlet mut it = cmd.as_ref().as_bytes().iter().copied().peekable();\n\t\t#[cfg(windows)]\n\t\tlet mut it = cmd.as_ref().encode_wide().peekable();\n\n\t\tlet mut buf = vec![];\n\t\twhile let Some(cur) = it.next() {\n\t\t\tif b2c(cur) == Some('%') && it.peek().is_some() {\n\t\t\t\tself.visit(&mut it, &mut buf);\n\t\t\t} else {\n\t\t\t\tbuf.push(cur);\n\t\t\t}\n\t\t}\n\n\t\t#[cfg(unix)]\n\t\treturn OsString::from_vec(buf);\n\t\t#[cfg(windows)]\n\t\treturn OsString::from_wide(&buf);\n\t}\n\n\tfn visit(&mut self, it: &mut Iter, buf: &mut Buf) {\n\t\tlet c = it.peek().copied().and_then(b2c);\n\t\tmatch c {\n\t\t\tSome('s') | Some('S') => self.visit_selected(it, buf),\n\t\t\tSome('h') | Some('H') => self.visit_hovered(it, buf),\n\t\t\tSome('d') | Some('D') => self.visit_dirname(it, buf),\n\t\t\tSome('t') | Some('T') => self.visit_tab(it, buf),\n\t\t\tSome('y') | Some('Y') => self.visit_yanked(it, buf),\n\t\t\tSome('%') => self.visit_escape(it, buf),\n\t\t\tSome('*') => self.visit_selected(it, buf), // TODO: remove this\n\t\t\tSome(c) if c.is_ascii_digit() => self.visit_digit(it, buf),\n\t\t\t_ => self.visit_unknown(it, buf),\n\t\t}\n\t}\n\n\tfn visit_selected(&mut self, it: &mut Iter, buf: &mut Buf) {\n\t\tlet c = it.next().and_then(b2c);\n\t\tlet idx = self.consume_digit(it);\n\n\t\tlet mut first = true;\n\t\tfor url in self.src.selected(self.tab, idx) {\n\t\t\tif !mem::replace(&mut first, false) {\n\t\t\t\tbuf.push(b' ' as _);\n\t\t\t}\n\n\t\t\tif c == Some('S') {\n\t\t\t\tcue(buf, url.os_str());\n\t\t\t} else {\n\t\t\t\tcue(buf, url.unified_path_str());\n\t\t\t}\n\t\t}\n\t\tif first && idx.is_some() {\n\t\t\tcue(buf, \"\");\n\t\t}\n\t}\n\n\tfn visit_hovered(&mut self, it: &mut Iter, buf: &mut Buf) {\n\t\tmatch it.next().and_then(b2c) {\n\t\t\tSome('h') => {\n\t\t\t\tcue(buf, self.src.hovered(self.tab).map(|u| u.unified_path_str()).unwrap_or_default());\n\t\t\t}\n\t\t\tSome('H') => {\n\t\t\t\tcue(buf, self.src.hovered(self.tab).map(|u| u.os_str()).unwrap_or_default());\n\t\t\t}\n\t\t\t_ => unreachable!(),\n\t\t}\n\t}\n\n\tfn visit_dirname(&mut self, it: &mut Iter, buf: &mut Buf) {\n\t\tlet c = it.next().and_then(b2c);\n\t\tlet idx = self.consume_digit(it);\n\n\t\tlet mut first = true;\n\t\tfor url in self.src.selected(self.tab, idx) {\n\t\t\tif !mem::replace(&mut first, false) {\n\t\t\t\tbuf.push(b' ' as _);\n\t\t\t}\n\n\t\t\tif c == Some('D') {\n\t\t\t\tcue(buf, url.parent().map(|p| p.os_str()).unwrap_or_default());\n\t\t\t} else {\n\t\t\t\tcue(buf, url.parent().map(|p| p.unified_path_str()).unwrap_or_default());\n\t\t\t}\n\t\t}\n\t\tif first && idx.is_some() {\n\t\t\tcue(buf, \"\");\n\t\t}\n\t}\n\n\tfn visit_tab(&mut self, it: &mut Iter, buf: &mut Buf) {\n\t\tlet old = self.tab;\n\t\tmatch it.next().and_then(b2c) {\n\t\t\tSome('t') => self.tab = self.tab.saturating_add(1),\n\t\t\tSome('T') => self.tab = self.tab.saturating_sub(1),\n\t\t\t_ => unreachable!(),\n\t\t}\n\n\t\tself.visit(it, buf);\n\t\tself.tab = old;\n\t}\n\n\tfn visit_digit(&mut self, it: &mut Iter, buf: &mut Buf) {\n\t\t// TODO: remove\n\t\tmatch self.consume_digit(it) {\n\t\t\tSome(0) => {\n\t\t\t\tcue(buf, self.src.hovered(self.tab).map(|u| u.unified_path_str()).unwrap_or_default());\n\t\t\t}\n\t\t\tSome(n) => {\n\t\t\t\tcue(\n\t\t\t\t\tbuf,\n\t\t\t\t\tself\n\t\t\t\t\t\t.src\n\t\t\t\t\t\t.selected(self.tab, Some(n))\n\t\t\t\t\t\t.next()\n\t\t\t\t\t\t.map(|u| u.unified_path_str())\n\t\t\t\t\t\t.unwrap_or_default(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tNone => unreachable!(),\n\t\t}\n\t}\n\n\tfn visit_yanked(&mut self, it: &mut Iter, buf: &mut Buf) {\n\t\tlet c = it.next().and_then(b2c);\n\n\t\tlet mut first = true;\n\t\tfor url in self.src.yanked() {\n\t\t\tif !mem::replace(&mut first, false) {\n\t\t\t\tbuf.push(b' ' as _);\n\t\t\t}\n\n\t\t\tif c == Some('Y') {\n\t\t\t\tcue(buf, url.os_str());\n\t\t\t} else {\n\t\t\t\tcue(buf, url.unified_path_str());\n\t\t\t}\n\t\t}\n\t}\n\n\tfn visit_escape(&mut self, it: &mut Iter, buf: &mut Buf) { buf.push(it.next().unwrap()); }\n\n\tfn visit_unknown(&mut self, it: &mut Iter, buf: &mut Buf) {\n\t\tbuf.push(b'%' as _);\n\t\tif let Some(b) = it.next() {\n\t\t\tbuf.push(b);\n\t\t}\n\t}\n\n\tfn consume_digit(&mut self, it: &mut Iter) -> Option<usize> {\n\t\tfn next(it: &mut Iter) -> Option<usize> {\n\t\t\tlet n = b2c(*it.peek()?)?.to_digit(10)? as usize;\n\t\t\tit.next();\n\t\t\tSome(n)\n\t\t}\n\n\t\tlet mut sum = next(it)?;\n\t\twhile let Some(n) = next(it) {\n\t\t\tsum = sum.checked_mul(10)?.checked_add(n)?;\n\t\t}\n\t\tSome(sum)\n\t}\n}\n\nimpl<T> Splatter<T> {\n\tpub fn spread(cmd: impl AsRef<OsStr>) -> bool {\n\t\tstruct Source(Cell<bool>);\n\n\t\timpl Splatable for &Source {\n\t\t\tfn tab(&self) -> usize { 0 }\n\n\t\t\tfn selected(&self, _tab: usize, idx: Option<usize>) -> impl Iterator<Item = Url<'_>> {\n\t\t\t\tif idx.is_none() {\n\t\t\t\t\tself.0.set(true);\n\t\t\t\t}\n\t\t\t\titer::empty()\n\t\t\t}\n\n\t\t\tfn hovered(&self, _tab: usize) -> Option<Url<'_>> { None }\n\n\t\t\tfn yanked(&self) -> impl Iterator<Item = Url<'_>> {\n\t\t\t\tself.0.set(true);\n\t\t\t\titer::empty()\n\t\t\t}\n\t\t}\n\n\t\tlet src = Source(Cell::new(false));\n\t\tSplatter { src: &src, tab: 1 }.splat(cmd.as_ref());\n\t\tsrc.0.get()\n\t}\n}\n\n// TODO: remove\nimpl<'a, T> Splatable for &'a T\nwhere\n\tT: AsRef<[UrlCow<'a>]>,\n{\n\tfn tab(&self) -> usize { 0 }\n\n\tfn selected(&self, tab: usize, idx: Option<usize>) -> impl Iterator<Item = Url<'_>> {\n\t\tself\n\t\t\t.as_ref()\n\t\t\t.iter()\n\t\t\t.filter(move |_| tab == 1)\n\t\t\t.map(|u| u.as_url())\n\t\t\t.skip(idx.unwrap_or(1))\n\t\t\t.take(if idx.is_some() { 1 } else { usize::MAX })\n\t}\n\n\tfn hovered(&self, tab: usize) -> Option<Url<'_>> {\n\t\tself.as_ref().first().filter(|_| tab == 1).map(|u| u.as_url())\n\t}\n\n\tfn yanked(&self) -> impl Iterator<Item = Url<'_>> { iter::empty() }\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\n\tstruct Source(usize);\n\n\timpl Splatable for Source {\n\t\tfn tab(&self) -> usize { self.0 }\n\n\t\tfn selected(&self, tab: usize, mut idx: Option<usize>) -> impl Iterator<Item = Url<'_>> {\n\t\t\tlet urls = if tab == 1 {\n\t\t\t\tvec![Url::regular(\"t1/s1\"), Url::regular(\"t1/s2\")]\n\t\t\t} else if tab == 2 {\n\t\t\t\tvec![Url::regular(\"t 2/s 1\"), Url::regular(\"t 2/s 2\")]\n\t\t\t} else {\n\t\t\t\tvec![]\n\t\t\t};\n\n\t\t\tidx = idx.and_then(|i| i.checked_sub(1));\n\t\t\turls.into_iter().skip(idx.unwrap_or(0)).take(if idx.is_some() { 1 } else { usize::MAX })\n\t\t}\n\n\t\tfn hovered(&self, tab: usize) -> Option<Url<'_>> {\n\t\t\tif tab == 1 {\n\t\t\t\tSome(Url::regular(\"hovered\"))\n\t\t\t} else if tab == 2 {\n\t\t\t\tSome(Url::regular(\"hover ed\"))\n\t\t\t} else {\n\t\t\t\tNone\n\t\t\t}\n\t\t}\n\n\t\tfn yanked(&self) -> impl Iterator<Item = Url<'_>> {\n\t\t\t[Url::regular(\"y1\"), Url::regular(\"y 2\"), Url::regular(\"y3\")].into_iter()\n\t\t}\n\t}\n\n\t#[test]\n\t#[cfg(unix)]\n\tfn test_unix() {\n\t\tlet cases = [\n\t\t\t// Selected\n\t\t\t(Source(0), r#\"ls %s\"#, r#\"ls t1/s1 t1/s2\"#),\n\t\t\t(Source(0), r#\"ls %s1 %s2 %s3\"#, r#\"ls t1/s1 t1/s2 ''\"#),\n\t\t\t(Source(0), r#\"ls %s %s2 %s\"#, r#\"ls t1/s1 t1/s2 t1/s2 t1/s1 t1/s2\"#),\n\t\t\t(Source(1), r#\"ls %s\"#, r#\"ls 't 2/s 1' 't 2/s 2'\"#),\n\t\t\t(Source(1), r#\"ls %s1 %s3 %s2\"#, r#\"ls 't 2/s 1' '' 't 2/s 2'\"#),\n\t\t\t(Source(2), r#\"ls %s\"#, r#\"ls \"#),\n\t\t\t(Source(2), r#\"ls %s1 %s %s2\"#, r#\"ls ''  ''\"#),\n\t\t\t// Hovered\n\t\t\t(Source(0), r#\"ls %h\"#, r#\"ls hovered\"#),\n\t\t\t(Source(1), r#\"ls %h\"#, r#\"ls 'hover ed'\"#),\n\t\t\t(Source(2), r#\"ls %h\"#, r#\"ls ''\"#),\n\t\t\t// Dirname\n\t\t\t(Source(0), r#\"cd %d\"#, r#\"cd t1 t1\"#),\n\t\t\t(Source(1), r#\"cd %d\"#, r#\"cd 't 2' 't 2'\"#),\n\t\t\t(Source(1), r#\"cd %d1 %d3 %d2\"#, r#\"cd 't 2' '' 't 2'\"#),\n\t\t\t(Source(2), r#\"cd %d %d1\"#, r#\"cd  ''\"#),\n\t\t\t// Yanked\n\t\t\t(Source(0), r#\"cd %y\"#, r#\"cd y1 'y 2' y3\"#),\n\t\t\t(Source(1), r#\"cd %y\"#, r#\"cd y1 'y 2' y3\"#),\n\t\t\t(Source(2), r#\"cd %y\"#, r#\"cd y1 'y 2' y3\"#),\n\t\t\t// Tab\n\t\t\t(Source(0), r#\"ls %s %ts %s\"#, r#\"ls t1/s1 t1/s2 't 2/s 1' 't 2/s 2' t1/s1 t1/s2\"#),\n\t\t\t(Source(1), r#\"ls %s1 %ts %s2\"#, r#\"ls 't 2/s 1'  't 2/s 2'\"#),\n\t\t\t(Source(1), r#\"ls %s1 %Ts1 %s2 %Ts2\"#, r#\"ls 't 2/s 1' t1/s1 't 2/s 2' t1/s2\"#),\n\t\t\t(Source(0), r#\"ls %s1 %Ts1 %s2 %Ts2\"#, r#\"ls t1/s1 '' t1/s2 ''\"#),\n\t\t\t(Source(0), r#\"ls %ty\"#, r#\"ls y1 'y 2' y3\"#),\n\t\t\t(Source(0), r#\"ls %Ty\"#, r#\"ls y1 'y 2' y3\"#),\n\t\t\t// Escape\n\t\t\t(\n\t\t\t\tSource(0),\n\t\t\t\tr#\"echo % %% %s2 %%h %d %%%y %%%%ts %%%%%ts1\"#,\n\t\t\t\tr#\"echo % % t1/s2 %h t1 t1 %y1 'y 2' y3 %%ts %%'t 2/s 1'\"#,\n\t\t\t),\n\t\t\t// TODO: remove\n\t\t\t(Source(0), r#\"ls %1 %* %2 %0 %3\"#, r#\"ls t1/s1 t1/s1 t1/s2 t1/s2 hovered ''\"#),\n\t\t];\n\n\t\tfor (src, cmd, expected) in cases {\n\t\t\tlet s = Splatter::new(src).splat(OsStr::new(cmd));\n\t\t\tassert_eq!(s, OsStr::new(expected), \"{cmd}\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/stage.rs",
    "content": "use serde::{Deserialize, Serialize};\n\nuse crate::error::Error;\n\n#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]\npub enum FolderStage {\n\t#[default]\n\tLoading,\n\tLoaded,\n\tFailed(Error),\n}\n\nimpl FolderStage {\n\tpub fn is_loading(&self) -> bool { *self == Self::Loading }\n}\n"
  },
  {
    "path": "yazi-fs/src/url.rs",
    "content": "use std::{borrow::Cow, ffi::OsStr, path::{Path, PathBuf}};\n\nuse yazi_shared::{path::{AsPath, PathDyn}, url::{AsUrl, Url, UrlBuf, UrlCow}};\n\nuse crate::{FsHash128, FsScheme, path::PercentEncoding};\n\npub trait FsUrl<'a> {\n\tfn cache(&self) -> Option<PathBuf>;\n\n\tfn cache_lock(&self) -> Option<PathBuf>;\n\n\tfn unified_path(self) -> Cow<'a, Path>;\n\n\tfn unified_path_str(self) -> Cow<'a, OsStr>\n\twhere\n\t\tSelf: Sized,\n\t{\n\t\tmatch self.unified_path() {\n\t\t\tCow::Borrowed(p) => p.as_os_str().into(),\n\t\t\tCow::Owned(p) => p.into_os_string().into(),\n\t\t}\n\t}\n}\n\nimpl<'a> FsUrl<'a> for Url<'a> {\n\tfn cache(&self) -> Option<PathBuf> {\n\t\tfn with_loc(loc: PathDyn, mut root: PathBuf) -> PathBuf {\n\t\t\tlet mut it = loc.components();\n\t\t\tif it.next() == Some(yazi_shared::path::Component::RootDir) {\n\t\t\t\troot.push(it.as_path().percent_encode());\n\t\t\t} else {\n\t\t\t\troot.push(\".%2F\");\n\t\t\t\troot.push(loc.percent_encode());\n\t\t\t}\n\t\t\troot\n\t\t}\n\n\t\tself.scheme().cache().map(|root| with_loc(self.loc(), root))\n\t}\n\n\tfn cache_lock(&self) -> Option<PathBuf> {\n\t\tself.scheme().cache().map(|mut root| {\n\t\t\troot.push(\"%lock\");\n\t\t\troot.push(format!(\"{:x}\", self.hash_u128()));\n\t\t\troot\n\t\t})\n\t}\n\n\tfn unified_path(self) -> Cow<'a, Path> {\n\t\tmatch self {\n\t\t\tSelf::Regular(loc) | Self::Search { loc, .. } => loc.as_inner().into(),\n\t\t\tSelf::Archive { .. } | Self::Sftp { .. } => {\n\t\t\t\tself.cache().expect(\"non-local URL should have a cache path\").into()\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl FsUrl<'_> for UrlBuf {\n\tfn cache(&self) -> Option<PathBuf> { self.as_url().cache() }\n\n\tfn cache_lock(&self) -> Option<PathBuf> { self.as_url().cache_lock() }\n\n\tfn unified_path(self) -> Cow<'static, Path> {\n\t\tmatch self {\n\t\t\tSelf::Regular(loc) | Self::Search { loc, .. } => loc.into_inner().into(),\n\t\t\tSelf::Archive { .. } | Self::Sftp { .. } => {\n\t\t\t\tself.cache().expect(\"non-local URL should have a cache path\").into()\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl<'a> FsUrl<'a> for UrlCow<'a> {\n\tfn cache(&self) -> Option<PathBuf> { self.as_url().cache() }\n\n\tfn cache_lock(&self) -> Option<PathBuf> { self.as_url().cache_lock() }\n\n\tfn unified_path(self) -> Cow<'a, Path> {\n\t\tmatch self {\n\t\t\tSelf::Regular(loc) | Self::Search { loc, .. } => loc.into_inner().into(),\n\t\t\tSelf::RegularRef(loc) | Self::SearchRef { loc, .. } => loc.as_inner().into(),\n\t\t\tSelf::Archive { .. } | Self::ArchiveRef { .. } | Self::Sftp { .. } | Self::SftpRef { .. } => {\n\t\t\t\tself.cache().expect(\"non-local URL should have a cache path\").into()\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-fs/src/xdg.rs",
    "content": "use std::{env, path::PathBuf, sync::OnceLock};\n\npub struct Xdg;\n\nimpl Xdg {\n\tpub fn config_dir() -> PathBuf {\n\t\tif let Some(p) = env::var_os(\"YAZI_CONFIG_HOME\").map(PathBuf::from)\n\t\t\t&& p.is_absolute()\n\t\t{\n\t\t\treturn p;\n\t\t}\n\n\t\t#[cfg(windows)]\n\t\t{\n\t\t\tdirs::config_dir()\n\t\t\t\t.map(|p| p.join(\"yazi\").join(\"config\"))\n\t\t\t\t.expect(\"Failed to get config directory\")\n\t\t}\n\t\t#[cfg(unix)]\n\t\t{\n\t\t\tenv::var_os(\"XDG_CONFIG_HOME\")\n\t\t\t\t.map(PathBuf::from)\n\t\t\t\t.filter(|p| p.is_absolute())\n\t\t\t\t.or_else(|| dirs::home_dir().map(|h| h.join(\".config\")))\n\t\t\t\t.map(|p| p.join(\"yazi\"))\n\t\t\t\t.expect(\"Failed to get config directory\")\n\t\t}\n\t}\n\n\tpub fn state_dir() -> PathBuf {\n\t\t#[cfg(windows)]\n\t\t{\n\t\t\tdirs::data_dir().map(|p| p.join(\"yazi\").join(\"state\")).expect(\"Failed to get state directory\")\n\t\t}\n\t\t#[cfg(unix)]\n\t\t{\n\t\t\tenv::var_os(\"XDG_STATE_HOME\")\n\t\t\t\t.map(PathBuf::from)\n\t\t\t\t.filter(|p| p.is_absolute())\n\t\t\t\t.or_else(|| dirs::home_dir().map(|h| h.join(\".local/state\")))\n\t\t\t\t.map(|p| p.join(\"yazi\"))\n\t\t\t\t.expect(\"Failed to get state directory\")\n\t\t}\n\t}\n\n\tpub fn cache_dir() -> &'static PathBuf {\n\t\tstatic CACHE: OnceLock<PathBuf> = OnceLock::new();\n\n\t\tCACHE.get_or_init(|| {\n\t\t\tlet mut p = env::temp_dir();\n\t\t\tassert!(p.is_absolute(), \"Temp dir is not absolute\");\n\n\t\t\t#[cfg(unix)]\n\t\t\t{\n\t\t\t\tuse uzers::Users;\n\t\t\t\tp.push(format!(\"yazi-{}\", yazi_shared::USERS_CACHE.get_current_uid()))\n\t\t\t}\n\t\t\t#[cfg(not(unix))]\n\t\t\tp.push(\"yazi\");\n\n\t\t\tp\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-macro/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-macro\"\ndescription            = \"Yazi macros\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n"
  },
  {
    "path": "yazi-macro/README.md",
    "content": "# yazi-macro\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-macro/src/actor.rs",
    "content": "#[macro_export]\nmacro_rules! act {\n\t(@pre $layer:ident : $name:ident, $cx:ident, $opt:ident) => {\n\t\tif let Some(hook) = <act!($layer:$name) as yazi_actor::Actor>::hook($cx, &$opt) {\n\t\t\t<act!(core:preflight)>::act($cx, (hook, yazi_dds::spark!($layer:$name, $opt))).map(|spark| spark.try_into().unwrap())\n\t\t} else {\n\t\t\tOk($opt)\n\t\t}\n\t};\n\t(@impl $layer:ident : $name:ident, $cx:ident, $opt:ident) => {{\n\t\t$cx.level += 1;\n\t\t#[cfg(debug_assertions)]\n\t\t$cx.backtrace.push(concat!(stringify!($layer), \":\", stringify!($name)));\n\n\t\tlet result = match act!(@pre $layer:$name, $cx, $opt) {\n\t\t\tOk(opt) => <act!($layer:$name) as yazi_actor::Actor>::act($cx, opt),\n\t\t\tErr(e) => Err(e),\n\t\t};\n\n\t\t$cx.level -= 1;\n\t\t#[cfg(debug_assertions)]\n\t\t$cx.backtrace.pop();\n\n\t\tresult\n\t}};\n\n\t($layer:ident : $name:ident, $cx:ident, $action:expr) => {\n\t\t<act!($layer:$name) as yazi_actor::Actor>::Options::try_from($action)\n\t\t\t.map_err(anyhow::Error::from)\n\t\t\t.and_then(|opt| act!(@impl $layer:$name, $cx, opt))\n\t};\n\t($layer:ident : $name:ident, $cx:ident) => {\n\t\tact!($layer:$name, $cx, <<act!($layer:$name) as yazi_actor::Actor>::Options as Default>::default())\n\t};\n\t($layer:ident : $name:ident) => {\n\t\tpaste::paste! { yazi_actor::$layer::[<$name:camel>] }\n\t};\n\n\t($name:ident, $cx:expr, $action:expr) => {\n\t\t$action.try_into().map_err(anyhow::Error::from).and_then(|opt| $cx.$name(opt))\n\t};\n\t($name:ident, $cx:expr) => {\n\t\t$cx.$name(Default::default())\n\t};\n}\n"
  },
  {
    "path": "yazi-macro/src/asset.rs",
    "content": "#[macro_export]\nmacro_rules! config_preset {\n\t($name:literal) => {{\n\t\t#[cfg(debug_assertions)]\n\t\t{\n\t\t\tstd::borrow::Cow::from(\n\t\t\t\tstd::fs::read_to_string(concat!(\n\t\t\t\t\tenv!(\"CARGO_MANIFEST_DIR\"),\n\t\t\t\t\t\"/preset/\",\n\t\t\t\t\t$name,\n\t\t\t\t\t\"-default.toml\"\n\t\t\t\t))\n\t\t\t\t.expect(concat!(\"Failed to read 'yazi-config/preset/\", $name, \"-default.toml'\")),\n\t\t\t)\n\t\t}\n\t\t#[cfg(not(debug_assertions))]\n\t\t{\n\t\t\tstd::borrow::Cow::from(include_str!(concat!(\n\t\t\t\tenv!(\"CARGO_MANIFEST_DIR\"),\n\t\t\t\t\"/preset/\",\n\t\t\t\t$name,\n\t\t\t\t\"-default.toml\"\n\t\t\t)))\n\t\t}\n\t}};\n}\n\n#[macro_export]\nmacro_rules! plugin_preset {\n\t($name:literal) => {{\n\t\t#[cfg(debug_assertions)]\n\t\t{\n\t\t\tstd::fs::read(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/preset/\", $name, \".lua\")).expect(concat!(\n\t\t\t\t\"Failed to read 'yazi-plugin/preset/\",\n\t\t\t\t$name,\n\t\t\t\t\".lua'\"\n\t\t\t))\n\t\t}\n\t\t#[cfg(not(debug_assertions))]\n\t\t{\n\t\t\t&include_bytes!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/preset/\", $name, \".lua\"))[..]\n\t\t}\n\t}};\n}\n\n#[macro_export]\nmacro_rules! theme_preset {\n\t($name:literal) => {{\n\t\t#[cfg(debug_assertions)]\n\t\t{\n\t\t\tstd::borrow::Cow::from(\n\t\t\t\tstd::fs::read_to_string(concat!(\n\t\t\t\t\tenv!(\"CARGO_MANIFEST_DIR\"),\n\t\t\t\t\t\"/preset/theme-\",\n\t\t\t\t\t$name,\n\t\t\t\t\t\".toml\"\n\t\t\t\t))\n\t\t\t\t.expect(concat!(\"Failed to read 'yazi-config/preset/theme-\", $name, \".toml'\")),\n\t\t\t)\n\t\t}\n\t\t#[cfg(not(debug_assertions))]\n\t\t{\n\t\t\tstd::borrow::Cow::from(include_str!(concat!(\n\t\t\t\tenv!(\"CARGO_MANIFEST_DIR\"),\n\t\t\t\t\"/preset/theme-\",\n\t\t\t\t$name,\n\t\t\t\t\".toml\"\n\t\t\t)))\n\t\t}\n\t}};\n}\n"
  },
  {
    "path": "yazi-macro/src/context.rs",
    "content": "#[macro_export]\nmacro_rules! tab {\n\t($cx:ident) => {{\n\t\tlet tab = $cx.tab;\n\t\t&mut $cx.core.mgr.tabs.items[tab]\n\t}};\n}\n"
  },
  {
    "path": "yazi-macro/src/event.rs",
    "content": "#[macro_export]\nmacro_rules! emit {\n\t(Call($action:expr)) => {\n\t\tyazi_shared::event::Event::Call(yazi_shared::event::ActionCow::from($action)).emit();\n\t};\n\t(Seq($actions:expr)) => {\n\t\tyazi_shared::event::Event::Seq($actions).emit();\n\t};\n\t($event:ident) => {\n\t\tyazi_shared::event::Event::$event.emit();\n\t};\n}\n\n#[macro_export]\nmacro_rules! relay {\n\t($layer:ident : $name:ident) => {\n\t\tyazi_shared::event::Action::new_relay(concat!(stringify!($layer), \":\", stringify!($name)))\n\t};\n\t($layer:ident : $name:ident, $args:expr) => {\n\t\tyazi_shared::event::Action::new_relay_args(\n\t\t\tconcat!(stringify!($layer), \":\", stringify!($name)),\n\t\t\t$args,\n\t\t)\n\t};\n}\n\n#[macro_export]\nmacro_rules! succ {\n\t($data:expr) => {\n\t\treturn Ok(yazi_shared::data::Data::from($data))\n\t};\n\t() => {\n\t\treturn Ok(yazi_shared::data::Data::Nil)\n\t};\n}\n"
  },
  {
    "path": "yazi-macro/src/fmt.rs",
    "content": "#[macro_export]\nmacro_rules! try_format {\n\t($($arg:tt)*) => {{\n\t\tuse std::fmt::Write;\n\n\t\tlet mut output = String::new();\n\t\toutput.write_fmt(format_args!($($arg)*)).map(|_| output)\n\t}}\n}\n"
  },
  {
    "path": "yazi-macro/src/fs.rs",
    "content": "#[macro_export]\nmacro_rules! ok_or_not_found {\n\t($result:expr, $not_found:expr) => {\n\t\tmatch $result {\n\t\t\tOk(v) => v,\n\t\t\tErr(e) if e.kind() == std::io::ErrorKind::NotFound => $not_found,\n\t\t\tErr(e) => Err(e)?,\n\t\t}\n\t};\n\t($result:expr) => {\n\t\tok_or_not_found!($result, Default::default())\n\t};\n}\n"
  },
  {
    "path": "yazi-macro/src/lib.rs",
    "content": "mod actor;\nmod asset;\nmod context;\nmod event;\nmod fmt;\nmod fs;\nmod log;\nmod module;\nmod platform;\nmod render;\nmod stdio;\n"
  },
  {
    "path": "yazi-macro/src/log.rs",
    "content": "#[macro_export]\nmacro_rules! time {\n\t($expr:expr) => {\n\t\t$crate::time!(stringify!($expr), $expr)\n\t};\n\t($label:expr, $expr:expr) => {\n\t\t$crate::time!($expr, \"{}\", $label)\n\t};\n\t($expr:expr, $fmt:expr, $($args:tt)*) => {{\n\t\tif tracing::enabled!(tracing::Level::DEBUG) {\n\t\t\tlet start = std::time::Instant::now();\n\t\t\tlet result = $expr;\n\t\t\ttracing::debug!(\"{} took {:?}\", format_args!($fmt, $($args)*), start.elapsed());\n\t\t\tresult\n\t\t} else {\n\t\t\t$expr\n\t\t}\n\t}};\n}\n\n#[macro_export]\nmacro_rules! err {\n\t($expr:expr) => {\n\t\t$crate::err!(stringify!($expr), $expr)\n\t};\n\t($label:expr, $expr:expr) => {\n\t\t$crate::err!($expr, \"{}\", $label)\n\t};\n\t($expr:expr, $fmt:expr, $($args:tt)*) => {{\n\t\tif let Err(e) = $expr {\n\t\t\ttracing::error!(\"{} failed: {e}\", format_args!($fmt, $($args)*));\n\t\t}\n\t}};\n}\n"
  },
  {
    "path": "yazi-macro/src/module.rs",
    "content": "#[macro_export]\nmacro_rules! mod_pub {\n    [ $( $name:ident $(,)? )+ ] => {\n        $(\n            pub mod $name;\n        )+\n    };\n}\n\n#[macro_export]\nmacro_rules! mod_flat {\n    [ $( $name:ident $(,)? )+ ] => {\n        $(\n            mod $name;\n            pub use $name::*;\n        )+\n    };\n}\n"
  },
  {
    "path": "yazi-macro/src/platform.rs",
    "content": "#[macro_export]\nmacro_rules! unix_either {\n\t($a:expr, $b:expr) => {{\n\t\t#[cfg(unix)]\n\t\t{\n\t\t\t$a\n\t\t}\n\t\t#[cfg(not(unix))]\n\t\t{\n\t\t\t$b\n\t\t}\n\t}};\n}\n\n#[macro_export]\nmacro_rules! win_either {\n\t($a:expr, $b:expr) => {{\n\t\t#[cfg(windows)]\n\t\t{\n\t\t\t$a\n\t\t}\n\t\t#[cfg(not(windows))]\n\t\t{\n\t\t\t$b\n\t\t}\n\t}};\n}\n"
  },
  {
    "path": "yazi-macro/src/render.rs",
    "content": "#[macro_export]\nmacro_rules! render {\n\t() => {\n\t\tyazi_shared::event::NEED_RENDER.store(1, std::sync::atomic::Ordering::Relaxed);\n\t};\n\t($cond:expr) => {\n\t\tif $cond {\n\t\t\trender!();\n\t\t}\n\t};\n\t($left:expr, > $right:expr) => {{\n\t\tlet val = $left;\n\t\tif val > $right {\n\t\t\trender!();\n\t\t}\n\t\tval\n\t}};\n}\n\n#[macro_export]\nmacro_rules! render_and {\n\t($cond:expr) => {\n\t\tif $cond {\n\t\t\tyazi_macro::render!();\n\t\t\ttrue\n\t\t} else {\n\t\t\tfalse\n\t\t}\n\t};\n}\n\n#[macro_export]\nmacro_rules! render_partial {\n\t() => {{\n\t\t_ = yazi_shared::event::NEED_RENDER.compare_exchange(\n\t\t\t0,\n\t\t\t2,\n\t\t\tstd::sync::atomic::Ordering::Relaxed,\n\t\t\tstd::sync::atomic::Ordering::Relaxed,\n\t\t);\n\t}};\n}\n"
  },
  {
    "path": "yazi-macro/src/stdio.rs",
    "content": "#[macro_export]\nmacro_rules! outln {\n\t($($tt:tt)*) => {{\n\t\tuse std::io::Write;\n\t\twriteln!(std::io::stdout(), $($tt)*)\n\t}}\n}\n\n#[macro_export]\nmacro_rules! errln {\n\t($($tt:tt)*) => {{\n\t\tuse std::io::Write;\n\t\twriteln!(std::io::stderr(), $($tt)*)\n\t}}\n}\n"
  },
  {
    "path": "yazi-packing/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-packing\"\ndescription            = \"Yazi packing\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[package.metadata.deb]\nname                      = \"yazi\"\nlicense-file              = [ \"../LICENSE\", \"0\" ]\ndepends                   = \"file, ffmpeg, 7zip, jq, poppler-utils, fd-find|fd, ripgrep, fzf, zoxide, imagemagick, xsel|xclip|wl-clipboard\"\nrecommends                = \"bash-completion\"\nextended-description-file = \"README.md\"\nsection                   = \"utility\"\npriority                  = \"optional\"\nassets                    = [\n\t[ \"target/release/ya\", \"usr/bin/\", \"755\" ],\n\t[ \"target/release/yazi\", \"usr/bin/\", \"755\" ],\n\t[ \"../README.md\",  \"usr/share/doc/yazi/README\", \"644\" ],\n\t[ \"../yazi-cli/completions/ya.bash\", \"usr/share/bash-completion/completions/ya\", \"644\" ],\n\t[ \"../yazi-boot/completions/yazi.bash\", \"usr/share/bash-completion/completions/yazi\", \"644\" ],\n]\n"
  },
  {
    "path": "yazi-packing/README.md",
    "content": "# yazi-packing\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-packing/src/lib.rs",
    "content": "\n"
  },
  {
    "path": "yazi-parser/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-parser\"\ndescription            = \"Yazi command parser\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[features]\ndefault      = [ \"vendored-lua\" ]\nvendored-lua = [ \"mlua/vendored\" ]\n\n[dependencies]\nyazi-binding = { path = \"../yazi-binding\", version = \"26.2.2\" }\nyazi-boot    = { path = \"../yazi-boot\", version = \"26.2.2\" }\nyazi-config  = { path = \"../yazi-config\", version = \"26.2.2\" }\nyazi-fs      = { path = \"../yazi-fs\", version = \"26.2.2\" }\nyazi-macro   = { path = \"../yazi-macro\", version = \"26.2.2\" }\nyazi-shared  = { path = \"../yazi-shared\", version = \"26.2.2\" }\nyazi-vfs     = { path = \"../yazi-vfs\", version = \"26.2.2\" }\nyazi-widgets = { path = \"../yazi-widgets\", version = \"26.2.2\" }\n\n# External dependencies\nanyhow        = { workspace = true }\nbitflags      = { workspace = true }\ncrossterm     = { workspace = true }\ndyn-clone     = { workspace = true }\nhashbrown     = { workspace = true }\nmlua          = { workspace = true }\nordered-float = { workspace = true }\nratatui       = { workspace = true }\nserde         = { workspace = true }\nserde_with    = { workspace = true }\ntokio         = { workspace = true }\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\ncrossterm = { workspace = true, features = [ \"use-dev-tty\", \"libc\" ] }\n"
  },
  {
    "path": "yazi-parser/README.md",
    "content": "# yazi-parser\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-parser/src/app/deprecate.rs",
    "content": "use anyhow::bail;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{SStr, event::ActionCow};\n\n#[derive(Debug)]\npub struct DeprecateOpt {\n\tpub content: SStr,\n}\n\nimpl TryFrom<ActionCow> for DeprecateOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tlet Ok(content) = a.take(\"content\") else {\n\t\t\tbail!(\"Invalid 'content' in DeprecateOpt\");\n\t\t};\n\n\t\tOk(Self { content })\n\t}\n}\n\nimpl FromLua for DeprecateOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for DeprecateOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/app/mod.rs",
    "content": "yazi_macro::mod_flat!(deprecate mouse plugin quit reflow resume stop title update_progress);\n"
  },
  {
    "path": "yazi-parser/src/app/mouse.rs",
    "content": "use crossterm::event::MouseEvent;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\n\n#[derive(Debug)]\npub struct MouseOpt {\n\tpub event: MouseEvent,\n}\n\nimpl From<MouseEvent> for MouseOpt {\n\tfn from(event: MouseEvent) -> Self { Self { event } }\n}\n\nimpl FromLua for MouseOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for MouseOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/app/plugin.rs",
    "content": "use std::{borrow::Cow, fmt::{self, Debug}, str::FromStr};\n\nuse anyhow::bail;\nuse dyn_clone::DynClone;\nuse hashbrown::HashMap;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value};\nuse serde::Deserialize;\nuse yazi_shared::{SStr, data::{Data, DataKey}, event::{Action, ActionCow}};\n\n#[derive(Clone, Debug, Default)]\npub struct PluginOpt {\n\tpub id:       SStr,\n\tpub args:     HashMap<DataKey, Data>,\n\tpub mode:     PluginMode,\n\tpub callback: Option<Box<dyn PluginCallback>>,\n}\n\nimpl TryFrom<ActionCow> for PluginOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tif let Some(opt) = a.take_any(\"opt\") {\n\t\t\treturn Ok(opt);\n\t\t}\n\n\t\tlet Some(id) = a.take_first::<SStr>().ok().filter(|s| !s.is_empty()) else {\n\t\t\tbail!(\"plugin id cannot be empty\");\n\t\t};\n\n\t\tlet args = if let Ok(s) = a.second() {\n\t\t\tlet (words, last) = yazi_shared::shell::unix::split(s, true)?;\n\t\t\tAction::parse_args(words, last)?\n\t\t} else {\n\t\t\tDefault::default()\n\t\t};\n\n\t\tlet mode = a.str(\"mode\").parse().unwrap_or_default();\n\t\tOk(Self { id: Self::normalize_id(id), args, mode, callback: a.take_any(\"callback\") })\n\t}\n}\n\nimpl FromLua for PluginOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for PluginOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl PluginOpt {\n\tpub fn new_callback(id: impl Into<SStr>, f: impl PluginCallback) -> Self {\n\t\tSelf {\n\t\t\tid: Self::normalize_id(id.into()),\n\t\t\tmode: PluginMode::Sync,\n\t\t\tcallback: Some(Box::new(f)),\n\t\t\t..Default::default()\n\t\t}\n\t}\n\n\tfn normalize_id(s: SStr) -> SStr {\n\t\tmatch s {\n\t\t\tCow::Borrowed(s) => s.trim_end_matches(\".main\").into(),\n\t\t\tCow::Owned(mut s) => {\n\t\t\t\ts.truncate(s.trim_end_matches(\".main\").len());\n\t\t\t\ts.into()\n\t\t\t}\n\t\t}\n\t}\n}\n\n// --- Mode\n#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]\n#[serde(rename_all = \"kebab-case\")]\npub enum PluginMode {\n\t#[default]\n\tAuto,\n\tSync,\n\tAsync,\n}\n\nimpl FromStr for PluginMode {\n\ttype Err = serde::de::value::Error;\n\n\tfn from_str(s: &str) -> Result<Self, Self::Err> {\n\t\tSelf::deserialize(serde::de::value::StrDeserializer::new(s))\n\t}\n}\n\nimpl PluginMode {\n\tpub fn auto_then(self, sync: bool) -> Self {\n\t\tif self != Self::Auto {\n\t\t\treturn self;\n\t\t}\n\t\tif sync { Self::Sync } else { Self::Async }\n\t}\n}\n\n// --- Callback\npub trait PluginCallback:\n\tFnOnce(&Lua, Table) -> mlua::Result<()> + Send + Sync + DynClone + 'static\n{\n}\n\nimpl<T> PluginCallback for T where\n\tT: FnOnce(&Lua, Table) -> mlua::Result<()> + Send + Sync + DynClone + 'static\n{\n}\n\nimpl Clone for Box<dyn PluginCallback> {\n\tfn clone(&self) -> Self { dyn_clone::clone_box(&**self) }\n}\n\nimpl fmt::Debug for dyn PluginCallback {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tf.debug_struct(\"PluginCallback\").finish_non_exhaustive()\n\t}\n}\n"
  },
  {
    "path": "yazi-parser/src/app/quit.rs",
    "content": "use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value};\nuse serde::{Deserialize, Serialize};\nuse yazi_binding::SER_OPT;\nuse yazi_shared::{event::ActionCow, strand::StrandBuf};\n\n#[derive(Clone, Debug, Default, Deserialize, Serialize)]\npub struct QuitOpt {\n\tpub code:        i32,\n\t#[serde(skip)]\n\tpub selected:    Option<StrandBuf>,\n\tpub no_cwd_file: bool,\n}\n\nimpl TryFrom<ActionCow> for QuitOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tif let Some(opt) = a.take_any2(\"opt\") {\n\t\t\treturn opt;\n\t\t}\n\n\t\tOk(Self {\n\t\t\tcode:        a.get(\"code\").unwrap_or_default(),\n\t\t\tselected:    None,\n\t\t\tno_cwd_file: a.bool(\"no-cwd-file\"),\n\t\t})\n\t}\n}\n\nimpl FromLua for QuitOpt {\n\tfn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> { lua.from_value(value) }\n}\n\nimpl IntoLua for QuitOpt {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> { lua.to_value_with(&self, SER_OPT) }\n}\n"
  },
  {
    "path": "yazi-parser/src/app/reflow.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value};\nuse ratatui::layout::Rect;\n\n#[derive(Debug)]\npub struct ReflowOpt {\n\tpub reflow: fn(Rect) -> mlua::Result<Table>,\n}\n\nimpl From<fn(Rect) -> mlua::Result<Table>> for ReflowOpt {\n\tfn from(f: fn(Rect) -> mlua::Result<Table>) -> Self { Self { reflow: f } }\n}\n\nimpl FromLua for ReflowOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for ReflowOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/app/resume.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value};\nuse ratatui::layout::Rect;\nuse tokio::sync::mpsc;\nuse yazi_shared::CompletionToken;\n\n#[derive(Debug)]\npub struct ResumeOpt {\n\tpub tx:     mpsc::UnboundedSender<(bool, CompletionToken)>,\n\tpub token:  CompletionToken,\n\tpub reflow: fn(Rect) -> mlua::Result<Table>,\n}\n\nimpl FromLua for ResumeOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for ResumeOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/app/stop.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse tokio::sync::mpsc;\nuse yazi_shared::CompletionToken;\n\n#[derive(Debug)]\npub struct StopOpt {\n\tpub tx:    mpsc::UnboundedSender<(bool, CompletionToken)>,\n\tpub token: CompletionToken,\n}\n\nimpl FromLua for StopOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for StopOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/app/title.rs",
    "content": "use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value};\nuse serde::{Deserialize, Serialize};\nuse yazi_binding::SER_OPT;\nuse yazi_shared::SStr;\n\n#[derive(Debug, Default, Deserialize, Serialize)]\npub struct TitleOpt {\n\tpub value: Option<SStr>,\n}\n\nimpl FromLua for TitleOpt {\n\tfn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> { lua.from_value(value) }\n}\n\nimpl IntoLua for TitleOpt {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> { lua.to_value_with(&self, SER_OPT) }\n}\n"
  },
  {
    "path": "yazi-parser/src/app/update_progress.rs",
    "content": "use anyhow::bail;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse ordered_float::OrderedFloat;\nuse serde::Serialize;\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug)]\npub struct UpdateProgressOpt {\n\tpub summary: TaskSummary,\n}\n\nimpl TryFrom<ActionCow> for UpdateProgressOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tlet Some(summary) = a.take_any(\"summary\") else {\n\t\t\tbail!(\"Invalid 'summary' in UpdateProgressOpt\");\n\t\t};\n\n\t\tOk(Self { summary })\n\t}\n}\n\nimpl FromLua for UpdateProgressOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for UpdateProgressOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n\n// --- Progress\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]\npub struct TaskSummary {\n\tpub total:   u32,\n\tpub success: u32,\n\tpub failed:  u32,\n\tpub percent: Option<OrderedFloat<f32>>,\n}\n"
  },
  {
    "path": "yazi-parser/src/arrow.rs",
    "content": "use anyhow::bail;\nuse mlua::{ExternalError, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\nuse yazi_widgets::Step;\n\n#[derive(Clone, Copy, Debug, Default)]\npub struct ArrowOpt {\n\tpub step: Step,\n}\n\nimpl TryFrom<ActionCow> for ArrowOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(a: ActionCow) -> Result<Self, Self::Error> {\n\t\tlet Ok(step) = a.first() else {\n\t\t\tbail!(\"Invalid 'step' in ArrowOpt\");\n\t\t};\n\n\t\tOk(Self { step })\n\t}\n}\n\nimpl From<isize> for ArrowOpt {\n\tfn from(n: isize) -> Self { Self { step: n.into() } }\n}\n\nimpl IntoLua for ArrowOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/cmp/close.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug, Default)]\npub struct CloseOpt {\n\tpub submit: bool,\n}\n\nimpl From<ActionCow> for CloseOpt {\n\tfn from(a: ActionCow) -> Self { Self { submit: a.bool(\"submit\") } }\n}\n\nimpl From<bool> for CloseOpt {\n\tfn from(submit: bool) -> Self { Self { submit } }\n}\n\nimpl FromLua for CloseOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for CloseOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/cmp/mod.rs",
    "content": "yazi_macro::mod_flat!(close show trigger);\n"
  },
  {
    "path": "yazi-parser/src/cmp/show.rs",
    "content": "use anyhow::bail;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{Id, event::ActionCow, path::PathBufDyn, strand::StrandBuf, url::UrlBuf};\n\n#[derive(Clone, Debug)]\npub struct ShowOpt {\n\tpub cache:      Vec<CmpItem>,\n\tpub cache_name: UrlBuf,\n\tpub word:       PathBufDyn,\n\tpub ticket:     Id,\n}\n\nimpl TryFrom<ActionCow> for ShowOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tif let Some(opt) = a.take_any2(\"opt\") {\n\t\t\topt\n\t\t} else {\n\t\t\tbail!(\"missing 'opt' argument\");\n\t\t}\n\t}\n}\n\nimpl FromLua for ShowOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for ShowOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n\n// --- Item\n#[derive(Debug, Clone)]\npub struct CmpItem {\n\tpub name:   StrandBuf,\n\tpub is_dir: bool,\n}\n"
  },
  {
    "path": "yazi-parser/src/cmp/trigger.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{Id, SStr, event::ActionCow};\n\n#[derive(Debug)]\npub struct TriggerOpt {\n\tpub word:   SStr,\n\tpub ticket: Option<Id>,\n}\n\nimpl From<ActionCow> for TriggerOpt {\n\tfn from(mut a: ActionCow) -> Self {\n\t\tSelf { word: a.take_first().unwrap_or_default(), ticket: a.get(\"ticket\").ok() }\n\t}\n}\n\nimpl FromLua for TriggerOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for TriggerOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/confirm/close.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug, Default)]\npub struct CloseOpt {\n\tpub submit: bool,\n}\n\nimpl From<ActionCow> for CloseOpt {\n\tfn from(a: ActionCow) -> Self { Self { submit: a.bool(\"submit\") } }\n}\n\nimpl From<bool> for CloseOpt {\n\tfn from(submit: bool) -> Self { Self { submit } }\n}\n\nimpl FromLua for CloseOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for CloseOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/confirm/mod.rs",
    "content": "yazi_macro::mod_flat!(close show);\n"
  },
  {
    "path": "yazi-parser/src/confirm/show.rs",
    "content": "use anyhow::bail;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_config::popup::ConfirmCfg;\nuse yazi_shared::{CompletionToken, event::ActionCow};\n\n#[derive(Debug)]\npub struct ShowOpt {\n\tpub cfg:   ConfirmCfg,\n\tpub token: CompletionToken,\n}\n\nimpl TryFrom<ActionCow> for ShowOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tlet Some(cfg) = a.take_any(\"cfg\") else {\n\t\t\tbail!(\"Invalid 'cfg' in ShowOpt\");\n\t\t};\n\n\t\tlet Some(token) = a.take_any(\"token\") else {\n\t\t\tbail!(\"Invalid 'token' in ShowOpt\");\n\t\t};\n\n\t\tOk(Self { cfg, token })\n\t}\n}\n\nimpl From<Box<Self>> for ShowOpt {\n\tfn from(value: Box<Self>) -> Self { *value }\n}\n\nimpl FromLua for ShowOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for ShowOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/help/mod.rs",
    "content": "yazi_macro::mod_flat!(toggle);\n"
  },
  {
    "path": "yazi-parser/src/help/toggle.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{Layer, event::ActionCow};\n\n#[derive(Debug)]\npub struct ToggleOpt {\n\tpub layer: Layer,\n}\n\nimpl TryFrom<ActionCow> for ToggleOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(a: ActionCow) -> Result<Self, Self::Error> { Ok(Self { layer: a.str(0).parse()? }) }\n}\n\nimpl From<Layer> for ToggleOpt {\n\tfn from(layer: Layer) -> Self { Self { layer } }\n}\n\nimpl FromLua for ToggleOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for ToggleOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/input/close.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug, Default)]\npub struct CloseOpt {\n\tpub submit: bool,\n}\n\nimpl From<ActionCow> for CloseOpt {\n\tfn from(a: ActionCow) -> Self { Self { submit: a.bool(\"submit\") } }\n}\n\nimpl From<bool> for CloseOpt {\n\tfn from(submit: bool) -> Self { Self { submit } }\n}\n\nimpl FromLua for CloseOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for CloseOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/input/mod.rs",
    "content": "yazi_macro::mod_flat!(close);\n"
  },
  {
    "path": "yazi-parser/src/lib.rs",
    "content": "yazi_macro::mod_pub!(app cmp confirm help input mgr notify pick spot tasks which);\n\nyazi_macro::mod_flat!(arrow void);\n"
  },
  {
    "path": "yazi-parser/src/mgr/cd.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse serde::{Deserialize, Serialize};\nuse yazi_fs::path::{clean_url, expand_url};\nuse yazi_shared::{event::ActionCow, url::{Url, UrlBuf}};\nuse yazi_vfs::provider;\n\n#[derive(Debug)]\npub struct CdOpt {\n\tpub target:      UrlBuf,\n\tpub interactive: bool,\n\tpub source:      CdSource,\n}\n\nimpl From<ActionCow> for CdOpt {\n\tfn from(mut a: ActionCow) -> Self {\n\t\tlet mut target = a.take_first().unwrap_or_default();\n\n\t\tif !a.bool(\"raw\") {\n\t\t\ttarget = expand_url(target);\n\t\t}\n\n\t\tif let Some(u) = provider::try_absolute(&target)\n\t\t\t&& u.is_owned()\n\t\t{\n\t\t\ttarget = u.into_static();\n\t\t}\n\n\t\tSelf {\n\t\t\ttarget:      clean_url(target),\n\t\t\tinteractive: a.bool(\"interactive\"),\n\t\t\tsource:      CdSource::Cd,\n\t\t}\n\t}\n}\n\nimpl From<(UrlBuf, CdSource)> for CdOpt {\n\tfn from((target, source): (UrlBuf, CdSource)) -> Self {\n\t\tSelf { target, interactive: false, source }\n\t}\n}\n\nimpl From<(Url<'_>, CdSource)> for CdOpt {\n\tfn from((target, source): (Url, CdSource)) -> Self { Self::from((target.to_owned(), source)) }\n}\n\nimpl FromLua for CdOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for CdOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n\n// --- Source\n#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]\n#[serde(rename_all = \"kebab-case\")]\npub enum CdSource {\n\tTab,\n\tCd,\n\tReveal,\n\tEnter,\n\tLeave,\n\tFollow,\n\tForward,\n\tBack,\n\tEscape,\n\tDisplace,\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/close.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\nuse crate::app::QuitOpt;\n\n#[derive(Debug, Default)]\npub struct CloseOpt(pub QuitOpt);\n\nimpl TryFrom<ActionCow> for CloseOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(a: ActionCow) -> Result<Self, Self::Error> { a.try_into().map(Self) }\n}\n\nimpl FromLua for CloseOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for CloseOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/copy.rs",
    "content": "use std::{borrow::Cow, str::FromStr};\n\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse serde::Deserialize;\nuse yazi_shared::{SStr, event::ActionCow, strand::AsStrand};\n\n#[derive(Debug)]\npub struct CopyOpt {\n\tpub r#type:    SStr,\n\tpub separator: CopySeparator,\n\tpub hovered:   bool,\n}\n\nimpl From<ActionCow> for CopyOpt {\n\tfn from(mut a: ActionCow) -> Self {\n\t\tSelf {\n\t\t\tr#type:    a.take_first().unwrap_or_default(),\n\t\t\tseparator: a.str(\"separator\").parse().unwrap_or_default(),\n\t\t\thovered:   a.bool(\"hovered\"),\n\t\t}\n\t}\n}\n\nimpl FromLua for CopyOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for CopyOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n\n// --- Separator\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Deserialize)]\n#[serde(rename_all = \"kebab-case\")]\npub enum CopySeparator {\n\t#[default]\n\tAuto,\n\tUnix,\n}\n\nimpl FromStr for CopySeparator {\n\ttype Err = serde::de::value::Error;\n\n\tfn from_str(s: &str) -> Result<Self, Self::Err> {\n\t\tSelf::deserialize(serde::de::value::StrDeserializer::new(s))\n\t}\n}\n\nimpl CopySeparator {\n\tpub fn transform<T>(self, s: &T) -> Cow<'_, [u8]>\n\twhere\n\t\tT: ?Sized + AsStrand,\n\t{\n\t\t#[cfg(windows)]\n\t\tif self == Self::Unix {\n\t\t\tuse yazi_shared::strand::StrandCow;\n\t\t\treturn match s.as_strand().backslash_to_slash() {\n\t\t\t\tStrandCow::Borrowed(s) => s.encoded_bytes().into(),\n\t\t\t\tStrandCow::Owned(s) => s.into_encoded_bytes().into(),\n\t\t\t};\n\t\t}\n\t\tCow::Borrowed(s.as_strand().encoded_bytes())\n\t}\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/create.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug)]\npub struct CreateOpt {\n\tpub dir:   bool,\n\tpub force: bool,\n}\n\nimpl From<ActionCow> for CreateOpt {\n\tfn from(a: ActionCow) -> Self { Self { dir: a.bool(\"dir\"), force: a.bool(\"force\") } }\n}\n\nimpl FromLua for CreateOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for CreateOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/displace_do.rs",
    "content": "use anyhow::bail;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{event::ActionCow, url::UrlBuf};\n\n#[derive(Clone, Debug)]\npub struct DisplaceDoOpt {\n\tpub to:   Result<UrlBuf, yazi_fs::error::Error>,\n\tpub from: UrlBuf,\n}\n\nimpl TryFrom<ActionCow> for DisplaceDoOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tif let Some(opt) = a.take_any2(\"opt\") {\n\t\t\topt\n\t\t} else {\n\t\t\tbail!(\"Invalid 'opt' in DisplaceDoOpt\");\n\t\t}\n\t}\n}\n\nimpl FromLua for DisplaceDoOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for DisplaceDoOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/download.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{event::ActionCow, url::UrlCow};\n\n#[derive(Debug, Default)]\npub struct DownloadOpt {\n\tpub urls: Vec<UrlCow<'static>>,\n\tpub open: bool,\n}\n\nimpl From<ActionCow> for DownloadOpt {\n\tfn from(mut a: ActionCow) -> Self { Self { urls: a.take_seq(), open: a.bool(\"open\") } }\n}\n\nimpl FromLua for DownloadOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for DownloadOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/escape.rs",
    "content": "use bitflags::bitflags;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\nbitflags! {\n\t#[derive(Debug)]\n\tpub struct EscapeOpt: u8 {\n\t\tconst FIND   = 0b00001;\n\t\tconst VISUAL = 0b00010;\n\t\tconst FILTER = 0b00100;\n\t\tconst SELECT = 0b01000;\n\t\tconst SEARCH = 0b10000;\n\t}\n}\n\nimpl From<ActionCow> for EscapeOpt {\n\tfn from(a: ActionCow) -> Self {\n\t\ta.args.iter().fold(Self::empty(), |acc, (k, v)| {\n\t\t\tmatch (k.as_str().unwrap_or(\"\"), v.try_into().unwrap_or(false)) {\n\t\t\t\t(\"all\", true) => Self::all(),\n\t\t\t\t(\"find\", true) => acc | Self::FIND,\n\t\t\t\t(\"visual\", true) => acc | Self::VISUAL,\n\t\t\t\t(\"filter\", true) => acc | Self::FILTER,\n\t\t\t\t(\"select\", true) => acc | Self::SELECT,\n\t\t\t\t(\"search\", true) => acc | Self::SEARCH,\n\t\t\t\t_ => acc,\n\t\t\t}\n\t\t})\n\t}\n}\n\nimpl FromLua for EscapeOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for EscapeOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/filter.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_fs::FilterCase;\nuse yazi_shared::{SStr, event::ActionCow};\n\n#[derive(Clone, Debug, Default)]\npub struct FilterOpt {\n\tpub query: SStr,\n\tpub case:  FilterCase,\n\tpub done:  bool,\n}\n\nimpl TryFrom<ActionCow> for FilterOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tif let Some(opt) = a.take_any2(\"opt\") {\n\t\t\treturn opt;\n\t\t}\n\n\t\tOk(Self {\n\t\t\tquery: a.take_first().unwrap_or_default(),\n\t\t\tcase:  FilterCase::from(&*a),\n\t\t\tdone:  a.bool(\"done\"),\n\t\t})\n\t}\n}\n\nimpl FromLua for FilterOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for FilterOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/find.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_fs::FilterCase;\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug)]\npub struct FindOpt {\n\tpub prev: bool,\n\tpub case: FilterCase,\n}\n\nimpl TryFrom<ActionCow> for FindOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(a: ActionCow) -> Result<Self, Self::Error> {\n\t\tOk(Self { prev: a.bool(\"previous\"), case: FilterCase::from(&*a) })\n\t}\n}\n\nimpl FromLua for FindOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for FindOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/find_arrow.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug)]\npub struct FindArrowOpt {\n\tpub prev: bool,\n}\n\nimpl From<ActionCow> for FindArrowOpt {\n\tfn from(a: ActionCow) -> Self { Self { prev: a.bool(\"previous\") } }\n}\n\nimpl FromLua for FindArrowOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for FindArrowOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/find_do.rs",
    "content": "use anyhow::bail;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_fs::FilterCase;\nuse yazi_shared::{SStr, event::ActionCow};\n\n#[derive(Clone, Debug)]\npub struct FindDoOpt {\n\tpub query: SStr,\n\tpub prev:  bool,\n\tpub case:  FilterCase,\n}\n\nimpl TryFrom<ActionCow> for FindDoOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tif let Some(opt) = a.take_any2(\"opt\") {\n\t\t\treturn opt;\n\t\t}\n\n\t\tlet Ok(query) = a.take_first() else {\n\t\t\tbail!(\"Invalid 'query' in FindDoOpt\");\n\t\t};\n\n\t\tOk(Self { query, prev: a.bool(\"previous\"), case: FilterCase::from(&*a) })\n\t}\n}\n\nimpl FromLua for FindDoOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for FindDoOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/hardlink.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug)]\npub struct HardlinkOpt {\n\tpub force:  bool,\n\tpub follow: bool,\n}\n\nimpl From<ActionCow> for HardlinkOpt {\n\tfn from(a: ActionCow) -> Self { Self { force: a.bool(\"force\"), follow: a.bool(\"follow\") } }\n}\n\nimpl FromLua for HardlinkOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for HardlinkOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/hidden.rs",
    "content": "use std::str::FromStr;\n\nuse mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value};\nuse serde::{Deserialize, Serialize};\nuse yazi_binding::SER_OPT;\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug, Default, Deserialize, Serialize)]\npub struct HiddenOpt {\n\tpub state: HiddenOptState,\n}\n\nimpl TryFrom<ActionCow> for HiddenOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(a: ActionCow) -> Result<Self, Self::Error> {\n\t\tOk(Self { state: a.str(0).parse().unwrap_or_default() })\n\t}\n}\n\nimpl FromLua for HiddenOpt {\n\tfn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> { lua.from_value(value) }\n}\n\nimpl IntoLua for HiddenOpt {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> { lua.to_value_with(&self, SER_OPT) }\n}\n\n// --- State\n#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]\n#[serde(rename_all = \"kebab-case\")]\npub enum HiddenOptState {\n\t#[default]\n\tNone,\n\tShow,\n\tHide,\n\tToggle,\n}\n\nimpl FromStr for HiddenOptState {\n\ttype Err = serde::de::value::Error;\n\n\tfn from_str(s: &str) -> Result<Self, Self::Err> {\n\t\tSelf::deserialize(serde::de::value::StrDeserializer::new(s))\n\t}\n}\n\nimpl HiddenOptState {\n\tpub fn bool(self, old: bool) -> bool {\n\t\tmatch self {\n\t\t\tSelf::None => old,\n\t\t\tSelf::Show => true,\n\t\t\tSelf::Hide => false,\n\t\t\tSelf::Toggle => !old,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/hover.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::path::PathBufDyn;\n\n#[derive(Debug, Default)]\npub struct HoverOpt {\n\tpub urn: Option<PathBufDyn>,\n}\n\nimpl From<Option<PathBufDyn>> for HoverOpt {\n\tfn from(urn: Option<PathBufDyn>) -> Self { Self { urn } }\n}\n\nimpl FromLua for HoverOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for HoverOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/linemode.rs",
    "content": "use anyhow::bail;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{SStr, event::ActionCow};\n\n#[derive(Debug)]\npub struct LinemodeOpt {\n\tpub new: SStr,\n}\n\nimpl TryFrom<ActionCow> for LinemodeOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tlet Ok(new) = a.take_first::<SStr>() else {\n\t\t\tbail!(\"a string argument is required for LinemodeOpt\");\n\t\t};\n\n\t\tif new.is_empty() || new.len() > 20 {\n\t\t\tbail!(\"Linemode must be between 1 and 20 characters long\");\n\t\t}\n\n\t\tOk(Self { new })\n\t}\n}\n\nimpl FromLua for LinemodeOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for LinemodeOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/link.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug)]\npub struct LinkOpt {\n\tpub relative: bool,\n\tpub force:    bool,\n}\n\nimpl From<ActionCow> for LinkOpt {\n\tfn from(a: ActionCow) -> Self { Self { relative: a.bool(\"relative\"), force: a.bool(\"force\") } }\n}\n\nimpl FromLua for LinkOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for LinkOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/mod.rs",
    "content": "yazi_macro::mod_flat!(\n\tcd\n\tclose\n\tcopy\n\tcreate\n\tdisplace_do\n\tdownload\n\tescape\n\tfilter\n\tfind\n\tfind_arrow\n\tfind_do\n\thardlink\n\thidden\n\thover\n\tlinemode\n\tlink\n\topen\n\topen_do\n\tpaste\n\tpeek\n\tremove\n\trename\n\treveal\n\tsearch\n\tseek\n\tshell\n\tsort\n\tspot\n\tstash\n\ttab_close\n\ttab_create\n\ttab_rename\n\ttab_switch\n\ttoggle\n\ttoggle_all\n\tupdate_files\n\tupdate_mimes\n\tupdate_paged\n\tupdate_peeked\n\tupdate_spotted\n\tupdate_yanked\n\tupload\n\tvisual_mode\n\tyank\n);\n"
  },
  {
    "path": "yazi-parser/src/mgr/open.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{event::ActionCow, url::UrlCow};\n\n#[derive(Clone, Debug)]\npub struct OpenOpt {\n\tpub cwd:         Option<UrlCow<'static>>,\n\tpub targets:     Vec<UrlCow<'static>>,\n\tpub interactive: bool,\n\tpub hovered:     bool,\n}\n\nimpl TryFrom<ActionCow> for OpenOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tif let Some(opt) = a.take_any2(\"opt\") {\n\t\t\treturn opt;\n\t\t}\n\n\t\tOk(Self {\n\t\t\tcwd:         a.take(\"cwd\").ok(),\n\t\t\ttargets:     a.take_seq(),\n\t\t\tinteractive: a.bool(\"interactive\"),\n\t\t\thovered:     a.bool(\"hovered\"),\n\t\t})\n\t}\n}\n\nimpl FromLua for OpenOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for OpenOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/open_do.rs",
    "content": "use anyhow::bail;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{event::ActionCow, url::UrlCow};\n\n#[derive(Clone, Debug, Default)]\npub struct OpenDoOpt {\n\tpub cwd:         UrlCow<'static>,\n\tpub targets:     Vec<UrlCow<'static>>,\n\tpub interactive: bool,\n}\n\nimpl TryFrom<ActionCow> for OpenDoOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tif let Some(opt) = a.take_any2(\"opt\") {\n\t\t\topt\n\t\t} else {\n\t\t\tbail!(\"Invalid 'opt' in OpenDoOpt\");\n\t\t}\n\t}\n}\n\nimpl FromLua for OpenDoOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for OpenDoOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/paste.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug)]\npub struct PasteOpt {\n\tpub force:  bool,\n\tpub follow: bool,\n}\n\nimpl From<ActionCow> for PasteOpt {\n\tfn from(a: ActionCow) -> Self { Self { force: a.bool(\"force\"), follow: a.bool(\"follow\") } }\n}\n\nimpl FromLua for PasteOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for PasteOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/peek.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{event::ActionCow, url::UrlCow};\n\n#[derive(Debug, Default)]\npub struct PeekOpt {\n\tpub skip:        Option<usize>,\n\tpub force:       bool,\n\tpub only_if:     Option<UrlCow<'static>>,\n\tpub upper_bound: bool,\n}\n\nimpl From<ActionCow> for PeekOpt {\n\tfn from(mut a: ActionCow) -> Self {\n\t\tSelf {\n\t\t\tskip:        a.first().ok(),\n\t\t\tforce:       a.bool(\"force\"),\n\t\t\tonly_if:     a.take(\"only-if\").ok(),\n\t\t\tupper_bound: a.bool(\"upper-bound\"),\n\t\t}\n\t}\n}\n\nimpl From<bool> for PeekOpt {\n\tfn from(force: bool) -> Self { Self { force, ..Default::default() } }\n}\n\nimpl FromLua for PeekOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for PeekOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/remove.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{event::ActionCow, url::UrlBuf};\n\n#[derive(Debug)]\npub struct RemoveOpt {\n\tpub force:       bool,\n\tpub permanently: bool,\n\tpub hovered:     bool,\n\tpub targets:     Vec<UrlBuf>,\n}\n\nimpl From<ActionCow> for RemoveOpt {\n\tfn from(mut a: ActionCow) -> Self {\n\t\tSelf {\n\t\t\tforce:       a.bool(\"force\"),\n\t\t\tpermanently: a.bool(\"permanently\"),\n\t\t\thovered:     a.bool(\"hovered\"),\n\t\t\ttargets:     a.take_any(\"targets\").unwrap_or_default(),\n\t\t}\n\t}\n}\n\nimpl FromLua for RemoveOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for RemoveOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/rename.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{SStr, event::ActionCow};\n\n#[derive(Debug)]\npub struct RenameOpt {\n\tpub hovered: bool,\n\tpub force:   bool,\n\tpub empty:   SStr,\n\tpub cursor:  SStr,\n}\n\nimpl From<ActionCow> for RenameOpt {\n\tfn from(mut a: ActionCow) -> Self {\n\t\tSelf {\n\t\t\thovered: a.bool(\"hovered\"),\n\t\t\tforce:   a.bool(\"force\"),\n\t\t\tempty:   a.take(\"empty\").unwrap_or_default(),\n\t\t\tcursor:  a.take(\"cursor\").unwrap_or_default(),\n\t\t}\n\t}\n}\n\nimpl FromLua for RenameOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for RenameOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/reveal.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_fs::path::{clean_url, expand_url};\nuse yazi_shared::{event::ActionCow, url::UrlBuf};\nuse yazi_vfs::provider;\n\nuse crate::mgr::CdSource;\n\n#[derive(Debug)]\npub struct RevealOpt {\n\tpub target:   UrlBuf,\n\tpub source:   CdSource,\n\tpub no_dummy: bool,\n}\n\nimpl From<ActionCow> for RevealOpt {\n\tfn from(mut a: ActionCow) -> Self {\n\t\tlet mut target = a.take_first().unwrap_or_default();\n\n\t\tif !a.bool(\"raw\") {\n\t\t\ttarget = expand_url(target);\n\t\t}\n\n\t\tif let Some(u) = provider::try_absolute(&target)\n\t\t\t&& u.is_owned()\n\t\t{\n\t\t\ttarget = u.into_static();\n\t\t}\n\n\t\tSelf { target: clean_url(target), source: CdSource::Reveal, no_dummy: a.bool(\"no-dummy\") }\n\t}\n}\n\nimpl From<UrlBuf> for RevealOpt {\n\tfn from(target: UrlBuf) -> Self { Self { target, source: CdSource::Reveal, no_dummy: false } }\n}\n\nimpl From<(UrlBuf, CdSource)> for RevealOpt {\n\tfn from((target, source): (UrlBuf, CdSource)) -> Self { Self { target, source, no_dummy: false } }\n}\n\nimpl FromLua for RevealOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for RevealOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/search.rs",
    "content": "use std::str::FromStr;\n\nuse anyhow::bail;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse serde::Deserialize;\nuse yazi_shared::{SStr, event::ActionCow, url::{UrlCow, UrlLike}};\n\n#[derive(Clone, Debug)]\npub struct SearchOpt {\n\tpub via:      SearchOptVia,\n\tpub subject:  SStr,\n\tpub args:     Vec<String>,\n\tpub args_raw: SStr,\n\tpub r#in:     Option<UrlCow<'static>>,\n}\n\nimpl TryFrom<ActionCow> for SearchOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tif let Some(opt) = a.take_any2(\"opt\") {\n\t\t\treturn opt;\n\t\t}\n\n\t\tlet r#in = a.take::<UrlCow>(\"in\").ok();\n\t\tif let Some(u) = &r#in\n\t\t\t&& (!u.is_absolute() || u.is_search())\n\t\t{\n\t\t\tbail!(\"invalid 'in' in SearchOpt\");\n\t\t}\n\n\t\tlet Ok(args) = yazi_shared::shell::unix::split(a.str(\"args\"), false) else {\n\t\t\tbail!(\"invalid 'args' in SearchOpt\");\n\t\t};\n\n\t\tOk(Self {\n\t\t\tvia: a.str(\"via\").parse()?,\n\t\t\tsubject: a.take_first().unwrap_or_default(),\n\t\t\targs: args.0,\n\t\t\targs_raw: a.take(\"args\").unwrap_or_default(),\n\t\t\tr#in,\n\t\t})\n\t}\n}\n\nimpl FromLua for SearchOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for SearchOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n\n// Via\n#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]\n#[serde(rename_all = \"kebab-case\")]\npub enum SearchOptVia {\n\tRg,\n\tRga,\n\tFd,\n}\n\nimpl FromStr for SearchOptVia {\n\ttype Err = serde::de::value::Error;\n\n\tfn from_str(s: &str) -> Result<Self, Self::Err> {\n\t\tSelf::deserialize(serde::de::value::StrDeserializer::new(s))\n\t}\n}\n\nimpl SearchOptVia {\n\tpub fn into_str(self) -> &'static str {\n\t\tmatch self {\n\t\t\tSelf::Rg => \"rg\",\n\t\t\tSelf::Rga => \"rga\",\n\t\t\tSelf::Fd => \"fd\",\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/seek.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug)]\npub struct SeekOpt {\n\tpub units: i16,\n}\n\nimpl From<ActionCow> for SeekOpt {\n\tfn from(a: ActionCow) -> Self { Self { units: a.first().unwrap_or(0) } }\n}\n\nimpl FromLua for SeekOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for SeekOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/shell.rs",
    "content": "use anyhow::bail;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{SStr, event::ActionCow, url::UrlCow};\n\n#[derive(Debug)]\npub struct ShellOpt {\n\tpub run: SStr,\n\tpub cwd: Option<UrlCow<'static>>,\n\n\tpub block:       bool,\n\tpub orphan:      bool,\n\tpub interactive: bool,\n\n\tpub cursor: Option<usize>,\n}\n\nimpl TryFrom<ActionCow> for ShellOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tlet me = Self {\n\t\t\trun: a.take_first().unwrap_or_default(),\n\t\t\tcwd: a.take(\"cwd\").ok(),\n\n\t\t\tblock:       a.bool(\"block\"),\n\t\t\torphan:      a.bool(\"orphan\"),\n\t\t\tinteractive: a.bool(\"interactive\"),\n\n\t\t\tcursor: a.get(\"cursor\").ok(),\n\t\t};\n\n\t\tif me.cursor.is_some_and(|c| c > me.run.chars().count()) {\n\t\t\tbail!(\"The cursor position is out of bounds.\");\n\t\t}\n\n\t\tOk(me)\n\t}\n}\n\nimpl FromLua for ShellOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for ShellOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/sort.rs",
    "content": "use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value};\nuse serde::{Deserialize, Serialize};\nuse yazi_binding::SER_OPT;\nuse yazi_fs::{SortBy, SortFallback};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug, Default, Deserialize, Serialize)]\npub struct SortOpt {\n\tpub by:        Option<SortBy>,\n\tpub reverse:   Option<bool>,\n\tpub dir_first: Option<bool>,\n\tpub sensitive: Option<bool>,\n\tpub translit:  Option<bool>,\n\tpub fallback:  Option<SortFallback>,\n}\n\nimpl TryFrom<ActionCow> for SortOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(a: ActionCow) -> Result<Self, Self::Error> {\n\t\tOk(Self {\n\t\t\tby:        a.first().ok().map(str::parse).transpose()?,\n\t\t\treverse:   a.get(\"reverse\").ok(),\n\t\t\tdir_first: a.get(\"dir-first\").ok(),\n\t\t\tsensitive: a.get(\"sensitive\").ok(),\n\t\t\ttranslit:  a.get(\"translit\").ok(),\n\t\t\tfallback:  a.get(\"fallback\").ok().map(str::parse).transpose()?,\n\t\t})\n\t}\n}\n\nimpl FromLua for SortOpt {\n\tfn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> { lua.from_value(value) }\n}\n\nimpl IntoLua for SortOpt {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> { lua.to_value_with(&self, SER_OPT) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/spot.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug, Default)]\npub struct SpotOpt {\n\tpub skip: Option<usize>,\n}\n\nimpl From<ActionCow> for SpotOpt {\n\tfn from(a: ActionCow) -> Self { Self { skip: a.get(\"skip\").ok() } }\n}\n\nimpl From<usize> for SpotOpt {\n\tfn from(skip: usize) -> Self { Self { skip: Some(skip) } }\n}\n\nimpl FromLua for SpotOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for SpotOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/stash.rs",
    "content": "use anyhow::bail;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, LuaSerdeExt, Value};\nuse serde::{Deserialize, Serialize};\nuse yazi_binding::{SER_OPT, Url};\nuse yazi_shared::{event::ActionCow, url::UrlBuf};\n\nuse crate::mgr::{CdOpt, CdSource};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct StashOpt {\n\tpub target: UrlBuf,\n\tpub source: CdSource,\n}\n\nimpl TryFrom<ActionCow> for StashOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(_: ActionCow) -> Result<Self, Self::Error> { bail!(\"unsupported\") }\n}\n\nimpl From<CdOpt> for StashOpt {\n\tfn from(opt: CdOpt) -> Self { Self { target: opt.target, source: opt.source } }\n}\n\nimpl FromLua for StashOpt {\n\tfn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {\n\t\tlet tbl = value.as_table().ok_or_else(|| \"expected table\".into_lua_err())?;\n\t\tOk(Self {\n\t\t\ttarget: tbl.get::<Url>(\"target\")?.into(),\n\t\t\tsource: lua.from_value(tbl.get(\"source\")?)?,\n\t\t})\n\t}\n}\n\nimpl IntoLua for StashOpt {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tlua\n\t\t\t.create_table_from([\n\t\t\t\t(\"target\", Url::new(self.target).into_lua(lua)?),\n\t\t\t\t(\"source\", lua.to_value_with(&self.source, SER_OPT)?),\n\t\t\t])?\n\t\t\t.into_lua(lua)\n\t}\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/tab_close.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug)]\npub struct TabCloseOpt {\n\tpub idx: usize,\n}\n\nimpl From<ActionCow> for TabCloseOpt {\n\tfn from(a: ActionCow) -> Self { Self { idx: a.first().unwrap_or(0) } }\n}\n\nimpl From<usize> for TabCloseOpt {\n\tfn from(idx: usize) -> Self { Self { idx } }\n}\n\nimpl FromLua for TabCloseOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for TabCloseOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/tab_create.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_boot::BOOT;\nuse yazi_fs::path::{clean_url, expand_url};\nuse yazi_shared::{event::ActionCow, url::UrlCow};\nuse yazi_vfs::provider;\n\n#[derive(Debug)]\npub struct TabCreateOpt {\n\tpub url: Option<UrlCow<'static>>,\n}\n\nimpl From<ActionCow> for TabCreateOpt {\n\tfn from(mut a: ActionCow) -> Self {\n\t\tif a.bool(\"current\") {\n\t\t\treturn Self { url: None };\n\t\t}\n\n\t\tlet Ok(mut url) = a.take_first() else {\n\t\t\treturn Self { url: Some(UrlCow::from(&BOOT.cwds[0])) };\n\t\t};\n\n\t\tif !a.bool(\"raw\") {\n\t\t\turl = expand_url(url);\n\t\t}\n\n\t\tif let Some(u) = provider::try_absolute(&url)\n\t\t\t&& u.is_owned()\n\t\t{\n\t\t\turl = u.into_static();\n\t\t}\n\n\t\tSelf { url: Some(clean_url(url).into()) }\n\t}\n}\n\nimpl FromLua for TabCreateOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for TabCreateOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/tab_rename.rs",
    "content": "use anyhow::bail;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{SStr, event::ActionCow};\n\n#[derive(Debug)]\npub struct TabRenameOpt {\n\tpub name:        Option<SStr>,\n\tpub interactive: bool,\n}\n\nimpl TryFrom<ActionCow> for TabRenameOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tlet name = a.take_first().ok();\n\t\tlet interactive = a.bool(\"interactive\");\n\n\t\tif name.is_none() && !interactive {\n\t\t\tbail!(\"either name or interactive must be specified in TabRenameOpt\");\n\t\t}\n\n\t\tOk(Self { name, interactive })\n\t}\n}\n\nimpl FromLua for TabRenameOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for TabRenameOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/tab_switch.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug)]\npub struct TabSwitchOpt {\n\tpub step:     isize,\n\tpub relative: bool,\n}\n\nimpl From<ActionCow> for TabSwitchOpt {\n\tfn from(a: ActionCow) -> Self {\n\t\tSelf { step: a.first().unwrap_or(0), relative: a.bool(\"relative\") }\n\t}\n}\n\nimpl FromLua for TabSwitchOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for TabSwitchOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/toggle.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{event::ActionCow, url::UrlCow};\n\n#[derive(Debug)]\npub struct ToggleOpt {\n\tpub url:   Option<UrlCow<'static>>,\n\tpub state: Option<bool>,\n}\n\nimpl From<ActionCow> for ToggleOpt {\n\tfn from(mut a: ActionCow) -> Self {\n\t\tSelf {\n\t\t\turl:   a.take_first().ok(),\n\t\t\tstate: match a.get(\"state\") {\n\t\t\t\tOk(\"on\") => Some(true),\n\t\t\t\tOk(\"off\") => Some(false),\n\t\t\t\t_ => None,\n\t\t\t},\n\t\t}\n\t}\n}\n\nimpl FromLua for ToggleOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for ToggleOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/toggle_all.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{event::ActionCow, url::UrlCow};\n\n#[derive(Debug)]\npub struct ToggleAllOpt {\n\tpub urls:  Vec<UrlCow<'static>>,\n\tpub state: Option<bool>,\n}\n\nimpl From<ActionCow> for ToggleAllOpt {\n\tfn from(mut a: ActionCow) -> Self {\n\t\tSelf {\n\t\t\turls:  a.take_seq(),\n\t\t\tstate: match a.get(\"state\") {\n\t\t\t\tOk(\"on\") => Some(true),\n\t\t\t\tOk(\"off\") => Some(false),\n\t\t\t\t_ => None,\n\t\t\t},\n\t\t}\n\t}\n}\n\nimpl From<Option<bool>> for ToggleAllOpt {\n\tfn from(state: Option<bool>) -> Self { Self { urls: vec![], state } }\n}\n\nimpl FromLua for ToggleAllOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for ToggleAllOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/update_files.rs",
    "content": "use anyhow::bail;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_fs::FilesOp;\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug)]\npub struct UpdateFilesOpt {\n\tpub op: FilesOp,\n}\n\nimpl TryFrom<ActionCow> for UpdateFilesOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tlet Some(op) = a.take_any(\"op\") else {\n\t\t\tbail!(\"Invalid 'op' in UpdateFilesOpt\");\n\t\t};\n\n\t\tOk(Self { op })\n\t}\n}\n\nimpl From<FilesOp> for UpdateFilesOpt {\n\tfn from(op: FilesOp) -> Self { Self { op } }\n}\n\nimpl FromLua for UpdateFilesOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for UpdateFilesOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/update_mimes.rs",
    "content": "use anyhow::bail;\nuse hashbrown::HashMap;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{data::{Data, DataKey}, event::ActionCow};\n\n#[derive(Debug)]\npub struct UpdateMimesOpt {\n\tpub updates: HashMap<DataKey, Data>,\n}\n\nimpl TryFrom<ActionCow> for UpdateMimesOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tlet Ok(updates) = a.take(\"updates\") else {\n\t\t\tbail!(\"Invalid 'updates' in UpdateMimesOpt\");\n\t\t};\n\n\t\tOk(Self { updates })\n\t}\n}\n\nimpl FromLua for UpdateMimesOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for UpdateMimesOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/update_paged.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{event::ActionCow, url::UrlCow};\n\n#[derive(Debug, Default)]\npub struct UpdatePagedOpt {\n\tpub page:    Option<usize>,\n\tpub only_if: Option<UrlCow<'static>>,\n}\n\nimpl From<ActionCow> for UpdatePagedOpt {\n\tfn from(mut a: ActionCow) -> Self {\n\t\tSelf { page: a.first().ok(), only_if: a.take(\"only-if\").ok() }\n\t}\n}\n\nimpl From<()> for UpdatePagedOpt {\n\tfn from(_: ()) -> Self { Self::default() }\n}\n\nimpl FromLua for UpdatePagedOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for UpdatePagedOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/update_peeked.rs",
    "content": "use anyhow::bail;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value};\nuse yazi_binding::{FileRef, elements::{Rect, Renderable}};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Clone, Debug)]\npub struct UpdatePeekedOpt {\n\tpub lock: PreviewLock,\n}\n\nimpl TryFrom<ActionCow> for UpdatePeekedOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tif let Some(opt) = a.take_any2(\"opt\") {\n\t\t\treturn opt;\n\t\t}\n\n\t\tlet Some(lock) = a.take_any(\"lock\") else {\n\t\t\tbail!(\"Invalid 'lock' in UpdatePeekedOpt\");\n\t\t};\n\n\t\tOk(Self { lock })\n\t}\n}\n\nimpl FromLua for UpdatePeekedOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for UpdatePeekedOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n\n// --- Lock\n#[derive(Clone, Debug, Default)]\npub struct PreviewLock {\n\tpub url:  yazi_shared::url::UrlBuf,\n\tpub cha:  yazi_fs::cha::Cha,\n\tpub mime: String,\n\n\tpub skip: usize,\n\tpub area: Rect,\n\tpub data: Vec<Renderable>,\n}\n\nimpl TryFrom<Table> for PreviewLock {\n\ttype Error = mlua::Error;\n\n\tfn try_from(t: Table) -> Result<Self, Self::Error> {\n\t\tlet file: FileRef = t.raw_get(\"file\")?;\n\t\tOk(Self {\n\t\t\turl:  file.url_owned(),\n\t\t\tcha:  file.cha,\n\t\t\tmime: t.raw_get(\"mime\")?,\n\n\t\t\tskip: t.raw_get(\"skip\")?,\n\t\t\tarea: t.raw_get(\"area\")?,\n\t\t\tdata: Default::default(),\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/update_spotted.rs",
    "content": "use anyhow::bail;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value};\nuse yazi_binding::{FileRef, elements::Renderable};\nuse yazi_shared::{Id, event::ActionCow};\n\n#[derive(Clone, Debug)]\npub struct UpdateSpottedOpt {\n\tpub lock: SpotLock,\n}\n\nimpl TryFrom<ActionCow> for UpdateSpottedOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tif let Some(opt) = a.take_any2(\"opt\") {\n\t\t\treturn opt;\n\t\t}\n\n\t\tlet Some(lock) = a.take_any(\"lock\") else {\n\t\t\tbail!(\"Invalid 'lock' in UpdateSpottedOpt\");\n\t\t};\n\n\t\tOk(Self { lock })\n\t}\n}\n\nimpl FromLua for UpdateSpottedOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for UpdateSpottedOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n\n// --- Lock\n#[derive(Clone, Debug)]\npub struct SpotLock {\n\tpub url:  yazi_shared::url::UrlBuf,\n\tpub cha:  yazi_fs::cha::Cha,\n\tpub mime: String,\n\n\tpub id:   Id,\n\tpub skip: usize,\n\tpub data: Vec<Renderable>,\n}\n\nimpl TryFrom<Table> for SpotLock {\n\ttype Error = mlua::Error;\n\n\tfn try_from(t: Table) -> Result<Self, Self::Error> {\n\t\tlet file: FileRef = t.raw_get(\"file\")?;\n\t\tOk(Self {\n\t\t\turl:  file.url_owned(),\n\t\t\tcha:  file.cha,\n\t\t\tmime: t.raw_get(\"mime\")?,\n\n\t\t\tid:   *t.raw_get::<yazi_binding::Id>(\"id\")?,\n\t\t\tskip: t.raw_get(\"skip\")?,\n\t\t\tdata: Default::default(),\n\t\t})\n\t}\n}\n\nimpl SpotLock {\n\tpub fn len(&self) -> Option<usize> { Some(self.table()?.len()) }\n\n\tpub fn select(&mut self, idx: Option<usize>) {\n\t\tif let Some(t) = self.table_mut() {\n\t\t\tt.select(idx);\n\t\t}\n\t}\n\n\tpub fn selected(&self) -> Option<usize> { self.table()?.selected() }\n\n\tpub fn table(&self) -> Option<&yazi_binding::elements::Table> {\n\t\tself.data.iter().rev().find_map(|r| match r {\n\t\t\tRenderable::Table(t) => Some(t.as_ref()),\n\t\t\t_ => None,\n\t\t})\n\t}\n\n\tpub fn table_mut(&mut self) -> Option<&mut yazi_binding::elements::Table> {\n\t\tself.data.iter_mut().rev().find_map(|r| match r {\n\t\t\tRenderable::Table(t) => Some(t.as_mut()),\n\t\t\t_ => None,\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/update_yanked.rs",
    "content": "use std::borrow::Cow;\n\nuse anyhow::bail;\nuse hashbrown::HashSet;\nuse mlua::{AnyUserData, ExternalError, FromLua, IntoLua, Lua, MetaMethod, MultiValue, ObjectLike, UserData, UserDataFields, UserDataMethods, Value};\nuse serde::{Deserialize, Serialize};\nuse yazi_binding::get_metatable;\nuse yazi_shared::{event::ActionCow, url::UrlBufCov};\n\ntype Iter = yazi_binding::Iter<\n\tstd::iter::Map<hashbrown::hash_set::IntoIter<UrlBufCov>, fn(UrlBufCov) -> yazi_binding::Url>,\n\tyazi_binding::Url,\n>;\n\n#[derive(Clone, Debug, Default, Deserialize, Serialize)]\npub struct UpdateYankedOpt<'a> {\n\tpub cut:  bool,\n\tpub urls: Cow<'a, HashSet<UrlBufCov>>,\n}\n\nimpl TryFrom<ActionCow> for UpdateYankedOpt<'_> {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tif let Some(opt) = a.take_any2(\"opt\") {\n\t\t\topt\n\t\t} else {\n\t\t\tbail!(\"Invalid 'opt' in UpdateYankedOpt\");\n\t\t}\n\t}\n}\n\nimpl FromLua for UpdateYankedOpt<'_> {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for UpdateYankedOpt<'_> {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tlet len = self.urls.len();\n\t\tlet iter = Iter::new(self.urls.into_owned().into_iter().map(yazi_binding::Url::new), Some(len));\n\t\tUpdateYankedIter { cut: self.cut, len, inner: lua.create_userdata(iter)? }.into_lua(lua)\n\t}\n}\n\n// --- Iter\npub struct UpdateYankedIter {\n\tcut:   bool,\n\tlen:   usize,\n\tinner: AnyUserData,\n}\n\nimpl UpdateYankedIter {\n\tpub fn into_opt(self, lua: &Lua) -> mlua::Result<UpdateYankedOpt<'static>> {\n\t\tOk(UpdateYankedOpt {\n\t\t\tcut:  self.cut,\n\t\t\turls: Cow::Owned(\n\t\t\t\tself\n\t\t\t\t\t.inner\n\t\t\t\t\t.take::<Iter>()?\n\t\t\t\t\t.into_iter(lua)\n\t\t\t\t\t.map(|result| result.map(Into::into))\n\t\t\t\t\t.collect::<mlua::Result<_>>()?,\n\t\t\t),\n\t\t})\n\t}\n}\n\nimpl UserData for UpdateYankedIter {\n\tfn add_fields<F: UserDataFields<Self>>(fields: &mut F) {\n\t\tfields.add_field_method_get(\"cut\", |_, me| Ok(me.cut));\n\t}\n\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.len));\n\n\t\tmethods.add_meta_method(MetaMethod::Pairs, |lua, me, ()| {\n\t\t\tget_metatable(lua, &me.inner)?\n\t\t\t\t.call_function::<MultiValue>(MetaMethod::Pairs.name(), me.inner.clone())\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/upload.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{event::ActionCow, url::UrlCow};\n\n#[derive(Debug, Default)]\npub struct UploadOpt {\n\tpub urls: Vec<UrlCow<'static>>,\n}\n\nimpl From<ActionCow> for UploadOpt {\n\tfn from(mut a: ActionCow) -> Self { Self { urls: a.take_seq() } }\n}\n\nimpl FromLua for UploadOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for UploadOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/visual_mode.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug)]\npub struct VisualModeOpt {\n\tpub unset: bool,\n}\n\nimpl From<ActionCow> for VisualModeOpt {\n\tfn from(a: ActionCow) -> Self { Self { unset: a.bool(\"unset\") } }\n}\n\nimpl FromLua for VisualModeOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for VisualModeOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/mgr/yank.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug)]\npub struct YankOpt {\n\tpub cut: bool,\n}\n\nimpl From<ActionCow> for YankOpt {\n\tfn from(a: ActionCow) -> Self { Self { cut: a.bool(\"cut\") } }\n}\n\nimpl FromLua for YankOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for YankOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/notify/mod.rs",
    "content": "yazi_macro::mod_flat!(push tick);\n"
  },
  {
    "path": "yazi-parser/src/notify/push.rs",
    "content": "use std::{str::FromStr, time::Duration};\n\nuse anyhow::anyhow;\nuse mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value};\nuse serde::{Deserialize, Serialize};\nuse serde_with::{DurationSecondsWithFrac, serde_as};\nuse yazi_binding::SER_OPT;\nuse yazi_config::{Style, THEME};\nuse yazi_shared::event::ActionCow;\n\n#[serde_as]\n#[derive(Clone, Debug, Deserialize, Serialize)]\npub struct PushOpt {\n\tpub title:   String,\n\tpub content: String,\n\t#[serde(default)]\n\tpub level:   PushLevel,\n\t#[serde_as(as = \"DurationSecondsWithFrac<f64>\")]\n\tpub timeout: Duration,\n}\n\nimpl TryFrom<ActionCow> for PushOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\ta.take_any(\"opt\").ok_or_else(|| anyhow!(\"Invalid 'opt' in NotifyOpt\"))\n\t}\n}\n\nimpl FromLua for PushOpt {\n\tfn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> { lua.from_value(value) }\n}\n\nimpl IntoLua for PushOpt {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> { lua.to_value_with(&self, SER_OPT) }\n}\n\n// --- Level\n#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]\n#[serde(rename_all = \"kebab-case\")]\npub enum PushLevel {\n\t#[default]\n\tInfo,\n\tWarn,\n\tError,\n}\n\nimpl PushLevel {\n\tpub fn icon(self) -> &'static str {\n\t\tmatch self {\n\t\t\tSelf::Info => &THEME.notify.icon_info,\n\t\t\tSelf::Warn => &THEME.notify.icon_warn,\n\t\t\tSelf::Error => &THEME.notify.icon_error,\n\t\t}\n\t}\n\n\tpub fn style(self) -> Style {\n\t\tmatch self {\n\t\t\tSelf::Info => THEME.notify.title_info,\n\t\t\tSelf::Warn => THEME.notify.title_warn,\n\t\t\tSelf::Error => THEME.notify.title_error,\n\t\t}\n\t}\n}\n\nimpl FromStr for PushLevel {\n\ttype Err = serde::de::value::Error;\n\n\tfn from_str(s: &str) -> Result<Self, Self::Err> {\n\t\tSelf::deserialize(serde::de::value::StrDeserializer::new(s))\n\t}\n}\n"
  },
  {
    "path": "yazi-parser/src/notify/tick.rs",
    "content": "use std::time::Duration;\n\nuse anyhow::bail;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug, Default)]\npub struct TickOpt {\n\tpub interval: Duration,\n}\n\nimpl TryFrom<ActionCow> for TickOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(a: ActionCow) -> Result<Self, Self::Error> {\n\t\tlet Ok(interval) = a.first() else {\n\t\t\tbail!(\"Invalid 'interval' in TickOpt\");\n\t\t};\n\n\t\tif interval < 0.0 {\n\t\t\tbail!(\"'interval' must be non-negative in TickOpt\");\n\t\t}\n\n\t\tOk(Self { interval: Duration::from_secs_f64(interval) })\n\t}\n}\n\nimpl FromLua for TickOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for TickOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/pick/close.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug, Default)]\npub struct CloseOpt {\n\tpub submit: bool,\n}\n\nimpl From<ActionCow> for CloseOpt {\n\tfn from(a: ActionCow) -> Self { Self { submit: a.bool(\"submit\") } }\n}\n\nimpl From<bool> for CloseOpt {\n\tfn from(submit: bool) -> Self { Self { submit } }\n}\n\nimpl FromLua for CloseOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for CloseOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/pick/mod.rs",
    "content": "yazi_macro::mod_flat!(close show);\n"
  },
  {
    "path": "yazi-parser/src/pick/show.rs",
    "content": "use anyhow::bail;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse tokio::sync::mpsc;\nuse yazi_config::popup::PickCfg;\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug)]\npub struct ShowOpt {\n\tpub cfg: PickCfg,\n\tpub tx:  mpsc::UnboundedSender<Option<usize>>,\n}\n\nimpl TryFrom<ActionCow> for ShowOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tlet Some(cfg) = a.take_any(\"cfg\") else {\n\t\t\tbail!(\"Invalid 'cfg' in ShowOpt\");\n\t\t};\n\n\t\tlet Some(tx) = a.take_any(\"tx\") else {\n\t\t\tbail!(\"Invalid 'tx' in ShowOpt\");\n\t\t};\n\n\t\tOk(Self { cfg, tx })\n\t}\n}\n\nimpl FromLua for ShowOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for ShowOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/spot/copy.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{SStr, event::ActionCow};\n\n#[derive(Debug)]\npub struct CopyOpt {\n\tpub r#type: SStr,\n}\n\nimpl From<ActionCow> for CopyOpt {\n\tfn from(mut a: ActionCow) -> Self { Self { r#type: a.take_first().unwrap_or_default() } }\n}\n\nimpl FromLua for CopyOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for CopyOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/spot/mod.rs",
    "content": "yazi_macro::mod_flat!(copy);\n"
  },
  {
    "path": "yazi-parser/src/tasks/mod.rs",
    "content": "yazi_macro::mod_flat!(process_open update_succeed);\n"
  },
  {
    "path": "yazi-parser/src/tasks/process_open.rs",
    "content": "use std::ffi::OsString;\n\nuse anyhow::anyhow;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{CompletionToken, event::ActionCow, url::UrlCow};\n\n// --- Exec\n#[derive(Clone, Debug)]\npub struct ProcessOpenOpt {\n\tpub cwd:    UrlCow<'static>,\n\tpub cmd:    OsString,\n\tpub args:   Vec<UrlCow<'static>>,\n\tpub block:  bool,\n\tpub orphan: bool,\n\tpub done:   Option<CompletionToken>,\n\n\tpub spread: bool, // TODO: remove\n}\n\nimpl TryFrom<ActionCow> for ProcessOpenOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\ta.take_any(\"opt\").ok_or_else(|| anyhow!(\"Missing 'opt' in ProcessOpenOpt\"))\n\t}\n}\n\nimpl FromLua for ProcessOpenOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for ProcessOpenOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/tasks/update_succeed.rs",
    "content": "use anyhow::bail;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{event::ActionCow, url::UrlBuf};\n\n#[derive(Debug)]\npub struct UpdateSucceedOpt {\n\tpub urls: Vec<UrlBuf>,\n}\n\nimpl TryFrom<ActionCow> for UpdateSucceedOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tlet Some(urls) = a.take_any(\"urls\") else {\n\t\t\tbail!(\"Invalid 'urls' in UpdateSucceedOpt\");\n\t\t};\n\n\t\tOk(Self { urls })\n\t}\n}\n\nimpl FromLua for UpdateSucceedOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for UpdateSucceedOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-parser/src/void.rs",
    "content": "use mlua::{FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]\npub struct VoidOpt;\n\nimpl From<ActionCow> for VoidOpt {\n\tfn from(_: ActionCow) -> Self { Self }\n}\n\nimpl From<()> for VoidOpt {\n\tfn from(_: ()) -> Self { Self }\n}\n\nimpl FromLua for VoidOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Ok(Self) }\n}\n\nimpl IntoLua for VoidOpt {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> { lua.create_table()?.into_lua(lua) }\n}\n"
  },
  {
    "path": "yazi-parser/src/which/activate.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value};\nuse tokio::sync::mpsc;\nuse yazi_config::{KEYMAP, keymap::{ChordCow, Key}};\nuse yazi_shared::{Layer, event::ActionCow};\n\n#[derive(Clone, Debug)]\npub struct ActivateOpt {\n\tpub tx:     Option<mpsc::UnboundedSender<Option<yazi_binding::ChordCow>>>,\n\tpub cands:  Vec<ChordCow>,\n\tpub silent: bool,\n\tpub times:  usize,\n}\n\nimpl TryFrom<ActionCow> for ActivateOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tif let Some(opt) = a.take_any2(\"opt\") {\n\t\t\treturn opt;\n\t\t}\n\n\t\tOk(Self {\n\t\t\ttx:     a.take_any2(\"tx\").transpose()?,\n\t\t\tcands:  a.take_any_iter::<yazi_binding::ChordCow>().map(Into::into).collect(),\n\t\t\tsilent: a.bool(\"silent\"),\n\t\t\ttimes:  a.get(\"times\").unwrap_or(0),\n\t\t})\n\t}\n}\n\nimpl From<(Layer, Key)> for ActivateOpt {\n\tfn from((layer, key): (Layer, Key)) -> Self {\n\t\tSelf {\n\t\t\ttx:     None,\n\t\t\tcands:  KEYMAP\n\t\t\t\t.get(layer)\n\t\t\t\t.iter()\n\t\t\t\t.filter(|c| c.on.len() > 1 && c.on[0] == key)\n\t\t\t\t.map(Into::into)\n\t\t\t\t.collect(),\n\t\t\ttimes:  1,\n\t\t\tsilent: false,\n\t\t}\n\t}\n}\n\nimpl FromLua for ActivateOpt {\n\tfn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {\n\t\tlet Value::Table(t) = value else {\n\t\t\treturn Err(\"expected a table\".into_lua_err());\n\t\t};\n\n\t\tOk(Self {\n\t\t\ttx:     t.raw_get::<yazi_binding::MpscUnboundedTx<_>>(\"tx\").ok().map(|t| t.0),\n\t\t\tcands:  t\n\t\t\t\t.raw_get::<Table>(\"cands\")?\n\t\t\t\t.sequence_values::<yazi_binding::ChordCow>()\n\t\t\t\t.map(|c| c.map(Into::into))\n\t\t\t\t.collect::<mlua::Result<Vec<_>>>()?,\n\t\t\ttimes:  t.raw_get(\"times\").unwrap_or_default(),\n\t\t\tsilent: t.raw_get(\"silent\")?,\n\t\t})\n\t}\n}\n\nimpl IntoLua for ActivateOpt {\n\t#[rustfmt::skip]\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tlua\n\t\t\t.create_table_from([\n\t\t\t\t(\"tx\", self.tx.map(yazi_binding::MpscUnboundedTx).into_lua(lua)?),\n\t\t\t\t(\"cands\", lua.create_sequence_from(self.cands.into_iter().map(yazi_binding::ChordCow))?.into_lua(lua)?),\n\t\t\t\t(\"times\", self.times.into_lua(lua)?),\n\t\t\t\t(\"silent\", self.silent.into_lua(lua)?),\n\t\t\t])?\n\t\t\t.into_lua(lua)\n\t}\n}\n"
  },
  {
    "path": "yazi-parser/src/which/mod.rs",
    "content": "yazi_macro::mod_flat!(activate);\n"
  },
  {
    "path": "yazi-plugin/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-plugin\"\ndescription            = \"Yazi plugin system\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[features]\ndefault      = [ \"vendored-lua\" ]\nvendored-lua = [ \"mlua/vendored\" ]\n\n[dependencies]\nyazi-adapter  = { path = \"../yazi-adapter\", version = \"26.2.2\" }\nyazi-binding  = { path = \"../yazi-binding\", version = \"26.2.2\" }\nyazi-boot     = { path = \"../yazi-boot\", version = \"26.2.2\" }\nyazi-config   = { path = \"../yazi-config\", version = \"26.2.2\" }\nyazi-dds      = { path = \"../yazi-dds\", version = \"26.2.2\" }\nyazi-emulator = { path = \"../yazi-emulator\", version = \"26.2.2\" }\nyazi-fs       = { path = \"../yazi-fs\", version = \"26.2.2\" }\nyazi-macro    = { path = \"../yazi-macro\", version = \"26.2.2\" }\nyazi-parser   = { path = \"../yazi-parser\", version = \"26.2.2\" }\nyazi-proxy    = { path = \"../yazi-proxy\", version = \"26.2.2\" }\nyazi-shared   = { path = \"../yazi-shared\", version = \"26.2.2\" }\nyazi-shim     = { path = \"../yazi-shim\", version = \"26.2.2\" }\nyazi-term     = { path = \"../yazi-term\", version = \"26.2.2\" }\nyazi-vfs      = { path = \"../yazi-vfs\", version = \"26.2.2\" }\nyazi-widgets  = { path = \"../yazi-widgets\", version = \"26.2.2\" }\n\n# External dependencies\nansi-to-tui   = { workspace = true }\nanyhow        = { workspace = true }\nfutures       = { workspace = true }\nhashbrown     = { workspace = true }\nmlua          = { workspace = true }\nparking_lot   = { workspace = true }\npaste         = { workspace = true }\nratatui       = { workspace = true }\nserde_json    = { workspace = true }\nsyntect       = { workspace = true }\ntokio         = { workspace = true }\ntokio-stream  = { workspace = true }\ntokio-util    = { workspace = true }\ntracing       = { workspace = true }\ntwox-hash     = { workspace = true }\nunicode-width = { workspace = true }\nyazi-prebuilt = \"0.1.0\"\n\n[target.\"cfg(unix)\".dependencies]\nlibc  = { workspace = true }\nuzers = { workspace = true }\n\n[target.\"cfg(windows)\".dependencies]\nwindows-sys = { version = \"0.61.2\", features = [ \"Win32_System_JobObjects\" ] }\n"
  },
  {
    "path": "yazi-plugin/README.md",
    "content": "# yazi-plugin\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-plugin/preset/compat.lua",
    "content": "--\n"
  },
  {
    "path": "yazi-plugin/preset/components/current.lua",
    "content": "Current = {\n\t_id = \"current\",\n}\n\nfunction Current:new(area, tab)\n\treturn setmetatable({\n\t\t_area = area,\n\t\t_tab = tab,\n\t\t_folder = tab.current,\n\t}, { __index = self })\nend\n\nfunction Current:empty()\n\tlocal s\n\tif self._folder.files.filter then\n\t\ts = \"No filter results\"\n\telse\n\t\tlocal done, err = self._folder.stage()\n\t\ts = not done and \"Loading...\" or not err and \"No items\" or string.format(\"Error: %s\", err)\n\tend\n\n\treturn {\n\t\tui.Text(s):area(self._area):align(ui.Align.CENTER):wrap(ui.Wrap.YES),\n\t}\nend\n\nfunction Current:reflow() return { self } end\n\nfunction Current:redraw()\n\tlocal files = self._folder.window\n\tif #files == 0 then\n\t\treturn self:empty()\n\tend\n\n\tlocal left, right = {}, {}\n\tfor _, f in ipairs(files) do\n\t\tlocal entity = Entity:new(f)\n\t\tleft[#left + 1], right[#right + 1] = entity:redraw(), Linemode:new(f):redraw()\n\n\t\tlocal max = math.max(0, self._area.w - right[#right]:width())\n\t\tleft[#left]:truncate { max = max, ellipsis = entity:ellipsis(max) }\n\tend\n\n\treturn {\n\t\tui.List(left):area(self._area),\n\t\tui.Text(right):area(self._area):align(ui.Align.RIGHT),\n\t}\nend\n\n-- Mouse events\nfunction Current:click(event, up)\n\tlocal y = event.y - self._area.y + 1\n\tif self._folder.window[y] then\n\t\tEntity:new(self._folder.window[y]):click(event, up)\n\tend\nend\n\nfunction Current:scroll(event, step) ya.emit(\"arrow\", { step }) end\n\nfunction Current:touch(event, step) end\n"
  },
  {
    "path": "yazi-plugin/preset/components/entity.lua",
    "content": "Entity = {\n\t_inc = 1000,\n\t_children = {\n\t\t{ \"padding\", id = 1, order = 1000 },\n\t\t{ \"icon\", id = 2, order = 2000 },\n\t\t{ \"prefix\", id = 3, order = 3000 },\n\t\t{ \"highlights\", id = 4, order = 4000 },\n\t\t{ \"found\", id = 5, order = 5000 },\n\t\t{ \"symlink\", id = 6, order = 6000 },\n\t},\n}\n\nfunction Entity:new(file) return setmetatable({ _file = file }, { __index = self }) end\n\nfunction Entity:padding()\n\tif not self._file.is_hovered then\n\t\treturn \" \"\n\tend\n\n\tlocal style = self:style_rev()\n\tif style then\n\t\treturn ui.Span(th.indicator.padding.open):style(style)\n\telse\n\t\treturn \" \"\n\tend\nend\n\nfunction Entity:icon()\n\tlocal icon = self._file:icon()\n\tif not icon then\n\t\treturn \"\"\n\telseif self._file.is_hovered then\n\t\treturn icon.text .. \" \"\n\telse\n\t\treturn ui.Line(icon.text .. \" \"):style(icon.style)\n\tend\nend\n\nfunction Entity:prefix()\n\tlocal prefix = self._file:prefix() or \"\"\n\treturn prefix ~= \"\" and prefix .. \"/\" or \"\"\nend\n\nfunction Entity:highlights()\n\tlocal name, p = self._file.name, ui.printable\n\tlocal highlights = self._file:highlights()\n\tif not highlights or #highlights == 0 then\n\t\treturn p(name)\n\tend\n\n\tlocal spans, last = {}, 0\n\tfor _, h in ipairs(highlights) do\n\t\tif h[1] > last then\n\t\t\tspans[#spans + 1] = p(name:sub(last + 1, h[1]))\n\t\tend\n\t\tspans[#spans + 1] = ui.Span(p(name:sub(h[1] + 1, h[2]))):style(th.mgr.find_keyword)\n\t\tlast = h[2]\n\tend\n\tif last < #name then\n\t\tspans[#spans + 1] = p(name:sub(last + 1))\n\tend\n\treturn ui.Line(spans)\nend\n\nfunction Entity:found()\n\tif not self._file.is_hovered then\n\t\treturn \"\"\n\tend\n\n\tlocal found = self._file:found()\n\tif not found then\n\t\treturn \"\"\n\telseif found[1] >= 99 then\n\t\treturn \"\"\n\tend\n\n\tlocal s = string.format(\"[%d/%s]\", found[1] + 1, found[2] >= 100 and \"99+\" or found[2])\n\treturn ui.Line { \"  \", ui.Span(s):style(th.mgr.find_position) }\nend\n\nfunction Entity:symlink()\n\tif not rt.mgr.show_symlink then\n\t\treturn \"\"\n\tend\n\n\tlocal to = self._file.link_to\n\treturn to and ui.Span(string.format(\" -> %s\", to)):style(th.mgr.symlink_target) or \"\"\nend\n\nfunction Entity:redraw()\n\tlocal lines = {}\n\tfor _, c in ipairs(self._children) do\n\t\tlocal line = (type(c[1]) == \"string\" and self[c[1]] or c[1])(self)\n\t\tc.width, lines[#lines + 1] = ui.width(line), line\n\tend\n\treturn ui.Line(lines):style(self:style())\nend\n\nfunction Entity:style()\n\tlocal s = self._file:style() or ui.Style()\n\tif not self._file.is_hovered then\n\t\treturn s\n\telseif self._file.in_current then\n\t\treturn s:patch(th.indicator.current)\n\telseif self._file.in_preview then\n\t\treturn s:patch(th.indicator.preview)\n\telse\n\t\treturn s:patch(th.indicator.parent)\n\tend\nend\n\nfunction Entity:style_rev()\n\tlocal s = self:style()\n\tlocal bg = s:bg(true)\n\tif bg then\n\t\treturn ui.Style():fg(bg):bg(\"reset\"):reverse(true)\n\telseif s:raw().reversed then\n\t\treturn ui.Style():bg(\"reset\"):reverse(true)\n\tend\nend\n\nfunction Entity:ellipsis(max)\n\tlocal adv, f = 0, self._file\n\tfor _, child in ipairs(self._children) do\n\t\tadv = adv + child.width\n\t\tif adv >= max then\n\t\t\treturn not f.cha.is_dir and f.url.ext and \"….\" .. f.url.ext or nil\n\t\telseif child.id == 4 then\n\t\t\tbreak\n\t\tend\n\tend\nend\n\n-- Mouse events\nfunction Entity:click(event, up)\n\tif up or event.is_middle then\n\t\treturn\n\tend\n\n\tya.emit(\"reveal\", { self._file.url })\n\tif event.is_right then\n\t\tya.emit(\"open\", {})\n\tend\nend\n\n-- Children\nfunction Entity:children_add(fn, order)\n\tself._inc = self._inc + 1\n\tself._children[#self._children + 1] = { fn, id = self._inc, order = order }\n\ttable.sort(self._children, function(a, b) return a.order < b.order end)\n\treturn self._inc\nend\n\nfunction Entity:children_remove(id)\n\tfor i, child in ipairs(self._children) do\n\t\tif child.id == id then\n\t\t\ttable.remove(self._children, i)\n\t\t\tbreak\n\t\tend\n\tend\nend\n"
  },
  {
    "path": "yazi-plugin/preset/components/header.lua",
    "content": "Header = {\n\t-- TODO: remove these two constants\n\tLEFT = 0,\n\tRIGHT = 1,\n\n\t_id = \"header\",\n\t_inc = 1000,\n\t_left = {\n\t\t{ \"cwd\", id = 1, order = 1000 },\n\t},\n\t_right = {\n\t\t{ \"count\", id = 1, order = 1000 },\n\t},\n}\n\nfunction Header:new(area, tab)\n\treturn setmetatable({\n\t\t_area = area,\n\t\t_tab = tab,\n\t\t_current = tab.current,\n\t}, { __index = self })\nend\n\nfunction Header:cwd()\n\tlocal max = self._area.w - self._right_width\n\tif max <= 0 then\n\t\treturn \"\"\n\tend\n\n\tlocal s = ya.readable_path(tostring(self._current.cwd)) .. self:flags()\n\treturn ui.Span(ui.truncate(s, { max = max, rtl = true })):style(th.mgr.cwd)\nend\n\nfunction Header:flags()\n\tlocal cwd = self._current.cwd\n\tlocal filter = self._current.files.filter\n\tlocal finder = self._tab.finder\n\n\tlocal t = {}\n\tif cwd.is_search then\n\t\tt[#t + 1] = string.format(\"search: %s\", cwd.domain)\n\tend\n\tif filter then\n\t\tt[#t + 1] = string.format(\"filter: %s\", filter)\n\tend\n\tif finder then\n\t\tt[#t + 1] = string.format(\"find: %s\", finder)\n\tend\n\treturn #t == 0 and \"\" or \" (\" .. table.concat(t, \", \") .. \")\"\nend\n\nfunction Header:count()\n\tlocal selected = #self._tab.selected\n\tlocal yanked = selected > 0 and 0 or #cx.yanked\n\n\tlocal span\n\tif selected > 0 then\n\t\tspan = ui.Span(\" \" .. selected .. \" \"):style(th.mgr.count_selected)\n\telseif yanked <= 0 then\n\t\treturn \"\"\n\telseif cx.yanked.is_cut then\n\t\tspan = ui.Span(\" \" .. yanked .. \" \"):style(th.mgr.count_cut)\n\telse\n\t\tspan = ui.Span(\" \" .. yanked .. \" \"):style(th.mgr.count_copied)\n\tend\n\n\treturn ui.Line { span, \" \" }\nend\n\nfunction Header:reflow() return { self } end\n\nfunction Header:redraw()\n\tlocal right = self:children_redraw(self.RIGHT)\n\tself._right_width = right:width()\n\n\tlocal left = self:children_redraw(self.LEFT)\n\n\treturn {\n\t\tui.Line(left):area(self._area),\n\t\tui.Line(right):area(self._area):align(ui.Align.RIGHT),\n\t}\nend\n\n-- Mouse events\nfunction Header:click(event, up) end\n\nfunction Header:scroll(event, step) end\n\nfunction Header:touch(event, step) end\n\n-- Children\nfunction Header:children_add(fn, order, side)\n\tself._inc = self._inc + 1\n\tlocal children = side == self.RIGHT and self._right or self._left\n\n\tchildren[#children + 1] = { fn, id = self._inc, order = order }\n\ttable.sort(children, function(a, b) return a.order < b.order end)\n\n\treturn self._inc\nend\n\nfunction Header:children_remove(id, side)\n\tlocal children = side == self.RIGHT and self._right or self._left\n\tfor i, child in ipairs(children) do\n\t\tif child.id == id then\n\t\t\ttable.remove(children, i)\n\t\t\tbreak\n\t\tend\n\tend\nend\n\nfunction Header:children_redraw(side)\n\tlocal lines = {}\n\tfor _, c in ipairs(side == self.RIGHT and self._right or self._left) do\n\t\tlines[#lines + 1] = (type(c[1]) == \"string\" and self[c[1]] or c[1])(self)\n\tend\n\treturn ui.Line(lines)\nend\n"
  },
  {
    "path": "yazi-plugin/preset/components/linemode.lua",
    "content": "Linemode = {\n\t_inc = 1000,\n\t_children = {\n\t\t{ \"solo\", id = 1, order = 1000 },\n\t\t{ \"padding\", id = 2, order = 2000 },\n\t},\n}\n\nfunction Linemode:new(file) return setmetatable({ _file = file }, { __index = self }) end\n\nfunction Linemode:solo()\n\tif not self._file.in_current then\n\t\treturn \"\"\n\tend\n\n\tlocal mode = cx.active.pref.linemode\n\tif mode == \"none\" or mode == \"solo\" then\n\t\treturn \"\"\n\telseif not self[mode] then\n\t\treturn \" \" .. mode\n\telse\n\t\tlocal line = ui.Line(self[mode](self))\n\t\treturn line:visible() and ui.Line { \" \", line } or line\n\tend\nend\n\nfunction Linemode:size()\n\tlocal size = self._file:size()\n\tif size then\n\t\treturn ya.readable_size(size)\n\telse\n\t\tlocal folder = cx.active:history(self._file.url)\n\t\treturn folder and tostring(#folder.files) or \"\"\n\tend\nend\n\nfunction Linemode:btime()\n\tlocal time = math.floor(self._file.cha.btime or 0)\n\tif time == 0 then\n\t\treturn \"\"\n\telseif os.date(\"%Y\", time) == os.date(\"%Y\") then\n\t\treturn os.date(\"%m/%d %H:%M\", time)\n\telse\n\t\treturn os.date(\"%m/%d  %Y\", time)\n\tend\nend\n\nfunction Linemode:mtime()\n\tlocal time = math.floor(self._file.cha.mtime or 0)\n\tif time == 0 then\n\t\treturn \"\"\n\telseif os.date(\"%Y\", time) == os.date(\"%Y\") then\n\t\treturn os.date(\"%m/%d %H:%M\", time)\n\telse\n\t\treturn os.date(\"%m/%d  %Y\", time)\n\tend\nend\n\nfunction Linemode:permissions() return self._file.cha:perm() or \"\" end\n\nfunction Linemode:owner()\n\tlocal user = ya.user_name and ya.user_name(self._file.cha.uid) or self._file.cha.uid\n\tlocal group = ya.group_name and ya.group_name(self._file.cha.gid) or self._file.cha.gid\n\treturn string.format(\"%s:%s\", user, group)\nend\n\nfunction Linemode:padding()\n\tif not self._file.is_hovered then\n\t\treturn \" \"\n\tend\n\n\tlocal style = Entity:new(self._file):style_rev()\n\tif style then\n\t\treturn ui.Span(th.indicator.padding.close):style(style)\n\telse\n\t\treturn \" \"\n\tend\nend\n\nfunction Linemode:redraw()\n\tlocal lines = {}\n\tfor _, c in ipairs(self._children) do\n\t\tlines[#lines + 1] = (type(c[1]) == \"string\" and self[c[1]] or c[1])(self)\n\tend\n\treturn ui.Line(lines)\nend\n\n-- Children\nfunction Linemode:children_add(fn, order)\n\tself._inc = self._inc + 1\n\tself._children[#self._children + 1] = { fn, id = self._inc, order = order }\n\ttable.sort(self._children, function(a, b) return a.order < b.order end)\n\treturn self._inc\nend\n\nfunction Linemode:children_remove(id)\n\tfor i, child in ipairs(self._children) do\n\t\tif child.id == id then\n\t\t\ttable.remove(self._children, i)\n\t\t\tbreak\n\t\tend\n\tend\nend\n"
  },
  {
    "path": "yazi-plugin/preset/components/marker.lua",
    "content": "Marker = {\n\t_id = \"marker\",\n}\n\nfunction Marker:new(area, folder)\n\treturn setmetatable({\n\t\t_area = area,\n\t\t_folder = folder,\n\t}, { __index = self })\nend\n\nfunction Marker:redraw()\n\tif self._area.w * self._area.h == 0 then\n\t\treturn {}\n\telseif not self._folder or #self._folder.window == 0 then\n\t\treturn {}\n\tend\n\n\tlocal elements = {}\n\tlocal append = function(last)\n\t\tif not last[3] then\n\t\t\treturn\n\t\tend\n\n\t\tlocal y = math.min(self._area.y + last[1], self._area.y + self._area.h) - 1\n\t\tlocal rect = ui.Rect {\n\t\t\tx = math.max(0, self._area.x - 1),\n\t\t\ty = y,\n\t\t\tw = 1,\n\t\t\th = math.min(1 + last[2] - last[1], self._area.y + self._area.h - y),\n\t\t}\n\t\telements[#elements + 1] = ui.Bar(ui.Edge.LEFT):area(rect):style(last[3]):symbol(th.mgr.marker_symbol)\n\tend\n\n\tlocal last = { 0, 0, nil } -- start, end, style\n\tfor i, f in ipairs(self._folder.window) do\n\t\tlocal style = self:style(f)\n\t\tif i - last[2] > 1 or last[3] ~= style then\n\t\t\tappend(last)\n\t\t\tlast = { i, i, style }\n\t\telse\n\t\t\tlast[2] = i\n\t\tend\n\tend\n\n\tappend(last)\n\treturn elements\nend\n\nfunction Marker:style(file)\n\tlocal marked = file:is_marked()\n\tif marked == 1 then\n\t\treturn th.mgr.marker_marked\n\telseif marked == 0 and file:is_selected() then\n\t\treturn th.mgr.marker_selected\n\tend\n\n\tlocal yanked = file:is_yanked()\n\tif yanked == 1 then\n\t\treturn th.mgr.marker_copied\n\telseif yanked == 2 then\n\t\treturn th.mgr.marker_cut\n\tend\nend\n\n-- Mouse events\nfunction Marker:click(event, up) end\n\nfunction Marker:scroll(event, step) end\n\nfunction Marker:touch(event, step) end\n"
  },
  {
    "path": "yazi-plugin/preset/components/modal.lua",
    "content": "Modal = {\n\t_id = \"modal\",\n\n\t_inc = 1000,\n\t_children = {},\n}\n\nfunction Modal:new(area) return setmetatable({ _area = area }, { __index = self }) end\n\nfunction Modal:reflow()\n\tlocal components = {}\n\tfor _, child in ipairs(self._children) do\n\t\tcomponents = ya.list_merge(components, child[1]:new(self._area):reflow())\n\tend\n\treturn components\nend\n\nfunction Modal:redraw() return {} end\n\n-- Children\nfunction Modal:children_add(tbl, order)\n\tself._inc = self._inc + 1\n\tself._children[#self._children + 1] = { tbl, id = self._inc, order = order }\n\n\ttable.sort(self._children, function(a, b) return a.order < b.order end)\n\treturn self._inc\nend\n\nfunction Modal:children_remove(id)\n\tfor i, child in ipairs(self._children) do\n\t\tif child.id == id then\n\t\t\ttable.remove(self._children, i)\n\t\t\tbreak\n\t\tend\n\tend\nend\n\nfunction Modal:children_redraw()\n\tlocal elements = {}\n\tfor _, child in ipairs(self._children) do\n\t\telements = ya.list_merge(elements, ui.redraw(child[1]:new(self._area)))\n\tend\n\treturn elements\nend\n"
  },
  {
    "path": "yazi-plugin/preset/components/parent.lua",
    "content": "Parent = {\n\t_id = \"parent\",\n}\n\nfunction Parent:new(area, tab)\n\treturn setmetatable({\n\t\t_area = area,\n\t\t_tab = tab,\n\t\t_folder = tab.parent,\n\t}, { __index = self })\nend\n\nfunction Parent:reflow() return { self } end\n\nfunction Parent:redraw()\n\tif not self._folder then\n\t\treturn {}\n\tend\n\n\tlocal left, right = {}, {}\n\tfor _, f in ipairs(self._folder.window) do\n\t\tlocal entity = Entity:new(f)\n\t\tleft[#left + 1], right[#right + 1] = entity:redraw(), Linemode:new(f):redraw()\n\n\t\tlocal max = math.max(0, self._area.w - right[#right]:width())\n\t\tleft[#left]:truncate { max = max, ellipsis = entity:ellipsis(max) }\n\tend\n\n\treturn {\n\t\tui.List(left):area(self._area),\n\t\tui.Text(right):area(self._area):align(ui.Align.RIGHT),\n\t}\nend\n\n-- Mouse events\nfunction Parent:click(event, up)\n\tlocal y = event.y - self._area.y + 1\n\tlocal window = self._folder and self._folder.window or {}\n\tif window[y] then\n\t\tEntity:new(window[y]):click(event, up)\n\telseif not up and event.is_left then\n\t\tya.emit(\"leave\", {})\n\tend\nend\n\nfunction Parent:scroll(event, step) end\n\nfunction Parent:touch(event, step) end\n"
  },
  {
    "path": "yazi-plugin/preset/components/preview.lua",
    "content": "Preview = {\n\t_id = \"preview\",\n}\n\nfunction Preview:new(area, tab)\n\treturn setmetatable({\n\t\t_area = area,\n\t\t_tab = tab,\n\t\t_folder = tab.preview.folder,\n\t}, { __index = self })\nend\n\nfunction Preview:reflow() return { self } end\n\nfunction Preview:redraw() return {} end\n\n-- Mouse events\nfunction Preview:click(event, up)\n\tlocal y = event.y - self._area.y + 1\n\tlocal window = self._folder and self._folder.window or {}\n\tif window[y] then\n\t\tEntity:new(window[y]):click(event, up)\n\telseif not up and event.is_left then\n\t\tya.emit(\"enter\", {})\n\tend\nend\n\nfunction Preview:scroll(event, step) ya.emit(\"seek\", { step }) end\n\nfunction Preview:touch(event, step) end\n"
  },
  {
    "path": "yazi-plugin/preset/components/progress.lua",
    "content": "Progress = {\n\t_id = \"progress\",\n}\n\nfunction Progress:new(area, offset)\n\tlocal me = setmetatable({ _area = area, _offset = offset }, { __index = self })\n\tme:layout()\n\treturn me\nend\n\nfunction Progress:use(area) return setmetatable({ _area = area }, { __index = self }) end\n\nfunction Progress:layout()\n\tself._area = ui.Rect {\n\t\tx = math.max(0, self._area.w - self._offset - 21),\n\t\ty = self._area.y,\n\t\tw = ya.clamp(0, self._area.w - self._offset - 1, 20),\n\t\th = math.min(1, self._area.h),\n\t}\nend\n\nfunction Progress:reflow() return { self } end\n\nfunction Progress:redraw()\n\tlocal summary = cx.tasks.summary\n\tif summary.total == 0 then\n\t\treturn {}\n\tend\n\n\tlocal gauge = ui.Gauge():area(self._area)\n\tif summary.failed == 0 then\n\t\tgauge:gauge_style(th.status.progress_normal)\n\telse\n\t\tgauge:gauge_style(th.status.progress_error)\n\tend\n\n\tlocal label, percent = \"\", summary.percent\n\tif percent then\n\t\tlabel = string.format(\"%3d%%, \", math.floor(percent))\n\telse\n\t\tpercent = 0\n\tend\n\n\tlabel = label .. string.format(\"%d left\", summary.total - summary.success)\n\treturn {\n\t\tui.Clear(self._area),\n\t\tgauge:percent(percent):label(ui.Span(label):style(th.status.progress_label)),\n\t}\nend\n"
  },
  {
    "path": "yazi-plugin/preset/components/rail.lua",
    "content": "Rail = {\n\t_id = \"rail\",\n}\n\nfunction Rail:new(chunks, tab)\n\tlocal me = setmetatable({ _chunks = chunks, _tab = tab }, { __index = self })\n\tme:build()\n\treturn me\nend\n\nfunction Rail:build()\n\tself._base = {\n\t\tui.Bar(ui.Edge.RIGHT):area(self._chunks[1]):symbol(th.mgr.border_symbol):style(th.mgr.border_style),\n\t\tui.Bar(ui.Edge.LEFT):area(self._chunks[3]):symbol(th.mgr.border_symbol):style(th.mgr.border_style),\n\t}\n\tself._children = {\n\t\tMarker:new(self._chunks[1], self._tab.parent),\n\t\tMarker:new(self._chunks[2], self._tab.current),\n\t}\nend\n\nfunction Rail:reflow() return {} end\n\nfunction Rail:redraw()\n\tlocal elements = self._base or {}\n\tfor _, child in ipairs(self._children) do\n\t\telements = ya.list_merge(elements, ui.redraw(child))\n\tend\n\treturn elements\nend\n\n-- Mouse events\nfunction Rail:click(event, up) end\n\nfunction Rail:scroll(event, step) end\n\nfunction Rail:touch(event, step) end\n"
  },
  {
    "path": "yazi-plugin/preset/components/root.lua",
    "content": "Root = {\n\t_id = \"root\",\n\t_drag_start = ui.Rect {},\n}\n\nfunction Root:new(area)\n\tlocal me = setmetatable({ _area = area }, { __index = self })\n\tme:layout()\n\tme:build()\n\treturn me\nend\n\nfunction Root:layout()\n\tself._chunks = ui.Layout()\n\t\t:direction(ui.Layout.VERTICAL)\n\t\t:constraints({\n\t\t\tui.Constraint.Length(1),\n\t\t\tui.Constraint.Length(Tabs.height()),\n\t\t\tui.Constraint.Fill(1),\n\t\t\tui.Constraint.Length(1),\n\t\t})\n\t\t:split(self._area)\nend\n\nfunction Root:build()\n\tself._children = {\n\t\tHeader:new(self._chunks[1], cx.active),\n\t\tTabs:new(self._chunks[2]),\n\t\tTab:new(self._chunks[3], cx.active),\n\t\tStatus:new(self._chunks[4], cx.active),\n\t\tModal:new(self._area),\n\t}\nend\n\nfunction Root:reflow()\n\tlocal components = { self }\n\tfor _, child in ipairs(self._children) do\n\t\tcomponents = ya.list_merge(components, child:reflow())\n\tend\n\treturn components\nend\n\nfunction Root:redraw()\n\tlocal elements = self._base or {}\n\tfor _, child in ipairs(self._children) do\n\t\telements = ya.list_merge(elements, ui.redraw(child))\n\tend\n\treturn elements\nend\n\n-- Mouse events\nfunction Root:click(event, up)\n\tif tostring(cx.layer) ~= \"mgr\" then\n\t\treturn\n\tend\n\tlocal c = ya.child_at(ui.Rect { x = event.x, y = event.y }, self:reflow())\n\treturn c and c:click(event, up)\nend\n\nfunction Root:scroll(event, step)\n\tif tostring(cx.layer) ~= \"mgr\" then\n\t\treturn\n\tend\n\tlocal c = ya.child_at(ui.Rect { x = event.x, y = event.y }, self:reflow())\n\treturn c and c:scroll(event, step)\nend\n\nfunction Root:touch(event, step)\n\tif tostring(cx.layer) ~= \"mgr\" then\n\t\treturn\n\tend\n\tlocal c = ya.child_at(ui.Rect { x = event.x, y = event.y }, self:reflow())\n\treturn c and c:touch(event, step)\nend\n\nfunction Root:move(event) end\n\nfunction Root:drag(event) end\n"
  },
  {
    "path": "yazi-plugin/preset/components/status.lua",
    "content": "Status = {\n\t-- TODO: remove these two constants\n\tLEFT = 0,\n\tRIGHT = 1,\n\n\t_id = \"status\",\n\t_inc = 1000,\n\t_left = {\n\t\t{ \"mode\", id = 1, order = 1000 },\n\t\t{ \"size\", id = 2, order = 2000 },\n\t\t{ \"name\", id = 3, order = 3000 },\n\t},\n\t_right = {\n\t\t{ \"perm\", id = 4, order = 1000 },\n\t\t{ \"percent\", id = 5, order = 2000 },\n\t\t{ \"position\", id = 6, order = 3000 },\n\t},\n}\n\nfunction Status:new(area, tab)\n\treturn setmetatable({\n\t\t_area = area,\n\t\t_tab = tab,\n\t\t_current = tab.current,\n\t}, { __index = self })\nend\n\nfunction Status:style()\n\tlocal m = th.mode\n\tif self._tab.mode.is_select then\n\t\treturn { main = m.select_main, alt = m.select_alt }\n\telseif self._tab.mode.is_unset then\n\t\treturn { main = m.unset_main, alt = m.unset_alt }\n\telse\n\t\treturn { main = m.normal_main, alt = m.normal_alt }\n\tend\nend\n\nfunction Status:mode()\n\tlocal mode = tostring(self._tab.mode):sub(1, 3):upper()\n\n\tlocal style = self:style()\n\treturn ui.Line {\n\t\tui.Span(th.status.sep_left.open):fg(style.main:bg()):bg(\"reset\"),\n\t\tui.Span(\" \" .. mode .. \" \"):style(style.main),\n\t\tui.Span(th.status.sep_left.close):fg(style.main:bg()):bg(style.alt:bg()),\n\t}\nend\n\nfunction Status:size()\n\tlocal h = self._current.hovered\n\tlocal size = h and (h:size() or h.cha.len) or 0\n\n\tlocal style = self:style()\n\treturn ui.Line {\n\t\tui.Span(\" \" .. ya.readable_size(size) .. \" \"):style(style.alt),\n\t\tui.Span(th.status.sep_left.close):fg(style.alt:bg()),\n\t}\nend\n\nfunction Status:name()\n\tlocal h = self._current.hovered\n\tif not h then\n\t\treturn \"\"\n\tend\n\n\treturn \" \" .. ui.printable(h.name)\nend\n\nfunction Status:perm()\n\tlocal h = self._current.hovered\n\tif not h then\n\t\treturn \"\"\n\tend\n\n\tlocal perm = h.cha:perm()\n\tif not perm then\n\t\treturn \"\"\n\tend\n\n\tlocal spans = {}\n\tfor i = 1, #perm do\n\t\tlocal c = perm:sub(i, i)\n\t\tlocal style = th.status.perm_type\n\t\tif c == \"-\" or c == \"?\" then\n\t\t\tstyle = th.status.perm_sep\n\t\telseif c == \"r\" then\n\t\t\tstyle = th.status.perm_read\n\t\telseif c == \"w\" then\n\t\t\tstyle = th.status.perm_write\n\t\telseif c == \"x\" or c == \"s\" or c == \"S\" or c == \"t\" or c == \"T\" then\n\t\t\tstyle = th.status.perm_exec\n\t\tend\n\t\tspans[i] = ui.Span(c):style(style)\n\tend\n\treturn ui.Line(spans)\nend\n\nfunction Status:percent()\n\tlocal percent = 0\n\tlocal cursor = self._current.cursor\n\tlocal length = #self._current.files\n\tif cursor ~= 0 and length ~= 0 then\n\t\tpercent = math.floor((cursor + 1) * 100 / length)\n\tend\n\n\tif percent == 0 then\n\t\tpercent = \" Top \"\n\telseif percent == 100 then\n\t\tpercent = \" Bot \"\n\telse\n\t\tpercent = string.format(\" %2d%% \", percent)\n\tend\n\n\tlocal style = self:style()\n\treturn ui.Line {\n\t\tui.Span(\" \" .. th.status.sep_right.open):fg(style.alt:bg()),\n\t\tui.Span(percent):style(style.alt),\n\t}\nend\n\nfunction Status:position()\n\tlocal cursor = self._current.cursor\n\tlocal length = #self._current.files\n\n\tlocal style = self:style()\n\treturn ui.Line {\n\t\tui.Span(th.status.sep_right.open):fg(style.main:bg()):bg(style.alt:bg()),\n\t\tui.Span(string.format(\" %2d/%-2d \", math.min(cursor + 1, length), length)):style(style.main),\n\t\tui.Span(th.status.sep_right.close):fg(style.main:bg()):bg(\"reset\"),\n\t}\nend\n\nfunction Status:reflow() return { self } end\n\nfunction Status:redraw()\n\tlocal left = self:children_redraw(self.LEFT)\n\n\tlocal right = self:children_redraw(self.RIGHT)\n\tlocal right_width = right:width()\n\n\treturn {\n\t\tui.Text(\"\"):area(self._area):style(th.status.overall),\n\t\tui.Line(left):area(self._area),\n\t\tui.Line(right):area(self._area):align(ui.Align.RIGHT),\n\t\ttable.unpack(ui.redraw(Progress:new(self._area, right_width))),\n\t}\nend\n\n-- Mouse events\nfunction Status:click(event, up) end\n\nfunction Status:scroll(event, step) end\n\nfunction Status:touch(event, step) end\n\n-- Children\nfunction Status:children_add(fn, order, side)\n\tself._inc = self._inc + 1\n\tlocal children = side == self.RIGHT and self._right or self._left\n\n\tchildren[#children + 1] = { fn, id = self._inc, order = order }\n\ttable.sort(children, function(a, b) return a.order < b.order end)\n\n\treturn self._inc\nend\n\nfunction Status:children_remove(id, side)\n\tlocal children = side == self.RIGHT and self._right or self._left\n\tfor i, child in ipairs(children) do\n\t\tif child.id == id then\n\t\t\ttable.remove(children, i)\n\t\t\tbreak\n\t\tend\n\tend\nend\n\nfunction Status:children_redraw(side)\n\tlocal lines = {}\n\tfor _, c in ipairs(side == self.RIGHT and self._right or self._left) do\n\t\tlines[#lines + 1] = (type(c[1]) == \"string\" and self[c[1]] or c[1])(self)\n\tend\n\treturn ui.Line(lines)\nend\n"
  },
  {
    "path": "yazi-plugin/preset/components/tab.lua",
    "content": "Tab = {\n\t_id = \"tab\",\n}\n\nfunction Tab:new(area, tab)\n\tlocal me = setmetatable({ _area = area, _tab = tab }, { __index = self })\n\tme:layout()\n\tme:build()\n\treturn me\nend\n\nfunction Tab:layout()\n\tlocal ratio = rt.mgr.ratio\n\tself._chunks = ui.Layout()\n\t\t:direction(ui.Layout.HORIZONTAL)\n\t\t:constraints({\n\t\t\tui.Constraint.Ratio(ratio.parent, ratio.all),\n\t\t\tui.Constraint.Ratio(ratio.current, ratio.all),\n\t\t\tui.Constraint.Ratio(ratio.preview, ratio.all),\n\t\t})\n\t\t:split(self._area)\nend\n\nfunction Tab:build()\n\tlocal c = self._chunks\n\tself._children = {\n\t\tParent:new(c[1]:pad(ui.Pad.x(1)), self._tab),\n\t\tCurrent:new(c[2]:pad(ui.Pad(0, c[3].w > 0 and 0 or 1, 0, c[1].w > 0 and 0 or 1)), self._tab),\n\t\tPreview:new(c[3]:pad(ui.Pad.x(1)), self._tab),\n\t\tRail:new(c, self._tab),\n\t}\nend\n\nfunction Tab:reflow()\n\tlocal components = { self }\n\tfor _, child in ipairs(self._children) do\n\t\tcomponents = ya.list_merge(components, child:reflow())\n\tend\n\treturn components\nend\n\nfunction Tab:redraw()\n\tlocal elements = self._base or {}\n\tfor _, child in ipairs(self._children) do\n\t\telements = ya.list_merge(elements, ui.redraw(child))\n\tend\n\treturn elements\nend\n\n-- Mouse events\nfunction Tab:click(event, up) end\n\nfunction Tab:scroll(event, step) end\n\nfunction Tab:touch(event, step) end\n"
  },
  {
    "path": "yazi-plugin/preset/components/tabs.lua",
    "content": "Tabs = {\n\t_id = \"tabs\",\n\t_offsets = {},\n}\n\nfunction Tabs:new(area)\n\treturn setmetatable({\n\t\t_area = area,\n\t}, { __index = self })\nend\n\nfunction Tabs:reflow() return { self } end\n\nfunction Tabs:redraw()\n\tif self.height() < 1 then\n\t\treturn {}\n\tend\n\n\tlocal lines = {\n\t\tui.Line(th.tabs.sep_outer.open):fg(th.tabs.inactive:bg()),\n\t}\n\n\tlocal pos = lines[1]:width()\n\tlocal max = math.floor(self:inner_width() / #cx.tabs)\n\tfor i = 1, #cx.tabs do\n\t\tlocal name = ui.truncate(string.format(\" %d %s \", i, cx.tabs[i].name), { max = max })\n\t\tif i == cx.tabs.idx then\n\t\t\tlines[#lines + 1] = ui.Line {\n\t\t\t\tui.Span(th.tabs.sep_inner.open):style(th.tabs.inactive),\n\t\t\t\tui.Span(name):style(th.tabs.active),\n\t\t\t\tui.Span(th.tabs.sep_inner.close):style(th.tabs.inactive),\n\t\t\t}\n\t\telse\n\t\t\tlines[#lines + 1] = ui.Line(name):style(th.tabs.inactive)\n\t\tend\n\t\tself._offsets[i], pos = pos, pos + lines[#lines]:width()\n\tend\n\n\tlines[#lines + 1] = ui.Line(th.tabs.sep_outer.close):fg(th.tabs.inactive:bg())\n\treturn ui.Line(lines):area(self._area)\nend\n\nfunction Tabs.height() return #cx.tabs > 1 and 1 or 0 end\n\nfunction Tabs:inner_width()\n\tlocal si, so = th.tabs.sep_inner, th.tabs.sep_outer\n\treturn math.max(0, self._area.w - ui.Line({ si.open, si.close, so.open, so.close }):width())\nend\n\n-- Mouse events\nfunction Tabs:click(event, up)\n\tif up or event.is_middle then\n\t\treturn\n\tend\n\tfor i = #cx.tabs, 1, -1 do\n\t\tif event.x >= self._offsets[i] then\n\t\t\tya.emit(\"tab_switch\", { i - 1 })\n\t\t\tbreak\n\t\tend\n\tend\nend\n\nfunction Tabs:scroll(event, step) end\n\nfunction Tabs:touch(event, step) end\n"
  },
  {
    "path": "yazi-plugin/preset/components/tasks.lua",
    "content": "Tasks = {\n\t_id = \"tasks\",\n}\n\nfunction Tasks:new(area)\n\tlocal me = setmetatable({ _area = area }, { __index = self })\n\tme:layout()\n\treturn me\nend\n\nfunction Tasks:layout()\n\tself._area = self._area:pad(ui.Pad(1, 1, 1, 3))\n\tself._chunks = ui.Layout()\n\t\t:direction(ui.Layout.HORIZONTAL)\n\t\t:constraints({\n\t\t\tui.Constraint.Percentage(60),\n\t\t\tui.Constraint.Percentage(40),\n\t\t})\n\t\t:split(self._area)\nend\n\nfunction Tasks:reflow() return { self } end\n\nfunction Tasks:redraw()\n\tlocal elements = {}\n\tfor i, snap in ipairs(cx.tasks.snaps) do\n\t\tlocal y = self._area.y + (i - 1) * 3\n\t\tif y >= self._area.bottom then\n\t\t\tbreak\n\t\tend\n\n\t\telements[#elements + 1] = ui.Line({ self:icon(snap), snap.name }):area(ui.Rect {\n\t\t\tx = self._area.x,\n\t\t\ty = y,\n\t\t\tw = self._area.w,\n\t\t\th = 1,\n\t\t})\n\n\t\tif i == cx.tasks.cursor + 1 then\n\t\t\telements[#elements] = elements[#elements]:style(th.tasks.hovered)\n\t\tend\n\n\t\tfor _, e in ipairs(self:progress_redraw(snap, y + 1)) do\n\t\t\telements[#elements + 1] = e\n\t\tend\n\n\t\telements[#elements + 1] = ui.Bar(ui.Edge.LEFT)\n\t\t\t:area(ui.Rect {\n\t\t\t\tx = math.max(0, self._area.x - 2),\n\t\t\t\ty = y,\n\t\t\t\tw = self._area.w,\n\t\t\t\th = 2,\n\t\t\t})\n\t\t\t:symbol(\"┃\")\n\n\t\tif i == cx.tasks.cursor + 1 then\n\t\t\telements[#elements] = elements[#elements]:style(th.tasks.hovered)\n\t\tend\n\tend\n\n\treturn elements\nend\n\nfunction Tasks:icon(snap)\n\tif snap.prog.kind == \"FileCopy\" then\n\t\treturn \"  \"\n\telseif snap.prog.kind == \"FileCut\" then\n\t\treturn \"  \"\n\telseif snap.prog.kind == \"FileDelete\" then\n\t\treturn \"  \"\n\telseif snap.prog.kind == \"FileDownload\" then\n\t\treturn \"  \"\n\telseif snap.prog.kind == \"FileUpload\" then\n\t\treturn \"  \"\n\telse\n\t\treturn \"  \"\n\tend\nend\n\nfunction Tasks:progress_redraw(snap, y)\n\tlocal kind = snap.prog.kind\n\tif\n\t\tkind == \"FileCopy\"\n\t\tor kind == \"FileCut\"\n\t\tor kind == \"FileDelete\"\n\t\tor kind == \"FileDownload\"\n\t\tor kind == \"FileUpload\"\n\tthen\n\t\tlocal percent\n\t\tif snap.cooked then\n\t\t\tpercent = \"Cleaning…\"\n\t\telse\n\t\t\tpercent = string.format(\"%3d%%\", math.floor(snap.percent))\n\t\tend\n\n\t\tlocal label = string.format(\n\t\t\t\"%s - %s / %s\",\n\t\t\tpercent,\n\t\t\tya.readable_size(snap.prog.processed_bytes),\n\t\t\tya.readable_size(snap.prog.total_bytes)\n\t\t)\n\n\t\tlocal style = th.status.progress_normal\n\t\tif snap.failed or snap.prog.failed_files > 0 then\n\t\t\tstyle = th.status.progress_error\n\t\tend\n\n\t\treturn {\n\t\t\tui.Gauge()\n\t\t\t\t:area(ui.Rect { x = self._chunks[1].x, y = y, w = self._chunks[1].w, h = 1 })\n\t\t\t\t:percent(snap.percent)\n\t\t\t\t:label(ui.Span(label):style(th.status.progress_label))\n\t\t\t\t:gauge_style(style),\n\n\t\t\tui.Line(string.format(\"%d/%d\", snap.prog.success_files, snap.prog.total_files))\n\t\t\t\t:fg(\"gray\")\n\t\t\t\t:area(ui.Rect { x = self._chunks[2].x, y = y, w = self._chunks[2].w, h = 1 })\n\t\t\t\t:align(ui.Align.RIGHT),\n\t\t}\n\telse\n\t\tlocal text\n\t\tif snap.cooked then\n\t\t\ttext = \"Cleaning…\"\n\t\telseif snap.running then\n\t\t\ttext = \"Running…\"\n\t\telse\n\t\t\ttext = \"Failed, press Enter to view log…\"\n\t\tend\n\t\treturn {\n\t\t\tui.Line(text):fg(\"gray\"):area(ui.Rect { x = self._chunks[1].x, y = y, w = self._chunks[1].w, h = 1 }),\n\t\t}\n\tend\nend\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/archive.lua",
    "content": "local M = {}\n\nfunction M:peek(job)\n\tlocal limit = job.area.h\n\tlocal items, err = self.list_archive({ \"-p\", tostring(job.file.path) }, job.skip, limit)\n\n\tlocal first = (#items == 1 and items[1]) or (#items == 0 and M.list_if_only_one(job.file.path))\n\tif first and M.should_decompress_tar(job.file, first) then\n\t\titems, err = self.list_compressed_tar({ \"-p\", tostring(job.file.path) }, job.skip, limit)\n\tend\n\n\tif err then\n\t\treturn ya.preview_widget(job, err)\n\telseif job.skip > 0 and #items < job.skip + limit then\n\t\treturn ya.emit(\"peek\", { math.max(0, #items - limit), only_if = job.file.url, upper_bound = true })\n\telseif #items == 0 then\n\t\titems = { M.make_item { path = job.file.url.stem } }\n\tend\n\n\tlocal left, right = {}, {}\n\tfor i = job.skip + 1, #items do\n\t\tlocal f = items[i]\n\t\tlocal icon = File({\n\t\t\turl = Url(f.path),\n\t\t\tcha = Cha { mode = tonumber(f.is_dir and \"40700\" or \"100644\", 8) },\n\t\t}):icon()\n\n\t\tif f.size > 0 then\n\t\t\tright[#right + 1] = \" \" .. ya.readable_size(f.size) .. \" \"\n\t\telse\n\t\t\tright[#right + 1] = \" \"\n\t\tend\n\n\t\tif icon then\n\t\t\tleft[#left + 1] = ui.Span(\" \" .. icon.text .. \" \"):style(icon.style)\n\t\telse\n\t\t\tleft[#left + 1] = \" \"\n\t\tend\n\n\t\tleft[#left] = ui.Line {\n\t\t\tstring.rep(\" │\", f.depth),\n\t\t\tleft[#left],\n\t\t\tui.truncate(f.path.name or tostring(f.path), {\n\t\t\t\trtl = true,\n\t\t\t\tmax = math.max(0, job.area.w - (f.depth * 2) - ui.width(left[#left]) - ui.width(right[#right])),\n\t\t\t}),\n\t\t}\n\tend\n\n\tya.preview_widget(job, {\n\t\tui.Text(left):area(job.area),\n\t\tui.Text(right):area(job.area):align(ui.Align.RIGHT),\n\t})\nend\n\nfunction M:seek(job) require(\"code\"):seek(job) end\n\nfunction M.spawn_7z(args)\n\tlocal last_err = nil\n\tlocal try = function(name)\n\t\tlocal stdout = args[1] == \"l\" and Command.PIPED or Command.NULL\n\t\tlocal child, err = Command(name):arg(args):stdout(stdout):stderr(Command.PIPED):spawn()\n\t\tif not child then\n\t\t\tlast_err = err\n\t\tend\n\t\treturn child\n\tend\n\n\tlocal child = try(\"7zz\") or try(\"7z\")\n\tif not child then\n\t\treturn ya.err(\"Failed to start either `7zz` or `7z`, error: \" .. last_err)\n\tend\n\treturn child, last_err\nend\n\n-- Spawn a 7z instance which pipes a \"7z {src_args}\" into a \"7z {dst_args}\"\n-- Used for previewing compressed tarballs, by doing \"7z x -so .. | 7z l -si ..\"\nfunction M.spawn_7z_piped(src_args, dst_args)\n\tlocal last_err = nil\n\tlocal try = function(name)\n\t\tlocal src, err = Command(name):arg(src_args):stdout(Command.PIPED):stderr(Command.PIPED):spawn()\n\t\tif not src then\n\t\t\tlast_err = err\n\t\t\treturn src\n\t\tend\n\t\tlocal dst, err =\n\t\t\tCommand(name):arg(dst_args):stdin(src:take_stdout()):stdout(Command.PIPED):stderr(Command.PIPED):spawn()\n\t\tif not dst then\n\t\t\tlast_err = err\n\t\tend\n\t\treturn src, dst\n\tend\n\n\tlocal src, dst = try(\"7zz\")\n\tif not src then\n\t\tsrc, dst = try(\"7z\")\n\tend\n\tif not dst then\n\t\treturn ya.err(\"Failed to start either `7zz` or `7z`, error: \" .. last_err)\n\tend\n\treturn src, dst, last_err\nend\n\n---List items in an archive\n---@param args table\n---@param skip integer\n---@param limit integer\n---@return table items\n---@return Error? err\nfunction M.list_archive(args, skip, limit)\n\tlocal child = M.spawn_7z { \"l\", \"-ba\", \"-slt\", \"-sccUTF-8\", \"-xr!__MACOSX\", table.unpack(args) }\n\tif not child then\n\t\treturn {}, Err(\"Failed to start either `7zz` or `7z`. Do you have 7-zip installed?\")\n\tend\n\n\tlocal items, err = M.parse_7z_slt(child, skip, limit)\n\tchild:start_kill()\n\n\treturn items, err\nend\n\n---List items in a compressed tarball\n---@param args table\n---@param skip integer\n---@param limit integer\n---@return table items\n---@return Error? err\nfunction M.list_compressed_tar(args, skip, limit)\n\tlocal src, dst = M.spawn_7z_piped(\n\t\t{ \"x\", \"-so\", table.unpack(args) },\n\t\t{ \"l\", \"-ba\", \"-slt\", \"-ttar\", \"-sccUTF-8\", \"-xr!__MACOSX\", \"-si\" }\n\t)\n\tif not dst then\n\t\treturn {}, Err(\"Failed to start either `7zz` or `7z`. Do you have 7-zip installed?\")\n\tend\n\n\tlocal items, err = M.parse_7z_slt(dst, skip, limit)\n\tsrc:start_kill()\n\tdst:start_kill()\n\n\treturn items, err\nend\n\n---@param path Path\n---@return table?\nfunction M.list_if_only_one(path)\n\t-- For certain compressed tarballs (e.g. .tar.xz),\n\t-- 7-zip doesn't print a .tar item if -slt is specified, so we are not doing that here\n\tlocal child = M.spawn_7z { \"l\", \"-ba\", \"-sccUTF-8\", \"-p\", tostring(path) }\n\tif not child then\n\t\treturn\n\tend\n\n\tlocal items = {}\n\twhile #items < 2 do\n\t\tlocal next, event = child:read_line()\n\t\tif event == 0 then\n\t\t\tlocal attr = next:sub(21, 25)\n\t\t\tlocal size = next:sub(27, 38):gsub(\"^%s+\", \"\")\n\t\t\tlocal packed_size = next:sub(40, 51):gsub(\"^%s+\", \"\")\n\t\t\tlocal path = next:sub(54):gsub(\"\\r?\\n$\", \"\")\n\t\t\tif path ~= \"\" then\n\t\t\t\titems[#items + 1] = M.make_item { path = path, size = size, packed_size = packed_size, attr = attr }\n\t\t\tend\n\t\telseif event ~= 1 then\n\t\t\tbreak\n\t\tend\n\tend\n\n\tchild:start_kill()\n\tif #items == 1 then\n\t\treturn items[1]\n\tend\nend\n\n---List metadata of an archive\n---@param args table\n---@return string? type\n---@return integer code\n---  0: success\n---  1: failed to spawn\n---  2: wrong password\n---  3: partial success\nfunction M.list_meta(args)\n\tlocal child = M.spawn_7z { \"l\", \"-slt\", \"-sccUTF-8\", table.unpack(args) }\n\tif not child then\n\t\treturn nil, 1\n\tend\n\n\tlocal i, head = 0, \"\"\n\tlocal typ, code = nil, 0\n\twhile i < 500 do\n\t\ti = i + 1\n\n\t\tlocal next, event = child:read_line()\n\t\tif event == 1 and M.is_encrypted(next) then\n\t\t\tcode = 2\n\t\t\tbreak\n\t\telseif event == 1 then\n\t\t\tcode = 3\n\t\telseif event == 0 then\n\t\t\thead = head .. next\n\t\telse\n\t\t\tbreak\n\t\tend\n\n\t\ttyp = head:gmatch(\"--[\\r\\n]+Path = .-[\\r\\n]+Type = (.-)[\\r\\n]+\")()\n\t\tif typ then\n\t\t\tbreak\n\t\tend\n\tend\n\n\tchild:start_kill()\n\treturn typ ~= \"\" and typ or nil, code\nend\n\nfunction M.is_encrypted(s) return s:find(\" Wrong password\", 1, true) end\n\nfunction M.is_tar(path) return M.list_meta { \"-p\", tostring(path) } == \"tar\" end\n\nfunction M.make_item(t)\n\tt = t or {}\n\tt.path = type(t.path or \"\") == \"string\" and Path.os(t.path or \"\") or t.path\n\tt.size = tonumber(t.size) or 0\n\tt.packed_size = tonumber(t.packed_size) or 0\n\tt.attr = t.attr or \"\"\n\tt.folder = t.folder or \"\"\n\tt.depth = tonumber(t.depth) or 0\n\treturn t\nend\n\n---@param file File\n---@param item table\n---@return boolean\nfunction M.should_decompress_tar(file, item)\n\tif (item.path.ext or \"\"):lower() ~= \"tar\" then\n\t\treturn false\n\telseif item.packed_size > 0 then\n\t\treturn item.packed_size <= 1024 * 1024 * 1024\n\telse\n\t\treturn file.cha.len <= 100 * 1024 * 1024\n\tend\nend\n\n-- Parse the output of a \"7z l -slt\" command.\n-- The caller is responsible for killing the child process right after the execution of this function\n---@param child Child\n---@param skip integer\n---@param limit integer\n---@return table items\n---@return Error? err\nfunction M.parse_7z_slt(child, skip, limit)\n\tlocal items, tops, parents, err = { M.make_item() }, {}, {}, nil\n\tlocal key, value, empty, stderr = \"\", \"\", Path.os(\"\"), {}\n\trepeat\n\t\tlocal next, event = child:read_line()\n\t\tif event == 1 and M.is_encrypted(next) then\n\t\t\terr = Err(\"File list of the archive is encrypted\")\n\t\t\tbreak\n\t\telseif event == 1 then\n\t\t\tstderr[#stderr + 1] = next\n\t\t\tgoto continue\n\t\telseif event ~= 0 then\n\t\t\tbreak\n\t\tend\n\n\t\tif next == \"\\n\" or next == \"\\r\\n\" then\n\t\t\tif items[#items].path ~= empty then\n\t\t\t\tM.treelize(items, tops, parents)\n\t\t\t\tM.pop_dup_dir(items, parents, false)\n\t\t\t\titems[#items + 1] = M.make_item()\n\t\t\tend\n\t\t\tgoto continue\n\t\tend\n\n\t\tkey, value = next:match(\"^(%u[%a ]+) = (.-)[\\r\\n]+\")\n\t\tif key == \"Path\" then\n\t\t\titems[#items].path = Path.os(value)\n\t\telseif key == \"Size\" then\n\t\t\titems[#items].size = tonumber(value) or 0\n\t\telseif key == \"Packed Size\" then\n\t\t\titems[#items].packed_size = tonumber(value) or 0\n\t\telseif key == \"Attributes\" then\n\t\t\titems[#items].attr = value\n\t\telseif key == \"Folder\" then\n\t\t\titems[#items].folder = value\n\t\tend\n\n\t\t::continue::\n\tuntil #items - 1 > skip + limit\n\n\tif items[#items].path == empty then\n\t\titems[#items] = nil\n\telse\n\t\tM.treelize(items, tops, parents)\n\tend\n\n\tM.pop_dup_dir(items, parents, #items <= skip + limit)\n\tif #items > skip + limit then\n\t\titems[#items] = nil\n\tend\n\n\tif #stderr ~= 0 then\n\t\terr = Err(\"7-zip errored out while listing items, stderr: %s\", table.concat(stderr, \"\\n\"))\n\tend\n\treturn items, err\nend\n\n---Convert a flat list of items into a tree structure\n---@param items table\n---@param tops Path[]\n---@param parents table<string, boolean>\nfunction M.treelize(items, tops, parents)\n\tlocal f = table.remove(items)\n\twhile #tops > 0 and not f.path:starts_with(tops[#tops]) do\n\t\ttops[#tops] = nil\n\tend\n\n\tlocal buf, it = {}, f.path.parent\n\twhile it and it ~= tops[#tops] do\n\t\tbuf[#buf + 1], it = it, it.parent\n\tend\n\tfor i = #buf, 1, -1 do\n\t\titems[#items + 1] = M.make_item { path = buf[i], depth = #tops, is_dir = true }\n\t\ttops[#tops + 1] = buf[i]\n\t\tM.pop_dup_dir(items, parents, false)\n\tend\n\n\tf.depth = #tops\n\tf.is_dir = f.folder == \"+\" or f.attr:sub(1, 1) == \"D\"\n\n\tif not f.is_dir then\n\t\titems[#items + 1] = f\n\telseif f.path ~= tops[#tops] then\n\t\titems[#items + 1], tops[#tops + 1] = f, f.path\n\tend\nend\n\n---@param items table\n---@param parents table<string, boolean>\n---@param eof boolean\nfunction M.pop_dup_dir(items, parents, eof)\n\tlocal n, i = #items, eof and #items or #items - 1\n\tif not items[i] or not items[i].is_dir then\n\t\treturn\n\tend\n\n\tlocal p = tostring(items[i].path)\n\tif not parents[p] then\n\t\tparents[p] = true\n\telseif eof then\n\t\titems[n] = nil\n\telseif not items[n].path:starts_with(items[i].path) then\n\t\titems[i], items[n] = items[n], nil\n\tend\nend\n\nreturn M\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/code.lua",
    "content": "local M = {}\n\nfunction M:peek(job)\n\tlocal err, bound = ya.preview_code(job)\n\tif bound then\n\t\tya.emit(\"peek\", { bound, only_if = job.file.url, upper_bound = true })\n\telseif err and not err:find(\"cancelled\", 1, true) then\n\t\trequire(\"empty\").msg(job, err)\n\tend\nend\n\nfunction M:seek(job)\n\tlocal h = cx.active.current.hovered\n\tif not h or h.url ~= job.file.url then\n\t\treturn\n\tend\n\n\tlocal step = math.floor(job.units * job.area.h / 10)\n\tstep = step == 0 and ya.clamp(-1, job.units, 1) or step\n\n\tya.emit(\"peek\", {\n\t\tmath.max(0, cx.active.preview.skip + step),\n\t\tonly_if = job.file.url,\n\t})\nend\n\nfunction M:spot(job) require(\"file\"):spot(job) end\n\nreturn M\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/dds.lua",
    "content": "local M = {}\n\nfunction M.parse_args(t, i)\n\tlocal j, args = 1, {}\n\tfor i = i, #t do\n\t\tlocal word = string.char(table.unpack(t[i]))\n\t\tlocal key = word:match(\"^%-%-([^=]+)\")\n\t\tif not key then\n\t\t\tj, args[j] = j + 1, word\n\t\telseif #key + 2 == #word then\n\t\t\targs[key] = true\n\t\telse\n\t\t\targs[key] = word:sub(#key + 4)\n\t\tend\n\tend\n\treturn args\nend\n\nfunction M:setup()\n\tps.sub_remote(\"dds-emit\", function(t) ya.emit(t[1], M.parse_args(t, 2)) end)\n\n\tps.sub_remote(\"dds-exec\", function(t)\n\t\tya.async(function()\n\t\t\tlocal ok, value = pcall(ya.exec, t[2], M.parse_args(t, 3))\n\t\t\tps.pub_to(t[1], \"dds-exec-result\", ok and {\n\t\t\t\tok = true,\n\t\t\t\tvalue = value,\n\t\t\t} or {\n\t\t\t\tok = false,\n\t\t\t\terror = tostring(value),\n\t\t\t})\n\t\tend)\n\tend)\nend\n\nreturn M\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/empty.lua",
    "content": "local M = {}\n\nfunction M.msg(job, s) ya.preview_widget(job, ui.Text(ui.Line(s):reverse()):area(job.area):wrap(ui.Wrap.YES)) end\n\nfunction M:peek(job)\n\tif not job.file.url:starts_with(\"/proc/\") then\n\t\treturn self.msg(job, \"Empty file\")\n\tend\n\n\tlocal fd, err = fs.access():read(true):open(job.file.url)\n\tif not fd then\n\t\treturn self.msg(job, \"Failed to open file: \" .. err)\n\tend\n\n\tlocal lines, err = M.read_up_to(fd, job.skip, job.area.h)\n\tya.drop(fd)\n\n\tif not lines then\n\t\tself.msg(job, tostring(err))\n\telseif lines.n == 0 then\n\t\tself.msg(job, \"Empty file\")\n\telseif job.skip > 0 and lines.n < job.skip + job.area.h then\n\t\tya.emit(\"peek\", { math.max(0, lines.n - job.area.h), only_if = job.file.url, upper_bound = true })\n\telse\n\t\tya.preview_widget(job, ui.Text(lines):area(job.area))\n\tend\nend\n\nfunction M:seek(job) require(\"code\"):seek(job) end\n\n--- @param fd Fd\n--- @param skip integer\n--- @param limit integer\n--- @return { [integer]: string, n: integer }?\n--- @return Error?\nfunction M.read_up_to(fd, skip, limit)\n\tlocal seen, lines = 0, { n = 0 }\n\twhile true do\n\t\tlocal chunk, err = fd:read(4096)\n\t\tif not chunk then\n\t\t\treturn nil, Err(\"Failed to read file: %s\", err)\n\t\telseif chunk == \"\" then\n\t\t\tbreak\n\t\tend\n\n\t\tseen = seen + #chunk\n\t\tif seen > 5242880 then\n\t\t\treturn nil, Err(\"File too large\")\n\t\tend\n\n\t\tfor line in chunk:gmatch(\"[^\\n]*\\n?\") do\n\t\t\tlines.n = lines.n + 1\n\t\t\tif lines.n > skip + limit then\n\t\t\t\tbreak\n\t\t\telseif lines.n > skip then\n\t\t\t\tlines[#lines + 1] = line\n\t\t\tend\n\t\tend\n\tend\n\treturn lines\nend\n\nreturn M\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/extract.lua",
    "content": "local function fail(s, ...) error(string.format(s, ...)) end\n\nlocal M = {}\n\nfunction M:setup()\n\tps.sub_remote(\"extract\", function(args)\n\t\tlocal noisy = #args == 1 and ' \"\" --noisy' or ' \"\"'\n\t\tfor _, arg in ipairs(args) do\n\t\t\tya.emit(\"plugin\", { self._id, ya.quote(arg, true) .. noisy })\n\t\tend\n\tend)\nend\n\nfunction M:entry(job)\n\tlocal from = job.args[1] and Url(job.args[1])\n\tlocal to = job.args[2] ~= \"\" and Url(job.args[2]) or nil\n\tif not from then\n\t\tfail(\"No URL provided\")\n\tend\n\n\tlocal pwd = \"\"\n\twhile true do\n\t\tif not M:try_with(from, pwd, to) then\n\t\t\tbreak\n\t\telseif not job.args.noisy then\n\t\t\tfail(\"'%s' is password-protected, please extract it individually and enter the password\", from)\n\t\tend\n\n\t\tlocal value, event = ya.input {\n\t\t\tpos = { \"top-center\", y = 2, w = 50 },\n\t\t\ttitle = string.format('Password for \"%s\":', from.name),\n\t\t\tobscure = true,\n\t\t}\n\t\tif event == 1 then\n\t\t\tpwd = value\n\t\telse\n\t\t\tbreak\n\t\tend\n\tend\nend\n\nfunction M:try_with(from, pwd, to)\n\tto = to or from.parent\n\tif not to then\n\t\tfail(\"Invalid URL '%s'\", from)\n\tend\n\n\tlocal tmp = fs.unique(\"dir\", to:join(self.tmp_name(from)))\n\tif not tmp then\n\t\tfail(\"Failed to determine a temporary directory for %s\", from)\n\tend\n\n\tlocal archive = require(\"archive\")\n\tlocal child, err = archive.spawn_7z { \"x\", \"-aou\", \"-sccUTF-8\", \"-p\" .. pwd, \"-o\" .. tostring(tmp), tostring(from) }\n\tif not child then\n\t\tfail(\"Failed to start either `7zz` or `7z`, error: \" .. err)\n\tend\n\n\tlocal output, err = child:wait_with_output()\n\tif output and output.status.code == 2 and archive.is_encrypted(output.stderr) then\n\t\tfs.remove(\"dir_all\", tmp)\n\t\treturn true -- Need to retry\n\tend\n\n\tself:tidy(from, to, tmp)\n\tif not output then\n\t\tfail(\"7zip failed to output when extracting '%s', error: %s\", from, err)\n\telseif output.status.code ~= 0 then\n\t\tfail(\"7zip exited with error code %s when extracting '%s':\\n%s\", output.status.code, from, output.stderr)\n\tend\nend\n\nfunction M:tidy(from, to, tmp)\n\tlocal outs = fs.read_dir(tmp, { limit = 2 })\n\tif not outs then\n\t\tfail(\"Failed to read the temporary directory '%s' when extracting '%s'\", tmp, from)\n\telseif #outs == 0 then\n\t\tfs.remove(\"dir\", tmp)\n\t\tfail(\"No files extracted from '%s'\", from)\n\tend\n\n\tlocal only = #outs == 1 and outs[1]\n\tif only and not only.cha.is_dir and require(\"archive\").is_tar(only.url) then\n\t\tself:entry { args = { tostring(only.url), tostring(to) } }\n\t\tfs.remove(\"file\", only.url)\n\t\tfs.remove(\"dir\", tmp)\n\t\treturn\n\tend\n\n\tlocal target = to:join(only and only.name or self.trim_ext(from.name))\n\ttarget = fs.unique(only and not only.cha.is_dir and \"file\" or \"dir\", target)\n\tif not target then\n\t\tfail(\"Failed to determine a target for '%s'\", from)\n\tend\n\n\tif only and not fs.rename(only.url, target) then\n\t\tfail('Failed to move \"%s\" to \"%s\"', only.url, target)\n\telseif not only and not fs.rename(tmp, target) then\n\t\tfail('Failed to move \"%s\" to \"%s\"', tmp, target)\n\tend\n\tfs.remove(\"dir\", tmp)\nend\n\nfunction M.tmp_name(url) return \".tmp_\" .. ya.hash(string.format(\"extract//%s//%.10f\", url, ya.time())) end\n\nfunction M.trim_ext(name)\n\t-- stylua: ignore\n\tlocal exts = { [\"7z\"] = true, apk = true, bz2 = true, bzip2 = true, cbr = true, cbz = true, exe = true, gz = true, gzip = true, iso = true, jar = true, rar = true, tar = true, tgz = true, xz = true, zip = true, zst = true }\n\n\twhile true do\n\t\tlocal s = name:gsub(\"%.([a-zA-Z0-9]+)$\", function(s) return (exts[s] or exts[s:lower()]) and \"\" end)\n\t\tif s == name or s == \"\" then\n\t\t\tbreak\n\t\telse\n\t\t\tname = s\n\t\tend\n\tend\n\treturn name\nend\n\nreturn M\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/file.lua",
    "content": "local M = {}\n\nfunction M:peek(job)\n\tlocal program = os.getenv(\"YAZI_FILE_ONE\") or \"file\"\n\tlocal path = tostring(job.file.path)\n\tlocal output, err = Command(program):arg({ \"-bL\", \"--\", path }):output()\n\n\tlocal text\n\tif output then\n\t\ttext = ui.Text.parse(\"----- File Type Classification -----\\n\\n\" .. output.stdout)\n\telse\n\t\ttext = ui.Text(string.format(\"Failed to start `%s`, error: %s\", program, err))\n\tend\n\n\tya.preview_widget(job, text:area(job.area):wrap(ui.Wrap.YES))\nend\n\nfunction M:seek() end\n\nfunction M:spot(job)\n\tya.spot_table(\n\t\tjob,\n\t\tui.Table(self:spot_base(job))\n\t\t\t:area(ui.Pos { \"center\", w = 60, h = 20 })\n\t\t\t:row(1)\n\t\t\t:col(1)\n\t\t\t:col_style(th.spot.tbl_col)\n\t\t\t:cell_style(th.spot.tbl_cell)\n\t\t\t:widths { ui.Constraint.Length(14), ui.Constraint.Fill(1) }\n\t)\nend\n\nfunction M:spot_base(job)\n\tlocal cha = job.file.cha\n\tlocal spotter = rt.plugin.spotter(job.file, job.mime)\n\tlocal previewer = rt.plugin.previewer(job.file, job.mime)\n\tlocal fetchers = rt.plugin.fetchers(job.file, job.mime)\n\tlocal preloaders = rt.plugin.preloaders(job.file, job.mime)\n\n\tfor i, v in ipairs(fetchers) do\n\t\tfetchers[i] = v.cmd\n\tend\n\tfetchers = #fetchers ~= 0 and fetchers or { \"-\" }\n\n\tfor i, v in ipairs(preloaders) do\n\t\tpreloaders[i] = v.cmd\n\tend\n\tpreloaders = #preloaders ~= 0 and preloaders or { \"-\" }\n\n\treturn {\n\t\tui.Row({ \"Base\" }):style(ui.Style():fg(\"green\")),\n\t\tui.Row { \"  Created:\", cha.btime and os.date(\"%Y-%m-%d %H:%M:%S\", math.floor(cha.btime)) or \"-\" },\n\t\tui.Row { \"  Modified:\", cha.mtime and os.date(\"%Y-%m-%d %H:%M:%S\", math.floor(cha.mtime)) or \"-\" },\n\t\tui.Row { \"  Mimetype:\", job.mime },\n\t\tui.Row {},\n\n\t\tui.Row({ \"Plugins\" }):style(ui.Style():fg(\"green\")),\n\t\tui.Row { \"  Spotter:\", spotter and spotter.cmd or \"-\" },\n\t\tui.Row { \"  Previewer:\", previewer and previewer.cmd or \"-\" },\n\t\tui.Row({ \"  Fetchers:\", fetchers }):height(#fetchers),\n\t\tui.Row({ \"  Preloaders:\", preloaders }):height(#preloaders),\n\t}\nend\n\nreturn M\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/folder.lua",
    "content": "--- @sync peek\n\nlocal M = {}\n\nfunction M:peek(job)\n\tlocal folder = cx.active.preview.folder\n\tif not folder then\n\t\treturn ya.preview_widget(job, ui.Line(\"Loading...\"):area(job.area):align(ui.Align.CENTER))\n\telseif folder.cwd ~= job.file.url then\n\t\treturn\n\tend\n\n\tlocal bound = math.max(0, #folder.files - job.area.h)\n\tif job.skip > bound then\n\t\treturn ya.emit(\"peek\", { bound, only_if = job.file.url, upper_bound = true })\n\tend\n\n\tif #folder.files == 0 then\n\t\tlocal done, err = folder.stage()\n\t\tlocal s = not done and \"Loading...\" or not err and \"No items\" or string.format(\"Error: %s\", err)\n\t\treturn ya.preview_widget(job, ui.Text(s):area(job.area):align(ui.Align.CENTER):wrap(ui.Wrap.YES))\n\tend\n\n\tlocal left, right = {}, {}\n\tfor _, f in ipairs(folder.window) do\n\t\tlocal entity = Entity:new(f)\n\t\tleft[#left + 1], right[#right + 1] = entity:redraw(), Linemode:new(f):redraw()\n\n\t\tlocal max = math.max(0, job.area.w - right[#right]:width())\n\t\tleft[#left]:truncate { max = max, ellipsis = entity:ellipsis(max) }\n\tend\n\n\tya.preview_widget(job, {\n\t\tui.List(left):area(job.area),\n\t\tui.Text(right):area(job.area):align(ui.Align.RIGHT),\n\t\ttable.unpack(Marker:new(job.area, folder):redraw()),\n\t})\nend\n\nfunction M:seek(job)\n\tlocal folder = cx.active.preview.folder\n\tif folder and folder.cwd == job.file.url then\n\t\tlocal step = math.floor(job.units * job.area.h / 10)\n\t\tlocal bound = math.max(0, #folder.files - job.area.h)\n\t\tya.emit(\"peek\", {\n\t\t\tya.clamp(0, cx.active.preview.skip + step, bound),\n\t\t\tonly_if = job.file.url,\n\t\t})\n\tend\nend\n\nfunction M:spot(job)\n\tlocal i, url = 0, job.file.url\n\tfor rows in self:spot_base(job) do\n\t\ti, rows[#rows + 1] = i + 1, ui.Row {}\n\t\tya.spot_table(\n\t\t\tjob,\n\t\t\tui.Table(ya.list_merge(rows, require(\"file\"):spot_base(job)))\n\t\t\t\t:area(ui.Pos { \"center\", w = 60, h = 20 })\n\t\t\t\t:row(i == 1 and 1 or nil)\n\t\t\t\t:col(1)\n\t\t\t\t:col_style(th.spot.tbl_col)\n\t\t\t\t:cell_style(th.spot.tbl_cell)\n\t\t\t\t:widths { ui.Constraint.Length(14), ui.Constraint.Fill(1) }\n\t\t)\n\tend\n\tif self.size then\n\t\tya.emit(\"update_files\", { op = fs.op(\"size\", { url = url.parent, sizes = { [url.urn] = self.size } }) })\n\tend\nend\n\nfunction M:spot_base(job)\n\tlocal function yield(s)\n\t\tcoroutine.yield {\n\t\t\tui.Row({ \"Folder\" }):style(ui.Style():fg(\"green\")),\n\t\t\tui.Row { \"  Size:\", s },\n\t\t}\n\tend\n\n\tself.size = nil\n\treturn ya.co(function()\n\t\tyield(\"0B (?)\")\n\n\t\tlocal it, size, last = fs.calc_size(job.file.url), 0, 0\n\t\tif not it then\n\t\t\treturn yield(\"Error\")\n\t\tend\n\n\t\twhile true do\n\t\t\tlocal next, now = it:recv(), ya.time()\n\t\t\tif not next then\n\t\t\t\tbreak\n\t\t\telseif now >= last + 0.1 then\n\t\t\t\tlast, size = now, size + next\n\t\t\t\tyield(ya.readable_size(size) .. \" (?)\")\n\t\t\telse\n\t\t\t\tsize = size + next\n\t\t\tend\n\t\tend\n\n\t\tself.size = size\n\t\tyield(ya.readable_size(size))\n\tend)\nend\n\nreturn M\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/font.lua",
    "content": "local TEXT = \"ABCDEFGHIJKLM\\nNOPQRSTUVWXYZ\\nabcdefghijklm\\nnopqrstuvwxyz\\n1234567890\\n!$&*()[]{}\"\n\nlocal M = {}\n\nfunction M:peek(job)\n\tlocal start, cache = os.clock(), ya.file_cache(job)\n\tif not cache then\n\t\treturn\n\tend\n\n\tlocal ok, err = self:preload(job)\n\tif not ok or err then\n\t\treturn ya.preview_widget(job, err)\n\tend\n\n\tya.sleep(math.max(0, rt.preview.image_delay / 1000 + start - os.clock()))\n\n\tlocal _, err = ya.image_show(cache, job.area)\n\tya.preview_widget(job, err)\nend\n\nfunction M:seek() end\n\nfunction M:preload(job)\n\tlocal cache = ya.file_cache(job)\n\tif not cache or fs.cha(cache) then\n\t\treturn true\n\tend\n\n\tlocal status, err = Command(\"magick\"):arg({\n\t\t\"-size\",\n\t\t\"800x560\",\n\t\t\"-gravity\",\n\t\t\"center\",\n\t\t\"-font\",\n\t\ttostring(job.file.path):gsub(\"\\\\\", \"\\\\\\\\\"),\n\t\t\"-pointsize\",\n\t\t64,\n\t\t\"xc:white\",\n\t\t\"-fill\",\n\t\t\"black\",\n\t\t\"-annotate\",\n\t\t\"+0+0\",\n\t\tTEXT,\n\t\t\"JPG:\" .. tostring(cache),\n\t}):status()\n\n\tif not status then\n\t\treturn true, Err(\"Failed to start `magick`, error: %s\", err)\n\telseif not status.success then\n\t\treturn false, Err(\"`magick` exited with error code: %s\", status.code)\n\telse\n\t\treturn true\n\tend\nend\n\nreturn M\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/fzf.lua",
    "content": "local M = {}\n\nlocal state = ya.sync(function()\n\tlocal selected = {}\n\tfor _, url in pairs(cx.active.selected) do\n\t\tselected[#selected + 1] = url\n\tend\n\treturn cx.active.current.cwd, selected\nend)\n\nfunction M:entry()\n\tya.emit(\"escape\", { visual = true })\n\n\tlocal cwd, selected = state()\n\tif cwd.scheme.is_virtual then\n\t\treturn ya.notify { title = \"Fzf\", content = \"Not supported under virtual filesystems\", timeout = 5, level = \"warn\" }\n\tend\n\n\tlocal permit = ui.hide()\n\tlocal output, err = M.run_with(cwd, selected)\n\n\tpermit:drop()\n\tif not output then\n\t\treturn ya.notify { title = \"Fzf\", content = tostring(err), timeout = 5, level = \"error\" }\n\tend\n\n\tlocal urls = M.split_urls(cwd, output)\n\tif #urls == 1 then\n\t\tlocal cha = #selected == 0 and fs.cha(urls[1])\n\t\tya.emit(cha and cha.is_dir and \"cd\" or \"reveal\", { urls[1], raw = true })\n\telseif #urls > 1 then\n\t\turls.state = #selected > 0 and \"off\" or \"on\"\n\t\tya.emit(\"toggle_all\", urls)\n\tend\nend\n\n---@param cwd Url\n---@param selected Url[]\n---@return string?, Error?\nfunction M.run_with(cwd, selected)\n\tlocal child, err = Command(\"fzf\")\n\t\t:arg(\"-m\")\n\t\t:cwd(tostring(cwd))\n\t\t:stdin(#selected > 0 and Command.PIPED or Command.INHERIT)\n\t\t:stdout(Command.PIPED)\n\t\t:spawn()\n\n\tif not child then\n\t\treturn nil, Err(\"Failed to start `fzf`, error: %s\", err)\n\tend\n\n\tfor _, u in ipairs(selected) do\n\t\tchild:write_all(string.format(\"%s\\n\", u))\n\tend\n\tif #selected > 0 then\n\t\tchild:flush()\n\tend\n\n\tlocal output, err = child:wait_with_output()\n\tif not output then\n\t\treturn nil, Err(\"Cannot read `fzf` output, error: %s\", err)\n\telseif not output.status.success and output.status.code ~= 130 then\n\t\treturn nil, Err(\"`fzf` exited with error code %s\", output.status.code)\n\tend\n\treturn output.stdout, nil\nend\n\nfunction M.split_urls(cwd, output)\n\tlocal t = {}\n\tfor line in output:gmatch(\"[^\\r\\n]+\") do\n\t\tlocal u = Url(line)\n\t\tif u.is_absolute then\n\t\t\tt[#t + 1] = u\n\t\telse\n\t\t\tt[#t + 1] = cwd:join(u)\n\t\tend\n\tend\n\treturn t\nend\n\nreturn M\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/image.lua",
    "content": "local M = {}\n\nfunction M:peek(job)\n\tlocal start, url = os.clock(), ya.file_cache(job)\n\tif not url or not fs.cha(url) then\n\t\turl = job.file.url\n\tend\n\n\tya.sleep(math.max(0, rt.preview.image_delay / 1000 + start - os.clock()))\n\n\tlocal _, err = ya.image_show(url, job.area)\n\tya.preview_widget(job, err)\nend\n\nfunction M:seek() end\n\nfunction M:preload(job)\n\tlocal cache = ya.file_cache(job)\n\tif not cache or fs.cha(cache) then\n\t\treturn true\n\tend\n\n\treturn ya.image_precache(job.file.url, cache)\nend\n\nfunction M:spot(job)\n\tlocal rows = self:spot_base(job)\n\trows[#rows + 1] = ui.Row {}\n\n\tya.spot_table(\n\t\tjob,\n\t\tui.Table(ya.list_merge(rows, require(\"file\"):spot_base(job)))\n\t\t\t:area(ui.Pos { \"center\", w = 60, h = 20 })\n\t\t\t:row(job.skip)\n\t\t\t:row(1)\n\t\t\t:col(1)\n\t\t\t:col_style(th.spot.tbl_col)\n\t\t\t:cell_style(th.spot.tbl_cell)\n\t\t\t:widths { ui.Constraint.Length(14), ui.Constraint.Fill(1) }\n\t)\nend\n\nfunction M:spot_base(job)\n\tlocal info = ya.image_info(job.file.url)\n\tif not info then\n\t\treturn {}\n\tend\n\n\treturn {\n\t\tui.Row({ \"Image\" }):style(ui.Style():fg(\"green\")),\n\t\tui.Row { \"  Format:\", tostring(info.format) },\n\t\tui.Row { \"  Size:\", string.format(\"%dx%d\", info.w, info.h) },\n\t\tui.Row { \"  Color:\", tostring(info.color) },\n\t}\nend\n\nreturn M\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/init.lua",
    "content": ""
  },
  {
    "path": "yazi-plugin/preset/plugins/json.lua",
    "content": "local M = {}\n\nfunction M:peek(job)\n\tlocal child = Command(\"jq\")\n\t\t:arg({ \"-b\", \"-C\", \"--tab\", \".\", tostring(job.file.path) })\n\t\t:stdout(Command.PIPED)\n\t\t:stderr(Command.PIPED)\n\t\t:spawn()\n\n\tif not child then\n\t\treturn require(\"code\"):peek(job)\n\tend\n\n\tlocal wrap = rt.preview.wrap\n\tlocal limit = job.area.h\n\tlocal i, lines = 0, \"\"\n\trepeat\n\t\tlocal next, event = child:read_line()\n\t\tif event == 1 then\n\t\t\treturn require(\"code\"):peek(job)\n\t\telseif event ~= 0 then\n\t\t\tbreak\n\t\tend\n\n\t\ti = i + ui.height(next, { width = job.area.w, ansi = true, wrap = wrap })\n\t\tif i > job.skip then\n\t\t\tlines = lines .. next\n\t\tend\n\tuntil i >= job.skip + limit\n\n\tchild:start_kill()\n\tif job.skip > 0 and i < job.skip + limit then\n\t\tya.emit(\"peek\", { math.max(0, i - limit), only_if = job.file.url, upper_bound = true })\n\telse\n\t\tlines = lines:gsub(\"\\t\", string.rep(\" \", rt.preview.tab_size))\n\t\tya.preview_widget(job, ui.Text.parse(lines):area(job.area):wrap(wrap))\n\tend\nend\n\nfunction M:seek(job) require(\"code\"):seek(job) end\n\nreturn M\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/magick.lua",
    "content": "local M = {}\n\nfunction M:peek(job)\n\tlocal start, cache = os.clock(), ya.file_cache(job)\n\tif not cache then\n\t\treturn\n\tend\n\n\tlocal ok, err = self:preload(job)\n\tif not ok or err then\n\t\treturn ya.preview_widget(job, err)\n\tend\n\n\tya.sleep(math.max(0, rt.preview.image_delay / 1000 + start - os.clock()))\n\n\tlocal _, err = ya.image_show(cache, job.area)\n\tya.preview_widget(job, err)\nend\n\nfunction M:seek() end\n\nfunction M:preload(job)\n\tlocal cache = ya.file_cache(job)\n\tif not cache or fs.cha(cache) then\n\t\treturn true\n\tend\n\n\tlocal cmd = M.with_limit():arg(tostring(job.file.path))\n\tif job.args.flatten then\n\t\tcmd:arg(\"-flatten\")\n\tend\n\tcmd:arg { \"-auto-orient\", \"-strip\" }\n\n\tlocal size = string.format(\"%dx%d>\", rt.preview.max_width, rt.preview.max_height)\n\tif rt.preview.image_filter == \"nearest\" then\n\t\tcmd:arg { \"-sample\", size }\n\telseif rt.preview.image_filter == \"catmull-rom\" then\n\t\tcmd:arg { \"-filter\", \"catrom\", \"-thumbnail\", size }\n\telseif rt.preview.image_filter == \"lanczos3\" then\n\t\tcmd:arg { \"-filter\", \"lanczos\", \"-thumbnail\", size }\n\telseif rt.preview.image_filter == \"gaussian\" then\n\t\tcmd:arg { \"-filter\", \"gaussian\", \"-thumbnail\", size }\n\telse\n\t\tcmd:arg { \"-filter\", \"triangle\", \"-thumbnail\", size }\n\tend\n\n\tcmd:arg { \"-quality\", rt.preview.image_quality }\n\tif job.args.bg then\n\t\tcmd:arg { \"-background\", job.args.bg, \"-alpha\", \"remove\" }\n\tend\n\n\tlocal status, err = cmd:arg(string.format(\"JPG:%s\", cache)):status()\n\tif not status then\n\t\treturn true, Err(\"Failed to start `magick`, error: %s\", err)\n\telseif not status.success then\n\t\treturn false, Err(\"`magick` exited with error code: %s\", status.code)\n\telse\n\t\treturn true\n\tend\nend\n\nfunction M:spot(job) require(\"file\"):spot(job) end\n\nfunction M.with_limit()\n\tlocal cmd = Command(\"magick\"):arg { \"-limit\", \"thread\", 1 }\n\tif rt.tasks.image_alloc > 0 then\n\t\tcmd:arg { \"-limit\", \"memory\", rt.tasks.image_alloc, \"-limit\", \"disk\", \"1MiB\" }\n\tend\n\tif rt.tasks.image_bound[1] > 0 then\n\t\tcmd:arg { \"-limit\", \"width\", rt.tasks.image_bound[1] }\n\tend\n\tif rt.tasks.image_bound[2] > 0 then\n\t\tcmd:arg { \"-limit\", \"height\", rt.tasks.image_bound[2] }\n\tend\n\treturn cmd\nend\n\nreturn M\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/mime-dir.lua",
    "content": "local function fetch(_, job)\n\tlocal updates = {}\n\tfor _, file in ipairs(job.files) do\n\t\tif file.url.scheme.is_virtual then\n\t\t\tupdates[file.url] = \"folder/remote\"\n\t\telse\n\t\t\tupdates[file.url] = \"folder/local\"\n\t\tend\n\tend\n\n\tya.emit(\"update_mimes\", { updates = updates })\n\treturn true\nend\n\nreturn { fetch = fetch }\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/mime-local.lua",
    "content": "-- stylua: ignore\nlocal TYPE_PATS = { \"text\", \"image\", \"video\", \"application\", \"audio\", \"font\", \"inode\", \"message\", \"model\", \"vector\", \"biosig\", \"chemical\", \"rinex\", \"x%-epoc\" }\n\nlocal M = {}\n\nfunction M:fetch(job)\n\tlocal urls, paths = {}, {}\n\tfor i, file in ipairs(job.files) do\n\t\tif file.cache then\n\t\t\turls[i], paths[i] = tostring(file.url), tostring(file.cache)\n\t\telse\n\t\t\tpaths[i] = tostring(file.path)\n\t\tend\n\tend\n\n\tlocal child, err = M.spawn_file1(paths)\n\tif not child then\n\t\tM.placeholder(err, urls, paths)\n\t\treturn true, err\n\tend\n\n\tlocal updates, last = {}, ya.time()\n\tlocal flush = function(force)\n\t\tif not force and ya.time() - last < 0.3 then\n\t\t\treturn\n\t\tend\n\t\tif next(updates) then\n\t\t\tya.emit(\"update_mimes\", { updates = updates })\n\t\t\tupdates, last = {}, ya.time()\n\t\tend\n\tend\n\n\tlocal i, state, match, ignore = 1, {}, nil, nil\n\trepeat\n\t\tlocal line, event = child:read_line_with { timeout = 300 }\n\t\tif event == 3 then\n\t\t\tflush(true)\n\t\t\tgoto continue\n\t\telseif event ~= 0 then\n\t\t\tbreak\n\t\tend\n\n\t\tmatch, ignore = M.match_mimetype(line)\n\t\tif match then\n\t\t\tupdates[urls[i] or paths[i]], state[i], i = match, true, i + 1\n\t\t\tflush(false)\n\t\telseif not ignore then\n\t\t\tstate[i], i = false, i + 1\n\t\tend\n\t\t::continue::\n\tuntil i > #paths\n\n\tflush(true)\n\treturn state\nend\n\nfunction M.match_mimetype(line)\n\tfor _, pat in ipairs(TYPE_PATS) do\n\t\tlocal typ, sub = line:match(string.format(\"(%s/)([+-.a-zA-Z0-9]+)%%s+$\", pat))\n\t\tif not sub then\n\t\telseif line:find(typ .. sub, 1, true) == 1 then\n\t\t\treturn typ:gsub(\"^x%-\", \"\", 1) .. sub:gsub(\"^x%-\", \"\", 1):gsub(\"^vnd%.\", \"\", 1)\n\t\telse\n\t\t\treturn nil, true\n\t\tend\n\tend\nend\n\nfunction M.file1_bin() return os.getenv(\"YAZI_FILE_ONE\") or \"file\" end\n\nfunction M.spawn_file1(paths)\n\tlocal bin = M.file1_bin()\n\tlocal windows = ya.target_family() == \"windows\"\n\n\tlocal cmd = Command(bin):arg({ \"-bL\", \"--mime-type\" }):stdout(Command.PIPED)\n\tif windows then\n\t\tcmd:arg({ \"-f\", \"-\" }):stdin(Command.PIPED)\n\telse\n\t\tcmd:arg(\"--\"):arg(paths)\n\tend\n\n\tlocal child, err = cmd:spawn()\n\tif not child then\n\t\tlocal e = Error.fs {\n\t\t\tkind = err.kind or \"Other\",\n\t\t\tcode = err.code,\n\t\t\tmessage = string.format(\"Failed to start `%s`, error: %s\", bin, err),\n\t\t}\n\t\treturn nil, e\n\telseif windows then\n\t\tchild:write_all(table.concat(paths, \"\\n\"))\n\t\tchild:flush()\n\t\tya.drop(child:take_stdin())\n\tend\n\n\treturn child\nend\n\nfunction M.placeholder(err, urls, paths)\n\tif err.kind ~= \"NotFound\" then\n\t\treturn\n\tend\n\n\tlocal updates = {}\n\tfor i = 1, #paths do\n\t\tupdates[urls[i] or paths[i]] = \"null/file1-not-found\"\n\tend\n\n\tya.emit(\"update_mimes\", { updates = updates })\nend\n\nreturn M\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/mime-remote.lua",
    "content": "local M = {}\n\nlocal function stale_cache(file)\n\tlocal url = file.url\n\tlocal lock = url.scheme.cache:join(string.format(\"%%lock/%s\", url:hash(true)))\n\n\tlocal fd = fs.access():read(true):open(Url(lock))\n\tif not fd then\n\t\treturn true\n\tend\n\n\tlocal hash = fd:read(32)\n\tya.drop(fd)\n\treturn hash ~= file.cha:hash(true)\nend\n\nfunction M:fetch(job)\n\tlocal updates, unknown, state = {}, {}, {}\n\tfor i, file in ipairs(job.files) do\n\t\tif file.cha.is_dummy then\n\t\t\t-- Skip dummy files\n\t\telseif not file.cache then\n\t\t\tunknown[#unknown + 1] = file\n\t\telseif not fs.cha(Url(file.cache)) then\n\t\t\tupdates[file.url], state[i] = \"vfs/absent\", true\n\t\telseif stale_cache(file) then\n\t\t\tupdates[file.url], state[i] = \"vfs/stale\", true\n\t\telse\n\t\t\tunknown[#unknown + 1] = file\n\t\tend\n\tend\n\n\tif next(updates) then\n\t\tya.emit(\"update_mimes\", { updates = updates })\n\tend\n\n\tif #unknown == 0 then\n\t\treturn state\n\telse\n\t\treturn self.fallback_local(job, unknown, state)\n\tend\nend\n\nfunction M.fallback_local(job, unknown, state)\n\tlocal indices = {}\n\tfor i, f in ipairs(job.files) do\n\t\tindices[f:hash()] = i\n\tend\n\n\tlocal result = require(\"mime.local\"):fetch(ya.dict_merge(job, { files = unknown }))\n\tfor i, f in ipairs(unknown) do\n\t\tif type(result) == \"table\" then\n\t\t\tstate[indices[f:hash()]] = result[i]\n\t\telse\n\t\t\tstate[indices[f:hash()]] = result\n\t\tend\n\tend\n\treturn state\nend\n\nreturn M\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/mime.lua",
    "content": ""
  },
  {
    "path": "yazi-plugin/preset/plugins/multi.lua",
    "content": "local M = {}\n\nlocal selected = ya.sync(function()\n\tlocal urls = {}\n\tfor _, u in pairs(cx.active.selected) do\n\t\turls[#urls + 1] = u\n\tend\n\treturn urls\nend)\n\nfunction M:spot(job)\n\tlocal i = 0\n\tfor rows in self:spot_base(job, selected()) do\n\t\ti = i + 1\n\t\tya.spot_table(\n\t\t\tjob,\n\t\t\tui.Table(rows)\n\t\t\t\t:area(ui.Pos { \"center\", w = 60, h = 20 })\n\t\t\t\t:row(i == 1 and 1 or nil)\n\t\t\t\t:col(1)\n\t\t\t\t:col_style(th.spot.tbl_col)\n\t\t\t\t:cell_style(th.spot.tbl_cell)\n\t\t\t\t:widths { ui.Constraint.Length(14), ui.Constraint.Fill(1) }\n\t\t)\n\t\tself:update_sizes()\n\tend\nend\n\nfunction M:spot_base(_, selected)\n\tlocal function yield(s)\n\t\tcoroutine.yield {\n\t\t\tui.Row({ \"Multi\" }):style(ui.Style():fg(\"green\")),\n\t\t\tui.Row { \"  Size:\", s },\n\t\t\tui.Row { \"  Count:\", string.format(\"%d selected\", #selected) },\n\t\t}\n\tend\n\n\tself.sizes = {}\n\treturn ya.co(function()\n\t\tyield(\"0B (?)\")\n\n\t\tlocal sum, last = 0, 0\n\t\tfor _, url in ipairs(selected) do\n\t\t\tlocal it, size = fs.calc_size(url), 0\n\t\t\twhile it do\n\t\t\t\tlocal next, now = it:recv(), ya.time()\n\t\t\t\tif not next then\n\t\t\t\t\tself.sizes[url] = it.cha.is_dir and size or nil\n\t\t\t\t\tbreak\n\t\t\t\telseif now >= last + 0.1 then\n\t\t\t\t\tlast, size, sum = now, size + next, sum + next\n\t\t\t\t\tyield(ya.readable_size(sum) .. \" (?)\")\n\t\t\t\telse\n\t\t\t\t\tsize, sum = size + next, sum + next\n\t\t\t\tend\n\t\t\tend\n\t\tend\n\n\t\tyield(ya.readable_size(sum))\n\tend)\nend\n\nfunction M:update_sizes()\n\tlocal parents = {}\n\tfor url, size in pairs(self.sizes) do\n\t\tlocal p = url.parent\n\t\tparents[p] = parents[p] or {}\n\t\tparents[p][url.urn] = size\n\tend\n\tfor parent, sizes in pairs(parents) do\n\t\tya.emit(\"update_files\", { op = fs.op(\"size\", { url = parent, sizes = sizes }) })\n\tend\nend\n\nreturn M\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/noop.lua",
    "content": "local M = {}\n\nfunction M:peek(job) ya.preview_widget(job, {}) end\n\nfunction M:seek() end\n\nfunction M:fetch() return true end\n\nfunction M:preload() return true end\n\nfunction M:spot() end\n\nreturn M\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/null.lua",
    "content": "local M = {}\n\nfunction M:peek(job)\n\tlocal err\n\tif job.mime == \"null/file1-not-found\" then\n\t\terr = string.format(\n\t\t\t\"Cannot find `%s` to detect the file's MIME type. Make sure it's installed and restart Yazi\",\n\t\t\trequire(\"mime.local\").file1_bin()\n\t\t)\n\telse\n\t\terr = \"Unknown error occurred while detecting MIME type\"\n\tend\n\n\tlocal line = ui.Line(err):reverse()\n\tya.preview_widget(job, ui.Text(line):area(job.area):wrap(ui.Wrap.YES))\nend\n\nfunction M:seek() end\n\nfunction M:spot(job) require(\"file\"):spot(job) end\n\nreturn M\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/pdf.lua",
    "content": "local M = {}\n\nfunction M:peek(job)\n\tlocal start, cache = os.clock(), ya.file_cache(job)\n\tif not cache then\n\t\treturn\n\tend\n\n\tlocal ok, err, bound = self:preload(job)\n\tif bound and bound > 0 then\n\t\treturn ya.emit(\"peek\", { bound - 1, only_if = job.file.url, upper_bound = true })\n\telseif not ok or err then\n\t\treturn ya.preview_widget(job, err)\n\tend\n\n\tya.sleep(math.max(0, rt.preview.image_delay / 1000 + start - os.clock()))\n\n\tlocal _, err = ya.image_show(cache, job.area)\n\tya.preview_widget(job, err)\nend\n\nfunction M:seek(job)\n\tlocal h = cx.active.current.hovered\n\tif h and h.url == job.file.url then\n\t\tlocal step = ya.clamp(-1, job.units, 1)\n\t\tya.emit(\"peek\", { math.max(0, cx.active.preview.skip + step), only_if = job.file.url })\n\tend\nend\n\nfunction M:preload(job)\n\tlocal cache = ya.file_cache(job)\n\tif not cache or fs.cha(cache) then\n\t\treturn true\n\tend\n\n\t-- stylua: ignore\n\tlocal output, err = Command(\"pdftoppm\")\n\t\t:arg({\n\t\t\t\"-f\", job.skip + 1,\n\t\t\t\"-l\", job.skip + 1,\n\t\t\t\"-singlefile\",\n\t\t\t\"-jpeg\", \"-jpegopt\", \"quality=\" .. rt.preview.image_quality,\n\t\t\ttostring(job.file.path), tostring(cache),\n\t\t})\n\t\t:output()\n\n\tif not output then\n\t\treturn true, Err(\"Failed to start `pdftoppm`, error: %s\", err)\n\telseif not output.status.success then\n\t\tlocal pages = job.skip > 0 and tonumber(output.stderr:match(\"the last page %((%d+)%)\"))\n\t\treturn true, Err(\"Failed to convert PDF to image, stderr: %s\", output.stderr), pages\n\tend\n\n\treturn ya.image_precache(Url(cache .. \".jpg\"), cache)\nend\n\nreturn M\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/session.lua",
    "content": "local function setup(_, opts)\n\tif opts.sync_yanked then\n\t\tps.sub_remote(\"@yank\", function(opt) ya.emit(\"update_yanked\", { opt = opt }) end)\n\tend\nend\n\nreturn { setup = setup }\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/svg.lua",
    "content": "local M = {}\n\nfunction M:peek(job)\n\tlocal start, cache = os.clock(), ya.file_cache(job)\n\tif not cache then\n\t\treturn\n\tend\n\n\tlocal ok, err = self:preload(job)\n\tif not ok or err then\n\t\treturn ya.preview_widget(job, err)\n\tend\n\n\tya.sleep(math.max(0, rt.preview.image_delay / 1000 + start - os.clock()))\n\n\tlocal _, err = ya.image_show(cache, job.area)\n\tya.preview_widget(job, err)\nend\n\nfunction M:seek() end\n\nfunction M:preload(job)\n\tlocal cache = ya.file_cache(job)\n\tif not cache or fs.cha(cache) then\n\t\treturn true\n\tend\n\n\t-- stylua: ignore\n\tlocal cmd = Command(\"resvg\"):arg {\n\t\t\"-w\", rt.preview.max_width, \"-h\", rt.preview.max_height,\n\t\t\"--image-rendering\", \"optimizeSpeed\",\n\t}\n\tif job.args.bg then\n\t\tcmd = cmd:arg { \"--background\", job.args.bg }\n\tend\n\tif rt.tasks.image_alloc > 0 then\n\t\tcmd = cmd:memory(rt.tasks.image_alloc)\n\tend\n\n\tlocal child, err = cmd:arg({ tostring(job.file.path), tostring(cache) }):spawn()\n\tif not child then\n\t\treturn true, Err(\"Failed to start `resvg`, error: %s\", err)\n\tend\n\n\tlocal status, err\n\tif rt.tasks.image_alloc == 0 then\n\t\tstatus, err = child:wait()\n\tend\n\n\twhile not status and not err do\n\t\tya.sleep(0.2)\n\n\t\tstatus, err = child:try_wait()\n\t\tif status or err then\n\t\t\tbreak\n\t\tend\n\n\t\tlocal id, mem = child:id(), nil\n\t\tif id then\n\t\t\tmem = ya.proc_info(id).mem_resident\n\t\tend\n\t\tif mem and mem > rt.tasks.image_alloc then\n\t\t\tchild:start_kill()\n\t\t\terr = Err(\"memory limit exceeded, pid: %s, memory: %s\", id, mem)\n\t\tend\n\tend\n\n\tif not status then\n\t\treturn true, Err(\"Error while running `resvg`: %s\", err)\n\telseif not status.success then\n\t\treturn false, Err(\"`resvg` exited with error code: %s\", status.code)\n\telse\n\t\treturn true\n\tend\nend\n\nfunction M:spot(job) require(\"file\"):spot(job) end\n\nreturn M\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/vfs.lua",
    "content": "local M = {}\n\nfunction M:peek(job)\n\tlocal line = ui.Line(\"Remote file, download to preview\"):reverse()\n\tya.preview_widget(job, ui.Text(line):area(job.area):wrap(ui.Wrap.YES))\nend\n\nfunction M:seek() end\n\nfunction M:spot(job) require(\"file\"):spot(job) end\n\nreturn M\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/video.lua",
    "content": "local M = {}\n\nfunction M:peek(job)\n\tlocal start, cache = os.clock(), ya.file_cache(job)\n\tif not cache then\n\t\treturn\n\tend\n\n\tlocal ok, err = self:preload(job)\n\tif not ok or err then\n\t\treturn ya.preview_widget(job, err)\n\tend\n\n\tya.sleep(math.max(0, rt.preview.image_delay / 1000 + start - os.clock()))\n\n\tlocal _, err = ya.image_show(cache, job.area)\n\tya.preview_widget(job, err)\nend\n\nfunction M:seek(job)\n\tlocal h = cx.active.current.hovered\n\tif h and h.url == job.file.url then\n\t\tya.emit(\"peek\", {\n\t\t\tmath.max(0, cx.active.preview.skip + job.units),\n\t\t\tonly_if = job.file.url,\n\t\t})\n\tend\nend\n\nfunction M:preload(job)\n\tlocal cache = ya.file_cache(job)\n\tif not cache then\n\t\treturn true\n\tend\n\n\tlocal cha = fs.cha(cache)\n\tif cha and cha.len > 0 then\n\t\treturn true\n\tend\n\n\tlocal meta, err = self.list_meta(job.file.path, \"format=duration:stream_disposition=attached_pic\")\n\tif not meta then\n\t\treturn true, err\n\telseif not meta.format.duration then\n\t\treturn true, Err(\"Failed to get video duration\")\n\tend\n\n\tlocal pic = M.has_pic(meta)\n\tlocal percent = (pic and 0 or 5) + job.skip\n\tif percent > 95 then\n\t\tya.emit(\"peek\", { pic and 95 or 90, only_if = job.file.url, upper_bound = true })\n\t\treturn false\n\tend\n\n\tlocal cmd = Command(\"ffmpeg\")\n\t\t:stderr(Command.PIPED)\n\t\t:arg { \"-v\", \"warning\", \"-hwaccel\", \"auto\", \"-threads\", 1, \"-an\", \"-sn\", \"-dn\" }\n\n\tif percent ~= 0 then\n\t\tcmd:arg { \"-ss\", math.floor(meta.format.duration * percent / 100) }\n\tend\n\tcmd:arg { \"-skip_frame\", \"nokey\", \"-i\", tostring(job.file.path) }\n\tif percent == 0 then\n\t\tcmd:arg { \"-map\", \"disp:attached_pic\" }\n\tend\n\n\t-- stylua: ignore\n\tlocal output, err = cmd:arg({\n\t\t\"-vframes\", 1,\n\t\t\"-q:v\", 31 - math.floor(rt.preview.image_quality * 0.3),\n\t\t\"-vf\", string.format(\"scale='min(%d,iw)':'min(%d,ih)':force_original_aspect_ratio=decrease:flags=fast_bilinear\", rt.preview.max_width, rt.preview.max_height),\n\t\t\"-f\", \"image2\",\n\t\t\"-y\", tostring(cache),\n\t}):output()\n\n\tif not output then\n\t\treturn true, Err(\"Failed to start `ffmpeg`, error: %s\", err)\n\telseif output.status.success then\n\t\treturn true\n\telseif output.stderr:find(\"No filtered frames for output stream\", 1, true) then\n\t\treturn true, Err(\"No more keyframes available\")\n\telse\n\t\treturn false, Err(\"`ffmpeg` exited with error code %s: %s\", output.status.code, output.stderr)\n\tend\nend\n\nfunction M:spot(job)\n\tlocal rows = self:spot_base(job)\n\trows[#rows + 1] = ui.Row {}\n\n\tya.spot_table(\n\t\tjob,\n\t\tui.Table(ya.list_merge(rows, require(\"file\"):spot_base(job)))\n\t\t\t:area(ui.Pos { \"center\", w = 60, h = 20 })\n\t\t\t:row(1)\n\t\t\t:col(1)\n\t\t\t:col_style(th.spot.tbl_col)\n\t\t\t:cell_style(th.spot.tbl_cell)\n\t\t\t:widths { ui.Constraint.Length(14), ui.Constraint.Fill(1) }\n\t)\nend\n\nfunction M:spot_base(job)\n\tlocal meta, err = self.list_meta(job.file.path, \"format=duration:stream=codec_name,codec_type,width,height\")\n\tif not meta then\n\t\tya.err(tostring(err))\n\t\treturn {}\n\tend\n\n\tlocal dur = meta.format.duration or 0\n\tlocal rows = {\n\t\tui.Row({ \"Video\" }):style(ui.Style():fg(\"green\")),\n\t\tui.Row { \"  Duration:\", string.format(\"%d:%02d\", math.floor(dur / 60), math.floor(dur % 60)) },\n\t}\n\n\tfor i, s in ipairs(meta.streams) do\n\t\tif s.codec_type == \"video\" then\n\t\t\trows[#rows + 1] = ui.Row { string.format(\"  Stream %d:\", i), \"video\" }\n\t\t\trows[#rows + 1] = ui.Row { \"    Codec:\", s.codec_name }\n\t\t\trows[#rows + 1] = ui.Row { \"    Size:\", string.format(\"%dx%d\", s.width, s.height) }\n\t\telseif s.codec_type == \"audio\" then\n\t\t\trows[#rows + 1] = ui.Row { string.format(\"  Stream %d:\", i), \"audio\" }\n\t\t\trows[#rows + 1] = ui.Row { \"    Codec:\", s.codec_name }\n\t\tend\n\tend\n\treturn rows\nend\n\nfunction M.list_meta(path, entries)\n\tlocal cmd = Command(\"ffprobe\"):arg { \"-v\", \"quiet\" }\n\tif not entries:find(\"attached_pic\", 1, true) then\n\t\tcmd:arg { \"-select_streams\", \"v\" }\n\tend\n\n\tlocal output, err = cmd:arg({ \"-show_entries\", entries, \"-of\", \"json=c=1\", tostring(path) }):output()\n\tif not output then\n\t\treturn nil, Err(\"Failed to start `ffprobe`, error: %s\", err)\n\tend\n\n\tlocal t = ya.json_decode(output.stdout)\n\tif not t then\n\t\treturn nil, Err(\"Failed to decode `ffprobe` output: %s\", output.stdout)\n\telseif type(t) ~= \"table\" then\n\t\treturn nil, Err(\"Invalid `ffprobe` output: %s\", output.stdout)\n\tend\n\n\tt.format = t.format or {}\n\tt.streams = t.streams or {}\n\treturn t\nend\n\nfunction M.has_pic(meta)\n\tfor _, s in ipairs(meta.streams) do\n\t\tif s.disposition and s.disposition.attached_pic == 1 then\n\t\t\treturn true\n\t\tend\n\tend\nend\n\nreturn M\n"
  },
  {
    "path": "yazi-plugin/preset/plugins/zoxide.lua",
    "content": "local M = {}\n\nlocal state = ya.sync(function(st)\n\treturn {\n\t\tcwd = tostring(cx.active.current.cwd),\n\t\tempty = st.empty,\n\t}\nend)\n\nlocal set_state = ya.sync(function(st, empty) st.empty = empty end)\n\nfunction M:setup(opts)\n\topts = opts or {}\n\n\tif opts.update_db then\n\t\tps.sub(\"cd\", function()\n\t\t\tlocal cwd = cx.active.current.cwd\n\t\t\tya.async(function() Command(\"zoxide\"):arg({ \"add\", tostring(cwd) }):status() end)\n\t\tend)\n\tend\nend\n\nfunction M:entry()\n\tlocal st = state()\n\tif st.empty == nil then\n\t\tst.empty = M.is_empty(st.cwd)\n\t\tset_state(st.empty)\n\tend\n\n\tif st.empty then\n\t\treturn ya.notify {\n\t\t\ttitle = \"Zoxide\",\n\t\t\tcontent = \"No directory history found, check Zoxide's doc to set it up and restart Yazi.\",\n\t\t\ttimeout = 5,\n\t\t\tlevel = \"error\",\n\t\t}\n\tend\n\n\tlocal permit = ui.hide()\n\tlocal target, err = M.run_with(st.cwd)\n\tpermit:drop()\n\n\tif not target then\n\t\tya.notify { title = \"Zoxide\", content = tostring(err), timeout = 5, level = \"error\" }\n\telseif target ~= \"\" then\n\t\tya.emit(\"cd\", { target, raw = true })\n\tend\nend\n\nfunction M.options()\n\t-- https://github.com/ajeetdsouza/zoxide/blob/main/src/cmd/query.rs#L92\n\tlocal default = {\n\t\t-- Search mode\n\t\t\"--exact\",\n\t\t-- Search result\n\t\t\"--no-sort\",\n\t\t-- Interface\n\t\t\"--bind=ctrl-z:ignore,btab:up,tab:down\",\n\t\t\"--cycle\",\n\t\t\"--keep-right\",\n\t\t-- Layout\n\t\t\"--layout=reverse\",\n\t\t\"--height=100%\",\n\t\t\"--border\",\n\t\t\"--scrollbar=▌\",\n\t\t\"--info=inline\",\n\t\t-- Display\n\t\t\"--tabstop=1\",\n\t\t-- Scripting\n\t\t\"--exit-0\",\n\t}\n\n\tif ya.target_family() == \"unix\" then\n\t\tdefault[#default + 1] = \"--preview-window=down,30%,sharp\"\n\t\tif ya.target_os() == \"linux\" then\n\t\t\tdefault[#default + 1] = [[--preview='\\command -p ls -Cp --color=always --group-directories-first {2..}']]\n\t\telse\n\t\t\tdefault[#default + 1] = [[--preview='\\command -p ls -Cp {2..}']]\n\t\tend\n\tend\n\n\treturn (os.getenv(\"FZF_DEFAULT_OPTS\") or \"\")\n\t\t.. \" \"\n\t\t.. table.concat(default, \" \")\n\t\t.. \" \"\n\t\t.. (os.getenv(\"YAZI_ZOXIDE_OPTS\") or \"\")\nend\n\n---@param cwd string\n---@return boolean\nfunction M.is_empty(cwd)\n\tlocal child = Command(\"zoxide\"):arg({ \"query\", \"-l\", \"--exclude\", cwd }):stdout(Command.PIPED):spawn()\n\tif not child then\n\t\treturn true\n\tend\n\n\tlocal first = child:read_line()\n\tchild:start_kill()\n\treturn not first\nend\n\n---@param cwd string\n---@return string?, Error?\nfunction M.run_with(cwd)\n\tlocal child, err = Command(\"zoxide\")\n\t\t:arg({ \"query\", \"-i\", \"--exclude\", cwd })\n\t\t:env(\"SHELL\", \"sh\")\n\t\t:env(\"CLICOLOR\", 1)\n\t\t:env(\"CLICOLOR_FORCE\", 1)\n\t\t:env(\"_ZO_FZF_OPTS\", M.options())\n\t\t:stdin(Command.INHERIT)\n\t\t:stdout(Command.PIPED)\n\t\t:stderr(Command.PIPED)\n\t\t:spawn()\n\n\tif not child then\n\t\treturn nil, Err(\"Failed to start `zoxide`, error: %s\", err)\n\tend\n\n\tlocal output, err = child:wait_with_output()\n\tif not output then\n\t\treturn nil, Err(\"Cannot read `zoxide` output, error: %s\", err)\n\telseif not output.status.success and output.status.code ~= 130 then\n\t\treturn nil, Err(\"`zoxide` exited with code %s: %s\", output.status.code, output.stderr:gsub(\"^zoxide:%s*\", \"\"))\n\tend\n\treturn output.stdout:gsub(\"\\n$\", \"\"), nil\nend\n\nreturn M\n"
  },
  {
    "path": "yazi-plugin/preset/setup.lua",
    "content": "os.setlocale(\"\")\n\nrequire(\"dds\"):setup()\nrequire(\"extract\"):setup()\n"
  },
  {
    "path": "yazi-plugin/preset/ya.lua",
    "content": "function Err(s, ...) return Error.custom(string.format(s, ...)) end\n\nfunction ya.clamp(min, x, max)\n\tif x < min then\n\t\treturn min\n\telseif x > max then\n\t\treturn max\n\telse\n\t\treturn x\n\tend\nend\n\nfunction ya.list_merge(a, b)\n\tfor _, v in ipairs(b) do\n\t\ta[#a + 1] = v\n\tend\n\treturn a\nend\n\nfunction ya.dict_merge(a, b)\n\tfor k, v in pairs(b) do\n\t\ta[k] = v\n\tend\n\treturn a\nend\n\nfunction ya.readable_size(size)\n\tlocal units = { \"B\", \"K\", \"M\", \"G\", \"T\", \"P\", \"E\", \"Z\", \"Y\", \"R\", \"Q\" }\n\tlocal i = 1\n\twhile size > 1024 and i < #units do\n\t\tsize = size / 1024\n\t\ti = i + 1\n\tend\n\tlocal s = string.format(\"%.1f%s\", size, units[i]):gsub(\"[.,]0\", \"\", 1)\n\treturn s\nend\n\nfunction ya.readable_path(path)\n\tlocal home = os.getenv(\"HOME\") or os.getenv(\"USERPROFILE\")\n\tif not home then\n\t\treturn path\n\telseif path:sub(1, #home) == home then\n\t\treturn \"~\" .. path:sub(#home + 1)\n\telse\n\t\treturn path\n\tend\nend\n\nfunction ya.child_at(pos, children)\n\tfor i = #children, 1, -1 do\n\t\tif children[i]._area:contains(pos) then\n\t\t\treturn children[i]\n\t\tend\n\tend\nend\n"
  },
  {
    "path": "yazi-plugin/src/elements/elements.rs",
    "content": "use std::borrow::Cow;\n\nuse ansi_to_tui::IntoText;\nuse mlua::{AnyUserData, ExternalError, ExternalResult, IntoLua, Lua, ObjectLike, Table, Value};\nuse tracing::error;\nuse unicode_width::{UnicodeWidthChar, UnicodeWidthStr};\nuse yazi_binding::{Composer, ComposerGet, ComposerSet, Permit, PermitRef, elements::{Line, Rect, Span, Wrap}, runtime};\nuse yazi_config::{LAYOUT, YAZI};\nuse yazi_proxy::AppProxy;\nuse yazi_shared::replace_to_printable;\nuse yazi_shim::ratatui::line_count;\nuse yazi_term::YIELD_TO_SUBPROCESS;\n\npub fn compose() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tmatch key {\n\t\t\tb\"area\" => area(lua)?,\n\t\t\tb\"height\" => height(lua)?,\n\t\t\tb\"hide\" => hide(lua)?,\n\t\t\tb\"printable\" => printable(lua)?,\n\t\t\tb\"redraw\" => redraw(lua)?,\n\t\t\tb\"render\" => render(lua)?,\n\t\t\tb\"truncate\" => truncate(lua)?,\n\t\t\tb\"width\" => width(lua)?,\n\t\t\t_ => return Ok(Value::Nil),\n\t\t}\n\t\t.into_lua(lua)\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tyazi_binding::elements::compose(get, set)\n}\n\npub(super) fn area(lua: &Lua) -> mlua::Result<Value> {\n\tlet f = lua.create_function(|_, s: mlua::String| {\n\t\tlet layout = LAYOUT.get();\n\t\tOk(match &*s.as_bytes() {\n\t\t\tb\"current\" => Rect(layout.current),\n\t\t\tb\"preview\" => Rect(layout.preview),\n\t\t\tb\"progress\" => Rect(layout.progress),\n\t\t\t_ => Err(format!(\"unknown area: {}\", s.display()).into_lua_err())?,\n\t\t})\n\t})?;\n\n\tf.into_lua(lua)\n}\n\npub(super) fn height(lua: &Lua) -> mlua::Result<Value> {\n\tlet f = lua.create_function(|_, (s, opts): (mlua::String, Table)| {\n\t\tlet width = opts.raw_get(\"width\")?;\n\t\tlet wrap: Wrap = opts.raw_get(\"wrap\")?;\n\n\t\tOk(if opts.raw_get(\"ansi\")? {\n\t\t\tline_count(s.to_string_lossy().to_text().into_lua_err()?, width, YAZI.preview.indent(), wrap)\n\t\t} else {\n\t\t\tline_count(s.to_string_lossy(), width, YAZI.preview.indent(), wrap)\n\t\t})\n\t})?;\n\n\tf.into_lua(lua)\n}\n\npub(super) fn hide(lua: &Lua) -> mlua::Result<Value> {\n\tlet f = lua.create_async_function(|lua, ()| async move {\n\t\tif runtime!(lua)?.blocking {\n\t\t\treturn Err(\"Cannot call `ui.hide()` while main thread is blocked\".into_lua_err());\n\t\t}\n\n\t\tif lua.named_registry_value::<PermitRef>(\"HIDE_PERMIT\").is_ok_and(|h| h.is_some()) {\n\t\t\treturn Err(\"Cannot hide while already hidden\".into_lua_err());\n\t\t}\n\n\t\tlet permit = YIELD_TO_SUBPROCESS.acquire().await.unwrap();\n\t\tAppProxy::stop().await;\n\n\t\tlua.set_named_registry_value(\"HIDE_PERMIT\", Permit::new(permit, AppProxy::resume()))?;\n\t\tlua.named_registry_value::<AnyUserData>(\"HIDE_PERMIT\")\n\t})?;\n\n\tf.into_lua(lua)\n}\n\npub(super) fn printable(lua: &Lua) -> mlua::Result<Value> {\n\tlet f = lua.create_function(|lua, s: mlua::String| {\n\t\tOk(match replace_to_printable(&s.as_bytes(), false, 1, true) {\n\t\t\tCow::Borrowed(_) => s,\n\t\t\tCow::Owned(new) => lua.create_external_string(new)?,\n\t\t})\n\t})?;\n\n\tf.into_lua(lua)\n}\n\npub(super) fn redraw(lua: &Lua) -> mlua::Result<Value> {\n\tlet f = lua.create_function(|lua, c: Table| {\n\t\tlet id: mlua::String = c.get(\"_id\")?;\n\n\t\tlet mut layout = LAYOUT.get();\n\t\tmatch &*id.as_bytes() {\n\t\t\tb\"current\" => layout.current = *c.raw_get::<Rect>(\"_area\")?,\n\t\t\tb\"preview\" => layout.preview = *c.raw_get::<Rect>(\"_area\")?,\n\t\t\tb\"progress\" => layout.progress = *c.raw_get::<Rect>(\"_area\")?,\n\t\t\t_ => {}\n\t\t}\n\n\t\tLAYOUT.set(layout);\n\t\tmatch c.call_method::<Value>(\"redraw\", ())? {\n\t\t\tValue::Table(tbl) => Ok(tbl),\n\t\t\tValue::UserData(ud) => lua.create_sequence_from([ud]),\n\t\t\t_ => {\n\t\t\t\terror!(\n\t\t\t\t\t\"Failed to `redraw()` the `{}` component: expected a table or UserData\",\n\t\t\t\t\tid.display(),\n\t\t\t\t);\n\t\t\t\tlua.create_table()\n\t\t\t}\n\t\t}\n\t})?;\n\n\tf.into_lua(lua)\n}\n\npub(super) fn render(lua: &Lua) -> mlua::Result<Value> {\n\tlet f = lua.create_function(|_, ()| {\n\t\tyazi_macro::render!();\n\t\tOk(())\n\t})?;\n\n\tf.into_lua(lua)\n}\n\npub(super) fn truncate(lua: &Lua) -> mlua::Result<Value> {\n\tfn traverse(it: impl Iterator<Item = (usize, char)>, max: usize) -> (Option<usize>, usize, bool) {\n\t\tlet (mut adv, mut last) = (0, 0);\n\t\tlet idx = it\n\t\t\t.take_while(|&(_, c)| {\n\t\t\t\t(last, adv) = (adv, adv + c.width().unwrap_or(0));\n\t\t\t\tadv <= max\n\t\t\t})\n\t\t\t.map(|(i, _)| i)\n\t\t\t.last();\n\t\t(idx, last, adv > max)\n\t}\n\n\tlet f = lua.create_function(|lua, (s, t): (mlua::String, Table)| {\n\t\tlet b = s.as_bytes();\n\t\tif b.is_empty() {\n\t\t\treturn Ok(s);\n\t\t}\n\n\t\tlet max = t.raw_get(\"max\")?;\n\t\tif b.len() <= max {\n\t\t\treturn Ok(s);\n\t\t} else if max < 1 {\n\t\t\treturn lua.create_string(\"\");\n\t\t}\n\n\t\tlet lossy = String::from_utf8_lossy(&b);\n\t\tlet rtl = t.raw_get(\"rtl\")?;\n\t\tlet (idx, width, remain) = if rtl {\n\t\t\ttraverse(lossy.char_indices().rev(), max)\n\t\t} else {\n\t\t\ttraverse(lossy.char_indices(), max)\n\t\t};\n\n\t\tlet Some(idx) = idx else { return lua.create_string(\"…\") };\n\t\tif !remain {\n\t\t\treturn Ok(s);\n\t\t}\n\n\t\tlet result: Vec<_> = match (rtl, width == max) {\n\t\t\t(false, false) => {\n\t\t\t\tlet len = lossy[idx..].chars().next().map_or(0, |c| c.len_utf8());\n\t\t\t\tlossy[..idx + len].bytes().chain(\"…\".bytes()).collect()\n\t\t\t}\n\t\t\t(false, true) => lossy[..idx].bytes().chain(\"…\".bytes()).collect(),\n\t\t\t(true, false) => \"…\".bytes().chain(lossy[idx..].bytes()).collect(),\n\t\t\t(true, true) => {\n\t\t\t\tlet len = lossy[idx..].chars().next().map_or(0, |c| c.len_utf8());\n\t\t\t\t\"…\".bytes().chain(lossy[idx + len..].bytes()).collect()\n\t\t\t}\n\t\t};\n\t\tlua.create_external_string(result)\n\t})?;\n\n\tf.into_lua(lua)\n}\n\npub(super) fn width(lua: &Lua) -> mlua::Result<Value> {\n\tlet f = lua.create_function(|_, v: Value| match v {\n\t\tValue::String(s) => {\n\t\t\tlet (mut acc, b) = (0, s.as_bytes());\n\t\t\tfor c in b.utf8_chunks() {\n\t\t\t\tacc += c.valid().width();\n\t\t\t\tif !c.invalid().is_empty() {\n\t\t\t\t\tacc += 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\tOk(acc)\n\t\t}\n\t\tValue::UserData(ud) => {\n\t\t\tif let Ok(line) = ud.borrow::<Line>() {\n\t\t\t\tOk(line.width())\n\t\t\t} else if let Ok(span) = ud.borrow::<Span>() {\n\t\t\t\tOk(span.width())\n\t\t\t} else {\n\t\t\t\tErr(\"expected a string, Line, or Span\".into_lua_err())?\n\t\t\t}\n\t\t}\n\t\t_ => Err(\"expected a string, Line, or Span\".into_lua_err())?,\n\t})?;\n\n\tf.into_lua(lua)\n}\n\n#[cfg(test)]\nmod tests {\n\tuse mlua::{Lua, chunk};\n\n\tfn truncate(s: &str, max: usize, rtl: bool) -> String {\n\t\tlet lua = Lua::new();\n\t\tlet f = super::truncate(&lua).unwrap();\n\n\t\tlua\n\t\t\t.load(chunk! {\n\t\t\t\treturn $f($s, { max = $max, rtl = $rtl })\n\t\t\t})\n\t\t\t.call(())\n\t\t\t.unwrap()\n\t}\n\n\t#[test]\n\tfn test_truncate() {\n\t\tassert_eq!(truncate(\"你好，world\", 0, false), \"\");\n\t\tassert_eq!(truncate(\"你好，world\", 1, false), \"…\");\n\t\tassert_eq!(truncate(\"你好，world\", 2, false), \"…\");\n\n\t\tassert_eq!(truncate(\"你好，世界\", 3, false), \"你…\");\n\t\tassert_eq!(truncate(\"你好，世界\", 4, false), \"你…\");\n\t\tassert_eq!(truncate(\"你好，世界\", 5, false), \"你好…\");\n\n\t\tassert_eq!(truncate(\"Hello, world\", 5, false), \"Hell…\");\n\t\tassert_eq!(truncate(\"Ni好，世界\", 3, false), \"Ni…\");\n\t}\n\n\t#[test]\n\tfn test_truncate_rtl() {\n\t\tassert_eq!(truncate(\"world，你好\", 0, true), \"\");\n\t\tassert_eq!(truncate(\"world，你好\", 1, true), \"…\");\n\t\tassert_eq!(truncate(\"world，你好\", 2, true), \"…\");\n\n\t\tassert_eq!(truncate(\"你好，世界\", 3, true), \"…界\");\n\t\tassert_eq!(truncate(\"你好，世界\", 4, true), \"…界\");\n\t\tassert_eq!(truncate(\"你好，世界\", 5, true), \"…世界\");\n\n\t\tassert_eq!(truncate(\"Hello, world\", 5, true), \"…orld\");\n\t\tassert_eq!(truncate(\"你好，Shi界\", 3, true), \"…界\");\n\t}\n\n\t#[test]\n\tfn test_truncate_oboe() {\n\t\tassert_eq!(truncate(\"Hello, world\", 11, false), \"Hello, wor…\");\n\t\tassert_eq!(truncate(\"你好，世界\", 9, false), \"你好，世…\");\n\t\tassert_eq!(truncate(\"你好，世Jie\", 9, false), \"你好，世…\");\n\n\t\tassert_eq!(truncate(\"Hello, world\", 11, true), \"…llo, world\");\n\t\tassert_eq!(truncate(\"你好，世界\", 9, true), \"…好，世界\");\n\t\tassert_eq!(truncate(\"Ni好，世界\", 9, true), \"…好，世界\");\n\t}\n\n\t#[test]\n\tfn test_truncate_exact() {\n\t\tassert_eq!(truncate(\"Hello, world\", 12, false), \"Hello, world\");\n\t\tassert_eq!(truncate(\"你好，世界\", 10, false), \"你好，世界\");\n\n\t\tassert_eq!(truncate(\"Hello, world\", 12, true), \"Hello, world\");\n\t\tassert_eq!(truncate(\"你好，世界\", 10, true), \"你好，世界\");\n\t}\n\n\t#[test]\n\tfn test_truncate_overflow() {\n\t\tassert_eq!(truncate(\"Hello, world\", 13, false), \"Hello, world\");\n\t\tassert_eq!(truncate(\"你好，世界\", 11, false), \"你好，世界\");\n\n\t\tassert_eq!(truncate(\"Hello, world\", 13, true), \"Hello, world\");\n\t\tassert_eq!(truncate(\"你好，世界\", 11, true), \"你好，世界\");\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/elements/mod.rs",
    "content": "yazi_macro::mod_flat!(elements);\n"
  },
  {
    "path": "yazi-plugin/src/external/fd.rs",
    "content": "use std::{path::Path, process::Stdio};\n\nuse anyhow::Result;\nuse tokio::{io::{AsyncBufReadExt, BufReader}, process::{Child, Command}, sync::mpsc::{self, UnboundedReceiver}};\nuse yazi_fs::{File, FsUrl};\nuse yazi_shared::url::{AsUrl, UrlBuf, UrlLike};\nuse yazi_vfs::VfsFile;\n\npub struct FdOpt {\n\tpub cwd:     UrlBuf,\n\tpub hidden:  bool,\n\tpub subject: String,\n\tpub args:    Vec<String>,\n}\n\npub fn fd(opt: FdOpt) -> Result<UnboundedReceiver<File>> {\n\tlet mut child = spawn(\"fd\", &opt).or_else(|_| spawn(\"fdfind\", &opt))?;\n\n\tlet mut it = BufReader::new(child.stdout.take().unwrap()).lines();\n\tlet (tx, rx) = mpsc::unbounded_channel();\n\n\ttokio::spawn(async move {\n\t\twhile let Ok(Some(line)) = it.next_line().await {\n\t\t\tif Path::new(&line).is_absolute() {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet Ok(url) = opt.cwd.try_join(line) else {\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tif let Ok(file) = File::new(url).await {\n\t\t\t\ttx.send(file).ok();\n\t\t\t}\n\t\t}\n\t\tchild.wait().await.ok();\n\t});\n\tOk(rx)\n}\n\nfn spawn(program: &str, opt: &FdOpt) -> std::io::Result<Child> {\n\tCommand::new(program)\n\t\t.arg(\"--base-directory\")\n\t\t.arg(&*opt.cwd.as_url().unified_path())\n\t\t.arg(\"--regex\")\n\t\t.arg(if opt.hidden { \"--hidden\" } else { \"--no-hidden\" })\n\t\t.args(&opt.args)\n\t\t.arg(&opt.subject)\n\t\t.kill_on_drop(true)\n\t\t.stdout(Stdio::piped())\n\t\t.stderr(Stdio::null())\n\t\t.spawn()\n}\n"
  },
  {
    "path": "yazi-plugin/src/external/highlighter.rs",
    "content": "use std::{io::Cursor, path::{Path, PathBuf}, sync::OnceLock};\n\nuse anyhow::{Result, anyhow};\nuse ratatui::{layout::Size, text::{Line, Span, Text}};\nuse syntect::{LoadingError, dumps, easy::HighlightLines, highlighting::{self, Theme, ThemeSet}, parsing::{SyntaxReference, SyntaxSet}};\nuse tokio::io::{AsyncBufReadExt, AsyncSeekExt, BufReader};\nuse yazi_config::{THEME, YAZI, preview::PreviewWrap};\nuse yazi_fs::provider::{Provider, local::Local};\nuse yazi_shared::{Ids, errors::PeekError, push_printable_char};\nuse yazi_shim::ratatui::line_count;\n\nstatic INCR: Ids = Ids::new();\nstatic SYNTECT: OnceLock<(Theme, SyntaxSet)> = OnceLock::new();\n\npub struct Highlighter {\n\tpath: PathBuf,\n}\n\nimpl Highlighter {\n\tpub fn new<P>(path: P) -> Self\n\twhere\n\t\tP: Into<PathBuf>,\n\t{\n\t\tSelf { path: path.into() }\n\t}\n\n\tpub fn init() -> (&'static Theme, &'static SyntaxSet) {\n\t\tlet f = || {\n\t\t\tlet theme = std::fs::File::open(&THEME.mgr.syntect_theme)\n\t\t\t\t.map_err(LoadingError::Io)\n\t\t\t\t.and_then(|f| ThemeSet::load_from_reader(&mut std::io::BufReader::new(f)))\n\t\t\t\t.or_else(|_| ThemeSet::load_from_reader(&mut Cursor::new(yazi_prebuilt::ansi_theme())));\n\n\t\t\tlet syntaxes = dumps::from_uncompressed_data(yazi_prebuilt::syntaxes());\n\n\t\t\t(theme.unwrap(), syntaxes.unwrap())\n\t\t};\n\n\t\tlet (theme, syntaxes) = SYNTECT.get_or_init(f);\n\t\t(theme, syntaxes)\n\t}\n\n\tpub fn abort() { INCR.next(); }\n\n\tpub async fn highlight(&self, skip: usize, size: Size) -> Result<Text<'static>, PeekError> {\n\t\tlet indent = YAZI.preview.indent();\n\t\tlet mut reader = BufReader::new(Local::regular(&self.path).open().await?);\n\n\t\tlet syntax = Self::find_syntax(&self.path, &mut reader).await;\n\t\tlet mut plain = syntax.is_err();\n\n\t\tlet mut before = Vec::with_capacity(if plain { 0 } else { skip });\n\t\tlet mut after = Vec::with_capacity(size.height as _);\n\n\t\tlet mut i = 0;\n\t\tlet mut buf = vec![];\n\t\tlet mut inspected = 0u16;\n\t\twhile reader.read_until(b'\\n', &mut buf).await.is_ok_and(|n| n > 0) {\n\t\t\tif Self::is_binary(&buf, &mut inspected) {\n\t\t\t\treturn Err(\"Binary file\".into());\n\t\t\t}\n\n\t\t\tlet remaining = Self::normalize_control_chars(&mut buf);\n\t\t\tif !plain && (remaining || buf.len() > 5000) {\n\t\t\t\tplain = true;\n\t\t\t\tbefore.clear();\n\t\t\t}\n\n\t\t\ti += if i >= skip {\n\t\t\t\tafter.push(String::from_utf8_lossy(&buf).into_owned());\n\t\t\t\tline_count(&**after.last().unwrap(), size.width, &indent, YAZI.preview.wrap)\n\t\t\t} else if !plain {\n\t\t\t\tbefore.push(String::from_utf8_lossy(&buf).into_owned());\n\t\t\t\tline_count(&**before.last().unwrap(), size.width, &indent, YAZI.preview.wrap)\n\t\t\t} else if YAZI.preview.wrap != PreviewWrap::No {\n\t\t\t\tline_count(String::from_utf8_lossy(&buf), size.width, &indent, YAZI.preview.wrap)\n\t\t\t} else {\n\t\t\t\t1\n\t\t\t};\n\n\t\t\tbuf.clear();\n\t\t\tif i > skip + size.height as usize {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif skip > 0 && i < skip + size.height as usize {\n\t\t\treturn Err(PeekError::Exceed(i.saturating_sub(size.height as _)));\n\t\t}\n\n\t\tOk(if plain {\n\t\t\tText::from(Self::merge_highlight_lines(&after, YAZI.preview.tab_size))\n\t\t} else {\n\t\t\tSelf::highlight_with(before, after, syntax.unwrap()).await?\n\t\t})\n\t}\n\n\tasync fn highlight_with(\n\t\tbefore: Vec<String>,\n\t\tafter: Vec<String>,\n\t\tsyntax: &'static SyntaxReference,\n\t) -> Result<Text<'static>, PeekError> {\n\t\tlet ticket = INCR.current();\n\n\t\ttokio::task::spawn_blocking(move || {\n\t\t\tlet (theme, syntaxes) = Self::init();\n\t\t\tlet mut h = HighlightLines::new(syntax, theme);\n\n\t\t\tfor line in before {\n\t\t\t\tif ticket != INCR.current() {\n\t\t\t\t\treturn Err(\"Highlighting cancelled\".into());\n\t\t\t\t}\n\t\t\t\th.highlight_line(&line, syntaxes).map_err(|e| anyhow!(e))?;\n\t\t\t}\n\n\t\t\tlet indent = YAZI.preview.indent();\n\t\t\tlet mut lines = Vec::with_capacity(after.len());\n\t\t\tfor line in after {\n\t\t\t\tif ticket != INCR.current() {\n\t\t\t\t\treturn Err(\"Highlighting cancelled\".into());\n\t\t\t\t}\n\n\t\t\t\tlet regions = h.highlight_line(&line, syntaxes).map_err(|e| anyhow!(e))?;\n\t\t\t\tlines.push(Self::to_line_widget(regions, &indent));\n\t\t\t}\n\n\t\t\tOk(Text::from(lines))\n\t\t})\n\t\t.await?\n\t}\n\n\tasync fn find_syntax(\n\t\tpath: &Path,\n\t\treader: &mut BufReader<tokio::fs::File>,\n\t) -> Result<&'static SyntaxReference> {\n\t\tlet (_, syntaxes) = Self::init();\n\t\tlet name = path.file_name().map(|n| n.to_string_lossy()).unwrap_or_default();\n\t\tif let Some(s) = syntaxes.find_syntax_by_extension(&name) {\n\t\t\treturn Ok(s);\n\t\t}\n\n\t\tlet ext = path.extension().map(|e| e.to_string_lossy()).unwrap_or_default();\n\t\tif let Some(s) = syntaxes.find_syntax_by_extension(&ext) {\n\t\t\treturn Ok(s);\n\t\t}\n\n\t\tlet mut line = String::new();\n\t\treader.read_line(&mut line).await?;\n\t\treader.rewind().await?;\n\t\tsyntaxes.find_syntax_by_first_line(&line).ok_or_else(|| anyhow!(\"No syntax found\"))\n\t}\n\n\t#[inline(always)]\n\tfn is_binary(buf: &[u8], inspected: &mut u16) -> bool {\n\t\tif let Some(n) = 1024u16.checked_sub(*inspected) {\n\t\t\t*inspected += n.min(buf.len() as u16);\n\t\t\tbuf.iter().take(n as usize).any(|&b| b == 0)\n\t\t} else {\n\t\t\tfalse\n\t\t}\n\t}\n\n\tfn normalize_control_chars(buf: &mut Vec<u8>) -> bool {\n\t\tif buf.ends_with(b\"\\r\\n\") {\n\t\t\tbuf.pop();\n\t\t\tbuf.pop();\n\t\t\tbuf.push(b'\\n');\n\t\t}\n\n\t\tlet mut remaining = false;\n\t\tfor b in buf.iter_mut() {\n\t\t\tif *b == b'\\r' {\n\t\t\t\t*b = b'\\n';\n\t\t\t} else {\n\t\t\t\tremaining |= matches!(b, 0..=0x08 | 0x0B..=0x1F | 0x7F);\n\t\t\t}\n\t\t}\n\t\tremaining\n\t}\n\n\tfn merge_highlight_lines(s: &[String], tab_size: u8) -> String {\n\t\tlet mut buf = Vec::new();\n\t\tbuf.reserve_exact(s.iter().map(|s| s.len()).sum::<usize>() | 15);\n\n\t\tfor &b in s.iter().flat_map(|s| s.as_bytes()) {\n\t\t\tpush_printable_char(&mut buf, b, true, tab_size, false);\n\t\t}\n\t\tunsafe { String::from_utf8_unchecked(buf) }\n\t}\n}\n\nimpl Highlighter {\n\tpub fn to_line_widget(regions: Vec<(highlighting::Style, &str)>, indent: &str) -> Line<'static> {\n\t\tlet spans: Vec<_> = regions\n\t\t\t.into_iter()\n\t\t\t.map(|(style, s)| {\n\t\t\t\tlet mut modifier = ratatui::style::Modifier::empty();\n\t\t\t\tif style.font_style.contains(highlighting::FontStyle::BOLD) {\n\t\t\t\t\tmodifier |= ratatui::style::Modifier::BOLD;\n\t\t\t\t}\n\t\t\t\tif style.font_style.contains(highlighting::FontStyle::ITALIC) {\n\t\t\t\t\tmodifier |= ratatui::style::Modifier::ITALIC;\n\t\t\t\t}\n\t\t\t\tif style.font_style.contains(highlighting::FontStyle::UNDERLINE) {\n\t\t\t\t\tmodifier |= ratatui::style::Modifier::UNDERLINED;\n\t\t\t\t}\n\n\t\t\t\tSpan {\n\t\t\t\t\tcontent: s.replace('\\t', indent).into(),\n\t\t\t\t\tstyle:   ratatui::style::Style {\n\t\t\t\t\t\tfg: Self::to_ansi_color(style.foreground),\n\t\t\t\t\t\t// bg: Self::to_ansi_color(style.background),\n\t\t\t\t\t\tadd_modifier: modifier,\n\t\t\t\t\t\t..Default::default()\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t})\n\t\t\t.collect();\n\n\t\tLine::from(spans)\n\t}\n\n\t// Copy from https://github.com/sharkdp/bat/blob/master/src/terminal.rs\n\tfn to_ansi_color(color: highlighting::Color) -> Option<ratatui::style::Color> {\n\t\tif color.a == 0 {\n\t\t\t// Themes can specify one of the user-configurable terminal colors by\n\t\t\t// encoding them as #RRGGBBAA with AA set to 00 (transparent) and RR set\n\t\t\t// to the 8-bit color palette number. The built-in themes ansi, base16,\n\t\t\t// and base16-256 use this.\n\t\t\tSome(match color.r {\n\t\t\t\t// For the first 8 colors, use the Color enum to produce ANSI escape\n\t\t\t\t// sequences using codes 30-37 (foreground) and 40-47 (background).\n\t\t\t\t// For example, red foreground is \\x1b[31m. This works on terminals\n\t\t\t\t// without 256-color support.\n\t\t\t\t0x00 => ratatui::style::Color::Black,\n\t\t\t\t0x01 => ratatui::style::Color::Red,\n\t\t\t\t0x02 => ratatui::style::Color::Green,\n\t\t\t\t0x03 => ratatui::style::Color::Yellow,\n\t\t\t\t0x04 => ratatui::style::Color::Blue,\n\t\t\t\t0x05 => ratatui::style::Color::Magenta,\n\t\t\t\t0x06 => ratatui::style::Color::Cyan,\n\t\t\t\t0x07 => ratatui::style::Color::White,\n\t\t\t\t// For all other colors, use Fixed to produce escape sequences using\n\t\t\t\t// codes 38;5 (foreground) and 48;5 (background). For example,\n\t\t\t\t// bright red foreground is \\x1b[38;5;9m. This only works on\n\t\t\t\t// terminals with 256-color support.\n\t\t\t\t//\n\t\t\t\t// TODO: When ansi_term adds support for bright variants using codes\n\t\t\t\t// 90-97 (foreground) and 100-107 (background), we should use those\n\t\t\t\t// for values 0x08 to 0x0f and only use Fixed for 0x10 to 0xff.\n\t\t\t\tn => ratatui::style::Color::Indexed(n),\n\t\t\t})\n\t\t} else if color.a == 1 {\n\t\t\t// Themes can specify the terminal's default foreground/background color\n\t\t\t// (i.e. no escape sequence) using the encoding #RRGGBBAA with AA set to\n\t\t\t// 01. The built-in theme ansi uses this.\n\t\t\tNone\n\t\t} else {\n\t\t\tSome(ratatui::style::Color::Rgb(color.r, color.g, color.b))\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/external/mod.rs",
    "content": "yazi_macro::mod_flat!(fd highlighter rg rga);\n"
  },
  {
    "path": "yazi-plugin/src/external/rg.rs",
    "content": "use std::{path::Path, process::Stdio};\n\nuse anyhow::Result;\nuse tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::{self, UnboundedReceiver}};\nuse yazi_fs::{File, FsUrl};\nuse yazi_shared::url::{AsUrl, UrlBuf, UrlLike};\nuse yazi_vfs::VfsFile;\n\npub struct RgOpt {\n\tpub cwd:     UrlBuf,\n\tpub hidden:  bool,\n\tpub subject: String,\n\tpub args:    Vec<String>,\n}\n\npub fn rg(opt: RgOpt) -> Result<UnboundedReceiver<File>> {\n\tlet mut child = Command::new(\"rg\")\n\t\t.args([\"--color=never\", \"--files-with-matches\", \"--smart-case\"])\n\t\t.arg(if opt.hidden { \"--hidden\" } else { \"--no-hidden\" })\n\t\t.args(opt.args)\n\t\t.arg(opt.subject)\n\t\t.current_dir(&*opt.cwd.as_url().unified_path())\n\t\t.kill_on_drop(true)\n\t\t.stdout(Stdio::piped())\n\t\t.stderr(Stdio::null())\n\t\t.spawn()?;\n\n\tlet mut it = BufReader::new(child.stdout.take().unwrap()).lines();\n\tlet (tx, rx) = mpsc::unbounded_channel();\n\n\ttokio::spawn(async move {\n\t\twhile let Ok(Some(line)) = it.next_line().await {\n\t\t\tif Path::new(&line).is_absolute() {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet Ok(url) = opt.cwd.try_join(line) else {\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tif let Ok(file) = File::new(url).await {\n\t\t\t\ttx.send(file).ok();\n\t\t\t}\n\t\t}\n\t\tchild.wait().await.ok();\n\t});\n\tOk(rx)\n}\n"
  },
  {
    "path": "yazi-plugin/src/external/rga.rs",
    "content": "use std::{path::Path, process::Stdio};\n\nuse anyhow::Result;\nuse tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc::{self, UnboundedReceiver}};\nuse yazi_fs::{File, FsUrl};\nuse yazi_shared::url::{AsUrl, UrlBuf, UrlLike};\nuse yazi_vfs::VfsFile;\n\npub struct RgaOpt {\n\tpub cwd:     UrlBuf,\n\tpub hidden:  bool,\n\tpub subject: String,\n\tpub args:    Vec<String>,\n}\n\npub fn rga(opt: RgaOpt) -> Result<UnboundedReceiver<File>> {\n\tlet mut child = Command::new(\"rga\")\n\t\t.args([\"--color=never\", \"--files-with-matches\", \"--smart-case\"])\n\t\t.arg(if opt.hidden { \"--hidden\" } else { \"--no-hidden\" })\n\t\t.args(opt.args)\n\t\t.arg(opt.subject)\n\t\t.current_dir(&*opt.cwd.as_url().unified_path())\n\t\t.kill_on_drop(true)\n\t\t.stdout(Stdio::piped())\n\t\t.stderr(Stdio::null())\n\t\t.spawn()?;\n\n\tlet mut it = BufReader::new(child.stdout.take().unwrap()).lines();\n\tlet (tx, rx) = mpsc::unbounded_channel();\n\n\ttokio::spawn(async move {\n\t\twhile let Ok(Some(line)) = it.next_line().await {\n\t\t\tif Path::new(&line).is_absolute() {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet Ok(url) = opt.cwd.try_join(line) else {\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tif let Ok(file) = File::new(url).await {\n\t\t\t\ttx.send(file).ok();\n\t\t\t}\n\t\t}\n\t\tchild.wait().await.ok();\n\t});\n\tOk(rx)\n}\n"
  },
  {
    "path": "yazi-plugin/src/fs/fs.rs",
    "content": "use std::str::FromStr;\n\nuse mlua::{ExternalError, Function, IntoLua, IntoLuaMulti, Lua, Table, Value};\nuse yazi_binding::{Cha, Composer, ComposerGet, ComposerSet, Error, File, SizeCalculator, Url, UrlRef, deprecate};\nuse yazi_config::Pattern;\nuse yazi_fs::{mounts::PARTITIONS, provider::{Attrs, DirReader, FileHolder}};\nuse yazi_shared::url::{UrlCow, UrlLike};\nuse yazi_vfs::{VfsFile, provider};\n\npub fn compose() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tmatch key {\n\t\t\tb\"access\" => access(lua)?,\n\t\t\tb\"calc_size\" => calc_size(lua)?,\n\t\t\tb\"cha\" => cha(lua)?,\n\t\t\tb\"copy\" => copy(lua)?,\n\t\t\tb\"create\" => create(lua)?,\n\t\t\tb\"cwd\" => cwd(lua)?,\n\t\t\tb\"expand_url\" => expand_url(lua)?,\n\t\t\tb\"op\" => op(lua)?,\n\t\t\tb\"partitions\" => partitions(lua)?,\n\t\t\tb\"read_dir\" => read_dir(lua)?,\n\t\t\tb\"remove\" => remove(lua)?,\n\t\t\tb\"rename\" => rename(lua)?,\n\t\t\tb\"unique\" => unique(lua)?,\n\t\t\tb\"unique_name\" => unique_name(lua)?,\n\t\t\tb\"write\" => write(lua)?,\n\t\t\t_ => return Ok(Value::Nil),\n\t\t}\n\t\t.into_lua(lua)\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n\nfn access(lua: &Lua) -> mlua::Result<Function> {\n\tlua.create_function(|_, ()| Ok(yazi_binding::Access::default()))\n}\n\nfn calc_size(lua: &Lua) -> mlua::Result<Function> {\n\tlua.create_async_function(|lua, url: UrlRef| async move {\n\t\tlet it = if let Some(path) = url.as_local() {\n\t\t\tyazi_fs::provider::local::SizeCalculator::new(path).await.map(SizeCalculator::Local)\n\t\t} else {\n\t\t\tyazi_vfs::provider::SizeCalculator::new(&*url).await.map(SizeCalculator::Remote)\n\t\t};\n\n\t\tmatch it {\n\t\t\tOk(it) => it.into_lua_multi(&lua),\n\t\t\tErr(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),\n\t\t}\n\t})\n}\n\nfn cha(lua: &Lua) -> mlua::Result<Function> {\n\tlua.create_async_function(|lua, (url, follow): (UrlRef, Option<bool>)| async move {\n\t\tlet cha = if follow.unwrap_or(false) {\n\t\t\tprovider::metadata(&*url).await\n\t\t} else {\n\t\t\tprovider::symlink_metadata(&*url).await\n\t\t};\n\n\t\tmatch cha {\n\t\t\tOk(c) => Cha(c).into_lua_multi(&lua),\n\t\t\tErr(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),\n\t\t}\n\t})\n}\n\nfn copy(lua: &Lua) -> mlua::Result<Function> {\n\tlua.create_async_function(|lua, (from, to): (UrlRef, UrlRef)| async move {\n\t\tmatch provider::copy(&*from, &*to, Attrs::default()).await {\n\t\t\tOk(len) => len.into_lua_multi(&lua),\n\t\t\tErr(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),\n\t\t}\n\t})\n}\n\nfn create(lua: &Lua) -> mlua::Result<Function> {\n\tlua.create_async_function(|lua, (r#type, url): (mlua::String, UrlRef)| async move {\n\t\tlet result = match &*r#type.as_bytes() {\n\t\t\tb\"dir\" => provider::create_dir(&*url).await,\n\t\t\tb\"dir_all\" => provider::create_dir_all(&*url).await,\n\t\t\t_ => Err(\"Creation type must be 'dir' or 'dir_all'\".into_lua_err())?,\n\t\t};\n\n\t\tmatch result {\n\t\t\tOk(()) => true.into_lua_multi(&lua),\n\t\t\tErr(e) => (false, Error::Io(e)).into_lua_multi(&lua),\n\t\t}\n\t})\n}\n\nfn cwd(lua: &Lua) -> mlua::Result<Function> {\n\tlua.create_function(|lua, ()| match std::env::current_dir() {\n\t\tOk(p) => Url::new(p).into_lua_multi(lua),\n\t\tErr(e) => (Value::Nil, Error::Io(e)).into_lua_multi(lua),\n\t})\n}\n\n#[allow(irrefutable_let_patterns)]\nfn expand_url(lua: &Lua) -> mlua::Result<Function> {\n\tlua.create_function(|lua, value: Value| {\n\t\tuse yazi_fs::path::expand_url;\n\t\tmatch &value {\n\t\t\tValue::String(s) => Url::new(expand_url(UrlCow::try_from(&*s.as_bytes())?)).into_lua(lua),\n\t\t\tValue::UserData(ud) => {\n\t\t\t\tif let u = expand_url(&*ud.borrow::<yazi_binding::Url>()?)\n\t\t\t\t\t&& u.is_owned()\n\t\t\t\t{\n\t\t\t\t\tUrl::new(u.into_owned()).into_lua(lua)\n\t\t\t\t} else {\n\t\t\t\t\tOk(value)\n\t\t\t\t}\n\t\t\t}\n\t\t\t_ => Err(\"must be a string or a Url\".into_lua_err())?,\n\t\t}\n\t})\n}\n\nfn op(lua: &Lua) -> mlua::Result<Function> {\n\tlua.create_function(|lua, (name, t): (mlua::String, Table)| match &*name.as_bytes() {\n\t\tb\"part\" => super::FilesOp::part(lua, t),\n\t\tb\"done\" => super::FilesOp::done(lua, t),\n\t\tb\"size\" => super::FilesOp::size(lua, t),\n\t\t_ => Err(\"Unknown operation\".into_lua_err())?,\n\t})\n}\n\nfn partitions(lua: &Lua) -> mlua::Result<Function> {\n\tlua.create_async_function(|lua, ()| async move {\n\t\tPARTITIONS\n\t\t\t.read()\n\t\t\t.iter()\n\t\t\t.filter(|&p| !p.systemic())\n\t\t\t.map(|p| {\n\t\t\t\tlua.create_table_from([\n\t\t\t\t\t(\"src\", p.src.clone().into_lua(&lua)?),\n\t\t\t\t\t(\"dist\", p.dist.clone().into_lua(&lua)?),\n\t\t\t\t\t(\"label\", p.label.clone().into_lua(&lua)?),\n\t\t\t\t\t(\"fstype\", p.fstype.clone().into_lua(&lua)?),\n\t\t\t\t\t(\"external\", p.external.into_lua(&lua)?),\n\t\t\t\t\t(\"removable\", p.removable.into_lua(&lua)?),\n\t\t\t\t])\n\t\t\t})\n\t\t\t.collect::<mlua::Result<Vec<Table>>>()\n\t})\n}\n\nfn read_dir(lua: &Lua) -> mlua::Result<Function> {\n\tlua.create_async_function(|lua, (dir, options): (UrlRef, Table)| async move {\n\t\tlet pat = if let Ok(s) = options.raw_get::<mlua::String>(\"glob\") {\n\t\t\tSome(Pattern::from_str(&s.to_str()?)?)\n\t\t} else {\n\t\t\tNone\n\t\t};\n\n\t\tlet limit = options.raw_get(\"limit\").unwrap_or(usize::MAX);\n\t\tlet resolve = options.raw_get::<bool>(\"resolve\")?;\n\n\t\tlet mut it = match provider::read_dir(&*dir).await {\n\t\t\tOk(it) => it,\n\t\t\tErr(e) => return (Value::Nil, Error::Io(e)).into_lua_multi(&lua),\n\t\t};\n\n\t\tlet mut files = vec![];\n\t\twhile let Ok(Some(next)) = it.next().await {\n\t\t\tlet url = next.url();\n\t\t\tif pat.as_ref().is_some_and(|p| !p.match_url(&url, p.is_dir)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet file = if !resolve {\n\t\t\t\tyazi_fs::File::from_dummy(url, next.file_type().await.ok())\n\t\t\t} else if let Ok(cha) = next.metadata().await {\n\t\t\t\tyazi_fs::File::from_follow(url, cha).await\n\t\t\t} else {\n\t\t\t\tyazi_fs::File::from_dummy(url, next.file_type().await.ok())\n\t\t\t};\n\n\t\t\tfiles.push(File::new(file));\n\t\t\tif files.len() == limit {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tlua.create_sequence_from(files)?.into_lua_multi(&lua)\n\t})\n}\n\nfn remove(lua: &Lua) -> mlua::Result<Function> {\n\tlua.create_async_function(|lua, (r#type, url): (mlua::String, UrlRef)| async move {\n\t\tlet result = match &*r#type.as_bytes() {\n\t\t\tb\"file\" => provider::remove_file(&*url).await,\n\t\t\tb\"dir\" => provider::remove_dir(&*url).await,\n\t\t\tb\"dir_all\" => provider::remove_dir_all(&*url).await,\n\t\t\tb\"dir_clean\" => provider::remove_dir_clean(&*url).await,\n\t\t\t_ => Err(\"Removal type must be 'file', 'dir', 'dir_all', or 'dir_clean'\".into_lua_err())?,\n\t\t};\n\n\t\tmatch result {\n\t\t\tOk(()) => true.into_lua_multi(&lua),\n\t\t\tErr(e) => (false, Error::Io(e)).into_lua_multi(&lua),\n\t\t}\n\t})\n}\n\nfn rename(lua: &Lua) -> mlua::Result<Function> {\n\tlua.create_async_function(|lua, (from, to): (UrlRef, UrlRef)| async move {\n\t\tmatch provider::rename(&*from, &*to).await {\n\t\t\tOk(()) => true.into_lua_multi(&lua),\n\t\t\tErr(e) => (false, Error::Io(e)).into_lua_multi(&lua),\n\t\t}\n\t})\n}\n\nfn unique(lua: &Lua) -> mlua::Result<Function> {\n\tlua.create_async_function(|lua, (r#type, url): (mlua::String, UrlRef)| async move {\n\t\tlet result = match &*r#type.as_bytes() {\n\t\t\tb\"dir\" => yazi_vfs::unique_file(url.clone(), true).await,\n\t\t\tb\"file\" => yazi_vfs::unique_file(url.clone(), false).await,\n\t\t\t_ => Err(\"Type must be 'dir' or 'file'\".into_lua_err())?,\n\t\t};\n\n\t\tmatch result {\n\t\t\tOk(u) => Url::new(u).into_lua_multi(&lua),\n\t\t\tErr(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),\n\t\t}\n\t})\n}\n\nfn unique_name(lua: &Lua) -> mlua::Result<Function> {\n\tlua.create_async_function(|lua, url: UrlRef| async move {\n\t\tdeprecate!(lua, \"`fs.unique_name()` is deprecated, use `fs.unique()` instead, in your {}\\nSee #3677 for more details: https://github.com/sxyazi/yazi/pull/3677\");\n\t\tmatch yazi_vfs::unique_name(url.clone(), async { false }).await {\n\t\t\tOk(u) => Url::new(u).into_lua_multi(&lua),\n\t\t\tErr(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),\n\t\t}\n\t})\n}\n\nfn write(lua: &Lua) -> mlua::Result<Function> {\n\tlua.create_async_function(|lua, (url, data): (UrlRef, mlua::String)| async move {\n\t\tmatch provider::write(&*url, data.as_bytes()).await {\n\t\t\tOk(()) => true.into_lua_multi(&lua),\n\t\t\tErr(e) => (false, Error::Io(e)).into_lua_multi(&lua),\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "yazi-plugin/src/fs/mod.rs",
    "content": "yazi_macro::mod_flat!(fs op);\n"
  },
  {
    "path": "yazi-plugin/src/fs/op.rs",
    "content": "use mlua::{IntoLua, Lua, Table, Value};\nuse yazi_binding::{Cha, File, Id, Path, Url};\n\npub(super) struct FilesOp(yazi_fs::FilesOp);\n\nimpl FilesOp {\n\tpub(super) fn part(_: &Lua, t: Table) -> mlua::Result<Self> {\n\t\tlet id: Id = t.raw_get(\"id\")?;\n\t\tlet url: Url = t.raw_get(\"url\")?;\n\t\tlet files: Table = t.raw_get(\"files\")?;\n\n\t\tOk(Self(yazi_fs::FilesOp::Part(\n\t\t\turl.into(),\n\t\t\tfiles\n\t\t\t\t.sequence_values::<File>()\n\t\t\t\t.map(|f| f.map(Into::into))\n\t\t\t\t.collect::<mlua::Result<Vec<_>>>()?,\n\t\t\t*id,\n\t\t)))\n\t}\n\n\tpub(super) fn done(_: &Lua, t: Table) -> mlua::Result<Self> {\n\t\tlet id: Id = t.raw_get(\"id\")?;\n\t\tlet cha: Cha = t.raw_get(\"cha\")?;\n\t\tlet url: Url = t.raw_get(\"url\")?;\n\n\t\tOk(Self(yazi_fs::FilesOp::Done(url.into(), *cha, *id)))\n\t}\n\n\tpub(super) fn size(_: &Lua, t: Table) -> mlua::Result<Self> {\n\t\tlet url: Url = t.raw_get(\"url\")?;\n\t\tlet sizes: Table = t.raw_get(\"sizes\")?;\n\n\t\tOk(Self(yazi_fs::FilesOp::Size(\n\t\t\turl.into(),\n\t\t\tsizes\n\t\t\t\t.pairs::<Path, u64>()\n\t\t\t\t.map(|r| r.map(|(urn, size)| (urn.into(), size)))\n\t\t\t\t.collect::<mlua::Result<_>>()?,\n\t\t)))\n\t}\n}\n\nimpl IntoLua for FilesOp {\n\tfn into_lua(self, lua: &Lua) -> mlua::Result<Value> {\n\t\tlua.create_any_userdata(self.0)?.into_lua(lua)\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/isolate/entry.rs",
    "content": "use mlua::{ExternalResult, ObjectLike};\nuse tokio::runtime::Handle;\nuse yazi_dds::Sendable;\nuse yazi_parser::app::PluginOpt;\n\nuse super::slim_lua;\nuse crate::loader::LOADER;\n\npub async fn entry(opt: PluginOpt) -> mlua::Result<()> {\n\tLOADER.ensure(&opt.id, |_| ()).await.into_lua_err()?;\n\n\ttokio::task::spawn_blocking(move || {\n\t\tlet lua = slim_lua(&opt.id)?;\n\t\tlet job = lua.create_table_from([(\"args\", Sendable::args_to_table(&lua, opt.args)?)])?;\n\n\t\tHandle::current()\n\t\t\t.block_on(async { LOADER.load(&lua, &opt.id).await?.call_async_method(\"entry\", job).await })\n\t})\n\t.await\n\t.into_lua_err()?\n}\n"
  },
  {
    "path": "yazi-plugin/src/isolate/fetch.rs",
    "content": "use mlua::{ExternalResult, FromLua, IntoLua, Lua, ObjectLike, Value};\nuse tokio::runtime::Handle;\nuse yazi_binding::{Error, File};\nuse yazi_dds::Sendable;\nuse yazi_shared::event::ActionCow;\n\nuse super::slim_lua;\nuse crate::loader::LOADER;\n\npub async fn fetch(\n\taction: ActionCow,\n\tfiles: Vec<yazi_fs::File>,\n) -> mlua::Result<(FetchState, Option<Error>)> {\n\tif files.is_empty() {\n\t\treturn Ok((FetchState::Bool(true), None));\n\t}\n\tLOADER.ensure(&action.name, |_| ()).await.into_lua_err()?;\n\n\ttokio::task::spawn_blocking(move || {\n\t\tlet lua = slim_lua(&action.name)?;\n\t\tlet job = lua.create_table_from([\n\t\t\t(\"args\", Sendable::args_to_table_ref(&lua, &action.args)?.into_lua(&lua)?),\n\t\t\t(\"files\", lua.create_sequence_from(files.into_iter().map(File::new))?.into_lua(&lua)?),\n\t\t])?;\n\n\t\tHandle::current().block_on(async {\n\t\t\tLOADER.load(&lua, &action.name).await?.call_async_method(\"fetch\", job).await\n\t\t})\n\t})\n\t.await\n\t.into_lua_err()?\n}\n\n// --- State\npub enum FetchState {\n\tBool(bool),\n\tVec(Vec<bool>),\n}\n\nimpl FetchState {\n\t#[inline]\n\tpub fn get(&self, idx: usize) -> bool {\n\t\tmatch self {\n\t\t\tSelf::Bool(b) => *b,\n\t\t\tSelf::Vec(v) => v.get(idx).copied().unwrap_or(false),\n\t\t}\n\t}\n}\n\nimpl FromLua for FetchState {\n\tfn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {\n\t\tOk(match value {\n\t\t\tValue::Boolean(b) => Self::Bool(b),\n\t\t\tValue::Table(tbl) => Self::Vec(tbl.sequence_values().collect::<mlua::Result<_>>()?),\n\t\t\t_ => Err(mlua::Error::FromLuaConversionError {\n\t\t\t\tfrom:    value.type_name(),\n\t\t\t\tto:      \"FetchState\".to_owned(),\n\t\t\t\tmessage: Some(\"expected a boolean or a table of booleans\".to_owned()),\n\t\t\t})?,\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/isolate/isolate.rs",
    "content": "use mlua::{IntoLua, Lua};\nuse yazi_binding::Runtime;\nuse yazi_macro::plugin_preset as preset;\n\npub fn slim_lua(name: &str) -> mlua::Result<Lua> {\n\tlet lua = Lua::new();\n\tlua.set_app_data(Runtime::new_isolate(name));\n\n\t// Base\n\tlet globals = lua.globals();\n\tglobals.raw_set(\"ui\", crate::elements::compose())?;\n\tglobals.raw_set(\"ya\", crate::utils::compose(true))?;\n\tglobals.raw_set(\"fs\", crate::fs::compose())?;\n\tglobals.raw_set(\"rt\", crate::runtime::compose())?;\n\tglobals.raw_set(\"th\", crate::theme::compose().into_lua(&lua)?)?;\n\n\tyazi_binding::Cha::install(&lua)?;\n\tyazi_binding::File::install(&lua)?;\n\tyazi_binding::Url::install(&lua)?;\n\tyazi_binding::Path::install(&lua)?;\n\n\tyazi_binding::Error::install(&lua)?;\n\tcrate::loader::install(&lua)?;\n\tcrate::process::install(&lua)?;\n\n\t// Addons\n\tlua.load(preset!(\"ya\")).set_name(\"ya.lua\").exec()?;\n\n\tOk(lua)\n}\n"
  },
  {
    "path": "yazi-plugin/src/isolate/mod.rs",
    "content": "yazi_macro::mod_flat!(entry fetch isolate peek preload seek spot);\n"
  },
  {
    "path": "yazi-plugin/src/isolate/peek.rs",
    "content": "use mlua::{ExternalError, HookTriggers, IntoLua, Lua, ObjectLike, Table, VmState};\nuse tokio::{runtime::Handle, select};\nuse tokio_util::sync::CancellationToken;\nuse tracing::error;\nuse yazi_binding::{Error, File, elements::{Rect, Renderable}};\nuse yazi_config::LAYOUT;\nuse yazi_dds::Sendable;\nuse yazi_parser::{app::PluginOpt, mgr::{PreviewLock, UpdatePeekedOpt}};\nuse yazi_proxy::{AppProxy, MgrProxy};\nuse yazi_shared::{event::Action, pool::Symbol};\n\nuse super::slim_lua;\nuse crate::loader::{LOADER, Loader};\n\npub fn peek(\n\taction: &'static Action,\n\tfile: yazi_fs::File,\n\tmime: Symbol<str>,\n\tskip: usize,\n) -> Option<CancellationToken> {\n\tlet (id, ..) = Loader::normalize_id(&action.name).ok()?;\n\n\tlet ct = CancellationToken::new();\n\tif let Some(c) = LOADER.read().get(id) {\n\t\tif let Err(e) = Loader::compatible_or_error(id, c) {\n\t\t\tpeek_error(file, mime, skip, e);\n\t\t\treturn None;\n\t\t} else if c.sync_peek {\n\t\t\tpeek_sync(action, file, mime, skip);\n\t\t} else {\n\t\t\tpeek_async(action, file, mime, skip, ct.clone());\n\t\t}\n\t\treturn Some(ct).filter(|_| !c.sync_peek);\n\t}\n\n\tlet ct_ = ct.clone();\n\ttokio::spawn(async move {\n\t\tselect! {\n\t\t\t_ = ct_.cancelled() => {},\n\t\t\tOk(b) = LOADER.ensure(id, |c| c.sync_peek) => {\n\t\t\t\tif b {\n\t\t\t\t\tpeek_sync(action, file, mime, skip);\n\t\t\t\t} else {\n\t\t\t\t\tpeek_async(action, file, mime, skip, ct_);\n\t\t\t\t}\n\t\t\t},\n\t\t\telse => {}\n\t\t}\n\t});\n\n\tSome(ct)\n}\n\nfn peek_sync(action: &'static Action, file: yazi_fs::File, mime: Symbol<str>, skip: usize) {\n\tlet cb = move |lua: &Lua, plugin: Table| {\n\t\tlet job = lua.create_table_from([\n\t\t\t(\"area\", Rect::from(LAYOUT.get().preview).into_lua(lua)?),\n\t\t\t(\"args\", Sendable::args_to_table_ref(lua, &action.args)?.into_lua(lua)?),\n\t\t\t(\"file\", File::new(file).into_lua(lua)?),\n\t\t\t(\"mime\", mime.into_lua(lua)?),\n\t\t\t(\"skip\", skip.into_lua(lua)?),\n\t\t])?;\n\n\t\tplugin.call_method(\"peek\", job)\n\t};\n\n\tAppProxy::plugin(PluginOpt::new_callback(&*action.name, cb));\n}\n\nfn peek_async(\n\taction: &'static Action,\n\tfile: yazi_fs::File,\n\tmime: Symbol<str>,\n\tskip: usize,\n\tct: CancellationToken,\n) {\n\tlet ct_ = ct.clone();\n\ttokio::task::spawn_blocking(move || {\n\t\tlet future = async {\n\t\t\tlet lua = slim_lua(&action.name)?;\n\t\t\tlua.set_hook(\n\t\t\t\tHookTriggers::new().on_calls().on_returns().every_nth_instruction(2000),\n\t\t\t\tmove |_, dbg| {\n\t\t\t\t\tif ct.is_cancelled() && dbg.source().what != \"C\" {\n\t\t\t\t\t\tErr(\"Peek task cancelled\".into_lua_err())\n\t\t\t\t\t} else {\n\t\t\t\t\t\tOk(VmState::Continue)\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t)?;\n\n\t\t\tlet plugin = LOADER.load(&lua, &action.name).await?;\n\t\t\tlet job = lua.create_table_from([\n\t\t\t\t(\"area\", Rect::from(LAYOUT.get().preview).into_lua(&lua)?),\n\t\t\t\t(\"args\", Sendable::args_to_table_ref(&lua, &action.args)?.into_lua(&lua)?),\n\t\t\t\t(\"file\", File::new(file).into_lua(&lua)?),\n\t\t\t\t(\"mime\", mime.into_lua(&lua)?),\n\t\t\t\t(\"skip\", skip.into_lua(&lua)?),\n\t\t\t])?;\n\n\t\t\tif ct_.is_cancelled() { Ok(()) } else { plugin.call_async_method(\"peek\", job).await }\n\t\t};\n\n\t\tlet result = Handle::current().block_on(async {\n\t\t\tselect! {\n\t\t\t\t_ = ct_.cancelled() => Ok(()),\n\t\t\t\tr = future => r,\n\t\t\t}\n\t\t});\n\n\t\tif let Err(e) = result\n\t\t\t&& !e.to_string().contains(\"Peek task cancelled\")\n\t\t{\n\t\t\terror!(\"{e}\");\n\t\t}\n\t});\n}\n\nfn peek_error(file: yazi_fs::File, mime: Symbol<str>, skip: usize, error: anyhow::Error) {\n\tlet area = LAYOUT.get().preview;\n\tMgrProxy::update_peeked(UpdatePeekedOpt {\n\t\tlock: PreviewLock {\n\t\t\turl: file.url,\n\t\t\tcha: file.cha,\n\t\t\tmime: mime.to_string(),\n\t\t\tskip,\n\t\t\tarea: area.into(),\n\t\t\tdata: vec![\n\t\t\t\tRenderable::Clear(yazi_binding::elements::Clear { area: area.into() }),\n\t\t\t\tRenderable::from(Error::custom(error.to_string())).with_area(area),\n\t\t\t],\n\t\t},\n\t});\n}\n"
  },
  {
    "path": "yazi-plugin/src/isolate/preload.rs",
    "content": "use mlua::{ExternalError, ExternalResult, HookTriggers, IntoLua, ObjectLike, VmState};\nuse tokio::{runtime::Handle, select};\nuse tokio_util::sync::CancellationToken;\nuse yazi_binding::{Error, File, elements::Rect};\nuse yazi_config::LAYOUT;\nuse yazi_dds::Sendable;\nuse yazi_shared::event::Action;\n\nuse super::slim_lua;\nuse crate::loader::LOADER;\n\npub async fn preload(\n\taction: &'static Action,\n\tfile: yazi_fs::File,\n\tct: CancellationToken,\n) -> mlua::Result<(bool, Option<Error>)> {\n\tlet ct_ = ct.clone();\n\ttokio::task::spawn_blocking(move || {\n\t\tlet future = async {\n\t\t\tLOADER.ensure(&action.name, |_| ()).await.into_lua_err()?;\n\n\t\t\tlet lua = slim_lua(&action.name)?;\n\t\t\tlua.set_hook(\n\t\t\t\tHookTriggers::new().on_calls().on_returns().every_nth_instruction(2000),\n\t\t\t\tmove |_, dbg| {\n\t\t\t\t\tif ct.is_cancelled() && dbg.source().what != \"C\" {\n\t\t\t\t\t\tErr(\"Preload task cancelled\".into_lua_err())\n\t\t\t\t\t} else {\n\t\t\t\t\t\tOk(VmState::Continue)\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t)?;\n\n\t\t\tlet plugin = LOADER.load(&lua, &action.name).await?;\n\t\t\tlet job = lua.create_table_from([\n\t\t\t\t(\"area\", Rect::from(LAYOUT.get().preview).into_lua(&lua)?),\n\t\t\t\t(\"args\", Sendable::args_to_table_ref(&lua, &action.args)?.into_lua(&lua)?),\n\t\t\t\t(\"file\", File::new(file).into_lua(&lua)?),\n\t\t\t\t(\"skip\", 0.into_lua(&lua)?),\n\t\t\t])?;\n\n\t\t\tif ct_.is_cancelled() {\n\t\t\t\tOk((false, None))\n\t\t\t} else {\n\t\t\t\tplugin.call_async_method(\"preload\", job).await\n\t\t\t}\n\t\t};\n\n\t\tHandle::current().block_on(async {\n\t\t\tselect! {\n\t\t\t\t_ = ct_.cancelled() => Ok((false, None)),\n\t\t\t\tr = future => match r {\n\t\t\t\t\tErr(e) if e.to_string().contains(\"Preload task cancelled\") => Ok((false, None)),\n\t\t\t\t\tOk(_) | Err(_) => r,\n\t\t\t\t},\n\t\t\t}\n\t\t})\n\t})\n\t.await\n\t.into_lua_err()?\n}\n"
  },
  {
    "path": "yazi-plugin/src/isolate/seek.rs",
    "content": "use mlua::{IntoLua, Lua, ObjectLike, Table};\nuse yazi_binding::{File, elements::Rect};\nuse yazi_config::LAYOUT;\nuse yazi_parser::app::PluginOpt;\nuse yazi_proxy::AppProxy;\nuse yazi_shared::event::Action;\n\npub fn seek_sync(action: &'static Action, file: yazi_fs::File, units: i16) {\n\tlet cb = move |lua: &Lua, plugin: Table| {\n\t\tlet job = lua.create_table_from([\n\t\t\t(\"file\", File::new(file).into_lua(lua)?),\n\t\t\t(\"area\", Rect::from(LAYOUT.get().preview).into_lua(lua)?),\n\t\t\t(\"units\", units.into_lua(lua)?),\n\t\t])?;\n\n\t\tplugin.call_method(\"seek\", job)\n\t};\n\n\tAppProxy::plugin(PluginOpt::new_callback(&*action.name, cb));\n}\n"
  },
  {
    "path": "yazi-plugin/src/isolate/spot.rs",
    "content": "use mlua::{ExternalError, ExternalResult, HookTriggers, IntoLua, ObjectLike, VmState};\nuse tokio::{runtime::Handle, select};\nuse tokio_util::sync::CancellationToken;\nuse tracing::error;\nuse yazi_binding::{File, Id};\nuse yazi_dds::Sendable;\nuse yazi_shared::{Ids, event::Action, pool::Symbol};\n\nuse super::slim_lua;\nuse crate::loader::LOADER;\n\nstatic IDS: Ids = Ids::new();\n\npub fn spot(\n\taction: &'static Action,\n\tfile: yazi_fs::File,\n\tmime: Symbol<str>,\n\tskip: usize,\n) -> CancellationToken {\n\tlet ct = CancellationToken::new();\n\tlet (ct1, ct2) = (ct.clone(), ct.clone());\n\n\ttokio::task::spawn_blocking(move || {\n\t\tlet future = async {\n\t\t\tLOADER.ensure(&action.name, |_| ()).await.into_lua_err()?;\n\n\t\t\tlet lua = slim_lua(&action.name)?;\n\t\t\tlua.set_hook(\n\t\t\t\tHookTriggers::new().on_calls().on_returns().every_nth_instruction(2000),\n\t\t\t\tmove |_, dbg| {\n\t\t\t\t\tif ct1.is_cancelled() && dbg.source().what != \"C\" {\n\t\t\t\t\t\tErr(\"Spot task cancelled\".into_lua_err())\n\t\t\t\t\t} else {\n\t\t\t\t\t\tOk(VmState::Continue)\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t)?;\n\n\t\t\tlet plugin = LOADER.load(&lua, &action.name).await?;\n\t\t\tlet job = lua.create_table_from([\n\t\t\t\t(\"id\", Id(IDS.next()).into_lua(&lua)?),\n\t\t\t\t(\"args\", Sendable::args_to_table_ref(&lua, &action.args)?.into_lua(&lua)?),\n\t\t\t\t(\"file\", File::new(file).into_lua(&lua)?),\n\t\t\t\t(\"mime\", mime.into_lua(&lua)?),\n\t\t\t\t(\"skip\", skip.into_lua(&lua)?),\n\t\t\t])?;\n\n\t\t\tif ct2.is_cancelled() { Ok(()) } else { plugin.call_async_method(\"spot\", job).await }\n\t\t};\n\n\t\tHandle::current().block_on(async {\n\t\t\tselect! {\n\t\t\t\t_ = ct2.cancelled() => {},\n\t\t\t\tErr(e) = future => if !e.to_string().contains(\"Spot task cancelled\") {\n\t\t\t\t\terror!(\"{e}\");\n\t\t\t\t},\n\t\t\t\telse => {}\n\t\t\t}\n\t\t});\n\t});\n\n\tct\n}\n"
  },
  {
    "path": "yazi-plugin/src/lib.rs",
    "content": "yazi_macro::mod_pub!(elements external fs isolate loader process pubsub runtime theme utils);\n\nyazi_macro::mod_flat!(lua);\n\npub fn init() -> anyhow::Result<()> {\n\tcrate::loader::init();\n\tcrate::init_lua()?;\n\tOk(())\n}\n"
  },
  {
    "path": "yazi-plugin/src/loader/chunk.rs",
    "content": "use std::borrow::Cow;\n\nuse mlua::{AsChunk, ChunkMode};\nuse yazi_shared::natsort;\n\npub struct Chunk {\n\tpub mode:       ChunkMode,\n\tpub bytes:      Cow<'static, [u8]>,\n\tpub since:      String,\n\tpub sync_peek:  bool,\n\tpub sync_entry: bool,\n}\n\nimpl Chunk {\n\t#[inline]\n\tpub fn compatible(&self) -> bool {\n\t\tlet s = yazi_boot::actions::Actions::version();\n\t\tnatsort(s.as_bytes(), self.since.as_bytes(), false) != std::cmp::Ordering::Less\n\t}\n\n\tfn analyze(&mut self) {\n\t\tfor line in self.bytes.split(|&b| b == b'\\n') {\n\t\t\tif line.trim_ascii().is_empty() {\n\t\t\t\tcontinue;\n\t\t\t};\n\n\t\t\tlet Some(rest) = line.strip_prefix(b\"---\") else { break };\n\t\t\tlet rest = rest.trim_ascii();\n\n\t\t\tlet Some(i) = rest.iter().position(|&b| b == b' ' || b == b'\\t') else { break };\n\t\t\tmatch (rest[..i].trim_ascii(), rest[i..].trim_ascii()) {\n\t\t\t\t(b\"@sync\", b\"peek\") => self.sync_peek = true,\n\t\t\t\t(b\"@sync\", b\"entry\") => self.sync_entry = true,\n\n\t\t\t\t(b\"@since\", b\"\") => continue,\n\t\t\t\t(b\"@since\", b) => self.since = String::from_utf8_lossy(b).to_string(),\n\n\t\t\t\t(_, []) => break,\n\t\t\t\t(b, _) if b.strip_prefix(b\"@\").unwrap_or(b\"\").is_empty() => break,\n\t\t\t\t_ => continue,\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl From<Cow<'static, [u8]>> for Chunk {\n\tfn from(b: Cow<'static, [u8]>) -> Self {\n\t\tlet mut chunk = Self {\n\t\t\tmode:       ChunkMode::Text,\n\t\t\tbytes:      b,\n\t\t\tsince:      String::new(),\n\t\t\tsync_entry: false,\n\t\t\tsync_peek:  false,\n\t\t};\n\t\tchunk.analyze();\n\t\tchunk\n\t}\n}\n\nimpl From<&'static [u8]> for Chunk {\n\tfn from(b: &'static [u8]) -> Self { Self::from(Cow::Borrowed(b)) }\n}\n\nimpl From<Vec<u8>> for Chunk {\n\tfn from(b: Vec<u8>) -> Self { Self::from(Cow::Owned(b)) }\n}\n\nimpl AsChunk for &Chunk {\n\tfn mode(&self) -> Option<ChunkMode> { Some(self.mode) }\n\n\tfn source<'a>(&self) -> std::io::Result<Cow<'a, [u8]>>\n\twhere\n\t\tSelf: 'a,\n\t{\n\t\tOk(Cow::Borrowed(&self.bytes))\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/loader/loader.rs",
    "content": "use std::{borrow::Cow, ops::Deref};\n\nuse anyhow::{Context, Result, bail, ensure};\nuse hashbrown::HashMap;\nuse mlua::{ChunkMode, ExternalError, Lua, Table};\nuse parking_lot::RwLock;\nuse yazi_boot::BOOT;\nuse yazi_fs::provider::local::Local;\nuse yazi_macro::plugin_preset as preset;\nuse yazi_shared::{BytesExt, LOG_LEVEL, RoCell};\n\nuse super::Chunk;\n\npub static LOADER: RoCell<Loader> = RoCell::new();\n\npub struct Loader {\n\tcache: RwLock<HashMap<String, Chunk>>,\n}\n\nimpl Deref for Loader {\n\ttype Target = RwLock<HashMap<String, Chunk>>;\n\n\tfn deref(&self) -> &Self::Target { &self.cache }\n}\n\nimpl Default for Loader {\n\tfn default() -> Self {\n\t\tlet cache = HashMap::from_iter([\n\t\t\t// Plugins\n\t\t\t(\"archive\".to_owned(), preset!(\"plugins/archive\").into()),\n\t\t\t(\"code\".to_owned(), preset!(\"plugins/code\").into()),\n\t\t\t(\"dds\".to_owned(), preset!(\"plugins/dds\").into()),\n\t\t\t(\"empty\".to_owned(), preset!(\"plugins/empty\").into()),\n\t\t\t(\"extract\".to_owned(), preset!(\"plugins/extract\").into()),\n\t\t\t(\"file\".to_owned(), preset!(\"plugins/file\").into()),\n\t\t\t(\"folder\".to_owned(), preset!(\"plugins/folder\").into()),\n\t\t\t(\"font\".to_owned(), preset!(\"plugins/font\").into()),\n\t\t\t(\"fzf\".to_owned(), preset!(\"plugins/fzf\").into()),\n\t\t\t(\"image\".to_owned(), preset!(\"plugins/image\").into()),\n\t\t\t(\"init\".to_owned(), preset!(\"plugins/init\").into()),\n\t\t\t(\"json\".to_owned(), preset!(\"plugins/json\").into()),\n\t\t\t(\"magick\".to_owned(), preset!(\"plugins/magick\").into()),\n\t\t\t(\"mime\".to_owned(), preset!(\"plugins/mime\").into()),\n\t\t\t(\"mime.dir\".to_owned(), preset!(\"plugins/mime-dir\").into()),\n\t\t\t(\"mime.local\".to_owned(), preset!(\"plugins/mime-local\").into()),\n\t\t\t(\"mime.remote\".to_owned(), preset!(\"plugins/mime-remote\").into()),\n\t\t\t(\"multi\".to_owned(), preset!(\"plugins/multi\").into()),\n\t\t\t(\"noop\".to_owned(), preset!(\"plugins/noop\").into()),\n\t\t\t(\"null\".to_owned(), preset!(\"plugins/null\").into()),\n\t\t\t(\"pdf\".to_owned(), preset!(\"plugins/pdf\").into()),\n\t\t\t(\"session\".to_owned(), preset!(\"plugins/session\").into()),\n\t\t\t(\"svg\".to_owned(), preset!(\"plugins/svg\").into()),\n\t\t\t(\"vfs\".to_owned(), preset!(\"plugins/vfs\").into()),\n\t\t\t(\"video\".to_owned(), preset!(\"plugins/video\").into()),\n\t\t\t(\"zoxide\".to_owned(), preset!(\"plugins/zoxide\").into()),\n\t\t\t// Components\n\t\t\t(\"current\".to_owned(), [][..].into()),\n\t\t\t(\"entity\".to_owned(), [][..].into()),\n\t\t\t(\"header\".to_owned(), [][..].into()),\n\t\t\t(\"linemode\".to_owned(), [][..].into()),\n\t\t\t(\"marker\".to_owned(), [][..].into()),\n\t\t\t(\"modal\".to_owned(), [][..].into()),\n\t\t\t(\"parent\".to_owned(), [][..].into()),\n\t\t\t(\"preview\".to_owned(), [][..].into()),\n\t\t\t(\"progress\".to_owned(), [][..].into()),\n\t\t\t(\"rail\".to_owned(), [][..].into()),\n\t\t\t(\"root\".to_owned(), [][..].into()),\n\t\t\t(\"status\".to_owned(), [][..].into()),\n\t\t\t(\"tab\".to_owned(), [][..].into()),\n\t\t\t(\"tabs\".to_owned(), [][..].into()),\n\t\t\t(\"tasks\".to_owned(), [][..].into()),\n\t\t]);\n\t\tSelf { cache: RwLock::new(cache) }\n\t}\n}\n\nimpl Loader {\n\tpub async fn ensure<F, T>(&self, id: &str, f: F) -> Result<T>\n\twhere\n\t\tF: FnOnce(&Chunk) -> T,\n\t{\n\t\tlet (id, plugin, entry) = Self::normalize_id(id)?;\n\t\tif let Some(c) = self.cache.read().get(id) {\n\t\t\treturn Self::compatible_or_error(id, c).map(|_| f(c));\n\t\t}\n\n\t\tlet p = BOOT.plugin_dir.join(format!(\"{plugin}.yazi/{entry}.lua\"));\n\t\tlet chunk = Local::regular(&p)\n\t\t\t.read()\n\t\t\t.await\n\t\t\t.with_context(|| format!(\"Failed to load plugin from {p:?}\"))?\n\t\t\t.into();\n\n\t\tlet result = Self::compatible_or_error(id, &chunk);\n\t\tlet inspect = f(&chunk);\n\n\t\tself.cache.write().insert(id.to_owned(), chunk);\n\t\tresult.map(|_| inspect)\n\t}\n\n\tpub async fn load(&self, lua: &Lua, id: &str) -> mlua::Result<Table> {\n\t\tlet (id, ..) = Self::normalize_id(id)?;\n\n\t\tlet loaded: Table = lua.globals().raw_get::<Table>(\"package\")?.raw_get(\"loaded\")?;\n\t\tif let Ok(t) = loaded.raw_get(id) {\n\t\t\treturn Ok(t);\n\t\t}\n\n\t\tlet t = self.load_new(lua, id).await?;\n\t\tt.raw_set(\"_id\", lua.create_string(id)?)?;\n\n\t\tloaded.raw_set(id, t.clone())?;\n\t\tOk(t)\n\t}\n\n\tasync fn load_new(&self, lua: &Lua, id: &str) -> mlua::Result<Table> {\n\t\tlet (id, ..) = Self::normalize_id(id)?;\n\n\t\tlet mut mode = ChunkMode::Text;\n\t\tlet f = match self.cache.read().get(id) {\n\t\t\tSome(c) => {\n\t\t\t\tmode = c.mode;\n\t\t\t\tlua.load(c).set_name(id).into_function()\n\t\t\t}\n\t\t\tNone => Err(format!(\"Plugin `{id}` not found\").into_lua_err()),\n\t\t}?;\n\n\t\tif mode != ChunkMode::Binary {\n\t\t\tlet b = f.dump(LOG_LEVEL.get().is_none());\n\t\t\tif let Some(c) = self.cache.write().get_mut(id) {\n\t\t\t\tc.mode = ChunkMode::Binary;\n\t\t\t\tc.bytes = Cow::Owned(b);\n\t\t\t}\n\t\t}\n\n\t\tf.call_async(()).await\n\t}\n\n\tpub fn load_chunk(&self, lua: &Lua, id: &str, chunk: &Chunk) -> mlua::Result<Table> {\n\t\tlet (id, ..) = Self::normalize_id(id)?;\n\n\t\tlet loaded: Table = lua.globals().raw_get::<Table>(\"package\")?.raw_get(\"loaded\")?;\n\t\tif let Ok(t) = loaded.raw_get(id) {\n\t\t\treturn Ok(t);\n\t\t}\n\n\t\tlet t: Table = lua.load(chunk).set_name(id).call(())?;\n\t\tt.raw_set(\"_id\", lua.create_string(id)?)?;\n\n\t\tloaded.raw_set(id, t.clone())?;\n\t\tOk(t)\n\t}\n\n\tpub fn try_load(&self, lua: &Lua, id: &str) -> mlua::Result<Table> {\n\t\tlet (id, ..) = Self::normalize_id(id)?;\n\t\tlua.globals().raw_get::<Table>(\"package\")?.raw_get::<Table>(\"loaded\")?.raw_get(id)\n\t}\n\n\tpub fn compatible_or_error(id: &str, chunk: &Chunk) -> Result<()> {\n\t\tif chunk.compatible() {\n\t\t\treturn Ok(());\n\t\t}\n\n\t\tbail!(\n\t\t\t\"Plugin `{id}` requires at least Yazi {}, but your current version is Yazi {}.\",\n\t\t\tchunk.since,\n\t\t\tyazi_boot::actions::Actions::version()\n\t\t);\n\t}\n\n\tpub fn normalize_id(id: &str) -> anyhow::Result<(&str, &str, &str)> {\n\t\tlet id = id.trim_end_matches(\".main\");\n\t\tlet (plugin, entry) = if let Some((a, b)) = id.split_once(\".\") { (a, b) } else { (id, \"main\") };\n\n\t\tensure!(plugin.as_bytes().kebab_cased(), \"Plugin name `{plugin}` must be in kebab-case\");\n\t\tensure!(entry.as_bytes().kebab_cased(), \"Entry name `{entry}` must be in kebab-case\");\n\t\tOk((id, plugin, entry))\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/loader/mod.rs",
    "content": "yazi_macro::mod_flat!(chunk loader require);\n\npub(super) fn init() { LOADER.with(<_>::default); }\n\npub(super) fn install(lua: &mlua::Lua) -> mlua::Result<()> { Require::install(lua) }\n"
  },
  {
    "path": "yazi-plugin/src/loader/require.rs",
    "content": "use std::{borrow::Cow, sync::Arc};\n\nuse mlua::{ExternalResult, Function, IntoLua, Lua, MetaMethod, MultiValue, ObjectLike, Table, Value};\nuse yazi_binding::{runtime, runtime_mut};\n\nuse super::LOADER;\n\npub(super) struct Require;\n\nimpl Require {\n\tpub(super) fn install(lua: &Lua) -> mlua::Result<()> {\n\t\tlua.globals().raw_set(\n\t\t\t\"require\",\n\t\t\tlua.create_async_function(|lua, id: mlua::String| async move {\n\t\t\t\tlet id = id.to_str()?;\n\t\t\t\tlet id = Self::absolute_id(&lua, &id)?;\n\t\t\t\tLOADER.ensure(&id, |_| ()).await.into_lua_err()?;\n\n\t\t\t\truntime_mut!(lua)?.push(&id);\n\t\t\t\tlet mod_ = LOADER.load(&lua, &id).await;\n\t\t\t\truntime_mut!(lua)?.pop()?;\n\n\t\t\t\tSelf::create_mt(&lua, id.into_owned(), mod_?)\n\t\t\t})?,\n\t\t)\n\t}\n\n\tfn create_mt(lua: &Lua, id: String, r#mod: Table) -> mlua::Result<Table> {\n\t\tlet id: Arc<str> = Arc::from(id);\n\t\tlet mt = lua.create_table_from([\n\t\t\t(\n\t\t\t\tMetaMethod::Index.name(),\n\t\t\t\tlua.create_function(move |lua, (ts, key): (Table, mlua::String)| {\n\t\t\t\t\tmatch ts.raw_get::<Table>(\"__mod\")?.raw_get::<Value>(&key)? {\n\t\t\t\t\t\tValue::Function(_) => {\n\t\t\t\t\t\t\tSelf::create_wrapper(lua, id.clone(), &key.to_str()?)?.into_lua(lua)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tv => Ok(v),\n\t\t\t\t\t}\n\t\t\t\t})?,\n\t\t\t),\n\t\t\t(\n\t\t\t\tMetaMethod::NewIndex.name(),\n\t\t\t\tlua.create_function(move |_, (ts, key, value): (Table, mlua::String, Value)| {\n\t\t\t\t\tts.raw_get::<Table>(\"__mod\")?.raw_set(key, value)\n\t\t\t\t})?,\n\t\t\t),\n\t\t])?;\n\n\t\tlet ts = lua.create_table_from([(\"__mod\", r#mod)])?;\n\t\tts.set_metatable(Some(mt))?;\n\t\tOk(ts)\n\t}\n\n\tfn create_wrapper(lua: &Lua, id: Arc<str>, f: &str) -> mlua::Result<Function> {\n\t\tlet f: Arc<str> = Arc::from(f);\n\n\t\tlua.create_async_function(move |lua, args: MultiValue| {\n\t\t\tlet (id, f) = (id.clone(), f.clone());\n\t\t\tasync move {\n\t\t\t\tlet (r#mod, args) = Self::split_mod_and_args(&lua, &id, args)?;\n\t\t\t\truntime_mut!(lua)?.push(&id);\n\t\t\t\tlet result = r#mod.call_async_function::<MultiValue>(&f, args).await;\n\t\t\t\truntime_mut!(lua)?.pop()?;\n\t\t\t\tresult\n\t\t\t}\n\t\t})\n\t}\n\n\tfn split_mod_and_args(\n\t\tlua: &Lua,\n\t\tid: &str,\n\t\tmut args: MultiValue,\n\t) -> mlua::Result<(Table, MultiValue)> {\n\t\tlet Some(front) = args.pop_front() else {\n\t\t\treturn Ok((LOADER.try_load(lua, id)?, args));\n\t\t};\n\t\tlet Value::Table(tbl) = front else {\n\t\t\targs.push_front(front);\n\t\t\treturn Ok((LOADER.try_load(lua, id)?, args));\n\t\t};\n\t\tOk(if let Ok(r#mod) = tbl.raw_get::<Table>(\"__mod\") {\n\t\t\targs.push_front(Value::Table(r#mod.clone()));\n\t\t\t(r#mod, args)\n\t\t} else {\n\t\t\targs.push_front(Value::Table(tbl));\n\t\t\t(LOADER.try_load(lua, id)?, args)\n\t\t})\n\t}\n\n\tfn absolute_id<'a>(lua: &Lua, id: &'a str) -> mlua::Result<Cow<'a, str>> {\n\t\tlet Some(stripped) = id.strip_prefix('.') else { return Ok(id.into()) };\n\n\t\tlet rt = runtime!(lua)?;\n\t\tlet cur = rt.current()?;\n\t\tOk(format!(\"{}.{stripped}\", cur.split('.').next().unwrap_or(cur)).into())\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/lua.rs",
    "content": "use anyhow::{Context, Result};\nuse futures::executor::block_on;\nuse mlua::Lua;\nuse yazi_binding::{Runtime, runtime_scope};\nuse yazi_boot::BOOT;\nuse yazi_macro::plugin_preset as preset;\nuse yazi_shared::RoCell;\n\npub static LUA: RoCell<Lua> = RoCell::new();\n\npub(super) fn init_lua() -> Result<()> {\n\tLUA.init(Lua::new());\n\n\tstage_1(&LUA).context(\"Lua setup failed\")?;\n\tstage_2(&LUA).context(\"Lua runtime failed\")?;\n\tOk(())\n}\n\nfn stage_1(lua: &'static Lua) -> Result<()> {\n\tlua.set_app_data(Runtime::default());\n\n\t// Base\n\tlet globals = lua.globals();\n\tglobals.raw_set(\"ui\", crate::elements::compose())?;\n\tglobals.raw_set(\"ya\", crate::utils::compose(false))?;\n\tglobals.raw_set(\"fs\", crate::fs::compose())?;\n\tglobals.raw_set(\"ps\", crate::pubsub::compose())?;\n\tglobals.raw_set(\"rt\", crate::runtime::compose())?;\n\tglobals.raw_set(\"th\", crate::theme::compose())?;\n\n\tyazi_binding::Error::install(lua)?;\n\tyazi_binding::Cha::install(lua)?;\n\tcrate::loader::install(lua)?;\n\tcrate::process::install(lua)?;\n\tyazi_binding::File::install(lua)?;\n\tyazi_binding::Url::install(lua)?;\n\tyazi_binding::Path::install(lua)?;\n\n\t// Addons\n\tlua.load(preset!(\"ya\")).set_name(\"ya.lua\").exec()?;\n\n\t// Components\n\tlua.load(preset!(\"components/current\")).set_name(\"current.lua\").exec()?;\n\tlua.load(preset!(\"components/entity\")).set_name(\"entity.lua\").exec()?;\n\tlua.load(preset!(\"components/header\")).set_name(\"header.lua\").exec()?;\n\tlua.load(preset!(\"components/linemode\")).set_name(\"linemode.lua\").exec()?;\n\n\tlua.load(preset!(\"components/marker\")).set_name(\"marker.lua\").exec()?;\n\tlua.load(preset!(\"components/modal\")).set_name(\"modal.lua\").exec()?;\n\tlua.load(preset!(\"components/parent\")).set_name(\"parent.lua\").exec()?;\n\tlua.load(preset!(\"components/preview\")).set_name(\"preview.lua\").exec()?;\n\tlua.load(preset!(\"components/progress\")).set_name(\"progress.lua\").exec()?;\n\tlua.load(preset!(\"components/rail\")).set_name(\"rail.lua\").exec()?;\n\tlua.load(preset!(\"components/root\")).set_name(\"root.lua\").exec()?;\n\tlua.load(preset!(\"components/status\")).set_name(\"status.lua\").exec()?;\n\tlua.load(preset!(\"components/tab\")).set_name(\"tab.lua\").exec()?;\n\tlua.load(preset!(\"components/tabs\")).set_name(\"tabs.lua\").exec()?;\n\tlua.load(preset!(\"components/tasks\")).set_name(\"tasks.lua\").exec()?;\n\n\tOk(())\n}\n\nfn stage_2(lua: &'static Lua) -> mlua::Result<()> {\n\tlua.load(preset!(\"setup\")).set_name(\"setup.lua\").exec()?;\n\tlua.load(preset!(\"compat\")).set_name(\"compat.lua\").exec()?;\n\n\tif let Ok(b) = std::fs::read(BOOT.config_dir.join(\"init.lua\")) {\n\t\truntime_scope!(lua, \"init\", block_on(lua.load(b).set_name(\"init.lua\").exec_async()))?;\n\t}\n\n\tOk(())\n}\n"
  },
  {
    "path": "yazi-plugin/src/process/child.rs",
    "content": "use std::{ops::DerefMut, process::ExitStatus, time::Duration};\n\nuse futures::future::try_join3;\nuse mlua::{AnyUserData, ExternalError, IntoLua, IntoLuaMulti, Table, UserData, UserDataMethods, Value};\nuse tokio::{io::{self, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}, process::{ChildStderr, ChildStdin, ChildStdout}, select};\nuse yazi_binding::Error;\n\nuse super::Status;\nuse crate::process::Output;\n\npub struct Child {\n\tinner:      tokio::process::Child,\n\tstdin:      Option<BufWriter<ChildStdin>>,\n\tstdout:     Option<BufReader<ChildStdout>>,\n\tstderr:     Option<BufReader<ChildStderr>>,\n\t#[cfg(windows)]\n\tjob_handle: Option<std::os::windows::io::RawHandle>,\n}\n\n#[cfg(windows)]\nimpl Drop for Child {\n\tfn drop(&mut self) {\n\t\tif let Some(h) = self.job_handle.take() {\n\t\t\tunsafe { windows_sys::Win32::Foundation::CloseHandle(h) };\n\t\t}\n\t}\n}\n\nimpl Child {\n\tpub fn new(\n\t\tmut inner: tokio::process::Child,\n\t\t#[cfg(windows)] job_handle: Option<std::os::windows::io::RawHandle>,\n\t) -> Self {\n\t\tlet stdin = inner.stdin.take().map(BufWriter::new);\n\t\tlet stdout = inner.stdout.take().map(BufReader::new);\n\t\tlet stderr = inner.stderr.take().map(BufReader::new);\n\t\tSelf {\n\t\t\tinner,\n\t\t\tstdin,\n\t\t\tstdout,\n\t\t\tstderr,\n\t\t\t#[cfg(windows)]\n\t\t\tjob_handle,\n\t\t}\n\t}\n\n\tpub(super) async fn wait(&mut self) -> io::Result<ExitStatus> {\n\t\tdrop(self.stdin.take());\n\t\tself.inner.wait().await\n\t}\n\n\tpub(super) async fn status(&mut self) -> io::Result<ExitStatus> {\n\t\tdrop(self.stdin.take());\n\t\tdrop(self.stdout.take());\n\t\tdrop(self.stderr.take());\n\t\tself.inner.wait().await\n\t}\n\n\tasync fn read_line(&mut self) -> (Option<Vec<u8>>, u8) {\n\t\tasync fn read(r: Option<impl AsyncBufReadExt + Unpin>) -> Option<Vec<u8>> {\n\t\t\tlet mut buf = Vec::new();\n\t\t\tmatch r?.read_until(b'\\n', &mut buf).await {\n\t\t\t\tOk(0) | Err(_) => None,\n\t\t\t\tOk(_) => Some(buf),\n\t\t\t}\n\t\t}\n\n\t\tselect! {\n\t\t\tr @ Some(_) = read(self.stdout.as_mut()) => (r, 0u8),\n\t\t\tr @ Some(_) = read(self.stderr.as_mut()) => (r, 1u8),\n\t\t\telse => (None, 2u8),\n\t\t}\n\t}\n\n\tpub(super) async fn wait_with_output(mut self) -> io::Result<std::process::Output> {\n\t\tasync fn read(r: &mut Option<impl AsyncBufReadExt + Unpin>) -> io::Result<Vec<u8>> {\n\t\t\tlet mut vec = Vec::new();\n\t\t\tif let Some(r) = r.as_mut() {\n\t\t\t\tr.read_to_end(&mut vec).await?;\n\t\t\t}\n\t\t\tOk(vec)\n\t\t}\n\n\t\t// Ensure stdin is closed so the child isn't stuck waiting on input while the\n\t\t// parent is waiting for it to exit.\n\t\tdrop(self.stdin.take());\n\n\t\t// Drop happens after `try_join` due to <https://github.com/tokio-rs/tokio/issues/4309>\n\t\tlet mut stdout = self.stdout.take();\n\t\tlet mut stderr = self.stderr.take();\n\n\t\tlet result = try_join3(self.inner.wait(), read(&mut stdout), read(&mut stderr)).await?;\n\t\tOk(std::process::Output { status: result.0, stdout: result.1, stderr: result.2 })\n\t}\n}\n\nimpl UserData for Child {\n\tfn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {\n\t\tmethods.add_method(\"id\", |_, me, ()| Ok(me.inner.id()));\n\n\t\tmethods.add_async_method_mut(\"read\", |_, mut me, len: usize| async move {\n\t\t\tasync fn read(r: Option<impl AsyncBufReadExt + Unpin>, len: usize) -> Option<Vec<u8>> {\n\t\t\t\tlet mut r = r?;\n\t\t\t\tlet mut buf = vec![0; len];\n\t\t\t\tmatch r.read(&mut buf).await {\n\t\t\t\t\tOk(0) | Err(_) => return None,\n\t\t\t\t\tOk(n) => buf.truncate(n),\n\t\t\t\t}\n\t\t\t\tSome(buf)\n\t\t\t}\n\n\t\t\tlet me = me.deref_mut();\n\t\t\tOk(select! {\n\t\t\t\tSome(r) = read(me.stdout.as_mut(), len) => (r, 0u8),\n\t\t\t\tSome(r) = read(me.stderr.as_mut(), len) => (r, 1u8),\n\t\t\t\telse => (vec![], 2u8)\n\t\t\t})\n\t\t});\n\t\tmethods.add_async_method_mut(\"read_line\", |lua, mut me, ()| async move {\n\t\t\tmatch me.read_line().await {\n\t\t\t\t(Some(b), event) => (lua.create_external_string(b)?, event).into_lua_multi(&lua),\n\t\t\t\t(None, event) => (Value::Nil, event).into_lua_multi(&lua),\n\t\t\t}\n\t\t});\n\t\t// TODO: deprecate this method\n\t\tmethods.add_async_method_mut(\"read_line_with\", |lua, mut me, options: Table| async move {\n\t\t\tlet timeout = Duration::from_millis(options.raw_get(\"timeout\")?);\n\t\t\tlet Ok(result) = tokio::time::timeout(timeout, me.read_line()).await else {\n\t\t\t\treturn (Value::Nil, 3u8).into_lua_multi(&lua);\n\t\t\t};\n\t\t\tmatch result {\n\t\t\t\t(Some(b), event) => (lua.create_external_string(b)?, event).into_lua_multi(&lua),\n\t\t\t\t(None, event) => (Value::Nil, event).into_lua_multi(&lua),\n\t\t\t}\n\t\t});\n\n\t\tmethods.add_async_method_mut(\"write_all\", |lua, mut me, src: mlua::String| async move {\n\t\t\tlet Some(stdin) = &mut me.stdin else {\n\t\t\t\treturn Err(\"stdin is not piped\".into_lua_err());\n\t\t\t};\n\t\t\tmatch stdin.write_all(&src.as_bytes()).await {\n\t\t\t\tOk(()) => true.into_lua_multi(&lua),\n\t\t\t\tErr(e) => (false, Error::Io(e)).into_lua_multi(&lua),\n\t\t\t}\n\t\t});\n\t\tmethods.add_async_method_mut(\"flush\", |lua, mut me, ()| async move {\n\t\t\tlet Some(stdin) = &mut me.stdin else {\n\t\t\t\treturn Err(\"stdin is not piped\".into_lua_err());\n\t\t\t};\n\t\t\tmatch stdin.flush().await {\n\t\t\t\tOk(()) => true.into_lua_multi(&lua),\n\t\t\t\tErr(e) => (false, Error::Io(e)).into_lua_multi(&lua),\n\t\t\t}\n\t\t});\n\n\t\tmethods.add_async_method_mut(\"wait\", |lua, mut me, ()| async move {\n\t\t\tmatch me.wait().await {\n\t\t\t\tOk(status) => Status::new(status).into_lua_multi(&lua),\n\t\t\t\tErr(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),\n\t\t\t}\n\t\t});\n\t\tmethods.add_async_function(\"wait_with_output\", |lua, ud: AnyUserData| async move {\n\t\t\tmatch ud.take::<Self>()?.wait_with_output().await {\n\t\t\t\tOk(output) => Output::new(output).into_lua_multi(&lua),\n\t\t\t\tErr(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),\n\t\t\t}\n\t\t});\n\t\tmethods.add_async_method_mut(\"try_wait\", |lua, mut me, ()| async move {\n\t\t\tmatch me.inner.try_wait() {\n\t\t\t\tOk(Some(status)) => Status::new(status).into_lua_multi(&lua),\n\t\t\t\tOk(None) => Value::Nil.into_lua_multi(&lua),\n\t\t\t\tErr(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),\n\t\t\t}\n\t\t});\n\t\tmethods.add_method_mut(\"start_kill\", |lua, me, ()| match me.inner.start_kill() {\n\t\t\tOk(_) => true.into_lua_multi(lua),\n\t\t\tErr(e) => (false, Error::Io(e)).into_lua_multi(lua),\n\t\t});\n\n\t\tmethods.add_method_mut(\"take_stdin\", |lua, me, ()| match me.stdin.take() {\n\t\t\tSome(stdin) => lua.create_any_userdata(stdin.into_inner())?.into_lua(lua),\n\t\t\tNone => Ok(Value::Nil),\n\t\t});\n\t\tmethods.add_method_mut(\"take_stdout\", |lua, me, ()| match me.stdout.take() {\n\t\t\tSome(stdout) => lua.create_any_userdata(stdout.into_inner())?.into_lua(lua),\n\t\t\tNone => Ok(Value::Nil),\n\t\t});\n\t\tmethods.add_method_mut(\"take_stderr\", |lua, me, ()| match me.stderr.take() {\n\t\t\tSome(stderr) => lua.create_any_userdata(stderr.into_inner())?.into_lua(lua),\n\t\t\tNone => Ok(Value::Nil),\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/process/command.rs",
    "content": "use std::{any::TypeId, ffi::OsStr, io, process::Stdio};\n\nuse mlua::{AnyUserData, ExternalError, IntoLua, IntoLuaMulti, Lua, MetaMethod, Table, UserData, Value};\nuse tokio::process::{ChildStderr, ChildStdin, ChildStdout};\nuse yazi_binding::Error;\nuse yazi_shared::wtf8::FromWtf8;\n\nuse super::{Child, output::Output};\nuse crate::process::Status;\n\npub struct Command {\n\tinner:  tokio::process::Command,\n\tmemory: Option<usize>,\n}\n\nconst NULL: u8 = 0;\nconst PIPED: u8 = 1;\nconst INHERIT: u8 = 2;\n\nimpl Command {\n\tpub fn install(lua: &Lua) -> mlua::Result<()> {\n\t\tlet new = lua.create_function(|_, (_, program): (Table, String)| {\n\t\t\tlet mut inner = tokio::process::Command::new(program);\n\t\t\tinner.kill_on_drop(true).stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());\n\n\t\t\tOk(Self { inner, memory: None })\n\t\t})?;\n\n\t\tlet command = lua.create_table_from([\n\t\t\t// Stdio\n\t\t\t(\"NULL\", NULL),\n\t\t\t(\"PIPED\", PIPED),\n\t\t\t(\"INHERIT\", INHERIT),\n\t\t])?;\n\n\t\tcommand.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;\n\n\t\tlua.globals().raw_set(\"Command\", command)\n\t}\n\n\t#[cfg(unix)]\n\tfn spawn(&mut self) -> io::Result<Child> {\n\t\tif let Some(max) = self.memory {\n\t\t\tunsafe {\n\t\t\t\tself.inner.pre_exec(move || {\n\t\t\t\t\tlet rlp = libc::rlimit { rlim_cur: max as _, rlim_max: max as _ };\n\t\t\t\t\tlibc::setrlimit(libc::RLIMIT_AS, &rlp);\n\t\t\t\t\tOk(())\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tself.inner.spawn().map(Child::new)\n\t}\n\n\t#[cfg(windows)]\n\tfn spawn(&mut self) -> io::Result<Child> {\n\t\tuse std::os::windows::io::RawHandle;\n\n\t\tuse windows_sys::Win32::System::JobObjects::{AssignProcessToJobObject, CreateJobObjectW, JOB_OBJECT_LIMIT_PROCESS_MEMORY, JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JobObjectExtendedLimitInformation, SetInformationJobObject};\n\n\t\tfn assign_job(max: usize, handle: RawHandle) -> io::Result<RawHandle> {\n\t\t\tunsafe {\n\t\t\t\tlet job = CreateJobObjectW(std::ptr::null_mut(), std::ptr::null());\n\t\t\t\tif job.is_null() {\n\t\t\t\t\treturn Err(io::Error::last_os_error());\n\t\t\t\t}\n\n\t\t\t\tlet mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = std::mem::zeroed();\n\t\t\t\tinfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_PROCESS_MEMORY;\n\t\t\t\tinfo.ProcessMemoryLimit = max;\n\n\t\t\t\tlet result = SetInformationJobObject(\n\t\t\t\t\tjob,\n\t\t\t\t\tJobObjectExtendedLimitInformation,\n\t\t\t\t\t&mut info as *mut _ as *mut _,\n\t\t\t\t\tstd::mem::size_of_val(&info) as u32,\n\t\t\t\t);\n\n\t\t\t\tif result == 0 {\n\t\t\t\t\tErr(io::Error::last_os_error())\n\t\t\t\t} else if AssignProcessToJobObject(job, handle) == 0 {\n\t\t\t\t\tErr(io::Error::last_os_error())\n\t\t\t\t} else {\n\t\t\t\t\tOk(job)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet child = self.inner.spawn()?;\n\t\tif let (Some(max), Some(handle)) = (self.memory, child.raw_handle()) {\n\t\t\tif let Ok(job) = assign_job(max, handle) {\n\t\t\t\treturn Ok(Child::new(child, Some(job)));\n\t\t\t}\n\t\t}\n\n\t\tOk(Child::new(child, None))\n\t}\n\n\tasync fn output(&mut self) -> io::Result<std::process::Output> {\n\t\tself.inner.stdout(Stdio::piped());\n\t\tself.inner.stderr(Stdio::piped());\n\t\tself.spawn()?.wait_with_output().await\n\t}\n\n\tasync fn status(&mut self) -> io::Result<std::process::ExitStatus> {\n\t\tself.spawn()?.status().await\n\t}\n}\n\nimpl UserData for Command {\n\tfn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {\n\t\t#[inline]\n\t\tfn make_stdio(v: Value) -> mlua::Result<Stdio> {\n\t\t\tmatch v {\n\t\t\t\tValue::Integer(n) => {\n\t\t\t\t\treturn Ok(match n as u8 {\n\t\t\t\t\t\tPIPED => Stdio::piped(),\n\t\t\t\t\t\tINHERIT => Stdio::inherit(),\n\t\t\t\t\t\t_ => Stdio::null(),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tValue::UserData(ud) => match ud.type_id() {\n\t\t\t\t\tSome(t) if t == TypeId::of::<ChildStdin>() => {\n\t\t\t\t\t\treturn Ok(ud.take::<ChildStdin>()?.try_into()?);\n\t\t\t\t\t}\n\t\t\t\t\tSome(t) if t == TypeId::of::<ChildStdout>() => {\n\t\t\t\t\t\treturn Ok(ud.take::<ChildStdout>()?.try_into()?);\n\t\t\t\t\t}\n\t\t\t\t\tSome(t) if t == TypeId::of::<ChildStderr>() => {\n\t\t\t\t\t\treturn Ok(ud.take::<ChildStderr>()?.try_into()?);\n\t\t\t\t\t}\n\t\t\t\t\t_ => {}\n\t\t\t\t},\n\t\t\t\t_ => {}\n\t\t\t}\n\n\t\t\tErr(\n\t\t\t\t\"must be one of Command.NULL, Command.PIPED, Command.INHERIT, or a ChildStdin, ChildStdout, or ChildStderr\".into_lua_err(),\n\t\t\t)\n\t\t}\n\n\t\tmethods.add_function_mut(\"arg\", |lua, (ud, arg): (AnyUserData, Value)| {\n\t\t\tlet mut me = ud.borrow_mut::<Self>()?;\n\t\t\tmatch arg {\n\t\t\t\tValue::Nil => return lua.create_sequence_from(me.inner.as_std().get_args())?.into_lua(lua),\n\t\t\t\tValue::String(s) => {\n\t\t\t\t\tme.inner.arg(OsStr::from_wtf8(&s.as_bytes())?);\n\t\t\t\t}\n\t\t\t\tValue::Table(t) => {\n\t\t\t\t\tfor s in t.sequence_values::<mlua::String>() {\n\t\t\t\t\t\tme.inner.arg(OsStr::from_wtf8(&s?.as_bytes())?);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t_ => Err(\"arg must be a string or table of strings\".into_lua_err())?,\n\t\t\t}\n\t\t\tud.into_lua(lua)\n\t\t});\n\t\tmethods.add_function_mut(\"cwd\", |_, (ud, dir): (AnyUserData, mlua::String)| {\n\t\t\tud.borrow_mut::<Self>()?.inner.current_dir(dir.to_str()?.as_ref());\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\n\t\t\t\"env\",\n\t\t\t|_, (ud, key, value): (AnyUserData, mlua::String, mlua::String)| {\n\t\t\t\tud.borrow_mut::<Self>()?\n\t\t\t\t\t.inner\n\t\t\t\t\t.env(OsStr::from_wtf8(&key.as_bytes())?, OsStr::from_wtf8(&value.as_bytes())?);\n\t\t\t\tOk(ud)\n\t\t\t},\n\t\t);\n\t\tmethods.add_function_mut(\"stdin\", |_, (ud, stdio): (AnyUserData, Value)| {\n\t\t\tud.borrow_mut::<Self>()?.inner.stdin(make_stdio(stdio)?);\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"stdout\", |_, (ud, stdio): (AnyUserData, Value)| {\n\t\t\tud.borrow_mut::<Self>()?.inner.stdout(make_stdio(stdio)?);\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"stderr\", |_, (ud, stdio): (AnyUserData, Value)| {\n\t\t\tud.borrow_mut::<Self>()?.inner.stderr(make_stdio(stdio)?);\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_function_mut(\"memory\", |_, (ud, max): (AnyUserData, usize)| {\n\t\t\tud.borrow_mut::<Self>()?.memory = Some(max);\n\t\t\tOk(ud)\n\t\t});\n\t\tmethods.add_method_mut(\"spawn\", |lua, me, ()| match me.spawn() {\n\t\t\tOk(child) => child.into_lua_multi(lua),\n\t\t\tErr(e) => (Value::Nil, Error::Io(e)).into_lua_multi(lua),\n\t\t});\n\t\tmethods.add_async_method_mut(\"output\", |lua, mut me, ()| async move {\n\t\t\tmatch me.output().await {\n\t\t\t\tOk(output) => Output::new(output).into_lua_multi(&lua),\n\t\t\t\tErr(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),\n\t\t\t}\n\t\t});\n\t\tmethods.add_async_method_mut(\"status\", |lua, mut me, ()| async move {\n\t\t\tmatch me.status().await {\n\t\t\t\tOk(status) => Status::new(status).into_lua_multi(&lua),\n\t\t\t\tErr(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),\n\t\t\t}\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/process/mod.rs",
    "content": "yazi_macro::mod_flat!(child command output process status);\n"
  },
  {
    "path": "yazi-plugin/src/process/output.rs",
    "content": "use std::mem;\n\nuse mlua::{UserData, Value};\nuse yazi_binding::{cached_field, cached_field_mut};\n\nuse super::Status;\n\npub struct Output {\n\tinner: std::process::Output,\n\n\tv_status: Option<Value>,\n\tv_stdout: Option<mlua::Result<Value>>,\n\tv_stderr: Option<mlua::Result<Value>>,\n}\n\nimpl Output {\n\tpub fn new(inner: std::process::Output) -> Self {\n\t\tSelf { inner, v_status: None, v_stdout: None, v_stderr: None }\n\t}\n}\n\nimpl UserData for Output {\n\tfn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {\n\t\tcached_field!(fields, status, |_, me| Ok(Status::new(me.inner.status)));\n\t\tcached_field_mut!(fields, stdout, |lua, me| {\n\t\t\tlua.create_external_string(mem::take(&mut me.inner.stdout))\n\t\t});\n\t\tcached_field_mut!(fields, stderr, |lua, me| {\n\t\t\tlua.create_external_string(mem::take(&mut me.inner.stderr))\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/process/process.rs",
    "content": "use mlua::Lua;\n\npub fn install(lua: &Lua) -> mlua::Result<()> {\n\tsuper::Command::install(lua)?;\n\n\tOk(())\n}\n"
  },
  {
    "path": "yazi-plugin/src/process/status.rs",
    "content": "use mlua::UserData;\n\npub struct Status {\n\tinner: std::process::ExitStatus,\n}\n\nimpl Status {\n\tpub fn new(inner: std::process::ExitStatus) -> Self { Self { inner } }\n}\n\nimpl UserData for Status {\n\tfn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {\n\t\tfields.add_field_method_get(\"success\", |_, me| Ok(me.inner.success()));\n\t\tfields.add_field_method_get(\"code\", |_, me| Ok(me.inner.code()));\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/pubsub/mod.rs",
    "content": "use mlua::{IntoLua, Lua, Value};\nuse yazi_binding::{Composer, ComposerGet, ComposerSet};\n\nyazi_macro::mod_flat!(pubsub);\n\npub(super) fn compose() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tmatch key {\n\t\t\tb\"pub\" => Pubsub::r#pub(lua)?,\n\t\t\tb\"pub_to\" => Pubsub::pub_to(lua)?,\n\t\t\tb\"sub\" => Pubsub::sub(lua)?,\n\t\t\tb\"sub_remote\" => Pubsub::sub_remote(lua)?,\n\t\t\tb\"unsub\" => Pubsub::unsub(lua)?,\n\t\t\tb\"unsub_remote\" => Pubsub::unsub_remote(lua)?,\n\t\t\t_ => return Ok(Value::Nil),\n\t\t}\n\t\t.into_lua(lua)\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n"
  },
  {
    "path": "yazi-plugin/src/pubsub/pubsub.rs",
    "content": "use mlua::{ExternalResult, Function, Lua, Value};\nuse yazi_binding::{Id, runtime};\nuse yazi_dds::ember::Ember;\n\npub struct Pubsub;\n\nimpl Pubsub {\n\tpub(super) fn r#pub(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|lua, (kind, value): (mlua::String, Value)| {\n\t\t\tyazi_dds::Pubsub::r#pub(Ember::from_lua(lua, &kind.to_str()?, value)?).into_lua_err()\n\t\t})\n\t}\n\n\tpub(super) fn pub_to(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|lua, (receiver, kind, value): (Id, mlua::String, Value)| {\n\t\t\tyazi_dds::Pubsub::pub_to(*receiver, Ember::from_lua(lua, &kind.to_str()?, value)?)\n\t\t\t\t.into_lua_err()\n\t\t})\n\t}\n\n\tpub(super) fn sub(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|lua, (kind, f): (mlua::String, Function)| {\n\t\t\tlet rt = runtime!(lua)?;\n\t\t\tif !yazi_dds::Pubsub::sub(rt.current()?, &kind.to_str()?, f) {\n\t\t\t\treturn Err(\"`sub()` called twice\").into_lua_err();\n\t\t\t}\n\t\t\tOk(())\n\t\t})\n\t}\n\n\tpub(super) fn sub_remote(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|lua, (kind, f): (mlua::String, Function)| {\n\t\t\tlet rt = runtime!(lua)?;\n\t\t\tif !yazi_dds::Pubsub::sub_remote(rt.current()?, &kind.to_str()?, f) {\n\t\t\t\treturn Err(\"`sub_remote()` called twice\").into_lua_err();\n\t\t\t}\n\t\t\tOk(())\n\t\t})\n\t}\n\n\tpub(super) fn unsub(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|lua, kind: mlua::String| {\n\t\t\tlet rt = runtime!(lua)?;\n\t\t\tOk(yazi_dds::Pubsub::unsub(rt.current()?, &kind.to_str()?))\n\t\t})\n\t}\n\n\tpub(super) fn unsub_remote(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|lua, kind: mlua::String| {\n\t\t\tlet rt = runtime!(lua)?;\n\t\t\tOk(yazi_dds::Pubsub::unsub_remote(rt.current()?, &kind.to_str()?))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/runtime/mod.rs",
    "content": "yazi_macro::mod_flat!(plugin runtime term);\n"
  },
  {
    "path": "yazi-plugin/src/runtime/plugin.rs",
    "content": "use mlua::{Function, IntoLua, Lua, UserData, Value};\nuse yazi_binding::{Composer, ComposerGet, ComposerSet, FileRef, cached_field};\nuse yazi_config::YAZI;\n\npub(super) fn plugin() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tmatch key {\n\t\t\tb\"fetchers\" => fetchers(lua)?,\n\t\t\tb\"spotter\" => spotter(lua)?,\n\t\t\tb\"preloaders\" => preloaders(lua)?,\n\t\t\tb\"previewer\" => previewer(lua)?,\n\t\t\t_ => return Ok(Value::Nil),\n\t\t}\n\t\t.into_lua(lua)\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n\nfn fetchers(lua: &Lua) -> mlua::Result<Function> {\n\tlua.create_function(|lua, (file, mime): (FileRef, mlua::String)| {\n\t\tlua.create_sequence_from(YAZI.plugin.fetchers(&file, &mime.to_str()?).map(Fetcher::new))\n\t})\n}\n\nfn spotter(lua: &Lua) -> mlua::Result<Function> {\n\tlua.create_function(|_, (file, mime): (FileRef, mlua::String)| {\n\t\tOk(YAZI.plugin.spotter(&file, &mime.to_str()?).map(Spotter::new))\n\t})\n}\n\nfn preloaders(lua: &Lua) -> mlua::Result<Function> {\n\tlua.create_function(|lua, (file, mime): (FileRef, mlua::String)| {\n\t\tlua.create_sequence_from(YAZI.plugin.preloaders(&file, &mime.to_str()?).map(Preloader::new))\n\t})\n}\n\nfn previewer(lua: &Lua) -> mlua::Result<Function> {\n\tlua.create_function(|_, (file, mime): (FileRef, mlua::String)| {\n\t\tOk(YAZI.plugin.previewer(&file, &mime.to_str()?).map(Previewer::new))\n\t})\n}\n\n// --- Fetcher\nstruct Fetcher {\n\tinner: &'static yazi_config::plugin::Fetcher,\n\n\tv_cmd: Option<Value>,\n}\n\nimpl Fetcher {\n\tpub fn new(inner: &'static yazi_config::plugin::Fetcher) -> Self { Self { inner, v_cmd: None } }\n}\n\nimpl UserData for Fetcher {\n\tfn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {\n\t\tcached_field!(fields, cmd, |lua, me| lua.create_string(&*me.inner.run.name));\n\t}\n}\n\n// --- Spotter\nstruct Spotter {\n\tinner: &'static yazi_config::plugin::Spotter,\n\n\tv_cmd: Option<Value>,\n}\n\nimpl Spotter {\n\tpub fn new(inner: &'static yazi_config::plugin::Spotter) -> Self { Self { inner, v_cmd: None } }\n}\n\nimpl UserData for Spotter {\n\tfn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {\n\t\tcached_field!(fields, cmd, |lua, me| lua.create_string(&*me.inner.run.name));\n\t}\n}\n\n// --- Preloader\nstruct Preloader {\n\tinner: &'static yazi_config::plugin::Preloader,\n\n\tv_cmd: Option<Value>,\n}\n\nimpl Preloader {\n\tpub fn new(inner: &'static yazi_config::plugin::Preloader) -> Self { Self { inner, v_cmd: None } }\n}\n\nimpl UserData for Preloader {\n\tfn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {\n\t\tcached_field!(fields, cmd, |lua, me| lua.create_string(&*me.inner.run.name));\n\t}\n}\n\n// --- Previewer\nstruct Previewer {\n\tinner: &'static yazi_config::plugin::Previewer,\n\n\tv_cmd: Option<Value>,\n}\n\nimpl Previewer {\n\tpub fn new(inner: &'static yazi_config::plugin::Previewer) -> Self { Self { inner, v_cmd: None } }\n}\n\nimpl UserData for Previewer {\n\tfn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {\n\t\tcached_field!(fields, cmd, |lua, me| lua.create_string(&*me.inner.run.name));\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/runtime/runtime.rs",
    "content": "use mlua::{IntoLua, Lua, LuaSerdeExt, Value};\nuse yazi_binding::{Composer, ComposerGet, ComposerSet, SER_OPT, Url, elements::Wrap};\nuse yazi_boot::ARGS;\nuse yazi_config::YAZI;\n\npub fn compose() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tmatch key {\n\t\t\tb\"args\" => args().into_lua(lua)?,\n\t\t\tb\"term\" => super::term().into_lua(lua)?,\n\t\t\tb\"mgr\" => mgr().into_lua(lua)?,\n\t\t\tb\"plugin\" => super::plugin().into_lua(lua)?,\n\t\t\tb\"preview\" => preview().into_lua(lua)?,\n\t\t\tb\"tasks\" => tasks().into_lua(lua)?,\n\t\t\t_ => return Ok(Value::Nil),\n\t\t}\n\t\t.into_lua(lua)\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n\nfn args() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tmatch key {\n\t\t\tb\"entries\" => lua.create_sequence_from(ARGS.entries.iter().map(Url::new))?.into_lua(lua),\n\t\t\tb\"cwd_file\" => ARGS.cwd_file.as_ref().map(Url::new).into_lua(lua),\n\t\t\tb\"chooser_file\" => ARGS.chooser_file.as_ref().map(Url::new).into_lua(lua),\n\t\t\t_ => Ok(Value::Nil),\n\t\t}\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n\nfn mgr() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tlet m = &YAZI.mgr;\n\t\tmatch key {\n\t\t\tb\"ratio\" => lua.to_value_with(&m.ratio, SER_OPT)?,\n\n\t\t\tb\"sort_by\" => lua.to_value_with(&m.sort_by, SER_OPT)?,\n\t\t\tb\"sort_sensitive\" => m.sort_sensitive.get().into_lua(lua)?,\n\t\t\tb\"sort_reverse\" => m.sort_reverse.get().into_lua(lua)?,\n\t\t\tb\"sort_dir_first\" => m.sort_dir_first.get().into_lua(lua)?,\n\t\t\tb\"sort_translit\" => m.sort_translit.get().into_lua(lua)?,\n\t\t\tb\"sort_fallback\" => lua.to_value_with(&m.sort_fallback, SER_OPT)?,\n\n\t\t\tb\"linemode\" => lua.create_string(&m.linemode)?.into_lua(lua)?,\n\t\t\tb\"show_hidden\" => m.show_hidden.get().into_lua(lua)?,\n\t\t\tb\"show_symlink\" => m.show_symlink.get().into_lua(lua)?,\n\t\t\tb\"scrolloff\" => m.scrolloff.get().into_lua(lua)?,\n\t\t\tb\"mouse_events\" => lua.to_value_with(&m.mouse_events, SER_OPT)?,\n\t\t\t_ => return Ok(Value::Nil),\n\t\t}\n\t\t.into_lua(lua)\n\t}\n\n\tfn set(lua: &Lua, key: &[u8], value: Value) -> mlua::Result<Value> {\n\t\tlet m = &YAZI.mgr;\n\t\tOk(match key {\n\t\t\tb\"ratio\" => {\n\t\t\t\tm.ratio.set(lua.from_value(value)?);\n\t\t\t\tValue::Nil\n\t\t\t}\n\t\t\t_ => value,\n\t\t})\n\t}\n\n\tComposer::new(get, set)\n}\n\nfn preview() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tlet p = &YAZI.preview;\n\t\tmatch key {\n\t\t\tb\"wrap\" => Wrap::from(p.wrap).into_lua(lua)?,\n\t\t\tb\"tab_size\" => p.tab_size.into_lua(lua)?,\n\t\t\tb\"max_width\" => p.max_width.into_lua(lua)?,\n\t\t\tb\"max_height\" => p.max_height.into_lua(lua)?,\n\n\t\t\tb\"cache_dir\" => lua.to_value_with(&p.cache_dir, SER_OPT)?,\n\n\t\t\tb\"image_delay\" => p.image_delay.into_lua(lua)?,\n\t\t\tb\"image_filter\" => lua.create_string(&p.image_filter)?.into_lua(lua)?,\n\t\t\tb\"image_quality\" => p.image_quality.into_lua(lua)?,\n\n\t\t\tb\"ueberzug_scale\" => p.ueberzug_scale.into_lua(lua)?,\n\t\t\tb\"ueberzug_offset\" => lua.to_value_with(&p.ueberzug_offset, SER_OPT)?,\n\t\t\t_ => return Ok(Value::Nil),\n\t\t}\n\t\t.into_lua(lua)\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n\nfn tasks() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tlet t = &YAZI.tasks;\n\t\tmatch key {\n\t\t\tb\"file_workers\" => t.file_workers.into_lua(lua)?,\n\t\t\tb\"plugin_workers\" => t.plugin_workers.into_lua(lua)?,\n\t\t\tb\"fetch_workers\" => t.fetch_workers.into_lua(lua)?,\n\t\t\tb\"preload_workers\" => t.preload_workers.into_lua(lua)?,\n\t\t\tb\"process_workers\" => t.process_workers.into_lua(lua)?,\n\t\t\tb\"bizarre_retry\" => t.bizarre_retry.into_lua(lua)?,\n\n\t\t\tb\"image_alloc\" => t.image_alloc.into_lua(lua)?,\n\t\t\tb\"image_bound\" => lua.to_value_with(&t.image_bound, SER_OPT)?,\n\n\t\t\tb\"suppress_preload\" => t.suppress_preload.into_lua(lua)?,\n\t\t\t_ => return Ok(Value::Nil),\n\t\t}\n\t\t.into_lua(lua)\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n"
  },
  {
    "path": "yazi-plugin/src/runtime/term.rs",
    "content": "use mlua::{Function, IntoLua, IntoLuaMulti, Lua, Value};\nuse yazi_binding::{Composer, ComposerGet, ComposerSet};\nuse yazi_emulator::{Dimension, EMULATOR};\n\npub(super) fn term() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tmatch key {\n\t\t\tb\"light\" => EMULATOR.light.into_lua(lua),\n\t\t\tb\"cell_size\" => cell_size(lua)?.into_lua(lua),\n\t\t\t_ => Ok(Value::Nil),\n\t\t}\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n\nfn cell_size(lua: &Lua) -> mlua::Result<Function> {\n\tlua.create_function(|lua, ()| {\n\t\tif let Some(s) = Dimension::cell_size() {\n\t\t\ts.into_lua_multi(lua)\n\t\t} else {\n\t\t\t().into_lua_multi(lua)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "yazi-plugin/src/theme/mod.rs",
    "content": "yazi_macro::mod_flat!(theme);\n"
  },
  {
    "path": "yazi-plugin/src/theme/theme.rs",
    "content": "use mlua::{IntoLua, Lua, Value};\nuse yazi_binding::{Composer, ComposerGet, ComposerSet, Style, Url};\nuse yazi_config::THEME;\n\npub fn compose() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tmatch key {\n\t\t\tb\"app\" => app(),\n\t\t\tb\"mgr\" => mgr(),\n\t\t\tb\"tabs\" => tabs(),\n\t\t\tb\"mode\" => mode(),\n\t\t\tb\"indicator\" => indicator(),\n\t\t\tb\"status\" => status(),\n\t\t\tb\"which\" => which(),\n\t\t\tb\"confirm\" => confirm(),\n\t\t\tb\"spot\" => spot(),\n\t\t\tb\"notify\" => notify(),\n\t\t\tb\"pick\" => pick(),\n\t\t\tb\"input\" => input(),\n\t\t\tb\"cmp\" => cmp(),\n\t\t\tb\"tasks\" => tasks(),\n\t\t\tb\"help\" => help(),\n\t\t\t_ => return Ok(Value::Nil),\n\t\t}\n\t\t.into_lua(lua)\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n\nfn app() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tlet a = &THEME.app;\n\t\tmatch key {\n\t\t\tb\"overall\" => Style::from(a.overall).into_lua(lua),\n\n\t\t\t_ => Ok(Value::Nil),\n\t\t}\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n\nfn mgr() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tlet m = &THEME.mgr;\n\t\tmatch key {\n\t\t\tb\"cwd\" => Style::from(m.cwd).into_lua(lua),\n\n\t\t\tb\"find_keyword\" => Style::from(m.find_keyword).into_lua(lua),\n\t\t\tb\"find_position\" => Style::from(m.find_position).into_lua(lua),\n\n\t\t\tb\"symlink_target\" => Style::from(m.symlink_target).into_lua(lua),\n\n\t\t\tb\"marker_copied\" => Style::from(m.marker_copied).into_lua(lua),\n\t\t\tb\"marker_cut\" => Style::from(m.marker_cut).into_lua(lua),\n\t\t\tb\"marker_marked\" => Style::from(m.marker_marked).into_lua(lua),\n\t\t\tb\"marker_selected\" => Style::from(m.marker_selected).into_lua(lua),\n\t\t\tb\"marker_symbol\" => lua.create_string(&m.marker_symbol)?.into_lua(lua),\n\n\t\t\tb\"count_copied\" => Style::from(m.count_copied).into_lua(lua),\n\t\t\tb\"count_cut\" => Style::from(m.count_cut).into_lua(lua),\n\t\t\tb\"count_selected\" => Style::from(m.count_selected).into_lua(lua),\n\n\t\t\tb\"border_symbol\" => lua.create_string(&m.border_symbol)?.into_lua(lua),\n\t\t\tb\"border_style\" => Style::from(m.border_style).into_lua(lua),\n\n\t\t\tb\"syntect_theme\" => Url::new(&m.syntect_theme).into_lua(lua),\n\t\t\t_ => Ok(Value::Nil),\n\t\t}\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n\nfn tabs() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tlet t = &THEME.tabs;\n\t\tmatch key {\n\t\t\tb\"active\" => Style::from(t.active).into_lua(lua),\n\t\t\tb\"inactive\" => Style::from(t.inactive).into_lua(lua),\n\n\t\t\tb\"sep_inner\" => lua\n\t\t\t\t.create_table_from([\n\t\t\t\t\t(\"open\", lua.create_string(&t.sep_inner.open)?),\n\t\t\t\t\t(\"close\", lua.create_string(&t.sep_inner.close)?),\n\t\t\t\t])?\n\t\t\t\t.into_lua(lua),\n\t\t\tb\"sep_outer\" => lua\n\t\t\t\t.create_table_from([\n\t\t\t\t\t(\"open\", lua.create_string(&t.sep_outer.open)?),\n\t\t\t\t\t(\"close\", lua.create_string(&t.sep_outer.close)?),\n\t\t\t\t])?\n\t\t\t\t.into_lua(lua),\n\n\t\t\t_ => Ok(Value::Nil),\n\t\t}\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n\nfn mode() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tlet t = &THEME.mode;\n\t\tmatch key {\n\t\t\tb\"normal_main\" => Style::from(t.normal_main).into_lua(lua),\n\t\t\tb\"normal_alt\" => Style::from(t.normal_alt).into_lua(lua),\n\n\t\t\tb\"select_main\" => Style::from(t.select_main).into_lua(lua),\n\t\t\tb\"select_alt\" => Style::from(t.select_alt).into_lua(lua),\n\n\t\t\tb\"unset_main\" => Style::from(t.unset_main).into_lua(lua),\n\t\t\tb\"unset_alt\" => Style::from(t.unset_alt).into_lua(lua),\n\n\t\t\t_ => Ok(Value::Nil),\n\t\t}\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n\nfn indicator() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tlet t = &THEME.indicator;\n\t\tmatch key {\n\t\t\tb\"parent\" => Style::from(t.parent).into_lua(lua),\n\t\t\tb\"current\" => Style::from(t.current).into_lua(lua),\n\t\t\tb\"preview\" => Style::from(t.preview).into_lua(lua),\n\t\t\tb\"padding\" => lua\n\t\t\t\t.create_table_from([\n\t\t\t\t\t(\"open\", lua.create_string(&t.padding.open)?),\n\t\t\t\t\t(\"close\", lua.create_string(&t.padding.close)?),\n\t\t\t\t])?\n\t\t\t\t.into_lua(lua),\n\n\t\t\t_ => Ok(Value::Nil),\n\t\t}\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n\nfn status() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tlet t = &THEME.status;\n\t\tmatch key {\n\t\t\tb\"overall\" => Style::from(t.overall).into_lua(lua),\n\t\t\tb\"sep_left\" => lua\n\t\t\t\t.create_table_from([\n\t\t\t\t\t(\"open\", lua.create_string(&t.sep_left.open)?),\n\t\t\t\t\t(\"close\", lua.create_string(&t.sep_left.close)?),\n\t\t\t\t])?\n\t\t\t\t.into_lua(lua),\n\t\t\tb\"sep_right\" => lua\n\t\t\t\t.create_table_from([\n\t\t\t\t\t(\"open\", lua.create_string(&t.sep_right.open)?),\n\t\t\t\t\t(\"close\", lua.create_string(&t.sep_right.close)?),\n\t\t\t\t])?\n\t\t\t\t.into_lua(lua),\n\n\t\t\tb\"perm_sep\" => Style::from(t.perm_sep).into_lua(lua),\n\t\t\tb\"perm_type\" => Style::from(t.perm_type).into_lua(lua),\n\t\t\tb\"perm_read\" => Style::from(t.perm_read).into_lua(lua),\n\t\t\tb\"perm_write\" => Style::from(t.perm_write).into_lua(lua),\n\t\t\tb\"perm_exec\" => Style::from(t.perm_exec).into_lua(lua),\n\n\t\t\tb\"progress_label\" => Style::from(t.progress_label).into_lua(lua),\n\t\t\tb\"progress_normal\" => Style::from(t.progress_normal).into_lua(lua),\n\t\t\tb\"progress_error\" => Style::from(t.progress_error).into_lua(lua),\n\n\t\t\t_ => Ok(Value::Nil),\n\t\t}\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n\nfn which() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tlet t = &THEME.which;\n\t\tmatch key {\n\t\t\tb\"cols\" => t.cols.into_lua(lua),\n\t\t\tb\"mask\" => Style::from(t.mask).into_lua(lua),\n\t\t\tb\"cand\" => Style::from(t.cand).into_lua(lua),\n\t\t\tb\"rest\" => Style::from(t.rest).into_lua(lua),\n\t\t\tb\"desc\" => Style::from(t.desc).into_lua(lua),\n\n\t\t\tb\"separator\" => lua.create_string(&t.separator)?.into_lua(lua),\n\t\t\tb\"separator_style\" => Style::from(t.separator_style).into_lua(lua),\n\n\t\t\t_ => Ok(Value::Nil),\n\t\t}\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n\nfn confirm() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tlet t = &THEME.confirm;\n\t\tmatch key {\n\t\t\tb\"border\" => Style::from(t.border).into_lua(lua),\n\t\t\tb\"title\" => Style::from(t.title).into_lua(lua),\n\t\t\tb\"body\" => Style::from(t.body).into_lua(lua),\n\t\t\tb\"list\" => Style::from(t.list).into_lua(lua),\n\n\t\t\tb\"btn_yes\" => Style::from(t.btn_yes).into_lua(lua),\n\t\t\tb\"btn_no\" => Style::from(t.btn_no).into_lua(lua),\n\t\t\tb\"btn_labels\" => lua\n\t\t\t\t.create_sequence_from([\n\t\t\t\t\tlua.create_string(&t.btn_labels[0])?,\n\t\t\t\t\tlua.create_string(&t.btn_labels[1])?,\n\t\t\t\t])?\n\t\t\t\t.into_lua(lua),\n\n\t\t\t_ => Ok(Value::Nil),\n\t\t}\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n\nfn spot() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tlet t = &THEME.spot;\n\t\tmatch key {\n\t\t\tb\"border\" => Style::from(t.border).into_lua(lua),\n\t\t\tb\"title\" => Style::from(t.title).into_lua(lua),\n\n\t\t\tb\"tbl_col\" => Style::from(t.tbl_col).into_lua(lua),\n\t\t\tb\"tbl_cell\" => Style::from(t.tbl_cell).into_lua(lua),\n\n\t\t\t_ => Ok(Value::Nil),\n\t\t}\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n\nfn notify() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tlet t = &THEME.notify;\n\t\tmatch key {\n\t\t\tb\"title_info\" => Style::from(t.title_info).into_lua(lua),\n\t\t\tb\"title_warn\" => Style::from(t.title_warn).into_lua(lua),\n\t\t\tb\"title_error\" => Style::from(t.title_error).into_lua(lua),\n\n\t\t\tb\"icon_info\" => lua.create_string(&t.icon_info)?.into_lua(lua),\n\t\t\tb\"icon_warn\" => lua.create_string(&t.icon_warn)?.into_lua(lua),\n\t\t\tb\"icon_error\" => lua.create_string(&t.icon_error)?.into_lua(lua),\n\n\t\t\t_ => Ok(Value::Nil),\n\t\t}\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n\nfn pick() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tlet t = &THEME.pick;\n\t\tmatch key {\n\t\t\tb\"border\" => Style::from(t.border).into_lua(lua),\n\t\t\tb\"active\" => Style::from(t.active).into_lua(lua),\n\t\t\tb\"inactive\" => Style::from(t.inactive).into_lua(lua),\n\n\t\t\t_ => Ok(Value::Nil),\n\t\t}\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n\nfn input() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tlet t = &THEME.input;\n\t\tmatch key {\n\t\t\tb\"border\" => Style::from(t.border).into_lua(lua),\n\t\t\tb\"title\" => Style::from(t.title).into_lua(lua),\n\t\t\tb\"value\" => Style::from(t.value).into_lua(lua),\n\t\t\tb\"selected\" => Style::from(t.selected).into_lua(lua),\n\n\t\t\t_ => Ok(Value::Nil),\n\t\t}\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n\nfn cmp() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tlet t = &THEME.cmp;\n\t\tmatch key {\n\t\t\tb\"border\" => Style::from(t.border).into_lua(lua),\n\t\t\tb\"active\" => Style::from(t.active).into_lua(lua),\n\t\t\tb\"inactive\" => Style::from(t.inactive).into_lua(lua),\n\n\t\t\tb\"icon_file\" => lua.create_string(&t.icon_file)?.into_lua(lua),\n\t\t\tb\"icon_folder\" => lua.create_string(&t.icon_folder)?.into_lua(lua),\n\t\t\tb\"icon_command\" => lua.create_string(&t.icon_command)?.into_lua(lua),\n\n\t\t\t_ => Ok(Value::Nil),\n\t\t}\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n\nfn tasks() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tlet t = &THEME.tasks;\n\t\tmatch key {\n\t\t\tb\"border\" => Style::from(t.border).into_lua(lua),\n\t\t\tb\"title\" => Style::from(t.title).into_lua(lua),\n\t\t\tb\"hovered\" => Style::from(t.hovered).into_lua(lua),\n\n\t\t\t_ => Ok(Value::Nil),\n\t\t}\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n\nfn help() -> Composer<ComposerGet, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {\n\t\tlet t = &THEME.help;\n\t\tmatch key {\n\t\t\tb\"on\" => Style::from(t.on).into_lua(lua),\n\t\t\tb\"run\" => Style::from(t.run).into_lua(lua),\n\t\t\tb\"desc\" => Style::from(t.desc).into_lua(lua),\n\n\t\t\tb\"hovered\" => Style::from(t.hovered).into_lua(lua),\n\t\t\tb\"footer\" => Style::from(t.footer).into_lua(lua),\n\n\t\t\t_ => Ok(Value::Nil),\n\t\t}\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(get, set)\n}\n"
  },
  {
    "path": "yazi-plugin/src/utils/app.rs",
    "content": "use std::any::TypeId;\n\nuse mlua::{AnyUserData, ExternalError, Function, Lua};\nuse tokio::process::{ChildStderr, ChildStdin, ChildStdout};\nuse yazi_binding::{Fd, Id};\n\nuse super::Utils;\n\nimpl Utils {\n\tpub(super) fn id(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|_, r#type: mlua::String| {\n\t\t\tOk(Id(match &*r#type.as_bytes() {\n\t\t\t\tb\"app\" => *yazi_dds::ID,\n\t\t\t\tb\"ft\" => yazi_fs::FILES_TICKET.next(),\n\t\t\t\t_ => Err(\"Invalid id type\".into_lua_err())?,\n\t\t\t}))\n\t\t})\n\t}\n\n\tpub(super) fn drop(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|_, ud: AnyUserData| {\n\t\t\tmatch ud.type_id() {\n\t\t\t\tSome(t) if t == TypeId::of::<Fd>() => {}\n\t\t\t\tSome(t) if t == TypeId::of::<ChildStdin>() => {}\n\t\t\t\tSome(t) if t == TypeId::of::<ChildStdout>() => {}\n\t\t\t\tSome(t) if t == TypeId::of::<ChildStderr>() => {}\n\t\t\t\tSome(t) => Err(format!(\"Cannot drop userdata of type {t:?}\").into_lua_err())?,\n\t\t\t\tNone => Err(\"Cannot drop scoped userdata\".into_lua_err())?,\n\t\t\t};\n\t\t\tud.destroy()\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/utils/cache.rs",
    "content": "use std::hash::Hash;\n\nuse mlua::{Function, Lua, Table};\nuse yazi_binding::{FileRef, Url};\nuse yazi_config::YAZI;\nuse yazi_shared::url::UrlLike;\nuse yazi_shim::Twox128;\n\nuse super::Utils;\n\nimpl Utils {\n\tpub(super) fn file_cache(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|_, t: Table| {\n\t\t\tlet file: FileRef = t.raw_get(\"file\")?;\n\t\t\tif file.url.parent() == Some(yazi_shared::url::Url::regular(&YAZI.preview.cache_dir)) {\n\t\t\t\treturn Ok(None);\n\t\t\t}\n\n\t\t\tlet hex = {\n\t\t\t\tlet mut h = Twox128::default();\n\t\t\t\tfile.hash(&mut h);\n\t\t\t\tt.raw_get(\"skip\").unwrap_or(0usize).hash(&mut h);\n\t\t\t\tformat!(\"{:x}\", h.finish_128())\n\t\t\t};\n\n\t\t\tOk(Some(Url::new(YAZI.preview.cache_dir.join(hex))))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/utils/call.rs",
    "content": "use mlua::{ExternalError, Function, Lua, Table};\nuse tokio::sync::mpsc;\nuse yazi_dds::Sendable;\nuse yazi_macro::emit;\nuse yazi_shared::{Layer, Source, data::Data, event::Action};\n\nuse super::Utils;\n\nimpl Utils {\n\tpub(super) fn emit(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|lua, (name, args): (String, Table)| {\n\t\t\tlet mut action = Action::new(name, Source::Emit, Some(Layer::Mgr))?;\n\t\t\taction.args = Sendable::table_to_args(lua, args)?;\n\t\t\tOk(emit!(Call(action)))\n\t\t})\n\t}\n\n\tpub(super) fn exec(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_async_function(|lua, (name, args): (String, Table)| async move {\n\t\t\tlet mut action = Action::new(name, Source::Emit, Some(Layer::Mgr))?;\n\t\t\taction.args = Sendable::table_to_args(&lua, args)?;\n\n\t\t\tlet (tx, mut rx) = mpsc::unbounded_channel::<anyhow::Result<Data>>();\n\t\t\temit!(Call(action.with_any(\"__reply\", tx)));\n\n\t\t\tSendable::data_to_value(\n\t\t\t\t&lua,\n\t\t\t\trx.recv().await.ok_or_else(|| \"channel closed before action response\".into_lua_err())??,\n\t\t\t)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/utils/image.rs",
    "content": "use mlua::{Function, IntoLuaMulti, Lua, Value};\nuse yazi_adapter::{ADAPTOR, Image};\nuse yazi_binding::{Error, ImageInfo, UrlRef, elements::Rect};\nuse yazi_fs::FsUrl;\nuse yazi_shared::url::{AsUrl, UrlLike};\n\nuse super::Utils;\n\nimpl Utils {\n\tpub(super) fn image_info(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_async_function(|lua, url: UrlRef| async move {\n\t\t\tlet path = url.as_url().unified_path().into_owned();\n\t\t\tmatch yazi_adapter::ImageInfo::new(path).await {\n\t\t\t\tOk(info) => ImageInfo::from(info).into_lua_multi(&lua),\n\t\t\t\tErr(e) => (Value::Nil, Error::custom(e.to_string())).into_lua_multi(&lua),\n\t\t\t}\n\t\t})\n\t}\n\n\tpub(super) fn image_show(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_async_function(|lua, (url, rect): (UrlRef, Rect)| async move {\n\t\t\tlet path = url.as_url().unified_path();\n\t\t\tmatch ADAPTOR.get().image_show(path, *rect).await {\n\t\t\t\tOk(area) => Rect::from(area).into_lua_multi(&lua),\n\t\t\t\tErr(e) => (Value::Nil, Error::custom(e.to_string())).into_lua_multi(&lua),\n\t\t\t}\n\t\t})\n\t}\n\n\tpub(super) fn image_precache(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_async_function(|lua, (src, dist): (UrlRef, UrlRef)| async move {\n\t\t\tlet Some(dist) = dist.as_local() else {\n\t\t\t\treturn (Value::Nil, Error::custom(\"Destination must be a local path\"))\n\t\t\t\t\t.into_lua_multi(&lua);\n\t\t\t};\n\t\t\tlet src = src.as_url().unified_path().into_owned();\n\t\t\tmatch Image::precache(src, dist).await {\n\t\t\t\tOk(()) => true.into_lua_multi(&lua),\n\t\t\t\tErr(e) => (false, Error::custom(e.to_string())).into_lua_multi(&lua),\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/utils/json.rs",
    "content": "use mlua::{Function, IntoLuaMulti, Lua, LuaSerdeExt, Value};\nuse yazi_binding::{Error, SER_OPT};\n\nuse super::Utils;\n\nimpl Utils {\n\tpub(super) fn json_encode(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_async_function(|lua, value: Value| async move {\n\t\t\tmatch serde_json::to_string(&value) {\n\t\t\t\tOk(s) => s.into_lua_multi(&lua),\n\t\t\t\tErr(e) => (Value::Nil, Error::Serde(e)).into_lua_multi(&lua),\n\t\t\t}\n\t\t})\n\t}\n\n\tpub(super) fn json_decode(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_async_function(|lua, s: mlua::String| async move {\n\t\t\tmatch serde_json::from_slice::<serde_json::Value>(&s.as_bytes()) {\n\t\t\t\tOk(v) => lua.to_value_with(&v, SER_OPT)?.into_lua_multi(&lua),\n\t\t\t\tErr(e) => (Value::Nil, Error::Serde(e)).into_lua_multi(&lua),\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/utils/layer.rs",
    "content": "use std::{str::FromStr, time::Duration};\n\nuse mlua::{ExternalError, ExternalResult, Function, IntoLuaMulti, Lua, Table, Value};\nuse tokio_stream::wrappers::UnboundedReceiverStream;\nuse yazi_binding::{InputRx, elements::{Line, Pos, Text}, runtime};\nuse yazi_config::{keymap::{Chord, ChordCow, Key}, popup::{ConfirmCfg, InputCfg}};\nuse yazi_macro::relay;\nuse yazi_parser::notify::PushOpt;\nuse yazi_proxy::{ConfirmProxy, InputProxy, NotifyProxy, WhichProxy};\nuse yazi_shared::{Debounce, Layer};\n\nuse super::Utils;\n\nimpl Utils {\n\tpub(super) fn which(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_async_function(|lua, t: Table| async move {\n\t\t\tif runtime!(lua)?.blocking {\n\t\t\t\treturn Err(\"Cannot call `ya.which()` while main thread is blocked\".into_lua_err());\n\t\t\t}\n\n\t\t\tlet cands: Vec<_> = t\n\t\t\t\t.raw_get::<Table>(\"cands\")?\n\t\t\t\t.sequence_values::<Table>()\n\t\t\t\t.enumerate()\n\t\t\t\t.map(|(i, cand)| {\n\t\t\t\t\tlet cand = cand?;\n\t\t\t\t\tOk(ChordCow::Owned(Chord {\n\t\t\t\t\t\ton:    Self::parse_keys(cand.raw_get(\"on\")?)?,\n\t\t\t\t\t\trun:   vec![relay!(which:callback, [i + 1])],\n\t\t\t\t\t\tdesc:  cand.raw_get(\"desc\").ok(),\n\t\t\t\t\t\tr#for: None,\n\t\t\t\t\t}))\n\t\t\t\t})\n\t\t\t\t.collect::<mlua::Result<_>>()?;\n\n\t\t\tlet idx: Option<usize> = WhichProxy::activate(cands, t.raw_get(\"silent\")?)\n\t\t\t\t.await\n\t\t\t\t.iter()\n\t\t\t\t.flat_map(|chord| &chord.run)\n\t\t\t\t.find(|action| action.layer == Layer::Which && action.name == \"callback\")\n\t\t\t\t.and_then(|action| action.first().ok());\n\n\t\t\tOk(idx)\n\t\t})\n\t}\n\n\tpub(super) fn input(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_async_function(|lua, t: Table| async move {\n\t\t\tif runtime!(lua)?.blocking {\n\t\t\t\treturn Err(\"Cannot call `ya.input()` while main thread is blocked\".into_lua_err());\n\t\t\t}\n\n\t\t\tlet realtime = t.raw_get(\"realtime\")?;\n\t\t\tlet rx = UnboundedReceiverStream::new(InputProxy::show(InputCfg {\n\t\t\t\ttitle: t.raw_get(\"title\")?,\n\t\t\t\tvalue: t.raw_get(\"value\").unwrap_or_default(),\n\t\t\t\tcursor: None, // TODO\n\t\t\t\tobscure: t.raw_get(\"obscure\")?,\n\t\t\t\tposition: t.raw_get::<Pos>(\"pos\")?.with_height(3).into(),\n\t\t\t\trealtime,\n\t\t\t\tcompletion: false,\n\t\t\t}));\n\n\t\t\tif !realtime {\n\t\t\t\treturn InputRx::consume(rx).await.into_lua_multi(&lua);\n\t\t\t}\n\n\t\t\tlet debounce = t.raw_get::<f64>(\"debounce\").unwrap_or_default();\n\t\t\tif debounce < 0.0 {\n\t\t\t\tErr(\"negative debounce duration\".into_lua_err())\n\t\t\t} else if debounce == 0.0 {\n\t\t\t\tInputRx::new(rx).into_lua_multi(&lua)\n\t\t\t} else {\n\t\t\t\tInputRx::new(Debounce::new(rx, Duration::from_secs_f64(debounce))).into_lua_multi(&lua)\n\t\t\t}\n\t\t})\n\t}\n\n\tpub(super) fn confirm(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_async_function(|lua, t: Table| async move {\n\t\t\tif runtime!(lua)?.blocking {\n\t\t\t\treturn Err(\"Cannot call `ya.confirm()` while main thread is blocked\".into_lua_err());\n\t\t\t}\n\n\t\t\tlet result = ConfirmProxy::show(ConfirmCfg {\n\t\t\t\tposition: t.raw_get::<Pos>(\"pos\")?.into(),\n\t\t\t\ttitle:    t.raw_get::<Line>(\"title\")?.into(),\n\t\t\t\tbody:     t.raw_get::<Option<Text>>(\"body\")?.unwrap_or_default().into(),\n\t\t\t\tlist:     Default::default(), // TODO\n\t\t\t});\n\n\t\t\tOk(result.await)\n\t\t})\n\t}\n\n\tpub(super) fn notify(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|_, opt: PushOpt| Ok(NotifyProxy::push(opt)))\n\t}\n\n\tfn parse_keys(value: Value) -> mlua::Result<Vec<Key>> {\n\t\tOk(match value {\n\t\t\tValue::String(s) => {\n\t\t\t\tvec![Key::from_str(&s.to_str()?).into_lua_err()?]\n\t\t\t}\n\t\t\tValue::Table(t) => {\n\t\t\t\tlet mut v = Vec::with_capacity(10);\n\t\t\t\tfor s in t.sequence_values::<mlua::String>() {\n\t\t\t\t\tv.push(Key::from_str(&s?.to_str()?).into_lua_err()?);\n\t\t\t\t}\n\t\t\t\tv\n\t\t\t}\n\t\t\t_ => Err(\"invalid `on`\".into_lua_err())?,\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/utils/log.rs",
    "content": "use std::{any::TypeId, fmt::Write};\n\nuse mlua::{Function, Lua, MultiValue, Value};\nuse tracing::{debug, error};\n\nuse super::Utils;\n\nimpl Utils {\n\tpub(super) fn dbg(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|_, values: MultiValue| Ok(debug!(\"{}\", Self::format_all(values)?)))\n\t}\n\n\tpub(super) fn err(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|_, values: MultiValue| Ok(error!(\"{}\", Self::format_all(values)?)))\n\t}\n\n\tfn format_all(values: MultiValue) -> anyhow::Result<String> {\n\t\tlet mut s = String::new();\n\t\tfor value in values {\n\t\t\tif !s.is_empty() {\n\t\t\t\ts.push(' ');\n\t\t\t}\n\t\t\tSelf::format_one(&mut s, value)?;\n\t\t}\n\t\tOk(s)\n\t}\n\n\tfn format_one(buf: &mut String, value: Value) -> anyhow::Result<()> {\n\t\tlet Value::UserData(ud) = &value else {\n\t\t\treturn Ok(write!(buf, \"{value:#?}\")?);\n\t\t};\n\n\t\tlet id = ud.type_id();\n\t\tlet ptr = ud.to_pointer();\n\t\tOk(match id {\n\t\t\tSome(t) if t == TypeId::of::<yazi_binding::Url>() => {\n\t\t\t\twrite!(buf, \"Url({ptr:?}): {:?}\", **ud.borrow::<yazi_binding::Url>()?)?\n\t\t\t}\n\t\t\tSome(t) if t == TypeId::of::<yazi_binding::Path>() => {\n\t\t\t\twrite!(buf, \"Path({ptr:?}): {:?}\", **ud.borrow::<yazi_binding::Path>()?)?\n\t\t\t}\n\t\t\tSome(t) if t == TypeId::of::<yazi_binding::Id>() => {\n\t\t\t\twrite!(buf, \"Id({ptr:?}): {}\", **ud.borrow::<yazi_binding::Id>()?)?\n\t\t\t}\n\t\t\t_ => write!(buf, \"{value:#?}\")?,\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/utils/mod.rs",
    "content": "yazi_macro::mod_flat!(\n\tapp cache call image json layer log preview process spot sync target text time user utils\n);\n"
  },
  {
    "path": "yazi-plugin/src/utils/preview.rs",
    "content": "use mlua::{ExternalError, Function, IntoLuaMulti, Lua, Table, Value};\nuse yazi_binding::{Error, elements::{Area, Renderable, Text}};\nuse yazi_config::YAZI;\nuse yazi_fs::FsUrl;\nuse yazi_parser::mgr::{PreviewLock, UpdatePeekedOpt};\nuse yazi_proxy::MgrProxy;\nuse yazi_shared::{errors::PeekError, url::AsUrl};\n\nuse super::Utils;\nuse crate::external::Highlighter;\n\nimpl Utils {\n\tpub(super) fn preview_code(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_async_function(|lua, t: Table| async move {\n\t\t\tlet area: Area = t.raw_get(\"area\")?;\n\t\t\tlet mut lock = PreviewLock::try_from(t)?;\n\n\t\t\tlet path = lock.url.as_url().unified_path();\n\t\t\tlet inner = match Highlighter::new(path).highlight(lock.skip, area.size()).await {\n\t\t\t\tOk(text) => text,\n\t\t\t\tErr(e @ PeekError::Exceed(max)) => return (e.to_string(), max).into_lua_multi(&lua),\n\t\t\t\tErr(e @ PeekError::Unexpected(_)) => {\n\t\t\t\t\treturn e.to_string().into_lua_multi(&lua);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tlock.data = vec![Renderable::Text(Text {\n\t\t\t\tarea,\n\t\t\t\tinner,\n\t\t\t\twrap: YAZI.preview.wrap.into(),\n\t\t\t\tscroll: Default::default(),\n\t\t\t})];\n\n\t\t\tMgrProxy::update_peeked(UpdatePeekedOpt { lock });\n\t\t\t().into_lua_multi(&lua)\n\t\t})\n\t}\n\n\tpub(super) fn preview_widget(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_async_function(|_, (t, value): (Table, Value)| async move {\n\t\t\tlet mut lock = PreviewLock::try_from(t)?;\n\t\t\tlock.data = match value {\n\t\t\t\tValue::Nil => vec![],\n\t\t\t\tValue::Table(tbl) => tbl.sequence_values::<Renderable>().collect::<mlua::Result<_>>()?,\n\t\t\t\tValue::UserData(ud) => match Renderable::try_from(&ud) {\n\t\t\t\t\tOk(r) => vec![r],\n\t\t\t\t\tErr(e) => {\n\t\t\t\t\t\tif let Ok(err) = ud.take::<Error>() {\n\t\t\t\t\t\t\tvec![\n\t\t\t\t\t\t\t\tRenderable::Clear(yazi_binding::elements::Clear { area: lock.area.into() }),\n\t\t\t\t\t\t\t\tRenderable::from(err).with_area(lock.area),\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tErr(e)?\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t_ => Err(\"preview widget must be a renderable element or a table of them\".into_lua_err())?,\n\t\t\t};\n\n\t\t\tMgrProxy::update_peeked(UpdatePeekedOpt { lock });\n\t\t\tOk(())\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/utils/process.rs",
    "content": "use mlua::{Function, Lua};\n\nuse super::Utils;\n\nimpl Utils {\n\t#[cfg(target_os = \"macos\")]\n\tpub(super) fn proc_info(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|lua, pid: usize| {\n\t\t\tlet info = unsafe {\n\t\t\t\tlet mut info: libc::proc_taskinfo = std::mem::zeroed();\n\t\t\t\tlibc::proc_pidinfo(\n\t\t\t\t\tpid as _,\n\t\t\t\t\tlibc::PROC_PIDTASKINFO,\n\t\t\t\t\t0,\n\t\t\t\t\t&mut info as *mut _ as *mut _,\n\t\t\t\t\tstd::mem::size_of_val(&info) as _,\n\t\t\t\t);\n\t\t\t\tinfo\n\t\t\t};\n\n\t\t\tlua.create_table_from([(\"mem_resident\", info.pti_resident_size)])\n\t\t})\n\t}\n\n\t#[cfg(not(target_os = \"macos\"))]\n\tpub(super) fn proc_info(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|lua, ()| lua.create_table())\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/utils/spot.rs",
    "content": "use mlua::{AnyUserData, Function, Lua, Table};\nuse yazi_binding::elements::{Edge, Renderable};\nuse yazi_config::THEME;\nuse yazi_parser::mgr::{SpotLock, UpdateSpottedOpt};\nuse yazi_proxy::MgrProxy;\n\nuse super::Utils;\n\nimpl Utils {\n\tpub(super) fn spot_table(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|_, (t, table): (mlua::Table, AnyUserData)| {\n\t\t\tlet mut lock = SpotLock::try_from(t)?;\n\t\t\tlet mut table = yazi_binding::elements::Table::try_from(table)?;\n\n\t\t\tlet area = table.area;\n\t\t\ttable.area = area.inner(ratatui::widgets::Padding::uniform(1));\n\n\t\t\tlock.data = vec![\n\t\t\t\tRenderable::Clear(yazi_binding::elements::Clear { area }),\n\t\t\t\tRenderable::Border(yazi_binding::elements::Border {\n\t\t\t\t\tarea,\n\t\t\t\t\tedge: Edge(ratatui::widgets::Borders::ALL),\n\t\t\t\t\tr#type: ratatui::widgets::BorderType::Rounded,\n\t\t\t\t\tstyle: THEME.spot.border.into(),\n\t\t\t\t\ttitles: vec![(\n\t\t\t\t\t\tratatui::widgets::TitlePosition::Top,\n\t\t\t\t\t\tratatui::text::Line::raw(\"Spot\").centered().style(THEME.spot.title),\n\t\t\t\t\t)],\n\t\t\t\t}),\n\t\t\t\tRenderable::Table(Box::new(table)),\n\t\t\t];\n\t\t\tMgrProxy::update_spotted(UpdateSpottedOpt { lock });\n\n\t\t\tOk(())\n\t\t})\n\t}\n\n\tpub(super) fn spot_widgets(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|_, (t, widgets): (Table, Vec<Renderable>)| {\n\t\t\tlet mut lock = SpotLock::try_from(t)?;\n\t\t\tlock.data = widgets;\n\n\t\t\tMgrProxy::update_spotted(UpdateSpottedOpt { lock });\n\t\t\tOk(())\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/utils/sync.rs",
    "content": "use anyhow::Context;\nuse futures::future::join_all;\nuse mlua::{ExternalError, ExternalResult, Function, IntoLuaMulti, Lua, MultiValue, Table, Value, Variadic};\nuse tokio::sync::mpsc;\nuse yazi_binding::{Handle, MpscRx, MpscTx, MpscUnboundedRx, MpscUnboundedTx, OneshotRx, OneshotTx, runtime, runtime_mut};\nuse yazi_dds::Sendable;\nuse yazi_parser::app::PluginOpt;\nuse yazi_proxy::AppProxy;\nuse yazi_shared::{LOCAL_SET, data::Data};\n\nuse super::Utils;\nuse crate::loader::LOADER;\n\nimpl Utils {\n\tpub(super) fn co(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|lua, f: Function| {\n\t\t\tlet thread = lua.create_thread(f)?;\n\t\t\tlua.create_async_function(move |lua, mut args: MultiValue| {\n\t\t\t\tlet thread = thread.clone();\n\t\t\t\tasync move {\n\t\t\t\t\tloop {\n\t\t\t\t\t\tlet values: MultiValue = thread.resume(args)?;\n\t\t\t\t\t\tif let Some(Value::LightUserData(ud)) = values.front()\n\t\t\t\t\t\t\t&& *ud == Lua::poll_pending()\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\targs = lua.yield_with(values).await?;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn Ok(values);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n\n\tpub(super) fn sync(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|lua, f: Function| {\n\t\t\tlet mut rt = runtime_mut!(lua)?;\n\t\t\tlet Some(block) = rt.put_block(&f) else {\n\t\t\t\treturn Err(\"`ya.sync()` must be called in a plugin\").into_lua_err();\n\t\t\t};\n\n\t\t\tlet current = rt.current_owned()?;\n\t\t\tlua.create_async_function(move |lua, mut args: MultiValue| {\n\t\t\t\tlet (f, current) = (f.clone(), current.clone());\n\t\t\t\tasync move {\n\t\t\t\t\tlet blocking = runtime!(lua)?.blocking;\n\t\t\t\t\tif blocking {\n\t\t\t\t\t\targs.push_front(Value::Table(LOADER.try_load(&lua, &current)?));\n\t\t\t\t\t\tf.call::<MultiValue>(args)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tSelf::retrieve(&lua, &current, block, args)\n\t\t\t\t\t\t\t.await\n\t\t\t\t\t\t\t.and_then(|data| Sendable::list_to_values(&lua, data))\n\t\t\t\t\t\t\t.with_context(|| {\n\t\t\t\t\t\t\t\tformat!(\"Failed to execute sync block-{block} in `{current}` plugin\")\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.into_lua_err()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n\n\tpub(super) fn r#async(lua: &Lua, isolate: bool) -> mlua::Result<Function> {\n\t\tif isolate {\n\t\t\tlua.create_function(|_, _: Function| {\n\t\t\t\tErr::<(), _>(\"`ya.async()` can only be used in sync context at the moment\".into_lua_err())\n\t\t\t})\n\t\t} else {\n\t\t\tlua.create_function(|lua, (f, args): (Function, MultiValue)| {\n\t\t\t\tlet name = runtime!(lua)?.current_owned()?;\n\t\t\t\tlet lua = lua.clone();\n\n\t\t\t\tOk(Handle::AsyncFn(LOCAL_SET.spawn_local(async move {\n\t\t\t\t\tlet blocking = runtime_mut!(lua)?.critical_push(&name, false);\n\t\t\t\t\tlet result = f.call_async::<MultiValue>(args).await;\n\t\t\t\t\truntime_mut!(lua)?.critical_pop(blocking)?;\n\n\t\t\t\t\tif let Err(ref e) = result {\n\t\t\t\t\t\tmatch name.as_str() {\n\t\t\t\t\t\t\t\"init\" => tracing::error!(\"Failed to execute async block in `init.lua`: {e}\"),\n\t\t\t\t\t\t\ts => tracing::error!(\"Failed to execute async block in `{s}` plugin: {e}\"),\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tresult\n\t\t\t\t})))\n\t\t\t})\n\t\t}\n\t}\n\n\tpub(super) fn chan(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|lua, (r#type, buffer): (mlua::String, Option<usize>)| {\n\t\t\tmatch (&*r#type.as_bytes(), buffer) {\n\t\t\t\t(b\"mpsc\", Some(buffer)) if buffer < 1 => {\n\t\t\t\t\tErr(\"Buffer size must be greater than 0\".into_lua_err())\n\t\t\t\t}\n\t\t\t\t(b\"mpsc\", Some(buffer)) => {\n\t\t\t\t\tlet (tx, rx) = tokio::sync::mpsc::channel::<Value>(buffer);\n\t\t\t\t\t(MpscTx(tx), MpscRx(rx)).into_lua_multi(lua)\n\t\t\t\t}\n\t\t\t\t(b\"mpsc\", None) => {\n\t\t\t\t\tlet (tx, rx) = tokio::sync::mpsc::unbounded_channel::<Value>();\n\t\t\t\t\t(MpscUnboundedTx(tx), MpscUnboundedRx(rx)).into_lua_multi(lua)\n\t\t\t\t}\n\t\t\t\t(b\"oneshot\", _) => {\n\t\t\t\t\tlet (tx, rx) = tokio::sync::oneshot::channel::<Value>();\n\t\t\t\t\t(OneshotTx(Some(tx)), OneshotRx(Some(rx))).into_lua_multi(lua)\n\t\t\t\t}\n\t\t\t\t_ => Err(\"Channel type must be `mpsc` or `oneshot`\".into_lua_err()),\n\t\t\t}\n\t\t})\n\t}\n\n\tpub(super) fn join(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_async_function(|_, fns: Variadic<Function>| async move {\n\t\t\tlet mut results = MultiValue::with_capacity(fns.len());\n\t\t\tfor r in join_all(fns.into_iter().map(|f| f.call_async::<MultiValue>(()))).await {\n\t\t\t\tresults.extend(r?);\n\t\t\t}\n\t\t\tOk(results)\n\t\t})\n\t}\n\n\t// TODO\n\tpub(super) fn select(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_async_function(|_lua, _futs: MultiValue| async move { Ok(()) })\n\t}\n\n\tasync fn retrieve(\n\t\tlua: &Lua,\n\t\tid: &str,\n\t\tcalls: usize,\n\t\targs: MultiValue,\n\t) -> mlua::Result<Vec<Data>> {\n\t\tlet args = Sendable::values_to_list(lua, args)?;\n\t\tlet (tx, mut rx) = mpsc::channel::<Vec<Data>>(1);\n\n\t\tlet id_ = id.to_owned();\n\t\tlet callback = move |lua: &Lua, plugin: Table| {\n\t\t\tlet Some(block) = runtime!(lua)?.get_block(&id_, calls) else {\n\t\t\t\treturn Err(\"sync block not found\".into_lua_err());\n\t\t\t};\n\n\t\t\tlet args = [Ok(Value::Table(plugin))]\n\t\t\t\t.into_iter()\n\t\t\t\t.chain(args.into_iter().map(|d| Sendable::data_to_value(lua, d)))\n\t\t\t\t.collect::<mlua::Result<MultiValue>>()?;\n\n\t\t\tlet values = Sendable::values_to_list(lua, block.call(args)?)?;\n\t\t\ttx.try_send(values).map_err(|_| \"send failed\".into_lua_err())\n\t\t};\n\n\t\tAppProxy::plugin(PluginOpt::new_callback(id.to_owned(), callback));\n\n\t\trx.recv().await.ok_or(\"recv failed\").into_lua_err()\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/utils/target.rs",
    "content": "use mlua::{Function, Lua};\n\nuse super::Utils;\n\nimpl Utils {\n\tpub(super) fn target_os(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|_, ()| Ok(std::env::consts::OS))\n\t}\n\n\tpub(super) fn target_family(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|_, ()| Ok(std::env::consts::FAMILY))\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/utils/text.rs",
    "content": "use mlua::{Function, Lua};\nuse twox_hash::XxHash3_128;\nuse yazi_widgets::CLIPBOARD;\n\nuse super::Utils;\n\nimpl Utils {\n\tpub(super) fn hash(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_async_function(move |_, s: mlua::String| async move {\n\t\t\tOk(format!(\"{:x}\", XxHash3_128::oneshot(&s.as_bytes())))\n\t\t})\n\t}\n\n\tpub(super) fn quote(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|lua, (s, unix): (mlua::String, Option<bool>)| {\n\t\t\tlet b = s.as_bytes();\n\t\t\tlet s = match unix {\n\t\t\t\tSome(true) => yazi_shared::shell::unix::escape_os_bytes(&b),\n\t\t\t\tSome(false) => yazi_shared::shell::windows::escape_os_bytes(&b),\n\t\t\t\tNone => yazi_shared::shell::escape_os_bytes(&b),\n\t\t\t};\n\t\t\tlua.create_external_string(s)\n\t\t})\n\t}\n\n\tpub(super) fn clipboard(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_async_function(|lua, text: Option<String>| async move {\n\t\t\tif let Some(text) = text {\n\t\t\t\tCLIPBOARD.set(text).await;\n\t\t\t\tOk(None)\n\t\t\t} else {\n\t\t\t\tSome(lua.create_external_string(CLIPBOARD.get().await)).transpose()\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/utils/time.rs",
    "content": "use std::time::{SystemTime, UNIX_EPOCH};\n\nuse mlua::{ExternalError, Function, Lua};\n\nuse super::Utils;\n\nimpl Utils {\n\tpub(super) fn time(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|_, ()| {\n\t\t\tOk(SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok())\n\t\t})\n\t}\n\n\tpub(super) fn sleep(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_async_function(|_, secs: f64| async move {\n\t\t\tif secs < 0.0 {\n\t\t\t\treturn Err(\"negative sleep duration\".into_lua_err());\n\t\t\t}\n\n\t\t\ttokio::time::sleep(tokio::time::Duration::from_secs_f64(secs)).await;\n\t\t\tOk(())\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/utils/user.rs",
    "content": "#[cfg(unix)]\nuse mlua::{Function, Lua};\n\nuse super::Utils;\n\nimpl Utils {\n\t#[cfg(unix)]\n\tpub(super) fn uid(lua: &Lua) -> mlua::Result<Function> {\n\t\tuse uzers::Users;\n\t\tlua.create_function(|_, ()| Ok(yazi_shared::USERS_CACHE.get_current_uid()))\n\t}\n\n\t#[cfg(unix)]\n\tpub(super) fn gid(lua: &Lua) -> mlua::Result<Function> {\n\t\tuse uzers::Groups;\n\t\tlua.create_function(|_, ()| Ok(yazi_shared::USERS_CACHE.get_current_gid()))\n\t}\n\n\t#[cfg(unix)]\n\tpub(super) fn user_name(lua: &Lua) -> mlua::Result<Function> {\n\t\tuse uzers::Users;\n\t\tuse yazi_shared::USERS_CACHE;\n\n\t\tlua.create_function(|lua, uid: Option<u32>| {\n\t\t\tUSERS_CACHE\n\t\t\t\t.get_user_by_uid(uid.unwrap_or_else(|| USERS_CACHE.get_current_uid()))\n\t\t\t\t.map(|s| lua.create_string(s.name().as_encoded_bytes()))\n\t\t\t\t.transpose()\n\t\t})\n\t}\n\n\t#[cfg(unix)]\n\tpub(super) fn group_name(lua: &Lua) -> mlua::Result<Function> {\n\t\tuse uzers::Groups;\n\t\tuse yazi_shared::USERS_CACHE;\n\n\t\tlua.create_function(|lua, gid: Option<u32>| {\n\t\t\tUSERS_CACHE\n\t\t\t\t.get_group_by_gid(gid.unwrap_or_else(|| USERS_CACHE.get_current_gid()))\n\t\t\t\t.map(|s| lua.create_string(s.name().as_encoded_bytes()))\n\t\t\t\t.transpose()\n\t\t})\n\t}\n\n\t#[cfg(unix)]\n\tpub(super) fn host_name(lua: &Lua) -> mlua::Result<Function> {\n\t\tlua.create_function(|lua, ()| yazi_shared::hostname().map(|s| lua.create_string(s)).transpose())\n\t}\n}\n"
  },
  {
    "path": "yazi-plugin/src/utils/utils.rs",
    "content": "use mlua::{IntoLua, Lua, Value};\nuse yazi_binding::{Composer, ComposerSet};\n\npub(super) struct Utils;\n\npub fn compose(\n\tisolate: bool,\n) -> Composer<impl Fn(&Lua, &[u8]) -> mlua::Result<Value>, ComposerSet> {\n\tfn get(lua: &Lua, key: &[u8], isolate: bool) -> mlua::Result<Value> {\n\t\tmatch key {\n\t\t\t// App\n\t\t\tb\"id\" => Utils::id(lua)?,\n\t\t\tb\"drop\" => Utils::drop(lua)?,\n\n\t\t\t// Cache\n\t\t\tb\"file_cache\" => Utils::file_cache(lua)?,\n\n\t\t\t// Call\n\t\t\tb\"emit\" => Utils::emit(lua)?,\n\t\t\tb\"exec\" => Utils::exec(lua)?,\n\n\t\t\t// Image\n\t\t\tb\"image_info\" => Utils::image_info(lua)?,\n\t\t\tb\"image_show\" => Utils::image_show(lua)?,\n\t\t\tb\"image_precache\" => Utils::image_precache(lua)?,\n\n\t\t\t// JSON\n\t\t\tb\"json_encode\" => Utils::json_encode(lua)?,\n\t\t\tb\"json_decode\" => Utils::json_decode(lua)?,\n\n\t\t\t// Layout\n\t\t\tb\"which\" => Utils::which(lua)?,\n\t\t\tb\"input\" => Utils::input(lua)?,\n\t\t\tb\"confirm\" => Utils::confirm(lua)?,\n\t\t\tb\"notify\" => Utils::notify(lua)?,\n\n\t\t\t// Log\n\t\t\tb\"dbg\" => Utils::dbg(lua)?,\n\t\t\tb\"err\" => Utils::err(lua)?,\n\n\t\t\t// Preview\n\t\t\tb\"preview_code\" => Utils::preview_code(lua)?,\n\t\t\tb\"preview_widget\" => Utils::preview_widget(lua)?,\n\n\t\t\t// Process\n\t\t\tb\"proc_info\" => Utils::proc_info(lua)?,\n\n\t\t\t// Spot\n\t\t\tb\"spot_table\" => Utils::spot_table(lua)?,\n\t\t\tb\"spot_widgets\" => Utils::spot_widgets(lua)?,\n\n\t\t\t// Sync\n\t\t\tb\"co\" => Utils::co(lua)?,\n\t\t\tb\"sync\" => Utils::sync(lua)?,\n\t\t\tb\"async\" => Utils::r#async(lua, isolate)?,\n\t\t\tb\"chan\" => Utils::chan(lua)?,\n\t\t\tb\"join\" => Utils::join(lua)?,\n\t\t\tb\"select\" => Utils::select(lua)?,\n\n\t\t\t// Target\n\t\t\tb\"target_os\" => Utils::target_os(lua)?,\n\t\t\tb\"target_family\" => Utils::target_family(lua)?,\n\n\t\t\t// Text\n\t\t\tb\"hash\" => Utils::hash(lua)?,\n\t\t\tb\"quote\" => Utils::quote(lua)?,\n\t\t\tb\"clipboard\" => Utils::clipboard(lua)?,\n\n\t\t\t// Time\n\t\t\tb\"time\" => Utils::time(lua)?,\n\t\t\tb\"sleep\" => Utils::sleep(lua)?,\n\n\t\t\t// User\n\t\t\t#[cfg(unix)]\n\t\t\tb\"uid\" => Utils::uid(lua)?,\n\t\t\t#[cfg(unix)]\n\t\t\tb\"gid\" => Utils::gid(lua)?,\n\t\t\t#[cfg(unix)]\n\t\t\tb\"user_name\" => Utils::user_name(lua)?,\n\t\t\t#[cfg(unix)]\n\t\t\tb\"group_name\" => Utils::group_name(lua)?,\n\t\t\t#[cfg(unix)]\n\t\t\tb\"host_name\" => Utils::host_name(lua)?,\n\n\t\t\t_ => return Ok(Value::Nil),\n\t\t}\n\t\t.into_lua(lua)\n\t}\n\n\tfn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }\n\n\tComposer::new(move |lua, key| get(lua, key, isolate), set)\n}\n"
  },
  {
    "path": "yazi-proxy/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-proxy\"\ndescription            = \"Yazi event proxy\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[dependencies]\nyazi-config  = { path = \"../yazi-config\", version = \"26.2.2\" }\nyazi-macro   = { path = \"../yazi-macro\", version = \"26.2.2\" }\nyazi-parser  = { path = \"../yazi-parser\", version = \"26.2.2\" }\nyazi-shared  = { path = \"../yazi-shared\", version = \"26.2.2\" }\nyazi-widgets = { path = \"../yazi-widgets\", version = \"26.2.2\" }\n\n# External dependencies\ntokio = { workspace = true }\n"
  },
  {
    "path": "yazi-proxy/README.md",
    "content": "# yazi-proxy\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-proxy/src/app.rs",
    "content": "use yazi_macro::{emit, relay};\nuse yazi_parser::app::{PluginOpt, QuitOpt, TaskSummary};\nuse yazi_shared::CompletionToken;\n\npub struct AppProxy;\n\nimpl AppProxy {\n\tpub fn quit(opt: QuitOpt) {\n\t\temit!(Call(relay!(app:quit).with_any(\"opt\", opt)));\n\t}\n\n\tpub async fn stop() {\n\t\tlet token = CompletionToken::default();\n\t\temit!(Call(relay!(app:stop).with_any(\"token\", token.clone())));\n\t\ttoken.future().await;\n\t}\n\n\tpub async fn resume() {\n\t\tlet token = CompletionToken::default();\n\t\temit!(Call(relay!(app:resume).with_any(\"token\", token.clone())));\n\t\ttoken.future().await;\n\t}\n\n\tpub fn plugin(opt: PluginOpt) {\n\t\temit!(Call(relay!(app:plugin).with_any(\"opt\", opt)));\n\t}\n\n\tpub fn plugin_do(opt: PluginOpt) {\n\t\temit!(Call(relay!(app:plugin_do).with_any(\"opt\", opt)));\n\t}\n\n\tpub fn update_progress(summary: TaskSummary) {\n\t\temit!(Call(relay!(app:update_progress).with_any(\"summary\", summary)));\n\t}\n}\n"
  },
  {
    "path": "yazi-proxy/src/cmp.rs",
    "content": "use yazi_macro::{emit, relay};\nuse yazi_parser::cmp::ShowOpt;\nuse yazi_shared::Id;\n\npub struct CmpProxy;\n\nimpl CmpProxy {\n\tpub fn show(opt: ShowOpt) {\n\t\temit!(Call(relay!(cmp:show).with_any(\"opt\", opt)));\n\t}\n\n\tpub fn trigger(word: impl Into<String>, ticket: Option<Id>) {\n\t\temit!(Call(relay!(cmp:trigger, [word.into()]).with_opt(\"ticket\", ticket)));\n\t}\n}\n"
  },
  {
    "path": "yazi-proxy/src/confirm.rs",
    "content": "use yazi_config::popup::ConfirmCfg;\nuse yazi_macro::{emit, relay};\nuse yazi_shared::CompletionToken;\n\npub struct ConfirmProxy;\n\nimpl ConfirmProxy {\n\tpub async fn show(cfg: ConfirmCfg) -> bool { Self::show_sync(cfg).future().await }\n\n\tpub fn show_sync(cfg: ConfirmCfg) -> CompletionToken {\n\t\tlet token = CompletionToken::default();\n\t\temit!(Call(relay!(confirm:show).with_any(\"cfg\", cfg).with_any(\"token\", token.clone())));\n\t\ttoken\n\t}\n}\n"
  },
  {
    "path": "yazi-proxy/src/input.rs",
    "content": "use tokio::sync::mpsc;\nuse yazi_config::popup::InputCfg;\nuse yazi_macro::{emit, relay};\nuse yazi_widgets::input::InputEvent;\n\npub struct InputProxy;\n\nimpl InputProxy {\n\tpub fn show(cfg: InputCfg) -> mpsc::UnboundedReceiver<InputEvent> {\n\t\tlet (tx, rx) = mpsc::unbounded_channel();\n\t\temit!(Call(relay!(input:show).with_any(\"tx\", tx).with_any(\"cfg\", cfg)));\n\t\trx\n\t}\n}\n"
  },
  {
    "path": "yazi-proxy/src/lib.rs",
    "content": "mod macros;\n\nyazi_macro::mod_flat!(app cmp confirm input mgr notify pick tasks which);\n"
  },
  {
    "path": "yazi-proxy/src/macros.rs",
    "content": "#[macro_export]\nmacro_rules! deprecate {\n\t($content:expr) => {{\n\t\tstatic WARNED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);\n\t\tif !WARNED.swap(true, std::sync::atomic::Ordering::Relaxed) {\n\t\t\t$crate::emit!(Call(\n\t\t\t\tyazi_shared::event::Action::new(\"app:deprecate\").with(\"content\", format!($tt, id))\n\t\t\t));\n\t\t}\n\t}};\n}\n"
  },
  {
    "path": "yazi-proxy/src/mgr.rs",
    "content": "use yazi_macro::{emit, relay};\nuse yazi_parser::mgr::{DisplaceDoOpt, FilterOpt, FindDoOpt, OpenDoOpt, OpenOpt, SearchOpt, UpdatePeekedOpt, UpdateSpottedOpt};\nuse yazi_shared::{Id, SStr, url::UrlBuf};\n\npub struct MgrProxy;\n\nimpl MgrProxy {\n\tpub fn arrow(step: impl Into<SStr>) {\n\t\temit!(Call(relay!(mgr:arrow, [step.into()])));\n\t}\n\n\tpub fn cd(target: impl Into<UrlBuf>) {\n\t\temit!(Call(relay!(mgr:cd, [target.into()]).with(\"raw\", true)));\n\t}\n\n\tpub fn displace_do(tab: Id, opt: DisplaceDoOpt) {\n\t\temit!(Call(relay!(mgr:displace_do).with(\"tab\", tab).with_any(\"opt\", opt)));\n\t}\n\n\tpub fn filter_do(opt: FilterOpt) {\n\t\temit!(Call(relay!(mgr:filter_do).with_any(\"opt\", opt)));\n\t}\n\n\tpub fn find_do(opt: FindDoOpt) {\n\t\temit!(Call(relay!(mgr:find_do).with_any(\"opt\", opt)));\n\t}\n\n\tpub fn open(opt: OpenOpt) {\n\t\temit!(Call(relay!(mgr:open).with_any(\"opt\", opt)));\n\t}\n\n\tpub fn open_do(opt: OpenDoOpt) {\n\t\temit!(Call(relay!(mgr:open_do).with_any(\"opt\", opt)));\n\t}\n\n\tpub fn refresh() {\n\t\temit!(Call(relay!(mgr:refresh)));\n\t}\n\n\tpub fn remove_do(targets: Vec<UrlBuf>, permanently: bool) {\n\t\temit!(Call(\n\t\t\trelay!(mgr:remove_do).with(\"permanently\", permanently).with_any(\"targets\", targets)\n\t\t));\n\t}\n\n\tpub fn reveal(target: impl Into<UrlBuf>) {\n\t\temit!(Call(relay!(mgr:reveal, [target.into()]).with(\"raw\", true).with(\"no-dummy\", true)));\n\t}\n\n\tpub fn search_do(opt: SearchOpt) {\n\t\temit!(Call(relay!(mgr:search_do).with_any(\"opt\", opt)));\n\t}\n\n\tpub fn tab_rename(tab: Id, name: impl Into<SStr>) {\n\t\temit!(Call(relay!(mgr:tab_rename, [name.into()]).with(\"tab\", tab)));\n\t}\n\n\tpub fn update_paged_by(page: usize, only_if: &UrlBuf) {\n\t\temit!(Call(relay!(mgr:update_paged, [page]).with(\"only-if\", only_if)));\n\t}\n\n\tpub fn update_peeked(opt: UpdatePeekedOpt) {\n\t\temit!(Call(relay!(mgr:update_peeked).with_any(\"opt\", opt)));\n\t}\n\n\tpub fn update_spotted(opt: UpdateSpottedOpt) {\n\t\temit!(Call(relay!(mgr:update_spotted).with_any(\"opt\", opt)));\n\t}\n\n\tpub fn upload<I>(urls: I)\n\twhere\n\t\tI: IntoIterator<Item = UrlBuf>,\n\t{\n\t\temit!(Call(relay!(mgr:upload).with_seq(urls)));\n\t}\n\n\tpub fn watch() {\n\t\temit!(Call(relay!(mgr:watch)));\n\t}\n}\n"
  },
  {
    "path": "yazi-proxy/src/notify.rs",
    "content": "use std::time::Duration;\n\nuse yazi_macro::{emit, relay};\nuse yazi_parser::notify::{PushLevel, PushOpt};\n\npub struct NotifyProxy;\n\nimpl NotifyProxy {\n\tpub fn push(opt: PushOpt) {\n\t\temit!(Call(relay!(notify:push).with_any(\"opt\", opt)));\n\t}\n\n\tpub fn push_warn(title: impl Into<String>, content: impl Into<String>) {\n\t\tSelf::push(PushOpt {\n\t\t\ttitle:   title.into(),\n\t\t\tcontent: content.into(),\n\t\t\tlevel:   PushLevel::Warn,\n\t\t\ttimeout: Duration::from_secs(5),\n\t\t});\n\t}\n\n\tpub fn push_error(title: &str, content: impl ToString) {\n\t\tSelf::push(PushOpt {\n\t\t\ttitle:   title.to_owned(),\n\t\t\tcontent: content.to_string(),\n\t\t\tlevel:   PushLevel::Error,\n\t\t\ttimeout: Duration::from_secs(10),\n\t\t});\n\t}\n\n\tpub fn tick(dur: Duration) {\n\t\temit!(Call(relay!(notify:tick, [dur.as_secs_f64()])));\n\t}\n}\n"
  },
  {
    "path": "yazi-proxy/src/pick.rs",
    "content": "use tokio::sync::mpsc;\nuse yazi_config::popup::PickCfg;\nuse yazi_macro::{emit, relay};\n\npub struct PickProxy;\n\nimpl PickProxy {\n\tpub async fn show(cfg: PickCfg) -> Option<usize> {\n\t\tlet (tx, mut rx) = mpsc::unbounded_channel::<Option<usize>>();\n\t\temit!(Call(relay!(pick:show).with_any(\"tx\", tx).with_any(\"cfg\", cfg)));\n\t\trx.recv().await?\n\t}\n}\n"
  },
  {
    "path": "yazi-proxy/src/tasks.rs",
    "content": "use std::ffi::OsString;\n\nuse yazi_macro::{emit, relay};\nuse yazi_parser::tasks::ProcessOpenOpt;\nuse yazi_shared::{CompletionToken, url::{UrlBuf, UrlCow}};\n\npub struct TasksProxy;\n\nimpl TasksProxy {\n\t// TODO: remove\n\tpub fn open_shell_compat(opt: ProcessOpenOpt) {\n\t\temit!(Call(relay!(tasks:open_shell_compat).with_any(\"opt\", opt)));\n\t}\n\n\tpub async fn process_exec(\n\t\tcwd: UrlCow<'static>,\n\t\tcmd: OsString,\n\t\targs: Vec<UrlCow<'static>>,\n\t\tblock: bool,\n\t\torphan: bool,\n\t) {\n\t\tlet done = CompletionToken::default();\n\t\temit!(Call(relay!(tasks:process_open).with_any(\"opt\", ProcessOpenOpt {\n\t\t\tcwd,\n\t\t\tcmd,\n\t\t\targs,\n\t\t\tblock,\n\t\t\torphan,\n\t\t\tdone: Some(done.clone()),\n\t\t\tspread: false\n\t\t})));\n\t\tdone.future().await;\n\t}\n\n\tpub fn update_succeed<I>(url: I)\n\twhere\n\t\tI: IntoIterator,\n\t\tI::Item: Into<UrlBuf>,\n\t{\n\t\tlet urls: Vec<_> = url.into_iter().map(Into::into).collect();\n\t\temit!(Call(relay!(tasks:update_succeed).with_any(\"urls\", urls)));\n\t}\n}\n"
  },
  {
    "path": "yazi-proxy/src/which.rs",
    "content": "use tokio::sync::mpsc;\nuse yazi_config::keymap::ChordCow;\nuse yazi_macro::{emit, relay};\nuse yazi_parser::which::ActivateOpt;\n\npub struct WhichProxy;\n\nimpl WhichProxy {\n\tpub async fn activate(cands: Vec<ChordCow>, silent: bool) -> Option<ChordCow> {\n\t\tlet (tx, mut rx) = mpsc::unbounded_channel();\n\t\temit!(Call(relay!(which:activate).with_any(\"opt\", ActivateOpt {\n\t\t\ttx: Some(tx),\n\t\t\tcands,\n\t\t\tsilent,\n\t\t\ttimes: 0,\n\t\t})));\n\t\tSome(rx.recv().await??.0)\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-scheduler\"\ndescription            = \"Yazi task scheduler\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[dependencies]\nyazi-binding = { path = \"../yazi-binding\", version = \"26.2.2\" }\nyazi-config  = { path = \"../yazi-config\", version = \"26.2.2\" }\nyazi-dds     = { path = \"../yazi-dds\", version = \"26.2.2\" }\nyazi-fs      = { path = \"../yazi-fs\", version = \"26.2.2\" }\nyazi-macro   = { path = \"../yazi-macro\", version = \"26.2.2\" }\nyazi-parser  = { path = \"../yazi-parser\", version = \"26.2.2\" }\nyazi-plugin  = { path = \"../yazi-plugin\", version = \"26.2.2\" }\nyazi-proxy   = { path = \"../yazi-proxy\", version = \"26.2.2\" }\nyazi-shared  = { path = \"../yazi-shared\", version = \"26.2.2\" }\nyazi-term    = { path = \"../yazi-term\", version = \"26.2.2\" }\nyazi-vfs     = { path = \"../yazi-vfs\", version = \"26.2.2\" }\n\n# External dependencies\nanyhow                 = { workspace = true }\nasync-priority-channel = \"0.2.0\"\nfoldhash               = { workspace = true }\nhashbrown              = { workspace = true }\nlru                    = { workspace = true }\nmlua                   = { workspace = true }\nordered-float          = { workspace = true }\nparking_lot            = { workspace = true }\nserde                  = { workspace = true }\ntokio                  = { workspace = true }\ntokio-util             = { workspace = true }\ntracing                = { workspace = true }\n\n[target.\"cfg(unix)\".dependencies]\nlibc = { workspace = true }\n"
  },
  {
    "path": "yazi-scheduler/README.md",
    "content": "# yazi-scheduler\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-scheduler/src/fetch/fetch.rs",
    "content": "use std::num::NonZeroUsize;\n\nuse anyhow::Result;\nuse lru::LruCache;\nuse parking_lot::Mutex;\nuse tokio::sync::mpsc;\nuse tracing::error;\nuse yazi_config::Priority;\nuse yazi_fs::FsHash64;\nuse yazi_plugin::isolate;\nuse yazi_shared::event::ActionCow;\n\nuse crate::{HIGH, LOW, TaskOp, TaskOps, fetch::{FetchIn, FetchOutFetch}};\n\npub struct Fetch {\n\tops:        TaskOps,\n\ttx:         async_priority_channel::Sender<FetchIn, u8>,\n\tpub loaded: Mutex<LruCache<u64, u16>>,\n}\n\nimpl Fetch {\n\tpub(crate) fn new(\n\t\tops: &mpsc::UnboundedSender<TaskOp>,\n\t\ttx: async_priority_channel::Sender<FetchIn, u8>,\n\t) -> Self {\n\t\tSelf {\n\t\t\tops: ops.into(),\n\t\t\ttx,\n\t\t\tloaded: Mutex::new(LruCache::new(NonZeroUsize::new(4096).unwrap())),\n\t\t}\n\t}\n\n\tpub(crate) async fn fetch(&self, task: FetchIn) -> Result<(), FetchOutFetch> {\n\t\tlet hashes: Vec<_> = task.targets.iter().map(|f| f.hash_u64()).collect();\n\t\tlet (state, err) = isolate::fetch(ActionCow::from(&task.plugin.run), task.targets).await?;\n\n\t\tlet mut loaded = self.loaded.lock();\n\t\tfor (_, h) in hashes.into_iter().enumerate().filter(|&(i, _)| !state.get(i)) {\n\t\t\tloaded.get_mut(&h).map(|x| *x &= !(1 << task.plugin.idx));\n\t\t}\n\t\tif let Some(e) = err {\n\t\t\terror!(\"Error when running fetcher `{}`:\\n{e}\", task.plugin.run.name);\n\t\t}\n\n\t\tOk(self.ops.out(task.id, FetchOutFetch::Succ))\n\t}\n}\n\nimpl Fetch {\n\tpub(crate) fn submit(&self, r#in: FetchIn) {\n\t\tlet priority = match r#in.plugin.prio {\n\t\t\tPriority::Low => LOW,\n\t\t\tPriority::Normal => HIGH,\n\t\t\tPriority::High => HIGH,\n\t\t};\n\n\t\t_ = self.tx.try_send(r#in, priority);\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/src/fetch/in.rs",
    "content": "use yazi_config::plugin::Fetcher;\nuse yazi_shared::Id;\n\n#[derive(Debug)]\npub(crate) struct FetchIn {\n\tpub(crate) id:      Id,\n\tpub(crate) plugin:  &'static Fetcher,\n\tpub(crate) targets: Vec<yazi_fs::File>,\n}\n\nimpl FetchIn {\n\tpub(crate) fn id(&self) -> Id { self.id }\n}\n"
  },
  {
    "path": "yazi-scheduler/src/fetch/mod.rs",
    "content": "yazi_macro::mod_flat!(out fetch progress r#in);\n"
  },
  {
    "path": "yazi-scheduler/src/fetch/out.rs",
    "content": "use crate::{Task, TaskProg};\n\n#[derive(Debug)]\npub(crate) enum FetchOutFetch {\n\tSucc,\n\tFail(String),\n}\n\nimpl From<mlua::Error> for FetchOutFetch {\n\tfn from(value: mlua::Error) -> Self { Self::Fail(value.to_string()) }\n}\n\nimpl FetchOutFetch {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tlet TaskProg::Fetch(prog) = &mut task.prog else { return };\n\t\tmatch self {\n\t\t\tSelf::Succ => {\n\t\t\t\tprog.state = Some(true);\n\t\t\t}\n\t\t\tSelf::Fail(reason) => {\n\t\t\t\tprog.state = Some(false);\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/src/fetch/progress.rs",
    "content": "use serde::Serialize;\nuse yazi_parser::app::TaskSummary;\n\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]\npub struct FetchProg {\n\tpub state: Option<bool>,\n}\n\nimpl From<FetchProg> for TaskSummary {\n\tfn from(value: FetchProg) -> Self {\n\t\tSelf {\n\t\t\ttotal:   1,\n\t\t\tsuccess: (value.state == Some(true)) as u32,\n\t\t\tfailed:  (value.state == Some(false)) as u32,\n\t\t\tpercent: value.percent().map(Into::into),\n\t\t}\n\t}\n}\n\nimpl FetchProg {\n\tpub fn cooked(self) -> bool { self.state == Some(true) }\n\n\tpub fn running(self) -> bool { self.state.is_none() }\n\n\tpub fn success(self) -> bool { self.cooked() }\n\n\tpub fn failed(self) -> bool { self.state == Some(false) }\n\n\tpub fn cleaned(self) -> Option<bool> { None }\n\n\tpub fn percent(self) -> Option<f32> { None }\n}\n"
  },
  {
    "path": "yazi-scheduler/src/file/file.rs",
    "content": "use std::mem;\n\nuse anyhow::{Context, Result, anyhow};\nuse tokio::{io::{self, ErrorKind::NotFound}, sync::mpsc};\nuse tracing::warn;\nuse yazi_config::YAZI;\nuse yazi_fs::{Cwd, FsHash128, FsUrl, cha::Cha, ok_or_not_found, path::path_relative_to, provider::{Attrs, FileHolder, Provider, local::Local}};\nuse yazi_shared::{path::PathCow, url::{AsUrl, UrlCow, UrlLike}};\nuse yazi_vfs::{VfsCha, maybe_exists, provider::{self, DirEntry}, unique_file};\n\nuse super::{FileInCopy, FileInDelete, FileInHardlink, FileInLink, FileInTrash};\nuse crate::{LOW, NORMAL, TaskOp, TaskOps, ctx, file::{FileIn, FileInCut, FileInDownload, FileInUpload, FileOutCopy, FileOutCopyDo, FileOutCut, FileOutCutDo, FileOutDelete, FileOutDeleteDo, FileOutDownload, FileOutDownloadDo, FileOutHardlink, FileOutHardlinkDo, FileOutLink, FileOutTrash, FileOutUpload, FileOutUploadDo, Transaction, Traverse}, hook::{HookInOutCopy, HookInOutCut}, ok_or_not_found, progress_or_break};\n\npub(crate) struct File {\n\tops: TaskOps,\n\ttx:  async_priority_channel::Sender<FileIn, u8>,\n}\n\nimpl File {\n\tpub(crate) fn new(\n\t\tops: &mpsc::UnboundedSender<TaskOp>,\n\t\ttx: async_priority_channel::Sender<FileIn, u8>,\n\t) -> Self {\n\t\tSelf { ops: ops.into(), tx }\n\t}\n\n\tpub(crate) async fn copy(&self, mut task: FileInCopy) -> Result<(), FileOutCopy> {\n\t\tlet id = task.id;\n\n\t\tif !task.force {\n\t\t\ttask.to = unique_file(mem::take(&mut task.to), task.init().await?.is_dir())\n\t\t\t\t.await\n\t\t\t\t.context(\"Cannot determine unique destination name\")?;\n\t\t}\n\n\t\tself.ops.out(id, HookInOutCopy::from(&task));\n\t\tsuper::traverse::<FileOutCopy, _, _, _, _, _>(\n\t\t\ttask,\n\t\t\tasync |dir| match provider::create_dir(dir).await {\n\t\t\t\tErr(e) if e.kind() != io::ErrorKind::AlreadyExists => Err(e)?,\n\t\t\t\t_ => Ok(()),\n\t\t\t},\n\t\t\tasync |task, cha| {\n\t\t\t\tOk(if cha.is_orphan() || (cha.is_link() && !task.follow) {\n\t\t\t\t\tself.ops.out(id, FileOutCopy::New(0));\n\t\t\t\t\tself.requeue(task.into_link(), NORMAL);\n\t\t\t\t} else {\n\t\t\t\t\tself.ops.out(id, FileOutCopy::New(cha.len));\n\t\t\t\t\tself.requeue(task, LOW);\n\t\t\t\t})\n\t\t\t},\n\t\t\t|err| {\n\t\t\t\tself.ops.out(id, FileOutCopy::Deform(err));\n\t\t\t},\n\t\t)\n\t\t.await?;\n\n\t\tOk(self.ops.out(id, FileOutCopy::Succ))\n\t}\n\n\tpub(crate) async fn copy_do(&self, mut task: FileInCopy) -> Result<(), FileOutCopyDo> {\n\t\tok_or_not_found!(task, Transaction::unlink(&task.to).await);\n\t\tlet mut it =\n\t\t\tctx!(task, provider::copy_with_progress(&task.from, &task.to, task.cha.unwrap()).await)?;\n\n\t\tloop {\n\t\t\tmatch progress_or_break!(it, task.done) {\n\t\t\t\tOk(0) => break,\n\t\t\t\tOk(n) => self.ops.out(task.id, FileOutCopyDo::Adv(n)),\n\t\t\t\tErr(e) if e.kind() == NotFound => {\n\t\t\t\t\twarn!(\"Copy task partially done: {task:?}\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t// Operation not permitted (os error 1)\n\t\t\t\t// Attribute not found (os error 93)\n\t\t\t\tErr(e)\n\t\t\t\t\tif task.retry < YAZI.tasks.bizarre_retry\n\t\t\t\t\t\t&& matches!(e.raw_os_error(), Some(1) | Some(93)) =>\n\t\t\t\t{\n\t\t\t\t\ttask.retry += 1;\n\t\t\t\t\tself.ops.out(task.id, FileOutCopyDo::Log(format!(\"Retrying due to error: {e}\")));\n\t\t\t\t\treturn Ok(self.requeue(task, LOW));\n\t\t\t\t}\n\t\t\t\tErr(e) => ctx!(task, Err(e))?,\n\t\t\t}\n\t\t}\n\t\tOk(self.ops.out(task.id, FileOutCopyDo::Succ))\n\t}\n\n\tpub(crate) async fn cut(&self, mut task: FileInCut) -> Result<(), FileOutCut> {\n\t\tlet id = task.id;\n\n\t\tif !task.force {\n\t\t\ttask.to = unique_file(mem::take(&mut task.to), task.init().await?.is_dir())\n\t\t\t\t.await\n\t\t\t\t.context(\"Cannot determine unique destination name\")?;\n\t\t}\n\n\t\tself.ops.out(id, HookInOutCut::from(&task));\n\t\tif !task.follow && ok_or_not_found(provider::rename(&task.from, &task.to).await).is_ok() {\n\t\t\treturn Ok(self.ops.out(id, FileOutCut::Succ));\n\t\t}\n\n\t\tlet (mut links, mut files) = (vec![], vec![]);\n\t\tlet reorder = task.follow && ctx!(task, provider::capabilities(&task.from).await)?.symlink;\n\n\t\tsuper::traverse::<FileOutCut, _, _, _, _, _>(\n\t\t\ttask,\n\t\t\tasync |dir| match provider::create_dir(dir).await {\n\t\t\t\tErr(e) if e.kind() != io::ErrorKind::AlreadyExists => Err(e)?,\n\t\t\t\t_ => Ok(()),\n\t\t\t},\n\t\t\t|task, cha| {\n\t\t\t\tlet nofollow = cha.is_orphan() || (cha.is_link() && !task.follow);\n\t\t\t\tself.ops.out(id, FileOutCut::New(if nofollow { 0 } else { cha.len }));\n\n\t\t\t\tif nofollow {\n\t\t\t\t\tself.requeue(task.into_link(), NORMAL);\n\t\t\t\t} else {\n\t\t\t\t\tmatch (cha.is_link(), reorder) {\n\t\t\t\t\t\t(_, false) => self.requeue(task, LOW),\n\t\t\t\t\t\t(true, true) => links.push(task),\n\t\t\t\t\t\t(false, true) => files.push(task),\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tasync { Ok(()) }\n\t\t\t},\n\t\t\t|err| {\n\t\t\t\tself.ops.out(id, FileOutCut::Deform(err));\n\t\t\t},\n\t\t)\n\t\t.await?;\n\n\t\tif !links.is_empty() {\n\t\t\tlet (tx, mut rx) = mpsc::channel(1);\n\t\t\tfor task in links {\n\t\t\t\tself.requeue(task.with_drop(&tx), LOW);\n\t\t\t}\n\t\t\tdrop(tx);\n\t\t\twhile rx.recv().await.is_some() {}\n\t\t}\n\n\t\tfor task in files {\n\t\t\tself.requeue(task, LOW);\n\t\t}\n\n\t\tOk(self.ops.out(id, FileOutCut::Succ))\n\t}\n\n\tpub(crate) async fn cut_do(&self, mut task: FileInCut) -> Result<(), FileOutCutDo> {\n\t\tok_or_not_found!(task, Transaction::unlink(&task.to).await);\n\t\tlet mut it =\n\t\t\tctx!(task, provider::copy_with_progress(&task.from, &task.to, task.cha.unwrap()).await)?;\n\n\t\tloop {\n\t\t\tmatch progress_or_break!(it, task.done) {\n\t\t\t\tOk(0) => {\n\t\t\t\t\tprovider::remove_file(&task.from).await.ok();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tOk(n) => self.ops.out(task.id, FileOutCutDo::Adv(n)),\n\t\t\t\tErr(e) if e.kind() == NotFound => {\n\t\t\t\t\twarn!(\"Cut task partially done: {task:?}\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t// Operation not permitted (os error 1)\n\t\t\t\t// Attribute not found (os error 93)\n\t\t\t\tErr(e)\n\t\t\t\t\tif task.retry < YAZI.tasks.bizarre_retry\n\t\t\t\t\t\t&& matches!(e.raw_os_error(), Some(1) | Some(93)) =>\n\t\t\t\t{\n\t\t\t\t\ttask.retry += 1;\n\t\t\t\t\tself.ops.out(task.id, FileOutCutDo::Log(format!(\"Retrying due to error: {e}\")));\n\t\t\t\t\treturn Ok(self.requeue(task, LOW));\n\t\t\t\t}\n\t\t\t\tErr(e) => ctx!(task, Err(e))?,\n\t\t\t}\n\t\t}\n\t\tOk(self.ops.out(task.id, FileOutCutDo::Succ))\n\t}\n\n\tpub(crate) async fn link(&self, mut task: FileInLink) -> Result<(), FileOutLink> {\n\t\tif !task.force {\n\t\t\ttask.to =\n\t\t\t\tunique_file(task.to, false).await.context(\"Cannot determine unique destination name\")?;\n\t\t}\n\n\t\tOk(self.requeue(task, NORMAL))\n\t}\n\n\tpub(crate) async fn link_do(&self, task: FileInLink) -> Result<(), FileOutLink> {\n\t\tlet mut src: PathCow = if task.resolve {\n\t\t\tok_or_not_found!(\n\t\t\t\ttask,\n\t\t\t\tprovider::read_link(&task.from).await,\n\t\t\t\treturn Ok(self.ops.out(task.id, FileOutLink::Succ))\n\t\t\t)\n\t\t\t.into()\n\t\t} else {\n\t\t\ttask.from.loc().into()\n\t\t};\n\n\t\tif task.relative {\n\t\t\tlet canon = ctx!(task, provider::canonicalize(task.to.parent().unwrap()).await)?;\n\t\t\tsrc = ctx!(task, path_relative_to(canon.loc(), src))?;\n\t\t}\n\n\t\tok_or_not_found!(task, provider::remove_file(&task.to).await);\n\t\tctx!(\n\t\t\ttask,\n\t\t\tprovider::symlink(&task.to, src, async || {\n\t\t\t\tOk(match task.cha {\n\t\t\t\t\tSome(cha) => cha.is_dir(),\n\t\t\t\t\tNone => Self::cha(&task.from, task.resolve, None).await?.is_dir(),\n\t\t\t\t})\n\t\t\t})\n\t\t\t.await\n\t\t)?;\n\n\t\tif task.delete {\n\t\t\tprovider::remove_file(&task.from).await.ok();\n\t\t}\n\t\tOk(self.ops.out(task.id, FileOutLink::Succ))\n\t}\n\n\tpub(crate) async fn hardlink(&self, mut task: FileInHardlink) -> Result<(), FileOutHardlink> {\n\t\tlet id = task.id;\n\n\t\tif !task.force {\n\t\t\ttask.to =\n\t\t\t\tunique_file(task.to, false).await.context(\"Cannot determine unique destination name\")?;\n\t\t}\n\n\t\tsuper::traverse::<FileOutHardlink, _, _, _, _, _>(\n\t\t\ttask,\n\t\t\tasync |dir| match provider::create_dir(dir).await {\n\t\t\t\tErr(e) if e.kind() != io::ErrorKind::AlreadyExists => Err(e)?,\n\t\t\t\t_ => Ok(()),\n\t\t\t},\n\t\t\tasync |task, _cha| {\n\t\t\t\tself.ops.out(id, FileOutHardlink::New);\n\t\t\t\tOk(self.requeue(task, NORMAL))\n\t\t\t},\n\t\t\t|err| {\n\t\t\t\tself.ops.out(id, FileOutHardlink::Deform(err));\n\t\t\t},\n\t\t)\n\t\t.await?;\n\n\t\tOk(self.ops.out(id, FileOutHardlink::Succ))\n\t}\n\n\tpub(crate) async fn hardlink_do(&self, task: FileInHardlink) -> Result<(), FileOutHardlinkDo> {\n\t\tlet src = if !task.follow {\n\t\t\tUrlCow::from(&task.from)\n\t\t} else if let Ok(p) = provider::canonicalize(&task.from).await {\n\t\t\tUrlCow::from(p)\n\t\t} else {\n\t\t\tUrlCow::from(&task.from)\n\t\t};\n\n\t\tok_or_not_found!(task, provider::remove_file(&task.to).await);\n\t\tok_or_not_found!(task, provider::hard_link(&src, &task.to).await);\n\n\t\tOk(self.ops.out(task.id, FileOutHardlinkDo::Succ))\n\t}\n\n\tpub(crate) async fn delete(&self, task: FileInDelete) -> Result<(), FileOutDelete> {\n\t\tlet id = task.id;\n\n\t\tsuper::traverse::<FileOutDelete, _, _, _, _, _>(\n\t\t\ttask,\n\t\t\tasync |_dir| Ok(()),\n\t\t\tasync |task, cha| {\n\t\t\t\tself.ops.out(id, FileOutDelete::New(cha.len));\n\t\t\t\tOk(self.requeue(task, NORMAL))\n\t\t\t},\n\t\t\t|_err| {},\n\t\t)\n\t\t.await?;\n\n\t\tOk(self.ops.out(id, FileOutDelete::Succ))\n\t}\n\n\tpub(crate) async fn delete_do(&self, task: FileInDelete) -> Result<(), FileOutDeleteDo> {\n\t\tmatch provider::remove_file(&task.target).await {\n\t\t\tOk(()) => {}\n\t\t\tErr(e) if e.kind() == NotFound => {}\n\t\t\tErr(_) if !maybe_exists(&task.target).await => {}\n\t\t\tErr(e) => ctx!(task, Err(e))?,\n\t\t}\n\t\tOk(self.ops.out(task.id, FileOutDeleteDo::Succ(task.cha.unwrap().len)))\n\t}\n\n\tpub(crate) async fn trash(&self, task: FileInTrash) -> Result<(), FileOutTrash> {\n\t\tOk(self.requeue(task, LOW))\n\t}\n\n\tpub(crate) async fn trash_do(&self, task: FileInTrash) -> Result<(), FileOutTrash> {\n\t\tctx!(task, provider::trash(&task.target).await)?;\n\t\tOk(self.ops.out(task.id, FileOutTrash::Succ))\n\t}\n\n\tpub(crate) async fn download(&self, task: FileInDownload) -> Result<(), FileOutDownload> {\n\t\tlet id = task.id;\n\n\t\tsuper::traverse::<FileOutDownload, _, _, _, _, _>(\n\t\t\ttask,\n\t\t\tasync |dir| {\n\t\t\t\tlet dir = dir.to_owned();\n\t\t\t\ttokio::task::spawn_blocking(move || _ = Cwd::ensure(dir.as_url())).await.ok();\n\t\t\t\tOk(())\n\t\t\t},\n\t\t\tasync |task, cha| {\n\t\t\t\tOk(if cha.is_orphan() {\n\t\t\t\t\tErr(anyhow!(\"Failed to work on {task:?}: source of symlink doesn't exist\"))?\n\t\t\t\t} else {\n\t\t\t\t\tself.ops.out(id, FileOutDownload::New(cha.len));\n\t\t\t\t\tself.requeue(task, LOW);\n\t\t\t\t})\n\t\t\t},\n\t\t\t|err| {\n\t\t\t\tself.ops.out(id, FileOutDownload::Deform(err));\n\t\t\t},\n\t\t)\n\t\t.await?;\n\n\t\tOk(self.ops.out(id, FileOutDownload::Succ))\n\t}\n\n\tpub(crate) async fn download_do(\n\t\t&self,\n\t\tmut task: FileInDownload,\n\t) -> Result<(), FileOutDownloadDo> {\n\t\tlet cha = task.cha.unwrap();\n\n\t\tlet cache = ctx!(task, task.target.cache(), \"Cannot determine cache path\")?;\n\t\tlet cache_tmp = ctx!(task, Transaction::tmp(&cache).await, \"Cannot determine download cache\")?;\n\n\t\tlet mut it = ctx!(task, provider::copy_with_progress(&task.target, &cache_tmp, cha).await)?;\n\t\tloop {\n\t\t\tmatch progress_or_break!(it, task.done) {\n\t\t\t\tOk(0) => {\n\t\t\t\t\tLocal::regular(&cache).remove_dir_all().await.ok();\n\t\t\t\t\tctx!(task, provider::rename(cache_tmp, cache).await, \"Cannot persist downloaded file\")?;\n\n\t\t\t\t\tlet lock = ctx!(task, task.target.cache_lock(), \"Cannot determine cache lock\")?;\n\t\t\t\t\tlet hash = format!(\"{:x}\", cha.hash_u128());\n\t\t\t\t\tctx!(task, Local::regular(&lock).write(hash).await, \"Cannot lock cache\")?;\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tOk(n) => self.ops.out(task.id, FileOutDownloadDo::Adv(n)),\n\t\t\t\tErr(e) if e.kind() == NotFound => {\n\t\t\t\t\twarn!(\"Download task partially done: {task:?}\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t// Operation not permitted (os error 1)\n\t\t\t\t// Attribute not found (os error 93)\n\t\t\t\tErr(e)\n\t\t\t\t\tif task.retry < YAZI.tasks.bizarre_retry\n\t\t\t\t\t\t&& matches!(e.raw_os_error(), Some(1) | Some(93)) =>\n\t\t\t\t{\n\t\t\t\t\ttask.retry += 1;\n\t\t\t\t\tself.ops.out(task.id, FileOutDownloadDo::Log(format!(\"Retrying due to error: {e}\")));\n\t\t\t\t\treturn Ok(self.requeue(task, LOW));\n\t\t\t\t}\n\t\t\t\tErr(e) => ctx!(task, Err(e))?,\n\t\t\t}\n\t\t}\n\t\tOk(self.ops.out(task.id, FileOutDownloadDo::Succ))\n\t}\n\n\tpub(crate) async fn upload(&self, task: FileInUpload) -> Result<(), FileOutUpload> {\n\t\tlet id = task.id;\n\n\t\tsuper::traverse::<FileOutUpload, _, _, _, _, _>(\n\t\t\ttask,\n\t\t\tasync |_dir| Ok(()),\n\t\t\tasync |task, cha| {\n\t\t\t\tlet cache = ctx!(task, task.cache.as_ref(), \"Cannot determine cache path\")?;\n\n\t\t\t\tOk(match Self::cha(cache, true, None).await {\n\t\t\t\t\tOk(c) if c.mtime == cha.mtime => {}\n\t\t\t\t\tOk(c) => {\n\t\t\t\t\t\tself.ops.out(id, FileOutUpload::New(c.len));\n\t\t\t\t\t\tself.requeue(task, LOW);\n\t\t\t\t\t}\n\t\t\t\t\tErr(e) if e.kind() == NotFound => {}\n\t\t\t\t\tErr(e) => ctx!(task, Err(e))?,\n\t\t\t\t})\n\t\t\t},\n\t\t\t|err| {\n\t\t\t\tself.ops.out(id, FileOutUpload::Deform(err));\n\t\t\t},\n\t\t)\n\t\t.await?;\n\n\t\tOk(self.ops.out(id, FileOutUpload::Succ))\n\t}\n\n\tpub(crate) async fn upload_do(&self, task: FileInUpload) -> Result<(), FileOutUploadDo> {\n\t\tlet cha = task.cha.unwrap();\n\t\tlet cache = ctx!(task, task.cache.as_ref(), \"Cannot determine cache path\")?;\n\t\tlet lock = ctx!(task, task.target.cache_lock(), \"Cannot determine cache lock\")?;\n\n\t\tlet hash = ctx!(task, Local::regular(&lock).read_to_string().await, \"Cannot read cache lock\")?;\n\t\tlet hash = ctx!(task, u128::from_str_radix(&hash, 16), \"Cannot parse hash from lock\")?;\n\t\tif hash != cha.hash_u128() {\n\t\t\tErr(anyhow!(\"Failed to work on: {task:?}: remote file has changed since last download\"))?;\n\t\t}\n\n\t\tlet tmp =\n\t\t\tctx!(task, Transaction::tmp(&task.target).await, \"Cannot determine temporary upload path\")?;\n\t\tlet mut it = ctx!(\n\t\t\ttask,\n\t\t\tprovider::copy_with_progress(cache, &tmp, Attrs {\n\t\t\t\tmode:  Some(cha.mode),\n\t\t\t\tatime: None,\n\t\t\t\tbtime: None,\n\t\t\t\tmtime: None,\n\t\t\t})\n\t\t\t.await\n\t\t)?;\n\n\t\tloop {\n\t\t\tmatch progress_or_break!(it, task.done) {\n\t\t\t\tOk(0) => {\n\t\t\t\t\tlet cha =\n\t\t\t\t\t\tctx!(task, Self::cha(&task.target, true, None).await, \"Cannot stat original file\")?;\n\t\t\t\t\tif hash != cha.hash_u128() {\n\t\t\t\t\t\tErr(anyhow!(\"Failed to work on: {task:?}: remote file has changed during upload\"))?;\n\t\t\t\t\t}\n\n\t\t\t\t\tctx!(task, provider::rename(&tmp, &task.target).await, \"Cannot persist uploaded file\")?;\n\n\t\t\t\t\tlet cha =\n\t\t\t\t\t\tctx!(task, Self::cha(&task.target, true, None).await, \"Cannot stat uploaded file\")?;\n\t\t\t\t\tlet hash = format!(\"{:x}\", cha.hash_u128());\n\t\t\t\t\tctx!(task, Local::regular(&lock).write(hash).await, \"Cannot lock cache\")?;\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tOk(n) => self.ops.out(task.id, FileOutUploadDo::Adv(n)),\n\t\t\t\tErr(e) => ctx!(task, Err(e))?,\n\t\t\t}\n\t\t}\n\t\tOk(self.ops.out(task.id, FileOutUploadDo::Succ))\n\t}\n\n\tpub(super) async fn cha<U>(url: U, follow: bool, entry: Option<DirEntry>) -> io::Result<Cha>\n\twhere\n\t\tU: AsUrl,\n\t{\n\t\tlet cha = if let Some(entry) = entry {\n\t\t\tentry.metadata().await?\n\t\t} else {\n\t\t\tprovider::symlink_metadata(url.as_url()).await?\n\t\t};\n\t\tOk(if follow { Cha::from_follow(url, cha).await } else { cha })\n\t}\n}\n\nimpl File {\n\t#[inline]\n\tpub(crate) fn submit(&self, r#in: impl Into<FileIn>, priority: u8) {\n\t\t_ = self.tx.try_send(r#in.into(), priority);\n\t}\n\n\t#[inline]\n\tfn requeue(&self, r#in: impl Into<FileIn>, priority: u8) {\n\t\t_ = self.tx.try_send(r#in.into().into_doable(), priority);\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/src/file/in.rs",
    "content": "use std::{mem, path::PathBuf};\n\nuse tokio::sync::mpsc;\nuse yazi_fs::cha::Cha;\nuse yazi_shared::{CompletionToken, Id, url::UrlBuf};\n\n#[derive(Debug)]\npub(crate) enum FileIn {\n\tCopy(FileInCopy),\n\tCopyDo(FileInCopy),\n\tCut(FileInCut),\n\tCutDo(FileInCut),\n\tLink(FileInLink),\n\tLinkDo(FileInLink),\n\tHardlink(FileInHardlink),\n\tHardlinkDo(FileInHardlink),\n\tDelete(FileInDelete),\n\tDeleteDo(FileInDelete),\n\tTrash(FileInTrash),\n\tTrashDo(FileInTrash),\n\tDownload(FileInDownload),\n\tDownloadDo(FileInDownload),\n\tUpload(FileInUpload),\n\tUploadDo(FileInUpload),\n}\n\nimpl_from_in! {\n\tCopy(FileInCopy),\n\tCut(FileInCut),\n\tLink(FileInLink),\n\tHardlink(FileInHardlink),\n\tDelete(FileInDelete),\n\tTrash(FileInTrash),\n\tDownload(FileInDownload),\n\tUpload(FileInUpload),\n}\n\nimpl FileIn {\n\tpub(crate) fn id(&self) -> Id {\n\t\tmatch self {\n\t\t\tSelf::Copy(r#in) => r#in.id,\n\t\t\tSelf::CopyDo(r#in) => r#in.id,\n\t\t\tSelf::Cut(r#in) => r#in.id,\n\t\t\tSelf::CutDo(r#in) => r#in.id,\n\t\t\tSelf::Link(r#in) => r#in.id,\n\t\t\tSelf::LinkDo(r#in) => r#in.id,\n\t\t\tSelf::Hardlink(r#in) => r#in.id,\n\t\t\tSelf::HardlinkDo(r#in) => r#in.id,\n\t\t\tSelf::Delete(r#in) => r#in.id,\n\t\t\tSelf::DeleteDo(r#in) => r#in.id,\n\t\t\tSelf::Trash(r#in) => r#in.id,\n\t\t\tSelf::TrashDo(r#in) => r#in.id,\n\t\t\tSelf::Download(r#in) => r#in.id,\n\t\t\tSelf::DownloadDo(r#in) => r#in.id,\n\t\t\tSelf::Upload(r#in) => r#in.id,\n\t\t\tSelf::UploadDo(r#in) => r#in.id,\n\t\t}\n\t}\n\n\tpub(crate) fn into_doable(self) -> Self {\n\t\tmatch self {\n\t\t\tSelf::Copy(r#in) => Self::CopyDo(r#in),\n\t\t\tSelf::CopyDo(_) => self,\n\t\t\tSelf::Cut(r#in) => Self::CutDo(r#in),\n\t\t\tSelf::CutDo(_) => self,\n\t\t\tSelf::Link(r#in) => Self::LinkDo(r#in),\n\t\t\tSelf::LinkDo(_) => self,\n\t\t\tSelf::Hardlink(r#in) => Self::HardlinkDo(r#in),\n\t\t\tSelf::HardlinkDo(_) => self,\n\t\t\tSelf::Delete(r#in) => Self::DeleteDo(r#in),\n\t\t\tSelf::DeleteDo(_) => self,\n\t\t\tSelf::Trash(r#in) => Self::TrashDo(r#in),\n\t\t\tSelf::TrashDo(_) => self,\n\t\t\tSelf::Download(r#in) => Self::DownloadDo(r#in),\n\t\t\tSelf::DownloadDo(_) => self,\n\t\t\tSelf::Upload(r#in) => Self::UploadDo(r#in),\n\t\t\tSelf::UploadDo(_) => self,\n\t\t}\n\t}\n}\n\n// --- Copy\n#[derive(Clone, Debug)]\npub(crate) struct FileInCopy {\n\tpub(crate) id:     Id,\n\tpub(crate) from:   UrlBuf,\n\tpub(crate) to:     UrlBuf,\n\tpub(crate) force:  bool,\n\tpub(crate) cha:    Option<Cha>,\n\tpub(crate) follow: bool,\n\tpub(crate) retry:  u8,\n\tpub(crate) done:   CompletionToken,\n}\n\nimpl FileInCopy {\n\tpub(super) fn into_link(self) -> FileInLink {\n\t\tFileInLink {\n\t\t\tid:       self.id,\n\t\t\tfrom:     self.from,\n\t\t\tto:       self.to,\n\t\t\tforce:    true,\n\t\t\tcha:      self.cha,\n\t\t\tresolve:  true,\n\t\t\trelative: false,\n\t\t\tdelete:   false,\n\t\t}\n\t}\n}\n\n// --- Cut\n#[derive(Clone, Debug)]\npub(crate) struct FileInCut {\n\tpub(crate) id:     Id,\n\tpub(crate) from:   UrlBuf,\n\tpub(crate) to:     UrlBuf,\n\tpub(crate) force:  bool,\n\tpub(crate) cha:    Option<Cha>,\n\tpub(crate) follow: bool,\n\tpub(crate) retry:  u8,\n\tpub(crate) done:   CompletionToken,\n\tpub(crate) drop:   Option<mpsc::Sender<()>>,\n}\n\nimpl Drop for FileInCut {\n\tfn drop(&mut self) { _ = self.drop.take(); }\n}\n\nimpl FileInCut {\n\tpub(super) fn into_link(mut self) -> FileInLink {\n\t\tFileInLink {\n\t\t\tid:       self.id,\n\t\t\tfrom:     mem::take(&mut self.from),\n\t\t\tto:       mem::take(&mut self.to),\n\t\t\tforce:    true,\n\t\t\tcha:      self.cha,\n\t\t\tresolve:  true,\n\t\t\trelative: false,\n\t\t\tdelete:   true,\n\t\t}\n\t}\n\n\tpub(super) fn with_drop(mut self, drop: &mpsc::Sender<()>) -> Self {\n\t\tself.drop = Some(drop.clone());\n\t\tself\n\t}\n}\n\n// --- Link\n#[derive(Clone, Debug)]\npub(crate) struct FileInLink {\n\tpub(crate) id:       Id,\n\tpub(crate) from:     UrlBuf,\n\tpub(crate) to:       UrlBuf,\n\tpub(crate) force:    bool,\n\tpub(crate) cha:      Option<Cha>,\n\tpub(crate) resolve:  bool,\n\tpub(crate) relative: bool,\n\tpub(crate) delete:   bool,\n}\n\n// --- Hardlink\n#[derive(Clone, Debug)]\npub(crate) struct FileInHardlink {\n\tpub(crate) id:     Id,\n\tpub(crate) from:   UrlBuf,\n\tpub(crate) to:     UrlBuf,\n\tpub(crate) force:  bool,\n\tpub(crate) cha:    Option<Cha>,\n\tpub(crate) follow: bool,\n}\n\n// --- Delete\n#[derive(Clone, Debug)]\npub(crate) struct FileInDelete {\n\tpub(crate) id:     Id,\n\tpub(crate) target: UrlBuf,\n\tpub(crate) cha:    Option<Cha>,\n}\n\n// --- Trash\n#[derive(Clone, Debug)]\npub(crate) struct FileInTrash {\n\tpub(crate) id:     Id,\n\tpub(crate) target: UrlBuf,\n}\n\n// --- Download\n#[derive(Clone, Debug)]\npub(crate) struct FileInDownload {\n\tpub(crate) id:     Id,\n\tpub(crate) target: UrlBuf,\n\tpub(crate) cha:    Option<Cha>,\n\tpub(crate) retry:  u8,\n\tpub(crate) done:   CompletionToken,\n}\n\n// --- Upload\n#[derive(Clone, Debug)]\npub(crate) struct FileInUpload {\n\tpub(crate) id:     Id,\n\tpub(crate) target: UrlBuf,\n\tpub(crate) cha:    Option<Cha>,\n\tpub(crate) cache:  Option<PathBuf>,\n\tpub(crate) done:   CompletionToken,\n}\n"
  },
  {
    "path": "yazi-scheduler/src/file/macros.rs",
    "content": "macro_rules! impl_from_in {\n\t($($variant:ident($type:ty)),* $(,)?) => {\n\t\t$(\n\t\t\timpl From<$type> for $crate::file::FileIn {\n\t\t\t\tfn from(value: $type) -> Self { Self::$variant(value) }\n\t\t\t}\n\t\t)*\n\t};\n}\n"
  },
  {
    "path": "yazi-scheduler/src/file/mod.rs",
    "content": "#[macro_use]\nmod macros;\n\nyazi_macro::mod_flat!(file out progress r#in transaction traverse);\n"
  },
  {
    "path": "yazi-scheduler/src/file/out.rs",
    "content": "use std::io;\n\nuse crate::{Task, TaskProg};\n\n// --- Copy\n#[derive(Debug)]\npub(crate) enum FileOutCopy {\n\tNew(u64),\n\tDeform(String),\n\tSucc,\n\tFail(String),\n\tClean,\n}\n\nimpl From<anyhow::Error> for FileOutCopy {\n\tfn from(value: anyhow::Error) -> Self { Self::Fail(format!(\"{value:?}\")) }\n}\n\nimpl From<std::io::Error> for FileOutCopy {\n\tfn from(value: std::io::Error) -> Self { Self::Fail(format!(\"{value:?}\")) }\n}\n\nimpl FileOutCopy {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tlet TaskProg::FileCopy(prog) = &mut task.prog else { return };\n\t\tmatch self {\n\t\t\tSelf::New(bytes) => {\n\t\t\t\tprog.total_files += 1;\n\t\t\t\tprog.total_bytes += bytes;\n\t\t\t}\n\t\t\tSelf::Deform(reason) => {\n\t\t\t\tprog.total_files += 1;\n\t\t\t\tprog.failed_files += 1;\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t\tSelf::Succ => {\n\t\t\t\tprog.collected = Some(true);\n\t\t\t}\n\t\t\tSelf::Fail(reason) => {\n\t\t\t\tprog.collected = Some(false);\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t\tSelf::Clean => {\n\t\t\t\tprog.cleaned = Some(true);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// --- CopyDo\n#[derive(Debug)]\npub(crate) enum FileOutCopyDo {\n\tAdv(u64),\n\tLog(String),\n\tSucc,\n\tFail(String),\n}\n\nimpl From<anyhow::Error> for FileOutCopyDo {\n\tfn from(value: anyhow::Error) -> Self { Self::Fail(format!(\"{value:?}\")) }\n}\n\nimpl FileOutCopyDo {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tlet TaskProg::FileCopy(prog) = &mut task.prog else { return };\n\t\tmatch self {\n\t\t\tSelf::Adv(size) => {\n\t\t\t\tprog.processed_bytes += size;\n\t\t\t}\n\t\t\tSelf::Log(line) => {\n\t\t\t\ttask.log(line);\n\t\t\t}\n\t\t\tSelf::Succ => {\n\t\t\t\tprog.success_files += 1;\n\t\t\t}\n\t\t\tSelf::Fail(reason) => {\n\t\t\t\tprog.failed_files += 1;\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// --- Cut\n#[derive(Debug)]\npub(crate) enum FileOutCut {\n\tNew(u64),\n\tDeform(String),\n\tSucc,\n\tFail(String),\n\tClean(io::Result<()>),\n}\n\nimpl From<anyhow::Error> for FileOutCut {\n\tfn from(value: anyhow::Error) -> Self { Self::Fail(format!(\"{value:?}\")) }\n}\n\nimpl From<std::io::Error> for FileOutCut {\n\tfn from(value: std::io::Error) -> Self { Self::Fail(format!(\"{value:?}\")) }\n}\n\nimpl FileOutCut {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tlet TaskProg::FileCut(prog) = &mut task.prog else { return };\n\t\tmatch self {\n\t\t\tSelf::New(bytes) => {\n\t\t\t\tprog.total_files += 1;\n\t\t\t\tprog.total_bytes += bytes;\n\t\t\t}\n\t\t\tSelf::Deform(reason) => {\n\t\t\t\tprog.total_files += 1;\n\t\t\t\tprog.failed_files += 1;\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t\tSelf::Succ => {\n\t\t\t\tprog.collected = Some(true);\n\t\t\t}\n\t\t\tSelf::Fail(reason) => {\n\t\t\t\tprog.collected = Some(false);\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t\tSelf::Clean(Ok(())) => {\n\t\t\t\tprog.cleaned = Some(true);\n\t\t\t}\n\t\t\tSelf::Clean(Err(reason)) => {\n\t\t\t\tprog.cleaned = Some(false);\n\t\t\t\ttask.log(format!(\"Failed cleaning up cut file: {reason:?}\"));\n\t\t\t}\n\t\t}\n\t}\n}\n\n// --- CutDo\n#[derive(Debug)]\npub(crate) enum FileOutCutDo {\n\tAdv(u64),\n\tLog(String),\n\tSucc,\n\tFail(String),\n}\n\nimpl From<anyhow::Error> for FileOutCutDo {\n\tfn from(value: anyhow::Error) -> Self { Self::Fail(format!(\"{value:?}\")) }\n}\n\nimpl FileOutCutDo {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tlet TaskProg::FileCut(prog) = &mut task.prog else { return };\n\t\tmatch self {\n\t\t\tSelf::Adv(size) => {\n\t\t\t\tprog.processed_bytes += size;\n\t\t\t}\n\t\t\tSelf::Log(line) => {\n\t\t\t\ttask.log(line);\n\t\t\t}\n\t\t\tSelf::Succ => {\n\t\t\t\tprog.success_files += 1;\n\t\t\t}\n\t\t\tSelf::Fail(reason) => {\n\t\t\t\tprog.failed_files += 1;\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// --- Link\n#[derive(Debug)]\npub(crate) enum FileOutLink {\n\tSucc,\n\tFail(String),\n}\n\nimpl From<anyhow::Error> for FileOutLink {\n\tfn from(value: anyhow::Error) -> Self { Self::Fail(format!(\"{value:?}\")) }\n}\n\nimpl FileOutLink {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tif let TaskProg::FileLink(prog) = &mut task.prog {\n\t\t\tmatch self {\n\t\t\t\tSelf::Succ => {\n\t\t\t\t\tprog.state = Some(true);\n\t\t\t\t}\n\t\t\t\tSelf::Fail(reason) => {\n\t\t\t\t\tprog.state = Some(false);\n\t\t\t\t\ttask.log(reason);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if let TaskProg::FileCopy(prog) = &mut task.prog {\n\t\t\tmatch self {\n\t\t\t\tSelf::Succ => {\n\t\t\t\t\tprog.success_files += 1;\n\t\t\t\t}\n\t\t\t\tSelf::Fail(reason) => {\n\t\t\t\t\tprog.failed_files += 1;\n\t\t\t\t\ttask.log(reason);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if let TaskProg::FileCut(prog) = &mut task.prog {\n\t\t\tmatch self {\n\t\t\t\tSelf::Succ => {\n\t\t\t\t\tprog.success_files += 1;\n\t\t\t\t}\n\t\t\t\tSelf::Fail(reason) => {\n\t\t\t\t\tprog.failed_files += 1;\n\t\t\t\t\ttask.log(reason);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// --- Hardlink\n#[derive(Debug)]\npub(crate) enum FileOutHardlink {\n\tNew,\n\tDeform(String),\n\tSucc,\n\tFail(String),\n}\n\nimpl From<anyhow::Error> for FileOutHardlink {\n\tfn from(value: anyhow::Error) -> Self { Self::Fail(format!(\"{value:?}\")) }\n}\n\nimpl From<std::io::Error> for FileOutHardlink {\n\tfn from(value: std::io::Error) -> Self { Self::Fail(format!(\"{value:?}\")) }\n}\n\nimpl FileOutHardlink {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tlet TaskProg::FileHardlink(prog) = &mut task.prog else { return };\n\t\tmatch self {\n\t\t\tSelf::New => {\n\t\t\t\tprog.total += 1;\n\t\t\t}\n\t\t\tSelf::Deform(reason) => {\n\t\t\t\tprog.total += 1;\n\t\t\t\tprog.failed += 1;\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t\tSelf::Succ => {\n\t\t\t\tprog.collected = Some(true);\n\t\t\t}\n\t\t\tSelf::Fail(reason) => {\n\t\t\t\tprog.collected = Some(false);\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// --- HardlinkDo\n#[derive(Debug)]\npub(crate) enum FileOutHardlinkDo {\n\tSucc,\n\tFail(String),\n}\n\nimpl From<anyhow::Error> for FileOutHardlinkDo {\n\tfn from(value: anyhow::Error) -> Self { Self::Fail(format!(\"{value:?}\")) }\n}\n\nimpl FileOutHardlinkDo {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tlet TaskProg::FileHardlink(prog) = &mut task.prog else { return };\n\t\tmatch self {\n\t\t\tSelf::Succ => {\n\t\t\t\tprog.success += 1;\n\t\t\t}\n\t\t\tSelf::Fail(reason) => {\n\t\t\t\tprog.failed += 1;\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// --- Delete\n#[derive(Debug)]\npub(crate) enum FileOutDelete {\n\tNew(u64),\n\tSucc,\n\tFail(String),\n\tClean(io::Result<()>),\n}\n\nimpl From<anyhow::Error> for FileOutDelete {\n\tfn from(value: anyhow::Error) -> Self { Self::Fail(format!(\"{value:?}\")) }\n}\n\nimpl FileOutDelete {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tlet TaskProg::FileDelete(prog) = &mut task.prog else { return };\n\t\tmatch self {\n\t\t\tSelf::New(size) => {\n\t\t\t\tprog.total_files += 1;\n\t\t\t\tprog.total_bytes += size;\n\t\t\t}\n\t\t\tSelf::Succ => {\n\t\t\t\tprog.collected = Some(true);\n\t\t\t}\n\t\t\tSelf::Fail(reason) => {\n\t\t\t\tprog.collected = Some(false);\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t\tSelf::Clean(Ok(())) => {\n\t\t\t\tprog.cleaned = Some(true);\n\t\t\t}\n\t\t\tSelf::Clean(Err(reason)) => {\n\t\t\t\tprog.cleaned = Some(false);\n\t\t\t\ttask.log(format!(\"Failed cleaning up deleted file: {reason:?}\"));\n\t\t\t}\n\t\t}\n\t}\n}\n\n// --- DeleteDo\n#[derive(Debug)]\npub(crate) enum FileOutDeleteDo {\n\tSucc(u64),\n\tFail(String),\n}\n\nimpl From<anyhow::Error> for FileOutDeleteDo {\n\tfn from(value: anyhow::Error) -> Self { Self::Fail(format!(\"{value:?}\")) }\n}\n\nimpl FileOutDeleteDo {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tlet TaskProg::FileDelete(prog) = &mut task.prog else { return };\n\t\tmatch self {\n\t\t\tSelf::Succ(size) => {\n\t\t\t\tprog.success_files += 1;\n\t\t\t\tprog.processed_bytes += size;\n\t\t\t}\n\t\t\tSelf::Fail(reason) => {\n\t\t\t\tprog.failed_files += 1;\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// --- Trash\n#[derive(Debug)]\npub(crate) enum FileOutTrash {\n\tSucc,\n\tFail(String),\n\tClean,\n}\n\nimpl From<anyhow::Error> for FileOutTrash {\n\tfn from(value: anyhow::Error) -> Self { Self::Fail(format!(\"{value:?}\")) }\n}\n\nimpl FileOutTrash {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tlet TaskProg::FileTrash(prog) = &mut task.prog else { return };\n\t\tmatch self {\n\t\t\tSelf::Succ => {\n\t\t\t\tprog.state = Some(true);\n\t\t\t}\n\t\t\tSelf::Fail(reason) => {\n\t\t\t\tprog.state = Some(false);\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t\tSelf::Clean => {\n\t\t\t\tprog.cleaned = Some(true);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// --- Download\n#[derive(Debug)]\npub(crate) enum FileOutDownload {\n\tNew(u64),\n\tDeform(String),\n\tSucc,\n\tFail(String),\n\tClean,\n}\n\nimpl From<anyhow::Error> for FileOutDownload {\n\tfn from(value: anyhow::Error) -> Self { Self::Fail(format!(\"{value:?}\")) }\n}\n\nimpl FileOutDownload {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tlet TaskProg::FileDownload(prog) = &mut task.prog else { return };\n\t\tmatch self {\n\t\t\tSelf::New(bytes) => {\n\t\t\t\tprog.total_files += 1;\n\t\t\t\tprog.total_bytes += bytes;\n\t\t\t}\n\t\t\tSelf::Deform(reason) => {\n\t\t\t\tprog.total_files += 1;\n\t\t\t\tprog.failed_files += 1;\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t\tSelf::Succ => {\n\t\t\t\tprog.collected = Some(true);\n\t\t\t}\n\t\t\tSelf::Fail(reason) => {\n\t\t\t\tprog.collected = Some(false);\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t\tSelf::Clean => {\n\t\t\t\tprog.cleaned = Some(true);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// --- DownloadDo\n#[derive(Debug)]\npub(crate) enum FileOutDownloadDo {\n\tAdv(u64),\n\tLog(String),\n\tSucc,\n\tFail(String),\n}\n\nimpl From<anyhow::Error> for FileOutDownloadDo {\n\tfn from(value: anyhow::Error) -> Self { Self::Fail(format!(\"{value:?}\")) }\n}\n\nimpl FileOutDownloadDo {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tlet TaskProg::FileDownload(prog) = &mut task.prog else { return };\n\t\tmatch self {\n\t\t\tSelf::Adv(size) => {\n\t\t\t\tprog.processed_bytes += size;\n\t\t\t}\n\t\t\tSelf::Log(line) => {\n\t\t\t\ttask.log(line);\n\t\t\t}\n\t\t\tSelf::Succ => {\n\t\t\t\tprog.success_files += 1;\n\t\t\t}\n\t\t\tSelf::Fail(reason) => {\n\t\t\t\tprog.failed_files += 1;\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// --- Upload\n#[derive(Debug)]\npub(crate) enum FileOutUpload {\n\tNew(u64),\n\tDeform(String),\n\tSucc,\n\tFail(String),\n\tClean,\n}\n\nimpl From<anyhow::Error> for FileOutUpload {\n\tfn from(value: anyhow::Error) -> Self { Self::Fail(format!(\"{value:?}\")) }\n}\n\nimpl FileOutUpload {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tlet TaskProg::FileUpload(prog) = &mut task.prog else { return };\n\t\tmatch self {\n\t\t\tSelf::New(bytes) => {\n\t\t\t\tprog.total_files += 1;\n\t\t\t\tprog.total_bytes += bytes;\n\t\t\t}\n\t\t\tSelf::Deform(reason) => {\n\t\t\t\tprog.total_files += 1;\n\t\t\t\tprog.failed_files += 1;\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t\tSelf::Succ => {\n\t\t\t\tprog.collected = Some(true);\n\t\t\t}\n\t\t\tSelf::Fail(reason) => {\n\t\t\t\tprog.collected = Some(false);\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t\tSelf::Clean => {\n\t\t\t\tprog.cleaned = Some(true);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// --- UploadDo\n#[derive(Debug)]\npub(crate) enum FileOutUploadDo {\n\tAdv(u64),\n\tSucc,\n\tFail(String),\n}\n\nimpl From<anyhow::Error> for FileOutUploadDo {\n\tfn from(value: anyhow::Error) -> Self { Self::Fail(format!(\"{value:?}\")) }\n}\n\nimpl FileOutUploadDo {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tlet TaskProg::FileUpload(prog) = &mut task.prog else { return };\n\t\tmatch self {\n\t\t\tSelf::Adv(size) => {\n\t\t\t\tprog.processed_bytes += size;\n\t\t\t}\n\t\t\tSelf::Succ => {\n\t\t\t\tprog.success_files += 1;\n\t\t\t}\n\t\t\tSelf::Fail(reason) => {\n\t\t\t\tprog.failed_files += 1;\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/src/file/progress.rs",
    "content": "use serde::Serialize;\nuse yazi_parser::app::TaskSummary;\n\n// --- Copy\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]\npub struct FileProgCopy {\n\tpub total_files:     u32,\n\tpub success_files:   u32,\n\tpub failed_files:    u32,\n\tpub total_bytes:     u64,\n\tpub processed_bytes: u64,\n\tpub collected:       Option<bool>,\n\tpub cleaned:         Option<bool>,\n}\n\nimpl From<FileProgCopy> for TaskSummary {\n\tfn from(value: FileProgCopy) -> Self {\n\t\tSelf {\n\t\t\ttotal:   value.total_files,\n\t\t\tsuccess: value.success_files,\n\t\t\tfailed:  value.failed_files,\n\t\t\tpercent: value.percent().map(Into::into),\n\t\t}\n\t}\n}\n\nimpl FileProgCopy {\n\tpub fn cooked(self) -> bool {\n\t\tself.collected == Some(true) && self.success_files == self.total_files\n\t}\n\n\tpub fn running(self) -> bool {\n\t\tself.collected.is_none()\n\t\t\t|| self.success_files + self.failed_files != self.total_files\n\t\t\t|| (self.cleaned.is_none() && self.cooked())\n\t}\n\n\tpub fn success(self) -> bool { self.cleaned == Some(true) && self.cooked() }\n\n\tpub fn failed(self) -> bool { self.cleaned == Some(false) || self.collected == Some(false) }\n\n\tpub fn cleaned(self) -> Option<bool> { self.cleaned }\n\n\tpub fn percent(self) -> Option<f32> {\n\t\tSome(if self.success() {\n\t\t\t100.0\n\t\t} else if self.failed() {\n\t\t\t0.0\n\t\t} else if self.total_bytes != 0 {\n\t\t\t99.99f32.min(self.processed_bytes as f32 / self.total_bytes as f32 * 100.0)\n\t\t} else {\n\t\t\t99.99\n\t\t})\n\t}\n}\n\n// --- Cut\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]\npub struct FileProgCut {\n\tpub total_files:     u32,\n\tpub success_files:   u32,\n\tpub failed_files:    u32,\n\tpub total_bytes:     u64,\n\tpub processed_bytes: u64,\n\tpub collected:       Option<bool>,\n\tpub cleaned:         Option<bool>,\n}\n\nimpl From<FileProgCut> for TaskSummary {\n\tfn from(value: FileProgCut) -> Self {\n\t\tSelf {\n\t\t\ttotal:   value.total_files,\n\t\t\tsuccess: value.success_files,\n\t\t\tfailed:  value.failed_files,\n\t\t\tpercent: value.percent().map(Into::into),\n\t\t}\n\t}\n}\n\nimpl FileProgCut {\n\tpub fn cooked(self) -> bool {\n\t\tself.collected == Some(true) && self.success_files == self.total_files\n\t}\n\n\tpub fn running(self) -> bool {\n\t\tself.collected.is_none()\n\t\t\t|| self.success_files + self.failed_files != self.total_files\n\t\t\t|| (self.cleaned.is_none() && self.cooked())\n\t}\n\n\tpub fn success(self) -> bool { self.cleaned == Some(true) && self.cooked() }\n\n\tpub fn failed(self) -> bool { self.cleaned == Some(false) || self.collected == Some(false) }\n\n\tpub fn cleaned(self) -> Option<bool> { self.cleaned }\n\n\tpub fn percent(self) -> Option<f32> {\n\t\tSome(if self.success() {\n\t\t\t100.0\n\t\t} else if self.failed() {\n\t\t\t0.0\n\t\t} else if self.total_bytes != 0 {\n\t\t\t99.99f32.min(self.processed_bytes as f32 / self.total_bytes as f32 * 100.0)\n\t\t} else {\n\t\t\t99.99\n\t\t})\n\t}\n}\n\n// --- Link\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]\npub struct FileProgLink {\n\tpub state: Option<bool>,\n}\n\nimpl From<FileProgLink> for TaskSummary {\n\tfn from(value: FileProgLink) -> Self {\n\t\tSelf {\n\t\t\ttotal:   1,\n\t\t\tsuccess: (value.state == Some(true)) as u32,\n\t\t\tfailed:  (value.state == Some(false)) as u32,\n\t\t\tpercent: value.percent().map(Into::into),\n\t\t}\n\t}\n}\n\nimpl FileProgLink {\n\tpub fn cooked(self) -> bool { self.state == Some(true) }\n\n\tpub fn running(self) -> bool { self.state.is_none() }\n\n\tpub fn success(self) -> bool { self.cooked() }\n\n\tpub fn failed(self) -> bool { self.state == Some(false) }\n\n\tpub fn cleaned(self) -> Option<bool> { None }\n\n\tpub fn percent(self) -> Option<f32> { None }\n}\n\n// --- Hardlink\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]\npub struct FileProgHardlink {\n\tpub total:     u32,\n\tpub success:   u32,\n\tpub failed:    u32,\n\tpub collected: Option<bool>,\n}\n\nimpl From<FileProgHardlink> for TaskSummary {\n\tfn from(value: FileProgHardlink) -> Self {\n\t\tSelf {\n\t\t\ttotal:   value.total,\n\t\t\tsuccess: value.success,\n\t\t\tfailed:  value.failed,\n\t\t\tpercent: value.percent().map(Into::into),\n\t\t}\n\t}\n}\n\nimpl FileProgHardlink {\n\tpub fn cooked(self) -> bool { self.collected == Some(true) && self.success == self.total }\n\n\tpub fn running(self) -> bool {\n\t\tself.collected.is_none() || self.success + self.failed != self.total\n\t}\n\n\tpub fn success(self) -> bool { self.cooked() }\n\n\tpub fn failed(self) -> bool { self.collected == Some(false) }\n\n\tpub fn cleaned(self) -> Option<bool> { None }\n\n\tpub fn percent(self) -> Option<f32> { None }\n}\n\n// --- Delete\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]\npub struct FileProgDelete {\n\tpub total_files:     u32,\n\tpub success_files:   u32,\n\tpub failed_files:    u32,\n\tpub total_bytes:     u64,\n\tpub processed_bytes: u64,\n\tpub collected:       Option<bool>,\n\tpub cleaned:         Option<bool>,\n}\n\nimpl From<FileProgDelete> for TaskSummary {\n\tfn from(value: FileProgDelete) -> Self {\n\t\tSelf {\n\t\t\ttotal:   value.total_files,\n\t\t\tsuccess: value.success_files,\n\t\t\tfailed:  value.failed_files,\n\t\t\tpercent: value.percent().map(Into::into),\n\t\t}\n\t}\n}\n\nimpl FileProgDelete {\n\tpub fn cooked(self) -> bool {\n\t\tself.collected == Some(true) && self.success_files == self.total_files\n\t}\n\n\tpub fn running(self) -> bool {\n\t\tself.collected.is_none()\n\t\t\t|| self.success_files + self.failed_files != self.total_files\n\t\t\t|| (self.cleaned.is_none() && self.cooked())\n\t}\n\n\tpub fn success(self) -> bool { self.cleaned == Some(true) && self.cooked() }\n\n\tpub fn failed(self) -> bool { self.cleaned == Some(false) || self.collected == Some(false) }\n\n\tpub fn cleaned(self) -> Option<bool> { self.cleaned }\n\n\tpub fn percent(self) -> Option<f32> {\n\t\tSome(if self.success() {\n\t\t\t100.0\n\t\t} else if self.failed() {\n\t\t\t0.0\n\t\t} else if self.total_bytes != 0 {\n\t\t\t99.99f32.min(self.processed_bytes as f32 / self.total_bytes as f32 * 100.0)\n\t\t} else {\n\t\t\t99.99\n\t\t})\n\t}\n}\n\n// --- Trash\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]\npub struct FileProgTrash {\n\tpub state:   Option<bool>,\n\tpub cleaned: Option<bool>,\n}\n\nimpl From<FileProgTrash> for TaskSummary {\n\tfn from(value: FileProgTrash) -> Self {\n\t\tSelf {\n\t\t\ttotal:   1,\n\t\t\tsuccess: (value.state == Some(true)) as u32,\n\t\t\tfailed:  (value.state == Some(false)) as u32,\n\t\t\tpercent: value.percent().map(Into::into),\n\t\t}\n\t}\n}\n\nimpl FileProgTrash {\n\tpub fn cooked(self) -> bool { self.state == Some(true) }\n\n\tpub fn running(self) -> bool { self.state.is_none() || (self.cleaned.is_none() && self.cooked()) }\n\n\tpub fn success(self) -> bool { self.cleaned == Some(true) && self.cooked() }\n\n\tpub fn failed(self) -> bool { self.cleaned == Some(false) || self.state == Some(false) }\n\n\tpub fn cleaned(self) -> Option<bool> { self.cleaned }\n\n\tpub fn percent(self) -> Option<f32> { None }\n}\n\n// --- Download\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]\npub struct FileProgDownload {\n\tpub total_files:     u32,\n\tpub success_files:   u32,\n\tpub failed_files:    u32,\n\tpub total_bytes:     u64,\n\tpub processed_bytes: u64,\n\tpub collected:       Option<bool>,\n\tpub cleaned:         Option<bool>,\n}\n\nimpl From<FileProgDownload> for TaskSummary {\n\tfn from(value: FileProgDownload) -> Self {\n\t\tSelf {\n\t\t\ttotal:   value.total_files,\n\t\t\tsuccess: value.success_files,\n\t\t\tfailed:  value.failed_files,\n\t\t\tpercent: value.percent().map(Into::into),\n\t\t}\n\t}\n}\n\nimpl FileProgDownload {\n\tpub fn cooked(self) -> bool {\n\t\tself.collected == Some(true) && self.success_files == self.total_files\n\t}\n\n\tpub fn running(self) -> bool {\n\t\tself.collected.is_none()\n\t\t\t|| self.success_files + self.failed_files != self.total_files\n\t\t\t|| (self.cleaned.is_none() && self.cooked())\n\t}\n\n\tpub fn success(self) -> bool { self.cleaned == Some(true) && self.cooked() }\n\n\tpub fn failed(self) -> bool { self.cleaned == Some(false) || self.collected == Some(false) }\n\n\tpub fn cleaned(self) -> Option<bool> { self.cleaned }\n\n\tpub fn percent(self) -> Option<f32> {\n\t\tSome(if self.success() {\n\t\t\t100.0\n\t\t} else if self.failed() {\n\t\t\t0.0\n\t\t} else if self.total_bytes != 0 {\n\t\t\t99.99f32.min(self.processed_bytes as f32 / self.total_bytes as f32 * 100.0)\n\t\t} else {\n\t\t\t99.99\n\t\t})\n\t}\n}\n\n// --- Upload\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]\npub struct FileProgUpload {\n\tpub total_files:     u32,\n\tpub success_files:   u32,\n\tpub failed_files:    u32,\n\tpub total_bytes:     u64,\n\tpub processed_bytes: u64,\n\tpub collected:       Option<bool>,\n\tpub cleaned:         Option<bool>,\n}\n\nimpl From<FileProgUpload> for TaskSummary {\n\tfn from(value: FileProgUpload) -> Self {\n\t\tSelf {\n\t\t\ttotal:   value.total_files,\n\t\t\tsuccess: value.success_files,\n\t\t\tfailed:  value.failed_files,\n\t\t\tpercent: value.percent().map(Into::into),\n\t\t}\n\t}\n}\n\nimpl FileProgUpload {\n\tpub fn cooked(self) -> bool {\n\t\tself.collected == Some(true) && self.success_files == self.total_files\n\t}\n\n\tpub fn running(self) -> bool {\n\t\tself.collected.is_none()\n\t\t\t|| self.success_files + self.failed_files != self.total_files\n\t\t\t|| (self.cleaned.is_none() && self.cooked())\n\t}\n\n\tpub fn success(self) -> bool { self.cleaned == Some(true) && self.cooked() }\n\n\tpub fn failed(self) -> bool { self.cleaned == Some(false) || self.collected == Some(false) }\n\n\tpub fn cleaned(self) -> Option<bool> { self.cleaned }\n\n\tpub fn percent(self) -> Option<f32> {\n\t\tSome(if self.success() {\n\t\t\t100.0\n\t\t} else if self.failed() {\n\t\t\t0.0\n\t\t} else if self.total_bytes != 0 {\n\t\t\t99.99f32.min(self.processed_bytes as f32 / self.total_bytes as f32 * 100.0)\n\t\t} else {\n\t\t\t99.99\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/src/file/transaction.rs",
    "content": "use std::{hash::{BuildHasher, Hash, Hasher}, io};\n\nuse yazi_macro::ok_or_not_found;\nuse yazi_shared::{timestamp_us, url::{AsUrl, Url, UrlBuf}};\nuse yazi_vfs::{provider, unique_file};\n\npub(super) struct Transaction;\n\nimpl Transaction {\n\tpub(super) async fn tmp<U>(url: U) -> io::Result<UrlBuf>\n\twhere\n\t\tU: AsUrl,\n\t{\n\t\tSelf::tmp_impl(url.as_url()).await\n\t}\n\n\tasync fn tmp_impl(url: Url<'_>) -> io::Result<UrlBuf> {\n\t\tlet Some(parent) = url.parent() else {\n\t\t\tErr(io::Error::new(io::ErrorKind::InvalidInput, \"Url has no parent\"))?\n\t\t};\n\n\t\tlet mut h = foldhash::fast::FixedState::default().build_hasher();\n\t\turl.hash(&mut h);\n\t\ttimestamp_us().hash(&mut h);\n\n\t\tunique_file(parent.try_join(format!(\".{:x}.%tmp\", h.finish()))?, false).await\n\t}\n\n\tpub(super) async fn unlink<U>(url: U) -> io::Result<()>\n\twhere\n\t\tU: AsUrl,\n\t{\n\t\tlet url = url.as_url();\n\t\tif ok_or_not_found!(provider::symlink_metadata(url).await, return Ok(())).is_link() {\n\t\t\tprovider::rename(Self::tmp(url).await?, url).await?;\n\t\t}\n\n\t\tOk(())\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/src/file/traverse.rs",
    "content": "use std::{collections::VecDeque, fmt::Debug};\n\nuse yazi_fs::{FsUrl, cha::Cha, path::skip_url, provider::{DirReader, FileHolder}};\nuse yazi_shared::{strand::StrandLike, url::{AsUrl, Url, UrlBuf, UrlLike}};\nuse yazi_vfs::provider::{self};\n\nuse crate::{ctx, file::{FileInCopy, FileInCut, FileInDelete, FileInDownload, FileInHardlink, FileInUpload}};\n\npub(super) trait Traverse {\n\tfn cha(&mut self) -> &mut Option<Cha>;\n\n\tfn follow(&self) -> bool;\n\n\tfn from(&self) -> Url<'_>;\n\n\tasync fn init(&mut self) -> anyhow::Result<Cha> {\n\t\tif self.cha().is_none() {\n\t\t\t*self.cha() = Some(super::File::cha(self.from(), self.follow(), None).await?)\n\t\t}\n\t\tOk(self.cha().unwrap())\n\t}\n\n\tfn spawn(&self, from: UrlBuf, to: Option<UrlBuf>, cha: Cha) -> Self;\n\n\tfn to(&self) -> Option<Url<'_>>;\n}\n\nimpl Traverse for FileInCopy {\n\tfn cha(&mut self) -> &mut Option<Cha> { &mut self.cha }\n\n\tfn follow(&self) -> bool { self.follow }\n\n\tfn from(&self) -> Url<'_> { self.from.as_url() }\n\n\tfn spawn(&self, from: UrlBuf, to: Option<UrlBuf>, cha: Cha) -> Self {\n\t\tSelf {\n\t\t\tid: self.id,\n\t\t\tfrom,\n\t\t\tto: to.unwrap(),\n\t\t\tforce: self.force,\n\t\t\tcha: Some(cha),\n\t\t\tfollow: self.follow,\n\t\t\tretry: self.retry,\n\t\t\tdone: self.done.clone(),\n\t\t}\n\t}\n\n\tfn to(&self) -> Option<Url<'_>> { Some(self.to.as_url()) }\n}\n\nimpl Traverse for FileInCut {\n\tfn cha(&mut self) -> &mut Option<Cha> { &mut self.cha }\n\n\tfn follow(&self) -> bool { self.follow }\n\n\tfn from(&self) -> Url<'_> { self.from.as_url() }\n\n\tfn spawn(&self, from: UrlBuf, to: Option<UrlBuf>, cha: Cha) -> Self {\n\t\tSelf {\n\t\t\tid: self.id,\n\t\t\tfrom,\n\t\t\tto: to.unwrap(),\n\t\t\tforce: self.force,\n\t\t\tcha: Some(cha),\n\t\t\tfollow: self.follow,\n\t\t\tretry: self.retry,\n\t\t\tdone: self.done.clone(),\n\t\t\tdrop: self.drop.clone(),\n\t\t}\n\t}\n\n\tfn to(&self) -> Option<Url<'_>> { Some(self.to.as_url()) }\n}\n\nimpl Traverse for FileInHardlink {\n\tfn cha(&mut self) -> &mut Option<Cha> { &mut self.cha }\n\n\tfn follow(&self) -> bool { self.follow }\n\n\tfn from(&self) -> Url<'_> { self.from.as_url() }\n\n\tfn spawn(&self, from: UrlBuf, to: Option<UrlBuf>, cha: Cha) -> Self {\n\t\tSelf {\n\t\t\tid: self.id,\n\t\t\tfrom,\n\t\t\tto: to.unwrap(),\n\t\t\tforce: self.force,\n\t\t\tcha: Some(cha),\n\t\t\tfollow: self.follow,\n\t\t}\n\t}\n\n\tfn to(&self) -> Option<Url<'_>> { Some(self.to.as_url()) }\n}\n\nimpl Traverse for FileInDelete {\n\tfn cha(&mut self) -> &mut Option<Cha> { &mut self.cha }\n\n\tfn follow(&self) -> bool { false }\n\n\tfn from(&self) -> Url<'_> { self.target.as_url() }\n\n\tfn spawn(&self, from: UrlBuf, _to: Option<UrlBuf>, cha: Cha) -> Self {\n\t\tSelf { id: self.id, target: from, cha: Some(cha) }\n\t}\n\n\tfn to(&self) -> Option<Url<'_>> { None }\n}\n\nimpl Traverse for FileInDownload {\n\tfn cha(&mut self) -> &mut Option<Cha> { &mut self.cha }\n\n\tfn follow(&self) -> bool { true }\n\n\tfn from(&self) -> Url<'_> { self.target.as_url() }\n\n\tfn spawn(&self, from: UrlBuf, _to: Option<UrlBuf>, cha: Cha) -> Self {\n\t\tSelf {\n\t\t\tid:     self.id,\n\t\t\ttarget: from,\n\t\t\tcha:    Some(cha),\n\t\t\tretry:  self.retry,\n\t\t\tdone:   self.done.clone(),\n\t\t}\n\t}\n\n\tfn to(&self) -> Option<Url<'_>> { None }\n}\n\nimpl Traverse for FileInUpload {\n\tfn cha(&mut self) -> &mut Option<Cha> { &mut self.cha }\n\n\tfn follow(&self) -> bool { true }\n\n\tfn from(&self) -> Url<'_> { self.target.as_url() }\n\n\tasync fn init(&mut self) -> anyhow::Result<Cha> {\n\t\tif self.cha.is_none() {\n\t\t\tself.cha = Some(super::File::cha(self.from(), self.follow(), None).await?)\n\t\t}\n\t\tif self.cache.is_none() {\n\t\t\tself.cache = self.target.cache();\n\t\t}\n\t\tOk(self.cha.unwrap())\n\t}\n\n\tfn spawn(&self, from: UrlBuf, _to: Option<UrlBuf>, cha: Cha) -> Self {\n\t\tSelf {\n\t\t\tid:     self.id,\n\t\t\tcha:    Some(cha),\n\t\t\tcache:  from.cache(),\n\t\t\ttarget: from,\n\t\t\tdone:   self.done.clone(),\n\t\t}\n\t}\n\n\tfn to(&self) -> Option<Url<'_>> { None }\n}\n\n#[allow(private_bounds)]\npub(super) async fn traverse<O, I, D, FC, FR, E>(\n\tmut task: I,\n\ton_dir: D,\n\tmut on_file: FC,\n\ton_error: E,\n) -> Result<(), O>\nwhere\n\tO: Debug + From<anyhow::Error>,\n\tI: Debug + Traverse,\n\tD: AsyncFn(Url) -> Result<(), O>,\n\tFC: FnMut(I, Cha) -> FR,\n\tFR: Future<Output = Result<(), O>>,\n\tE: Fn(String),\n{\n\tlet cha = ctx!(task, task.init().await)?;\n\tif !cha.is_dir() {\n\t\treturn on_file(task, cha).await;\n\t}\n\n\tlet root = task.to();\n\tlet skip = task.from().components().count();\n\tlet mut dirs = VecDeque::from([task.from().to_owned()]);\n\n\tmacro_rules! err {\n\t\t($result:expr, $($args:tt)*) => {\n\t\t\tmatch $result {\n\t\t\t\tOk(v) => v,\n\t\t\t\tErr(e) => {\n\t\t\t\t\ton_error(format!(\"{}: {e:?}\", format_args!($($args)*)));\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\twhile let Some(src) = dirs.pop_front() {\n\t\tlet mut it = err!(provider::read_dir(&src).await, \"Cannot read directory {src:?}\");\n\n\t\tlet dest = if let Some(root) = root {\n\t\t\tlet s = skip_url(&src, skip);\n\t\t\terr!(root.try_join(&s), \"Cannot join {root:?} with {}\", s.display())\n\t\t} else {\n\t\t\tsrc\n\t\t};\n\n\t\t() = err!(on_dir(dest.as_url()).await, \"Cannot process directory {dest:?}\");\n\n\t\twhile let Ok(Some(entry)) = it.next().await {\n\t\t\tlet from = entry.url();\n\t\t\tlet cha = err!(\n\t\t\t\tsuper::File::cha(&from, task.follow(), Some(entry)).await,\n\t\t\t\t\"Cannot get metadata for {from:?}\"\n\t\t\t);\n\n\t\t\tif cha.is_dir() {\n\t\t\t\tdirs.push_back(from);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet to = if root.is_some() {\n\t\t\t\tlet name = from.name().unwrap();\n\t\t\t\tSome(err!(dest.try_join(name), \"Cannot join {dest:?} with {}\", name.display()))\n\t\t\t} else {\n\t\t\t\tNone\n\t\t\t};\n\n\t\t\terr!(on_file(task.spawn(from, to, cha), cha).await, \"Cannot process file\");\n\t\t}\n\t}\n\n\tOk(())\n}\n"
  },
  {
    "path": "yazi-scheduler/src/hook/hook.rs",
    "content": "use std::sync::Arc;\n\nuse parking_lot::Mutex;\nuse tokio::sync::mpsc;\nuse yazi_dds::Pump;\nuse yazi_fs::ok_or_not_found;\nuse yazi_proxy::TasksProxy;\nuse yazi_vfs::provider;\n\nuse crate::{Ongoing, TaskOp, TaskOps, file::{FileOutCopy, FileOutCut, FileOutDelete, FileOutDownload, FileOutTrash, FileOutUpload}, hook::{HookIn, HookInDelete, HookInDownload, HookInOutCopy, HookInOutCut, HookInTrash, HookInUpload}};\n\npub(crate) struct Hook {\n\tops:     TaskOps,\n\tongoing: Arc<Mutex<Ongoing>>,\n\ttx:      async_priority_channel::Sender<HookIn, u8>,\n}\n\nimpl Hook {\n\tpub(crate) fn new(\n\t\tops: &mpsc::UnboundedSender<TaskOp>,\n\t\tongoing: &Arc<Mutex<Ongoing>>,\n\t\ttx: async_priority_channel::Sender<HookIn, u8>,\n\t) -> Self {\n\t\tSelf { ops: ops.into(), ongoing: ongoing.clone(), tx }\n\t}\n\n\t// --- File\n\tpub(crate) async fn cut(&self, task: HookInOutCut) {\n\t\tif !self.ongoing.lock().intact(task.id) {\n\t\t\treturn self.ops.out(task.id, FileOutCut::Clean(Ok(())));\n\t\t}\n\n\t\tlet result = ok_or_not_found(provider::remove_dir_clean(&task.from).await);\n\t\tTasksProxy::update_succeed([&task.to, &task.from]);\n\t\tPump::push_move(task.from, task.to);\n\n\t\tself.ops.out(task.id, FileOutCut::Clean(result));\n\t}\n\n\tpub(crate) async fn copy(&self, task: HookInOutCopy) {\n\t\tif self.ongoing.lock().intact(task.id) {\n\t\t\tTasksProxy::update_succeed([&task.to]);\n\t\t\tPump::push_duplicate(task.from, task.to);\n\t\t}\n\n\t\tself.ops.out(task.id, FileOutCopy::Clean);\n\t}\n\n\tpub(crate) async fn delete(&self, task: HookInDelete) {\n\t\tif !self.ongoing.lock().intact(task.id) {\n\t\t\treturn self.ops.out(task.id, FileOutDelete::Clean(Ok(())));\n\t\t}\n\n\t\tlet result = ok_or_not_found(provider::remove_dir_all(&task.target).await);\n\t\tTasksProxy::update_succeed([&task.target]);\n\t\tPump::push_delete(task.target);\n\n\t\tself.ops.out(task.id, FileOutDelete::Clean(result));\n\t}\n\n\tpub(crate) async fn trash(&self, task: HookInTrash) {\n\t\tlet intact = self.ongoing.lock().intact(task.id);\n\t\tif intact {\n\t\t\tTasksProxy::update_succeed([&task.target]);\n\t\t\tPump::push_trash(task.target);\n\t\t}\n\t\tself.ops.out(task.id, FileOutTrash::Clean);\n\t}\n\n\tpub(crate) async fn download(&self, task: HookInDownload) {\n\t\tlet intact = self.ongoing.lock().intact(task.id);\n\t\tif intact {\n\t\t\tTasksProxy::update_succeed([&task.target]);\n\t\t\tPump::push_download(task.target);\n\t\t}\n\t\tself.ops.out(task.id, FileOutDownload::Clean);\n\t}\n\n\tpub(crate) async fn upload(&self, task: HookInUpload) {\n\t\tlet intact = self.ongoing.lock().intact(task.id);\n\t\tif intact {\n\t\t\tTasksProxy::update_succeed([task.target]);\n\t\t}\n\t\tself.ops.out(task.id, FileOutUpload::Clean);\n\t}\n}\n\nimpl Hook {\n\t#[inline]\n\tpub(crate) fn submit(&self, r#in: impl Into<HookIn>, priority: u8) {\n\t\t_ = self.tx.try_send(r#in.into(), priority);\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/src/hook/in.rs",
    "content": "use yazi_shared::{Id, url::UrlBuf};\n\nuse crate::{Task, TaskProg, file::{FileInCopy, FileInCut}};\n\n#[derive(Debug)]\npub(crate) enum HookIn {\n\tCopy(HookInOutCopy),\n\tCut(HookInOutCut),\n\tDelete(HookInDelete),\n\tTrash(HookInTrash),\n\tDownload(HookInDownload),\n\tUpload(HookInUpload),\n}\n\nimpl_from_in!(\n\tCopy(HookInOutCopy),\n\tCut(HookInOutCut),\n\tDelete(HookInDelete),\n\tTrash(HookInTrash),\n\tDownload(HookInDownload),\n\tUpload(HookInUpload),\n);\n\nimpl HookIn {\n\tpub(crate) fn id(&self) -> Id {\n\t\tmatch self {\n\t\t\tSelf::Copy(r#in) => r#in.id,\n\t\t\tSelf::Cut(r#in) => r#in.id,\n\t\t\tSelf::Delete(r#in) => r#in.id,\n\t\t\tSelf::Trash(r#in) => r#in.id,\n\t\t\tSelf::Download(r#in) => r#in.id,\n\t\t\tSelf::Upload(r#in) => r#in.id,\n\t\t}\n\t}\n}\n\n// --- Copy\n#[derive(Debug)]\npub(crate) struct HookInOutCopy {\n\tpub(crate) id:   Id,\n\tpub(crate) from: UrlBuf,\n\tpub(crate) to:   UrlBuf,\n}\n\nimpl From<&FileInCopy> for HookInOutCopy {\n\tfn from(value: &FileInCopy) -> Self {\n\t\tSelf { id: value.id, from: value.from.clone(), to: value.to.clone() }\n\t}\n}\n\nimpl HookInOutCopy {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tif let TaskProg::FileCopy(_) = &task.prog {\n\t\t\ttask.hook = Some(self.into());\n\t\t}\n\t}\n}\n\n// --- Cut\n#[derive(Debug)]\npub(crate) struct HookInOutCut {\n\tpub(crate) id:   Id,\n\tpub(crate) from: UrlBuf,\n\tpub(crate) to:   UrlBuf,\n}\n\nimpl From<&FileInCut> for HookInOutCut {\n\tfn from(value: &FileInCut) -> Self {\n\t\tSelf { id: value.id, from: value.from.clone(), to: value.to.clone() }\n\t}\n}\n\nimpl HookInOutCut {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tif let TaskProg::FileCut(_) = &task.prog {\n\t\t\ttask.hook = Some(self.into());\n\t\t}\n\t}\n}\n\n// --- Delete\n#[derive(Debug)]\npub(crate) struct HookInDelete {\n\tpub(crate) id:     Id,\n\tpub(crate) target: UrlBuf,\n}\n\n// --- Trash\n#[derive(Debug)]\npub(crate) struct HookInTrash {\n\tpub(crate) id:     Id,\n\tpub(crate) target: UrlBuf,\n}\n\n// --- Download\n#[derive(Debug)]\npub(crate) struct HookInDownload {\n\tpub(crate) id:     Id,\n\tpub(crate) target: UrlBuf,\n}\n\n// --- Upload\n#[derive(Debug)]\npub(crate) struct HookInUpload {\n\tpub(crate) id:     Id,\n\tpub(crate) target: UrlBuf,\n}\n"
  },
  {
    "path": "yazi-scheduler/src/hook/macros.rs",
    "content": "macro_rules! impl_from_in {\n\t($($variant:ident($type:ty)),* $(,)?) => {\n\t\t$(\n\t\t\timpl From<$type> for $crate::hook::HookIn {\n\t\t\t\tfn from(value: $type) -> Self { Self::$variant(value) }\n\t\t\t}\n\t\t)*\n\t};\n}\n"
  },
  {
    "path": "yazi-scheduler/src/hook/mod.rs",
    "content": "#[macro_use]\nmod macros;\n\nyazi_macro::mod_flat!(hook r#in);\n"
  },
  {
    "path": "yazi-scheduler/src/lib.rs",
    "content": "mod macros;\n\nyazi_macro::mod_pub!(fetch file hook plugin preload process size);\n\nyazi_macro::mod_flat!(ongoing op out progress runner scheduler snap task);\n\nconst LOW: u8 = yazi_config::Priority::Low as u8;\nconst NORMAL: u8 = yazi_config::Priority::Normal as u8;\nconst HIGH: u8 = yazi_config::Priority::High as u8;\n"
  },
  {
    "path": "yazi-scheduler/src/macros.rs",
    "content": "#[macro_export]\nmacro_rules! ctx {\n\t($task:ident, $result:expr) => {{\n\t\tuse anyhow::Context;\n\t\t$result.with_context(|| format!(\"Failed to work on {:?}\", $task))\n\t}};\n\t($task:ident, $result:expr, $($args:tt)*) => {{\n\t\tuse anyhow::Context;\n\t\t$result.with_context(|| format!(\"Failed to work on {:?}: {}\", $task, format_args!($($args)*)))\n\t}};\n}\n\n#[macro_export]\nmacro_rules! ok_or_not_found {\n\t($task:ident, $result:expr, $not_found:expr) => {\n\t\tmatch $result {\n\t\t\tOk(v) => v,\n\t\t\tErr(e) if e.kind() == std::io::ErrorKind::NotFound => $not_found,\n\t\t\tErr(e) => $crate::ctx!($task, Err(e))?,\n\t\t}\n\t};\n\t($task:ident, $result:expr) => {\n\t\tok_or_not_found!($task, $result, Default::default())\n\t};\n}\n\n#[macro_export]\nmacro_rules! progress_or_break {\n\t($rx:ident, $done:expr) => {\n\t\ttokio::select! {\n\t\t\tr = $rx.recv() => {\n\t\t\t\tmatch r {\n\t\t\t\t\tSome(prog) => prog,\n\t\t\t\t\tNone => break,\n\t\t\t\t}\n\t\t\t},\n\t\t\tfalse = $done.future() => break,\n\t\t}\n\t};\n}\n\n#[macro_export]\nmacro_rules! impl_from_out {\n\t($($variant:ident($type:ty)),* $(,)?) => {\n\t\t$(\n\t\t\timpl From<$type> for $crate::TaskOut {\n\t\t\t\tfn from(value: $type) -> Self { Self::$variant(value) }\n\t\t\t}\n\t\t)*\n\t};\n}\n\n#[macro_export]\nmacro_rules! impl_from_prog {\n\t($($variant:ident($type:ty)),* $(,)?) => {\n\t\t$(\n\t\t\timpl From<$type> for $crate::TaskProg {\n\t\t\t\tfn from(value: $type) -> Self { Self::$variant(value) }\n\t\t\t}\n\t\t)*\n\t};\n}\n"
  },
  {
    "path": "yazi-scheduler/src/ongoing.rs",
    "content": "use hashbrown::{HashMap, hash_map::Entry};\nuse ordered_float::OrderedFloat;\nuse yazi_config::YAZI;\nuse yazi_parser::app::TaskSummary;\nuse yazi_shared::{CompletionToken, Id, Ids};\n\nuse super::Task;\nuse crate::{TaskProg, hook::HookIn};\n\n#[derive(Default)]\npub struct Ongoing {\n\tinner: HashMap<Id, Task>,\n}\n\nimpl Ongoing {\n\tpub(super) fn add<T>(&mut self, name: String) -> &mut Task\n\twhere\n\t\tT: Into<TaskProg> + Default,\n\t{\n\t\tstatic IDS: Ids = Ids::new();\n\n\t\tlet id = IDS.next();\n\t\tself.inner.entry(id).insert(Task::new::<T>(id, name)).into_mut()\n\t}\n\n\tpub(super) fn cancel(&mut self, id: Id) -> Option<HookIn> {\n\t\tmatch self.inner.entry(id) {\n\t\t\tEntry::Occupied(mut oe) => {\n\t\t\t\tlet task = oe.get_mut();\n\t\t\t\ttask.done.complete(false);\n\n\t\t\t\tif let Some(hook) = task.hook.take() {\n\t\t\t\t\treturn Some(hook);\n\t\t\t\t}\n\n\t\t\t\toe.remove();\n\t\t\t}\n\t\t\tEntry::Vacant(_) => {}\n\t\t}\n\t\tNone\n\t}\n\n\tpub(super) fn fulfill(&mut self, id: Id) -> Option<Task> {\n\t\tlet task = self.inner.remove(&id)?;\n\t\ttask.done.complete(true);\n\t\tSome(task)\n\t}\n\n\t#[inline]\n\tpub fn get_mut(&mut self, id: Id) -> Option<&mut Task> { self.inner.get_mut(&id) }\n\n\tpub fn get_id(&self, idx: usize) -> Option<Id> { self.values().nth(idx).map(|t| t.id) }\n\n\t#[inline]\n\tpub fn get_token(&self, id: Id) -> Option<CompletionToken> {\n\t\tself.inner.get(&id).map(|t| t.done.clone())\n\t}\n\n\tpub fn len(&self) -> usize {\n\t\tif YAZI.tasks.suppress_preload {\n\t\t\tself.inner.values().filter(|&t| t.prog.is_user()).count()\n\t\t} else {\n\t\t\tself.inner.len()\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn exists(&self, id: Id) -> bool { self.inner.contains_key(&id) }\n\n\t#[inline]\n\tpub fn intact(&self, id: Id) -> bool {\n\t\tself.inner.get(&id).is_some_and(|t| t.done.completed() != Some(false))\n\t}\n\n\tpub fn values(&self) -> Box<dyn Iterator<Item = &Task> + '_> {\n\t\tif YAZI.tasks.suppress_preload {\n\t\t\tBox::new(self.inner.values().filter(|&t| t.prog.is_user()))\n\t\t} else {\n\t\t\tBox::new(self.inner.values())\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn is_empty(&self) -> bool { self.len() == 0 }\n\n\tpub fn summary(&self) -> TaskSummary {\n\t\tlet mut summary = TaskSummary::default();\n\t\tlet mut percent_sum = 0.0f64;\n\t\tlet mut percent_count = 0;\n\n\t\tfor task in self.values() {\n\t\t\tlet s: TaskSummary = task.prog.into();\n\t\t\tif s.total == 0 && !task.prog.failed() {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tsummary.total += 1;\n\t\t\tif let Some(p) = s.percent {\n\t\t\t\tpercent_sum += p.0 as f64;\n\t\t\t\tpercent_count += 1;\n\t\t\t}\n\n\t\t\tif task.prog.running() {\n\t\t\t\tcontinue;\n\t\t\t} else if task.prog.success() {\n\t\t\t\tsummary.success += 1;\n\t\t\t} else {\n\t\t\t\tsummary.failed += 1;\n\t\t\t}\n\t\t}\n\n\t\tsummary.percent = if percent_count == 0 {\n\t\t\tNone\n\t\t} else {\n\t\t\tSome(OrderedFloat((percent_sum / percent_count as f64) as f32))\n\t\t};\n\t\tsummary\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/src/op.rs",
    "content": "use tokio::sync::mpsc;\nuse yazi_shared::Id;\n\nuse crate::TaskOut;\n\n#[derive(Debug)]\npub(crate) struct TaskOp {\n\tpub(crate) id:  Id,\n\tpub(crate) out: TaskOut,\n}\n\n// --- Ops\n#[derive(Clone)]\npub struct TaskOps(pub(super) mpsc::UnboundedSender<TaskOp>);\n\nimpl From<&mpsc::UnboundedSender<TaskOp>> for TaskOps {\n\tfn from(tx: &mpsc::UnboundedSender<TaskOp>) -> Self { Self(tx.clone()) }\n}\n\nimpl TaskOps {\n\t#[inline]\n\tpub(crate) fn out(&self, id: Id, out: impl Into<TaskOut>) {\n\t\t_ = self.0.send(TaskOp { id, out: out.into() });\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/src/out.rs",
    "content": "use crate::{Task, fetch::FetchOutFetch, file::{FileOutCopy, FileOutCopyDo, FileOutCut, FileOutCutDo, FileOutDelete, FileOutDeleteDo, FileOutDownload, FileOutDownloadDo, FileOutHardlink, FileOutHardlinkDo, FileOutLink, FileOutTrash, FileOutUpload, FileOutUploadDo}, hook::{HookInOutCopy, HookInOutCut}, impl_from_out, plugin::PluginOutEntry, preload::PreloadOut, process::{ProcessOutBg, ProcessOutBlock, ProcessOutOrphan}, size::SizeOut};\n\n#[derive(Debug)]\npub(super) enum TaskOut {\n\t// File\n\tFileCopy(FileOutCopy),\n\tFileCopyDo(FileOutCopyDo),\n\tFileCut(FileOutCut),\n\tFileCutDo(FileOutCutDo),\n\tFileLink(FileOutLink),\n\tFileHardlink(FileOutHardlink),\n\tFileHardlinkDo(FileOutHardlinkDo),\n\tFileDelete(FileOutDelete),\n\tFileDeleteDo(FileOutDeleteDo),\n\tFileTrash(FileOutTrash),\n\tFileDownload(FileOutDownload),\n\tFileDownloadDo(FileOutDownloadDo),\n\tFileUpload(FileOutUpload),\n\tFileUploadDo(FileOutUploadDo),\n\t// Plugin\n\tPluginEntry(PluginOutEntry),\n\t// Fetch\n\tFetch(FetchOutFetch),\n\t// Preload\n\tPreload(PreloadOut),\n\t// Size\n\tSize(SizeOut),\n\t// Process\n\tProcessBlock(ProcessOutBlock),\n\tProcessOrphan(ProcessOutOrphan),\n\tProcessBg(ProcessOutBg),\n\t// Hook\n\tHookCopy(HookInOutCopy),\n\tHookCut(HookInOutCut),\n}\n\nimpl_from_out! {\n\t// File\n\tFileCopy(FileOutCopy), FileCopyDo(FileOutCopyDo), FileCut(FileOutCut), FileCutDo(FileOutCutDo), FileLink(FileOutLink), FileHardlink(FileOutHardlink), FileHardlinkDo(FileOutHardlinkDo), FileDelete(FileOutDelete), FileDeleteDo(FileOutDeleteDo), FileTrash(FileOutTrash), FileDownload(FileOutDownload), FileDownloadDo(FileOutDownloadDo), FileUpload(FileOutUpload), FileUploadDo(FileOutUploadDo),\n\t// Plugin\n\tPluginEntry(PluginOutEntry),\n\t// Fetch\n\tFetch(FetchOutFetch),\n\t// Preload\n\tPreload(PreloadOut),\n\t// Size\n\tSize(SizeOut),\n\t// Process\n\tProcessBlock(ProcessOutBlock), ProcessOrphan(ProcessOutOrphan), ProcessBg(ProcessOutBg),\n\t// Hook\n\tHookCopy(HookInOutCopy), HookCut(HookInOutCut),\n}\n\nimpl TaskOut {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tmatch self {\n\t\t\t// File\n\t\t\tSelf::FileCopy(out) => out.reduce(task),\n\t\t\tSelf::FileCopyDo(out) => out.reduce(task),\n\t\t\tSelf::FileCut(out) => out.reduce(task),\n\t\t\tSelf::FileCutDo(out) => out.reduce(task),\n\t\t\tSelf::FileLink(out) => out.reduce(task),\n\t\t\tSelf::FileHardlink(out) => out.reduce(task),\n\t\t\tSelf::FileHardlinkDo(out) => out.reduce(task),\n\t\t\tSelf::FileDelete(out) => out.reduce(task),\n\t\t\tSelf::FileDeleteDo(out) => out.reduce(task),\n\t\t\tSelf::FileTrash(out) => out.reduce(task),\n\t\t\tSelf::FileDownload(out) => out.reduce(task),\n\t\t\tSelf::FileDownloadDo(out) => out.reduce(task),\n\t\t\tSelf::FileUpload(out) => out.reduce(task),\n\t\t\tSelf::FileUploadDo(out) => out.reduce(task),\n\t\t\t// Plugin\n\t\t\tSelf::PluginEntry(out) => out.reduce(task),\n\t\t\t// Prework\n\t\t\tSelf::Fetch(out) => out.reduce(task),\n\t\t\tSelf::Preload(out) => out.reduce(task),\n\t\t\tSelf::Size(out) => out.reduce(task),\n\t\t\t// Process\n\t\t\tSelf::ProcessBlock(out) => out.reduce(task),\n\t\t\tSelf::ProcessOrphan(out) => out.reduce(task),\n\t\t\tSelf::ProcessBg(out) => out.reduce(task),\n\t\t\t// Hook\n\t\t\tSelf::HookCopy(out) => out.reduce(task),\n\t\t\tSelf::HookCut(out) => out.reduce(task),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/src/plugin/in.rs",
    "content": "use yazi_parser::app::PluginOpt;\nuse yazi_shared::Id;\n\n#[derive(Debug)]\npub(crate) enum PluginIn {\n\tEntry(PluginInEntry),\n}\n\nimpl_from_in!(Entry(PluginInEntry));\n\nimpl PluginIn {\n\tpub(crate) fn id(&self) -> Id {\n\t\tmatch self {\n\t\t\tSelf::Entry(r#in) => r#in.id,\n\t\t}\n\t}\n}\n\n// --- Entry\n#[derive(Debug)]\npub(crate) struct PluginInEntry {\n\tpub(crate) id:  Id,\n\tpub(crate) opt: PluginOpt,\n}\n"
  },
  {
    "path": "yazi-scheduler/src/plugin/macros.rs",
    "content": "macro_rules! impl_from_in {\n\t($($variant:ident($type:ty)),* $(,)?) => {\n\t\t$(\n\t\t\timpl From<$type> for $crate::plugin::PluginIn {\n\t\t\t\tfn from(value: $type) -> Self { Self::$variant(value) }\n\t\t\t}\n\t\t)*\n\t};\n}\n"
  },
  {
    "path": "yazi-scheduler/src/plugin/mod.rs",
    "content": "#[macro_use]\nmod macros;\n\nyazi_macro::mod_flat!(out plugin progress r#in);\n"
  },
  {
    "path": "yazi-scheduler/src/plugin/out.rs",
    "content": "use crate::{Task, TaskProg};\n\n// --- Entry\n#[derive(Debug)]\npub(crate) enum PluginOutEntry {\n\tSucc,\n\tFail(String),\n}\n\nimpl From<mlua::Error> for PluginOutEntry {\n\tfn from(value: mlua::Error) -> Self { Self::Fail(value.to_string()) }\n}\n\nimpl PluginOutEntry {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tlet TaskProg::PluginEntry(prog) = &mut task.prog else { return };\n\t\tmatch self {\n\t\t\tSelf::Succ => {\n\t\t\t\tprog.state = Some(true);\n\t\t\t}\n\t\t\tSelf::Fail(reason) => {\n\t\t\t\tprog.state = Some(false);\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/src/plugin/plugin.rs",
    "content": "use anyhow::Result;\nuse tokio::sync::mpsc;\nuse yazi_plugin::isolate;\n\nuse super::PluginInEntry;\nuse crate::{TaskOp, TaskOps, plugin::{PluginIn, PluginOutEntry}};\n\npub(crate) struct Plugin {\n\tops: TaskOps,\n\ttx:  async_priority_channel::Sender<PluginIn, u8>,\n}\n\nimpl Plugin {\n\tpub(crate) fn new(\n\t\tops: &mpsc::UnboundedSender<TaskOp>,\n\t\ttx: async_priority_channel::Sender<PluginIn, u8>,\n\t) -> Self {\n\t\tSelf { ops: ops.into(), tx }\n\t}\n\n\tpub(crate) async fn entry(&self, task: PluginInEntry) -> Result<(), PluginOutEntry> {\n\t\tisolate::entry(task.opt).await?;\n\t\tOk(self.ops.out(task.id, PluginOutEntry::Succ))\n\t}\n}\n\nimpl Plugin {\n\t#[inline]\n\tpub(crate) fn submit(&self, r#in: impl Into<PluginIn>, priority: u8) {\n\t\t_ = self.tx.try_send(r#in.into(), priority);\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/src/plugin/progress.rs",
    "content": "use serde::Serialize;\nuse yazi_parser::app::TaskSummary;\n\n// --- Entry\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]\npub struct PluginProgEntry {\n\tpub state: Option<bool>,\n}\n\nimpl From<PluginProgEntry> for TaskSummary {\n\tfn from(value: PluginProgEntry) -> Self {\n\t\tSelf {\n\t\t\ttotal:   1,\n\t\t\tsuccess: (value.state == Some(true)) as u32,\n\t\t\tfailed:  (value.state == Some(false)) as u32,\n\t\t\tpercent: value.percent().map(Into::into),\n\t\t}\n\t}\n}\n\nimpl PluginProgEntry {\n\tpub fn cooked(self) -> bool { self.state == Some(true) }\n\n\tpub fn running(self) -> bool { self.state.is_none() }\n\n\tpub fn success(self) -> bool { self.cooked() }\n\n\tpub fn failed(self) -> bool { self.state == Some(false) }\n\n\tpub fn cleaned(self) -> Option<bool> { None }\n\n\tpub fn percent(self) -> Option<f32> { None }\n}\n"
  },
  {
    "path": "yazi-scheduler/src/preload/in.rs",
    "content": "use yazi_config::plugin::Preloader;\nuse yazi_shared::Id;\n\n#[derive(Clone, Debug)]\npub(crate) struct PreloadIn {\n\tpub(crate) id:     Id,\n\tpub(crate) plugin: &'static Preloader,\n\tpub(crate) target: yazi_fs::File,\n}\n\nimpl PreloadIn {\n\tpub(crate) fn id(&self) -> Id { self.id }\n}\n"
  },
  {
    "path": "yazi-scheduler/src/preload/mod.rs",
    "content": "yazi_macro::mod_flat!(out preload progress r#in);\n"
  },
  {
    "path": "yazi-scheduler/src/preload/out.rs",
    "content": "use crate::{Task, TaskProg};\n\n#[derive(Debug)]\npub(crate) enum PreloadOut {\n\tSucc,\n\tFail(String),\n}\n\nimpl From<mlua::Error> for PreloadOut {\n\tfn from(value: mlua::Error) -> Self { Self::Fail(value.to_string()) }\n}\n\nimpl PreloadOut {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tlet TaskProg::Preload(prog) = &mut task.prog else { return };\n\t\tmatch self {\n\t\t\tSelf::Succ => {\n\t\t\t\tprog.state = Some(true);\n\t\t\t}\n\t\t\tSelf::Fail(reason) => {\n\t\t\t\tprog.state = Some(false);\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/src/preload/preload.rs",
    "content": "use std::num::NonZeroUsize;\n\nuse anyhow::Result;\nuse lru::LruCache;\nuse parking_lot::Mutex;\nuse tokio::sync::mpsc;\nuse tokio_util::sync::CancellationToken;\nuse tracing::error;\nuse yazi_config::Priority;\nuse yazi_fs::FsHash64;\nuse yazi_plugin::isolate;\n\nuse crate::{HIGH, LOW, NORMAL, TaskOp, TaskOps, preload::{PreloadIn, PreloadOut}};\n\npub struct Preload {\n\tops: TaskOps,\n\ttx:  async_priority_channel::Sender<PreloadIn, u8>,\n\n\tpub loaded:  Mutex<LruCache<u64, u16>>,\n\tpub loading: Mutex<LruCache<u64, CancellationToken>>,\n}\n\nimpl Preload {\n\tpub(crate) fn new(\n\t\tops: &mpsc::UnboundedSender<TaskOp>,\n\t\ttx: async_priority_channel::Sender<PreloadIn, u8>,\n\t) -> Self {\n\t\tSelf {\n\t\t\tops: ops.into(),\n\t\t\ttx,\n\n\t\t\tloaded: Mutex::new(LruCache::new(NonZeroUsize::new(4096).unwrap())),\n\t\t\tloading: Mutex::new(LruCache::new(NonZeroUsize::new(256).unwrap())),\n\t\t}\n\t}\n\n\tpub(crate) async fn preload(&self, task: PreloadIn) -> Result<(), PreloadOut> {\n\t\tlet ct = CancellationToken::new();\n\t\tif let Some(ct) = self.loading.lock().put(task.target.url.hash_u64(), ct.clone()) {\n\t\t\tct.cancel();\n\t\t}\n\n\t\tlet hash = task.target.hash_u64();\n\t\tlet (ok, err) = isolate::preload(&task.plugin.run, task.target, ct).await?;\n\n\t\tif !ok {\n\t\t\tself.loaded.lock().get_mut(&hash).map(|x| *x &= !(1 << task.plugin.idx));\n\t\t}\n\t\tif let Some(e) = err {\n\t\t\terror!(\"Error when running preloader `{}`:\\n{e}\", task.plugin.run.name);\n\t\t}\n\n\t\tOk(self.ops.out(task.id, PreloadOut::Succ))\n\t}\n}\n\nimpl Preload {\n\tpub(crate) fn submit(&self, r#in: PreloadIn) {\n\t\tlet priority = match r#in.plugin.prio {\n\t\t\tPriority::Low => LOW,\n\t\t\tPriority::Normal => NORMAL,\n\t\t\tPriority::High => HIGH,\n\t\t};\n\n\t\t_ = self.tx.try_send(r#in, priority);\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/src/preload/progress.rs",
    "content": "use serde::Serialize;\nuse yazi_parser::app::TaskSummary;\n\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]\npub struct PreloadProg {\n\tpub state: Option<bool>,\n}\n\nimpl From<PreloadProg> for TaskSummary {\n\tfn from(value: PreloadProg) -> Self {\n\t\tSelf {\n\t\t\ttotal:   1,\n\t\t\tsuccess: (value.state == Some(true)) as u32,\n\t\t\tfailed:  (value.state == Some(false)) as u32,\n\t\t\tpercent: value.percent().map(Into::into),\n\t\t}\n\t}\n}\n\nimpl PreloadProg {\n\tpub fn cooked(self) -> bool { self.state == Some(true) }\n\n\tpub fn running(self) -> bool { self.state.is_none() }\n\n\tpub fn success(self) -> bool { self.cooked() }\n\n\tpub fn failed(self) -> bool { self.state == Some(false) }\n\n\tpub fn cleaned(self) -> Option<bool> { None }\n\n\tpub fn percent(self) -> Option<f32> { None }\n}\n"
  },
  {
    "path": "yazi-scheduler/src/process/in.rs",
    "content": "use std::ffi::OsString;\n\nuse yazi_shared::{CompletionToken, Id, url::UrlCow};\n\nuse super::ShellOpt;\n\n#[derive(Debug)]\npub(crate) enum ProcessIn {\n\tBlock(ProcessInBlock),\n\tOrphan(ProcessInOrphan),\n\tBg(ProcessInBg),\n}\n\nimpl_from_in!(Block(ProcessInBlock), Orphan(ProcessInOrphan), Bg(ProcessInBg));\n\nimpl ProcessIn {\n\tpub(crate) fn id(&self) -> Id {\n\t\tmatch self {\n\t\t\tSelf::Block(r#in) => r#in.id,\n\t\t\tSelf::Orphan(r#in) => r#in.id,\n\t\t\tSelf::Bg(r#in) => r#in.id,\n\t\t}\n\t}\n}\n\n// --- Block\n#[derive(Debug)]\npub(crate) struct ProcessInBlock {\n\tpub(crate) id:   Id,\n\tpub(crate) cwd:  UrlCow<'static>,\n\tpub(crate) cmd:  OsString,\n\tpub(crate) args: Vec<UrlCow<'static>>,\n}\n\nimpl From<ProcessInBlock> for ShellOpt {\n\tfn from(r#in: ProcessInBlock) -> Self {\n\t\tSelf { cwd: r#in.cwd, cmd: r#in.cmd, args: r#in.args, piped: false, orphan: false }\n\t}\n}\n\n// --- Orphan\n#[derive(Debug)]\npub(crate) struct ProcessInOrphan {\n\tpub(crate) id:   Id,\n\tpub(crate) cwd:  UrlCow<'static>,\n\tpub(crate) cmd:  OsString,\n\tpub(crate) args: Vec<UrlCow<'static>>,\n}\n\nimpl From<ProcessInOrphan> for ShellOpt {\n\tfn from(r#in: ProcessInOrphan) -> Self {\n\t\tSelf { cwd: r#in.cwd, cmd: r#in.cmd, args: r#in.args, piped: false, orphan: true }\n\t}\n}\n\n// --- Bg\n#[derive(Debug)]\npub(crate) struct ProcessInBg {\n\tpub(crate) id:   Id,\n\tpub(crate) cwd:  UrlCow<'static>,\n\tpub(crate) cmd:  OsString,\n\tpub(crate) args: Vec<UrlCow<'static>>,\n\tpub(crate) done: CompletionToken,\n}\n\nimpl From<ProcessInBg> for ShellOpt {\n\tfn from(r#in: ProcessInBg) -> Self {\n\t\tSelf { cwd: r#in.cwd, cmd: r#in.cmd, args: r#in.args, piped: true, orphan: false }\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/src/process/macros.rs",
    "content": "macro_rules! impl_from_in {\n\t($($variant:ident($type:ty)),* $(,)?) => {\n\t\t$(\n\t\t\timpl From<$type> for $crate::process::ProcessIn {\n\t\t\t\tfn from(value: $type) -> Self { Self::$variant(value) }\n\t\t\t}\n\t\t)*\n\t};\n}\n"
  },
  {
    "path": "yazi-scheduler/src/process/mod.rs",
    "content": "#[macro_use]\nmod macros;\n\nyazi_macro::mod_flat!(out process progress r#in shell);\n"
  },
  {
    "path": "yazi-scheduler/src/process/out.rs",
    "content": "use crate::{Task, TaskProg};\n\n#[derive(Debug)]\npub(crate) enum ProcessOutBlock {\n\tSucc,\n\tFail(String),\n}\n\nimpl From<std::io::Error> for ProcessOutBlock {\n\tfn from(value: std::io::Error) -> Self { Self::Fail(value.to_string()) }\n}\n\nimpl ProcessOutBlock {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tlet TaskProg::ProcessBlock(prog) = &mut task.prog else { return };\n\t\tmatch self {\n\t\t\tSelf::Succ => {\n\t\t\t\tprog.state = Some(true);\n\t\t\t}\n\t\t\tSelf::Fail(reason) => {\n\t\t\t\tprog.state = Some(false);\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// --- Orphan\n#[derive(Debug)]\npub(crate) enum ProcessOutOrphan {\n\tSucc,\n\tFail(String),\n}\n\nimpl From<anyhow::Error> for ProcessOutOrphan {\n\tfn from(value: anyhow::Error) -> Self { Self::Fail(value.to_string()) }\n}\n\nimpl ProcessOutOrphan {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tlet TaskProg::ProcessOrphan(prog) = &mut task.prog else { return };\n\t\tmatch self {\n\t\t\tSelf::Succ => {\n\t\t\t\tprog.state = Some(true);\n\t\t\t}\n\t\t\tSelf::Fail(reason) => {\n\t\t\t\tprog.state = Some(false);\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// --- Bg\n#[derive(Debug)]\npub(crate) enum ProcessOutBg {\n\tLog(String),\n\tSucc,\n\tFail(String),\n}\n\nimpl From<anyhow::Error> for ProcessOutBg {\n\tfn from(value: anyhow::Error) -> Self { Self::Fail(value.to_string()) }\n}\n\nimpl ProcessOutBg {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tlet TaskProg::ProcessBg(prog) = &mut task.prog else { return };\n\t\tmatch self {\n\t\t\tSelf::Log(line) => {\n\t\t\t\ttask.log(line);\n\t\t\t}\n\t\t\tSelf::Succ => {\n\t\t\t\tprog.state = Some(true);\n\t\t\t}\n\t\t\tSelf::Fail(reason) => {\n\t\t\t\tprog.state = Some(false);\n\t\t\t\ttask.log(reason);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/src/process/process.rs",
    "content": "use anyhow::{Result, anyhow};\nuse tokio::{io::{AsyncBufReadExt, BufReader}, select, sync::mpsc};\nuse yazi_binding::Permit;\nuse yazi_proxy::{AppProxy, NotifyProxy};\nuse yazi_term::YIELD_TO_SUBPROCESS;\n\nuse super::{ProcessInBg, ProcessInBlock, ProcessInOrphan, ShellOpt};\nuse crate::{TaskOp, TaskOps, process::{ProcessIn, ProcessOutBg, ProcessOutBlock, ProcessOutOrphan}};\n\npub(crate) struct Process {\n\tops: TaskOps,\n\ttx:  async_priority_channel::Sender<ProcessIn, u8>,\n}\n\nimpl Process {\n\tpub(crate) fn new(\n\t\tops: &mpsc::UnboundedSender<TaskOp>,\n\t\ttx: async_priority_channel::Sender<ProcessIn, u8>,\n\t) -> Self {\n\t\tSelf { ops: ops.into(), tx }\n\t}\n\n\tpub(crate) async fn block(&self, task: ProcessInBlock) -> Result<(), ProcessOutBlock> {\n\t\tlet _permit = Permit::new(YIELD_TO_SUBPROCESS.acquire().await.unwrap(), AppProxy::resume());\n\t\tAppProxy::stop().await;\n\n\t\tlet (id, cmd) = (task.id, task.cmd.clone());\n\t\tlet result = super::shell(task.into()).await;\n\t\tif let Err(e) = result {\n\t\t\tNotifyProxy::push_warn(cmd.to_string_lossy(), format!(\"Failed to start process: {e}\"));\n\t\t\treturn Ok(self.ops.out(id, ProcessOutBlock::Succ));\n\t\t}\n\n\t\tlet status = result.unwrap().wait().await?;\n\t\tif !status.success() {\n\t\t\tlet content = match status.code() {\n\t\t\t\tSome(130) => return Ok(self.ops.out(id, ProcessOutBlock::Succ)), // Ctrl-C pressed by user\n\t\t\t\tSome(code) => format!(\"Process exited with status code: {code}\"),\n\t\t\t\tNone => \"Process terminated by signal\".to_string(),\n\t\t\t};\n\t\t\tNotifyProxy::push_warn(cmd.to_string_lossy(), content);\n\t\t}\n\n\t\tOk(self.ops.out(id, ProcessOutBlock::Succ))\n\t}\n\n\tpub(crate) async fn orphan(&self, task: ProcessInOrphan) -> Result<(), ProcessOutOrphan> {\n\t\tlet id = task.id;\n\n\t\tsuper::shell(task.into()).await?;\n\t\tOk(self.ops.out(id, ProcessOutOrphan::Succ))\n\t}\n\n\tpub(crate) async fn bg(&self, task: ProcessInBg) -> Result<(), ProcessOutBg> {\n\t\tlet mut child = super::shell(ShellOpt {\n\t\t\tcwd:    task.cwd,\n\t\t\tcmd:    task.cmd,\n\t\t\targs:   task.args,\n\t\t\tpiped:  true,\n\t\t\torphan: false,\n\t\t})\n\t\t.await?;\n\n\t\tlet done = task.done;\n\t\tlet mut stdout = BufReader::new(child.stdout.take().unwrap()).lines();\n\t\tlet mut stderr = BufReader::new(child.stderr.take().unwrap()).lines();\n\t\tloop {\n\t\t\tselect! {\n\t\t\t\tfalse = done.future() => {\n\t\t\t\t\tchild.start_kill().ok();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tOk(Some(line)) = stdout.next_line() => {\n\t\t\t\t\tself.ops.out(task.id, ProcessOutBg::Log(line));\n\t\t\t\t}\n\t\t\t\tOk(Some(line)) = stderr.next_line() => {\n\t\t\t\t\tself.ops.out(task.id, ProcessOutBg::Log(line));\n\t\t\t\t}\n\t\t\t\tOk(status) = child.wait() => {\n\t\t\t\t\tself.ops.out(task.id, ProcessOutBg::Log(match status.code() {\n\t\t\t\t\t\tSome(code) => format!(\"Exited with status code: {code}\"),\n\t\t\t\t\t\tNone => \"Process terminated by signal\".to_string(),\n\t\t\t\t\t}));\n\t\t\t\t\tif !status.success() {\n\t\t\t\t\t\tErr(anyhow!(\"Process failed\"))?;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tOk(self.ops.out(task.id, ProcessOutBg::Succ))\n\t}\n}\n\nimpl Process {\n\t#[inline]\n\tpub(crate) fn submit(&self, r#in: impl Into<ProcessIn>, priority: u8) {\n\t\t_ = self.tx.try_send(r#in.into(), priority);\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/src/process/progress.rs",
    "content": "use serde::Serialize;\nuse yazi_parser::app::TaskSummary;\n\n// --- Block\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]\npub struct ProcessProgBlock {\n\tpub state: Option<bool>,\n}\n\nimpl From<ProcessProgBlock> for TaskSummary {\n\tfn from(value: ProcessProgBlock) -> Self {\n\t\tSelf {\n\t\t\ttotal:   (value.state == Some(false)) as u32,\n\t\t\tsuccess: 0,\n\t\t\tfailed:  (value.state == Some(false)) as u32,\n\t\t\tpercent: value.percent().map(Into::into),\n\t\t}\n\t}\n}\n\nimpl ProcessProgBlock {\n\tpub fn cooked(self) -> bool { self.state == Some(true) }\n\n\tpub fn running(self) -> bool { self.state.is_none() }\n\n\tpub fn success(self) -> bool { self.cooked() }\n\n\tpub fn failed(self) -> bool { self.state == Some(false) }\n\n\tpub fn cleaned(self) -> Option<bool> { None }\n\n\tpub fn percent(self) -> Option<f32> { None }\n}\n\n// --- Orphan\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]\npub struct ProcessProgOrphan {\n\tpub state: Option<bool>,\n}\n\nimpl From<ProcessProgOrphan> for TaskSummary {\n\tfn from(value: ProcessProgOrphan) -> Self {\n\t\tSelf {\n\t\t\ttotal:   (value.state == Some(false)) as u32,\n\t\t\tsuccess: 0,\n\t\t\tfailed:  (value.state == Some(false)) as u32,\n\t\t\tpercent: value.percent().map(Into::into),\n\t\t}\n\t}\n}\n\nimpl ProcessProgOrphan {\n\tpub fn cooked(self) -> bool { self.state == Some(true) }\n\n\tpub fn running(self) -> bool { self.state.is_none() }\n\n\tpub fn success(self) -> bool { self.cooked() }\n\n\tpub fn failed(self) -> bool { self.state == Some(false) }\n\n\tpub fn cleaned(self) -> Option<bool> { None }\n\n\tpub fn percent(self) -> Option<f32> { None }\n}\n\n// --- Bg\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]\npub struct ProcessProgBg {\n\tpub state: Option<bool>,\n}\n\nimpl From<ProcessProgBg> for TaskSummary {\n\tfn from(value: ProcessProgBg) -> Self {\n\t\tSelf {\n\t\t\ttotal:   1,\n\t\t\tsuccess: (value.state == Some(true)) as u32,\n\t\t\tfailed:  (value.state == Some(false)) as u32,\n\t\t\tpercent: value.percent().map(Into::into),\n\t\t}\n\t}\n}\n\nimpl ProcessProgBg {\n\tpub fn cooked(self) -> bool { self.state == Some(true) }\n\n\tpub fn running(self) -> bool { self.state.is_none() }\n\n\tpub fn success(self) -> bool { self.cooked() }\n\n\tpub fn failed(self) -> bool { self.state == Some(false) }\n\n\tpub fn cleaned(self) -> Option<bool> { None }\n\n\tpub fn percent(self) -> Option<f32> { None }\n}\n"
  },
  {
    "path": "yazi-scheduler/src/process/shell.rs",
    "content": "use std::{ffi::OsString, process::Stdio};\n\nuse anyhow::Result;\nuse tokio::process::{Child, Command};\nuse yazi_fs::Cwd;\nuse yazi_shared::url::{AsUrl, UrlCow};\n\npub(crate) struct ShellOpt {\n\tpub(crate) cwd:    UrlCow<'static>,\n\tpub(crate) cmd:    OsString,\n\tpub(crate) args:   Vec<UrlCow<'static>>,\n\tpub(crate) piped:  bool,\n\tpub(crate) orphan: bool,\n}\n\nimpl ShellOpt {\n\t#[inline]\n\tfn stdio(&self) -> Stdio {\n\t\tif self.orphan {\n\t\t\tStdio::null()\n\t\t} else if self.piped {\n\t\t\tStdio::piped()\n\t\t} else {\n\t\t\tStdio::inherit()\n\t\t}\n\t}\n}\n\npub(crate) async fn shell(opt: ShellOpt) -> Result<Child> {\n\ttokio::task::spawn_blocking(move || {\n\t\tlet cwd = Cwd::ensure(opt.cwd.as_url());\n\n\t\t#[cfg(unix)]\n\t\treturn Ok(unsafe {\n\t\t\tuse yazi_fs::FsUrl;\n\t\t\tuse yazi_shared::url::AsUrl;\n\n\t\t\tCommand::new(\"sh\")\n\t\t\t\t.stdin(opt.stdio())\n\t\t\t\t.stdout(opt.stdio())\n\t\t\t\t.stderr(opt.stdio())\n\t\t\t\t.arg(\"-c\")\n\t\t\t\t.arg(opt.cmd)\n\t\t\t\t// TODO: remove\n\t\t\t\t.args(opt.args.iter().map(|u| u.as_url().unified_path_str()))\n\t\t\t\t.current_dir(cwd)\n\t\t\t\t.kill_on_drop(!opt.orphan)\n\t\t\t\t.pre_exec(move || {\n\t\t\t\t\tif (opt.piped || opt.orphan) && libc::setsid() < 0 {\n\t\t\t\t\t\treturn Err(std::io::Error::last_os_error());\n\t\t\t\t\t}\n\t\t\t\t\tOk(())\n\t\t\t\t})\n\t\t\t\t.spawn()?\n\t\t});\n\n\t\t#[cfg(windows)]\n\t\treturn Ok(\n\t\t\tCommand::new(\"cmd.exe\")\n\t\t\t\t.stdin(opt.stdio())\n\t\t\t\t.stdout(opt.stdio())\n\t\t\t\t.stderr(opt.stdio())\n\t\t\t\t.env(\"=\", r#\"\"^\\n\\n\"\"#)\n\t\t\t\t.raw_arg(r#\"/Q /S /D /V:OFF /E:ON /C \"\"#)\n\t\t\t\t.raw_arg(opt.cmd)\n\t\t\t\t.raw_arg(r#\"\"\"#)\n\t\t\t\t.current_dir(cwd)\n\t\t\t\t.kill_on_drop(!opt.orphan)\n\t\t\t\t.spawn()?,\n\t\t);\n\t})\n\t.await?\n}\n"
  },
  {
    "path": "yazi-scheduler/src/progress.rs",
    "content": "use serde::Serialize;\nuse yazi_parser::app::TaskSummary;\n\nuse crate::{fetch::FetchProg, file::{FileProgCopy, FileProgCut, FileProgDelete, FileProgDownload, FileProgHardlink, FileProgLink, FileProgTrash, FileProgUpload}, impl_from_prog, plugin::PluginProgEntry, preload::PreloadProg, process::{ProcessProgBg, ProcessProgBlock, ProcessProgOrphan}, size::SizeProg};\n\n#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]\n#[serde(tag = \"kind\")]\npub enum TaskProg {\n\t// File\n\tFileCopy(FileProgCopy),\n\tFileCut(FileProgCut),\n\tFileLink(FileProgLink),\n\tFileHardlink(FileProgHardlink),\n\tFileDelete(FileProgDelete),\n\tFileTrash(FileProgTrash),\n\tFileDownload(FileProgDownload),\n\tFileUpload(FileProgUpload),\n\t// Plugin\n\tPluginEntry(PluginProgEntry),\n\t// Fetch\n\tFetch(FetchProg),\n\t// Preload\n\tPreload(PreloadProg),\n\t// Size\n\tSize(SizeProg),\n\t// Process\n\tProcessBlock(ProcessProgBlock),\n\tProcessOrphan(ProcessProgOrphan),\n\tProcessBg(ProcessProgBg),\n}\n\nimpl_from_prog! {\n\t// File\n\tFileCopy(FileProgCopy), FileCut(FileProgCut), FileLink(FileProgLink), FileHardlink(FileProgHardlink), FileDelete(FileProgDelete), FileTrash(FileProgTrash), FileDownload(FileProgDownload), FileUpload(FileProgUpload),\n\t// Plugin\n\tPluginEntry(PluginProgEntry),\n\t// Fetch\n\tFetch(FetchProg),\n\t// Preload\n\tPreload(PreloadProg),\n\t// Size\n\tSize(SizeProg),\n\t// Process\n\tProcessBlock(ProcessProgBlock), ProcessOrphan(ProcessProgOrphan), ProcessBg(ProcessProgBg),\n}\n\nimpl From<TaskProg> for TaskSummary {\n\tfn from(value: TaskProg) -> Self {\n\t\tmatch value {\n\t\t\t// File\n\t\t\tTaskProg::FileCopy(p) => p.into(),\n\t\t\tTaskProg::FileCut(p) => p.into(),\n\t\t\tTaskProg::FileLink(p) => p.into(),\n\t\t\tTaskProg::FileHardlink(p) => p.into(),\n\t\t\tTaskProg::FileDelete(p) => p.into(),\n\t\t\tTaskProg::FileTrash(p) => p.into(),\n\t\t\tTaskProg::FileDownload(p) => p.into(),\n\t\t\tTaskProg::FileUpload(p) => p.into(),\n\t\t\t// Plugin\n\t\t\tTaskProg::PluginEntry(p) => p.into(),\n\t\t\t// Prework\n\t\t\tTaskProg::Fetch(p) => p.into(),\n\t\t\tTaskProg::Preload(p) => p.into(),\n\t\t\tTaskProg::Size(p) => p.into(),\n\t\t\t// Process\n\t\t\tTaskProg::ProcessBlock(p) => p.into(),\n\t\t\tTaskProg::ProcessOrphan(p) => p.into(),\n\t\t\tTaskProg::ProcessBg(p) => p.into(),\n\t\t}\n\t}\n}\n\nimpl TaskProg {\n\tpub fn cooked(self) -> bool {\n\t\tmatch self {\n\t\t\t// File\n\t\t\tSelf::FileCopy(p) => p.cooked(),\n\t\t\tSelf::FileCut(p) => p.cooked(),\n\t\t\tSelf::FileLink(p) => p.cooked(),\n\t\t\tSelf::FileHardlink(p) => p.cooked(),\n\t\t\tSelf::FileDelete(p) => p.cooked(),\n\t\t\tSelf::FileTrash(p) => p.cooked(),\n\t\t\tSelf::FileDownload(p) => p.cooked(),\n\t\t\tSelf::FileUpload(p) => p.cooked(),\n\t\t\t// Plugin\n\t\t\tSelf::PluginEntry(p) => p.cooked(),\n\t\t\t// Prework\n\t\t\tSelf::Fetch(p) => p.cooked(),\n\t\t\tSelf::Preload(p) => p.cooked(),\n\t\t\tSelf::Size(p) => p.cooked(),\n\t\t\t// Process\n\t\t\tSelf::ProcessBlock(p) => p.cooked(),\n\t\t\tSelf::ProcessOrphan(p) => p.cooked(),\n\t\t\tSelf::ProcessBg(p) => p.cooked(),\n\t\t}\n\t}\n\n\tpub fn running(self) -> bool {\n\t\tmatch self {\n\t\t\t// File\n\t\t\tSelf::FileCopy(p) => p.running(),\n\t\t\tSelf::FileCut(p) => p.running(),\n\t\t\tSelf::FileLink(p) => p.running(),\n\t\t\tSelf::FileHardlink(p) => p.running(),\n\t\t\tSelf::FileDelete(p) => p.running(),\n\t\t\tSelf::FileTrash(p) => p.running(),\n\t\t\tSelf::FileDownload(p) => p.running(),\n\t\t\tSelf::FileUpload(p) => p.running(),\n\t\t\t// Plugin\n\t\t\tSelf::PluginEntry(p) => p.running(),\n\t\t\t// Prework\n\t\t\tSelf::Fetch(p) => p.running(),\n\t\t\tSelf::Preload(p) => p.running(),\n\t\t\tSelf::Size(p) => p.running(),\n\t\t\t// Process\n\t\t\tSelf::ProcessBlock(p) => p.running(),\n\t\t\tSelf::ProcessOrphan(p) => p.running(),\n\t\t\tSelf::ProcessBg(p) => p.running(),\n\t\t}\n\t}\n\n\tpub fn success(self) -> bool {\n\t\tmatch self {\n\t\t\t// File\n\t\t\tSelf::FileCopy(p) => p.success(),\n\t\t\tSelf::FileCut(p) => p.success(),\n\t\t\tSelf::FileLink(p) => p.success(),\n\t\t\tSelf::FileHardlink(p) => p.success(),\n\t\t\tSelf::FileDelete(p) => p.success(),\n\t\t\tSelf::FileTrash(p) => p.success(),\n\t\t\tSelf::FileDownload(p) => p.success(),\n\t\t\tSelf::FileUpload(p) => p.success(),\n\t\t\t// Plugin\n\t\t\tSelf::PluginEntry(p) => p.success(),\n\t\t\t// Prework\n\t\t\tSelf::Fetch(p) => p.success(),\n\t\t\tSelf::Preload(p) => p.success(),\n\t\t\tSelf::Size(p) => p.success(),\n\t\t\t// Process\n\t\t\tSelf::ProcessBlock(p) => p.success(),\n\t\t\tSelf::ProcessOrphan(p) => p.success(),\n\t\t\tSelf::ProcessBg(p) => p.success(),\n\t\t}\n\t}\n\n\tpub fn failed(self) -> bool {\n\t\tmatch self {\n\t\t\t// File\n\t\t\tSelf::FileCopy(p) => p.failed(),\n\t\t\tSelf::FileCut(p) => p.failed(),\n\t\t\tSelf::FileLink(p) => p.failed(),\n\t\t\tSelf::FileHardlink(p) => p.failed(),\n\t\t\tSelf::FileDelete(p) => p.failed(),\n\t\t\tSelf::FileTrash(p) => p.failed(),\n\t\t\tSelf::FileDownload(p) => p.failed(),\n\t\t\tSelf::FileUpload(p) => p.failed(),\n\t\t\t// Plugin\n\t\t\tSelf::PluginEntry(p) => p.failed(),\n\t\t\t// Prework\n\t\t\tSelf::Fetch(p) => p.failed(),\n\t\t\tSelf::Preload(p) => p.failed(),\n\t\t\tSelf::Size(p) => p.failed(),\n\t\t\t// Process\n\t\t\tSelf::ProcessBlock(p) => p.failed(),\n\t\t\tSelf::ProcessOrphan(p) => p.failed(),\n\t\t\tSelf::ProcessBg(p) => p.failed(),\n\t\t}\n\t}\n\n\tpub fn cleaned(self) -> Option<bool> {\n\t\tmatch self {\n\t\t\t// File\n\t\t\tSelf::FileCopy(p) => p.cleaned(),\n\t\t\tSelf::FileCut(p) => p.cleaned(),\n\t\t\tSelf::FileLink(p) => p.cleaned(),\n\t\t\tSelf::FileHardlink(p) => p.cleaned(),\n\t\t\tSelf::FileDelete(p) => p.cleaned(),\n\t\t\tSelf::FileTrash(p) => p.cleaned(),\n\t\t\tSelf::FileDownload(p) => p.cleaned(),\n\t\t\tSelf::FileUpload(p) => p.cleaned(),\n\t\t\t// Plugin\n\t\t\tSelf::PluginEntry(p) => p.cleaned(),\n\t\t\t// Prework\n\t\t\tSelf::Fetch(p) => p.cleaned(),\n\t\t\tSelf::Preload(p) => p.cleaned(),\n\t\t\tSelf::Size(p) => p.cleaned(),\n\t\t\t// Process\n\t\t\tSelf::ProcessBlock(p) => p.cleaned(),\n\t\t\tSelf::ProcessOrphan(p) => p.cleaned(),\n\t\t\tSelf::ProcessBg(p) => p.cleaned(),\n\t\t}\n\t}\n\n\tpub fn percent(self) -> Option<f32> {\n\t\tmatch self {\n\t\t\t// File\n\t\t\tSelf::FileCopy(p) => p.percent(),\n\t\t\tSelf::FileCut(p) => p.percent(),\n\t\t\tSelf::FileLink(p) => p.percent(),\n\t\t\tSelf::FileHardlink(p) => p.percent(),\n\t\t\tSelf::FileDelete(p) => p.percent(),\n\t\t\tSelf::FileTrash(p) => p.percent(),\n\t\t\tSelf::FileDownload(p) => p.percent(),\n\t\t\tSelf::FileUpload(p) => p.percent(),\n\t\t\t// Plugin\n\t\t\tSelf::PluginEntry(p) => p.percent(),\n\t\t\t// Prework\n\t\t\tSelf::Fetch(p) => p.percent(),\n\t\t\tSelf::Preload(p) => p.percent(),\n\t\t\tSelf::Size(p) => p.percent(),\n\t\t\t// Process\n\t\t\tSelf::ProcessBlock(p) => p.percent(),\n\t\t\tSelf::ProcessOrphan(p) => p.percent(),\n\t\t\tSelf::ProcessBg(p) => p.percent(),\n\t\t}\n\t}\n\n\tpub(crate) fn is_user(self) -> bool {\n\t\tmatch self {\n\t\t\t// File\n\t\t\tSelf::FileCopy(_) => true,\n\t\t\tSelf::FileCut(_) => true,\n\t\t\tSelf::FileLink(_) => true,\n\t\t\tSelf::FileHardlink(_) => true,\n\t\t\tSelf::FileDelete(_) => true,\n\t\t\tSelf::FileTrash(_) => true,\n\t\t\tSelf::FileDownload(_) => true,\n\t\t\tSelf::FileUpload(_) => true,\n\t\t\t// Plugin\n\t\t\tSelf::PluginEntry(_) => true,\n\t\t\t// Prework\n\t\t\tSelf::Fetch(_) => false,\n\t\t\tSelf::Preload(_) => false,\n\t\t\tSelf::Size(_) => false,\n\t\t\t// Process\n\t\t\tSelf::ProcessBlock(_) => true,\n\t\t\tSelf::ProcessOrphan(_) => true,\n\t\t\tSelf::ProcessBg(_) => true,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/src/runner.rs",
    "content": "use std::sync::Arc;\n\nuse parking_lot::Mutex;\nuse tokio::{select, sync::mpsc, task::JoinHandle};\nuse yazi_config::YAZI;\n\nuse crate::{LOW, Ongoing, TaskOp, TaskOps, TaskOut, fetch::{Fetch, FetchIn}, file::{File, FileIn}, hook::{Hook, HookIn}, plugin::{Plugin, PluginIn}, preload::{Preload, PreloadIn}, process::{Process, ProcessIn}, size::{Size, SizeIn}};\n\n#[derive(Clone)]\npub struct Runner {\n\tpub(super) file:    Arc<File>,\n\tpub(super) plugin:  Arc<Plugin>,\n\tpub fetch:          Arc<Fetch>,\n\tpub preload:        Arc<Preload>,\n\tpub size:           Arc<Size>,\n\tpub(super) process: Arc<Process>,\n\tpub(super) hook:    Arc<Hook>,\n\n\tpub ops:     TaskOps,\n\tpub ongoing: Arc<Mutex<Ongoing>>,\n}\n\nimpl Runner {\n\tpub(super) fn make() -> (Self, Vec<JoinHandle<()>>) {\n\t\tlet (file_tx, file_rx) = async_priority_channel::unbounded();\n\t\tlet (plugin_tx, plugin_rx) = async_priority_channel::unbounded();\n\t\tlet (fetch_tx, fetch_rx) = async_priority_channel::unbounded();\n\t\tlet (preload_tx, preload_rx) = async_priority_channel::unbounded();\n\t\tlet (size_tx, size_rx) = async_priority_channel::unbounded();\n\t\tlet (process_tx, process_rx) = async_priority_channel::unbounded();\n\t\tlet (hook_tx, hook_rx) = async_priority_channel::unbounded();\n\t\tlet (op_tx, op_rx) = mpsc::unbounded_channel();\n\t\tlet ongoing = Arc::new(Mutex::new(Ongoing::default()));\n\n\t\tlet me = Self {\n\t\t\tfile: Arc::new(File::new(&op_tx, file_tx)),\n\t\t\tplugin: Arc::new(Plugin::new(&op_tx, plugin_tx)),\n\t\t\tfetch: Arc::new(Fetch::new(&op_tx, fetch_tx)),\n\t\t\tpreload: Arc::new(Preload::new(&op_tx, preload_tx)),\n\t\t\tsize: Arc::new(Size::new(&op_tx, size_tx)),\n\t\t\tprocess: Arc::new(Process::new(&op_tx, process_tx)),\n\t\t\thook: Arc::new(Hook::new(&op_tx, &ongoing, hook_tx)),\n\n\t\t\tops: TaskOps(op_tx),\n\t\t\tongoing,\n\t\t};\n\n\t\tlet handles = []\n\t\t\t.into_iter()\n\t\t\t.chain((0..YAZI.tasks.file_workers).map(|_| me.file(file_rx.clone())))\n\t\t\t.chain((0..YAZI.tasks.plugin_workers).map(|_| me.plugin(plugin_rx.clone())))\n\t\t\t.chain((0..YAZI.tasks.fetch_workers).map(|_| me.fetch(fetch_rx.clone())))\n\t\t\t.chain((0..YAZI.tasks.preload_workers).map(|_| me.preload(preload_rx.clone())))\n\t\t\t.chain((0..3).map(|_| me.size(size_rx.clone())))\n\t\t\t.chain((0..YAZI.tasks.process_workers).map(|_| me.process(process_rx.clone())))\n\t\t\t.chain((0..3).map(|_| me.hook(hook_rx.clone())))\n\t\t\t.chain([me.op(op_rx)])\n\t\t\t.collect();\n\n\t\t(me, handles)\n\t}\n\n\tfn file(&self, rx: async_priority_channel::Receiver<FileIn, u8>) -> JoinHandle<()> {\n\t\tlet me = self.clone();\n\t\ttokio::spawn(async move {\n\t\t\tloop {\n\t\t\t\tif let Ok((r#in, _)) = rx.recv().await {\n\t\t\t\t\tlet id = r#in.id();\n\t\t\t\t\tlet Some(token) = me.ongoing.lock().get_token(id) else {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t};\n\n\t\t\t\t\tlet result = select! {\n\t\t\t\t\t\tr = me.file_do(r#in) => r,\n\t\t\t\t\t\tfalse = token.future() => Ok(())\n\t\t\t\t\t};\n\n\t\t\t\t\tif let Err(out) = result {\n\t\t\t\t\t\tme.ops.out(id, out);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n\tasync fn file_do(&self, r#in: FileIn) -> Result<(), TaskOut> {\n\t\tmatch r#in {\n\t\t\tFileIn::Copy(r#in) => self.file.copy(r#in).await.map_err(Into::into),\n\t\t\tFileIn::CopyDo(r#in) => self.file.copy_do(r#in).await.map_err(Into::into),\n\t\t\tFileIn::Cut(r#in) => self.file.cut(r#in).await.map_err(Into::into),\n\t\t\tFileIn::CutDo(r#in) => self.file.cut_do(r#in).await.map_err(Into::into),\n\t\t\tFileIn::Link(r#in) => self.file.link(r#in).await.map_err(Into::into),\n\t\t\tFileIn::LinkDo(r#in) => self.file.link_do(r#in).await.map_err(Into::into),\n\t\t\tFileIn::Hardlink(r#in) => self.file.hardlink(r#in).await.map_err(Into::into),\n\t\t\tFileIn::HardlinkDo(r#in) => self.file.hardlink_do(r#in).await.map_err(Into::into),\n\t\t\tFileIn::Delete(r#in) => self.file.delete(r#in).await.map_err(Into::into),\n\t\t\tFileIn::DeleteDo(r#in) => self.file.delete_do(r#in).await.map_err(Into::into),\n\t\t\tFileIn::Trash(r#in) => self.file.trash(r#in).await.map_err(Into::into),\n\t\t\tFileIn::TrashDo(r#in) => self.file.trash_do(r#in).await.map_err(Into::into),\n\t\t\tFileIn::Download(r#in) => self.file.download(r#in).await.map_err(Into::into),\n\t\t\tFileIn::DownloadDo(r#in) => self.file.download_do(r#in).await.map_err(Into::into),\n\t\t\tFileIn::Upload(r#in) => self.file.upload(r#in).await.map_err(Into::into),\n\t\t\tFileIn::UploadDo(r#in) => self.file.upload_do(r#in).await.map_err(Into::into),\n\t\t}\n\t}\n\n\tfn plugin(&self, rx: async_priority_channel::Receiver<PluginIn, u8>) -> JoinHandle<()> {\n\t\tlet me = self.clone();\n\t\ttokio::spawn(async move {\n\t\t\tloop {\n\t\t\t\tif let Ok((r#in, _)) = rx.recv().await {\n\t\t\t\t\tlet id = r#in.id();\n\t\t\t\t\tlet Some(token) = me.ongoing.lock().get_token(id) else {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t};\n\n\t\t\t\t\tlet result = select! {\n\t\t\t\t\t\tr = me.plugin_do(r#in) => r,\n\t\t\t\t\t\tfalse = token.future() => Ok(())\n\t\t\t\t\t};\n\n\t\t\t\t\tif let Err(out) = result {\n\t\t\t\t\t\tme.ops.out(id, out);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n\tasync fn plugin_do(&self, r#in: PluginIn) -> Result<(), TaskOut> {\n\t\tmatch r#in {\n\t\t\tPluginIn::Entry(r#in) => self.plugin.entry(r#in).await.map_err(Into::into),\n\t\t}\n\t}\n\n\tfn fetch(&self, rx: async_priority_channel::Receiver<FetchIn, u8>) -> JoinHandle<()> {\n\t\tlet me = self.clone();\n\t\ttokio::spawn(async move {\n\t\t\tloop {\n\t\t\t\tif let Ok((r#in, _)) = rx.recv().await {\n\t\t\t\t\tlet id = r#in.id();\n\t\t\t\t\tlet Some(token) = me.ongoing.lock().get_token(id) else {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t};\n\n\t\t\t\t\tlet result = select! {\n\t\t\t\t\t\tr = me.fetch_do(r#in) => r,\n\t\t\t\t\t\tfalse = token.future() => Ok(())\n\t\t\t\t\t};\n\n\t\t\t\t\tif let Err(out) = result {\n\t\t\t\t\t\tme.ops.out(id, out);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n\tasync fn fetch_do(&self, r#in: FetchIn) -> Result<(), TaskOut> {\n\t\tself.fetch.fetch(r#in).await.map_err(Into::into)\n\t}\n\n\tfn preload(&self, rx: async_priority_channel::Receiver<PreloadIn, u8>) -> JoinHandle<()> {\n\t\tlet me = self.clone();\n\t\ttokio::spawn(async move {\n\t\t\tloop {\n\t\t\t\tif let Ok((r#in, _)) = rx.recv().await {\n\t\t\t\t\tlet id = r#in.id();\n\t\t\t\t\tlet Some(token) = me.ongoing.lock().get_token(id) else {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t};\n\n\t\t\t\t\tlet result = select! {\n\t\t\t\t\t\tr = me.preload_do(r#in) => r,\n\t\t\t\t\t\tfalse = token.future() => Ok(())\n\t\t\t\t\t};\n\n\t\t\t\t\tif let Err(out) = result {\n\t\t\t\t\t\tme.ops.out(id, out);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n\tasync fn preload_do(&self, r#in: PreloadIn) -> Result<(), TaskOut> {\n\t\tself.preload.preload(r#in).await.map_err(Into::into)\n\t}\n\n\tfn size(&self, rx: async_priority_channel::Receiver<SizeIn, u8>) -> JoinHandle<()> {\n\t\tlet me = self.clone();\n\t\ttokio::spawn(async move {\n\t\t\tloop {\n\t\t\t\tif let Ok((r#in, _)) = rx.recv().await {\n\t\t\t\t\tlet id = r#in.id();\n\t\t\t\t\tlet Some(token) = me.ongoing.lock().get_token(id) else {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t};\n\n\t\t\t\t\tlet result = select! {\n\t\t\t\t\t\tr = me.size_do(r#in) => r,\n\t\t\t\t\t\tfalse = token.future() => Ok(())\n\t\t\t\t\t};\n\n\t\t\t\t\tif let Err(out) = result {\n\t\t\t\t\t\tme.ops.out(id, out);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n\tasync fn size_do(&self, r#in: SizeIn) -> Result<(), TaskOut> {\n\t\tself.size.size(r#in).await.map_err(Into::into)\n\t}\n\n\tfn process(&self, rx: async_priority_channel::Receiver<ProcessIn, u8>) -> JoinHandle<()> {\n\t\tlet me = self.clone();\n\t\ttokio::spawn(async move {\n\t\t\tloop {\n\t\t\t\tif let Ok((r#in, _)) = rx.recv().await {\n\t\t\t\t\tlet id = r#in.id();\n\t\t\t\t\tlet Some(token) = me.ongoing.lock().get_token(id) else {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t};\n\n\t\t\t\t\tlet result = select! {\n\t\t\t\t\t\tr = me.process_do(r#in) => r,\n\t\t\t\t\t\tfalse = token.future() => Ok(())\n\t\t\t\t\t};\n\n\t\t\t\t\tif let Err(out) = result {\n\t\t\t\t\t\tme.ops.out(id, out);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n\tasync fn process_do(&self, r#in: ProcessIn) -> Result<(), TaskOut> {\n\t\tmatch r#in {\n\t\t\tProcessIn::Block(r#in) => self.process.block(r#in).await.map_err(Into::into),\n\t\t\tProcessIn::Orphan(r#in) => self.process.orphan(r#in).await.map_err(Into::into),\n\t\t\tProcessIn::Bg(r#in) => self.process.bg(r#in).await.map_err(Into::into),\n\t\t}\n\t}\n\n\tfn hook(&self, rx: async_priority_channel::Receiver<HookIn, u8>) -> JoinHandle<()> {\n\t\tlet me = self.clone();\n\t\ttokio::spawn(async move {\n\t\t\tloop {\n\t\t\t\tif let Ok((r#in, _)) = rx.recv().await {\n\t\t\t\t\tlet id = r#in.id();\n\t\t\t\t\tif !me.ongoing.lock().exists(id) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tme.hook_do(r#in).await;\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n\tasync fn hook_do(&self, r#in: HookIn) {\n\t\tmatch r#in {\n\t\t\tHookIn::Copy(r#in) => self.hook.copy(r#in).await,\n\t\t\tHookIn::Cut(r#in) => self.hook.cut(r#in).await,\n\t\t\tHookIn::Delete(r#in) => self.hook.delete(r#in).await,\n\t\t\tHookIn::Trash(r#in) => self.hook.trash(r#in).await,\n\t\t\tHookIn::Download(r#in) => self.hook.download(r#in).await,\n\t\t\tHookIn::Upload(r#in) => self.hook.upload(r#in).await,\n\t\t}\n\t}\n\n\tfn op(&self, mut rx: mpsc::UnboundedReceiver<TaskOp>) -> JoinHandle<()> {\n\t\tlet me = self.clone();\n\t\ttokio::spawn(async move {\n\t\t\twhile let Some(op) = rx.recv().await {\n\t\t\t\tlet mut ongoing = me.ongoing.lock();\n\t\t\t\tlet Some(task) = ongoing.get_mut(op.id) else { continue };\n\n\t\t\t\top.out.reduce(task);\n\t\t\t\tif !task.prog.cooked() && task.done.completed() != Some(false) {\n\t\t\t\t\tcontinue; // Not cooked yet, also not canceled\n\t\t\t\t} else if task.prog.cleaned() == Some(false) {\n\t\t\t\t\tcontinue; // Failed to clean up\n\t\t\t\t} else if let Some(hook) = task.hook.take() {\n\t\t\t\t\tme.hook.submit(hook, LOW);\n\t\t\t\t} else {\n\t\t\t\t\tongoing.fulfill(op.id);\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/src/scheduler.rs",
    "content": "use std::{ops::Deref, sync::Arc, time::Duration};\n\nuse tokio::task::JoinHandle;\nuse yazi_config::{YAZI, plugin::{Fetcher, Preloader}};\nuse yazi_parser::{app::PluginOpt, tasks::ProcessOpenOpt};\nuse yazi_shared::{CompletionToken, Id, Throttle, url::{UrlBuf, UrlLike}};\n\nuse crate::{HIGH, LOW, NORMAL, Runner, fetch::{FetchIn, FetchProg}, file::{FileInCopy, FileInCut, FileInDelete, FileInDownload, FileInHardlink, FileInLink, FileInTrash, FileInUpload, FileOutCopy, FileOutCut, FileOutDownload, FileOutHardlink, FileOutUpload, FileProgCopy, FileProgCut, FileProgDelete, FileProgDownload, FileProgHardlink, FileProgLink, FileProgTrash, FileProgUpload}, hook::{HookInDelete, HookInDownload, HookInTrash, HookInUpload}, plugin::{PluginInEntry, PluginProgEntry}, preload::{PreloadIn, PreloadProg}, process::{ProcessInBg, ProcessInBlock, ProcessInOrphan, ProcessProgBg, ProcessProgBlock, ProcessProgOrphan}, size::{SizeIn, SizeProg}};\n\npub struct Scheduler {\n\tpub runner: Runner,\n\thandles:    Vec<JoinHandle<()>>,\n}\n\nimpl Deref for Scheduler {\n\ttype Target = Runner;\n\n\tfn deref(&self) -> &Self::Target { &self.runner }\n}\n\nimpl Scheduler {\n\tpub fn serve() -> Self {\n\t\tlet (runner, handles) = Runner::make();\n\t\tSelf { runner, handles }\n\t}\n\n\tpub fn cancel(&self, id: Id) -> bool {\n\t\tif let Some(hook) = self.ongoing.lock().cancel(id) {\n\t\t\tself.hook.submit(hook, HIGH);\n\t\t\treturn false;\n\t\t}\n\n\t\ttrue\n\t}\n\n\tpub fn shutdown(&self) {\n\t\tfor handle in &self.handles {\n\t\t\thandle.abort();\n\t\t}\n\t}\n\n\tpub fn file_cut(&self, from: UrlBuf, to: UrlBuf, force: bool) {\n\t\tlet mut ongoing = self.ongoing.lock();\n\t\tlet task = ongoing.add::<FileProgCut>(format!(\"Cut {} to {}\", from.display(), to.display()));\n\n\t\tif to.try_starts_with(&from).unwrap_or(false) && !to.covariant(&from) {\n\t\t\treturn self\n\t\t\t\t.ops\n\t\t\t\t.out(task.id, FileOutCut::Fail(\"Cannot cut directory into itself\".to_owned()));\n\t\t}\n\n\t\tlet follow = !from.scheme().covariant(to.scheme());\n\t\tself.file.submit(\n\t\t\tFileInCut {\n\t\t\t\tid: task.id,\n\t\t\t\tfrom,\n\t\t\t\tto,\n\t\t\t\tforce,\n\t\t\t\tcha: None,\n\t\t\t\tfollow,\n\t\t\t\tretry: 0,\n\t\t\t\tdrop: None,\n\t\t\t\tdone: task.done.clone(),\n\t\t\t},\n\t\t\tLOW,\n\t\t);\n\t}\n\n\tpub fn file_copy(&self, from: UrlBuf, to: UrlBuf, force: bool, follow: bool) {\n\t\tlet mut ongoing = self.ongoing.lock();\n\t\tlet task = ongoing.add::<FileProgCopy>(format!(\"Copy {} to {}\", from.display(), to.display()));\n\n\t\tif to.try_starts_with(&from).unwrap_or(false) && !to.covariant(&from) {\n\t\t\treturn self\n\t\t\t\t.ops\n\t\t\t\t.out(task.id, FileOutCopy::Fail(\"Cannot copy directory into itself\".to_owned()));\n\t\t}\n\n\t\tlet follow = follow || !from.scheme().covariant(to.scheme());\n\t\tself.file.submit(\n\t\t\tFileInCopy {\n\t\t\t\tid: task.id,\n\t\t\t\tfrom,\n\t\t\t\tto,\n\t\t\t\tforce,\n\t\t\t\tcha: None,\n\t\t\t\tfollow,\n\t\t\t\tretry: 0,\n\t\t\t\tdone: task.done.clone(),\n\t\t\t},\n\t\t\tLOW,\n\t\t);\n\t}\n\n\tpub fn file_link(&self, from: UrlBuf, to: UrlBuf, relative: bool, force: bool) {\n\t\tlet mut ongoing = self.ongoing.lock();\n\t\tlet task = ongoing.add::<FileProgLink>(format!(\"Link {} to {}\", from.display(), to.display()));\n\n\t\tself.file.submit(\n\t\t\tFileInLink {\n\t\t\t\tid: task.id,\n\t\t\t\tfrom,\n\t\t\t\tto,\n\t\t\t\tforce,\n\t\t\t\tcha: None,\n\t\t\t\tresolve: false,\n\t\t\t\trelative,\n\t\t\t\tdelete: false,\n\t\t\t},\n\t\t\tLOW,\n\t\t);\n\t}\n\n\tpub fn file_hardlink(&self, from: UrlBuf, to: UrlBuf, force: bool, follow: bool) {\n\t\tlet mut ongoing = self.ongoing.lock();\n\t\tlet task =\n\t\t\tongoing.add::<FileProgHardlink>(format!(\"Hardlink {} to {}\", from.display(), to.display()));\n\n\t\tif !from.scheme().covariant(to.scheme()) {\n\t\t\treturn self\n\t\t\t\t.ops\n\t\t\t\t.out(task.id, FileOutHardlink::Fail(\"Cannot hardlink cross filesystem\".to_owned()));\n\t\t}\n\n\t\tif to.try_starts_with(&from).unwrap_or(false) && !to.covariant(&from) {\n\t\t\treturn self\n\t\t\t\t.ops\n\t\t\t\t.out(task.id, FileOutHardlink::Fail(\"Cannot hardlink directory into itself\".to_owned()));\n\t\t}\n\n\t\tself.file.submit(FileInHardlink { id: task.id, from, to, force, cha: None, follow }, LOW);\n\t}\n\n\tpub fn file_delete(&self, target: UrlBuf) {\n\t\tlet mut ongoing = self.ongoing.lock();\n\t\tlet task = ongoing.add::<FileProgDelete>(format!(\"Delete {}\", target.display()));\n\n\t\ttask.set_hook(HookInDelete { id: task.id, target: target.clone() });\n\t\tself.file.submit(FileInDelete { id: task.id, target, cha: None }, LOW);\n\t}\n\n\tpub fn file_trash(&self, target: UrlBuf) {\n\t\tlet mut ongoing = self.ongoing.lock();\n\t\tlet task = ongoing.add::<FileProgTrash>(format!(\"Trash {}\", target.display()));\n\n\t\ttask.set_hook(HookInTrash { id: task.id, target: target.clone() });\n\t\tself.file.submit(FileInTrash { id: task.id, target }, LOW);\n\t}\n\n\tpub fn file_download(&self, target: UrlBuf) -> CompletionToken {\n\t\tlet mut ongoing = self.ongoing.lock();\n\t\tlet task = ongoing.add::<FileProgDownload>(format!(\"Download {}\", target.display()));\n\n\t\tif target.kind().is_remote() {\n\t\t\ttask.set_hook(HookInDownload { id: task.id, target: target.clone() });\n\t\t\tself.file.submit(\n\t\t\t\tFileInDownload { id: task.id, target, cha: None, retry: 0, done: task.done.clone() },\n\t\t\t\tLOW,\n\t\t\t);\n\t\t} else {\n\t\t\tself.ops.out(task.id, FileOutDownload::Fail(\"Cannot download non-remote file\".to_owned()));\n\t\t}\n\n\t\ttask.done.clone()\n\t}\n\n\tpub fn file_upload(&self, target: UrlBuf) {\n\t\tlet mut ongoing = self.ongoing.lock();\n\t\tlet task = ongoing.add::<FileProgUpload>(format!(\"Upload {}\", target.display()));\n\n\t\tif !target.kind().is_remote() {\n\t\t\treturn self\n\t\t\t\t.ops\n\t\t\t\t.out(task.id, FileOutUpload::Fail(\"Cannot upload non-remote file\".to_owned()));\n\t\t};\n\n\t\ttask.set_hook(HookInUpload { id: task.id, target: target.clone() });\n\t\tself.file.submit(\n\t\t\tFileInUpload { id: task.id, target, cha: None, cache: None, done: task.done.clone() },\n\t\t\tLOW,\n\t\t);\n\t}\n\n\tpub fn plugin_entry(&self, opt: PluginOpt) {\n\t\tlet mut ongoing = self.ongoing.lock();\n\t\tlet task = ongoing.add::<PluginProgEntry>(format!(\"Run micro plugin `{}`\", opt.id));\n\n\t\tself.plugin.submit(PluginInEntry { id: task.id, opt }, NORMAL);\n\t}\n\n\tpub fn fetch_paged(\n\t\t&self,\n\t\tfetcher: &'static Fetcher,\n\t\ttargets: Vec<yazi_fs::File>,\n\t) -> CompletionToken {\n\t\tlet mut ongoing = self.ongoing.lock();\n\t\tlet task = ongoing.add::<FetchProg>(format!(\n\t\t\t\"Run fetcher `{}` with {} target(s)\",\n\t\t\tfetcher.run.name,\n\t\t\ttargets.len()\n\t\t));\n\n\t\tself.fetch.submit(FetchIn { id: task.id, plugin: fetcher, targets });\n\t\ttask.done.clone()\n\t}\n\n\tpub async fn fetch_mimetype(&self, targets: Vec<yazi_fs::File>) -> bool {\n\t\tlet mut wg = vec![];\n\t\tfor (fetcher, targets) in YAZI.plugin.mime_fetchers(targets) {\n\t\t\twg.push(self.fetch_paged(fetcher, targets));\n\t\t}\n\n\t\tfor done in wg {\n\t\t\tif !done.future().await {\n\t\t\t\treturn false; // Canceled\n\t\t\t}\n\t\t}\n\t\ttrue\n\t}\n\n\tpub fn preload_paged(&self, preloader: &'static Preloader, target: &yazi_fs::File) {\n\t\tlet mut ongoing = self.ongoing.lock();\n\t\tlet task = ongoing.add::<PreloadProg>(format!(\"Run preloader `{}`\", preloader.run.name));\n\n\t\tlet target = target.clone();\n\t\tself.preload.submit(PreloadIn { id: task.id, plugin: preloader, target });\n\t}\n\n\tpub fn prework_size(&self, targets: Vec<&UrlBuf>) {\n\t\tlet throttle = Arc::new(Throttle::new(targets.len(), Duration::from_millis(300)));\n\t\tlet mut ongoing = self.ongoing.lock();\n\n\t\tfor target in targets {\n\t\t\tlet task = ongoing.add::<SizeProg>(format!(\"Calculate the size of {}\", target.display()));\n\t\t\tlet target = target.clone();\n\t\t\tlet throttle = throttle.clone();\n\n\t\t\tself.size.submit(SizeIn { id: task.id, target, throttle }, NORMAL);\n\t\t}\n\t}\n\n\tpub fn process_open(&self, opt: ProcessOpenOpt) {\n\t\tlet name = {\n\t\t\tlet args = opt.args.iter().map(|a| a.display().to_string()).collect::<Vec<_>>().join(\" \");\n\t\t\tif args.is_empty() {\n\t\t\t\tformat!(\"Run {:?}\", opt.cmd)\n\t\t\t} else {\n\t\t\t\tformat!(\"Run {:?} with `{args}`\", opt.cmd)\n\t\t\t}\n\t\t};\n\n\t\tlet mut ongoing = self.ongoing.lock();\n\t\tlet task = if opt.block {\n\t\t\tongoing.add::<ProcessProgBlock>(name)\n\t\t} else if opt.orphan {\n\t\t\tongoing.add::<ProcessProgOrphan>(name)\n\t\t} else {\n\t\t\tongoing.add::<ProcessProgBg>(name)\n\t\t};\n\n\t\tif let Some(done) = opt.done {\n\t\t\ttask.done = done;\n\t\t}\n\n\t\tif opt.block {\n\t\t\tself\n\t\t\t\t.process\n\t\t\t\t.submit(ProcessInBlock { id: task.id, cwd: opt.cwd, cmd: opt.cmd, args: opt.args }, NORMAL);\n\t\t} else if opt.orphan {\n\t\t\tself.process.submit(\n\t\t\t\tProcessInOrphan { id: task.id, cwd: opt.cwd, cmd: opt.cmd, args: opt.args },\n\t\t\t\tNORMAL,\n\t\t\t);\n\t\t} else {\n\t\t\tself.process.submit(\n\t\t\t\tProcessInBg {\n\t\t\t\t\tid:   task.id,\n\t\t\t\t\tcwd:  opt.cwd,\n\t\t\t\t\tcmd:  opt.cmd,\n\t\t\t\t\targs: opt.args,\n\t\t\t\t\tdone: task.done.clone(),\n\t\t\t\t},\n\t\t\t\tNORMAL,\n\t\t\t);\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/src/size/in.rs",
    "content": "use std::sync::Arc;\n\nuse yazi_shared::{Id, Throttle, url::UrlBuf};\n\n#[derive(Debug)]\npub(crate) struct SizeIn {\n\tpub(crate) id:       Id,\n\tpub(crate) target:   UrlBuf,\n\tpub(crate) throttle: Arc<Throttle<(UrlBuf, u64)>>,\n}\n\nimpl SizeIn {\n\tpub(crate) fn id(&self) -> Id { self.id }\n}\n"
  },
  {
    "path": "yazi-scheduler/src/size/mod.rs",
    "content": "yazi_macro::mod_flat!(out progress r#in size);\n"
  },
  {
    "path": "yazi-scheduler/src/size/out.rs",
    "content": "use crate::{Task, TaskProg};\n\n#[derive(Debug)]\npub(crate) enum SizeOut {\n\tDone,\n}\n\nimpl SizeOut {\n\tpub(crate) fn reduce(self, task: &mut Task) {\n\t\tlet TaskProg::Size(prog) = &mut task.prog else { return };\n\t\tmatch self {\n\t\t\tSelf::Done => {\n\t\t\t\tprog.done = true;\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/src/size/progress.rs",
    "content": "use serde::Serialize;\nuse yazi_parser::app::TaskSummary;\n\n#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]\npub struct SizeProg {\n\tpub done: bool,\n}\n\nimpl From<SizeProg> for TaskSummary {\n\tfn from(value: SizeProg) -> Self {\n\t\tSelf {\n\t\t\ttotal:   1,\n\t\t\tsuccess: value.done as u32,\n\t\t\tfailed:  0,\n\t\t\tpercent: value.percent().map(Into::into),\n\t\t}\n\t}\n}\n\nimpl SizeProg {\n\tpub fn cooked(self) -> bool { self.done }\n\n\tpub fn running(self) -> bool { !self.done }\n\n\tpub fn success(self) -> bool { self.cooked() }\n\n\tpub fn failed(self) -> bool { false }\n\n\tpub fn cleaned(self) -> Option<bool> { None }\n\n\tpub fn percent(self) -> Option<f32> { None }\n}\n"
  },
  {
    "path": "yazi-scheduler/src/size/size.rs",
    "content": "use anyhow::Result;\nuse hashbrown::{HashMap, HashSet};\nuse parking_lot::RwLock;\nuse tokio::sync::mpsc;\nuse yazi_fs::FilesOp;\nuse yazi_shared::url::{UrlBuf, UrlLike};\nuse yazi_vfs::provider;\n\nuse super::SizeIn;\nuse crate::{TaskOp, TaskOps, size::SizeOut};\n\npub struct Size {\n\tops: TaskOps,\n\ttx:  async_priority_channel::Sender<SizeIn, u8>,\n\n\tpub sizing: RwLock<HashSet<UrlBuf>>,\n}\n\nimpl Size {\n\tpub(crate) fn new(\n\t\tops: &mpsc::UnboundedSender<TaskOp>,\n\t\ttx: async_priority_channel::Sender<SizeIn, u8>,\n\t) -> Self {\n\t\tSelf { ops: ops.into(), tx, sizing: Default::default() }\n\t}\n\n\tpub(crate) async fn size(&self, task: SizeIn) -> Result<(), SizeOut> {\n\t\tlet length = provider::calculate(&task.target).await.unwrap_or(0);\n\t\ttask.throttle.done((task.target, length), |buf| {\n\t\t\t{\n\t\t\t\tlet mut loading = self.sizing.write();\n\t\t\t\tfor (path, _) in &buf {\n\t\t\t\t\tloading.remove(path);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet parent = buf[0].0.parent().unwrap();\n\t\t\tFilesOp::Size(\n\t\t\t\tparent.into(),\n\t\t\t\tHashMap::from_iter(buf.into_iter().map(|(u, s)| (u.urn().into(), s))),\n\t\t\t)\n\t\t\t.emit();\n\t\t});\n\n\t\tOk(self.ops.out(task.id, SizeOut::Done))\n\t}\n}\n\nimpl Size {\n\t#[inline]\n\tpub(crate) fn submit(&self, r#in: impl Into<SizeIn>, priority: u8) {\n\t\t_ = self.tx.try_send(r#in.into(), priority);\n\t}\n}\n"
  },
  {
    "path": "yazi-scheduler/src/snap.rs",
    "content": "use serde::Serialize;\n\nuse crate::{Task, TaskProg};\n\n#[derive(Debug, PartialEq, Eq, Serialize)]\npub struct TaskSnap {\n\tpub name: String,\n\tpub prog: TaskProg,\n}\n\nimpl From<&Task> for TaskSnap {\n\tfn from(task: &Task) -> Self { Self { name: task.name.clone(), prog: task.prog } }\n}\n"
  },
  {
    "path": "yazi-scheduler/src/task.rs",
    "content": "use tokio::sync::mpsc;\nuse yazi_shared::{CompletionToken, Id};\n\nuse crate::{TaskProg, hook::HookIn};\n\n#[derive(Debug)]\npub struct Task {\n\tpub id:          Id,\n\tpub name:        String,\n\tpub(crate) prog: TaskProg,\n\tpub(crate) hook: Option<HookIn>,\n\tpub done:        CompletionToken,\n\n\tpub logs:   String,\n\tpub logger: Option<mpsc::UnboundedSender<String>>,\n}\n\nimpl Task {\n\tpub(super) fn new<T>(id: Id, name: String) -> Self\n\twhere\n\t\tT: Into<TaskProg> + Default,\n\t{\n\t\tSelf {\n\t\t\tid,\n\t\t\tname,\n\t\t\tprog: T::default().into(),\n\t\t\thook: None,\n\t\t\tdone: Default::default(),\n\n\t\t\tlogs: Default::default(),\n\t\t\tlogger: Default::default(),\n\t\t}\n\t}\n\n\tpub(crate) fn log(&mut self, line: String) {\n\t\tself.logs.push_str(&line);\n\t\tself.logs.push('\\n');\n\n\t\tif let Some(logger) = &self.logger {\n\t\t\tlogger.send(line).ok();\n\t\t}\n\t}\n\n\tpub(super) fn set_hook(&mut self, hook: impl Into<HookIn>) { self.hook = Some(hook.into()); }\n}\n"
  },
  {
    "path": "yazi-sftp/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-sftp\"\ndescription            = \"Yazi SFTP client\"\nauthors                = [ \"AspectUnk\", \"sxyazi <sxyazi@gmail.com>\" ]\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[dependencies]\nbitflags    = { workspace = true }\nparking_lot = { workspace = true }\nrussh       = { workspace = true }\nserde       = { workspace = true }\ntokio       = { workspace = true }\ntyped-path  = { workspace = true }\n"
  },
  {
    "path": "yazi-sftp/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": "yazi-sftp/README.md",
    "content": "# yazi-sftp\n\nA fork of [`russh-sftp`](https://github.com/AspectUnk/russh-sftp) used by Yazi, with some changes:\n\n- Supports paths containing invalid UTF-8\n- Supports retrieving file nlink, username, and group\n- Uses generic return parameters for a more idiomatic API, e.g.:\n  ```rust\n  let attrs: responses::Attrs = session.send(requests::Stat::new(path)).await?\n  ```\n- Reduced dependencies\n- Performance optimizations:\n  - Copy-on-write for all packets to avoid unnecessary memory allocation\n  - Packet lengths are precomputed to avoid secondary allocations\n  - Avoids cloning buffers in `AsyncRead` and `AsyncWrite` implementations\n"
  },
  {
    "path": "yazi-sftp/src/de.rs",
    "content": "use serde::{Deserializer as _, de::{EnumAccess, MapAccess, SeqAccess, VariantAccess, value::U32Deserializer}};\n\nuse crate::Error;\n\npub(super) struct Deserializer<'a> {\n\tinput: &'a [u8],\n}\n\nimpl<'a> Deserializer<'a> {\n\tpub(super) fn once<'de, T>(input: &'de [u8]) -> Result<T, Error>\n\twhere\n\t\tT: serde::Deserialize<'de>,\n\t{\n\t\tlet mut de = Deserializer { input };\n\t\tlet t = T::deserialize(&mut de)?;\n\t\tif !de.input.is_empty() {\n\t\t\treturn Err(Error::serde(\"trailing bytes\"));\n\t\t}\n\t\tOk(t)\n\t}\n}\n\nimpl<'de> serde::Deserializer<'de> for &mut Deserializer<'de> {\n\ttype Error = Error;\n\n\tfn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tlet len = self.input.len();\n\t\tvisitor.visit_seq(SeqDeserializer { de: self, remaining: len })\n\t}\n\n\tfn deserialize_bool<V>(self, _visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tErr(Error::serde(\"bool not supported\"))\n\t}\n\n\tfn deserialize_i8<V>(self, _visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tErr(Error::serde(\"i8 not supported\"))\n\t}\n\n\tfn deserialize_i16<V>(self, _visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tErr(Error::serde(\"i16 not supported\"))\n\t}\n\n\tfn deserialize_i32<V>(self, _visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tErr(Error::serde(\"i32 not supported\"))\n\t}\n\n\tfn deserialize_i64<V>(self, _visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tErr(Error::serde(\"i64 not supported\"))\n\t}\n\n\tfn deserialize_u8<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tlet b = *self.input.first().ok_or(Error::serde(\"u8 not enough\"))?;\n\n\t\tself.input = &self.input[1..];\n\t\tvisitor.visit_u8(b)\n\t}\n\n\tfn deserialize_u16<V>(self, _visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tErr(Error::serde(\"u16 not supported\"))\n\t}\n\n\tfn deserialize_u32<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tlet b: [u8; 4] = self.input.get(..4).ok_or(Error::serde(\"u32 not enough\"))?.try_into().unwrap();\n\n\t\tself.input = &self.input[4..];\n\t\tvisitor.visit_u32(u32::from_be_bytes(b))\n\t}\n\n\tfn deserialize_u64<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tlet b: [u8; 8] = self.input.get(..8).ok_or(Error::serde(\"u64 not enough\"))?.try_into().unwrap();\n\n\t\tself.input = &self.input[8..];\n\t\tvisitor.visit_u64(u64::from_be_bytes(b))\n\t}\n\n\tfn deserialize_f32<V>(self, _visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tErr(Error::serde(\"f32 not supported\"))\n\t}\n\n\tfn deserialize_f64<V>(self, _visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tErr(Error::serde(\"f64 not supported\"))\n\t}\n\n\tfn deserialize_char<V>(self, _visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tErr(Error::serde(\"char not supported\"))\n\t}\n\n\tfn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tlet len: [u8; 4] =\n\t\t\tself.input.get(..4).ok_or(Error::serde(\"invalid string length\"))?.try_into().unwrap();\n\t\tlet len = u32::from_be_bytes(len) as usize;\n\n\t\tself.input = &self.input[4..];\n\t\tlet b = self.input.get(..len).ok_or(Error::serde(\"string not enough\"))?;\n\n\t\tself.input = &self.input[len..];\n\t\tvisitor.visit_str(str::from_utf8(b).map_err(|e| Error::serde(e.to_string()))?)\n\t}\n\n\tfn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tself.deserialize_str(visitor)\n\t}\n\n\tfn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tlet len: [u8; 4] =\n\t\t\tself.input.get(..4).ok_or(Error::serde(\"invalid bytes length\"))?.try_into().unwrap();\n\t\tlet len = u32::from_be_bytes(len) as usize;\n\t\tlet b = self.input.get(4..4 + len).ok_or(Error::serde(\"bytes not enough\"))?;\n\n\t\tself.input = &self.input[4 + len..];\n\t\tvisitor.visit_bytes(b)\n\t}\n\n\tfn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tself.deserialize_bytes(visitor)\n\t}\n\n\tfn deserialize_option<V>(self, _visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tErr(Error::serde(\"option not supported\"))\n\t}\n\n\tfn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tvisitor.visit_unit()\n\t}\n\n\tfn deserialize_unit_struct<V>(\n\t\tself,\n\t\t_name: &'static str,\n\t\tvisitor: V,\n\t) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tvisitor.visit_unit()\n\t}\n\n\tfn deserialize_newtype_struct<V>(\n\t\tself,\n\t\t_name: &'static str,\n\t\tvisitor: V,\n\t) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tvisitor.visit_newtype_struct(self)\n\t}\n\n\tfn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tlet len: [u8; 4] =\n\t\t\tself.input.get(..4).ok_or(Error::serde(\"invalid seq length\"))?.try_into().unwrap();\n\n\t\tself.input = &self.input[4..];\n\t\tvisitor.visit_seq(SeqDeserializer { de: self, remaining: u32::from_be_bytes(len) as _ })\n\t}\n\n\tfn deserialize_tuple<V>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tvisitor.visit_seq(SeqDeserializer { de: self, remaining: len })\n\t}\n\n\tfn deserialize_tuple_struct<V>(\n\t\tself,\n\t\t_name: &'static str,\n\t\tlen: usize,\n\t\tvisitor: V,\n\t) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tself.deserialize_tuple(len, visitor)\n\t}\n\n\tfn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tvisitor.visit_map(MapDeserializer { de: self })\n\t}\n\n\tfn deserialize_struct<V>(\n\t\tself,\n\t\t_name: &'static str,\n\t\tfields: &'static [&'static str],\n\t\tvisitor: V,\n\t) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tself.deserialize_tuple(fields.len(), visitor)\n\t}\n\n\tfn deserialize_enum<V>(\n\t\tself,\n\t\t_name: &'static str,\n\t\t_variants: &'static [&'static str],\n\t\tvisitor: V,\n\t) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tvisitor.visit_enum(self)\n\t}\n\n\tfn deserialize_identifier<V>(self, _visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tErr(Error::serde(\"identifier not supported\"))\n\t}\n\n\tfn deserialize_ignored_any<V>(self, _visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tErr(Error::serde(\"ignored any not supported\"))\n\t}\n\n\tfn is_human_readable(&self) -> bool { false }\n}\n\nstruct SeqDeserializer<'a, 'de: 'a> {\n\tde:        &'a mut Deserializer<'de>,\n\tremaining: usize,\n}\n\nimpl<'de> SeqAccess<'de> for SeqDeserializer<'_, 'de> {\n\ttype Error = Error;\n\n\tfn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error>\n\twhere\n\t\tT: serde::de::DeserializeSeed<'de>,\n\t{\n\t\tif self.remaining == 0 {\n\t\t\tOk(None)\n\t\t} else {\n\t\t\tself.remaining -= 1;\n\t\t\tseed.deserialize(&mut *self.de).map(Some)\n\t\t}\n\t}\n\n\tfn size_hint(&self) -> Option<usize> { Some(self.remaining) }\n}\n\nstruct MapDeserializer<'a, 'de: 'a> {\n\tde: &'a mut Deserializer<'de>,\n}\n\nimpl<'de> MapAccess<'de> for MapDeserializer<'_, 'de> {\n\ttype Error = Error;\n\n\tfn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>\n\twhere\n\t\tK: serde::de::DeserializeSeed<'de>,\n\t{\n\t\tif self.de.input.is_empty() { Ok(None) } else { seed.deserialize(&mut *self.de).map(Some) }\n\t}\n\n\tfn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::DeserializeSeed<'de>,\n\t{\n\t\tseed.deserialize(&mut *self.de)\n\t}\n}\n\nimpl<'de> VariantAccess<'de> for &mut Deserializer<'de> {\n\ttype Error = Error;\n\n\tfn unit_variant(self) -> Result<(), Self::Error> { Ok(()) }\n\n\tfn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value, Self::Error>\n\twhere\n\t\tT: serde::de::DeserializeSeed<'de>,\n\t{\n\t\tseed.deserialize(self)\n\t}\n\n\tfn tuple_variant<V>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tuse serde::Deserializer;\n\t\tself.deserialize_tuple(len, visitor)\n\t}\n\n\tfn struct_variant<V>(\n\t\tself,\n\t\tfields: &'static [&'static str],\n\t\tvisitor: V,\n\t) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tself.deserialize_tuple(fields.len(), visitor)\n\t}\n}\n\nimpl<'de> EnumAccess<'de> for &mut Deserializer<'de> {\n\ttype Error = Error;\n\ttype Variant = Self;\n\n\tfn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error>\n\twhere\n\t\tV: serde::de::DeserializeSeed<'de>,\n\t{\n\t\tlet b: [u8; 4] =\n\t\t\tself.input.get(..4).ok_or(Error::serde(\"enum not enough\"))?.try_into().unwrap();\n\t\tself.input = &self.input[4..];\n\n\t\tlet de = U32Deserializer::<Self::Error>::new(u32::from_be_bytes(b));\n\t\tOk((seed.deserialize(de)?, self))\n\t}\n}\n"
  },
  {
    "path": "yazi-sftp/src/error.rs",
    "content": "use std::borrow::Cow;\n\nuse crate::responses;\n\n#[derive(Debug)]\npub enum Error {\n\tIO(std::io::Error),\n\tSerde(Cow<'static, str>),\n\tStatus(responses::Status),\n\tPacket(&'static str),\n\tTimeout,\n\tUnsupported,\n\tCustom(Cow<'static, str>),\n}\n\nimpl Error {\n\tpub(super) fn serde(s: impl Into<Cow<'static, str>>) -> Self { Self::Serde(s.into()) }\n\n\tpub(super) fn custom(s: impl Into<Cow<'static, str>>) -> Self { Self::Custom(s.into()) }\n}\n\nimpl serde::ser::Error for Error {\n\tfn custom<T: std::fmt::Display>(msg: T) -> Self { Self::serde(msg.to_string()) }\n}\n\nimpl serde::de::Error for Error {\n\tfn custom<T: std::fmt::Display>(msg: T) -> Self { Self::serde(msg.to_string()) }\n}\n\nimpl From<Error> for std::io::Error {\n\tfn from(err: Error) -> Self {\n\t\tmatch err {\n\t\t\tError::IO(e) => e,\n\t\t\tError::Serde(e) => Self::new(std::io::ErrorKind::InvalidData, e),\n\t\t\tError::Status(status) => match status.code {\n\t\t\t\tresponses::StatusCode::Ok => Self::other(\"unexpected OK\"),\n\t\t\t\tresponses::StatusCode::Eof => Self::from(std::io::ErrorKind::UnexpectedEof),\n\t\t\t\tresponses::StatusCode::NoSuchFile => Self::from(std::io::ErrorKind::NotFound),\n\t\t\t\tresponses::StatusCode::PermissionDenied => Self::from(std::io::ErrorKind::PermissionDenied),\n\t\t\t\tresponses::StatusCode::Failure => Self::from(std::io::ErrorKind::Other),\n\t\t\t\tresponses::StatusCode::BadMessage => Self::from(std::io::ErrorKind::InvalidData),\n\t\t\t\tresponses::StatusCode::NoConnection => Self::from(std::io::ErrorKind::NotConnected),\n\t\t\t\tresponses::StatusCode::ConnectionLost => Self::from(std::io::ErrorKind::ConnectionReset),\n\t\t\t\tresponses::StatusCode::OpUnsupported => Self::from(std::io::ErrorKind::Unsupported),\n\t\t\t\tresponses::StatusCode::InvalidHandle => Self::from(std::io::ErrorKind::InvalidInput),\n\t\t\t\tresponses::StatusCode::NoSuchPath => Self::from(std::io::ErrorKind::NotFound),\n\t\t\t\tresponses::StatusCode::FileAlreadyExists => Self::from(std::io::ErrorKind::AlreadyExists),\n\t\t\t\tresponses::StatusCode::WriteProtect => Self::from(std::io::ErrorKind::PermissionDenied),\n\t\t\t\tresponses::StatusCode::NoMedia => Self::from(std::io::ErrorKind::Other),\n\t\t\t},\n\t\t\tError::Packet(e) => Self::new(std::io::ErrorKind::InvalidData, e),\n\t\t\tError::Timeout => Self::from(std::io::ErrorKind::TimedOut),\n\t\t\tError::Unsupported => Self::from(std::io::ErrorKind::Unsupported),\n\t\t\tError::Custom(_) => Self::other(err),\n\t\t}\n\t}\n}\n\nimpl<T> From<tokio::sync::mpsc::error::SendError<T>> for Error {\n\tfn from(_: tokio::sync::mpsc::error::SendError<T>) -> Self { Self::custom(\"channel closed\") }\n}\n\nimpl From<tokio::sync::oneshot::error::RecvError> for Error {\n\tfn from(_: tokio::sync::oneshot::error::RecvError) -> Self { Self::custom(\"channel closed\") }\n}\n\nimpl From<tokio::time::error::Elapsed> for Error {\n\tfn from(_: tokio::time::error::Elapsed) -> Self { Self::Timeout }\n}\n\nimpl std::error::Error for Error {}\n\nimpl std::fmt::Display for Error {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tmatch self {\n\t\t\tSelf::IO(e) => write!(f, \"IO error: {e}\"),\n\t\t\tSelf::Serde(s) => write!(f, \"Serde error: {s}\"),\n\t\t\tSelf::Status(s) => write!(f, \"Status error: {s:?}\"),\n\t\t\tSelf::Packet(s) => write!(f, \"Unexpected packet: {s}\"),\n\t\t\tSelf::Timeout => write!(f, \"Operation timed out\"),\n\t\t\tSelf::Unsupported => write!(f, \"Operation not supported\"),\n\t\t\tSelf::Custom(s) => write!(f, \"{s}\"),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-sftp/src/fs/attrs.rs",
    "content": "use std::{collections::HashMap, fmt};\n\nuse serde::{Deserialize, Deserializer, Serialize, de::Visitor, ser::SerializeStruct};\n\n#[derive(Clone, Debug, Default, Eq, PartialEq)]\npub struct Attrs {\n\tpub size:     Option<u64>,\n\tpub uid:      Option<u32>,\n\tpub gid:      Option<u32>,\n\tpub perm:     Option<u32>,\n\tpub atime:    Option<u32>,\n\tpub mtime:    Option<u32>,\n\tpub extended: HashMap<String, String>,\n}\n\nimpl Attrs {\n\tpub fn is_empty(&self) -> bool { *self == Self::default() }\n\n\tpub fn len(&self) -> usize {\n\t\tlet mut len = 4;\n\t\tif let Some(size) = self.size {\n\t\t\tlen += size_of_val(&size);\n\t\t}\n\t\tif self.uid.is_some() || self.gid.is_some() {\n\t\t\tlen += 4 + 4;\n\t\t}\n\t\tif let Some(perm) = self.perm {\n\t\t\tlen += size_of_val(&perm);\n\t\t}\n\t\tif self.atime.is_some() || self.mtime.is_some() {\n\t\t\tlen += 4 + 4;\n\t\t}\n\t\tif !self.extended.is_empty() {\n\t\t\tlen += 4 + self.extended.iter().map(|(k, v)| 4 + k.len() + 4 + v.len()).sum::<usize>();\n\t\t}\n\t\tlen\n\t}\n}\n\nimpl Serialize for Attrs {\n\tfn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n\twhere\n\t\tS: serde::Serializer,\n\t{\n\t\tlet mut flags: u32 = 0;\n\t\tlet mut len = 1;\n\n\t\tif self.size.is_some() {\n\t\t\tflags |= 0x1;\n\t\t\tlen += 1;\n\t\t}\n\t\tif self.uid.is_some() || self.gid.is_some() {\n\t\t\tflags |= 0x2;\n\t\t\tlen += 2;\n\t\t}\n\t\tif self.perm.is_some() {\n\t\t\tflags |= 0x4;\n\t\t\tlen += 1;\n\t\t}\n\t\tif self.atime.is_some() || self.mtime.is_some() {\n\t\t\tflags |= 0x8;\n\t\t\tlen += 2;\n\t\t}\n\t\tif !self.extended.is_empty() {\n\t\t\tflags |= 0x80000000;\n\t\t\tlen += 1 + self.extended.len() * 2;\n\t\t}\n\n\t\tlet mut seq = serializer.serialize_struct(\"Attrs\", len)?;\n\t\tseq.serialize_field(\"flags\", &flags)?;\n\t\tif let Some(size) = self.size {\n\t\t\tseq.serialize_field(\"size\", &size)?;\n\t\t}\n\t\tif self.uid.is_some() || self.gid.is_some() {\n\t\t\tseq.serialize_field(\"uid\", &self.uid.unwrap_or(0))?;\n\t\t\tseq.serialize_field(\"gid\", &self.gid.unwrap_or(0))?;\n\t\t}\n\t\tif let Some(perm) = self.perm {\n\t\t\tseq.serialize_field(\"perm\", &perm)?;\n\t\t}\n\t\tif self.atime.is_some() || self.mtime.is_some() {\n\t\t\tseq.serialize_field(\"atime\", &self.atime.unwrap_or(0))?;\n\t\t\tseq.serialize_field(\"mtime\", &self.mtime.unwrap_or(0))?;\n\t\t}\n\t\tif !self.extended.is_empty() {\n\t\t\tlet count = self.extended.len() as u32;\n\t\t\tseq.serialize_field(\"extended_count\", &count)?;\n\t\t\tfor (k, v) in self.extended.iter().take(count as usize) {\n\t\t\t\tseq.serialize_field(\"extended_key\", k)?;\n\t\t\t\tseq.serialize_field(\"extended_value\", v)?;\n\t\t\t}\n\t\t}\n\t\tseq.end()\n\t}\n}\n\nimpl<'de> Deserialize<'de> for Attrs {\n\tfn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n\twhere\n\t\tD: Deserializer<'de>,\n\t{\n\t\tstruct AttrsVisitor;\n\n\t\timpl<'de> Visitor<'de> for AttrsVisitor {\n\t\t\ttype Value = Attrs;\n\n\t\t\tfn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n\t\t\t\tformatter.write_str(\"attributes\")\n\t\t\t}\n\n\t\t\tfn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>\n\t\t\twhere\n\t\t\t\tA: serde::de::SeqAccess<'de>,\n\t\t\t{\n\t\t\t\tlet b = seq.next_element::<u32>()?.unwrap_or(0);\n\t\t\t\tlet mut attrs = Attrs::default();\n\n\t\t\t\tif b & 0x1 != 0 {\n\t\t\t\t\tattrs.size = seq.next_element()?;\n\t\t\t\t}\n\t\t\t\tif b & 0x2 != 0 {\n\t\t\t\t\tattrs.uid = seq.next_element()?;\n\t\t\t\t\tattrs.gid = seq.next_element()?;\n\t\t\t\t}\n\t\t\t\tif b & 0x4 != 0 {\n\t\t\t\t\tattrs.perm = seq.next_element()?;\n\t\t\t\t}\n\t\t\t\tif b & 0x8 != 0 {\n\t\t\t\t\tattrs.atime = seq.next_element()?;\n\t\t\t\t\tattrs.mtime = seq.next_element()?;\n\t\t\t\t}\n\t\t\t\tif b & 0x80000000 != 0 {\n\t\t\t\t\tlet count: u32 = seq.next_element()?.unwrap_or(0);\n\t\t\t\t\tfor _ in 0..count {\n\t\t\t\t\t\tattrs.extended.insert(\n\t\t\t\t\t\t\tseq.next_element()?.unwrap_or_default(),\n\t\t\t\t\t\t\tseq.next_element()?.unwrap_or_default(),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tOk(attrs)\n\t\t\t}\n\t\t}\n\n\t\tdeserializer.deserialize_any(AttrsVisitor)\n\t}\n}\n"
  },
  {
    "path": "yazi-sftp/src/fs/dir_entry.rs",
    "content": "use std::sync::Arc;\n\nuse typed_path::UnixPathBuf;\n\nuse crate::fs::Attrs;\n\npub struct DirEntry {\n\tpub(super) dir:       Arc<typed_path::UnixPathBuf>,\n\tpub(super) name:      Vec<u8>,\n\tpub(super) long_name: Vec<u8>,\n\tpub(super) attrs:     Attrs,\n}\n\nimpl DirEntry {\n\t#[must_use]\n\tpub fn path(&self) -> UnixPathBuf { self.dir.join(&self.name) }\n\n\tpub fn name(&self) -> &[u8] { &self.name }\n\n\tpub fn long_name(&self) -> &[u8] { &self.long_name }\n\n\tpub fn attrs(&self) -> &Attrs { &self.attrs }\n\n\tpub fn nlink(&self) -> Option<u64> { str::from_utf8(self.long_name_field(1)?).ok()?.parse().ok() }\n\n\tpub fn user(&self) -> Option<&[u8]> { self.long_name_field(2) }\n\n\tpub fn group(&self) -> Option<&[u8]> { self.long_name_field(3) }\n\n\tfn long_name_field(&self, n: usize) -> Option<&[u8]> {\n\t\tself.long_name.split(|b| b.is_ascii_whitespace()).filter(|s| !s.is_empty()).nth(n)\n\t}\n}\n"
  },
  {
    "path": "yazi-sftp/src/fs/file.rs",
    "content": "use std::{io::{self, SeekFrom}, pin::Pin, sync::Arc, task::{Context, Poll, ready}, time::Duration};\n\nuse tokio::{io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf}, time::{Timeout, timeout}};\n\nuse crate::{Error, Operator, Packet, Receiver, Session, fs::Attrs, requests};\n\npub struct File {\n\tsession: Arc<Session>,\n\n\thandle: String,\n\tcursor: u64,\n\tclosed: bool,\n\n\tclose_rx: Option<Timeout<Receiver>>,\n\tread_rx:  Option<Receiver>,\n\tseek_rx:  Option<SeekState>,\n\twrite_rx: Option<(Receiver, usize)>,\n\tflush_rx: Option<Timeout<Receiver>>,\n}\n\nenum SeekState {\n\tNonBlocking(u64),\n\tBlocking(i64, Timeout<Receiver>),\n}\n\nimpl Unpin for File {}\n\nimpl Drop for File {\n\tfn drop(&mut self) {\n\t\tif !self.closed {\n\t\t\tOperator::from(&self.session).close(&self.handle).ok();\n\t\t}\n\t}\n}\n\nimpl File {\n\tpub(crate) fn new(session: &Arc<Session>, handle: impl Into<String>) -> Self {\n\t\tSelf {\n\t\t\tsession: session.clone(),\n\n\t\t\thandle: handle.into(),\n\t\t\tclosed: false,\n\t\t\tcursor: 0,\n\n\t\t\tclose_rx: None,\n\t\t\tread_rx:  None,\n\t\t\tseek_rx:  None,\n\t\t\twrite_rx: None,\n\t\t\tflush_rx: None,\n\t\t}\n\t}\n\n\tpub async fn fstat(&self) -> Result<Attrs, Error> {\n\t\tOperator::from(&self.session).fstat(&self.handle).await\n\t}\n\n\tpub async fn fsetstat(&self, attrs: &Attrs) -> Result<(), Error> {\n\t\tOperator::from(&self.session).fsetstat(&self.handle, attrs).await\n\t}\n}\n\nimpl AsyncRead for File {\n\tfn poll_read(\n\t\tself: Pin<&mut Self>,\n\t\tcx: &mut Context<'_>,\n\t\tbuf: &mut ReadBuf<'_>,\n\t) -> Poll<io::Result<()>> {\n\t\tlet me = unsafe { self.get_unchecked_mut() };\n\n\t\tif me.read_rx.is_none() {\n\t\t\tlet max = buf.remaining().min(261120) as u32;\n\t\t\tme.read_rx = Some(Operator::from(&me.session).read(&me.handle, me.cursor, max)?);\n\t\t}\n\n\t\tlet result = ready!(Pin::new(me.read_rx.as_mut().unwrap()).poll(cx));\n\t\tme.read_rx = None;\n\n\t\tPoll::Ready(match result {\n\t\t\tOk(Packet::Data(data)) => {\n\t\t\t\tlet len = buf.remaining().min(data.data.len());\n\t\t\t\tme.cursor += len as u64;\n\t\t\t\tbuf.put_slice(&data.data[..len]);\n\t\t\t\tOk(())\n\t\t\t}\n\t\t\tOk(Packet::Status(status)) if status.is_eof() => Ok(()),\n\t\t\tOk(Packet::Status(status)) => Err(Error::Status(status).into()),\n\t\t\tOk(_) => Err(Error::Packet(\"not a Data or Status\").into()),\n\t\t\tErr(e) => Err(Error::from(e).into()),\n\t\t})\n\t}\n}\n\nimpl AsyncSeek for File {\n\tfn start_seek(mut self: Pin<&mut Self>, position: io::SeekFrom) -> io::Result<()> {\n\t\tif self.seek_rx.is_some() {\n\t\t\treturn Err(io::Error::other(\n\t\t\t\t\"other file operation is pending, call poll_complete before start_seek\",\n\t\t\t));\n\t\t}\n\n\t\tself.seek_rx = Some(match position {\n\t\t\tSeekFrom::Start(n) => SeekState::NonBlocking(n),\n\t\t\tSeekFrom::Current(n) => self\n\t\t\t\t.cursor\n\t\t\t\t.checked_add_signed(n)\n\t\t\t\t.map(SeekState::NonBlocking)\n\t\t\t\t.ok_or_else(|| io::Error::other(\"seeking to a negative or overflowed position\"))?,\n\t\t\tSeekFrom::End(n) => SeekState::Blocking(\n\t\t\t\tn,\n\t\t\t\ttimeout(\n\t\t\t\t\tDuration::from_secs(45),\n\t\t\t\t\tself.session.send_sync(requests::Fstat::new(&self.handle))?,\n\t\t\t\t),\n\t\t\t),\n\t\t});\n\n\t\tOk(())\n\t}\n\n\tfn poll_complete(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {\n\t\tlet me = unsafe { self.get_unchecked_mut() };\n\n\t\tlet Some(state) = &mut me.seek_rx else {\n\t\t\treturn Poll::Ready(Ok(me.cursor));\n\t\t};\n\n\t\tfn imp(cx: &mut Context<'_>, state: &mut SeekState) -> Poll<io::Result<u64>> {\n\t\t\tuse Poll::Ready;\n\t\t\tlet (n, rx) = match state {\n\t\t\t\tSeekState::NonBlocking(n) => return Ready(Ok(*n)),\n\t\t\t\tSeekState::Blocking(n, rx) => (n, rx),\n\t\t\t};\n\n\t\t\tlet Ok(result) = ready!(unsafe { Pin::new_unchecked(rx) }.poll(cx)) else {\n\t\t\t\treturn Ready(Err(Error::Timeout.into()));\n\t\t\t};\n\n\t\t\tlet packet = match result {\n\t\t\t\tOk(Packet::Attrs(packet)) => packet,\n\t\t\t\tOk(_) => return Ready(Err(Error::Packet(\"not an Attrs\").into())),\n\t\t\t\tErr(e) => return Ready(Err(Error::from(e).into())),\n\t\t\t};\n\n\t\t\tlet Some(size) = packet.attrs.size else {\n\t\t\t\treturn Ready(Err(io::Error::other(\"could not get file size for seeking from end\")));\n\t\t\t};\n\n\t\t\tReady(\n\t\t\t\tsize\n\t\t\t\t\t.checked_add_signed(*n)\n\t\t\t\t\t.ok_or_else(|| io::Error::other(\"seeking to a negative or overflowed position\")),\n\t\t\t)\n\t\t}\n\n\t\tlet result = ready!(imp(cx, state));\n\t\tif let Ok(n) = result {\n\t\t\tme.cursor = n;\n\t\t}\n\n\t\tme.seek_rx = None;\n\t\tPoll::Ready(result)\n\t}\n}\n\nimpl AsyncWrite for File {\n\tfn poll_write(\n\t\tself: Pin<&mut Self>,\n\t\tcx: &mut Context<'_>,\n\t\tbuf: &[u8],\n\t) -> Poll<Result<usize, io::Error>> {\n\t\tlet me = unsafe { self.get_unchecked_mut() };\n\n\t\tlet (rx, len) = match &mut me.write_rx {\n\t\t\tSome((rx, len)) => (rx, *len),\n\t\t\tNone => {\n\t\t\t\tlet max = buf.len().min(261120);\n\t\t\t\tlet rx = Operator::from(&me.session).write(&me.handle, me.cursor, &buf[..max])?;\n\t\t\t\t(&mut me.write_rx.get_or_insert((rx, max)).0, max)\n\t\t\t}\n\t\t};\n\n\t\tlet result = ready!(Pin::new(rx).poll(cx));\n\t\tme.write_rx = None;\n\n\t\tPoll::Ready(match result {\n\t\t\tOk(Packet::Status(status)) if status.is_ok() => {\n\t\t\t\tme.cursor += len as u64;\n\t\t\t\tOk(len)\n\t\t\t}\n\t\t\tOk(Packet::Status(status)) => Err(Error::Status(status).into()),\n\t\t\tOk(_) => Err(Error::Packet(\"not a Status\").into()),\n\t\t\tErr(e) => Err(Error::from(e).into()),\n\t\t})\n\t}\n\n\tfn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {\n\t\tlet me = unsafe { self.get_unchecked_mut() };\n\n\t\tif me.flush_rx.is_none() {\n\t\t\tmatch Operator::from(&me.session).fsync(&me.handle) {\n\t\t\t\tOk(rx) => me.flush_rx = Some(timeout(Duration::from_secs(45), rx)),\n\t\t\t\tErr(Error::Unsupported) => return Poll::Ready(Ok(())),\n\t\t\t\tErr(e) => Err(e)?,\n\t\t\t}\n\t\t}\n\n\t\tlet rx = unsafe { Pin::new_unchecked(me.flush_rx.as_mut().unwrap()) };\n\t\tlet result = ready!(rx.poll(cx));\n\t\tme.flush_rx = None;\n\n\t\tlet Ok(result) = result else {\n\t\t\treturn Poll::Ready(Err(Error::Timeout.into()));\n\t\t};\n\n\t\tPoll::Ready(match result {\n\t\t\tOk(Packet::Status(status)) if status.is_ok() => Ok(()),\n\t\t\tOk(Packet::Status(status)) => Err(Error::Status(status).into()),\n\t\t\tOk(_) => Err(Error::Packet(\"not a Status\").into()),\n\t\t\tErr(e) => Err(Error::from(e).into()),\n\t\t})\n\t}\n\n\tfn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {\n\t\tlet me = unsafe { self.get_unchecked_mut() };\n\n\t\tif me.close_rx.is_none() {\n\t\t\tme.close_rx =\n\t\t\t\tSome(timeout(Duration::from_secs(10), Operator::from(&me.session).close(&me.handle)?));\n\t\t}\n\n\t\tlet rx = unsafe { Pin::new_unchecked(me.close_rx.as_mut().unwrap()) };\n\t\tlet result = ready!(rx.poll(cx));\n\t\tme.close_rx = None;\n\n\t\tlet Ok(result) = result else {\n\t\t\treturn Poll::Ready(Err(Error::Timeout.into()));\n\t\t};\n\n\t\tPoll::Ready(match result {\n\t\t\tOk(Packet::Status(status)) if status.is_ok() => {\n\t\t\t\tme.closed = true;\n\t\t\t\tOk(())\n\t\t\t}\n\t\t\tOk(Packet::Status(status)) => Err(Error::Status(status).into()),\n\t\t\tOk(_) => Err(Error::Packet(\"not a Status\").into()),\n\t\t\tErr(e) => Err(Error::from(e).into()),\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-sftp/src/fs/flags.rs",
    "content": "use bitflags::bitflags;\nuse serde::{Deserialize, Serialize};\n\n#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)]\npub struct Flags(u32);\n\nbitflags! {\n\timpl Flags: u32 {\n\t\tconst READ     = 0b000001;\n\t\tconst WRITE    = 0b000010;\n\t\tconst APPEND   = 0b000100;\n\t\tconst CREATE   = 0b001000;\n\t\tconst TRUNCATE = 0b010000;\n\t\tconst EXCLUDE  = 0b100000;\n\t}\n}\n"
  },
  {
    "path": "yazi-sftp/src/fs/mod.rs",
    "content": "mod attrs;\nmod dir_entry;\nmod file;\nmod flags;\nmod read_dir;\n\npub use attrs::*;\npub use dir_entry::*;\npub use file::*;\npub use flags::*;\npub use read_dir::*;\n"
  },
  {
    "path": "yazi-sftp/src/fs/read_dir.rs",
    "content": "use std::{mem, sync::Arc, time::Duration};\n\nuse crate::{Error, Operator, Session, SftpPath, fs::DirEntry, requests, responses};\n\npub struct ReadDir {\n\tsession: Arc<Session>,\n\tdir:     Arc<typed_path::UnixPathBuf>,\n\thandle:  String,\n\n\tname:   responses::Name<'static>,\n\tcursor: usize,\n\tdone:   bool,\n}\n\nimpl Drop for ReadDir {\n\tfn drop(&mut self) { Operator::from(&self.session).close(&self.handle).ok(); }\n}\n\nimpl ReadDir {\n\tpub(crate) fn new(session: &Arc<Session>, dir: SftpPath, handle: String) -> Self {\n\t\tSelf {\n\t\t\tsession: session.clone(),\n\t\t\tdir: Arc::new(dir.into_owned()),\n\t\t\thandle,\n\n\t\t\tname: Default::default(),\n\t\t\tcursor: 0,\n\t\t\tdone: false,\n\t\t}\n\t}\n\n\tpub async fn next(&mut self) -> Result<Option<DirEntry>, Error> {\n\t\tloop {\n\t\t\tself.fetch().await?;\n\t\t\tlet Some(item) = self.name.items.get_mut(self.cursor).map(mem::take) else {\n\t\t\t\treturn Ok(None);\n\t\t\t};\n\n\t\t\tself.cursor += 1;\n\t\t\tif &*item.name != b\".\" && &*item.name != b\"..\" {\n\t\t\t\treturn Ok(Some(DirEntry {\n\t\t\t\t\tdir:       self.dir.clone(),\n\t\t\t\t\tname:      item.name.into_owned(),\n\t\t\t\t\tlong_name: item.long_name.into_owned(),\n\t\t\t\t\tattrs:     item.attrs,\n\t\t\t\t}));\n\t\t\t}\n\t\t}\n\t}\n\n\tasync fn fetch(&mut self) -> Result<(), Error> {\n\t\tif self.cursor < self.name.items.len() || self.done {\n\t\t\treturn Ok(());\n\t\t}\n\n\t\tlet result = self\n\t\t\t.session\n\t\t\t.send_with_timeout(requests::ReadDir::new(&self.handle), Duration::from_mins(45))\n\t\t\t.await;\n\n\t\tself.name = match result {\n\t\t\tOk(resp) => resp,\n\t\t\tErr(Error::Status(status)) if status.is_eof() => {\n\t\t\t\tself.done = true;\n\t\t\t\treturn Ok(());\n\t\t\t}\n\t\t\tErr(e) => return Err(e),\n\t\t};\n\n\t\tself.cursor = 0;\n\t\tOk(())\n\t}\n}\n"
  },
  {
    "path": "yazi-sftp/src/id.rs",
    "content": "use std::sync::atomic::{AtomicU32, Ordering};\n\npub(super) struct Id(AtomicU32);\n\nimpl Default for Id {\n\tfn default() -> Self { Self(AtomicU32::new(1)) }\n}\n\nimpl Id {\n\tpub(super) fn next(&self) -> u32 {\n\t\tloop {\n\t\t\tlet old = self.0.fetch_add(1, Ordering::Relaxed);\n\t\t\tif old != 0 {\n\t\t\t\treturn old;\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-sftp/src/lib.rs",
    "content": "pub mod fs;\npub mod requests;\npub mod responses;\n\nmod de;\nmod error;\nmod id;\nmod macros;\nmod operator;\nmod packet;\nmod path;\nmod receiver;\nmod ser;\nmod session;\n\npub(crate) use de::*;\npub use error::*;\npub(crate) use id::*;\npub use operator::*;\npub use packet::*;\npub use path::*;\npub use receiver::*;\npub(crate) use ser::*;\npub use session::*;\n"
  },
  {
    "path": "yazi-sftp/src/macros.rs",
    "content": "#[macro_export]\nmacro_rules! impl_from_packet {\n\t($($variant:ident($type:ty)),* $(,)?) => {\n\t\t$(\n\t\t\timpl<'a> From<$type> for $crate::Packet<'a> {\n\t\t\t\tfn from(value: $type) -> Self {\n\t\t\t\t\tSelf::$variant(value)\n\t\t\t\t}\n\t\t\t}\n\t\t)*\n\t};\n}\n\n#[macro_export]\nmacro_rules! impl_try_from_packet {\n\t($($variant:ident($type:ty)),* $(,)?) => {\n\t\t$(\n\t\t\timpl<'a> TryFrom<$crate::Packet<'a>> for $type {\n\t\t\t\ttype Error = $crate::Error;\n\n\t\t\t\tfn try_from(value: $crate::Packet<'a>) -> Result<Self, Self::Error> {\n\t\t\t\t\tmatch value {\n\t\t\t\t\t\t$crate::Packet::$variant(v) => Ok(v),\n\t\t\t\t\t\t_ => Err($crate::Error::Packet(concat!(\"not a \", stringify!($variant)))),\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t)*\n\t};\n}\n"
  },
  {
    "path": "yazi-sftp/src/operator.rs",
    "content": "use std::{ops::Deref, sync::Arc};\n\nuse russh::{ChannelStream, client::Msg};\nuse typed_path::UnixPathBuf;\n\nuse crate::{AsSftpPath, Error, Receiver, Session, SftpPath, fs::{Attrs, File, Flags, ReadDir}, requests, responses};\n\npub struct Operator(Arc<Session>);\n\nimpl Deref for Operator {\n\ttype Target = Arc<Session>;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl From<&Arc<Session>> for Operator {\n\tfn from(session: &Arc<Session>) -> Self { Self(session.clone()) }\n}\n\nimpl Operator {\n\tpub fn make(stream: ChannelStream<Msg>) -> Self { Self(Session::make(stream)) }\n\n\tpub async fn init(&mut self) -> Result<(), Error> {\n\t\tlet version: responses::Version = self.send(requests::Init::default()).await?;\n\t\t*self.extensions.lock() = version.extensions;\n\t\tOk(())\n\t}\n\n\tpub async fn open<'a, P>(&self, path: P, flags: Flags, attrs: &'a Attrs) -> Result<File, Error>\n\twhere\n\t\tP: AsSftpPath<'a>,\n\t{\n\t\tlet handle: responses::Handle = self.send(requests::Open::new(path, flags, attrs)).await?;\n\n\t\tOk(File::new(&self.0, handle.handle))\n\t}\n\n\tpub fn close(&self, handle: &str) -> Result<Receiver, Error> {\n\t\tself.send_sync(requests::Close::new(handle))\n\t}\n\n\tpub fn read(&self, handle: &str, offset: u64, len: u32) -> Result<Receiver, Error> {\n\t\tself.send_sync(requests::Read::new(handle, offset, len))\n\t}\n\n\tpub fn write(&self, handle: &str, offset: u64, data: &[u8]) -> Result<Receiver, Error> {\n\t\tself.send_sync(requests::Write::new(handle, offset, data))\n\t}\n\n\tpub async fn lstat<'a, P>(&self, path: P) -> Result<Attrs, Error>\n\twhere\n\t\tP: AsSftpPath<'a>,\n\t{\n\t\tlet attrs: responses::Attrs = self.send(requests::Lstat::new(path)).await?;\n\t\tOk(attrs.attrs)\n\t}\n\n\tpub async fn fstat(&self, handle: &str) -> Result<Attrs, Error> {\n\t\tlet attrs: responses::Attrs = self.send(requests::Fstat::new(handle)).await?;\n\t\tOk(attrs.attrs)\n\t}\n\n\tpub async fn setstat<'a, P>(&self, path: P, attrs: Attrs) -> Result<(), Error>\n\twhere\n\t\tP: AsSftpPath<'a>,\n\t{\n\t\tlet status: responses::Status = self.send(requests::SetStat::new(path, attrs)).await?;\n\t\tstatus.into()\n\t}\n\n\tpub async fn fsetstat(&self, handle: &str, attrs: &Attrs) -> Result<(), Error> {\n\t\tlet status: responses::Status = self.send(requests::FSetStat::new(handle, attrs)).await?;\n\t\tstatus.into()\n\t}\n\n\tpub async fn read_dir<'a, P>(&'a self, dir: P) -> Result<ReadDir, Error>\n\twhere\n\t\tP: AsSftpPath<'a>,\n\t{\n\t\tlet dir: SftpPath = dir.as_sftp_path();\n\t\tlet handle: responses::Handle = self.send(requests::OpenDir::new(&dir)).await?;\n\n\t\tOk(ReadDir::new(&self.0, dir, handle.handle))\n\t}\n\n\tpub async fn remove<'a, P>(&self, path: P) -> Result<(), Error>\n\twhere\n\t\tP: AsSftpPath<'a>,\n\t{\n\t\tlet status: responses::Status = self.send(requests::Remove::new(path)).await?;\n\t\tstatus.into()\n\t}\n\n\tpub async fn mkdir<'a, P>(&self, path: P, attrs: Attrs) -> Result<(), Error>\n\twhere\n\t\tP: AsSftpPath<'a>,\n\t{\n\t\tlet status: responses::Status = self.send(requests::Mkdir::new(path, attrs)).await?;\n\t\tstatus.into()\n\t}\n\n\tpub async fn rmdir<'a, P>(&self, path: P) -> Result<(), Error>\n\twhere\n\t\tP: AsSftpPath<'a>,\n\t{\n\t\tlet status: responses::Status = self.send(requests::Rmdir::new(path)).await?;\n\t\tstatus.into()\n\t}\n\n\tpub async fn realpath<'a, P>(&self, path: P) -> Result<UnixPathBuf, Error>\n\twhere\n\t\tP: AsSftpPath<'a>,\n\t{\n\t\tlet mut name: responses::Name = self.send(requests::Realpath::new(path)).await?;\n\t\tif name.items.is_empty() {\n\t\t\tErr(Error::custom(\"realpath returned no names\"))\n\t\t} else {\n\t\t\tOk(name.items.swap_remove(0).name.into_owned().into())\n\t\t}\n\t}\n\n\tpub async fn stat<'a, P>(&self, path: P) -> Result<Attrs, Error>\n\twhere\n\t\tP: AsSftpPath<'a>,\n\t{\n\t\tlet attrs: responses::Attrs = self.send(requests::Stat::new(path)).await?;\n\t\tOk(attrs.attrs)\n\t}\n\n\tpub async fn rename<'a, F, T>(&self, from: F, to: T) -> Result<(), Error>\n\twhere\n\t\tF: AsSftpPath<'a>,\n\t\tT: AsSftpPath<'a>,\n\t{\n\t\tlet status: responses::Status = self.send(requests::Rename::new(from, to)).await?;\n\t\tstatus.into()\n\t}\n\n\tpub async fn rename_posix<'a, F, T>(&self, from: F, to: T) -> Result<(), Error>\n\twhere\n\t\tF: AsSftpPath<'a>,\n\t\tT: AsSftpPath<'a>,\n\t{\n\t\tif self.extensions.lock().get(\"posix-rename@openssh.com\").is_none_or(|s| s != \"1\") {\n\t\t\treturn Err(Error::Unsupported);\n\t\t}\n\n\t\tlet data = requests::ExtendedRename::new(from, to);\n\t\tlet status: responses::Status =\n\t\t\tself.send(requests::Extended::new(\"posix-rename@openssh.com\", data)).await?;\n\t\tstatus.into()\n\t}\n\n\tpub async fn readlink<'a, P>(&self, path: P) -> Result<UnixPathBuf, Error>\n\twhere\n\t\tP: AsSftpPath<'a>,\n\t{\n\t\tlet mut name: responses::Name = self.send(requests::Readlink::new(path)).await?;\n\t\tif name.items.is_empty() {\n\t\t\tErr(Error::custom(\"readlink returned no names\"))\n\t\t} else {\n\t\t\tOk(name.items.swap_remove(0).name.into_owned().into())\n\t\t}\n\t}\n\n\tpub async fn symlink<'a, L, O>(&self, original: O, link: L) -> Result<(), Error>\n\twhere\n\t\tO: AsSftpPath<'a>,\n\t\tL: AsSftpPath<'a>,\n\t{\n\t\tlet status: responses::Status = self.send(requests::Symlink::new(original, link)).await?;\n\t\tstatus.into()\n\t}\n\n\tpub fn fsync(&self, handle: &str) -> Result<Receiver, Error> {\n\t\tif self.extensions.lock().get(\"fsync@openssh.com\").is_none_or(|s| s != \"1\") {\n\t\t\treturn Err(Error::Unsupported);\n\t\t}\n\n\t\tlet data = requests::ExtendedFsync::new(handle);\n\t\tself.send_sync(requests::Extended::new(\"fsync@openssh.com\", data))\n\t}\n\n\tpub async fn hardlink<'a, O, L>(&self, original: O, link: L) -> Result<(), Error>\n\twhere\n\t\tO: AsSftpPath<'a>,\n\t\tL: AsSftpPath<'a>,\n\t{\n\t\tif self.extensions.lock().get(\"hardlink@openssh.com\").is_none_or(|s| s != \"1\") {\n\t\t\treturn Err(Error::Unsupported);\n\t\t}\n\n\t\tlet data = requests::ExtendedHardlink::new(original, link);\n\t\tlet status: responses::Status =\n\t\t\tself.send(requests::Extended::new(\"hardlink@openssh.com\", data)).await?;\n\t\tstatus.into()\n\t}\n\n\tpub async fn limits(&self) -> Result<responses::ExtendedLimits, Error> {\n\t\tif self.extensions.lock().get(\"limits@openssh.com\").is_none_or(|s| s != \"1\") {\n\t\t\treturn Err(Error::Unsupported);\n\t\t}\n\n\t\tlet extended: responses::Extended =\n\t\t\tself.send(requests::Extended::new(\"limits@openssh.com\", requests::ExtendedLimits)).await?;\n\t\textended.try_into()\n\t}\n}\n"
  },
  {
    "path": "yazi-sftp/src/packet.rs",
    "content": "use serde::{Deserialize, Serialize};\n\nuse super::de::Deserializer;\nuse crate::{Error, impl_from_packet, impl_try_from_packet, requests, responses};\n\n#[derive(Debug, Deserialize, Serialize)]\n#[serde(untagged)]\npub enum Packet<'a> {\n\tInit(requests::Init),\n\tOpen(requests::Open<'a>),\n\tClose(requests::Close<'a>),\n\tRead(requests::Read<'a>),\n\tWrite(requests::Write<'a>),\n\tLstat(requests::Lstat<'a>),\n\tFstat(requests::Fstat<'a>),\n\tSetStat(requests::SetStat<'a>),\n\tFSetStat(requests::FSetStat<'a>),\n\tOpenDir(requests::OpenDir<'a>),\n\tReadDir(requests::ReadDir<'a>),\n\tRemove(requests::Remove<'a>),\n\tMkdir(requests::Mkdir<'a>),\n\tRmdir(requests::Rmdir<'a>),\n\tRealpath(requests::Realpath<'a>),\n\tStat(requests::Stat<'a>),\n\tRename(requests::Rename<'a>),\n\tReadlink(requests::Readlink<'a>),\n\tSymlink(requests::Symlink<'a>),\n\tExtendedRename(requests::Extended<'a, requests::ExtendedRename<'a>>),\n\tExtendedFsync(requests::Extended<'a, requests::ExtendedFsync<'a>>),\n\tExtendedHardlink(requests::Extended<'a, requests::ExtendedHardlink<'a>>),\n\tExtendedLimits(requests::Extended<'a, requests::ExtendedLimits>),\n\n\t// Responses\n\tVersion(responses::Version),\n\tStatus(responses::Status),\n\tHandle(responses::Handle),\n\tData(responses::Data),\n\tName(responses::Name<'a>),\n\tAttrs(responses::Attrs),\n\tExtendedReply(responses::Extended<'a>),\n}\n\nimpl_from_packet! {\n\tInit(requests::Init),\n\tOpen(requests::Open<'a>),\n\tClose(requests::Close<'a>),\n\tRead(requests::Read<'a>),\n\tWrite(requests::Write<'a>),\n\tLstat(requests::Lstat<'a>),\n\tFstat(requests::Fstat<'a>),\n\tSetStat(requests::SetStat<'a>),\n\tFSetStat(requests::FSetStat<'a>),\n\tOpenDir(requests::OpenDir<'a>),\n\tReadDir(requests::ReadDir<'a>),\n\tRemove(requests::Remove<'a>),\n\tMkdir(requests::Mkdir<'a>),\n\tRmdir(requests::Rmdir<'a>),\n\tRealpath(requests::Realpath<'a>),\n\tStat(requests::Stat<'a>),\n\tRename(requests::Rename<'a>),\n\tReadlink(requests::Readlink<'a>),\n\tSymlink(requests::Symlink<'a>),\n\tExtendedRename(requests::Extended<'a, requests::ExtendedRename<'a>>),\n\tExtendedFsync(requests::Extended<'a, requests::ExtendedFsync<'a>>),\n\tExtendedHardlink(requests::Extended<'a, requests::ExtendedHardlink<'a>>),\n\tExtendedLimits(requests::Extended<'a, requests::ExtendedLimits>),\n\n\t// Responses\n\tVersion(responses::Version),\n\tStatus(responses::Status),\n\tHandle(responses::Handle),\n\tData(responses::Data),\n\tName(responses::Name<'a>),\n\tAttrs(responses::Attrs),\n\tExtendedReply(responses::Extended<'a>),\n}\n\nimpl_try_from_packet! {\n\tVersion(responses::Version),\n\tStatus(responses::Status),\n\tHandle(responses::Handle),\n\tData(responses::Data),\n\tName(responses::Name<'a>),\n\tAttrs(responses::Attrs),\n\tExtendedReply(responses::Extended<'a>),\n}\n\nimpl Packet<'_> {\n\tfn kind(&self) -> u8 {\n\t\tmatch self {\n\t\t\tSelf::Init(_) => 1,\n\t\t\tSelf::Open(_) => 3,\n\t\t\tSelf::Close(_) => 4,\n\t\t\tSelf::Read(_) => 5,\n\t\t\tSelf::Write(_) => 6,\n\t\t\tSelf::Lstat(_) => 7,\n\t\t\tSelf::Fstat(_) => 8,\n\t\t\tSelf::SetStat(_) => 9,\n\t\t\tSelf::FSetStat(_) => 10,\n\t\t\tSelf::OpenDir(_) => 11,\n\t\t\tSelf::ReadDir(_) => 12,\n\t\t\tSelf::Remove(_) => 13,\n\t\t\tSelf::Mkdir(_) => 14,\n\t\t\tSelf::Rmdir(_) => 15,\n\t\t\tSelf::Realpath(_) => 16,\n\t\t\tSelf::Stat(_) => 17,\n\t\t\tSelf::Rename(_) => 18,\n\t\t\tSelf::Readlink(_) => 19,\n\t\t\tSelf::Symlink(_) => 20,\n\t\t\tSelf::ExtendedRename(_) => 200,\n\t\t\tSelf::ExtendedFsync(_) => 200,\n\t\t\tSelf::ExtendedHardlink(_) => 200,\n\t\t\tSelf::ExtendedLimits(_) => 200,\n\n\t\t\t// Responses\n\t\t\tSelf::Version(_) => 2,\n\t\t\tSelf::Status(_) => 101,\n\t\t\tSelf::Handle(_) => 102,\n\t\t\tSelf::Data(_) => 103,\n\t\t\tSelf::Name(_) => 104,\n\t\t\tSelf::Attrs(_) => 105,\n\t\t\tSelf::ExtendedReply(_) => 201,\n\t\t}\n\t}\n\n\tpub fn id(&self) -> u32 {\n\t\tmatch self {\n\t\t\tSelf::Init(_) => 0,\n\t\t\tSelf::Open(v) => v.id,\n\t\t\tSelf::Close(v) => v.id,\n\t\t\tSelf::Read(v) => v.id,\n\t\t\tSelf::Write(v) => v.id,\n\t\t\tSelf::Lstat(v) => v.id,\n\t\t\tSelf::Fstat(v) => v.id,\n\t\t\tSelf::SetStat(v) => v.id,\n\t\t\tSelf::FSetStat(v) => v.id,\n\t\t\tSelf::OpenDir(v) => v.id,\n\t\t\tSelf::ReadDir(v) => v.id,\n\t\t\tSelf::Remove(v) => v.id,\n\t\t\tSelf::Mkdir(v) => v.id,\n\t\t\tSelf::Rmdir(v) => v.id,\n\t\t\tSelf::Realpath(v) => v.id,\n\t\t\tSelf::Stat(v) => v.id,\n\t\t\tSelf::Rename(v) => v.id,\n\t\t\tSelf::Readlink(v) => v.id,\n\t\t\tSelf::Symlink(v) => v.id,\n\t\t\tSelf::ExtendedRename(v) => v.id,\n\t\t\tSelf::ExtendedFsync(v) => v.id,\n\t\t\tSelf::ExtendedHardlink(v) => v.id,\n\t\t\tSelf::ExtendedLimits(v) => v.id,\n\n\t\t\t// Responses\n\t\t\tSelf::Version(_) => 0,\n\t\t\tSelf::Status(v) => v.id,\n\t\t\tSelf::Handle(v) => v.id,\n\t\t\tSelf::Data(v) => v.id,\n\t\t\tSelf::Name(v) => v.id,\n\t\t\tSelf::Attrs(v) => v.id,\n\t\t\tSelf::ExtendedReply(v) => v.id,\n\t\t}\n\t}\n\n\tpub fn with_id(mut self, id: u32) -> Self {\n\t\tmatch &mut self {\n\t\t\tSelf::Init(_) => {}\n\t\t\tSelf::Open(v) => v.id = id,\n\t\t\tSelf::Close(v) => v.id = id,\n\t\t\tSelf::Read(v) => v.id = id,\n\t\t\tSelf::Write(v) => v.id = id,\n\t\t\tSelf::Lstat(v) => v.id = id,\n\t\t\tSelf::Fstat(v) => v.id = id,\n\t\t\tSelf::SetStat(v) => v.id = id,\n\t\t\tSelf::FSetStat(v) => v.id = id,\n\t\t\tSelf::OpenDir(v) => v.id = id,\n\t\t\tSelf::ReadDir(v) => v.id = id,\n\t\t\tSelf::Remove(v) => v.id = id,\n\t\t\tSelf::Mkdir(v) => v.id = id,\n\t\t\tSelf::Rmdir(v) => v.id = id,\n\t\t\tSelf::Realpath(v) => v.id = id,\n\t\t\tSelf::Stat(v) => v.id = id,\n\t\t\tSelf::Rename(v) => v.id = id,\n\t\t\tSelf::Readlink(v) => v.id = id,\n\t\t\tSelf::Symlink(v) => v.id = id,\n\t\t\tSelf::ExtendedRename(v) => v.id = id,\n\t\t\tSelf::ExtendedFsync(v) => v.id = id,\n\t\t\tSelf::ExtendedHardlink(v) => v.id = id,\n\t\t\tSelf::ExtendedLimits(v) => v.id = id,\n\n\t\t\t// Responses\n\t\t\tSelf::Version(_) => {}\n\t\t\tSelf::Status(v) => v.id = id,\n\t\t\tSelf::Handle(v) => v.id = id,\n\t\t\tSelf::Data(v) => v.id = id,\n\t\t\tSelf::Name(v) => v.id = id,\n\t\t\tSelf::Attrs(v) => v.id = id,\n\t\t\tSelf::ExtendedReply(v) => v.id = id,\n\t\t}\n\t\tself\n\t}\n\n\tfn len(&self) -> usize {\n\t\tlet type_len = 1;\n\t\tmatch self {\n\t\t\tSelf::Init(v) => type_len + v.len(),\n\t\t\tSelf::Open(v) => type_len + v.len(),\n\t\t\tSelf::Close(v) => type_len + v.len(),\n\t\t\tSelf::Read(v) => type_len + v.len(),\n\t\t\tSelf::Write(v) => type_len + v.len(),\n\t\t\tSelf::Lstat(v) => type_len + v.len(),\n\t\t\tSelf::Fstat(v) => type_len + v.len(),\n\t\t\tSelf::SetStat(v) => type_len + v.len(),\n\t\t\tSelf::FSetStat(v) => type_len + v.len(),\n\t\t\tSelf::OpenDir(v) => type_len + v.len(),\n\t\t\tSelf::ReadDir(v) => type_len + v.len(),\n\t\t\tSelf::Remove(v) => type_len + v.len(),\n\t\t\tSelf::Mkdir(v) => type_len + v.len(),\n\t\t\tSelf::Rmdir(v) => type_len + v.len(),\n\t\t\tSelf::Realpath(v) => type_len + v.len(),\n\t\t\tSelf::Stat(v) => type_len + v.len(),\n\t\t\tSelf::Rename(v) => type_len + v.len(),\n\t\t\tSelf::Readlink(v) => type_len + v.len(),\n\t\t\tSelf::Symlink(v) => type_len + v.len(),\n\t\t\tSelf::ExtendedRename(v) => type_len + v.len(),\n\t\t\tSelf::ExtendedFsync(v) => type_len + v.len(),\n\t\t\tSelf::ExtendedHardlink(v) => type_len + v.len(),\n\t\t\tSelf::ExtendedLimits(v) => type_len + v.len(),\n\n\t\t\t// Responses\n\t\t\tSelf::Version(v) => type_len + v.len(),\n\t\t\tSelf::Status(v) => type_len + v.len(),\n\t\t\tSelf::Handle(v) => type_len + v.len(),\n\t\t\tSelf::Data(v) => type_len + v.len(),\n\t\t\tSelf::Name(v) => type_len + v.len(),\n\t\t\tSelf::Attrs(v) => type_len + v.len(),\n\t\t\tSelf::ExtendedReply(v) => type_len + v.len(),\n\t\t}\n\t}\n}\n\npub fn to_bytes<'a, T>(value: T) -> Result<Vec<u8>, Error>\nwhere\n\tT: Into<Packet<'a>> + Serialize,\n{\n\tlet packet: Packet = value.into();\n\n\tlet len = u32::try_from(packet.len()).map_err(|_| Error::serde(\"packet too large\"))?;\n\n\tlet mut output = Vec::with_capacity(4 + len as usize);\n\toutput.extend_from_slice(&len.to_be_bytes());\n\toutput.push(packet.kind());\n\n\tlet mut serializer = crate::Serializer { output };\n\tpacket.serialize(&mut serializer)?;\n\tOk(serializer.output)\n}\n\n// TODO: use Vec<u8>\npub fn from_bytes(mut bytes: &[u8]) -> Result<Packet<'static>, Error> {\n\tlet kind = *bytes.first().ok_or(Error::serde(\"empty packet\"))?;\n\tbytes = &bytes[1..];\n\n\tOk(match kind {\n\t\t2 => Packet::Version(Deserializer::once(bytes)?),\n\t\t101 => Packet::Status(Deserializer::once(bytes)?),\n\t\t102 => Packet::Handle(Deserializer::once(bytes)?),\n\t\t103 => Packet::Data(Deserializer::once(bytes)?),\n\t\t104 => Packet::Name(Deserializer::once(bytes)?),\n\t\t105 => Packet::Attrs(Deserializer::once(bytes)?),\n\t\t201 => Packet::ExtendedReply(Deserializer::once(bytes)?),\n\t\t_ => return Err(Error::Packet(\"unknown packet kind\")),\n\t})\n}\n"
  },
  {
    "path": "yazi-sftp/src/path.rs",
    "content": "use std::{borrow::Cow, ops::Deref};\n\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug)]\npub enum SftpPath<'a> {\n\tBorrowed(&'a typed_path::UnixPath),\n\tOwned(typed_path::UnixPathBuf),\n}\n\nimpl Deref for SftpPath<'_> {\n\ttype Target = typed_path::UnixPath;\n\n\tfn deref(&self) -> &Self::Target {\n\t\tmatch self {\n\t\t\tSftpPath::Borrowed(p) => p,\n\t\t\tSftpPath::Owned(p) => p.as_path(),\n\t\t}\n\t}\n}\n\nimpl Default for SftpPath<'_> {\n\tfn default() -> Self { SftpPath::Borrowed(typed_path::UnixPath::new(\"\")) }\n}\n\nimpl<'a> From<&'a Self> for SftpPath<'a> {\n\tfn from(value: &'a SftpPath) -> Self { SftpPath::Borrowed(value) }\n}\n\nimpl<'a> From<Cow<'a, [u8]>> for SftpPath<'a> {\n\tfn from(value: Cow<'a, [u8]>) -> Self {\n\t\tmatch value {\n\t\t\tCow::Borrowed(b) => Self::Borrowed(typed_path::UnixPath::new(b)),\n\t\t\tCow::Owned(b) => SftpPath::Owned(typed_path::UnixPathBuf::from(b)),\n\t\t}\n\t}\n}\n\nimpl Serialize for SftpPath<'_> {\n\tfn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n\twhere\n\t\tS: serde::Serializer,\n\t{\n\t\tserializer.serialize_bytes(self.as_bytes())\n\t}\n}\n\nimpl<'de> Deserialize<'de> for SftpPath<'_> {\n\tfn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n\twhere\n\t\tD: serde::Deserializer<'de>,\n\t{\n\t\tlet cow = <Cow<'de, [u8]>>::deserialize(deserializer)?;\n\t\tOk(Self::Owned(cow.into_owned().into()))\n\t}\n}\n\nimpl<'a> SftpPath<'a> {\n\tpub fn len(&self) -> usize { self.as_bytes().len() }\n\n\tpub fn into_owned(self) -> typed_path::UnixPathBuf {\n\t\tmatch self {\n\t\t\tSftpPath::Borrowed(p) => p.to_owned(),\n\t\t\tSftpPath::Owned(p) => p,\n\t\t}\n\t}\n}\n\n// --- Traits\npub trait AsSftpPath<'a> {\n\tfn as_sftp_path(self) -> SftpPath<'a>;\n}\n\nimpl<'a, T> AsSftpPath<'a> for &'a T\nwhere\n\tT: ?Sized + AsRef<typed_path::UnixPath>,\n{\n\tfn as_sftp_path(self) -> SftpPath<'a> { SftpPath::Borrowed(self.as_ref()) }\n}\n\nimpl<'a> AsSftpPath<'a> for &'a SftpPath<'a> {\n\tfn as_sftp_path(self) -> SftpPath<'a> { SftpPath::Borrowed(self) }\n}\n"
  },
  {
    "path": "yazi-sftp/src/receiver.rs",
    "content": "use std::{pin::Pin, sync::Arc, task::Poll};\n\nuse tokio::sync::oneshot;\n\nuse crate::{Packet, Session};\n\npub struct Receiver {\n\trx:       oneshot::Receiver<Packet<'static>>,\n\treceived: bool,\n\n\tsession: Arc<Session>,\n\tid:      u32,\n}\n\nimpl Drop for Receiver {\n\tfn drop(&mut self) {\n\t\tif !self.received {\n\t\t\tself.session.callback.lock().remove(&self.id);\n\t\t}\n\t}\n}\n\nimpl Receiver {\n\tpub(crate) fn new(\n\t\tsession: &Arc<Session>,\n\t\tid: u32,\n\t\trx: oneshot::Receiver<Packet<'static>>,\n\t) -> Self {\n\t\tSelf { rx, received: false, session: session.clone(), id }\n\t}\n}\n\nimpl Future for Receiver {\n\ttype Output = Result<Packet<'static>, oneshot::error::RecvError>;\n\n\tfn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {\n\t\tlet me = self.get_mut();\n\t\tmatch Pin::new(&mut me.rx).poll(cx) {\n\t\t\tPoll::Ready(Ok(packet)) => {\n\t\t\t\tme.received = true;\n\t\t\t\tPoll::Ready(Ok(packet))\n\t\t\t}\n\t\t\tPoll::Ready(Err(e)) => Poll::Ready(Err(e)),\n\t\t\tPoll::Pending => Poll::Pending,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-sftp/src/requests/close.rs",
    "content": "use std::borrow::Cow;\n\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Close<'a> {\n\tpub id:     u32,\n\tpub handle: Cow<'a, str>,\n}\n\nimpl<'a> Close<'a> {\n\tpub fn new(handle: impl Into<Cow<'a, str>>) -> Self { Self { id: 0, handle: handle.into() } }\n\n\tpub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.handle.len() }\n}\n"
  },
  {
    "path": "yazi-sftp/src/requests/extended.rs",
    "content": "use std::{borrow::Cow, fmt::Debug};\n\nuse serde::{Deserialize, Serialize};\n\nuse crate::{AsSftpPath, SftpPath};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Extended<'a, D> {\n\tpub id:      u32,\n\tpub request: Cow<'a, str>,\n\tpub data:    D,\n}\n\nimpl<D: ExtendedData> Extended<'_, D> {\n\tpub fn new<'a, R>(request: R, data: D) -> Extended<'a, D>\n\twhere\n\t\tR: Into<Cow<'a, str>>,\n\t{\n\t\tExtended { id: 0, request: request.into(), data }\n\t}\n\n\tpub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.request.len() + self.data.len() }\n}\n\n// --- Data\npub trait ExtendedData: Debug + Serialize + for<'de> Deserialize<'de> {\n\tfn len(&self) -> usize;\n}\n\n// --- POSIX Rename\n#[derive(Debug, Deserialize, Serialize)]\npub struct ExtendedRename<'a> {\n\tpub from: SftpPath<'a>,\n\tpub to:   SftpPath<'a>,\n}\n\nimpl<'a> ExtendedRename<'a> {\n\tpub fn new<F, T>(from: F, to: T) -> Self\n\twhere\n\t\tF: AsSftpPath<'a>,\n\t\tT: AsSftpPath<'a>,\n\t{\n\t\tSelf { from: from.as_sftp_path(), to: to.as_sftp_path() }\n\t}\n}\n\nimpl ExtendedData for ExtendedRename<'_> {\n\tfn len(&self) -> usize { 4 + self.from.len() + 4 + self.to.len() }\n}\n\n// --- Fsync\n#[derive(Debug, Deserialize, Serialize)]\npub struct ExtendedFsync<'a> {\n\tpub handle: Cow<'a, str>,\n}\n\nimpl<'a> ExtendedFsync<'a> {\n\tpub fn new(handle: impl Into<Cow<'a, str>>) -> Self { Self { handle: handle.into() } }\n}\n\nimpl ExtendedData for ExtendedFsync<'_> {\n\tfn len(&self) -> usize { 4 + self.handle.len() }\n}\n\n// --- Hardlink\n#[derive(Debug, Deserialize, Serialize)]\npub struct ExtendedHardlink<'a> {\n\tpub original: SftpPath<'a>,\n\tpub link:     SftpPath<'a>,\n}\n\nimpl<'a> ExtendedHardlink<'a> {\n\tpub fn new<O, L>(original: O, link: L) -> Self\n\twhere\n\t\tO: AsSftpPath<'a>,\n\t\tL: AsSftpPath<'a>,\n\t{\n\t\tSelf { original: original.as_sftp_path(), link: link.as_sftp_path() }\n\t}\n}\n\nimpl ExtendedData for ExtendedHardlink<'_> {\n\tfn len(&self) -> usize { 4 + self.original.len() + 4 + self.link.len() }\n}\n\n// --- Limits\n#[derive(Debug, Deserialize, Serialize)]\npub struct ExtendedLimits;\n\nimpl ExtendedData for ExtendedLimits {\n\tfn len(&self) -> usize { 0 }\n}\n"
  },
  {
    "path": "yazi-sftp/src/requests/fstat.rs",
    "content": "use std::borrow::Cow;\n\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Fstat<'a> {\n\tpub id:     u32,\n\tpub handle: Cow<'a, str>,\n}\n\nimpl<'a> Fstat<'a> {\n\tpub fn new(handle: impl Into<Cow<'a, str>>) -> Self { Self { id: 0, handle: handle.into() } }\n\n\tpub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.handle.len() }\n}\n"
  },
  {
    "path": "yazi-sftp/src/requests/init.rs",
    "content": "use std::collections::HashMap;\n\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Init {\n\tpub version:    u32,\n\tpub extensions: HashMap<String, String>,\n}\n\nimpl Init {\n\tpub fn new(extensions: HashMap<String, String>) -> Self { Self { version: 3, extensions } }\n\n\tpub fn len(&self) -> usize {\n\t\tsize_of_val(&self.version)\n\t\t\t+ self.extensions.iter().map(|(k, v)| 4 + k.len() + 4 + v.len()).sum::<usize>()\n\t}\n}\n\nimpl Default for Init {\n\tfn default() -> Self { Self::new(HashMap::new()) }\n}\n"
  },
  {
    "path": "yazi-sftp/src/requests/lstat.rs",
    "content": "use serde::{Deserialize, Serialize};\n\nuse crate::{AsSftpPath, SftpPath};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Lstat<'a> {\n\tpub id:   u32,\n\tpub path: SftpPath<'a>,\n}\n\nimpl Lstat<'_> {\n\tpub fn new<'a, P>(path: P) -> Lstat<'a>\n\twhere\n\t\tP: AsSftpPath<'a>,\n\t{\n\t\tLstat { id: 0, path: path.as_sftp_path() }\n\t}\n\n\tpub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() }\n}\n"
  },
  {
    "path": "yazi-sftp/src/requests/mkdir.rs",
    "content": "use serde::{Deserialize, Serialize};\n\nuse crate::{AsSftpPath, SftpPath, fs::Attrs};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Mkdir<'a> {\n\tpub id:    u32,\n\tpub path:  SftpPath<'a>,\n\tpub attrs: Attrs,\n}\n\nimpl<'a> Mkdir<'a> {\n\tpub fn new<P>(path: P, attrs: Attrs) -> Self\n\twhere\n\t\tP: AsSftpPath<'a>,\n\t{\n\t\tSelf { id: 0, path: path.as_sftp_path(), attrs }\n\t}\n\n\tpub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() + self.attrs.len() }\n}\n"
  },
  {
    "path": "yazi-sftp/src/requests/mod.rs",
    "content": "mod close;\nmod extended;\nmod fstat;\nmod init;\nmod lstat;\nmod mkdir;\nmod open;\nmod open_dir;\nmod read;\nmod read_dir;\nmod readlink;\nmod realpath;\nmod remove;\nmod rename;\nmod rmdir;\nmod set_stat;\nmod stat;\nmod symlink;\nmod write;\n\npub use close::*;\npub use extended::*;\npub use fstat::*;\npub use init::*;\npub use lstat::*;\npub use mkdir::*;\npub use open::*;\npub use open_dir::*;\npub use read::*;\npub use read_dir::*;\npub use readlink::*;\npub use realpath::*;\npub use remove::*;\npub use rename::*;\npub use rmdir::*;\npub use set_stat::*;\npub use stat::*;\npub use symlink::*;\npub use write::*;\n"
  },
  {
    "path": "yazi-sftp/src/requests/open.rs",
    "content": "use std::borrow::Cow;\n\nuse serde::{Deserialize, Serialize};\n\nuse crate::{AsSftpPath, SftpPath, fs::{Attrs, Flags}};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Open<'a> {\n\tpub id:    u32,\n\tpub path:  SftpPath<'a>,\n\tpub flags: Flags,\n\tpub attrs: Cow<'a, Attrs>,\n}\n\nimpl<'a> Open<'a> {\n\tpub fn new<P>(path: P, flags: Flags, attrs: &'a Attrs) -> Self\n\twhere\n\t\tP: AsSftpPath<'a>,\n\t{\n\t\tSelf { id: 0, path: path.as_sftp_path(), flags, attrs: Cow::Borrowed(attrs) }\n\t}\n\n\tpub fn len(&self) -> usize {\n\t\tsize_of_val(&self.id) + 4 + self.path.len() + size_of_val(&self.flags) + self.attrs.len()\n\t}\n}\n"
  },
  {
    "path": "yazi-sftp/src/requests/open_dir.rs",
    "content": "use serde::{Deserialize, Serialize};\n\nuse crate::{AsSftpPath, SftpPath};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct OpenDir<'a> {\n\tpub id:   u32,\n\tpub path: SftpPath<'a>,\n}\n\nimpl<'a> OpenDir<'a> {\n\tpub fn new<P>(path: P) -> Self\n\twhere\n\t\tP: AsSftpPath<'a>,\n\t{\n\t\tSelf { id: 0, path: path.as_sftp_path() }\n\t}\n\n\tpub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() }\n}\n"
  },
  {
    "path": "yazi-sftp/src/requests/read.rs",
    "content": "use std::borrow::Cow;\n\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Read<'a> {\n\tpub id:     u32,\n\tpub handle: Cow<'a, str>,\n\tpub offset: u64,\n\tpub len:    u32,\n}\n\nimpl<'a> Read<'a> {\n\tpub fn new<H>(handle: H, offset: u64, len: u32) -> Self\n\twhere\n\t\tH: Into<Cow<'a, str>>,\n\t{\n\t\tSelf { id: 0, handle: handle.into(), offset, len }\n\t}\n\n\tpub fn len(&self) -> usize {\n\t\tsize_of_val(&self.id)\n\t\t\t+ 4 + self.handle.len()\n\t\t\t+ size_of_val(&self.offset)\n\t\t\t+ size_of_val(&self.len)\n\t}\n}\n"
  },
  {
    "path": "yazi-sftp/src/requests/read_dir.rs",
    "content": "use std::borrow::Cow;\n\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct ReadDir<'a> {\n\tpub id:     u32,\n\tpub handle: Cow<'a, str>,\n}\n\nimpl<'a> ReadDir<'a> {\n\tpub fn new(handle: &'a str) -> Self { Self { id: 0, handle: handle.into() } }\n\n\tpub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.handle.len() }\n}\n"
  },
  {
    "path": "yazi-sftp/src/requests/readlink.rs",
    "content": "use serde::{Deserialize, Serialize};\n\nuse crate::{AsSftpPath, SftpPath};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Readlink<'a> {\n\tpub id:   u32,\n\tpub path: SftpPath<'a>,\n}\n\nimpl<'a> Readlink<'a> {\n\tpub fn new<P>(path: P) -> Self\n\twhere\n\t\tP: AsSftpPath<'a>,\n\t{\n\t\tSelf { id: 0, path: path.as_sftp_path() }\n\t}\n\n\tpub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() }\n}\n"
  },
  {
    "path": "yazi-sftp/src/requests/realpath.rs",
    "content": "use serde::{Deserialize, Serialize};\n\nuse crate::{AsSftpPath, SftpPath};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Realpath<'a> {\n\tpub id:   u32,\n\tpub path: SftpPath<'a>,\n}\n\nimpl<'a> Realpath<'a> {\n\tpub fn new<P>(path: P) -> Self\n\twhere\n\t\tP: AsSftpPath<'a>,\n\t{\n\t\tSelf { id: 0, path: path.as_sftp_path() }\n\t}\n\n\tpub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() }\n}\n"
  },
  {
    "path": "yazi-sftp/src/requests/remove.rs",
    "content": "use serde::{Deserialize, Serialize};\n\nuse crate::{AsSftpPath, SftpPath};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Remove<'a> {\n\tpub id:   u32,\n\tpub path: SftpPath<'a>,\n}\n\nimpl<'a> Remove<'a> {\n\tpub fn new<P>(path: P) -> Self\n\twhere\n\t\tP: AsSftpPath<'a>,\n\t{\n\t\tSelf { id: 0, path: path.as_sftp_path() }\n\t}\n\n\tpub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() }\n}\n"
  },
  {
    "path": "yazi-sftp/src/requests/rename.rs",
    "content": "use serde::{Deserialize, Serialize};\n\nuse crate::{AsSftpPath, SftpPath};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Rename<'a> {\n\tpub id:   u32,\n\tpub from: SftpPath<'a>,\n\tpub to:   SftpPath<'a>,\n}\n\nimpl<'a> Rename<'a> {\n\tpub fn new<F, T>(from: F, to: T) -> Self\n\twhere\n\t\tF: AsSftpPath<'a>,\n\t\tT: AsSftpPath<'a>,\n\t{\n\t\tSelf { id: 0, from: from.as_sftp_path(), to: to.as_sftp_path() }\n\t}\n\n\tpub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.from.len() + 4 + self.to.len() }\n}\n"
  },
  {
    "path": "yazi-sftp/src/requests/rmdir.rs",
    "content": "use serde::{Deserialize, Serialize};\n\nuse crate::{AsSftpPath, SftpPath};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Rmdir<'a> {\n\tpub id:   u32,\n\tpub path: SftpPath<'a>,\n}\n\nimpl<'a> Rmdir<'a> {\n\tpub fn new<P>(path: P) -> Self\n\twhere\n\t\tP: AsSftpPath<'a>,\n\t{\n\t\tSelf { id: 0, path: path.as_sftp_path() }\n\t}\n\n\tpub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() }\n}\n"
  },
  {
    "path": "yazi-sftp/src/requests/set_stat.rs",
    "content": "use std::borrow::Cow;\n\nuse serde::{Deserialize, Serialize};\n\nuse crate::{AsSftpPath, SftpPath, fs::Attrs};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct SetStat<'a> {\n\tpub id:    u32,\n\tpub path:  SftpPath<'a>,\n\tpub attrs: Attrs,\n}\n\nimpl<'a> SetStat<'a> {\n\tpub fn new<P>(path: P, attrs: Attrs) -> Self\n\twhere\n\t\tP: AsSftpPath<'a>,\n\t{\n\t\tSelf { id: 0, path: path.as_sftp_path(), attrs }\n\t}\n\n\tpub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() + self.attrs.len() }\n}\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct FSetStat<'a> {\n\tpub id:     u32,\n\tpub handle: Cow<'a, str>,\n\tpub attrs:  Cow<'a, Attrs>,\n}\n\nimpl<'a> FSetStat<'a> {\n\tpub fn new(handle: impl Into<Cow<'a, str>>, attrs: &'a Attrs) -> Self {\n\t\tSelf { id: 0, handle: handle.into(), attrs: Cow::Borrowed(attrs) }\n\t}\n\n\tpub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.handle.len() + self.attrs.len() }\n}\n"
  },
  {
    "path": "yazi-sftp/src/requests/stat.rs",
    "content": "use serde::{Deserialize, Serialize};\n\nuse crate::{AsSftpPath, SftpPath};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Stat<'a> {\n\tpub id:   u32,\n\tpub path: SftpPath<'a>,\n}\n\nimpl<'a> Stat<'a> {\n\tpub fn new<P>(path: P) -> Self\n\twhere\n\t\tP: AsSftpPath<'a>,\n\t{\n\t\tSelf { id: 0, path: path.as_sftp_path() }\n\t}\n\n\tpub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() }\n}\n"
  },
  {
    "path": "yazi-sftp/src/requests/symlink.rs",
    "content": "use serde::{Deserialize, Serialize};\n\nuse crate::{AsSftpPath, SftpPath};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Symlink<'a> {\n\tpub id:       u32,\n\tpub link:     SftpPath<'a>,\n\tpub original: SftpPath<'a>,\n}\n\nimpl<'a> Symlink<'a> {\n\tpub fn new<L, O>(link: L, original: O) -> Self\n\twhere\n\t\tL: AsSftpPath<'a>,\n\t\tO: AsSftpPath<'a>,\n\t{\n\t\tSelf { id: 0, link: link.as_sftp_path(), original: original.as_sftp_path() }\n\t}\n\n\tpub fn len(&self) -> usize {\n\t\tsize_of_val(&self.id) + 4 + self.link.len() + 4 + self.original.len()\n\t}\n}\n"
  },
  {
    "path": "yazi-sftp/src/requests/write.rs",
    "content": "use std::borrow::Cow;\n\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Write<'a> {\n\tpub id:     u32,\n\tpub handle: Cow<'a, str>,\n\tpub offset: u64,\n\tpub data:   Cow<'a, [u8]>,\n}\n\nimpl Write<'_> {\n\tpub fn new<'a, H, D>(handle: H, offset: u64, data: D) -> Write<'a>\n\twhere\n\t\tH: Into<Cow<'a, str>>,\n\t\tD: Into<Cow<'a, [u8]>>,\n\t{\n\t\tWrite { id: 0, handle: handle.into(), offset, data: data.into() }\n\t}\n\n\tpub fn len(&self) -> usize {\n\t\tsize_of_val(&self.id) + 4 + self.handle.len() + size_of_val(&self.offset) + 4 + self.data.len()\n\t}\n}\n"
  },
  {
    "path": "yazi-sftp/src/responses/attrs.rs",
    "content": "use serde::{Deserialize, Serialize};\n\nuse crate::fs;\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Attrs {\n\tpub id:    u32,\n\tpub attrs: fs::Attrs,\n}\n\nimpl Attrs {\n\tpub fn len(&self) -> usize { size_of_val(&self.id) + self.attrs.len() }\n}\n"
  },
  {
    "path": "yazi-sftp/src/responses/data.rs",
    "content": "use serde::{Deserialize, Serialize};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Data {\n\tpub id:   u32,\n\tpub data: Vec<u8>,\n}\n\nimpl Data {\n\tpub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.data.len() }\n}\n"
  },
  {
    "path": "yazi-sftp/src/responses/extended.rs",
    "content": "use std::{borrow::Cow, fmt, ops::Deref};\n\nuse serde::{Deserialize, Serialize, de::Visitor, ser::SerializeSeq};\n\nuse crate::Error;\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Extended<'a> {\n\tpub id:   u32,\n\tpub data: ExtendedData<'a>,\n}\n\nimpl<'a> Extended<'a> {\n\tpub fn len(&self) -> usize { size_of_val(&self.id) + self.data.len() }\n}\n\n// --- Data\n#[derive(Debug)]\npub struct ExtendedData<'a>(Cow<'a, [u8]>);\n\nimpl Deref for ExtendedData<'_> {\n\ttype Target = [u8];\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl Serialize for ExtendedData<'_> {\n\tfn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n\twhere\n\t\tS: serde::Serializer,\n\t{\n\t\tlet mut seq = serializer.serialize_seq(None)?;\n\t\tfor b in &*self.0 {\n\t\t\tseq.serialize_element(b)?;\n\t\t}\n\t\tseq.end()\n\t}\n}\n\nimpl<'de> Deserialize<'de> for ExtendedData<'_> {\n\tfn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n\twhere\n\t\tD: serde::Deserializer<'de>,\n\t{\n\t\tstruct ExtendedDataVisitor;\n\n\t\timpl<'de> Visitor<'de> for ExtendedDataVisitor {\n\t\t\ttype Value = Vec<u8>;\n\n\t\t\tfn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\n\t\t\t\tformatter.write_str(\"extended data\")\n\t\t\t}\n\n\t\t\tfn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>\n\t\t\twhere\n\t\t\t\tA: serde::de::SeqAccess<'de>,\n\t\t\t{\n\t\t\t\tlet mut bytes = Vec::with_capacity(seq.size_hint().unwrap_or(0));\n\t\t\t\twhile let Some(b) = seq.next_element()? {\n\t\t\t\t\tbytes.push(b);\n\t\t\t\t}\n\t\t\t\tOk(bytes)\n\t\t\t}\n\t\t}\n\n\t\tdeserializer.deserialize_any(ExtendedDataVisitor).map(Cow::Owned).map(ExtendedData)\n\t}\n}\n\n// --- Limits\n#[derive(Debug, Deserialize, Serialize)]\npub struct ExtendedLimits {\n\tpub packet_len:   u64,\n\tpub read_len:     u64,\n\tpub write_len:    u64,\n\tpub open_handles: u64,\n}\n\nimpl TryFrom<Extended<'_>> for ExtendedLimits {\n\ttype Error = Error;\n\n\tfn try_from(value: Extended<'_>) -> Result<Self, Self::Error> {\n\t\tcrate::Deserializer::once(&value.data)\n\t}\n}\n"
  },
  {
    "path": "yazi-sftp/src/responses/handle.rs",
    "content": "use serde::{Deserialize, Serialize};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Handle {\n\tpub id:     u32,\n\tpub handle: String,\n}\n\nimpl Handle {\n\tpub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.handle.len() }\n}\n"
  },
  {
    "path": "yazi-sftp/src/responses/mod.rs",
    "content": "mod attrs;\nmod data;\nmod extended;\nmod handle;\nmod name;\nmod status;\nmod version;\n\npub use attrs::*;\npub use data::*;\npub use extended::*;\npub use handle::*;\npub use name::*;\npub use status::*;\npub use version::*;\n"
  },
  {
    "path": "yazi-sftp/src/responses/name.rs",
    "content": "use std::borrow::Cow;\n\nuse serde::{Deserialize, Serialize};\n\nuse crate::fs::Attrs;\n\n#[derive(Debug, Default, Deserialize, Serialize)]\npub struct Name<'a> {\n\tpub id:    u32,\n\tpub items: Vec<NameItem<'a>>,\n}\n\n#[derive(Debug, Default, Deserialize, Serialize)]\npub struct NameItem<'a> {\n\tpub name:      Cow<'a, [u8]>,\n\tpub long_name: Cow<'a, [u8]>,\n\tpub attrs:     Attrs,\n}\n\nimpl Name<'_> {\n\tpub fn len(&self) -> usize {\n\t\tsize_of_val(&self.id) + 4 + self.items.iter().map(|v| v.len()).sum::<usize>()\n\t}\n}\n\nimpl NameItem<'_> {\n\tpub fn len(&self) -> usize { 4 + self.name.len() + 4 + self.long_name.len() + self.attrs.len() }\n}\n"
  },
  {
    "path": "yazi-sftp/src/responses/status.rs",
    "content": "use serde::{Deserialize, Serialize};\n\nuse crate::Error;\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Status {\n\tpub id:       u32,\n\tpub code:     StatusCode,\n\tpub message:  String,\n\tpub language: String,\n}\n\nimpl From<Status> for Result<(), Error> {\n\tfn from(status: Status) -> Self {\n\t\tif status.is_ok() { Ok(()) } else { Err(Error::Status(status)) }\n\t}\n}\n\nimpl Status {\n\tpub fn len(&self) -> usize {\n\t\tsize_of_val(&self.id)\n\t\t\t+ size_of_val(&(self.code as u32))\n\t\t\t+ 4 + self.message.len()\n\t\t\t+ 4 + self.language.len()\n\t}\n\n\tpub fn is_ok(&self) -> bool { self.code == StatusCode::Ok }\n\n\tpub fn is_eof(&self) -> bool { self.code == StatusCode::Eof }\n\n\tpub fn is_failure(&self) -> bool { self.code == StatusCode::Failure }\n\n\tpub(crate) fn connection_lost(id: u32) -> Self {\n\t\tSelf {\n\t\t\tid,\n\t\t\tcode: StatusCode::ConnectionLost,\n\t\t\tmessage: \"connection lost\".to_owned(),\n\t\t\tlanguage: \"en\".to_owned(),\n\t\t}\n\t}\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]\npub enum StatusCode {\n\tOk                = 0,\n\tEof               = 1,\n\tNoSuchFile        = 2,\n\tPermissionDenied  = 3,\n\tFailure           = 4,\n\tBadMessage        = 5,\n\tNoConnection      = 6,\n\tConnectionLost    = 7,\n\tOpUnsupported     = 8,\n\tInvalidHandle     = 9,\n\tNoSuchPath        = 10,\n\tFileAlreadyExists = 11,\n\tWriteProtect      = 12,\n\tNoMedia           = 13,\n}\n"
  },
  {
    "path": "yazi-sftp/src/responses/version.rs",
    "content": "use std::collections::HashMap;\n\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Deserialize, Serialize)]\npub struct Version {\n\tpub version:    u32,\n\tpub extensions: HashMap<String, String>,\n}\n\nimpl Version {\n\tpub fn len(&self) -> usize {\n\t\tsize_of_val(&self.version)\n\t\t\t+ self.extensions.iter().map(|(k, v)| 4 + k.len() + 4 + v.len()).sum::<usize>()\n\t}\n}\n"
  },
  {
    "path": "yazi-sftp/src/ser.rs",
    "content": "use serde::ser::{SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple, SerializeTupleStruct, SerializeTupleVariant};\n\nuse crate::Error;\n\npub(super) struct Serializer {\n\tpub(super) output: Vec<u8>,\n}\n\nimpl<'a> serde::Serializer for &'a mut Serializer {\n\ttype Error = crate::Error;\n\ttype Ok = ();\n\ttype SerializeMap = &'a mut Serializer;\n\ttype SerializeSeq = &'a mut Serializer;\n\ttype SerializeStruct = &'a mut Serializer;\n\ttype SerializeStructVariant = &'a mut Serializer;\n\ttype SerializeTuple = &'a mut Serializer;\n\ttype SerializeTupleStruct = &'a mut Serializer;\n\ttype SerializeTupleVariant = &'a mut Serializer;\n\n\tfn serialize_bool(self, _v: bool) -> Result<Self::Ok, Self::Error> {\n\t\tErr(Error::serde(\"bool not supported\"))\n\t}\n\n\tfn serialize_i8(self, _v: i8) -> Result<Self::Ok, Self::Error> {\n\t\tErr(Error::serde(\"i8 not supported\"))\n\t}\n\n\tfn serialize_i16(self, _v: i16) -> Result<Self::Ok, Self::Error> {\n\t\tErr(Error::serde(\"i16 not supported\"))\n\t}\n\n\tfn serialize_i32(self, _v: i32) -> Result<Self::Ok, Self::Error> {\n\t\tErr(Error::serde(\"i32 not supported\"))\n\t}\n\n\tfn serialize_i64(self, _v: i64) -> Result<Self::Ok, Self::Error> {\n\t\tErr(Error::serde(\"i64 not supported\"))\n\t}\n\n\tfn serialize_u8(self, v: u8) -> Result<Self::Ok, Self::Error> {\n\t\tself.output.push(v);\n\t\tOk(())\n\t}\n\n\tfn serialize_u16(self, _v: u16) -> Result<Self::Ok, Self::Error> {\n\t\tErr(Error::serde(\"u16 not supported\"))\n\t}\n\n\tfn serialize_u32(self, v: u32) -> Result<Self::Ok, Self::Error> {\n\t\tself.output.extend_from_slice(&v.to_be_bytes());\n\t\tOk(())\n\t}\n\n\tfn serialize_u64(self, v: u64) -> Result<Self::Ok, Self::Error> {\n\t\tself.output.extend_from_slice(&v.to_be_bytes());\n\t\tOk(())\n\t}\n\n\tfn serialize_f32(self, _v: f32) -> Result<Self::Ok, Self::Error> {\n\t\tErr(Error::serde(\"f32 not supported\"))\n\t}\n\n\tfn serialize_f64(self, _v: f64) -> Result<Self::Ok, Self::Error> {\n\t\tErr(Error::serde(\"f64 not supported\"))\n\t}\n\n\tfn serialize_char(self, _v: char) -> Result<Self::Ok, Self::Error> {\n\t\tErr(Error::serde(\"char not supported\"))\n\t}\n\n\tfn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {\n\t\tself.serialize_bytes(v.as_bytes())\n\t}\n\n\tfn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok, Self::Error> {\n\t\tlet len = u32::try_from(v.len()).map_err(|_| Error::serde(\"bytes too long\"))?;\n\t\tself.output.extend_from_slice(&len.to_be_bytes());\n\t\tself.output.extend_from_slice(v);\n\t\tOk(())\n\t}\n\n\tfn serialize_none(self) -> Result<Self::Ok, Self::Error> { Ok(()) }\n\n\tfn serialize_some<T>(self, value: &T) -> Result<Self::Ok, Self::Error>\n\twhere\n\t\tT: serde::Serialize + ?Sized,\n\t{\n\t\tvalue.serialize(self)\n\t}\n\n\tfn serialize_unit(self) -> Result<Self::Ok, Self::Error> {\n\t\tErr(Error::serde(\"unit not supported\"))\n\t}\n\n\tfn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok, Self::Error> { Ok(()) }\n\n\tfn serialize_unit_variant(\n\t\tself,\n\t\t_name: &'static str,\n\t\tvariant_index: u32,\n\t\t_variant: &'static str,\n\t) -> Result<Self::Ok, Self::Error> {\n\t\tself.serialize_u32(variant_index)\n\t}\n\n\tfn serialize_newtype_struct<T>(\n\t\tself,\n\t\t_name: &'static str,\n\t\tvalue: &T,\n\t) -> Result<Self::Ok, Self::Error>\n\twhere\n\t\tT: serde::Serialize + ?Sized,\n\t{\n\t\tvalue.serialize(self)\n\t}\n\n\tfn serialize_newtype_variant<T>(\n\t\tself,\n\t\t_name: &'static str,\n\t\t_variant_index: u32,\n\t\t_variant: &'static str,\n\t\tvalue: &T,\n\t) -> Result<Self::Ok, Self::Error>\n\twhere\n\t\tT: serde::Serialize + ?Sized,\n\t{\n\t\tvalue.serialize(self)\n\t}\n\n\tfn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {\n\t\tif let Some(len) = len {\n\t\t\tself.serialize_u32(len.try_into().map_err(|_| Error::serde(\"sequence too long\"))?)?;\n\t\t}\n\n\t\tOk(self)\n\t}\n\n\tfn serialize_tuple_variant(\n\t\tself,\n\t\t_name: &'static str,\n\t\t_variant_index: u32,\n\t\t_variant: &'static str,\n\t\t_len: usize,\n\t) -> Result<Self::SerializeTupleVariant, Self::Error> {\n\t\tOk(self)\n\t}\n\n\tfn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, Self::Error> { Ok(self) }\n\n\tfn serialize_tuple_struct(\n\t\tself,\n\t\t_name: &'static str,\n\t\t_len: usize,\n\t) -> Result<Self::SerializeTupleStruct, Self::Error> {\n\t\tOk(self)\n\t}\n\n\tfn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {\n\t\tOk(self)\n\t}\n\n\tfn serialize_struct(\n\t\tself,\n\t\t_name: &'static str,\n\t\t_len: usize,\n\t) -> Result<Self::SerializeStruct, Self::Error> {\n\t\tOk(self)\n\t}\n\n\tfn serialize_struct_variant(\n\t\tself,\n\t\t_name: &'static str,\n\t\t_variant_index: u32,\n\t\t_variant: &'static str,\n\t\t_len: usize,\n\t) -> Result<Self::SerializeStructVariant, Self::Error> {\n\t\tErr(Error::serde(\"struct variant not supported\"))\n\t}\n\n\tfn is_human_readable(&self) -> bool { false }\n}\n\nimpl SerializeMap for &mut Serializer {\n\ttype Error = Error;\n\ttype Ok = ();\n\n\tfn serialize_key<T>(&mut self, key: &T) -> Result<(), Self::Error>\n\twhere\n\t\tT: serde::Serialize + ?Sized,\n\t{\n\t\tkey.serialize(&mut **self)\n\t}\n\n\tfn serialize_value<T>(&mut self, value: &T) -> Result<(), Self::Error>\n\twhere\n\t\tT: serde::Serialize + ?Sized,\n\t{\n\t\tvalue.serialize(&mut **self)\n\t}\n\n\tfn end(self) -> Result<Self::Ok, Self::Error> { Ok(()) }\n}\n\nimpl SerializeSeq for &mut Serializer {\n\ttype Error = Error;\n\ttype Ok = ();\n\n\tfn serialize_element<T>(&mut self, value: &T) -> Result<(), Self::Error>\n\twhere\n\t\tT: serde::Serialize + ?Sized,\n\t{\n\t\tvalue.serialize(&mut **self)\n\t}\n\n\tfn end(self) -> Result<Self::Ok, Self::Error> { Ok(()) }\n}\n\nimpl SerializeStruct for &mut Serializer {\n\ttype Error = Error;\n\ttype Ok = ();\n\n\tfn serialize_field<T>(&mut self, _key: &'static str, value: &T) -> Result<(), Self::Error>\n\twhere\n\t\tT: serde::Serialize + ?Sized,\n\t{\n\t\tvalue.serialize(&mut **self)\n\t}\n\n\tfn end(self) -> Result<Self::Ok, Self::Error> { Ok(()) }\n}\n\nimpl SerializeStructVariant for &mut Serializer {\n\ttype Error = Error;\n\ttype Ok = ();\n\n\tfn serialize_field<T>(&mut self, _key: &'static str, value: &T) -> Result<(), Self::Error>\n\twhere\n\t\tT: serde::Serialize + ?Sized,\n\t{\n\t\tvalue.serialize(&mut **self)\n\t}\n\n\tfn end(self) -> Result<Self::Ok, Self::Error> { Ok(()) }\n}\n\nimpl SerializeTuple for &mut Serializer {\n\ttype Error = Error;\n\ttype Ok = ();\n\n\tfn serialize_element<T>(&mut self, value: &T) -> Result<(), Self::Error>\n\twhere\n\t\tT: serde::Serialize + ?Sized,\n\t{\n\t\tvalue.serialize(&mut **self)\n\t}\n\n\tfn end(self) -> Result<Self::Ok, Self::Error> { Ok(()) }\n}\n\nimpl SerializeTupleStruct for &mut Serializer {\n\ttype Error = Error;\n\ttype Ok = ();\n\n\tfn serialize_field<T>(&mut self, value: &T) -> Result<(), Self::Error>\n\twhere\n\t\tT: serde::Serialize + ?Sized,\n\t{\n\t\tvalue.serialize(&mut **self)\n\t}\n\n\tfn end(self) -> Result<Self::Ok, Self::Error> { Ok(()) }\n}\n\nimpl SerializeTupleVariant for &mut Serializer {\n\ttype Error = Error;\n\ttype Ok = ();\n\n\tfn serialize_field<T>(&mut self, value: &T) -> Result<(), Self::Error>\n\twhere\n\t\tT: serde::Serialize + ?Sized,\n\t{\n\t\tvalue.serialize(&mut **self)\n\t}\n\n\tfn end(self) -> Result<Self::Ok, Self::Error> { Ok(()) }\n}\n"
  },
  {
    "path": "yazi-sftp/src/session.rs",
    "content": "use std::{any::TypeId, collections::HashMap, io::{self, ErrorKind}, sync::Arc, time::Duration};\n\nuse parking_lot::Mutex;\nuse russh::{ChannelStream, client::Msg};\nuse serde::Serialize;\nuse tokio::{io::{AsyncReadExt, AsyncWriteExt, ReadHalf, WriteHalf}, select, sync::{mpsc, oneshot}};\n\nuse crate::{Error, Id, Packet, Receiver, responses};\n\npub struct Session {\n\ttx:                    mpsc::UnboundedSender<Vec<u8>>,\n\tid:                    Id,\n\tpub(super) callback:   Mutex<HashMap<u32, oneshot::Sender<Packet<'static>>>>,\n\tpub(super) extensions: Mutex<HashMap<String, String>>,\n}\n\nimpl Drop for Session {\n\tfn drop(&mut self) { self.tx.send(vec![]).ok(); }\n}\n\nimpl Session {\n\tpub(super) fn make(stream: ChannelStream<Msg>) -> Arc<Self> {\n\t\tlet (tx, mut rx) = mpsc::unbounded_channel();\n\t\tlet me = Arc::new(Self {\n\t\t\ttx,\n\t\t\tid: Id::default(),\n\t\t\tcallback: Default::default(),\n\t\t\textensions: Default::default(),\n\t\t});\n\n\t\tasync fn read(reader: &mut ReadHalf<ChannelStream<Msg>>) -> io::Result<Vec<u8>> {\n\t\t\tlet len = reader.read_u32().await?;\n\t\t\tlet mut buf = vec![0; len as usize];\n\t\t\treader.read_exact(&mut buf).await?;\n\t\t\tOk(buf)\n\t\t}\n\n\t\tasync fn write(writer: &mut WriteHalf<ChannelStream<Msg>>, buf: Vec<u8>) -> io::Result<()> {\n\t\t\tif buf.is_empty() {\n\t\t\t\tErr(io::Error::from(ErrorKind::BrokenPipe))\n\t\t\t} else {\n\t\t\t\twriter.write_all(&buf).await\n\t\t\t}\n\t\t}\n\n\t\tlet me_ = me.clone();\n\t\tlet (mut reader, mut writer) = tokio::io::split(stream);\n\t\ttokio::spawn(async move {\n\t\t\twhile let Some(data) = rx.recv().await {\n\t\t\t\tif let Err(e) = write(&mut writer, data).await\n\t\t\t\t\t&& e.kind() == ErrorKind::BrokenPipe\n\t\t\t\t{\n\t\t\t\t\trx.close();\n\t\t\t\t\twriter.shutdown().await.ok();\n\t\t\t\t\tfor (id, cb) in me_.callback.lock().drain() {\n\t\t\t\t\t\tcb.send(responses::Status::connection_lost(id).into()).ok();\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tlet me_ = me.clone();\n\t\ttokio::spawn(async move {\n\t\t\tloop {\n\t\t\t\tselect! {\n\t\t\t\t\tresult = read(&mut reader) => {\n\t\t\t\t\t\tlet buf = match result {\n\t\t\t\t\t\t\tOk(b) => b,\n\t\t\t\t\t\t\tErr(e) if e.kind() == ErrorKind::UnexpectedEof => {\n\t\t\t\t\t\t\t\tme_.tx.send(vec![]).ok();\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tErr(_) => continue,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tif let Ok(packet) = crate::from_bytes(&buf)\n\t\t\t\t\t\t\t&& let Some(cb) = me_.callback.lock().remove(&packet.id())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcb.send(packet).ok();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t_ = me_.tx.closed() => break,\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tme\n\t}\n\n\tpub async fn send<'a, I, O>(self: &Arc<Self>, input: I) -> Result<O, Error>\n\twhere\n\t\tI: Into<Packet<'a>> + Serialize,\n\t\tO: TryFrom<Packet<'static>, Error = Error> + 'static,\n\t{\n\t\tself.send_with_timeout(input, Duration::from_secs(45)).await\n\t}\n\n\tpub fn send_sync<'a, I>(self: &Arc<Self>, input: I) -> Result<Receiver, Error>\n\twhere\n\t\tI: Into<Packet<'a>> + Serialize,\n\t{\n\t\tlet mut request: Packet = input.into();\n\t\tif request.id() == 0 {\n\t\t\trequest = request.with_id(self.id.next());\n\t\t}\n\n\t\tlet id = request.id();\n\t\tlet (tx, rx) = oneshot::channel();\n\n\t\tself.callback.lock().insert(id, tx);\n\t\tself.tx.send(crate::to_bytes(request)?)?;\n\t\tOk(Receiver::new(self, id, rx))\n\t}\n\n\tpub async fn send_with_timeout<'a, I, O>(\n\t\tself: &Arc<Self>,\n\t\tinput: I,\n\t\ttimeout: Duration,\n\t) -> Result<O, Error>\n\twhere\n\t\tI: Into<Packet<'a>> + Serialize,\n\t\tO: TryFrom<Packet<'static>, Error = Error> + 'static,\n\t{\n\t\tmatch tokio::time::timeout(timeout, self.send_sync(input)?).await?? {\n\t\t\tPacket::Status(status) if TypeId::of::<O>() != TypeId::of::<responses::Status>() => {\n\t\t\t\tErr(Error::Status(status))\n\t\t\t}\n\t\t\tresponse => response.try_into(),\n\t\t}\n\t}\n\n\tpub fn is_closed(self: &Arc<Self>) -> bool { self.tx.is_closed() }\n}\n"
  },
  {
    "path": "yazi-shared/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-shared\"\ndescription            = \"Yazi shared library\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[dependencies]\nyazi-macro = { path = \"../yazi-macro\", version = \"26.2.2\" }\n\n# External dependencies\nanyhow           = { workspace = true }\ncrossterm        = { workspace = true }\ndyn-clone        = { workspace = true }\nfoldhash         = { workspace = true }\nfutures          = { workspace = true }\nhashbrown        = { workspace = true }\nmemchr           = \"2.8.0\"\nordered-float    = { workspace = true }\nparking_lot      = { workspace = true }\npercent-encoding = { workspace = true }\nserde            = { workspace = true }\nthiserror        = { workspace = true }\ntokio            = { workspace = true }\ntyped-path       = { workspace = true }\n\n[target.\"cfg(unix)\".dependencies]\nlibc  = { workspace = true }\nuzers = { workspace = true }\n\n[target.'cfg(windows)'.dependencies]\nwindows-sys = { version = \"0.61.2\", features = [ \"Win32_UI_Shell\" ] }\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\ncrossterm = { workspace = true, features = [ \"use-dev-tty\", \"libc\" ] }\n"
  },
  {
    "path": "yazi-shared/README.md",
    "content": "# yazi-shared\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-shared/src/alias.rs",
    "content": "pub type SStr = std::borrow::Cow<'static, str>;\n"
  },
  {
    "path": "yazi-shared/src/bytes.rs",
    "content": "use std::fmt::Display;\n\nuse crate::BytePredictor;\n\npub trait BytesExt {\n\tfn display(&self) -> impl Display;\n\n\tfn kebab_cased(&self) -> bool;\n\n\tfn rsplit_pred_once<P: BytePredictor>(&self, pred: P) -> Option<(&[u8], &[u8])>;\n\n\tfn rsplit_seq_once(&self, sep: &[u8]) -> Option<(&[u8], &[u8])>;\n\n\tfn split_seq_once(&self, sep: &[u8]) -> Option<(&[u8], &[u8])>;\n}\n\nimpl BytesExt for [u8] {\n\tfn display(&self) -> impl Display {\n\t\tstruct D<'a>(&'a [u8]);\n\n\t\timpl Display for D<'_> {\n\t\t\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\t\t\tfor chunk in self.0.utf8_chunks() {\n\t\t\t\t\tchunk.valid().fmt(f)?;\n\t\t\t\t\tif !chunk.invalid().is_empty() {\n\t\t\t\t\t\tchar::REPLACEMENT_CHARACTER.fmt(f)?;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tOk(())\n\t\t\t}\n\t\t}\n\n\t\tD(self)\n\t}\n\n\tfn kebab_cased(&self) -> bool {\n\t\tself.iter().all(|&b| matches!(b, b'0'..=b'9' | b'a'..=b'z' | b'-'))\n\t}\n\n\tfn rsplit_pred_once<P: BytePredictor>(&self, pred: P) -> Option<(&[u8], &[u8])> {\n\t\tfor (i, &byte) in self.iter().enumerate().rev() {\n\t\t\tif pred.predicate(byte) {\n\t\t\t\tlet (a, b) = self.split_at(i);\n\t\t\t\treturn Some((a, &b[1..]));\n\t\t\t}\n\t\t}\n\t\tNone\n\t}\n\n\tfn split_seq_once(&self, sep: &[u8]) -> Option<(&[u8], &[u8])> {\n\t\tlet idx = memchr::memmem::find(self, sep)?;\n\t\tlet (a, b) = self.split_at(idx);\n\t\tSome((a, &b[sep.len()..]))\n\t}\n\n\tfn rsplit_seq_once(&self, sep: &[u8]) -> Option<(&[u8], &[u8])> {\n\t\tlet idx = memchr::memmem::rfind(self, sep)?;\n\t\tlet (a, b) = self.split_at(idx);\n\t\tSome((a, &b[sep.len()..]))\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/chars.rs",
    "content": "use core::str;\nuse std::borrow::Cow;\n\n#[derive(Clone, Copy, PartialEq, Eq)]\npub enum CharKind {\n\tSpace,\n\tPunct,\n\tOther,\n}\n\nimpl CharKind {\n\tpub fn new(c: char) -> Self {\n\t\tif c.is_whitespace() {\n\t\t\tSelf::Space\n\t\t} else if c.is_ascii_punctuation() {\n\t\t\tSelf::Punct\n\t\t} else {\n\t\t\tSelf::Other\n\t\t}\n\t}\n\n\tpub fn vary(self, other: Self, far: bool) -> bool {\n\t\tif far { (self == Self::Space) != (other == Self::Space) } else { self != other }\n\t}\n}\n\npub fn strip_trailing_newline(mut s: String) -> String {\n\twhile s.ends_with('\\n') || s.ends_with('\\r') {\n\t\ts.pop();\n\t}\n\ts\n}\n\npub fn replace_cow<'a, T>(s: T, from: &str, to: &str) -> Cow<'a, str>\nwhere\n\tT: Into<Cow<'a, str>>,\n{\n\tlet cow = s.into();\n\tmatch replace_cow_impl(&cow, cow.match_indices(from), to) {\n\t\tCow::Borrowed(_) => cow,\n\t\tCow::Owned(new) => Cow::Owned(new),\n\t}\n}\n\npub fn replacen_cow<'a, T>(s: T, from: &str, to: &str, n: usize) -> Cow<'a, str>\nwhere\n\tT: Into<Cow<'a, str>>,\n{\n\tlet cow = s.into();\n\tmatch replace_cow_impl(&cow, cow.match_indices(from).take(n), to) {\n\t\tCow::Borrowed(_) => cow,\n\t\tCow::Owned(now) => Cow::Owned(now),\n\t}\n}\n\nfn replace_cow_impl<'a, T>(src: &'a str, mut indices: T, to: &str) -> Cow<'a, str>\nwhere\n\tT: Iterator<Item = (usize, &'a str)>,\n{\n\tlet Some((first_idx, first_sub)) = indices.next() else {\n\t\treturn Cow::Borrowed(src);\n\t};\n\n\tlet mut result = String::with_capacity(src.len());\n\tresult += unsafe { src.get_unchecked(..first_idx) };\n\tresult += to;\n\n\tlet mut last = first_idx + first_sub.len();\n\tfor (idx, sub) in indices {\n\t\tresult += unsafe { src.get_unchecked(last..idx) };\n\t\tresult += to;\n\t\tlast = idx + sub.len();\n\t}\n\n\tCow::Owned(result + unsafe { src.get_unchecked(last..) })\n}\n\npub fn replace_vec_cow<'a>(v: &'a [u8], from: &[u8], to: &[u8]) -> Cow<'a, [u8]> {\n\tlet mut it = memchr::memmem::find_iter(v, from);\n\tlet Some(mut last) = it.next() else { return Cow::Borrowed(v) };\n\n\tlet mut out = Vec::with_capacity(v.len());\n\tout.extend_from_slice(&v[..last]);\n\tout.extend_from_slice(to);\n\tlast += from.len();\n\n\tfor idx in it {\n\t\tout.extend_from_slice(&v[last..idx]);\n\t\tout.extend_from_slice(to);\n\t\tlast = idx + from.len();\n\t}\n\n\tout.extend_from_slice(&v[last..]);\n\tCow::Owned(out)\n}\n\npub fn replace_to_printable(b: &[u8], lf: bool, tab_size: u8, replacement: bool) -> Cow<'_, [u8]> {\n\t// Fast path to skip over printable chars at the beginning of the string\n\tlet printable_len = b.iter().take_while(|&&c| !c.is_ascii_control()).count();\n\tif printable_len >= b.len() {\n\t\treturn Cow::Borrowed(b);\n\t}\n\n\tlet (printable, rest) = b.split_at(printable_len);\n\n\tlet mut out = Vec::new();\n\tout.reserve_exact(b.len() | 15);\n\tout.extend_from_slice(printable);\n\n\tfor &c in rest {\n\t\tpush_printable_char(&mut out, c, lf, tab_size, replacement);\n\t}\n\tCow::Owned(out)\n}\n\n#[inline]\npub fn push_printable_char(buf: &mut Vec<u8>, c: u8, lf: bool, tab_size: u8, replacement: bool) {\n\tmatch c {\n\t\tb'\\n' if lf => buf.push(b'\\n'),\n\t\tb'\\t' => {\n\t\t\tbuf.extend((0..tab_size).map(|_| b' '));\n\t\t}\n\t\tb'\\0'..=b'\\x1F' => {\n\t\t\tif replacement {\n\t\t\t\tbuf.extend_from_slice(&[0xef, 0xbf, 0xbd]);\n\t\t\t} else {\n\t\t\t\tbuf.push(b'^');\n\t\t\t\tbuf.push(c + b'@');\n\t\t\t}\n\t\t}\n\t\t0x7f => {\n\t\t\tif replacement {\n\t\t\t\tbuf.extend_from_slice(&[0xef, 0xbf, 0xbd]);\n\t\t\t} else {\n\t\t\t\tbuf.push(b'^');\n\t\t\t\tbuf.push(b'?');\n\t\t\t}\n\t\t}\n\t\t_ => buf.push(c),\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/completion_token.rs",
    "content": "use std::sync::{Arc, atomic::{AtomicU8, Ordering}};\n\nuse tokio::sync::Notify;\n\n#[derive(Clone, Debug, Default)]\npub struct CompletionToken {\n\tinner: Arc<(AtomicU8, Notify)>,\n}\n\nimpl CompletionToken {\n\tpub fn complete(&self, success: bool) {\n\t\tlet new = if success { 1 } else { 2 };\n\t\tself.inner.0.compare_exchange(0, new, Ordering::Relaxed, Ordering::Relaxed).ok();\n\t\tself.inner.1.notify_waiters();\n\t}\n\n\tpub fn completed(&self) -> Option<bool> {\n\t\tlet state = self.inner.0.load(Ordering::Relaxed);\n\t\tif state == 0 { None } else { Some(state == 1) }\n\t}\n\n\tpub async fn future(&self) -> bool {\n\t\tloop {\n\t\t\tif let Some(state) = self.completed() {\n\t\t\t\treturn state;\n\t\t\t}\n\n\t\t\tlet notified = self.inner.1.notified();\n\t\t\tif let Some(state) = self.completed() {\n\t\t\t\treturn state;\n\t\t\t}\n\n\t\t\tnotified.await;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/condition.rs",
    "content": "use std::str::FromStr;\n\nuse anyhow::bail;\nuse serde::{Deserialize, Deserializer, de};\n\n#[derive(Debug, PartialEq, Eq)]\npub enum ConditionOp {\n\tOr,\n\tAnd,\n\tNot,\n\n\t// Only used in `build()`\n\tSpace,\n\tLeftParen,\n\tRightParen,\n\tUnknown,\n\n\t// Only used in `eval()`\n\tTerm(String),\n}\n\nimpl ConditionOp {\n\tpub fn new(c: char) -> Self {\n\t\tmatch c {\n\t\t\t'|' => Self::Or,\n\t\t\t'&' => Self::And,\n\t\t\t'!' => Self::Not,\n\n\t\t\t'(' => Self::LeftParen,\n\t\t\t')' => Self::RightParen,\n\t\t\t_ if c.is_ascii_whitespace() => Self::Space,\n\t\t\t_ => Self::Unknown,\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn prec(&self) -> u8 {\n\t\tmatch self {\n\t\t\tSelf::Or => 1,\n\t\t\tSelf::And => 2,\n\t\t\tSelf::Not => 3,\n\t\t\t_ => 0,\n\t\t}\n\t}\n}\n\n#[derive(Debug)]\npub struct Condition {\n\tops: Vec<ConditionOp>,\n}\n\nimpl FromStr for Condition {\n\ttype Err = anyhow::Error;\n\n\tfn from_str(expr: &str) -> Result<Self, Self::Err> {\n\t\tlet cond = Self::build(expr);\n\t\tif cond.eval(|_| true).is_none() {\n\t\t\tbail!(\"Invalid condition: {expr}\");\n\t\t}\n\n\t\tOk(cond)\n\t}\n}\n\nimpl<'de> Deserialize<'de> for Condition {\n\tfn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n\twhere\n\t\tD: Deserializer<'de>,\n\t{\n\t\tlet s = String::deserialize(deserializer)?;\n\t\tFromStr::from_str(&s).map_err(de::Error::custom)\n\t}\n}\n\nimpl Condition {\n\tfn build(expr: &str) -> Self {\n\t\tlet mut stack: Vec<ConditionOp> = vec![];\n\t\tlet mut output: Vec<ConditionOp> = vec![];\n\n\t\tlet mut chars = expr.chars().peekable();\n\t\twhile let Some(token) = chars.next() {\n\t\t\tlet op = ConditionOp::new(token);\n\t\t\tmatch op {\n\t\t\t\tConditionOp::Or | ConditionOp::And | ConditionOp::Not => {\n\t\t\t\t\twhile matches!(stack.last(), Some(last) if last.prec() >= op.prec()) {\n\t\t\t\t\t\toutput.push(stack.pop().unwrap());\n\t\t\t\t\t}\n\t\t\t\t\tstack.push(op);\n\t\t\t\t}\n\t\t\t\tConditionOp::Space => continue,\n\t\t\t\tConditionOp::LeftParen => stack.push(op),\n\t\t\t\tConditionOp::RightParen => {\n\t\t\t\t\twhile matches!(stack.last(), Some(last) if last != &ConditionOp::LeftParen) {\n\t\t\t\t\t\toutput.push(stack.pop().unwrap());\n\t\t\t\t\t}\n\t\t\t\t\tstack.pop();\n\t\t\t\t}\n\t\t\t\tConditionOp::Unknown => {\n\t\t\t\t\tlet mut s = String::from(token);\n\t\t\t\t\twhile matches!(chars.peek(), Some(&c) if ConditionOp::new(c) == op) {\n\t\t\t\t\t\ts.push(chars.next().unwrap());\n\t\t\t\t\t}\n\t\t\t\t\toutput.push(ConditionOp::Term(s));\n\t\t\t\t}\n\t\t\t\tConditionOp::Term(_) => unreachable!(),\n\t\t\t}\n\t\t}\n\n\t\twhile let Some(op) = stack.pop() {\n\t\t\toutput.push(op);\n\t\t}\n\n\t\tSelf { ops: output }\n\t}\n\n\tpub fn eval(&self, f: impl Fn(&str) -> bool) -> Option<bool> {\n\t\tlet mut stack: Vec<bool> = Vec::with_capacity(self.ops.len());\n\t\tfor op in &self.ops {\n\t\t\tmatch op {\n\t\t\t\tConditionOp::Or => {\n\t\t\t\t\tlet b = stack.pop()? | stack.pop()?;\n\t\t\t\t\tstack.push(b);\n\t\t\t\t}\n\t\t\t\tConditionOp::And => {\n\t\t\t\t\tlet b = stack.pop()? & stack.pop()?;\n\t\t\t\t\tstack.push(b);\n\t\t\t\t}\n\t\t\t\tConditionOp::Not => {\n\t\t\t\t\tlet b = !stack.pop()?;\n\t\t\t\t\tstack.push(b);\n\t\t\t\t}\n\t\t\t\tConditionOp::Term(s) => {\n\t\t\t\t\tstack.push(f(s));\n\t\t\t\t}\n\t\t\t\t_ => return None,\n\t\t\t}\n\t\t}\n\n\t\tif stack.len() == 1 { Some(stack[0]) } else { None }\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/data/any.rs",
    "content": "use std::{any::Any, fmt};\n\nuse dyn_clone::DynClone;\n\npub trait DataAny: Any + Send + Sync + DynClone {\n\tfn as_any(&self) -> &dyn Any;\n\n\tfn into_any(self: Box<Self>) -> Box<dyn Any>;\n}\n\nimpl<T> DataAny for T\nwhere\n\tT: Any + Send + Sync + DynClone,\n{\n\tfn as_any(&self) -> &dyn Any { self }\n\n\tfn into_any(self: Box<Self>) -> Box<dyn Any> { self }\n}\n\nimpl Clone for Box<dyn DataAny> {\n\tfn clone(&self) -> Self { dyn_clone::clone_box(&**self) }\n}\n\nimpl fmt::Debug for dyn DataAny {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tf.debug_struct(\"DataAny\").finish_non_exhaustive()\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/data/data.rs",
    "content": "use std::borrow::Cow;\n\nuse anyhow::{Result, bail};\nuse hashbrown::HashMap;\nuse serde::{Deserialize, Serialize};\n\nuse crate::{Id, SStr, data::{DataAny, DataKey}, path::PathBufDyn, strand::{IntoStrand, StrandBuf}, url::{UrlBuf, UrlCow}};\n\n// --- Data\n#[derive(Clone, Debug, Default, Deserialize, Serialize)]\n#[serde(untagged)]\npub enum Data {\n\t#[default]\n\tNil,\n\tBoolean(bool),\n\tInteger(i64),\n\tNumber(f64),\n\tString(SStr),\n\tList(Vec<Self>),\n\tDict(HashMap<DataKey, Self>),\n\tId(Id),\n\t#[serde(skip_deserializing)]\n\tUrl(UrlBuf),\n\t#[serde(skip)]\n\tPath(PathBufDyn),\n\t#[serde(skip)]\n\tBytes(Vec<u8>),\n\t#[serde(skip)]\n\tAny(Box<dyn DataAny>),\n}\n\nimpl From<()> for Data {\n\tfn from(_: ()) -> Self { Self::Nil }\n}\n\nimpl From<bool> for Data {\n\tfn from(value: bool) -> Self { Self::Boolean(value) }\n}\n\nimpl From<i32> for Data {\n\tfn from(value: i32) -> Self { Self::Integer(value as i64) }\n}\n\nimpl From<i64> for Data {\n\tfn from(value: i64) -> Self { Self::Integer(value) }\n}\n\nimpl From<f64> for Data {\n\tfn from(value: f64) -> Self { Self::Number(value) }\n}\n\nimpl From<usize> for Data {\n\tfn from(value: usize) -> Self { Self::Id(value.into()) }\n}\n\nimpl From<String> for Data {\n\tfn from(value: String) -> Self { Self::String(Cow::Owned(value)) }\n}\n\nimpl From<SStr> for Data {\n\tfn from(value: SStr) -> Self { Self::String(value) }\n}\n\nimpl From<Id> for Data {\n\tfn from(value: Id) -> Self { Self::Id(value) }\n}\n\nimpl From<UrlBuf> for Data {\n\tfn from(value: UrlBuf) -> Self { Self::Url(value) }\n}\n\nimpl From<&UrlBuf> for Data {\n\tfn from(value: &UrlBuf) -> Self { Self::Url(value.clone()) }\n}\n\nimpl From<&str> for Data {\n\tfn from(value: &str) -> Self { Self::String(Cow::Owned(value.to_owned())) }\n}\n\nimpl TryFrom<&Data> for bool {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(value: &Data) -> Result<Self, Self::Error> {\n\t\tmatch value {\n\t\t\tData::Boolean(b) => Ok(*b),\n\t\t\tData::String(s) if s == \"no\" => Ok(false),\n\t\t\tData::String(s) if s == \"yes\" => Ok(true),\n\t\t\t_ => bail!(\"not a boolean\"),\n\t\t}\n\t}\n}\n\nimpl<'a> TryFrom<&'a Data> for &'a str {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(value: &'a Data) -> Result<Self, Self::Error> {\n\t\tmatch value {\n\t\t\tData::String(s) => Ok(s),\n\t\t\t_ => bail!(\"not a string\"),\n\t\t}\n\t}\n}\n\nimpl TryFrom<Data> for SStr {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(value: Data) -> Result<Self, Self::Error> {\n\t\tmatch value {\n\t\t\tData::String(s) => Ok(s),\n\t\t\t_ => bail!(\"not a string\"),\n\t\t}\n\t}\n}\n\nimpl<'a> TryFrom<&'a Data> for Cow<'a, str> {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(value: &'a Data) -> Result<Self, Self::Error> {\n\t\tmatch value {\n\t\t\tData::String(s) => Ok(Cow::Borrowed(s)),\n\t\t\t_ => bail!(\"not a string\"),\n\t\t}\n\t}\n}\n\nimpl TryFrom<Data> for HashMap<DataKey, Data> {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(value: Data) -> Result<Self, Self::Error> {\n\t\tmatch value {\n\t\t\tData::Dict(d) => Ok(d),\n\t\t\t_ => bail!(\"not a dict\"),\n\t\t}\n\t}\n}\n\nimpl TryFrom<&Data> for HashMap<DataKey, Data> {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(_: &Data) -> Result<Self, Self::Error> {\n\t\tbail!(\"cannot take ownership of dict from &Data\");\n\t}\n}\n\nimpl TryFrom<Data> for UrlCow<'static> {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(value: Data) -> Result<Self, Self::Error> {\n\t\tmatch value {\n\t\t\tData::String(s) => s.try_into(),\n\t\t\tData::Url(u) => Ok(u.into()),\n\t\t\tData::Bytes(b) => b.try_into(),\n\t\t\t_ => bail!(\"not a URL\"),\n\t\t}\n\t}\n}\n\nimpl<'a> TryFrom<&'a Data> for UrlCow<'a> {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(value: &'a Data) -> Result<Self, Self::Error> {\n\t\tmatch value {\n\t\t\tData::String(s) => Self::try_from(&**s),\n\t\t\tData::Url(u) => Ok(u.into()),\n\t\t\tData::Bytes(b) => b.as_slice().try_into(),\n\t\t\t_ => bail!(\"not a URL\"),\n\t\t}\n\t}\n}\n\nimpl<'a> TryFrom<&'a Data> for &'a [u8] {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(value: &'a Data) -> Result<Self, Self::Error> {\n\t\tmatch value {\n\t\t\tData::Bytes(b) => Ok(b),\n\t\t\t_ => bail!(\"not bytes\"),\n\t\t}\n\t}\n}\n\nimpl TryFrom<Data> for StrandBuf {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(value: Data) -> Result<Self, Self::Error> {\n\t\tOk(match value {\n\t\t\tData::String(s) => s.into_owned().into(),\n\t\t\tData::Path(p) => p.into_strand(),\n\t\t\tData::Bytes(b) => Self::Bytes(b),\n\t\t\t_ => bail!(\"cannot convert to StrandBuf\"),\n\t\t})\n\t}\n}\n\nimpl TryFrom<&Data> for StrandBuf {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(value: &Data) -> Result<Self, Self::Error> {\n\t\tOk(match value {\n\t\t\tData::String(s) => s.to_string().into(),\n\t\t\tData::Path(p) => p.into_strand(),\n\t\t\tData::Bytes(b) => Self::Bytes(b.clone()),\n\t\t\t_ => bail!(\"cannot convert to StrandBuf\"),\n\t\t})\n\t}\n}\n\nimpl PartialEq<bool> for Data {\n\tfn eq(&self, other: &bool) -> bool { self.try_into().is_ok_and(|b| *other == b) }\n}\n\nimpl Data {\n\tpub fn as_str(&self) -> Option<&str> {\n\t\tmatch self {\n\t\t\tSelf::String(s) => Some(s),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tpub fn as_any<T: 'static>(&self) -> Option<&T> {\n\t\tmatch self {\n\t\t\tSelf::Any(b) => (&**b).as_any().downcast_ref::<T>(),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tpub fn into_string(self) -> Option<SStr> {\n\t\tmatch self {\n\t\t\tSelf::String(s) => Some(s),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tpub fn into_any<T: 'static>(self) -> Option<T> {\n\t\tmatch self {\n\t\t\tSelf::Any(b) => b.into_any().downcast::<T>().ok().map(|b| *b),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\t// FIXME: find a better name\n\tpub fn into_any2<T: 'static>(self) -> Result<T> {\n\t\tif let Self::Any(b) = self\n\t\t\t&& let Ok(t) = b.into_any().downcast::<T>()\n\t\t{\n\t\t\tOk(*t)\n\t\t} else {\n\t\t\tbail!(\"Failed to downcast Data into {}\", std::any::type_name::<T>())\n\t\t}\n\t}\n}\n\n// --- Macros\nmacro_rules! impl_into_integer {\n\t($t:ty) => {\n\t\timpl TryFrom<&Data> for $t {\n\t\t\ttype Error = anyhow::Error;\n\n\t\t\tfn try_from(value: &Data) -> Result<Self, Self::Error> {\n\t\t\t\tOk(match value {\n\t\t\t\t\tData::Integer(i) => <$t>::try_from(*i)?,\n\t\t\t\t\tData::String(s) => s.parse()?,\n\t\t\t\t\tData::Id(i) => <$t>::try_from(i.get())?,\n\t\t\t\t\t_ => bail!(\"not an integer\"),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t};\n}\n\nmacro_rules! impl_into_number {\n\t($t:ty) => {\n\t\timpl TryFrom<&Data> for $t {\n\t\t\ttype Error = anyhow::Error;\n\n\t\t\tfn try_from(value: &Data) -> Result<Self, Self::Error> {\n\t\t\t\tOk(match value {\n\t\t\t\t\tData::Integer(i) if *i == (*i as $t as _) => *i as $t,\n\t\t\t\t\tData::Number(n) if *n == (*n as $t as _) => *n as $t,\n\t\t\t\t\tData::String(s) => s.parse()?,\n\t\t\t\t\tData::Id(i) if i.0 == (i.0 as $t as _) => i.0 as $t,\n\t\t\t\t\t_ => bail!(\"not a number\"),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t};\n}\n\nimpl_into_integer!(i8);\nimpl_into_integer!(i16);\nimpl_into_integer!(i32);\nimpl_into_integer!(i64);\nimpl_into_integer!(isize);\nimpl_into_integer!(u8);\nimpl_into_integer!(u16);\nimpl_into_integer!(u32);\nimpl_into_integer!(u64);\nimpl_into_integer!(usize);\nimpl_into_integer!(crate::Id);\n\nimpl_into_number!(f32);\nimpl_into_number!(f64);\n"
  },
  {
    "path": "yazi-shared/src/data/de.rs",
    "content": "use serde::de::{Error, IntoDeserializer, MapAccess, SeqAccess};\n\nuse crate::data::{Data, DataKey};\n\nimpl<'de> serde::Deserializer<'de> for &Data {\n\ttype Error = serde::de::value::Error;\n\n\tfn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tmatch self {\n\t\t\tData::Nil => visitor.visit_unit(),\n\t\t\tData::Boolean(b) => visitor.visit_bool(*b),\n\t\t\tData::Integer(i) => visitor.visit_i64(*i),\n\t\t\tData::Number(n) => visitor.visit_f64(*n),\n\t\t\tData::String(s) => visitor.visit_str(s),\n\t\t\tData::List(l) => visitor.visit_seq(DataSeqAccess { iter: l.iter() }),\n\t\t\tData::Dict(d) => visitor.visit_map(DataMapAccess { iter: d.iter(), value: None }),\n\t\t\tData::Id(i) => visitor.visit_u64(i.get()),\n\t\t\tData::Url(_) => Err(Error::custom(\"url not supported\")),\n\t\t\tData::Path(_) => Err(Error::custom(\"path not supported\")),\n\t\t\tData::Bytes(b) => visitor.visit_bytes(b),\n\t\t\tData::Any(_) => Err(Error::custom(\"any not supported\")),\n\t\t}\n\t}\n\n\tfn deserialize_bool<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tvisitor.visit_bool(self.try_into().map_err(Error::custom)?)\n\t}\n\n\tfn deserialize_i8<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tvisitor.visit_i8(self.try_into().map_err(Error::custom)?)\n\t}\n\n\tfn deserialize_i16<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tvisitor.visit_i16(self.try_into().map_err(Error::custom)?)\n\t}\n\n\tfn deserialize_i32<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tvisitor.visit_i32(self.try_into().map_err(Error::custom)?)\n\t}\n\n\tfn deserialize_i64<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tvisitor.visit_i64(self.try_into().map_err(Error::custom)?)\n\t}\n\n\tfn deserialize_u8<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tvisitor.visit_u8(self.try_into().map_err(Error::custom)?)\n\t}\n\n\tfn deserialize_u16<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tvisitor.visit_u16(self.try_into().map_err(Error::custom)?)\n\t}\n\n\tfn deserialize_u32<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tvisitor.visit_u32(self.try_into().map_err(Error::custom)?)\n\t}\n\n\tfn deserialize_u64<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tvisitor.visit_u64(self.try_into().map_err(Error::custom)?)\n\t}\n\n\tfn deserialize_f32<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tvisitor.visit_f32(self.try_into().map_err(Error::custom)?)\n\t}\n\n\tfn deserialize_f64<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tvisitor.visit_f64(self.try_into().map_err(Error::custom)?)\n\t}\n\n\tfn deserialize_char<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tlet s: &str = self.try_into().map_err(Error::custom)?;\n\t\tlet mut chars = s.chars();\n\t\tmatch (chars.next(), chars.next()) {\n\t\t\t(Some(ch), None) => visitor.visit_char(ch),\n\t\t\t_ => Err(Error::custom(\"not a char\")),\n\t\t}\n\t}\n\n\tfn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tvisitor.visit_str(self.try_into().map_err(Error::custom)?)\n\t}\n\n\tfn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tlet s: &str = self.try_into().map_err(Error::custom)?;\n\t\tvisitor.visit_string(s.to_owned())\n\t}\n\n\tfn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tvisitor.visit_bytes(self.try_into().map_err(Error::custom)?)\n\t}\n\n\tfn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tlet bytes: &[u8] = self.try_into().map_err(Error::custom)?;\n\t\tvisitor.visit_byte_buf(bytes.to_vec())\n\t}\n\n\tfn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tmatch self {\n\t\t\tData::Nil => visitor.visit_none(),\n\t\t\t_ => visitor.visit_some(self),\n\t\t}\n\t}\n\n\tfn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tmatch self {\n\t\t\tData::Nil => visitor.visit_unit(),\n\t\t\t_ => Err(Error::custom(\"expected unit\")),\n\t\t}\n\t}\n\n\tfn deserialize_unit_struct<V>(\n\t\tself,\n\t\t_name: &'static str,\n\t\tvisitor: V,\n\t) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tmatch self {\n\t\t\tData::Nil => visitor.visit_unit(),\n\t\t\t_ => Err(Error::custom(\"expected unit struct\")),\n\t\t}\n\t}\n\n\tfn deserialize_newtype_struct<V>(\n\t\tself,\n\t\t_name: &'static str,\n\t\tvisitor: V,\n\t) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tvisitor.visit_newtype_struct(self)\n\t}\n\n\tfn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tmatch self {\n\t\t\tData::List(l) => visitor.visit_seq(DataSeqAccess { iter: l.iter() }),\n\t\t\tData::Bytes(b) => visitor.visit_seq(DataByteSeqAccess { iter: b.iter() }),\n\t\t\t_ => Err(Error::custom(\"not a sequence\")),\n\t\t}\n\t}\n\n\tfn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tself.deserialize_seq(visitor)\n\t}\n\n\tfn deserialize_tuple_struct<V>(\n\t\tself,\n\t\t_name: &'static str,\n\t\t_len: usize,\n\t\tvisitor: V,\n\t) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tself.deserialize_seq(visitor)\n\t}\n\n\tfn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tif let Data::Dict(d) = self {\n\t\t\tvisitor.visit_map(DataMapAccess { iter: d.iter(), value: None })\n\t\t} else {\n\t\t\tErr(Error::custom(\"not a map\"))\n\t\t}\n\t}\n\n\tfn deserialize_struct<V>(\n\t\tself,\n\t\t_name: &'static str,\n\t\t_fields: &'static [&'static str],\n\t\tvisitor: V,\n\t) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tself.deserialize_map(visitor)\n\t}\n\n\tfn deserialize_enum<V>(\n\t\tself,\n\t\t_name: &'static str,\n\t\t_variants: &'static [&'static str],\n\t\tvisitor: V,\n\t) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tmatch self {\n\t\t\tData::String(s) => visitor.visit_enum((&**s).into_deserializer()),\n\t\t\t_ => Err(Error::custom(\"not an enum\")),\n\t\t}\n\t}\n\n\tfn deserialize_identifier<V>(self, _visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tErr(Error::custom(\"identifier not supported\"))\n\t}\n\n\tfn deserialize_ignored_any<V>(self, _visitor: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::Visitor<'de>,\n\t{\n\t\tErr(Error::custom(\"ignored any not supported\"))\n\t}\n}\n\nstruct DataSeqAccess<'a> {\n\titer: std::slice::Iter<'a, Data>,\n}\n\nimpl<'de> SeqAccess<'de> for DataSeqAccess<'_> {\n\ttype Error = serde::de::value::Error;\n\n\tfn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error>\n\twhere\n\t\tT: serde::de::DeserializeSeed<'de>,\n\t{\n\t\tself.iter.next().map(|value| seed.deserialize(value)).transpose()\n\t}\n\n\tfn size_hint(&self) -> Option<usize> { Some(self.iter.len()) }\n}\n\nstruct DataByteSeqAccess<'a> {\n\titer: std::slice::Iter<'a, u8>,\n}\n\nimpl<'de> SeqAccess<'de> for DataByteSeqAccess<'_> {\n\ttype Error = serde::de::value::Error;\n\n\tfn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error>\n\twhere\n\t\tT: serde::de::DeserializeSeed<'de>,\n\t{\n\t\tself.iter.next().map(|value| seed.deserialize((*value).into_deserializer())).transpose()\n\t}\n\n\tfn size_hint(&self) -> Option<usize> { Some(self.iter.len()) }\n}\n\nstruct DataMapAccess<'a> {\n\titer:  hashbrown::hash_map::Iter<'a, DataKey, Data>,\n\tvalue: Option<&'a Data>,\n}\n\nimpl<'de> MapAccess<'de> for DataMapAccess<'_> {\n\ttype Error = serde::de::value::Error;\n\n\tfn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>\n\twhere\n\t\tK: serde::de::DeserializeSeed<'de>,\n\t{\n\t\tlet Some((key, value)) = self.iter.next() else { return Ok(None) };\n\t\tself.value = Some(value);\n\n\t\tmatch key {\n\t\t\tDataKey::Boolean(b) => seed.deserialize((*b).into_deserializer()).map(Some),\n\t\t\tDataKey::Integer(i) => seed.deserialize((*i).into_deserializer()).map(Some),\n\t\t\tDataKey::Number(n) => seed.deserialize((*n).into_deserializer()).map(Some),\n\t\t\tDataKey::String(s) => seed.deserialize((&**s).into_deserializer()).map(Some),\n\t\t\tDataKey::Id(id) => seed.deserialize(id.get().into_deserializer()).map(Some),\n\t\t\t_ => Err(Error::custom(\"unsupported map key type\")),\n\t\t}\n\t}\n\n\tfn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>\n\twhere\n\t\tV: serde::de::DeserializeSeed<'de>,\n\t{\n\t\tseed.deserialize(self.value.take().ok_or_else(|| Error::custom(\"value missing for key\"))?)\n\t}\n\n\tfn size_hint(&self) -> Option<usize> { Some(self.iter.len()) }\n}\n"
  },
  {
    "path": "yazi-shared/src/data/key.rs",
    "content": "use std::borrow::Cow;\n\nuse ordered_float::OrderedFloat;\nuse serde::{Deserialize, Serialize, de};\n\nuse crate::{Id, SStr, path::PathBufDyn, url::{UrlBuf, UrlCow}};\n\n#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]\n#[serde(untagged)]\npub enum DataKey {\n\tNil,\n\tBoolean(bool),\n\t#[serde(deserialize_with = \"Self::deserialize_integer\")]\n\tInteger(i64),\n\tNumber(OrderedFloat<f64>),\n\tString(SStr),\n\tId(Id),\n\t#[serde(skip_deserializing)]\n\tUrl(UrlBuf),\n\t#[serde(skip)]\n\tPath(PathBufDyn),\n\t#[serde(skip)]\n\tBytes(Vec<u8>),\n}\n\nimpl DataKey {\n\tpub fn is_integer(&self) -> bool { matches!(self, Self::Integer(_)) }\n\n\tpub fn as_str(&self) -> Option<&str> {\n\t\tmatch self {\n\t\t\tSelf::String(s) => Some(s),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tpub fn into_url(self) -> Option<UrlCow<'static>> {\n\t\tmatch self {\n\t\t\tSelf::String(s) => s.try_into().ok(),\n\t\t\tSelf::Url(u) => Some(u.into()),\n\t\t\tSelf::Bytes(b) => b.try_into().ok(),\n\t\t\t_ => None,\n\t\t}\n\t}\n\n\tfn deserialize_integer<'de, D>(deserializer: D) -> Result<i64, D::Error>\n\twhere\n\t\tD: de::Deserializer<'de>,\n\t{\n\t\tstruct Visitor;\n\n\t\timpl de::Visitor<'_> for Visitor {\n\t\t\ttype Value = i64;\n\n\t\t\tfn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {\n\t\t\t\tformatter.write_str(\"an integer or a string of an integer\")\n\t\t\t}\n\n\t\t\tfn visit_i64<E>(self, value: i64) -> Result<i64, E> { Ok(value) }\n\n\t\t\tfn visit_str<E>(self, value: &str) -> Result<i64, E>\n\t\t\twhere\n\t\t\t\tE: de::Error,\n\t\t\t{\n\t\t\t\tvalue.parse().map_err(de::Error::custom)\n\t\t\t}\n\t\t}\n\n\t\tdeserializer.deserialize_any(Visitor)\n\t}\n}\n\nimpl From<usize> for DataKey {\n\tfn from(value: usize) -> Self { Self::Integer(value as i64) }\n}\n\nimpl From<&'static str> for DataKey {\n\tfn from(value: &'static str) -> Self { Self::String(Cow::Borrowed(value)) }\n}\n\nimpl From<String> for DataKey {\n\tfn from(value: String) -> Self { Self::String(Cow::Owned(value)) }\n}\n"
  },
  {
    "path": "yazi-shared/src/data/mod.rs",
    "content": "yazi_macro::mod_flat!(any data de key);\n"
  },
  {
    "path": "yazi-shared/src/debounce.rs",
    "content": "use std::{pin::Pin, task::{Context, Poll}, time::Duration};\n\nuse futures::{FutureExt, Stream, StreamExt};\nuse tokio::time::{Instant, Sleep, sleep};\n\npub struct Debounce<S>\nwhere\n\tS: Stream,\n{\n\tstream:   S,\n\tinterval: Duration,\n\n\tsleep: Sleep,\n\tlast:  Option<S::Item>,\n}\n\nimpl<S> Debounce<S>\nwhere\n\tS: Stream + Unpin,\n{\n\tpub fn new(stream: S, interval: Duration) -> Self {\n\t\tSelf { stream, interval, sleep: sleep(Duration::ZERO), last: None }\n\t}\n}\n\nimpl<S> Stream for Debounce<S>\nwhere\n\tS: Stream + Unpin,\n{\n\ttype Item = S::Item;\n\n\tfn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {\n\t\tlet (mut stream, interval, mut sleep, last) = unsafe {\n\t\t\tlet me = self.get_unchecked_mut();\n\t\t\t(Pin::new(&mut me.stream), me.interval, Pin::new_unchecked(&mut me.sleep), &mut me.last)\n\t\t};\n\n\t\tif sleep.poll_unpin(cx).is_ready()\n\t\t\t&& let Some(last) = last.take()\n\t\t{\n\t\t\treturn Poll::Ready(Some(last));\n\t\t}\n\n\t\twhile let Poll::Ready(next) = stream.poll_next_unpin(cx) {\n\t\t\tmatch next {\n\t\t\t\tSome(next) => {\n\t\t\t\t\t*last = Some(next);\n\t\t\t\t}\n\t\t\t\tNone if last.is_none() => {\n\t\t\t\t\treturn Poll::Ready(None);\n\t\t\t\t}\n\t\t\t\tNone => {\n\t\t\t\t\tsleep.reset(Instant::now());\n\t\t\t\t\treturn Poll::Pending;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tsleep.reset(Instant::now() + interval);\n\t\tPoll::Pending\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/env.rs",
    "content": "use std::fmt::{Display, Formatter};\n\npub static LOG_LEVEL: crate::SyncCell<LogLevel> = crate::SyncCell::new(LogLevel::None);\n\n#[inline]\npub fn env_exists(name: &str) -> bool { std::env::var_os(name).is_some_and(|s| !s.is_empty()) }\n\n#[inline]\npub fn in_wsl() -> bool {\n\t#[cfg(target_os = \"linux\")]\n\t{\n\t\tstd::fs::read(\"/proc/sys/kernel/osrelease\")\n\t\t\t.is_ok_and(|b| b.windows(11).any(|w| w == b\"-microsoft-\"))\n\t}\n\t#[cfg(not(target_os = \"linux\"))]\n\t{\n\t\tfalse\n\t}\n}\n\n#[inline]\npub fn in_ssh_connection() -> bool {\n\tenv_exists(\"SSH_CLIENT\") || env_exists(\"SSH_TTY\") || env_exists(\"SSH_CONNECTION\")\n}\n\n// LogLevel\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum LogLevel {\n\tNone,\n\tError,\n\tWarn,\n\tInfo,\n\tDebug,\n}\n\nimpl LogLevel {\n\t#[inline]\n\tpub fn is_none(self) -> bool { self == Self::None }\n}\n\nimpl From<String> for LogLevel {\n\tfn from(mut s: String) -> Self {\n\t\ts.make_ascii_uppercase();\n\t\tmatch s.as_str() {\n\t\t\t\"ERROR\" => Self::Error,\n\t\t\t\"WARN\" => Self::Warn,\n\t\t\t\"INFO\" => Self::Info,\n\t\t\t\"DEBUG\" => Self::Debug,\n\t\t\t_ => Self::None,\n\t\t}\n\t}\n}\n\nimpl AsRef<str> for LogLevel {\n\tfn as_ref(&self) -> &str {\n\t\tmatch self {\n\t\t\tSelf::None => \"yazi=NONE\",\n\t\t\tSelf::Error => \"yazi=ERROR\",\n\t\t\tSelf::Warn => \"yazi=WARN\",\n\t\t\tSelf::Info => \"yazi=INFO\",\n\t\t\tSelf::Debug => \"yazi=DEBUG\",\n\t\t}\n\t}\n}\n\nimpl Display for LogLevel {\n\tfn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, \"{}\", self.as_ref()) }\n}\n"
  },
  {
    "path": "yazi-shared/src/errors/mod.rs",
    "content": "yazi_macro::mod_flat!(peek);\n"
  },
  {
    "path": "yazi-shared/src/errors/peek.rs",
    "content": "use std::{error::Error, fmt::{self, Display}};\n\n#[derive(Debug)]\npub enum PeekError {\n\tExceed(usize),\n\tUnexpected(String),\n}\n\nimpl Display for PeekError {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tmatch self {\n\t\t\tSelf::Exceed(lines) => write!(f, \"Exceed maximum lines {lines}\"),\n\t\t\tSelf::Unexpected(msg) => write!(f, \"{msg}\"),\n\t\t}\n\t}\n}\n\nimpl Error for PeekError {}\n\nimpl From<String> for PeekError {\n\tfn from(error: String) -> Self { Self::Unexpected(error) }\n}\nimpl From<&str> for PeekError {\n\tfn from(error: &str) -> Self { Self::from(error.to_owned()) }\n}\nimpl From<anyhow::Error> for PeekError {\n\tfn from(error: anyhow::Error) -> Self { Self::from(error.to_string()) }\n}\nimpl From<std::io::Error> for PeekError {\n\tfn from(error: std::io::Error) -> Self { Self::from(error.to_string()) }\n}\nimpl From<tokio::task::JoinError> for PeekError {\n\tfn from(error: tokio::task::JoinError) -> Self { Self::from(error.to_string()) }\n}\n"
  },
  {
    "path": "yazi-shared/src/event/action.rs",
    "content": "use std::{borrow::Cow, fmt::{self, Display}, mem, str::FromStr};\n\nuse anyhow::{Result, anyhow, bail};\nuse hashbrown::HashMap;\nuse serde::{Deserialize, de};\n\nuse crate::{Layer, SStr, Source, data::{Data, DataAny, DataKey}};\n\n#[derive(Clone, Debug, Default)]\npub struct Action {\n\tpub name:   SStr,\n\tpub args:   HashMap<DataKey, Data>,\n\tpub layer:  Layer,\n\tpub source: Source,\n}\n\nimpl Action {\n\tpub fn new<N>(name: N, source: Source, default: Option<Layer>) -> Result<Self>\n\twhere\n\t\tN: Into<SStr>,\n\t{\n\t\tlet cow: SStr = name.into();\n\t\tlet (layer, name) = match cow.find(':') {\n\t\t\tNone => (default.ok_or_else(|| anyhow!(\"Cannot infer layer from action name: {cow}\"))?, cow),\n\t\t\tSome(i) => (cow[..i].parse()?, match cow {\n\t\t\t\tCow::Borrowed(s) => Cow::Borrowed(&s[i + 1..]),\n\t\t\t\tCow::Owned(mut s) => {\n\t\t\t\t\ts.drain(..i + 1);\n\t\t\t\t\tCow::Owned(s)\n\t\t\t\t}\n\t\t\t}),\n\t\t};\n\n\t\tOk(Self { name, args: Default::default(), layer, source })\n\t}\n\n\tpub fn new_relay<N>(name: N) -> Self\n\twhere\n\t\tN: Into<SStr>,\n\t{\n\t\tSelf::new(name, Source::Relay, None).unwrap_or(Self::null())\n\t}\n\n\tpub fn new_relay_args<N, D, I>(name: N, args: I) -> Self\n\twhere\n\t\tN: Into<SStr>,\n\t\tD: Into<Data>,\n\t\tI: IntoIterator<Item = D>,\n\t{\n\t\tlet mut action = Self::new(name, Source::Relay, None).unwrap_or(Self::null());\n\t\taction.args =\n\t\t\targs.into_iter().enumerate().map(|(i, a)| (DataKey::Integer(i as i64), a.into())).collect();\n\t\taction\n\t}\n\n\tfn null() -> Self { Self { name: Cow::Borrowed(\"null\"), ..Default::default() } }\n\n\tpub fn len(&self) -> usize { self.args.len() }\n\n\tpub fn is_empty(&self) -> bool { self.args.is_empty() }\n\n\t// --- With\n\tpub fn with(mut self, name: impl Into<DataKey>, value: impl Into<Data>) -> Self {\n\t\tself.args.insert(name.into(), value.into());\n\t\tself\n\t}\n\n\tpub fn with_opt(mut self, name: impl Into<DataKey>, value: Option<impl Into<Data>>) -> Self {\n\t\tif let Some(value) = value {\n\t\t\tself.args.insert(name.into(), value.into());\n\t\t}\n\t\tself\n\t}\n\n\tpub fn with_seq<I>(mut self, values: I) -> Self\n\twhere\n\t\tI: IntoIterator,\n\t\tI::Item: Into<Data>,\n\t{\n\t\tfor (i, v) in values.into_iter().enumerate() {\n\t\t\tself.args.insert(DataKey::Integer(i as i64), v.into());\n\t\t}\n\t\tself\n\t}\n\n\tpub fn with_any(mut self, name: impl Into<DataKey>, data: impl DataAny) -> Self {\n\t\tself.args.insert(name.into(), Data::Any(Box::new(data)));\n\t\tself\n\t}\n\n\t// --- Get\n\tpub fn get<'a, T>(&'a self, name: impl Into<DataKey>) -> Result<T>\n\twhere\n\t\tT: TryFrom<&'a Data>,\n\t\tT::Error: Into<anyhow::Error>,\n\t{\n\t\tlet name = name.into();\n\t\tmatch self.args.get(&name) {\n\t\t\tSome(data) => data.try_into().map_err(Into::into),\n\t\t\tNone => bail!(\"argument not found: {:?}\", name),\n\t\t}\n\t}\n\n\tpub fn str(&self, name: impl Into<DataKey>) -> &str { self.get(name).unwrap_or_default() }\n\n\tpub fn bool(&self, name: impl Into<DataKey>) -> bool { self.get(name).unwrap_or(false) }\n\n\tpub fn any<T: 'static>(&self, name: impl Into<DataKey>) -> Option<&T> {\n\t\tself.args.get(&name.into())?.as_any()\n\t}\n\n\tpub fn first<'a, T>(&'a self) -> Result<T>\n\twhere\n\t\tT: TryFrom<&'a Data>,\n\t\tT::Error: Into<anyhow::Error>,\n\t{\n\t\tself.get(0)\n\t}\n\n\tpub fn second<'a, T>(&'a self) -> Result<T>\n\twhere\n\t\tT: TryFrom<&'a Data>,\n\t\tT::Error: Into<anyhow::Error>,\n\t{\n\t\tself.get(1)\n\t}\n\n\tpub fn seq<'a, T>(&'a self) -> Vec<T>\n\twhere\n\t\tT: TryFrom<&'a Data>,\n\t{\n\t\tlet mut seq = Vec::with_capacity(self.len());\n\t\tfor i in 0..self.len() {\n\t\t\tif let Ok(data) = self.get::<&Data>(i)\n\t\t\t\t&& let Ok(v) = data.try_into()\n\t\t\t{\n\t\t\t\tseq.push(v);\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tseq\n\t}\n\n\t// --- Take\n\tpub fn take<T>(&mut self, name: impl Into<DataKey>) -> Result<T>\n\twhere\n\t\tT: TryFrom<Data>,\n\t\tT::Error: Into<anyhow::Error>,\n\t{\n\t\tlet name = name.into();\n\t\tmatch self.args.remove(&name) {\n\t\t\tSome(data) => data.try_into().map_err(Into::into),\n\t\t\tNone => bail!(\"argument not found: {:?}\", name),\n\t\t}\n\t}\n\n\tpub fn take_first<T>(&mut self) -> Result<T>\n\twhere\n\t\tT: TryFrom<Data>,\n\t\tT::Error: Into<anyhow::Error>,\n\t{\n\t\tself.take(0)\n\t}\n\n\tpub fn take_second<T>(&mut self) -> Result<T>\n\twhere\n\t\tT: TryFrom<Data>,\n\t\tT::Error: Into<anyhow::Error>,\n\t{\n\t\tself.take(1)\n\t}\n\n\tpub fn take_seq<T>(&mut self) -> Vec<T>\n\twhere\n\t\tT: TryFrom<Data>,\n\t{\n\t\tlet mut seq = Vec::with_capacity(self.len());\n\t\tfor i in 0..self.len() {\n\t\t\tif let Ok(data) = self.take::<Data>(i)\n\t\t\t\t&& let Ok(v) = data.try_into()\n\t\t\t{\n\t\t\t\tseq.push(v);\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tseq\n\t}\n\n\tpub fn take_any<T: 'static>(&mut self, name: impl Into<DataKey>) -> Option<T> {\n\t\tself.args.remove(&name.into())?.into_any()\n\t}\n\n\tpub fn take_any2<T: 'static>(&mut self, name: impl Into<DataKey>) -> Option<Result<T>> {\n\t\tself.args.remove(&name.into()).map(Data::into_any2)\n\t}\n\n\tpub fn take_any_iter<T: 'static>(&mut self) -> impl Iterator<Item = T> {\n\t\t(0..self.len()).filter_map(|i| self.args.remove(&DataKey::from(i))?.into_any())\n\t}\n\n\t// Parse\n\tpub fn parse_args<I>(words: I, last: Option<String>) -> Result<HashMap<DataKey, Data>>\n\twhere\n\t\tI: IntoIterator<Item = String>,\n\t{\n\t\tlet mut i = 0i64;\n\t\twords\n\t\t\t.into_iter()\n\t\t\t.map(|s| (s, true))\n\t\t\t.chain(last.into_iter().map(|s| (s, false)))\n\t\t\t.map(|(word, normal)| {\n\t\t\t\tlet Some(arg) = word.strip_prefix(\"--\").filter(|&s| normal && !s.is_empty()) else {\n\t\t\t\t\ti += 1;\n\t\t\t\t\treturn Ok((DataKey::Integer(i - 1), word.into()));\n\t\t\t\t};\n\n\t\t\t\tlet mut parts = arg.splitn(2, '=');\n\t\t\t\tlet key = parts.next().expect(\"at least one part\");\n\t\t\t\tlet val = parts.next().map_or(Data::Boolean(true), Data::from);\n\n\t\t\t\tOk((key.to_owned().into(), val))\n\t\t\t})\n\t\t\t.collect()\n\t}\n}\n\nimpl Display for Action {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\twrite!(f, \"{}\", self.name)?;\n\n\t\tfor i in 0..self.args.len() {\n\t\t\tlet Ok(s) = self.get::<&str>(i) else { break };\n\t\t\twrite!(f, \" {s}\")?;\n\t\t}\n\n\t\tfor (k, v) in &self.args {\n\t\t\tif let DataKey::String(k) = k {\n\t\t\t\tif v.try_into().is_ok_and(|b| b) {\n\t\t\t\t\twrite!(f, \" --{k}\")?;\n\t\t\t\t} else if let Some(s) = v.as_str() {\n\t\t\t\t\twrite!(f, \" --{k}={s}\")?;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tOk(())\n\t}\n}\n\nimpl FromStr for Action {\n\ttype Err = anyhow::Error;\n\n\tfn from_str(s: &str) -> Result<Self, Self::Err> {\n\t\tlet (mut words, last) = crate::shell::unix::split(s, true)?;\n\t\tif words.is_empty() || words[0].is_empty() {\n\t\t\tbail!(\"action name cannot be empty\");\n\t\t}\n\n\t\tlet mut me = Self::new(mem::take(&mut words[0]), Default::default(), Some(Default::default()))?;\n\t\tme.args = Self::parse_args(words.into_iter().skip(1), last)?;\n\t\tOk(me)\n\t}\n}\n\nimpl<'de> Deserialize<'de> for Action {\n\tfn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n\twhere\n\t\tD: serde::Deserializer<'de>,\n\t{\n\t\t<_>::from_str(&String::deserialize(deserializer)?).map_err(de::Error::custom)\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/event/cow.rs",
    "content": "use std::{iter, ops::Deref};\n\nuse anyhow::Result;\n\nuse super::Action;\nuse crate::data::{Data, DataKey};\n\n#[derive(Debug)]\npub enum ActionCow {\n\tOwned(Action),\n\tBorrowed(&'static Action),\n}\n\nimpl Deref for ActionCow {\n\ttype Target = Action;\n\n\tfn deref(&self) -> &Self::Target {\n\t\tmatch self {\n\t\t\tSelf::Owned(c) => c,\n\t\t\tSelf::Borrowed(c) => c,\n\t\t}\n\t}\n}\n\nimpl From<ActionCow> for () {\n\tfn from(_: ActionCow) -> Self {}\n}\n\nimpl From<Action> for ActionCow {\n\tfn from(a: Action) -> Self { Self::Owned(a) }\n}\n\nimpl From<&'static Action> for ActionCow {\n\tfn from(a: &'static Action) -> Self { Self::Borrowed(a) }\n}\n\nimpl ActionCow {\n\tpub fn take<'a, T>(&mut self, name: impl Into<DataKey>) -> Result<T>\n\twhere\n\t\tT: TryFrom<Data> + TryFrom<&'a Data>,\n\t\t<T as TryFrom<Data>>::Error: Into<anyhow::Error>,\n\t\t<T as TryFrom<&'a Data>>::Error: Into<anyhow::Error>,\n\t{\n\t\tmatch self {\n\t\t\tSelf::Owned(c) => c.take(name),\n\t\t\tSelf::Borrowed(c) => c.get(name),\n\t\t}\n\t}\n\n\tpub fn take_first<'a, T>(&mut self) -> Result<T>\n\twhere\n\t\tT: TryFrom<Data> + TryFrom<&'a Data>,\n\t\t<T as TryFrom<Data>>::Error: Into<anyhow::Error>,\n\t\t<T as TryFrom<&'a Data>>::Error: Into<anyhow::Error>,\n\t{\n\t\tmatch self {\n\t\t\tSelf::Owned(c) => c.take_first(),\n\t\t\tSelf::Borrowed(c) => c.first(),\n\t\t}\n\t}\n\n\tpub fn take_second<'a, T>(&mut self) -> Result<T>\n\twhere\n\t\tT: TryFrom<Data> + TryFrom<&'a Data>,\n\t\t<T as TryFrom<Data>>::Error: Into<anyhow::Error>,\n\t\t<T as TryFrom<&'a Data>>::Error: Into<anyhow::Error>,\n\t{\n\t\tmatch self {\n\t\t\tSelf::Owned(c) => c.take_second(),\n\t\t\tSelf::Borrowed(c) => c.second(),\n\t\t}\n\t}\n\n\tpub fn take_seq<'a, T>(&mut self) -> Vec<T>\n\twhere\n\t\tT: TryFrom<Data> + TryFrom<&'a Data>,\n\t\t<T as TryFrom<Data>>::Error: Into<anyhow::Error>,\n\t\t<T as TryFrom<&'a Data>>::Error: Into<anyhow::Error>,\n\t{\n\t\tmatch self {\n\t\t\tSelf::Owned(c) => c.take_seq(),\n\t\t\tSelf::Borrowed(c) => c.seq(),\n\t\t}\n\t}\n\n\tpub fn take_any<T: 'static>(&mut self, name: impl Into<DataKey>) -> Option<T> {\n\t\tmatch self {\n\t\t\tSelf::Owned(c) => c.take_any(name),\n\t\t\tSelf::Borrowed(_) => None,\n\t\t}\n\t}\n\n\tpub fn take_any2<T: 'static>(&mut self, name: impl Into<DataKey>) -> Option<Result<T>> {\n\t\tmatch self {\n\t\t\tSelf::Owned(c) => c.take_any2(name),\n\t\t\tSelf::Borrowed(_) => None,\n\t\t}\n\t}\n\n\tpub fn take_any_iter<'a, T: 'static>(&'a mut self) -> Box<dyn Iterator<Item = T> + 'a> {\n\t\tmatch self {\n\t\t\tSelf::Owned(c) => Box::new(c.take_any_iter()),\n\t\t\tSelf::Borrowed(_) => Box::new(iter::empty()),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/event/event.rs",
    "content": "use crossterm::event::{KeyEvent, MouseEvent};\nuse tokio::sync::mpsc;\n\nuse super::ActionCow;\nuse crate::RoCell;\n\nstatic TX: RoCell<mpsc::UnboundedSender<Event>> = RoCell::new();\nstatic RX: RoCell<mpsc::UnboundedReceiver<Event>> = RoCell::new();\n\n#[derive(Debug)]\npub enum Event {\n\tCall(ActionCow),\n\tSeq(Vec<ActionCow>),\n\tRender(bool),\n\tKey(KeyEvent),\n\tMouse(MouseEvent),\n\tResize,\n\tFocus,\n\tPaste(String),\n}\n\nimpl Event {\n\t#[inline]\n\tpub fn init() {\n\t\tlet (tx, rx) = mpsc::unbounded_channel();\n\t\tTX.init(tx);\n\t\tRX.init(rx);\n\t}\n\n\t#[inline]\n\tpub fn take() -> mpsc::UnboundedReceiver<Self> { RX.drop() }\n\n\t#[inline]\n\tpub fn emit(self) { TX.send(self).ok(); }\n}\n"
  },
  {
    "path": "yazi-shared/src/event/mod.rs",
    "content": "yazi_macro::mod_flat!(action cow event);\n\npub static NEED_RENDER: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0);\n"
  },
  {
    "path": "yazi-shared/src/id.rs",
    "content": "use std::{fmt::Display, str::FromStr, sync::atomic::{AtomicU64, Ordering}};\n\nuse serde::{Deserialize, Serialize};\n\n#[derive(\n\tClone, Copy, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize,\n)]\npub struct Id(pub u64);\n\nimpl Id {\n\t#[inline]\n\tpub const fn get(&self) -> u64 { self.0 }\n\n\tpub fn unique() -> Self { Self(crate::timestamp_us()) }\n}\n\nimpl Display for Id {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) }\n}\n\nimpl FromStr for Id {\n\ttype Err = <u64 as FromStr>::Err;\n\n\tfn from_str(s: &str) -> Result<Self, Self::Err> { s.parse().map(Self) }\n}\n\nimpl From<u64> for Id {\n\tfn from(value: u64) -> Self { Self(value) }\n}\n\nimpl From<usize> for Id {\n\tfn from(value: usize) -> Self { Self(value as u64) }\n}\n\nimpl TryFrom<i64> for Id {\n\ttype Error = <u64 as TryFrom<i64>>::Error;\n\n\tfn try_from(value: i64) -> Result<Self, Self::Error> { u64::try_from(value).map(Self) }\n}\n\nimpl PartialEq<u64> for Id {\n\tfn eq(&self, other: &u64) -> bool { self.0 == *other }\n}\n\n// --- Ids\npub struct Ids {\n\tnext: AtomicU64,\n}\n\nimpl Ids {\n\t#[inline]\n\tpub const fn new() -> Self { Self { next: AtomicU64::new(1) } }\n\n\t#[inline]\n\tpub fn next(&self) -> Id {\n\t\tloop {\n\t\t\tlet old = self.next.fetch_add(1, Ordering::Relaxed);\n\t\t\tif old != 0 {\n\t\t\t\treturn Id(old);\n\t\t\t}\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn current(&self) -> Id { Id(self.next.load(Ordering::Relaxed)) }\n}\n\nimpl Default for Ids {\n\tfn default() -> Self { Self::new() }\n}\n"
  },
  {
    "path": "yazi-shared/src/last_value.rs",
    "content": "use std::sync::Arc;\n\nuse parking_lot::Mutex;\nuse tokio::sync::Notify;\n\n#[derive(Clone, Debug, Default)]\npub struct LastValue<T> {\n\tinner: Arc<(Notify, Mutex<Option<T>>)>,\n}\n\nimpl<T> LastValue<T> {\n\tpub fn set(&self, data: T) {\n\t\t*self.inner.1.lock() = Some(data);\n\t\tself.inner.0.notify_waiters();\n\t}\n\n\tpub async fn get(&self) -> T {\n\t\tloop {\n\t\t\tlet notified = self.inner.0.notified();\n\t\t\tif let Some(data) = self.inner.1.lock().take() {\n\t\t\t\treturn data;\n\t\t\t}\n\n\t\t\tnotified.await;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/layer.rs",
    "content": "use std::{fmt::Display, str::FromStr};\n\nuse serde::Deserialize;\n\n#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy, Deserialize)]\n#[serde(rename_all = \"kebab-case\")]\npub enum Layer {\n\t#[default]\n\tApp,\n\tMgr,\n\tTasks,\n\tSpot,\n\tPick,\n\tInput,\n\tConfirm,\n\tHelp,\n\tCmp,\n\tWhich,\n\tNotify,\n}\n\nimpl Display for Layer {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tf.write_str(match self {\n\t\t\tSelf::App => \"app\",\n\t\t\tSelf::Mgr => \"mgr\",\n\t\t\tSelf::Tasks => \"tasks\",\n\t\t\tSelf::Spot => \"spot\",\n\t\t\tSelf::Pick => \"pick\",\n\t\t\tSelf::Input => \"input\",\n\t\t\tSelf::Confirm => \"confirm\",\n\t\t\tSelf::Help => \"help\",\n\t\t\tSelf::Cmp => \"cmp\",\n\t\t\tSelf::Which => \"which\",\n\t\t\tSelf::Notify => \"notify\",\n\t\t})\n\t}\n}\n\nimpl FromStr for Layer {\n\ttype Err = serde::de::value::Error;\n\n\tfn from_str(s: &str) -> Result<Self, Self::Err> {\n\t\tSelf::deserialize(serde::de::value::StrDeserializer::new(s))\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/lib.rs",
    "content": "yazi_macro::mod_pub!(data errors event loc path pool scheme shell strand translit url wtf8);\n\nyazi_macro::mod_flat!(alias bytes chars completion_token condition debounce env id last_value layer localset natsort os predictor ro_cell source sync_cell terminal tests throttle time utf8);\n\npub fn init() {\n\tLOCAL_SET.with(tokio::task::LocalSet::new);\n\n\tLOG_LEVEL.replace(<_>::from(std::env::var(\"YAZI_LOG\").unwrap_or_default()));\n\n\t#[cfg(unix)]\n\tUSERS_CACHE.with(<_>::default);\n\n\tpool::init();\n\tevent::Event::init();\n}\n"
  },
  {
    "path": "yazi-shared/src/loc/able.rs",
    "content": "use std::{ffi::{OsStr, OsString}, fmt::Debug, hash::Hash};\n\nuse crate::{path::{AsPath, AsPathView}, strand::AsStrandView};\n\n// --- LocAble\npub trait LocAble<'p>\nwhere\n\tSelf: Copy + AsStrandView<'p, Self::Strand<'p>>,\n{\n\ttype Strand<'a>: StrandAble<'a> + StrandAbleImpl<'a>;\n\ttype Owned: LocBufAble + LocBufAbleImpl + Into<Self::Owned>;\n\ttype Components<'a>: Clone + DoubleEndedIterator + AsStrandView<'a, Self::Strand<'a>>;\n}\n\nimpl<'p> LocAble<'p> for &'p std::path::Path {\n\ttype Components<'a> = std::path::Components<'a>;\n\ttype Owned = std::path::PathBuf;\n\ttype Strand<'a> = &'a OsStr;\n}\n\nimpl<'p> LocAble<'p> for &'p typed_path::UnixPath {\n\ttype Components<'a> = typed_path::UnixComponents<'a>;\n\ttype Owned = typed_path::UnixPathBuf;\n\ttype Strand<'a> = &'a [u8];\n}\n\n// --- LocBufAble\npub trait LocBufAble\nwhere\n\tSelf: 'static + AsPath + Default,\n{\n\ttype Strand<'a>: StrandAble<'a>;\n\ttype Borrowed<'a>: LocAble<'a>\n\t\t+ LocAbleImpl<'a>\n\t\t+ AsPathView<'a, Self::Borrowed<'a>>\n\t\t+ Debug\n\t\t+ Hash;\n}\n\nimpl LocBufAble for std::path::PathBuf {\n\ttype Borrowed<'a> = &'a std::path::Path;\n\ttype Strand<'a> = &'a OsStr;\n}\n\nimpl LocBufAble for typed_path::UnixPathBuf {\n\ttype Borrowed<'a> = &'a typed_path::UnixPath;\n\ttype Strand<'a> = &'a [u8];\n}\n\n// --- StrandAble\npub trait StrandAble<'a>: Copy {}\n\nimpl<'a> StrandAble<'a> for &'a OsStr {}\n\nimpl<'a> StrandAble<'a> for &'a [u8] {}\n\n// --- LocAbleImpl\npub(super) trait LocAbleImpl<'p>: LocAble<'p> {\n\tfn as_encoded_bytes(self) -> &'p [u8];\n\n\tfn components(self) -> Self::Components<'p>;\n\n\tfn empty() -> Self;\n\n\tfn file_name(self) -> Option<Self::Strand<'p>>;\n\n\tunsafe fn from_encoded_bytes_unchecked(bytes: &'p [u8]) -> Self;\n\n\tfn join<'a, T>(self, path: T) -> Self::Owned\n\twhere\n\t\tT: AsStrandView<'a, Self::Strand<'a>>;\n\n\tfn len(self) -> usize { self.as_encoded_bytes().len() }\n\n\tfn parent(self) -> Option<Self>;\n\n\tfn strip_prefix<'a, T>(self, base: T) -> Option<Self>\n\twhere\n\t\tT: AsStrandView<'a, Self::Strand<'a>>;\n\n\tfn to_path_buf(self) -> Self::Owned;\n}\n\nimpl<'p> LocAbleImpl<'p> for &'p std::path::Path {\n\tfn as_encoded_bytes(self) -> &'p [u8] { self.as_os_str().as_encoded_bytes() }\n\n\tfn components(self) -> Self::Components<'p> { self.components() }\n\n\tfn empty() -> Self { std::path::Path::new(\"\") }\n\n\tfn file_name(self) -> Option<Self::Strand<'p>> { self.file_name() }\n\n\tunsafe fn from_encoded_bytes_unchecked(bytes: &'p [u8]) -> Self {\n\t\tstd::path::Path::new(unsafe { OsStr::from_encoded_bytes_unchecked(bytes) })\n\t}\n\n\tfn join<'a, T>(self, path: T) -> Self::Owned\n\twhere\n\t\tT: AsStrandView<'a, Self::Strand<'a>>,\n\t{\n\t\tself.join(path.as_strand_view())\n\t}\n\n\tfn parent(self) -> Option<Self> { self.parent() }\n\n\tfn strip_prefix<'a, T>(self, base: T) -> Option<Self>\n\twhere\n\t\tT: AsStrandView<'a, Self::Strand<'a>>,\n\t{\n\t\tuse std::path::is_separator;\n\n\t\tlet p = self.strip_prefix(base.as_strand_view()).ok()?;\n\t\tlet mut b = p.as_encoded_bytes();\n\n\t\tif b.last().is_none_or(|&c| !is_separator(c as char)) || p.parent().is_none() {\n\t\t\treturn Some(p);\n\t\t}\n\n\t\twhile let [head @ .., last] = b\n\t\t\t&& is_separator(*last as char)\n\t\t{\n\t\t\tb = head;\n\t\t}\n\n\t\tSome(unsafe { Self::from_encoded_bytes_unchecked(b) })\n\t}\n\n\tfn to_path_buf(self) -> Self::Owned { self.to_path_buf() }\n}\n\nimpl<'p> LocAbleImpl<'p> for &'p typed_path::UnixPath {\n\tfn as_encoded_bytes(self) -> &'p [u8] { self.as_bytes() }\n\n\tfn components(self) -> Self::Components<'p> { self.components() }\n\n\tfn empty() -> Self { typed_path::UnixPath::new(\"\") }\n\n\tfn file_name(self) -> Option<Self::Strand<'p>> { self.file_name() }\n\n\tunsafe fn from_encoded_bytes_unchecked(bytes: &'p [u8]) -> Self {\n\t\ttyped_path::UnixPath::new(bytes)\n\t}\n\n\tfn join<'a, T>(self, path: T) -> Self::Owned\n\twhere\n\t\tT: AsStrandView<'a, Self::Strand<'a>>,\n\t{\n\t\tself.join(path.as_strand_view())\n\t}\n\n\tfn parent(self) -> Option<Self> { self.parent() }\n\n\tfn strip_prefix<'a, T>(self, base: T) -> Option<Self>\n\twhere\n\t\tT: AsStrandView<'a, Self::Strand<'a>>,\n\t{\n\t\tlet p = self.strip_prefix(base.as_strand_view()).ok()?;\n\t\tlet mut b = p.as_bytes();\n\n\t\tif b.last().is_none_or(|&c| c != b'/') || p.parent().is_none() {\n\t\t\treturn Some(p);\n\t\t}\n\n\t\twhile let [head @ .., b'/'] = b {\n\t\t\tb = head;\n\t\t}\n\n\t\tSome(typed_path::UnixPath::new(b))\n\t}\n\n\tfn to_path_buf(self) -> Self::Owned { self.to_path_buf() }\n}\n\n// --- LocBufAbleImpl\npub(super) trait LocBufAbleImpl: LocBufAble {\n\tfn as_encoded_bytes(&self) -> &[u8] { self.borrow().as_encoded_bytes() }\n\n\tfn borrow(&self) -> Self::Borrowed<'_>;\n\n\tunsafe fn from_encoded_bytes_unchecked(bytes: Vec<u8>) -> Self;\n\n\tfn into_encoded_bytes(self) -> Vec<u8>;\n\n\tfn len(&self) -> usize { self.borrow().len() }\n\n\tfn set_file_name<'a, T>(&mut self, name: T)\n\twhere\n\t\tT: AsStrandView<'a, Self::Strand<'a>>;\n}\n\nimpl LocBufAbleImpl for std::path::PathBuf {\n\tfn borrow(&self) -> Self::Borrowed<'_> { self.as_path() }\n\n\tunsafe fn from_encoded_bytes_unchecked(bytes: Vec<u8>) -> Self {\n\t\tSelf::from(unsafe { OsString::from_encoded_bytes_unchecked(bytes) })\n\t}\n\n\tfn into_encoded_bytes(self) -> Vec<u8> { self.into_os_string().into_encoded_bytes() }\n\n\tfn set_file_name<'a, T>(&mut self, name: T)\n\twhere\n\t\tT: AsStrandView<'a, Self::Strand<'a>>,\n\t{\n\t\tself.set_file_name(name.as_strand_view())\n\t}\n}\n\nimpl LocBufAbleImpl for typed_path::UnixPathBuf {\n\tfn borrow(&self) -> Self::Borrowed<'_> { self.as_path() }\n\n\tunsafe fn from_encoded_bytes_unchecked(bytes: Vec<u8>) -> Self { bytes.into() }\n\n\tfn into_encoded_bytes(self) -> Vec<u8> { self.into_vec() }\n\n\tfn set_file_name<'a, T>(&mut self, name: T)\n\twhere\n\t\tT: AsStrandView<'a, Self::Strand<'a>>,\n\t{\n\t\tself.set_file_name(name.as_strand_view())\n\t}\n}\n\n// --- StrandAbleImpl\npub(super) trait StrandAbleImpl<'a>: StrandAble<'a> {\n\tfn as_encoded_bytes(self) -> &'a [u8];\n\n\tfn len(self) -> usize { self.as_encoded_bytes().len() }\n}\n\nimpl<'a> StrandAbleImpl<'a> for &'a OsStr {\n\tfn as_encoded_bytes(self) -> &'a [u8] { self.as_encoded_bytes() }\n}\n\nimpl<'a> StrandAbleImpl<'a> for &'a [u8] {\n\tfn as_encoded_bytes(self) -> &'a [u8] { self }\n}\n"
  },
  {
    "path": "yazi-shared/src/loc/buf.rs",
    "content": "use std::{cmp, ffi::OsStr, fmt::{self, Debug, Formatter}, hash::{Hash, Hasher}, marker::PhantomData, mem, ops::Deref};\n\nuse anyhow::Result;\n\nuse crate::{loc::{Loc, LocAble, LocAbleImpl, LocBufAble, LocBufAbleImpl}, path::{AsPath, AsPathView, PathDyn, SetNameError}, scheme::SchemeKind, strand::AsStrandView};\n\n#[derive(Clone, Default, Eq, PartialEq)]\npub struct LocBuf<P = std::path::PathBuf> {\n\tpub(super) inner: P,\n\tpub(super) uri:   usize,\n\tpub(super) urn:   usize,\n}\n\nimpl<P> Deref for LocBuf<P>\nwhere\n\tP: LocBufAble,\n{\n\ttype Target = P;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\n// FIXME: remove\nimpl AsRef<std::path::Path> for LocBuf<std::path::PathBuf> {\n\tfn as_ref(&self) -> &std::path::Path { self.inner.as_ref() }\n}\n\nimpl<T> AsPath for LocBuf<T>\nwhere\n\tT: LocBufAble + AsPath,\n{\n\tfn as_path(&self) -> PathDyn<'_> { self.inner.as_path() }\n}\n\nimpl<T> AsPath for &LocBuf<T>\nwhere\n\tT: LocBufAble + AsPath,\n{\n\tfn as_path(&self) -> PathDyn<'_> { self.inner.as_path() }\n}\n\nimpl<P> Ord for LocBuf<P>\nwhere\n\tP: LocBufAble + Ord,\n{\n\tfn cmp(&self, other: &Self) -> cmp::Ordering { self.inner.cmp(&other.inner) }\n}\n\nimpl<P> PartialOrd for LocBuf<P>\nwhere\n\tP: LocBufAble + PartialOrd,\n{\n\tfn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {\n\t\tself.inner.partial_cmp(&other.inner)\n\t}\n}\n\n// --- Hash\nimpl<P> Hash for LocBuf<P>\nwhere\n\tP: LocBufAble + LocBufAbleImpl,\n\tfor<'a> &'a P: AsPathView<'a, P::Borrowed<'a>>,\n{\n\tfn hash<H: Hasher>(&self, state: &mut H) { self.as_loc().hash(state) }\n}\n\nimpl<P> Debug for LocBuf<P>\nwhere\n\tP: LocBufAble + LocBufAbleImpl + Debug,\n\tfor<'a> &'a P: AsPathView<'a, P::Borrowed<'a>>,\n{\n\tfn fmt(&self, f: &mut Formatter) -> fmt::Result {\n\t\tf.debug_struct(\"LocBuf\")\n\t\t\t.field(\"path\", &self.inner)\n\t\t\t.field(\"uri\", &self.uri())\n\t\t\t.field(\"urn\", &self.urn())\n\t\t\t.finish()\n\t}\n}\n\nimpl<P> From<P> for LocBuf<P>\nwhere\n\tP: LocBufAble + LocBufAbleImpl,\n\tfor<'a> &'a P: AsPathView<'a, P::Borrowed<'a>>,\n{\n\tfn from(path: P) -> Self {\n\t\tlet Loc { inner, uri, urn, _phantom } = Loc::bare(&path);\n\t\tlet len = inner.len();\n\n\t\tlet mut bytes = path.into_encoded_bytes();\n\t\tbytes.truncate(len);\n\t\tSelf { inner: unsafe { P::from_encoded_bytes_unchecked(bytes) }, uri, urn }\n\t}\n}\n\nimpl<T: ?Sized + AsRef<OsStr>> From<&T> for LocBuf<std::path::PathBuf> {\n\tfn from(value: &T) -> Self { Self::from(std::path::PathBuf::from(value)) }\n}\n\nimpl<P> LocBuf<P>\nwhere\n\tP: LocBufAble + LocBufAbleImpl,\n\tfor<'a> &'a P: AsPathView<'a, P::Borrowed<'a>>,\n{\n\tpub fn new<'a, S>(path: P, base: S, trail: S) -> Self\n\twhere\n\t\tS: for<'b> AsStrandView<'a, <P::Borrowed<'b> as LocAble<'b>>::Strand<'a>>,\n\t{\n\t\tlet loc = Self::from(path);\n\t\tlet Loc { inner, uri, urn, _phantom } = Loc::new(&loc.inner, base, trail);\n\n\t\tdebug_assert!(inner.as_encoded_bytes() == loc.inner.as_encoded_bytes());\n\t\tSelf { inner: loc.inner, uri, urn }\n\t}\n\n\tpub fn with(path: P, uri: usize, urn: usize) -> Result<Self>\n\twhere\n\t\tfor<'a> P::Borrowed<'a>: LocAble<'a>,\n\t{\n\t\tlet loc = Self::from(path);\n\t\tlet Loc { inner, uri, urn, _phantom } = Loc::with(&loc.inner, uri, urn)?;\n\n\t\tdebug_assert!(inner.as_encoded_bytes() == loc.inner.as_encoded_bytes());\n\t\tOk(Self { inner: loc.inner, uri, urn })\n\t}\n\n\tpub fn zeroed<T>(path: T) -> Self\n\twhere\n\t\tT: Into<P>,\n\t{\n\t\tlet loc = Self::from(path.into());\n\t\tlet Loc { inner, uri, urn, _phantom } = Loc::zeroed(&loc.inner);\n\n\t\tdebug_assert!(inner.as_encoded_bytes() == loc.inner.as_encoded_bytes());\n\t\tSelf { inner: loc.inner, uri, urn }\n\t}\n\n\tpub fn floated<'a, S>(path: P, base: S) -> Self\n\twhere\n\t\tS: for<'b> AsStrandView<'a, <P::Borrowed<'b> as LocAble<'b>>::Strand<'a>>,\n\t{\n\t\tlet loc = Self::from(path);\n\t\tlet Loc { inner, uri, urn, _phantom } = Loc::floated(&loc.inner, base);\n\n\t\tdebug_assert!(inner.as_encoded_bytes() == loc.inner.as_encoded_bytes());\n\t\tSelf { inner: loc.inner, uri, urn }\n\t}\n\n\tpub fn saturated(path: P, kind: SchemeKind) -> Self {\n\t\tlet loc = Self::from(path);\n\t\tlet Loc { inner, uri, urn, _phantom } = Loc::saturated(&loc.inner, kind);\n\n\t\tdebug_assert!(inner.as_encoded_bytes() == loc.inner.as_encoded_bytes());\n\t\tSelf { inner: loc.inner, uri, urn }\n\t}\n\n\t#[inline]\n\tpub fn as_loc<'a>(&'a self) -> Loc<'a, P::Borrowed<'a>> {\n\t\tLoc {\n\t\t\tinner:    self.inner.as_path_view(),\n\t\t\turi:      self.uri,\n\t\t\turn:      self.urn,\n\t\t\t_phantom: PhantomData,\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn into_inner(self) -> P { self.inner }\n\n\tpub fn try_set_name<'a, T>(&mut self, name: T) -> Result<(), SetNameError>\n\twhere\n\t\tT: AsStrandView<'a, P::Strand<'a>>,\n\t{\n\t\tlet old = self.inner.len();\n\t\tself.mutate(|path| path.set_file_name(name));\n\n\t\tlet new = self.inner.len();\n\t\tif new == old {\n\t\t\treturn Ok(());\n\t\t}\n\n\t\tif self.uri != 0 {\n\t\t\tif new > old {\n\t\t\t\tself.uri += new - old;\n\t\t\t} else {\n\t\t\t\tself.uri -= old - new;\n\t\t\t}\n\t\t}\n\t\tif self.urn != 0 {\n\t\t\tif new > old {\n\t\t\t\tself.urn += new - old;\n\t\t\t} else {\n\t\t\t\tself.urn -= old - new;\n\t\t\t}\n\t\t}\n\t\tOk(())\n\t}\n\n\t#[inline]\n\tpub fn rebase<'a, 'b>(&'a self, base: P::Borrowed<'b>) -> Self\n\twhere\n\t\t'a: 'b,\n\t\tfor<'c> <P::Borrowed<'c> as LocAble<'c>>::Owned: Into<Self>,\n\t{\n\t\tlet mut loc: Self = base.join(self.uri()).into();\n\t\t(loc.uri, loc.urn) = (self.uri, self.urn);\n\t\tloc\n\t}\n\n\t#[inline]\n\tpub fn parent(&self) -> Option<P::Borrowed<'_>> { self.as_loc().parent() }\n\n\t#[inline]\n\tfn mutate<T, F: FnOnce(&mut P) -> T>(&mut self, f: F) -> T {\n\t\tlet mut inner = mem::take(&mut self.inner);\n\t\tlet result = f(&mut inner);\n\t\tself.inner = Self::from(inner).inner;\n\t\tresult\n\t}\n}\n\n// FIXME: macro\nimpl<P> LocBuf<P>\nwhere\n\tP: LocBufAble + LocBufAbleImpl,\n\tfor<'a> &'a P: AsPathView<'a, P::Borrowed<'a>>,\n{\n\t#[inline]\n\tpub fn uri(&self) -> P::Borrowed<'_> { self.as_loc().uri() }\n\n\t#[inline]\n\tpub fn urn(&self) -> P::Borrowed<'_> { self.as_loc().urn() }\n\n\t#[inline]\n\tpub fn base(&self) -> P::Borrowed<'_> { self.as_loc().base() }\n\n\t#[inline]\n\tpub fn has_base(&self) -> bool { self.as_loc().has_base() }\n\n\t#[inline]\n\tpub fn trail(&self) -> P::Borrowed<'_> { self.as_loc().trail() }\n\n\t#[inline]\n\tpub fn has_trail(&self) -> bool { self.as_loc().has_trail() }\n}\n\nimpl LocBuf<std::path::PathBuf> {\n\tpub const fn empty() -> Self { Self { inner: std::path::PathBuf::new(), uri: 0, urn: 0 } }\n}\n\n#[cfg(test)]\nmod tests {\n\tuse std::path::{Path, PathBuf};\n\n\tuse super::*;\n\tuse crate::url::{UrlBuf, UrlLike};\n\n\t#[test]\n\tfn test_new() {\n\t\tlet loc: LocBuf = Path::new(\"/\").into();\n\t\tassert_eq!(loc.uri().as_os_str(), OsStr::new(\"\"));\n\t\tassert_eq!(loc.urn().as_os_str(), OsStr::new(\"\"));\n\t\tassert_eq!(loc.file_name(), None);\n\t\tassert_eq!(loc.base().as_os_str(), OsStr::new(\"/\"));\n\t\tassert_eq!(loc.trail().as_os_str(), OsStr::new(\"/\"));\n\n\t\tlet loc: LocBuf = Path::new(\"/root\").into();\n\t\tassert_eq!(loc.uri().as_os_str(), OsStr::new(\"root\"));\n\t\tassert_eq!(loc.urn().as_os_str(), OsStr::new(\"root\"));\n\t\tassert_eq!(loc.file_name().unwrap(), OsStr::new(\"root\"));\n\t\tassert_eq!(loc.base().as_os_str(), OsStr::new(\"/\"));\n\t\tassert_eq!(loc.trail().as_os_str(), OsStr::new(\"/\"));\n\n\t\tlet loc: LocBuf = Path::new(\"/root/code/foo/\").into();\n\t\tassert_eq!(loc.uri().as_os_str(), OsStr::new(\"foo\"));\n\t\tassert_eq!(loc.urn().as_os_str(), OsStr::new(\"foo\"));\n\t\tassert_eq!(loc.file_name().unwrap(), OsStr::new(\"foo\"));\n\t\tassert_eq!(loc.base().as_os_str(), OsStr::new(\"/root/code/\"));\n\t\tassert_eq!(loc.trail().as_os_str(), OsStr::new(\"/root/code/\"));\n\t}\n\n\t#[test]\n\tfn test_with() -> Result<()> {\n\t\tlet loc = LocBuf::<PathBuf>::with(\"/\".into(), 0, 0)?;\n\t\tassert_eq!(loc.uri().as_os_str(), OsStr::new(\"\"));\n\t\tassert_eq!(loc.urn().as_os_str(), OsStr::new(\"\"));\n\t\tassert_eq!(loc.file_name(), None);\n\t\tassert_eq!(loc.base().as_os_str(), OsStr::new(\"/\"));\n\t\tassert_eq!(loc.trail().as_os_str(), OsStr::new(\"/\"));\n\n\t\tlet loc = LocBuf::<PathBuf>::with(\"/root/code/\".into(), 1, 1)?;\n\t\tassert_eq!(loc.uri().as_os_str(), OsStr::new(\"code\"));\n\t\tassert_eq!(loc.urn().as_os_str(), OsStr::new(\"code\"));\n\t\tassert_eq!(loc.file_name().unwrap(), OsStr::new(\"code\"));\n\t\tassert_eq!(loc.base().as_os_str(), OsStr::new(\"/root/\"));\n\t\tassert_eq!(loc.trail().as_os_str(), OsStr::new(\"/root/\"));\n\n\t\tlet loc = LocBuf::<PathBuf>::with(\"/root/code/foo//\".into(), 2, 1)?;\n\t\tassert_eq!(loc.uri().as_os_str(), OsStr::new(\"code/foo\"));\n\t\tassert_eq!(loc.urn().as_os_str(), OsStr::new(\"foo\"));\n\t\tassert_eq!(loc.file_name().unwrap(), OsStr::new(\"foo\"));\n\t\tassert_eq!(loc.base().as_os_str(), OsStr::new(\"/root/\"));\n\t\tassert_eq!(loc.trail().as_os_str(), OsStr::new(\"/root/code/\"));\n\n\t\tlet loc = LocBuf::<PathBuf>::with(\"/root/code/foo//\".into(), 2, 2)?;\n\t\tassert_eq!(loc.uri().as_os_str(), OsStr::new(\"code/foo\"));\n\t\tassert_eq!(loc.urn().as_os_str(), OsStr::new(\"code/foo\"));\n\t\tassert_eq!(loc.file_name().unwrap(), OsStr::new(\"foo\"));\n\t\tassert_eq!(loc.base().as_os_str(), OsStr::new(\"/root/\"));\n\t\tassert_eq!(loc.trail().as_os_str(), OsStr::new(\"/root/\"));\n\n\t\tlet loc = LocBuf::<PathBuf>::with(\"/root/code/foo//bar/\".into(), 2, 2)?;\n\t\tassert_eq!(loc.uri().as_os_str(), OsStr::new(\"foo//bar\"));\n\t\tassert_eq!(loc.urn().as_os_str(), OsStr::new(\"foo//bar\"));\n\t\tassert_eq!(loc.file_name().unwrap(), OsStr::new(\"bar\"));\n\t\tassert_eq!(loc.base().as_os_str(), OsStr::new(\"/root/code/\"));\n\t\tassert_eq!(loc.trail().as_os_str(), OsStr::new(\"/root/code/\"));\n\n\t\tlet loc = LocBuf::<PathBuf>::with(\"/root/code/foo//bar/\".into(), 3, 2)?;\n\t\tassert_eq!(loc.uri().as_os_str(), OsStr::new(\"code/foo//bar\"));\n\t\tassert_eq!(loc.urn().as_os_str(), OsStr::new(\"foo//bar\"));\n\t\tassert_eq!(loc.file_name().unwrap(), OsStr::new(\"bar\"));\n\t\tassert_eq!(loc.base().as_os_str(), OsStr::new(\"/root/\"));\n\t\tassert_eq!(loc.trail().as_os_str(), OsStr::new(\"/root/code/\"));\n\t\tOk(())\n\t}\n\n\t#[test]\n\tfn test_set_name() -> Result<()> {\n\t\tcrate::init_tests();\n\t\tlet cases = [\n\t\t\t// Regular\n\t\t\t(\"/\", \"a\", \"/a\"),\n\t\t\t(\"/a/b\", \"c\", \"/a/c\"),\n\t\t\t// Archive\n\t\t\t(\"archive:////\", \"a.zip\", \"archive:////a.zip\"),\n\t\t\t(\"archive:////a.zip/b\", \"c\", \"archive:////a.zip/c\"),\n\t\t\t(\"archive://:2//a.zip/b\", \"c\", \"archive://:2//a.zip/c\"),\n\t\t\t(\"archive://:2:1//a.zip/b\", \"c\", \"archive://:2:1//a.zip/c\"),\n\t\t\t// Empty\n\t\t\t(\"/a\", \"\", \"/\"),\n\t\t\t(\"archive:////a.zip\", \"\", \"archive:////\"),\n\t\t\t(\"archive:////a.zip/b\", \"\", \"archive:////a.zip\"),\n\t\t\t(\"archive://:1:1//a.zip\", \"\", \"archive:////\"),\n\t\t\t(\"archive://:2//a.zip/b\", \"\", \"archive://:1//a.zip\"),\n\t\t\t(\"archive://:2:2//a.zip/b\", \"\", \"archive://:1:1//a.zip\"),\n\t\t];\n\n\t\tfor (input, name, expected) in cases {\n\t\t\tlet mut a: UrlBuf = input.parse()?;\n\t\t\tlet b: UrlBuf = expected.parse()?;\n\t\t\ta.try_set_name(name).unwrap();\n\t\t\tassert_eq!(\n\t\t\t\t(a.name(), format!(\"{a:?}\").replace(r\"\\\", \"/\")),\n\t\t\t\t(b.name(), expected.replace(r\"\\\", \"/\"))\n\t\t\t);\n\t\t}\n\n\t\tOk(())\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/loc/loc.rs",
    "content": "use std::{hash::{Hash, Hasher}, marker::PhantomData, ops::Deref};\n\nuse anyhow::{Result, bail};\n\nuse super::LocAbleImpl;\nuse crate::{loc::{LocAble, LocBuf, LocBufAble, StrandAbleImpl}, path::{AsPath, AsPathView, PathDyn}, scheme::SchemeKind, strand::AsStrandView};\n\n#[derive(Clone, Copy, Debug)]\npub struct Loc<'p, P = &'p std::path::Path> {\n\tpub(super) inner:    P,\n\tpub(super) uri:      usize,\n\tpub(super) urn:      usize,\n\tpub(super) _phantom: PhantomData<&'p ()>,\n}\n\nimpl<'p, P> Default for Loc<'p, P>\nwhere\n\tP: LocAble<'p> + LocAbleImpl<'p>,\n{\n\tfn default() -> Self { Self { inner: P::empty(), uri: 0, urn: 0, _phantom: PhantomData } }\n}\n\nimpl<'p, P> Deref for Loc<'p, P>\nwhere\n\tP: LocAble<'p>,\n{\n\ttype Target = P;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n\nimpl<'p, P> AsPath for Loc<'p, P>\nwhere\n\tP: LocAble<'p> + AsPath,\n{\n\tfn as_path(&self) -> PathDyn<'_> { self.inner.as_path() }\n}\n\n// FIXME: remove\nimpl AsRef<std::path::Path> for Loc<'_, &std::path::Path> {\n\tfn as_ref(&self) -> &std::path::Path { self.inner }\n}\n\n// --- Hash\nimpl<'p, P> Hash for Loc<'p, P>\nwhere\n\tP: LocAble<'p> + Hash,\n{\n\tfn hash<H: Hasher>(&self, state: &mut H) { self.inner.hash(state) }\n}\n\nimpl<'p, P> From<Loc<'p, P>> for LocBuf<<P as LocAble<'p>>::Owned>\nwhere\n\tP: LocAble<'p> + LocAbleImpl<'p>,\n\t<P as LocAble<'p>>::Owned: LocBufAble,\n{\n\tfn from(value: Loc<'p, P>) -> Self {\n\t\tSelf { inner: value.inner.to_path_buf(), uri: value.uri, urn: value.urn }\n\t}\n}\n\n// --- Eq\nimpl<'p, P> PartialEq for Loc<'p, P>\nwhere\n\tP: LocAble<'p> + PartialEq,\n{\n\tfn eq(&self, other: &Self) -> bool { self.inner == other.inner }\n}\n\nimpl<'p, P> Eq for Loc<'p, P> where P: LocAble<'p> + Eq {}\n\nimpl<'p, P> Loc<'p, P>\nwhere\n\tP: LocAble<'p> + LocAbleImpl<'p>,\n{\n\t#[inline]\n\tpub fn as_inner(self) -> P { self.inner }\n\n\t#[inline]\n\tpub fn as_loc(self) -> Self { self }\n\n\tpub fn bare<T>(path: T) -> Self\n\twhere\n\t\tT: AsPathView<'p, P>,\n\t{\n\t\tlet path = path.as_path_view();\n\t\tlet Some(name) = path.file_name() else {\n\t\t\tlet p = path.strip_prefix(P::empty()).unwrap();\n\t\t\treturn Self { inner: p, uri: 0, urn: 0, _phantom: PhantomData };\n\t\t};\n\n\t\tlet name_len = name.len();\n\t\tlet prefix_len = unsafe {\n\t\t\tname.as_encoded_bytes().as_ptr().offset_from_unsigned(path.as_encoded_bytes().as_ptr())\n\t\t};\n\n\t\tlet bytes = &path.as_encoded_bytes()[..prefix_len + name_len];\n\t\tSelf {\n\t\t\tinner:    unsafe { P::from_encoded_bytes_unchecked(bytes) },\n\t\t\turi:      name_len,\n\t\t\turn:      name_len,\n\t\t\t_phantom: PhantomData,\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn base(self) -> P {\n\t\tunsafe {\n\t\t\tP::from_encoded_bytes_unchecked(\n\t\t\t\tself.inner.as_encoded_bytes().get_unchecked(..self.inner.len() - self.uri),\n\t\t\t)\n\t\t}\n\t}\n\n\tpub fn floated<'a, T, S>(path: T, base: S) -> Self\n\twhere\n\t\tT: AsPathView<'p, P>,\n\t\tS: AsStrandView<'a, P::Strand<'a>>,\n\t{\n\t\tlet mut loc = Self::bare(path);\n\t\tloc.uri = loc.inner.strip_prefix(base).expect(\"Loc must start with the given base\").len();\n\t\tloc\n\t}\n\n\t#[inline]\n\tpub fn has_base(self) -> bool { self.inner.len() != self.uri }\n\n\t#[inline]\n\tpub fn has_trail(self) -> bool { self.inner.len() != self.urn }\n\n\t#[inline]\n\tpub fn is_empty(self) -> bool { self.inner.len() == 0 }\n\n\tpub fn new<'a, T, S>(path: T, base: S, trail: S) -> Self\n\twhere\n\t\tT: AsPathView<'p, P>,\n\t\tS: AsStrandView<'a, P::Strand<'a>>,\n\t{\n\t\tlet mut loc = Self::bare(path);\n\t\tloc.uri = loc.inner.strip_prefix(base).expect(\"Loc must start with the given base\").len();\n\t\tloc.urn = loc.inner.strip_prefix(trail).expect(\"Loc must start with the given trail\").len();\n\t\tloc\n\t}\n\n\t#[inline]\n\tpub fn parent(self) -> Option<P> {\n\t\tself.inner.parent().filter(|p| !p.as_encoded_bytes().is_empty())\n\t}\n\n\tpub fn saturated<'a, T>(path: T, kind: SchemeKind) -> Self\n\twhere\n\t\tT: AsPathView<'p, P>,\n\t{\n\t\tmatch kind {\n\t\t\tSchemeKind::Regular => Self::bare(path),\n\t\t\tSchemeKind::Search => Self::zeroed(path),\n\t\t\tSchemeKind::Archive => Self::zeroed(path),\n\t\t\tSchemeKind::Sftp => Self::bare(path),\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn trail(self) -> P {\n\t\tunsafe {\n\t\t\tP::from_encoded_bytes_unchecked(\n\t\t\t\tself.inner.as_encoded_bytes().get_unchecked(..self.inner.len() - self.urn),\n\t\t\t)\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn triple(self) -> (P, P, P) {\n\t\tlet len = self.inner.len();\n\n\t\tlet base = ..len - self.uri;\n\t\tlet rest = len - self.uri..len - self.urn;\n\t\tlet urn = len - self.urn..;\n\n\t\tunsafe {\n\t\t\t(\n\t\t\t\tP::from_encoded_bytes_unchecked(self.inner.as_encoded_bytes().get_unchecked(base)),\n\t\t\t\tP::from_encoded_bytes_unchecked(self.inner.as_encoded_bytes().get_unchecked(rest)),\n\t\t\t\tP::from_encoded_bytes_unchecked(self.inner.as_encoded_bytes().get_unchecked(urn)),\n\t\t\t)\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn uri(self) -> P {\n\t\tunsafe {\n\t\t\tP::from_encoded_bytes_unchecked(\n\t\t\t\tself.inner.as_encoded_bytes().get_unchecked(self.inner.len() - self.uri..),\n\t\t\t)\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn urn(self) -> P {\n\t\tunsafe {\n\t\t\tP::from_encoded_bytes_unchecked(\n\t\t\t\tself.inner.as_encoded_bytes().get_unchecked(self.inner.len() - self.urn..),\n\t\t\t)\n\t\t}\n\t}\n\n\tpub fn with<T>(path: T, uri: usize, urn: usize) -> Result<Self>\n\twhere\n\t\tT: AsPathView<'p, P>,\n\t{\n\t\tif urn > uri {\n\t\t\tbail!(\"URN cannot be longer than URI\");\n\t\t}\n\n\t\tlet mut loc = Self::bare(path);\n\t\tif uri == 0 {\n\t\t\t(loc.uri, loc.urn) = (0, 0);\n\t\t\treturn Ok(loc);\n\t\t} else if urn == 0 {\n\t\t\tloc.urn = 0;\n\t\t}\n\n\t\tlet mut it = loc.inner.components();\n\t\tfor i in 1..=uri {\n\t\t\tif it.next_back().is_none() {\n\t\t\t\tbail!(\"URI exceeds the entire URL\");\n\t\t\t}\n\t\t\tif i == urn {\n\t\t\t\tloc.urn = loc.strip_prefix(it.clone()).unwrap().len();\n\t\t\t}\n\t\t\tif i == uri {\n\t\t\t\tloc.uri = loc.strip_prefix(it).unwrap().len();\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tOk(loc)\n\t}\n\n\tpub fn zeroed<T>(path: T) -> Self\n\twhere\n\t\tT: AsPathView<'p, P>,\n\t{\n\t\tlet mut loc = Self::bare(path);\n\t\t(loc.uri, loc.urn) = (0, 0);\n\t\tloc\n\t}\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\n\t#[test]\n\tfn test_with() -> Result<()> {\n\t\tlet cases = [\n\t\t\t// Relative paths\n\t\t\t(\"tmp/test.zip/foo/bar\", 3, 2, \"test.zip/foo/bar\", \"foo/bar\"),\n\t\t\t(\"tmp/test.zip/foo/bar/\", 3, 2, \"test.zip/foo/bar\", \"foo/bar\"),\n\t\t\t// Absolute paths\n\t\t\t(\"/tmp/test.zip/foo/bar\", 3, 2, \"test.zip/foo/bar\", \"foo/bar\"),\n\t\t\t(\"/tmp/test.zip/foo/bar/\", 3, 2, \"test.zip/foo/bar\", \"foo/bar\"),\n\t\t\t// Relative path with parent components\n\t\t\t(\"tmp/test.zip/foo/bar/../..\", 5, 4, \"test.zip/foo/bar/../..\", \"foo/bar/../..\"),\n\t\t\t(\"tmp/test.zip/foo/bar/../../\", 5, 4, \"test.zip/foo/bar/../..\", \"foo/bar/../..\"),\n\t\t\t// Absolute path with parent components\n\t\t\t(\"/tmp/test.zip/foo/bar/../..\", 5, 4, \"test.zip/foo/bar/../..\", \"foo/bar/../..\"),\n\t\t\t(\"/tmp/test.zip/foo/bar/../../\", 5, 4, \"test.zip/foo/bar/../..\", \"foo/bar/../..\"),\n\t\t];\n\n\t\tfor (path, uri, urn, expect_uri, expect_urn) in cases {\n\t\t\tlet loc = Loc::with(std::path::Path::new(path), uri, urn)?;\n\t\t\tassert_eq!(loc.uri().to_str().unwrap(), expect_uri);\n\t\t\tassert_eq!(loc.urn().to_str().unwrap(), expect_urn);\n\n\t\t\tlet loc = Loc::with(typed_path::UnixPath::new(path), uri, urn)?;\n\t\t\tassert_eq!(loc.uri().to_str().unwrap(), expect_uri);\n\t\t\tassert_eq!(loc.urn().to_str().unwrap(), expect_urn);\n\t\t}\n\n\t\tOk(())\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/loc/mod.rs",
    "content": "#![allow(private_bounds)]\n\nyazi_macro::mod_flat!(able buf loc);\n"
  },
  {
    "path": "yazi-shared/src/localset.rs",
    "content": "use tokio::task::LocalSet;\n\nuse crate::RoCell;\n\npub static LOCAL_SET: RoCell<LocalSet> = RoCell::new();\n"
  },
  {
    "path": "yazi-shared/src/natsort.rs",
    "content": "// A natural sort implementation in Rust.\n// Copyright (c) 2023, sxyazi.\n//\n// This is a port of the C version of Martin Pool's `strnatcmp.c`:\n// http://sourcefrog.net/projects/natsort/\n\nuse std::cmp::Ordering;\n\nmacro_rules! return_unless_equal {\n\t($ord:expr) => {\n\t\tmatch $ord {\n\t\t\tOrdering::Equal => {}\n\t\t\tord => return ord,\n\t\t}\n\t};\n}\n\n#[inline(always)]\nfn compare_left(left: &[u8], right: &[u8], li: &mut usize, ri: &mut usize) -> Ordering {\n\tlet mut l;\n\tlet mut r;\n\n\tloop {\n\t\tl = left.get(*li);\n\t\tr = right.get(*ri);\n\n\t\tmatch (l.is_some_and(|b| b.is_ascii_digit()), r.is_some_and(|b| b.is_ascii_digit())) {\n\t\t\t(true, true) => {\n\t\t\t\treturn_unless_equal!(unsafe { l.unwrap_unchecked().cmp(r.unwrap_unchecked()) })\n\t\t\t}\n\t\t\t(true, false) => return Ordering::Greater,\n\t\t\t(false, true) => return Ordering::Less,\n\t\t\t(false, false) => return Ordering::Equal,\n\t\t}\n\n\t\t*li += 1;\n\t\t*ri += 1;\n\t}\n}\n\n#[inline(always)]\nfn compare_right(left: &[u8], right: &[u8], li: &mut usize, ri: &mut usize) -> Ordering {\n\tlet mut l;\n\tlet mut r;\n\tlet mut bias = Ordering::Equal;\n\n\tloop {\n\t\tl = left.get(*li);\n\t\tr = right.get(*ri);\n\n\t\tmatch (l.is_some_and(|b| b.is_ascii_digit()), r.is_some_and(|b| b.is_ascii_digit())) {\n\t\t\t(true, true) => {\n\t\t\t\tif bias == Ordering::Equal {\n\t\t\t\t\tbias = unsafe { l.unwrap_unchecked().cmp(r.unwrap_unchecked()) };\n\t\t\t\t}\n\t\t\t}\n\t\t\t(true, false) => return Ordering::Greater,\n\t\t\t(false, true) => return Ordering::Less,\n\t\t\t(false, false) => return bias,\n\t\t}\n\n\t\t*li += 1;\n\t\t*ri += 1;\n\t}\n}\n\npub fn natsort(left: &[u8], right: &[u8], insensitive: bool) -> Ordering {\n\tlet mut li = 0;\n\tlet mut ri = 0;\n\n\tlet mut l = left.get(li);\n\tlet mut r = right.get(ri);\n\n\tmacro_rules! left_next {\n\t\t() => {{\n\t\t\tli += 1;\n\t\t\tl = left.get(li);\n\t\t}};\n\t}\n\n\tmacro_rules! right_next {\n\t\t() => {{\n\t\t\tri += 1;\n\t\t\tr = right.get(ri);\n\t\t}};\n\t}\n\n\tloop {\n\t\twhile l.is_some_and(|c| c.is_ascii_whitespace()) {\n\t\t\tleft_next!();\n\t\t}\n\t\twhile r.is_some_and(|c| c.is_ascii_whitespace()) {\n\t\t\tright_next!();\n\t\t}\n\n\t\tmatch (l, r) {\n\t\t\t(Some(&ll), Some(&rr)) => {\n\t\t\t\tif ll.is_ascii_digit() && rr.is_ascii_digit() {\n\t\t\t\t\tif ll == b'0' || rr == b'0' {\n\t\t\t\t\t\treturn_unless_equal!(compare_left(left, right, &mut li, &mut ri));\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn_unless_equal!(compare_right(left, right, &mut li, &mut ri));\n\t\t\t\t\t}\n\n\t\t\t\t\tl = left.get(li);\n\t\t\t\t\tr = right.get(ri);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif insensitive {\n\t\t\t\t\treturn_unless_equal!(ll.to_ascii_lowercase().cmp(&rr.to_ascii_lowercase()));\n\t\t\t\t} else {\n\t\t\t\t\treturn_unless_equal!(ll.cmp(&rr));\n\t\t\t\t}\n\t\t\t}\n\t\t\t(Some(_), None) => return Ordering::Greater,\n\t\t\t(None, Some(_)) => return Ordering::Less,\n\t\t\t(None, None) => return Ordering::Equal,\n\t\t}\n\n\t\tleft_next!();\n\t\tright_next!();\n\t}\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\n\tfn cmp(left: &[&str]) {\n\t\tlet mut right = left.to_vec();\n\t\tright.sort_by(|a, b| natsort(a.as_bytes(), b.as_bytes(), true));\n\t\tassert_eq!(left, right);\n\t}\n\n\t#[test]\n\tfn test_natsort() {\n\t\tlet dates = [\"1999-3-3\", \"1999-12-25\", \"2000-1-2\", \"2000-1-10\", \"2000-3-23\"];\n\t\tlet fractions = [\n\t\t\t\"1.002.01\", \"1.002.03\", \"1.002.08\", \"1.009.02\", \"1.009.10\", \"1.009.20\", \"1.010.12\",\n\t\t\t\"1.011.02\",\n\t\t];\n\t\tlet words = [\n\t\t\t\"1-02\",\n\t\t\t\"1-2\",\n\t\t\t\"1-20\",\n\t\t\t\"10-20\",\n\t\t\t\"fred\",\n\t\t\t\"jane\",\n\t\t\t\"pic01\",\n\t\t\t\"pic02\",\n\t\t\t\"pic02a\",\n\t\t\t\"pic02000\",\n\t\t\t\"pic05\",\n\t\t\t\"pic2\",\n\t\t\t\"pic3\",\n\t\t\t\"pic4\",\n\t\t\t\"pic 4 else\",\n\t\t\t\"pic 5\",\n\t\t\t\"pic 5 \",\n\t\t\t\"pic 5 something\",\n\t\t\t\"pic 6\",\n\t\t\t\"pic   7\",\n\t\t\t\"pic100\",\n\t\t\t\"pic100a\",\n\t\t\t\"pic120\",\n\t\t\t\"pic121\",\n\t\t\t\"tom\",\n\t\t\t\"x2-g8\",\n\t\t\t\"x2-y08\",\n\t\t\t\"x2-y7\",\n\t\t\t\"x8-y8\",\n\t\t];\n\n\t\tcmp(&dates);\n\t\tcmp(&fractions);\n\t\tcmp(&words);\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/os.rs",
    "content": "#[cfg(unix)]\npub static USERS_CACHE: crate::RoCell<uzers::UsersCache> = crate::RoCell::new();\n\n#[cfg(unix)]\npub fn hostname() -> Option<&'static str> {\n\tstatic CACHE: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();\n\n\tCACHE.get_or_init(|| hostname_impl().ok()).as_deref()\n}\n\n#[cfg(unix)]\nfn hostname_impl() -> Result<String, std::io::Error> {\n\tuse libc::{gethostname, strlen};\n\n\tlet mut s = [0; 256];\n\tlet len = unsafe {\n\t\tif gethostname(s.as_mut_ptr() as *mut _, 255) == -1 {\n\t\t\treturn Err(std::io::Error::last_os_error());\n\t\t}\n\n\t\tstrlen(s.as_ptr() as *const _)\n\t};\n\n\tstd::str::from_utf8(&s[..len])\n\t\t.map_err(|_| std::io::Error::other(\"invalid hostname\"))\n\t\t.map(|s| s.to_owned())\n}\n\n#[cfg(unix)]\npub fn session_leader() -> bool { unsafe { libc::getsid(0) == libc::getpid() } }\n"
  },
  {
    "path": "yazi-shared/src/path/buf.rs",
    "content": "use std::{borrow::Cow, ffi::OsString, hash::{Hash, Hasher}};\n\nuse hashbrown::Equivalent;\n\nuse crate::{path::{AsPath, Component, PathDyn, PathDynError, PathKind, SetNameError}, strand::AsStrand, wtf8::FromWtf8Vec};\n\n// --- PathBufDyn\n#[derive(Clone, Debug, Eq)]\npub enum PathBufDyn {\n\tOs(std::path::PathBuf),\n\tUnix(typed_path::UnixPathBuf),\n}\n\nimpl From<&std::path::Path> for PathBufDyn {\n\tfn from(value: &std::path::Path) -> Self { Self::Os(value.into()) }\n}\n\nimpl From<std::path::PathBuf> for PathBufDyn {\n\tfn from(value: std::path::PathBuf) -> Self { Self::Os(value) }\n}\n\nimpl From<typed_path::UnixPathBuf> for PathBufDyn {\n\tfn from(value: typed_path::UnixPathBuf) -> Self { Self::Unix(value) }\n}\n\nimpl From<PathDyn<'_>> for PathBufDyn {\n\tfn from(value: PathDyn<'_>) -> Self { value.to_owned() }\n}\n\nimpl From<Cow<'_, std::path::Path>> for PathBufDyn {\n\tfn from(value: Cow<'_, std::path::Path>) -> Self { Self::Os(value.into_owned()) }\n}\n\nimpl TryFrom<PathBufDyn> for std::path::PathBuf {\n\ttype Error = PathDynError;\n\n\tfn try_from(value: PathBufDyn) -> Result<Self, Self::Error> { value.into_os() }\n}\n\nimpl TryFrom<PathBufDyn> for typed_path::UnixPathBuf {\n\ttype Error = PathDynError;\n\n\tfn try_from(value: PathBufDyn) -> Result<Self, Self::Error> { value.into_unix() }\n}\n\n// --- Hash\nimpl Hash for PathBufDyn {\n\tfn hash<H: Hasher>(&self, state: &mut H) { self.as_path().hash(state) }\n}\n\n// --- PartialEq\nimpl PartialEq for PathBufDyn {\n\tfn eq(&self, other: &Self) -> bool { self.as_path() == other.as_path() }\n}\n\nimpl PartialEq<PathDyn<'_>> for PathBufDyn {\n\tfn eq(&self, other: &PathDyn<'_>) -> bool { self.as_path() == *other }\n}\n\nimpl PartialEq<PathDyn<'_>> for &PathBufDyn {\n\tfn eq(&self, other: &PathDyn<'_>) -> bool { self.as_path() == *other }\n}\n\nimpl Equivalent<PathDyn<'_>> for PathBufDyn {\n\tfn equivalent(&self, key: &PathDyn<'_>) -> bool { self.as_path() == *key }\n}\n\nimpl PathBufDyn {\n\t#[inline]\n\tpub unsafe fn from_encoded_bytes<K>(kind: K, bytes: Vec<u8>) -> Self\n\twhere\n\t\tK: Into<PathKind>,\n\t{\n\t\tmatch kind.into() {\n\t\t\tPathKind::Os => Self::Os(unsafe { OsString::from_encoded_bytes_unchecked(bytes) }.into()),\n\t\t\tPathKind::Unix => Self::Unix(bytes.into()),\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn from_components<'a, K, I>(kind: K, iter: I) -> Result<Self, PathDynError>\n\twhere\n\t\tK: Into<PathKind>,\n\t\tI: IntoIterator<Item = Component<'a>>,\n\t{\n\t\tOk(match kind.into() {\n\t\t\tPathKind::Os => Self::Os(iter.into_iter().collect::<Result<_, PathDynError>>()?),\n\t\t\tPathKind::Unix => Self::Unix(iter.into_iter().collect::<Result<_, PathDynError>>()?),\n\t\t})\n\t}\n\n\tpub fn into_encoded_bytes(self) -> Vec<u8> {\n\t\tmatch self {\n\t\t\tSelf::Os(p) => p.into_os_string().into_encoded_bytes(),\n\t\t\tSelf::Unix(p) => p.into_vec(),\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn into_os(self) -> Result<std::path::PathBuf, PathDynError> {\n\t\tOk(match self {\n\t\t\tSelf::Os(p) => p,\n\t\t\tSelf::Unix(_) => Err(PathDynError::AsOs)?,\n\t\t})\n\t}\n\n\t#[inline]\n\tpub fn into_unix(self) -> Result<typed_path::UnixPathBuf, PathDynError> {\n\t\tOk(match self {\n\t\t\tSelf::Os(_) => Err(PathDynError::AsUnix)?,\n\t\t\tSelf::Unix(p) => p,\n\t\t})\n\t}\n\n\t#[inline]\n\tpub fn new(kind: PathKind) -> Self {\n\t\tmatch kind {\n\t\t\tPathKind::Os => Self::Os(std::path::PathBuf::new()),\n\t\t\tPathKind::Unix => Self::Unix(typed_path::UnixPathBuf::new()),\n\t\t}\n\t}\n\n\tpub fn try_extend<T>(&mut self, paths: T) -> Result<(), PathDynError>\n\twhere\n\t\tT: IntoIterator,\n\t\tT::Item: AsPath,\n\t{\n\t\tfor p in paths {\n\t\t\tself.try_push(p)?;\n\t\t}\n\t\tOk(())\n\t}\n\n\tpub fn try_push<T>(&mut self, path: T) -> Result<(), PathDynError>\n\twhere\n\t\tT: AsPath,\n\t{\n\t\tlet path = path.as_path();\n\t\tOk(match self {\n\t\t\tSelf::Os(p) => p.push(path.as_os()?),\n\t\t\tSelf::Unix(p) => p.push(path.encoded_bytes()),\n\t\t})\n\t}\n\n\tpub fn try_set_name<T>(&mut self, name: T) -> Result<(), SetNameError>\n\twhere\n\t\tT: AsStrand,\n\t{\n\t\tlet s = name.as_strand();\n\t\tOk(match self {\n\t\t\tSelf::Os(p) => p.set_file_name(s.as_os()?),\n\t\t\tSelf::Unix(p) => p.set_file_name(s.encoded_bytes()),\n\t\t})\n\t}\n\n\tpub fn with<K>(kind: K, bytes: Vec<u8>) -> Result<Self, PathDynError>\n\twhere\n\t\tK: Into<PathKind>,\n\t{\n\t\tOk(match kind.into() {\n\t\t\tPathKind::Os => {\n\t\t\t\tSelf::Os(std::path::PathBuf::from_wtf8_vec(bytes).map_err(|_| PathDynError::AsOs)?)\n\t\t\t}\n\t\t\tPathKind::Unix => Self::Unix(bytes.into()),\n\t\t})\n\t}\n\n\tpub fn with_capacity<K>(kind: K, capacity: usize) -> Self\n\twhere\n\t\tK: Into<PathKind>,\n\t{\n\t\tmatch kind.into() {\n\t\t\tPathKind::Os => Self::Os(std::path::PathBuf::with_capacity(capacity)),\n\t\t\tPathKind::Unix => Self::Unix(typed_path::UnixPathBuf::with_capacity(capacity)),\n\t\t}\n\t}\n\n\tpub fn with_str<K, S>(kind: K, s: S) -> Self\n\twhere\n\t\tK: Into<PathKind>,\n\t\tS: Into<String>,\n\t{\n\t\tlet s = s.into();\n\t\tmatch kind.into() {\n\t\t\tPathKind::Os => Self::Os(s.into()),\n\t\t\tPathKind::Unix => Self::Unix(s.into()),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/path/component.rs",
    "content": "use std::path::PrefixComponent;\n\nuse crate::{path::PathDynError, strand::Strand};\n\n#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]\npub enum Component<'a> {\n\tPrefix(PrefixComponent<'a>),\n\tRootDir,\n\tCurDir,\n\tParentDir,\n\tNormal(Strand<'a>),\n}\n\nimpl<'a> Component<'a> {\n\tpub fn as_normal(&self) -> Option<Strand<'a>> {\n\t\tmatch self {\n\t\t\tSelf::Normal(s) => Some(*s),\n\t\t\t_ => None,\n\t\t}\n\t}\n}\n\nimpl<'a> From<std::path::Component<'a>> for Component<'a> {\n\tfn from(value: std::path::Component<'a>) -> Self {\n\t\tmatch value {\n\t\t\tstd::path::Component::Prefix(p) => Self::Prefix(p),\n\t\t\tstd::path::Component::RootDir => Self::RootDir,\n\t\t\tstd::path::Component::CurDir => Self::CurDir,\n\t\t\tstd::path::Component::ParentDir => Self::ParentDir,\n\t\t\tstd::path::Component::Normal(s) => Self::Normal(Strand::Os(s)),\n\t\t}\n\t}\n}\n\nimpl<'a> From<typed_path::UnixComponent<'a>> for Component<'a> {\n\tfn from(value: typed_path::UnixComponent<'a>) -> Self {\n\t\tmatch value {\n\t\t\ttyped_path::UnixComponent::RootDir => Self::RootDir,\n\t\t\ttyped_path::UnixComponent::CurDir => Self::CurDir,\n\t\t\ttyped_path::UnixComponent::ParentDir => Self::ParentDir,\n\t\t\ttyped_path::UnixComponent::Normal(b) => Self::Normal(Strand::Bytes(b)),\n\t\t}\n\t}\n}\n\nimpl<'a> TryFrom<Component<'a>> for std::path::Component<'a> {\n\ttype Error = PathDynError;\n\n\tfn try_from(value: Component<'a>) -> Result<Self, Self::Error> {\n\t\tOk(match value {\n\t\t\tComponent::Prefix(p) => Self::Prefix(p),\n\t\t\tComponent::RootDir => Self::RootDir,\n\t\t\tComponent::CurDir => Self::CurDir,\n\t\t\tComponent::ParentDir => Self::ParentDir,\n\t\t\tComponent::Normal(s) => Self::Normal(s.as_os()?),\n\t\t})\n\t}\n}\n\nimpl<'a> TryFrom<Component<'a>> for typed_path::UnixComponent<'a> {\n\ttype Error = PathDynError;\n\n\tfn try_from(value: Component<'a>) -> Result<Self, Self::Error> {\n\t\tOk(match value {\n\t\t\tComponent::Prefix(_) => Err(PathDynError::AsUnix)?,\n\t\t\tComponent::RootDir => Self::RootDir,\n\t\t\tComponent::CurDir => Self::CurDir,\n\t\t\tComponent::ParentDir => Self::ParentDir,\n\t\t\tComponent::Normal(s) => Self::Normal(s.encoded_bytes()),\n\t\t})\n\t}\n}\n\nimpl<'a> FromIterator<Component<'a>> for Result<std::path::PathBuf, PathDynError> {\n\tfn from_iter<I: IntoIterator<Item = Component<'a>>>(iter: I) -> Self {\n\t\titer.into_iter().map(std::path::Component::try_from).collect()\n\t}\n}\n\nimpl<'a> FromIterator<Component<'a>> for Result<typed_path::UnixPathBuf, PathDynError> {\n\tfn from_iter<I: IntoIterator<Item = Component<'a>>>(iter: I) -> Self {\n\t\titer.into_iter().map(typed_path::UnixComponent::try_from).collect()\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/path/components.rs",
    "content": "use std::iter::FusedIterator;\n\nuse typed_path::Components as _;\n\nuse crate::{path::{Component, PathDyn}, strand::Strand};\n\n#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]\npub enum Components<'a> {\n\tOs(std::path::Components<'a>),\n\tUnix(typed_path::UnixComponents<'a>),\n}\n\nimpl<'a> Components<'a> {\n\tpub fn path(&self) -> PathDyn<'a> {\n\t\tmatch self {\n\t\t\tSelf::Os(c) => PathDyn::Os(c.as_path()),\n\t\t\tSelf::Unix(c) => PathDyn::Unix(c.as_path()),\n\t\t}\n\t}\n\n\tpub fn strand(&self) -> Strand<'a> {\n\t\tmatch self {\n\t\t\tSelf::Os(c) => Strand::Os(c.as_path().as_os_str()),\n\t\t\tSelf::Unix(c) => Strand::Bytes(c.as_bytes()),\n\t\t}\n\t}\n}\n\nimpl<'a> Iterator for Components<'a> {\n\ttype Item = Component<'a>;\n\n\tfn next(&mut self) -> Option<Component<'a>> {\n\t\tmatch self {\n\t\t\tSelf::Os(c) => c.next().map(Into::into),\n\t\t\tSelf::Unix(c) => c.next().map(Into::into),\n\t\t}\n\t}\n}\n\nimpl<'a> DoubleEndedIterator for Components<'a> {\n\tfn next_back(&mut self) -> Option<Component<'a>> {\n\t\tmatch self {\n\t\t\tSelf::Os(c) => c.next_back().map(Into::into),\n\t\t\tSelf::Unix(c) => c.next_back().map(Into::into),\n\t\t}\n\t}\n}\n\nimpl FusedIterator for Components<'_> {}\n"
  },
  {
    "path": "yazi-shared/src/path/conversion.rs",
    "content": "use super::{PathBufDyn, PathDyn};\nuse crate::path::PathCow;\n\n// --- AsPath\npub trait AsPath {\n\tfn as_path(&self) -> PathDyn<'_>;\n}\n\nimpl AsPath for std::path::Path {\n\tfn as_path(&self) -> PathDyn<'_> { PathDyn::Os(self) }\n}\n\nimpl AsPath for std::path::PathBuf {\n\tfn as_path(&self) -> PathDyn<'_> { PathDyn::Os(self) }\n}\n\nimpl AsPath for typed_path::UnixPath {\n\tfn as_path(&self) -> PathDyn<'_> { PathDyn::Unix(self) }\n}\n\nimpl AsPath for typed_path::UnixPathBuf {\n\tfn as_path(&self) -> PathDyn<'_> { PathDyn::Unix(self) }\n}\n\nimpl AsPath for PathDyn<'_> {\n\tfn as_path(&self) -> PathDyn<'_> { *self }\n}\n\nimpl AsPath for PathBufDyn {\n\tfn as_path(&self) -> PathDyn<'_> {\n\t\tmatch self {\n\t\t\tSelf::Os(p) => PathDyn::Os(p),\n\t\t\tSelf::Unix(p) => PathDyn::Unix(p),\n\t\t}\n\t}\n}\n\nimpl AsPath for PathCow<'_> {\n\tfn as_path(&self) -> PathDyn<'_> {\n\t\tmatch self {\n\t\t\tPathCow::Borrowed(p) => *p,\n\t\t\tPathCow::Owned(p) => p.as_path(),\n\t\t}\n\t}\n}\n\nimpl AsPath for super::Components<'_> {\n\tfn as_path(&self) -> PathDyn<'_> { self.path() }\n}\n\n// --- AsPathRef\npub trait AsPathRef<'a> {\n\tfn as_path_ref(self) -> PathDyn<'a>;\n}\n\nimpl<'a> AsPathRef<'a> for PathDyn<'a> {\n\tfn as_path_ref(self) -> Self { self }\n}\n"
  },
  {
    "path": "yazi-shared/src/path/cow.rs",
    "content": "use std::borrow::Cow;\n\nuse anyhow::Result;\n\nuse crate::path::{AsPath, PathBufDyn, PathDyn, PathDynError, PathKind};\n\n// --- PathCow\n#[derive(Debug)]\npub enum PathCow<'a> {\n\tBorrowed(PathDyn<'a>),\n\tOwned(PathBufDyn),\n}\n\nimpl<'a> From<PathDyn<'a>> for PathCow<'a> {\n\tfn from(value: PathDyn<'a>) -> Self { Self::Borrowed(value) }\n}\n\nimpl From<PathBufDyn> for PathCow<'_> {\n\tfn from(value: PathBufDyn) -> Self { Self::Owned(value) }\n}\n\nimpl<'a> From<std::path::PathBuf> for PathCow<'a> {\n\tfn from(value: std::path::PathBuf) -> Self { Self::Owned(value.into()) }\n}\n\nimpl<'a> From<&'a PathCow<'_>> for PathCow<'a> {\n\tfn from(value: &'a PathCow<'_>) -> Self { Self::Borrowed(value.as_path()) }\n}\n\nimpl From<PathCow<'_>> for PathBufDyn {\n\tfn from(value: PathCow<'_>) -> Self { value.into_owned() }\n}\n\nimpl PartialEq for PathCow<'_> {\n\tfn eq(&self, other: &Self) -> bool { self.as_path() == other.as_path() }\n}\n\nimpl PartialEq<&str> for PathCow<'_> {\n\tfn eq(&self, other: &&str) -> bool {\n\t\tmatch self {\n\t\t\tSelf::Borrowed(s) => s.as_path() == *other,\n\t\t\tSelf::Owned(s) => s.as_path() == *other,\n\t\t}\n\t}\n}\n\nimpl<'a> PathCow<'a> {\n\tpub fn into_owned(self) -> PathBufDyn {\n\t\tmatch self {\n\t\t\tSelf::Borrowed(p) => p.to_owned(),\n\t\t\tSelf::Owned(p) => p,\n\t\t}\n\t}\n\n\tpub fn into_os(self) -> Result<std::path::PathBuf, PathDynError> {\n\t\tmatch self {\n\t\t\tPathCow::Borrowed(p) => p.to_os_owned(),\n\t\t\tPathCow::Owned(p) => p.into_os(),\n\t\t}\n\t}\n\n\tpub fn is_borrowed(&self) -> bool { matches!(self, Self::Borrowed(_)) }\n\n\tpub fn with<K, T>(kind: K, bytes: T) -> Result<Self>\n\twhere\n\t\tK: Into<PathKind>,\n\t\tT: Into<Cow<'a, [u8]>>,\n\t{\n\t\tOk(match bytes.into() {\n\t\t\tCow::Borrowed(b) => PathDyn::with(kind, b)?.into(),\n\t\t\tCow::Owned(b) => PathBufDyn::with(kind, b)?.into(),\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/path/display.rs",
    "content": "use crate::path::PathDyn;\n\npub struct Display<'a>(pub PathDyn<'a>);\n\nimpl std::fmt::Display for Display<'_> {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tmatch self.0 {\n\t\t\tPathDyn::Os(p) => write!(f, \"{}\", p.display()),\n\t\t\tPathDyn::Unix(p) => write!(f, \"{}\", p.display()),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/path/error.rs",
    "content": "use thiserror::Error;\n\nuse crate::strand::StrandError;\n\n// --- EndsWithError\n#[derive(Debug, Error)]\n#[error(\"calling ends_with on paths with different encodings\")]\npub enum EndsWithError {\n\tFromStrand(#[from] StrandError),\n}\n\n// --- JoinError\n#[derive(Debug, Error)]\n#[error(\"calling join on paths with different encodings\")]\npub enum JoinError {\n\tFromStrand(#[from] StrandError),\n\tFromPathDyn(#[from] PathDynError),\n}\n\nimpl From<StartsWithError> for JoinError {\n\tfn from(err: StartsWithError) -> Self {\n\t\tmatch err {\n\t\t\tStartsWithError::FromStrand(e) => Self::FromStrand(e),\n\t\t}\n\t}\n}\n\nimpl From<JoinError> for std::io::Error {\n\tfn from(err: JoinError) -> Self { Self::other(err) }\n}\n\n// --- PathDynError\n#[derive(Debug, Error)]\npub enum PathDynError {\n\t#[error(\"conversion to OS path failed\")]\n\tAsOs,\n\t#[error(\"conversion to Unix path failed\")]\n\tAsUnix,\n\t#[error(\"conversion to UTF-8 path failed\")]\n\tAsUtf8,\n}\n\nimpl From<StrandError> for PathDynError {\n\tfn from(err: StrandError) -> Self {\n\t\tmatch err {\n\t\t\tStrandError::AsOs => Self::AsOs,\n\t\t\tStrandError::AsUtf8 => Self::AsUtf8,\n\t\t}\n\t}\n}\n\nimpl From<PathDynError> for std::io::Error {\n\tfn from(err: PathDynError) -> Self { Self::other(err) }\n}\n\n// --- SetNameError\n#[derive(Debug, Error)]\n#[error(\"calling set_name on paths with different encodings\")]\npub enum SetNameError {\n\tFromStrand(#[from] StrandError),\n}\n\nimpl From<SetNameError> for std::io::Error {\n\tfn from(err: SetNameError) -> Self { Self::other(err) }\n}\n\n// --- RsplitOnce\n#[derive(Error, Debug)]\n#[error(\"calling rsplit_once on paths with different encodings\")]\npub enum RsplitOnceError {\n\t#[error(\"conversion to OsStr failed\")]\n\tAsOs,\n\t#[error(\"conversion to UTF-8 str failed\")]\n\tAsUtf8,\n\t#[error(\"the pattern was not found\")]\n\tNotFound,\n}\n\nimpl From<StrandError> for RsplitOnceError {\n\tfn from(err: StrandError) -> Self {\n\t\tmatch err {\n\t\t\tStrandError::AsOs => Self::AsOs,\n\t\t\tStrandError::AsUtf8 => Self::AsUtf8,\n\t\t}\n\t}\n}\n\n// --- StartsWithError\n#[derive(Error, Debug)]\n#[error(\"calling starts_with on paths with different encodings\")]\npub enum StartsWithError {\n\tFromStrand(#[from] StrandError),\n}\n\n// --- StripPrefixError\n#[derive(Debug, Error)]\npub enum StripPrefixError {\n\t#[error(\"calling strip_prefix on URLs with different schemes\")]\n\tExotic,\n\t#[error(\"the base is not a prefix of the path\")]\n\tNotPrefix,\n\t#[error(\"calling strip_prefix on paths with different encodings\")]\n\tWrongEncoding,\n}\n\nimpl From<StrandError> for StripPrefixError {\n\tfn from(err: StrandError) -> Self {\n\t\tmatch err {\n\t\t\tStrandError::AsOs | StrandError::AsUtf8 => Self::WrongEncoding,\n\t\t}\n\t}\n}\n\nimpl From<std::path::StripPrefixError> for StripPrefixError {\n\tfn from(_: std::path::StripPrefixError) -> Self { Self::NotPrefix }\n}\n\nimpl From<typed_path::StripPrefixError> for StripPrefixError {\n\tfn from(_: typed_path::StripPrefixError) -> Self { Self::NotPrefix }\n}\n\n// --- StripSuffixError\n#[derive(Debug, Error)]\npub enum StripSuffixError {\n\t#[error(\"calling strip_suffix on URLs with different schemes\")]\n\tExotic,\n\t#[error(\"the base is not a suffix of the path\")]\n\tNotSuffix,\n\t#[error(\"calling strip_suffix on paths with different encodings\")]\n\tWrongEncoding,\n}\n\nimpl From<StrandError> for StripSuffixError {\n\tfn from(err: StrandError) -> Self {\n\t\tmatch err {\n\t\t\tStrandError::AsOs | StrandError::AsUtf8 => Self::WrongEncoding,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/path/kind.rs",
    "content": "use crate::scheme::SchemeKind;\n\npub enum PathKind {\n\tOs,\n\tUnix,\n}\n\nimpl From<SchemeKind> for PathKind {\n\tfn from(value: SchemeKind) -> Self {\n\t\tmatch value {\n\t\t\tSchemeKind::Regular => Self::Os,\n\t\t\tSchemeKind::Search => Self::Os,\n\t\t\tSchemeKind::Archive => Self::Os,\n\t\t\tSchemeKind::Sftp => Self::Unix,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/path/like.rs",
    "content": "use std::borrow::Cow;\n\nuse anyhow::Result;\n\nuse crate::{Utf8BytePredictor, path::{AsPath, Components, Display, EndsWithError, JoinError, PathBufDyn, PathCow, PathDyn, PathDynError, PathKind, RsplitOnceError, StartsWithError, StripPrefixError, StripSuffixError}, strand::{AsStrand, Strand}};\n\npub trait PathLike: AsPath {\n\tfn as_os(&self) -> Result<&std::path::Path, PathDynError> { self.as_path().as_os() }\n\n\tfn as_unix(&self) -> Result<&typed_path::UnixPath, PathDynError> { self.as_path().as_unix() }\n\n\tfn components(&self) -> Components<'_> { self.as_path().components() }\n\n\tfn display(&self) -> Display<'_> { self.as_path().display() }\n\n\tfn encoded_bytes(&self) -> &[u8] { self.as_path().encoded_bytes() }\n\n\tfn ext(&self) -> Option<Strand<'_>> { self.as_path().ext() }\n\n\tfn has_root(&self) -> bool { self.as_path().has_root() }\n\n\tfn is_absolute(&self) -> bool { self.as_path().is_absolute() }\n\n\tfn is_empty(&self) -> bool { self.as_path().is_empty() }\n\n\t#[cfg(unix)]\n\tfn is_hidden(&self) -> bool { self.as_path().is_hidden() }\n\n\tfn kind(&self) -> PathKind { self.as_path().kind() }\n\n\tfn len(&self) -> usize { self.as_path().len() }\n\n\tfn name(&self) -> Option<Strand<'_>> { self.as_path().name() }\n\n\tfn parent(&self) -> Option<PathDyn<'_>> { self.as_path().parent() }\n\n\tfn rsplit_pred<T>(&self, pred: T) -> Option<(PathDyn<'_>, PathDyn<'_>)>\n\twhere\n\t\tT: Utf8BytePredictor,\n\t{\n\t\tself.as_path().rsplit_pred(pred)\n\t}\n\n\tfn stem(&self) -> Option<Strand<'_>> { self.as_path().stem() }\n\n\tfn to_os_owned(&self) -> Result<std::path::PathBuf, PathDynError> { self.as_path().to_os_owned() }\n\n\tfn to_owned(&self) -> PathBufDyn { self.as_path().to_owned() }\n\n\tfn to_str(&self) -> Result<&str, std::str::Utf8Error> { self.as_path().to_str() }\n\n\tfn to_string_lossy(&self) -> Cow<'_, str> { self.as_path().to_string_lossy() }\n\n\tfn try_ends_with<T>(&self, child: T) -> Result<bool, EndsWithError>\n\twhere\n\t\tT: AsStrand,\n\t{\n\t\tself.as_path().try_ends_with(child)\n\t}\n\n\tfn try_join<T>(&self, path: T) -> Result<PathBufDyn, JoinError>\n\twhere\n\t\tT: AsStrand,\n\t{\n\t\tself.as_path().try_join(path)\n\t}\n\n\tfn try_rsplit_seq<T>(&self, pat: T) -> Result<(PathDyn<'_>, PathDyn<'_>), RsplitOnceError>\n\twhere\n\t\tT: AsStrand,\n\t{\n\t\tself.as_path().try_rsplit_seq(pat)\n\t}\n\n\tfn try_starts_with<T>(&self, base: T) -> Result<bool, StartsWithError>\n\twhere\n\t\tT: AsStrand,\n\t{\n\t\tself.as_path().try_starts_with(base)\n\t}\n\n\tfn try_strip_prefix<T>(&self, base: T) -> Result<PathDyn<'_>, StripPrefixError>\n\twhere\n\t\tT: AsStrand,\n\t{\n\t\tself.as_path().try_strip_prefix(base)\n\t}\n\n\tfn try_strip_suffix<T>(&self, suffix: T) -> Result<PathDyn<'_>, StripSuffixError>\n\twhere\n\t\tT: AsStrand,\n\t{\n\t\tself.as_path().try_strip_suffix(suffix)\n\t}\n}\n\nimpl<P> From<&P> for PathBufDyn\nwhere\n\tP: PathLike,\n{\n\tfn from(value: &P) -> Self { value.to_owned() }\n}\n\nimpl PathLike for PathBufDyn {}\nimpl PathLike for PathCow<'_> {}\n"
  },
  {
    "path": "yazi-shared/src/path/mod.rs",
    "content": "yazi_macro::mod_flat!(buf component components conversion cow display error kind like path view);\n"
  },
  {
    "path": "yazi-shared/src/path/path.rs",
    "content": "use std::{borrow::Cow, ffi::OsStr};\n\nuse anyhow::Result;\nuse hashbrown::Equivalent;\n\nuse super::{RsplitOnceError, StartsWithError};\nuse crate::{BytesExt, Utf8BytePredictor, path::{AsPath, Components, Display, EndsWithError, JoinError, PathBufDyn, PathDynError, PathKind, StripPrefixError, StripSuffixError}, strand::{AsStrand, Strand, StrandError}};\n\n#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]\npub enum PathDyn<'p> {\n\tOs(&'p std::path::Path),\n\tUnix(&'p typed_path::UnixPath),\n}\n\nimpl<'a> From<&'a std::path::Path> for PathDyn<'a> {\n\tfn from(value: &'a std::path::Path) -> Self { Self::Os(value) }\n}\n\nimpl<'a> From<&'a typed_path::UnixPath> for PathDyn<'a> {\n\tfn from(value: &'a typed_path::UnixPath) -> Self { Self::Unix(value) }\n}\n\nimpl<'a> From<&'a PathBufDyn> for PathDyn<'a> {\n\tfn from(value: &'a PathBufDyn) -> Self { value.as_path() }\n}\n\nimpl PartialEq<PathBufDyn> for PathDyn<'_> {\n\tfn eq(&self, other: &PathBufDyn) -> bool { *self == other.as_path() }\n}\n\nimpl PartialEq<PathDyn<'_>> for &std::path::Path {\n\tfn eq(&self, other: &PathDyn<'_>) -> bool { matches!(*other, PathDyn::Os(p) if p == *self) }\n}\n\nimpl PartialEq<&std::path::Path> for PathDyn<'_> {\n\tfn eq(&self, other: &&std::path::Path) -> bool { matches!(*self, PathDyn::Os(p) if p == *other) }\n}\n\nimpl PartialEq<&str> for PathDyn<'_> {\n\tfn eq(&self, other: &&str) -> bool {\n\t\tmatch *self {\n\t\t\tPathDyn::Os(p) => p == *other,\n\t\t\tPathDyn::Unix(p) => p == typed_path::UnixPath::new(other),\n\t\t}\n\t}\n}\n\nimpl Equivalent<PathBufDyn> for PathDyn<'_> {\n\tfn equivalent(&self, key: &PathBufDyn) -> bool { *self == key.as_path() }\n}\n\nimpl<'p> PathDyn<'p> {\n\t#[inline]\n\tpub fn as_os(self) -> Result<&'p std::path::Path, PathDynError> {\n\t\tmatch self {\n\t\t\tSelf::Os(p) => Ok(p),\n\t\t\tSelf::Unix(_) => Err(PathDynError::AsOs),\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn as_unix(self) -> Result<&'p typed_path::UnixPath, PathDynError> {\n\t\tmatch self {\n\t\t\tSelf::Os(_) => Err(PathDynError::AsUnix),\n\t\t\tSelf::Unix(p) => Ok(p),\n\t\t}\n\t}\n\n\tpub fn components(self) -> Components<'p> {\n\t\tmatch self {\n\t\t\tSelf::Os(p) => Components::Os(p.components()),\n\t\t\tSelf::Unix(p) => Components::Unix(p.components()),\n\t\t}\n\t}\n\n\tpub fn display(self) -> Display<'p> { Display(self) }\n\n\tpub fn encoded_bytes(self) -> &'p [u8] {\n\t\tmatch self {\n\t\t\tSelf::Os(p) => p.as_os_str().as_encoded_bytes(),\n\t\t\tSelf::Unix(p) => p.as_bytes(),\n\t\t}\n\t}\n\n\tpub fn ext(self) -> Option<Strand<'p>> {\n\t\tSome(match self {\n\t\t\tSelf::Os(p) => p.extension()?.into(),\n\t\t\tSelf::Unix(p) => p.extension()?.into(),\n\t\t})\n\t}\n\n\t#[inline]\n\tpub unsafe fn from_encoded_bytes<K>(kind: K, bytes: &'p [u8]) -> Self\n\twhere\n\t\tK: Into<PathKind>,\n\t{\n\t\tmatch kind.into() {\n\t\t\tPathKind::Os => Self::Os(unsafe { OsStr::from_encoded_bytes_unchecked(bytes) }.as_ref()),\n\t\t\tPathKind::Unix => Self::Unix(typed_path::UnixPath::new(bytes)),\n\t\t}\n\t}\n\n\tpub fn has_root(self) -> bool {\n\t\tmatch self {\n\t\t\tSelf::Os(p) => p.has_root(),\n\t\t\tSelf::Unix(p) => p.has_root(),\n\t\t}\n\t}\n\n\tpub fn is_absolute(self) -> bool {\n\t\tmatch self {\n\t\t\tSelf::Os(p) => p.is_absolute(),\n\t\t\tSelf::Unix(p) => p.is_absolute(),\n\t\t}\n\t}\n\n\tpub fn is_empty(self) -> bool { self.encoded_bytes().is_empty() }\n\n\t#[cfg(unix)]\n\tpub fn is_hidden(self) -> bool {\n\t\tself.name().is_some_and(|n| n.encoded_bytes().first() == Some(&b'.'))\n\t}\n\n\tpub fn kind(self) -> PathKind {\n\t\tmatch self {\n\t\t\tSelf::Os(_) => PathKind::Os,\n\t\t\tSelf::Unix(_) => PathKind::Unix,\n\t\t}\n\t}\n\n\tpub fn len(self) -> usize { self.encoded_bytes().len() }\n\n\tpub fn name(self) -> Option<Strand<'p>> {\n\t\tSome(match self {\n\t\t\tSelf::Os(p) => p.file_name()?.into(),\n\t\t\tSelf::Unix(p) => p.file_name()?.into(),\n\t\t})\n\t}\n\n\tpub fn parent(self) -> Option<Self> {\n\t\tSome(match self {\n\t\t\tSelf::Os(p) => Self::Os(p.parent().filter(|p| !p.as_os_str().is_empty())?),\n\t\t\tSelf::Unix(p) => Self::Unix(p.parent().filter(|p| !p.as_bytes().is_empty())?),\n\t\t})\n\t}\n\n\tpub fn rsplit_pred<T>(self, pred: T) -> Option<(Self, Self)>\n\twhere\n\t\tT: Utf8BytePredictor,\n\t{\n\t\tlet (a, b) = self.encoded_bytes().rsplit_pred_once(pred)?;\n\t\tSome(unsafe {\n\t\t\t(Self::from_encoded_bytes(self.kind(), a), Self::from_encoded_bytes(self.kind(), b))\n\t\t})\n\t}\n\n\tpub fn stem(self) -> Option<Strand<'p>> {\n\t\tSome(match self {\n\t\t\tSelf::Os(p) => p.file_stem()?.into(),\n\t\t\tSelf::Unix(p) => p.file_stem()?.into(),\n\t\t})\n\t}\n\n\t#[inline]\n\tpub fn to_os_owned(self) -> Result<std::path::PathBuf, PathDynError> {\n\t\tmatch self {\n\t\t\tSelf::Os(p) => Ok(p.to_owned()),\n\t\t\tSelf::Unix(_) => Err(PathDynError::AsOs),\n\t\t}\n\t}\n\n\tpub fn to_owned(self) -> PathBufDyn {\n\t\tmatch self {\n\t\t\tSelf::Os(p) => PathBufDyn::Os(p.to_owned()),\n\t\t\tSelf::Unix(p) => PathBufDyn::Unix(p.to_owned()),\n\t\t}\n\t}\n\n\tpub fn to_str(self) -> Result<&'p str, std::str::Utf8Error> {\n\t\tstr::from_utf8(self.encoded_bytes())\n\t}\n\n\tpub fn to_string_lossy(self) -> Cow<'p, str> { String::from_utf8_lossy(self.encoded_bytes()) }\n\n\tpub fn to_unix_owned(self) -> Result<typed_path::UnixPathBuf, PathDynError> {\n\t\tmatch self {\n\t\t\tSelf::Os(_) => Err(PathDynError::AsUnix),\n\t\t\tSelf::Unix(p) => Ok(p.to_owned()),\n\t\t}\n\t}\n\n\tpub fn try_ends_with<T>(self, child: T) -> Result<bool, EndsWithError>\n\twhere\n\t\tT: AsStrand,\n\t{\n\t\tlet s = child.as_strand();\n\t\tOk(match self {\n\t\t\tSelf::Os(p) => p.ends_with(s.as_os()?),\n\t\t\tSelf::Unix(p) => p.ends_with(s.encoded_bytes()),\n\t\t})\n\t}\n\n\tpub fn try_join<T>(self, path: T) -> Result<PathBufDyn, JoinError>\n\twhere\n\t\tT: AsStrand,\n\t{\n\t\tlet s = path.as_strand();\n\t\tOk(match self {\n\t\t\tSelf::Os(p) => PathBufDyn::Os(p.join(s.as_os()?)),\n\t\t\tSelf::Unix(p) => PathBufDyn::Unix(p.join(s.encoded_bytes())),\n\t\t})\n\t}\n\n\tpub fn try_rsplit_seq<T>(self, pat: T) -> Result<(Self, Self), RsplitOnceError>\n\twhere\n\t\tT: AsStrand,\n\t{\n\t\tlet pat = pat.as_strand();\n\n\t\tlet (a, b) = match self {\n\t\t\tPathDyn::Os(p) => {\n\t\t\t\tp.as_os_str().as_encoded_bytes().rsplit_seq_once(pat.as_os()?.as_encoded_bytes())\n\t\t\t}\n\t\t\tPathDyn::Unix(p) => p.as_bytes().rsplit_seq_once(pat.encoded_bytes()),\n\t\t}\n\t\t.ok_or(RsplitOnceError::NotFound)?;\n\n\t\tOk(unsafe {\n\t\t\t(Self::from_encoded_bytes(self.kind(), a), Self::from_encoded_bytes(self.kind(), b))\n\t\t})\n\t}\n\n\tpub fn try_starts_with<T>(self, base: T) -> Result<bool, StartsWithError>\n\twhere\n\t\tT: AsStrand,\n\t{\n\t\tlet s = base.as_strand();\n\t\tOk(match self {\n\t\t\tSelf::Os(p) => p.starts_with(s.as_os()?),\n\t\t\tSelf::Unix(p) => p.starts_with(s.encoded_bytes()),\n\t\t})\n\t}\n\n\tpub fn try_strip_prefix<T>(self, base: T) -> Result<Self, StripPrefixError>\n\twhere\n\t\tT: AsStrand,\n\t{\n\t\tlet s = base.as_strand();\n\t\tOk(match self {\n\t\t\tSelf::Os(p) => Self::Os(p.strip_prefix(s.as_os()?)?),\n\t\t\tSelf::Unix(p) => Self::Unix(p.strip_prefix(s.encoded_bytes())?),\n\t\t})\n\t}\n\n\tpub fn try_strip_suffix<T>(self, suffix: T) -> Result<Self, StripSuffixError>\n\twhere\n\t\tT: AsStrand,\n\t{\n\t\tlet s = suffix.as_strand();\n\t\tlet mut me_comps = self.components();\n\t\tlet mut suf_comps = match self.kind() {\n\t\t\tPathKind::Os => Components::Os(s.as_os_path()?.components()),\n\t\t\tPathKind::Unix => Components::Unix(s.as_unix_path().components()),\n\t\t};\n\n\t\twhile let Some(next) = suf_comps.next_back() {\n\t\t\tif me_comps.next_back() != Some(next) {\n\t\t\t\treturn Err(StripSuffixError::NotSuffix);\n\t\t\t}\n\t\t}\n\n\t\tOk(me_comps.path())\n\t}\n\n\tpub fn with<K, S>(kind: K, strand: &'p S) -> Result<Self, StrandError>\n\twhere\n\t\tK: Into<PathKind>,\n\t\tS: ?Sized + AsStrand,\n\t{\n\t\tlet s = strand.as_strand();\n\t\tOk(match kind.into() {\n\t\t\tPathKind::Os => Self::Os(s.as_os_path()?),\n\t\t\tPathKind::Unix => Self::Unix(s.as_unix_path()),\n\t\t})\n\t}\n\n\tpub fn with_str<K, S>(kind: K, s: &'p S) -> Self\n\twhere\n\t\tK: Into<PathKind>,\n\t\tS: ?Sized + AsRef<str>,\n\t{\n\t\tlet s = s.as_ref();\n\t\tmatch kind.into() {\n\t\t\tPathKind::Os => Self::Os(s.as_ref()),\n\t\t\tPathKind::Unix => Self::Unix(s.as_ref()),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/path/view.rs",
    "content": "// --- AsPathView\npub trait AsPathView<'a, T> {\n\tfn as_path_view(self) -> T;\n}\n\nimpl<'a> AsPathView<'a, &'a std::path::Path> for &'a std::path::Path {\n\tfn as_path_view(self) -> &'a std::path::Path { self }\n}\n\nimpl<'a> AsPathView<'a, &'a std::path::Path> for &'a std::path::PathBuf {\n\tfn as_path_view(self) -> &'a std::path::Path { self }\n}\n\nimpl<'a> AsPathView<'a, &'a typed_path::UnixPath> for &'a typed_path::UnixPath {\n\tfn as_path_view(self) -> &'a typed_path::UnixPath { self }\n}\n\nimpl<'a> AsPathView<'a, &'a typed_path::UnixPath> for &'a typed_path::UnixPathBuf {\n\tfn as_path_view(self) -> &'a typed_path::UnixPath { self }\n}\n"
  },
  {
    "path": "yazi-shared/src/pool/cow.rs",
    "content": "use std::ops::Deref;\n\nuse crate::pool::{Pool, Symbol};\n\npub enum SymbolCow<'a, T: ?Sized> {\n\tBorrowed(&'a T),\n\tOwned(Symbol<T>),\n}\n\nimpl<T: ?Sized> Clone for SymbolCow<'_, T> {\n\tfn clone(&self) -> Self {\n\t\tmatch self {\n\t\t\tSelf::Borrowed(t) => Self::Borrowed(t),\n\t\t\tSelf::Owned(t) => Self::Owned(t.clone()),\n\t\t}\n\t}\n}\n\nimpl AsRef<[u8]> for SymbolCow<'_, [u8]> {\n\tfn as_ref(&self) -> &[u8] {\n\t\tmatch self {\n\t\t\tSelf::Borrowed(b) => b,\n\t\t\tSelf::Owned(b) => b.as_ref(),\n\t\t}\n\t}\n}\n\nimpl AsRef<str> for SymbolCow<'_, str> {\n\tfn as_ref(&self) -> &str {\n\t\tmatch self {\n\t\t\tSelf::Borrowed(s) => s,\n\t\t\tSelf::Owned(s) => s.as_ref(),\n\t\t}\n\t}\n}\n\n// --- Deref\nimpl Deref for SymbolCow<'_, [u8]> {\n\ttype Target = [u8];\n\n\tfn deref(&self) -> &Self::Target { self.as_ref() }\n}\n\nimpl Deref for SymbolCow<'_, str> {\n\ttype Target = str;\n\n\tfn deref(&self) -> &Self::Target { self.as_ref() }\n}\n\n// --- From\nimpl<'a, T: ?Sized> From<&'a T> for SymbolCow<'a, T> {\n\tfn from(value: &'a T) -> Self { Self::Borrowed(value) }\n}\n\nimpl<T: ?Sized> From<Symbol<T>> for SymbolCow<'_, T> {\n\tfn from(value: Symbol<T>) -> Self { Self::Owned(value) }\n}\n\nimpl From<SymbolCow<'_, [u8]>> for Symbol<[u8]> {\n\tfn from(value: SymbolCow<'_, [u8]>) -> Self { value.into_owned() }\n}\n\nimpl From<SymbolCow<'_, str>> for Symbol<str> {\n\tfn from(value: SymbolCow<'_, str>) -> Self { value.into_owned() }\n}\n\n// --- Debug\nimpl std::fmt::Debug for SymbolCow<'_, [u8]> {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\twrite!(f, \"SymbolCow<[u8]>({:?})\", self.as_ref())\n\t}\n}\n\nimpl std::fmt::Debug for SymbolCow<'_, str> {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\twrite!(f, \"SymbolCow<str>({:?})\", self.as_ref())\n\t}\n}\n\nimpl SymbolCow<'_, [u8]> {\n\tpub fn into_owned(self) -> Symbol<[u8]> {\n\t\tmatch self {\n\t\t\tSelf::Borrowed(t) => Pool::<[u8]>::intern(t),\n\t\t\tSelf::Owned(t) => t,\n\t\t}\n\t}\n}\n\nimpl SymbolCow<'_, str> {\n\tpub fn into_owned(self) -> Symbol<str> {\n\t\tmatch self {\n\t\t\tSelf::Borrowed(t) => Pool::<str>::intern(t),\n\t\t\tSelf::Owned(t) => t,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/pool/mod.rs",
    "content": "yazi_macro::mod_flat!(cow pool ptr symbol traits);\n\nstatic SYMBOLS: crate::RoCell<\n\tparking_lot::Mutex<hashbrown::HashMap<SymbolPtr, u64, foldhash::fast::FixedState>>,\n> = crate::RoCell::new();\n\npub(super) fn init() { SYMBOLS.with(<_>::default); }\n\n#[inline]\npub(super) fn compute_hash<T: std::hash::Hash>(value: T) -> u64 {\n\tuse core::hash::BuildHasher;\n\tfoldhash::fast::FixedState::default().hash_one(value)\n}\n"
  },
  {
    "path": "yazi-shared/src/pool/pool.rs",
    "content": "use std::marker::PhantomData;\n\nuse hashbrown::hash_map::RawEntryMut;\n\nuse crate::pool::{SYMBOLS, Symbol, SymbolPtr, compute_hash};\n\npub struct Pool<T: ?Sized> {\n\t_phantom: PhantomData<T>,\n}\n\nimpl Pool<[u8]> {\n\tpub fn intern(value: &[u8]) -> Symbol<[u8]> {\n\t\tlet hash = compute_hash(value);\n\n\t\tmatch SYMBOLS.lock().raw_entry_mut().from_key_hashed_nocheck(hash, value) {\n\t\t\tRawEntryMut::Occupied(mut oe) => {\n\t\t\t\tlet (ptr, count) = oe.get_key_value_mut();\n\n\t\t\t\t*count += 1;\n\t\t\t\tSymbol::new(ptr.clone())\n\t\t\t}\n\t\t\tRawEntryMut::Vacant(ve) => {\n\t\t\t\tlet boxed = value.to_vec().into_boxed_slice();\n\t\t\t\tlet ptr = SymbolPtr::leaked(Box::leak(boxed));\n\n\t\t\t\tve.insert_hashed_nocheck(hash, ptr.clone(), 1);\n\t\t\t\tSymbol::new(ptr)\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl Pool<str> {\n\tpub fn intern(value: impl AsRef<str>) -> Symbol<str> {\n\t\tlet symbol = Pool::<[u8]>::intern(value.as_ref().as_bytes());\n\t\tSymbol::new(symbol.into_ptr())\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/pool/ptr.rs",
    "content": "use std::{borrow::Borrow, hash::{Hash, Hasher}, ops::Deref, ptr::NonNull};\n\nuse hashbrown::Equivalent;\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub(super) struct SymbolPtr(NonNull<[u8]>);\n\nunsafe impl Send for SymbolPtr {}\n\nunsafe impl Sync for SymbolPtr {}\n\nimpl Deref for SymbolPtr {\n\ttype Target = NonNull<[u8]>;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl Borrow<[u8]> for SymbolPtr {\n\tfn borrow(&self) -> &[u8] { self.bytes() }\n}\n\nimpl Hash for SymbolPtr {\n\tfn hash<H: Hasher>(&self, state: &mut H) { self.bytes().hash(state); }\n}\n\nimpl Equivalent<[u8]> for SymbolPtr {\n\tfn equivalent(&self, key: &[u8]) -> bool { self.bytes() == key }\n}\n\nimpl SymbolPtr {\n\t#[inline]\n\tpub(super) fn leaked(leaked: &'static mut [u8]) -> Self { Self(NonNull::from(leaked)) }\n\n\t#[inline]\n\tpub(super) fn bytes(&self) -> &[u8] { unsafe { self.0.as_ref() } }\n}\n"
  },
  {
    "path": "yazi-shared/src/pool/symbol.rs",
    "content": "use std::{hash::{Hash, Hasher}, marker::PhantomData, mem::ManuallyDrop, ops::Deref, str};\n\nuse hashbrown::hash_map::RawEntryMut;\n\nuse crate::pool::{Pool, SYMBOLS, SymbolPtr, compute_hash};\n\npub struct Symbol<T: ?Sized> {\n\tptr:      SymbolPtr,\n\t_phantom: PhantomData<T>,\n}\n\nunsafe impl<T: ?Sized> Send for Symbol<T> {}\n\nunsafe impl<T: ?Sized> Sync for Symbol<T> {}\n\nimpl<T: ?Sized> Clone for Symbol<T> {\n\tfn clone(&self) -> Self {\n\t\tlet hash = compute_hash(&self.ptr);\n\t\tmatch SYMBOLS.lock().raw_entry_mut().from_key_hashed_nocheck(hash, &self.ptr) {\n\t\t\tRawEntryMut::Occupied(mut oe) => *oe.get_mut() += 1,\n\t\t\tRawEntryMut::Vacant(_) => unreachable!(),\n\t\t}\n\t\tSelf::new(self.ptr.clone())\n\t}\n}\n\nimpl<T: ?Sized> Drop for Symbol<T> {\n\tfn drop(&mut self) {\n\t\tlet hash = compute_hash(&self.ptr);\n\t\tmatch SYMBOLS.lock().raw_entry_mut().from_key_hashed_nocheck(hash, &self.ptr) {\n\t\t\tRawEntryMut::Occupied(mut oe) => {\n\t\t\t\tlet count = oe.get_mut();\n\t\t\t\t*count -= 1;\n\n\t\t\t\tif *count == 0 {\n\t\t\t\t\toe.remove();\n\t\t\t\t\tdrop(unsafe { Box::from_raw(self.ptr.as_ptr()) });\n\t\t\t\t}\n\t\t\t}\n\t\t\tRawEntryMut::Vacant(_) => unreachable!(),\n\t\t}\n\t}\n}\n\nimpl AsRef<[u8]> for Symbol<[u8]> {\n\tfn as_ref(&self) -> &[u8] { self.ptr.bytes() }\n}\n\nimpl AsRef<str> for Symbol<str> {\n\tfn as_ref(&self) -> &str { unsafe { str::from_utf8_unchecked(self.ptr.bytes()) } }\n}\n\nimpl Deref for Symbol<[u8]> {\n\ttype Target = [u8];\n\n\tfn deref(&self) -> &Self::Target { self.as_ref() }\n}\n\nimpl Deref for Symbol<str> {\n\ttype Target = str;\n\n\tfn deref(&self) -> &Self::Target { self.as_ref() }\n}\n\n// --- Default\nimpl Default for Symbol<[u8]> {\n\tfn default() -> Self { Pool::<[u8]>::intern(b\"\") }\n}\n\nimpl Default for Symbol<str> {\n\tfn default() -> Self { Pool::<str>::intern(\"\") }\n}\n\n// --- Eq\nimpl<T: ?Sized> PartialEq for Symbol<T> {\n\tfn eq(&self, other: &Self) -> bool { self.ptr == other.ptr }\n}\n\nimpl<T: ?Sized> Eq for Symbol<T> {}\n\nimpl PartialEq<str> for Symbol<str> {\n\tfn eq(&self, other: &str) -> bool { self.as_ref() == other }\n}\n\nimpl PartialEq<[u8]> for Symbol<[u8]> {\n\tfn eq(&self, other: &[u8]) -> bool { self.as_ref() == other }\n}\n\n// --- Hash\nimpl<T: ?Sized> Hash for Symbol<T> {\n\tfn hash<H: Hasher>(&self, state: &mut H) { self.ptr.as_ptr().hash(state); }\n}\n\n// --- Ord\nimpl Ord for Symbol<[u8]> {\n\tfn cmp(&self, other: &Self) -> std::cmp::Ordering { self.as_ref().cmp(other.as_ref()) }\n}\n\nimpl Ord for Symbol<str> {\n\tfn cmp(&self, other: &Self) -> std::cmp::Ordering { self.as_ref().cmp(other.as_ref()) }\n}\n\n// --- PartialOrd\nimpl PartialOrd for Symbol<[u8]> {\n\tfn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { Some(self.cmp(other)) }\n}\n\nimpl PartialOrd for Symbol<str> {\n\tfn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { Some(self.cmp(other)) }\n}\n\n// --- Display\nimpl std::fmt::Display for Symbol<str> {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\twrite!(f, \"{}\", self.as_ref())\n\t}\n}\n\n// --- Debug\nimpl std::fmt::Debug for Symbol<[u8]> {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\twrite!(f, \"Symbol<[u8]>({:?})\", self.as_ref())\n\t}\n}\n\nimpl std::fmt::Debug for Symbol<str> {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\twrite!(f, \"Symbol<str>({:?})\", self.as_ref())\n\t}\n}\n\nimpl<T: ?Sized> Symbol<T> {\n\t#[inline]\n\tpub(super) fn new(ptr: SymbolPtr) -> Self { Self { ptr, _phantom: PhantomData } }\n\n\t#[inline]\n\tpub(super) fn into_ptr(self) -> SymbolPtr { ManuallyDrop::new(self).ptr.clone() }\n}\n"
  },
  {
    "path": "yazi-shared/src/pool/traits.rs",
    "content": "use crate::pool::{Pool, Symbol};\n\npub trait InternStr {\n\tfn intern(&self) -> Symbol<str>;\n}\n\nimpl<T: AsRef<str>> InternStr for T {\n\tfn intern(&self) -> Symbol<str> { Pool::<str>::intern(self) }\n}\n"
  },
  {
    "path": "yazi-shared/src/predictor.rs",
    "content": "// --- BytePredictor\npub trait BytePredictor {\n\tfn predicate(&self, byte: u8) -> bool;\n}\n\n// --- Utf8BytePredictor\npub trait Utf8BytePredictor {\n\tfn predicate(&self, byte: u8) -> bool;\n}\n\n// --- AnyAsciiChar\npub struct AnyAsciiChar<'a>(&'a [u8]);\n\nimpl<'a> AnyAsciiChar<'a> {\n\tpub fn new(chars: &'a [u8]) -> Option<Self> {\n\t\tif chars.iter().all(|&b| b <= 0x7f) { Some(Self(chars)) } else { None }\n\t}\n}\n\nimpl Utf8BytePredictor for AnyAsciiChar<'_> {\n\tfn predicate(&self, byte: u8) -> bool { self.0.contains(&byte) }\n}\n\nimpl<T> BytePredictor for T\nwhere\n\tT: Utf8BytePredictor,\n{\n\tfn predicate(&self, byte: u8) -> bool { self.predicate(byte) }\n}\n"
  },
  {
    "path": "yazi-shared/src/ro_cell.rs",
    "content": "use std::{cell::UnsafeCell, fmt::{self, Display}, mem::MaybeUninit, ops::Deref};\n\n// Read-only cell. It's safe to use this in a static variable, but it's not safe\n// to mutate it. This is useful for storing static data that is expensive to\n// initialize, but is immutable once.\npub struct RoCell<T> {\n\tinner:       UnsafeCell<MaybeUninit<T>>,\n\t#[cfg(debug_assertions)]\n\tinitialized: UnsafeCell<bool>,\n}\n\nunsafe impl<T> Sync for RoCell<T> {}\n\nimpl<T> RoCell<T> {\n\t#[inline]\n\tpub const fn new() -> Self {\n\t\tSelf {\n\t\t\tinner:                                UnsafeCell::new(MaybeUninit::uninit()),\n\t\t\t#[cfg(debug_assertions)]\n\t\t\tinitialized:                          UnsafeCell::new(false),\n\t\t}\n\t}\n\n\t#[inline]\n\tpub const fn new_const(value: T) -> Self {\n\t\tSelf {\n\t\t\tinner:                                UnsafeCell::new(MaybeUninit::new(value)),\n\t\t\t#[cfg(debug_assertions)]\n\t\t\tinitialized:                          UnsafeCell::new(true),\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn init(&self, value: T) {\n\t\tunsafe {\n\t\t\t#[cfg(debug_assertions)]\n\t\t\tassert!(!self.initialized.get().replace(true));\n\t\t\t*self.inner.get() = MaybeUninit::new(value);\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn with<F>(&self, f: F)\n\twhere\n\t\tF: FnOnce() -> T,\n\t{\n\t\tself.init(f());\n\t}\n\n\t#[inline]\n\tpub fn drop(&self) -> T {\n\t\tunsafe {\n\t\t\t#[cfg(debug_assertions)]\n\t\t\tassert!(self.initialized.get().replace(false));\n\t\t\tself.inner.get().replace(MaybeUninit::uninit()).assume_init()\n\t\t}\n\t}\n}\n\nimpl<T> Default for RoCell<T> {\n\tfn default() -> Self { Self::new() }\n}\n\nimpl<T> Deref for RoCell<T> {\n\ttype Target = T;\n\n\tfn deref(&self) -> &Self::Target {\n\t\tunsafe {\n\t\t\t#[cfg(debug_assertions)]\n\t\t\tassert!(*self.initialized.get());\n\t\t\t(*self.inner.get()).assume_init_ref()\n\t\t}\n\t}\n}\n\nimpl<T> Display for RoCell<T>\nwhere\n\tT: Display,\n{\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.deref().fmt(f) }\n}\n"
  },
  {
    "path": "yazi-shared/src/scheme/cow.rs",
    "content": "use std::borrow::Cow;\n\nuse anyhow::{Result, ensure};\nuse percent_encoding::percent_decode;\n\nuse crate::{path::{PathCow, PathLike}, pool::{InternStr, SymbolCow}, scheme::{AsScheme, Scheme, SchemeKind, SchemeRef}, url::Url};\n\n#[derive(Clone, Debug)]\npub enum SchemeCow<'a> {\n\tBorrowed(SchemeRef<'a>),\n\tOwned(Scheme),\n}\n\nimpl<'a> From<SchemeRef<'a>> for SchemeCow<'a> {\n\tfn from(value: SchemeRef<'a>) -> Self { Self::Borrowed(value) }\n}\n\nimpl<'a, T> From<&'a T> for SchemeCow<'a>\nwhere\n\tT: AsScheme + ?Sized,\n{\n\tfn from(value: &'a T) -> Self { Self::Borrowed(value.as_scheme()) }\n}\n\nimpl From<Scheme> for SchemeCow<'_> {\n\tfn from(value: Scheme) -> Self { Self::Owned(value) }\n}\n\nimpl From<SchemeCow<'_>> for Scheme {\n\tfn from(value: SchemeCow<'_>) -> Self { value.into_owned() }\n}\n\nimpl PartialEq<SchemeRef<'_>> for SchemeCow<'_> {\n\tfn eq(&self, other: &SchemeRef) -> bool { self.as_scheme() == *other }\n}\n\nimpl<'a> SchemeCow<'a> {\n\tpub fn regular(uri: usize, urn: usize) -> Self { SchemeRef::Regular { uri, urn }.into() }\n\n\tpub fn search<T>(domain: T, uri: usize, urn: usize) -> Self\n\twhere\n\t\tT: Into<Cow<'a, str>>,\n\t{\n\t\tmatch domain.into() {\n\t\t\tCow::Borrowed(domain) => SchemeRef::Search { domain, uri, urn }.into(),\n\t\t\tCow::Owned(domain) => Scheme::Search { domain: domain.intern(), uri, urn }.into(),\n\t\t}\n\t}\n\n\tpub fn archive<T>(domain: T, uri: usize, urn: usize) -> Self\n\twhere\n\t\tT: Into<Cow<'a, str>>,\n\t{\n\t\tmatch domain.into() {\n\t\t\tCow::Borrowed(domain) => SchemeRef::Archive { domain, uri, urn }.into(),\n\t\t\tCow::Owned(domain) => Scheme::Archive { domain: domain.intern(), uri, urn }.into(),\n\t\t}\n\t}\n\n\tpub fn sftp<T>(domain: T, uri: usize, urn: usize) -> Self\n\twhere\n\t\tT: Into<Cow<'a, str>>,\n\t{\n\t\tmatch domain.into() {\n\t\t\tCow::Borrowed(domain) => SchemeRef::Sftp { domain, uri, urn }.into(),\n\t\t\tCow::Owned(domain) => Scheme::Sftp { domain: domain.intern(), uri, urn }.into(),\n\t\t}\n\t}\n\n\tpub fn parse(bytes: &'a [u8]) -> Result<(Self, PathCow<'a>)> {\n\t\tlet Some((kind, tilde)) = SchemeKind::parse(bytes)? else {\n\t\t\tlet path = Self::decode_path(SchemeKind::Regular, false, bytes)?;\n\t\t\tlet (uri, urn) = Self::normalize_ports(SchemeKind::Regular, None, None, &path)?;\n\t\t\treturn Ok((Self::regular(uri, urn), path));\n\t\t};\n\n\t\t// Decode domain and ports\n\t\tlet mut skip = kind.offset(tilde);\n\t\tlet (domain, uri, urn) = match kind {\n\t\t\tSchemeKind::Regular => (\"\".into(), None, None),\n\t\t\tSchemeKind::Search => Self::decode_param(&bytes[skip..], &mut skip)?,\n\t\t\tSchemeKind::Archive => Self::decode_param(&bytes[skip..], &mut skip)?,\n\t\t\tSchemeKind::Sftp => Self::decode_param(&bytes[skip..], &mut skip)?,\n\t\t};\n\n\t\t// Decode path\n\t\tlet path = Self::decode_path(kind, tilde, &bytes[skip..])?;\n\n\t\t// Build scheme\n\t\tlet (uri, urn) = Self::normalize_ports(kind, uri, urn, &path)?;\n\t\tlet scheme = match kind {\n\t\t\tSchemeKind::Regular => Self::regular(uri, urn),\n\t\t\tSchemeKind::Search => Self::search(domain, uri, urn),\n\t\t\tSchemeKind::Archive => Self::archive(domain, uri, urn),\n\t\t\tSchemeKind::Sftp => Self::sftp(domain, uri, urn),\n\t\t};\n\n\t\tOk((scheme, path))\n\t}\n\n\tfn decode_param(\n\t\tbytes: &'a [u8],\n\t\tskip: &mut usize,\n\t) -> Result<(Cow<'a, str>, Option<usize>, Option<usize>)> {\n\t\tlet mut len = bytes.iter().copied().take_while(|&b| b != b'/').count();\n\t\tlet slash = bytes.get(len).is_some_and(|&b| b == b'/');\n\t\t*skip += len + slash as usize;\n\n\t\tlet (uri, urn) = Self::decode_ports(&bytes[..len], &mut len)?;\n\t\tlet domain = match Cow::from(percent_decode(&bytes[..len])) {\n\t\t\tCow::Borrowed(b) => str::from_utf8(b)?.into(),\n\t\t\tCow::Owned(b) => String::from_utf8(b)?.into(),\n\t\t};\n\n\t\tOk((domain, uri, urn))\n\t}\n\n\tfn decode_ports(bytes: &[u8], skip: &mut usize) -> Result<(Option<usize>, Option<usize>)> {\n\t\tlet Some(a_idx) = bytes.iter().rposition(|&b| b == b':') else { return Ok((None, None)) };\n\t\tlet a_len = bytes.len() - a_idx;\n\t\t*skip -= a_len;\n\t\tlet a = if a_len == 1 { None } else { Some(str::from_utf8(&bytes[a_idx + 1..])?.parse()?) };\n\n\t\tlet Some(b_idx) = bytes[..a_idx].iter().rposition(|&b| b == b':') else {\n\t\t\treturn Ok((a, None));\n\t\t};\n\t\tlet b_len = bytes[..a_idx].len() - b_idx;\n\t\t*skip -= b_len;\n\t\tlet b =\n\t\t\tif b_len == 1 { None } else { Some(str::from_utf8(&bytes[b_idx + 1..a_idx])?.parse()?) };\n\n\t\tOk((b, a))\n\t}\n\n\tfn decode_path(kind: SchemeKind, tilde: bool, bytes: &'a [u8]) -> Result<PathCow<'a>> {\n\t\tlet bytes: Cow<_> = if tilde { percent_decode(bytes).into() } else { bytes.into() };\n\t\tPathCow::with(kind, bytes)\n\t}\n\n\tfn normalize_ports(\n\t\tkind: SchemeKind,\n\t\turi: Option<usize>,\n\t\turn: Option<usize>,\n\t\tpath: &PathCow,\n\t) -> Result<(usize, usize)> {\n\t\tOk(match kind {\n\t\t\tSchemeKind::Regular => {\n\t\t\t\tensure!(uri.is_none() && urn.is_none(), \"Regular scheme cannot have ports\");\n\t\t\t\t(path.name().is_some() as usize, path.name().is_some() as usize)\n\t\t\t}\n\t\t\tSchemeKind::Search => {\n\t\t\t\tlet (uri, urn) = (uri.unwrap_or(0), urn.unwrap_or(0));\n\t\t\t\tensure!(uri == urn, \"Search scheme requires URI and URN to be equal\");\n\t\t\t\t(uri, urn)\n\t\t\t}\n\t\t\tSchemeKind::Archive => (uri.unwrap_or(0), urn.unwrap_or(0)),\n\t\t\tSchemeKind::Sftp => {\n\t\t\t\tlet uri = uri.unwrap_or(path.name().is_some() as usize);\n\t\t\t\tlet urn = urn.unwrap_or(path.name().is_some() as usize);\n\t\t\t\t(uri, urn)\n\t\t\t}\n\t\t})\n\t}\n\n\tpub fn retrieve_ports(url: Url) -> (usize, usize) {\n\t\tmatch url {\n\t\t\tUrl::Regular(loc) => (loc.file_name().is_some() as usize, loc.file_name().is_some() as usize),\n\t\t\tUrl::Search { loc, .. } => (loc.uri().components().count(), loc.urn().components().count()),\n\t\t\tUrl::Archive { loc, .. } => (loc.uri().components().count(), loc.urn().components().count()),\n\t\t\tUrl::Sftp { loc, .. } => (loc.uri().components().count(), loc.urn().components().count()),\n\t\t}\n\t}\n}\n\nimpl<'a> SchemeCow<'a> {\n\t#[inline]\n\tpub fn into_domain(self) -> Option<SymbolCow<'a, str>> {\n\t\tSome(match self {\n\t\t\tSchemeCow::Borrowed(s) => s.domain()?.into(),\n\t\t\tSchemeCow::Owned(s) => s.into_domain()?.into(),\n\t\t})\n\t}\n\n\t#[inline]\n\tpub fn into_owned(self) -> Scheme {\n\t\tmatch self {\n\t\t\tSelf::Borrowed(s) => s.to_owned(),\n\t\t\tSelf::Owned(s) => s,\n\t\t}\n\t}\n\n\tpub fn with_ports(self, uri: usize, urn: usize) -> Self {\n\t\tmatch self {\n\t\t\tSelf::Borrowed(s) => s.with_ports(uri, urn).into(),\n\t\t\tSelf::Owned(s) => s.with_ports(uri, urn).into(),\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn zeroed(self) -> Self { self.with_ports(0, 0) }\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\n\t#[test]\n\tfn test_decode_ports() -> Result<()> {\n\t\tfn assert(s: &str, len: usize, uri: Option<usize>, urn: Option<usize>) -> Result<()> {\n\t\t\tlet mut n = usize::MAX;\n\t\t\tlet port = SchemeCow::decode_ports(s.as_bytes(), &mut n)?;\n\t\t\tassert_eq!((usize::MAX - n, port.0, port.1), (len, uri, urn));\n\t\t\tOk(())\n\t\t}\n\n\t\t// Zeros\n\t\tassert(\"\", 0, None, None)?;\n\t\tassert(\":\", 1, None, None)?;\n\t\tassert(\"::\", 2, None, None)?;\n\n\t\t// URI\n\t\tassert(\":2\", 2, Some(2), None)?;\n\t\tassert(\":2:\", 3, Some(2), None)?;\n\t\tassert(\":22:\", 4, Some(22), None)?;\n\n\t\t// URN\n\t\tassert(\"::1\", 3, None, Some(1))?;\n\t\tassert(\":2:1\", 4, Some(2), Some(1))?;\n\t\tassert(\":22:11\", 6, Some(22), Some(11))?;\n\t\tOk(())\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/scheme/encode.rs",
    "content": "use std::fmt::{self, Display};\n\nuse percent_encoding::{AsciiSet, CONTROLS, PercentEncode, percent_encode};\n\nuse crate::{scheme::SchemeKind, url::Url};\n\n#[derive(Clone, Copy)]\npub struct Encode<'a>(pub Url<'a>);\n\nimpl<'a> From<crate::url::Encode<'a>> for Encode<'a> {\n\tfn from(value: crate::url::Encode<'a>) -> Self { Self(value.0) }\n}\n\nimpl<'a> Encode<'a> {\n\t#[inline]\n\tpub fn domain<'s>(s: &'s str) -> PercentEncode<'s> {\n\t\tconst SET: &AsciiSet = &CONTROLS.add(b'/').add(b':');\n\t\tpercent_encode(s.as_bytes(), SET)\n\t}\n\n\tpub(crate) fn ports(self) -> impl Display {\n\t\tstruct D<'a>(Encode<'a>);\n\n\t\timpl Display for D<'_> {\n\t\t\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\t\t\tmacro_rules! w {\n\t\t\t\t\t($default_uri:expr, $default_urn:expr) => {{\n\t\t\t\t\t\tlet (uri, urn) = self.0.0.scheme().ports();\n\t\t\t\t\t\tmatch (uri != $default_uri, urn != $default_urn) {\n\t\t\t\t\t\t\t(true, true) => write!(f, \":{uri}:{urn}\"),\n\t\t\t\t\t\t\t(true, false) => write!(f, \":{uri}\"),\n\t\t\t\t\t\t\t(false, true) => write!(f, \"::{urn}\"),\n\t\t\t\t\t\t\t(false, false) => Ok(()),\n\t\t\t\t\t\t}\n\t\t\t\t\t}};\n\t\t\t\t}\n\n\t\t\t\tmatch self.0.0.kind() {\n\t\t\t\t\tSchemeKind::Regular => Ok(()),\n\t\t\t\t\tSchemeKind::Search | SchemeKind::Archive => w!(0, 0),\n\t\t\t\t\tSchemeKind::Sftp => {\n\t\t\t\t\t\tw!(self.0.0.loc().name().is_some() as usize, self.0.0.loc().name().is_some() as usize)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tD(self)\n\t}\n}\n\nimpl Display for Encode<'_> {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tmatch self.0 {\n\t\t\tUrl::Regular(_) => write!(f, \"regular://\"),\n\t\t\tUrl::Search { domain, .. } => write!(f, \"search://{}{}/\", Self::domain(domain), self.ports()),\n\t\t\tUrl::Archive { domain, .. } => {\n\t\t\t\twrite!(f, \"archive://{}{}/\", Self::domain(domain), self.ports())\n\t\t\t}\n\t\t\tUrl::Sftp { domain, .. } => write!(f, \"sftp://{}{}/\", Self::domain(domain), self.ports()),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/scheme/kind.rs",
    "content": "use anyhow::{Result, bail};\n\nuse crate::{BytesExt, scheme::{AsScheme, SchemeRef}};\n\n#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]\npub enum SchemeKind {\n\tRegular,\n\tSearch,\n\tArchive,\n\tSftp,\n}\n\nimpl<T> From<T> for SchemeKind\nwhere\n\tT: AsScheme,\n{\n\tfn from(value: T) -> Self {\n\t\tmatch value.as_scheme() {\n\t\t\tSchemeRef::Regular { .. } => Self::Regular,\n\t\t\tSchemeRef::Search { .. } => Self::Search,\n\t\t\tSchemeRef::Archive { .. } => Self::Archive,\n\t\t\tSchemeRef::Sftp { .. } => Self::Sftp,\n\t\t}\n\t}\n}\n\nimpl TryFrom<&[u8]> for SchemeKind {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(value: &[u8]) -> Result<Self, Self::Error> {\n\t\tmatch value {\n\t\t\tb\"regular\" => Ok(Self::Regular),\n\t\t\tb\"search\" => Ok(Self::Search),\n\t\t\tb\"archive\" => Ok(Self::Archive),\n\t\t\tb\"sftp\" => Ok(Self::Sftp),\n\t\t\t_ => bail!(\"invalid scheme kind: {}\", String::from_utf8_lossy(value)),\n\t\t}\n\t}\n}\n\nimpl SchemeKind {\n\t#[inline]\n\tpub const fn as_str(self) -> &'static str {\n\t\tmatch self {\n\t\t\tSelf::Regular => \"regular\",\n\t\t\tSelf::Search => \"search\",\n\t\t\tSelf::Archive => \"archive\",\n\t\t\tSelf::Sftp => \"sftp\",\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn is_local(self) -> bool {\n\t\tmatch self {\n\t\t\tSelf::Regular | Self::Search => true,\n\t\t\tSelf::Archive | Self::Sftp => false,\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn is_remote(self) -> bool {\n\t\tmatch self {\n\t\t\tSelf::Regular | Self::Search | Self::Archive => false,\n\t\t\tSelf::Sftp => true,\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn is_virtual(self) -> bool {\n\t\tmatch self {\n\t\t\tSelf::Regular | Self::Search => false,\n\t\t\tSelf::Archive | Self::Sftp => true,\n\t\t}\n\t}\n\n\t#[inline]\n\tpub(super) const fn offset(self, tilde: bool) -> usize {\n\t\t3 + self.as_str().len() + tilde as usize\n\t}\n\n\tpub fn parse(bytes: &[u8]) -> Result<Option<(Self, bool)>> {\n\t\tlet Some((kind, _)) = bytes.split_seq_once(b\"://\") else {\n\t\t\treturn Ok(None);\n\t\t};\n\n\t\tOk(Some(if let Some(stripped) = kind.strip_suffix(b\"~\") {\n\t\t\t(Self::try_from(stripped)?, true)\n\t\t} else {\n\t\t\t(Self::try_from(kind)?, false)\n\t\t}))\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/scheme/mod.rs",
    "content": "yazi_macro::mod_flat!(cow encode kind r#ref scheme traits);\n"
  },
  {
    "path": "yazi-shared/src/scheme/ref.rs",
    "content": "use std::{hash::Hash, ops::Deref};\n\nuse crate::{pool::InternStr, scheme::{AsScheme, Scheme, SchemeKind}};\n\n#[derive(Clone, Copy, Debug)]\npub enum SchemeRef<'a> {\n\tRegular { uri: usize, urn: usize },\n\tSearch { domain: &'a str, uri: usize, urn: usize },\n\tArchive { domain: &'a str, uri: usize, urn: usize },\n\tSftp { domain: &'a str, uri: usize, urn: usize },\n}\n\nimpl Deref for SchemeRef<'_> {\n\ttype Target = SchemeKind;\n\n\t#[inline]\n\tfn deref(&self) -> &Self::Target {\n\t\tmatch self {\n\t\t\tSelf::Regular { .. } => &SchemeKind::Regular,\n\t\t\tSelf::Search { .. } => &SchemeKind::Search,\n\t\t\tSelf::Archive { .. } => &SchemeKind::Archive,\n\t\t\tSelf::Sftp { .. } => &SchemeKind::Sftp,\n\t\t}\n\t}\n}\n\nimpl Hash for SchemeRef<'_> {\n\tfn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n\t\tself.kind().hash(state);\n\t\tself.domain().hash(state);\n\t}\n}\n\nimpl PartialEq<SchemeRef<'_>> for SchemeRef<'_> {\n\tfn eq(&self, other: &SchemeRef) -> bool {\n\t\tself.kind() == other.kind() && self.domain() == other.domain()\n\t}\n}\n\nimpl From<SchemeRef<'_>> for Scheme {\n\tfn from(value: SchemeRef) -> Self { value.to_owned() }\n}\n\nimpl<'a> SchemeRef<'a> {\n\t#[inline]\n\tpub fn covariant(self, other: impl AsScheme) -> bool {\n\t\tlet other = other.as_scheme();\n\t\tif self.is_virtual() || other.is_virtual() { self == other } else { true }\n\t}\n\n\t#[inline]\n\tpub const fn domain(self) -> Option<&'a str> {\n\t\tmatch self {\n\t\t\tSelf::Regular { .. } => None,\n\t\t\tSelf::Search { domain, .. } | Self::Archive { domain, .. } | Self::Sftp { domain, .. } => {\n\t\t\t\tSome(domain)\n\t\t\t}\n\t\t}\n\t}\n\n\t#[inline]\n\tpub const fn kind(self) -> SchemeKind {\n\t\tmatch self {\n\t\t\tSelf::Regular { .. } => SchemeKind::Regular,\n\t\t\tSelf::Search { .. } => SchemeKind::Search,\n\t\t\tSelf::Archive { .. } => SchemeKind::Archive,\n\t\t\tSelf::Sftp { .. } => SchemeKind::Sftp,\n\t\t}\n\t}\n\n\t#[inline]\n\tpub const fn ports(self) -> (usize, usize) {\n\t\tmatch self {\n\t\t\tSelf::Regular { uri, urn } => (uri, urn),\n\t\t\tSelf::Search { uri, urn, .. } => (uri, urn),\n\t\t\tSelf::Archive { uri, urn, .. } => (uri, urn),\n\t\t\tSelf::Sftp { uri, urn, .. } => (uri, urn),\n\t\t}\n\t}\n\n\tpub fn to_owned(self) -> Scheme {\n\t\tmatch self {\n\t\t\tSelf::Regular { uri, urn } => Scheme::Regular { uri, urn },\n\t\t\tSelf::Search { domain, uri, urn } => Scheme::Search { domain: domain.intern(), uri, urn },\n\t\t\tSelf::Archive { domain, uri, urn } => Scheme::Archive { domain: domain.intern(), uri, urn },\n\t\t\tSelf::Sftp { domain, uri, urn } => Scheme::Sftp { domain: domain.intern(), uri, urn },\n\t\t}\n\t}\n\n\tpub const fn with_ports(self, uri: usize, urn: usize) -> Self {\n\t\tmatch self {\n\t\t\tSelf::Regular { .. } => Self::Regular { uri, urn },\n\t\t\tSelf::Search { domain, .. } => Self::Search { domain, uri, urn },\n\t\t\tSelf::Archive { domain, .. } => Self::Archive { domain, uri, urn },\n\t\t\tSelf::Sftp { domain, .. } => Self::Sftp { domain, uri, urn },\n\t\t}\n\t}\n\n\t#[inline]\n\tpub const fn zeroed(self) -> Self { self.with_ports(0, 0) }\n}\n"
  },
  {
    "path": "yazi-shared/src/scheme/scheme.rs",
    "content": "use std::hash::{Hash, Hasher};\n\nuse crate::{pool::Symbol, scheme::{AsScheme, SchemeRef}};\n\n#[derive(Clone, Debug, Eq, PartialEq)]\npub enum Scheme {\n\tRegular { uri: usize, urn: usize },\n\tSearch { domain: Symbol<str>, uri: usize, urn: usize },\n\tArchive { domain: Symbol<str>, uri: usize, urn: usize },\n\tSftp { domain: Symbol<str>, uri: usize, urn: usize },\n}\n\nimpl Hash for Scheme {\n\tfn hash<H: Hasher>(&self, state: &mut H) { self.as_scheme().hash(state); }\n}\n\nimpl PartialEq<SchemeRef<'_>> for Scheme {\n\tfn eq(&self, other: &SchemeRef<'_>) -> bool { self.as_scheme() == *other }\n}\n\nimpl Scheme {\n\t#[inline]\n\tpub fn into_domain(self) -> Option<Symbol<str>> {\n\t\tmatch self {\n\t\t\tSelf::Regular { .. } => None,\n\t\t\tSelf::Search { domain, .. } | Self::Archive { domain, .. } | Self::Sftp { domain, .. } => {\n\t\t\t\tSome(domain)\n\t\t\t}\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn with_ports(self, uri: usize, urn: usize) -> Self {\n\t\tmatch self {\n\t\t\tSelf::Regular { .. } => Self::Regular { uri, urn },\n\t\t\tSelf::Search { domain, .. } => Self::Search { domain, uri, urn },\n\t\t\tSelf::Archive { domain, .. } => Self::Archive { domain, uri, urn },\n\t\t\tSelf::Sftp { domain, .. } => Self::Sftp { domain, uri, urn },\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn zeroed(self) -> Self { self.with_ports(0, 0) }\n}\n"
  },
  {
    "path": "yazi-shared/src/scheme/traits.rs",
    "content": "use crate::scheme::{Scheme, SchemeCow, SchemeKind, SchemeRef};\n\npub trait AsScheme {\n\tfn as_scheme(&self) -> SchemeRef<'_>;\n}\n\nimpl AsScheme for SchemeRef<'_> {\n\t#[inline]\n\tfn as_scheme(&self) -> SchemeRef<'_> { *self }\n}\n\nimpl AsScheme for Scheme {\n\t#[inline]\n\tfn as_scheme(&self) -> SchemeRef<'_> {\n\t\tmatch *self {\n\t\t\tSelf::Regular { uri, urn } => SchemeRef::Regular { uri, urn },\n\t\t\tSelf::Search { ref domain, uri, urn } => SchemeRef::Search { domain, uri, urn },\n\t\t\tSelf::Archive { ref domain, uri, urn } => SchemeRef::Archive { domain, uri, urn },\n\t\t\tSelf::Sftp { ref domain, uri, urn } => SchemeRef::Sftp { domain, uri, urn },\n\t\t}\n\t}\n}\n\nimpl AsScheme for &Scheme {\n\t#[inline]\n\tfn as_scheme(&self) -> SchemeRef<'_> { (**self).as_scheme() }\n}\n\nimpl AsScheme for SchemeCow<'_> {\n\t#[inline]\n\tfn as_scheme(&self) -> SchemeRef<'_> {\n\t\tmatch self {\n\t\t\tSchemeCow::Borrowed(s) => *s,\n\t\t\tSchemeCow::Owned(s) => s.as_scheme(),\n\t\t}\n\t}\n}\n\nimpl AsScheme for &SchemeCow<'_> {\n\t#[inline]\n\tfn as_scheme(&self) -> SchemeRef<'_> { (**self).as_scheme() }\n}\n\n// --- SchemeLike\npub trait SchemeLike\nwhere\n\tSelf: AsScheme + Sized,\n{\n\tfn kind(&self) -> SchemeKind { *self.as_scheme() }\n\n\tfn domain(&self) -> Option<&str> { self.as_scheme().domain() }\n\n\tfn covariant(&self, other: impl AsScheme) -> bool { self.as_scheme().covariant(other) }\n\n\tfn is_local(&self) -> bool { self.as_scheme().is_local() }\n\n\tfn is_remote(&self) -> bool { self.as_scheme().is_remote() }\n\n\tfn is_virtual(&self) -> bool { self.as_scheme().is_virtual() }\n}\n\nimpl SchemeLike for Scheme {}\nimpl SchemeLike for SchemeCow<'_> {}\n"
  },
  {
    "path": "yazi-shared/src/shell/error.rs",
    "content": "use thiserror::Error;\n\n#[derive(Debug, Error)]\npub enum SplitError {\n\t#[error(\"missing closing single quote\")]\n\tMissingSingleQuote,\n\t#[error(\"missing closing double quote\")]\n\tMissingDoubleQuote,\n\t#[error(\"missing quote after escape slash\")]\n\tMissingQuoteAfterSlash,\n}\n"
  },
  {
    "path": "yazi-shared/src/shell/mod.rs",
    "content": "//! Escape characters that may have special meaning in a shell, including\n//! spaces. This is a modified version of the [`shell-escape`], [`shell-words`],\n//! [Rust std] and [this PR].\n//!\n//! [`shell-escape`]: https://crates.io/crates/shell-escape\n//! [`shell-words`]: https://crates.io/crates/shell-words\n//! [Rust std]: https://github.com/rust-lang/rust/blob/main/library/std/src/sys/args/windows.rs#L220\n//! [this PR]: https://github.com/sfackler/shell-escape/pull/9\n\nuse std::{borrow::Cow, ffi::OsStr};\n\nyazi_macro::mod_pub!(unix, windows);\n\nyazi_macro::mod_flat!(error);\n\n#[inline]\npub fn escape_os_bytes(b: &[u8]) -> Cow<'_, [u8]> {\n\t#[cfg(unix)]\n\t{\n\t\tunix::escape_os_bytes(b)\n\t}\n\t#[cfg(windows)]\n\t{\n\t\twindows::escape_os_bytes(b)\n\t}\n}\n\n#[inline]\npub fn escape_os_str(s: &OsStr) -> Cow<'_, OsStr> {\n\t#[cfg(unix)]\n\t{\n\t\tunix::escape_os_str(s)\n\t}\n\t#[cfg(windows)]\n\t{\n\t\twindows::escape_os_str(s)\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/shell/unix.rs",
    "content": "use std::{borrow::Cow, mem};\n\nuse crate::shell::SplitError;\n\npub fn escape_os_bytes(b: &[u8]) -> Cow<'_, [u8]> {\n\tif !b.is_empty() && b.iter().copied().all(allowed) {\n\t\treturn Cow::Borrowed(b);\n\t}\n\n\tlet mut escaped = Vec::with_capacity(b.len() + 2);\n\tescaped.push(b'\\'');\n\n\tfor &c in b {\n\t\tmatch c {\n\t\t\tb'\\'' | b'!' => {\n\t\t\t\tescaped.reserve(4);\n\t\t\t\tescaped.push(b'\\'');\n\t\t\t\tescaped.push(b'\\\\');\n\t\t\t\tescaped.push(c);\n\t\t\t\tescaped.push(b'\\'');\n\t\t\t}\n\t\t\t_ => escaped.push(c),\n\t\t}\n\t}\n\n\tescaped.push(b'\\'');\n\tescaped.into()\n}\n\n#[cfg(unix)]\npub fn escape_os_str(s: &std::ffi::OsStr) -> Cow<'_, std::ffi::OsStr> {\n\tuse std::os::unix::ffi::{OsStrExt, OsStringExt};\n\n\tmatch escape_os_bytes(s.as_bytes()) {\n\t\tCow::Borrowed(_) => Cow::Borrowed(s),\n\t\tCow::Owned(v) => std::ffi::OsString::from_vec(v).into(),\n\t}\n}\n\nfn allowed(b: u8) -> bool {\n\tmatches!(b, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-' | b'_' | b'=' | b'/' | b',' | b'.' | b'+')\n}\n\npub fn split(s: &str, eoo: bool) -> Result<(Vec<String>, Option<String>), SplitError> {\n\tenum State {\n\t\t/// Within a delimiter.\n\t\tDelimiter,\n\t\t/// After backslash, but before starting word.\n\t\tBackslash,\n\t\t/// Within an unquoted word.\n\t\tUnquoted,\n\t\t/// After backslash in an unquoted word.\n\t\tUnquotedBackslash,\n\t\t/// Within a single quoted word.\n\t\tSingleQuoted,\n\t\t/// Within a double quoted word.\n\t\tDoubleQuoted,\n\t\t/// After backslash inside a double quoted word.\n\t\tDoubleQuotedBackslash,\n\t\t/// Inside a comment.\n\t\tComment,\n\t}\n\tuse State::*;\n\n\tlet mut words = Vec::new();\n\tlet mut word = String::new();\n\tlet mut chars = s.chars();\n\tlet mut state = Delimiter;\n\n\tmacro_rules! flush {\n\t\t() => {\n\t\t\tif word == \"--\" && eoo {\n\t\t\t\treturn Ok((words, Some(chars.collect())));\n\t\t\t}\n\t\t\twords.push(mem::take(&mut word));\n\t\t};\n\t}\n\n\tloop {\n\t\tlet c = chars.next();\n\t\tstate = match state {\n\t\t\tDelimiter => match c {\n\t\t\t\tNone => break,\n\t\t\t\tSome('\\'') => SingleQuoted,\n\t\t\t\tSome('\\\"') => DoubleQuoted,\n\t\t\t\tSome('\\\\') => Backslash,\n\t\t\t\tSome('\\t') | Some(' ') | Some('\\n') => Delimiter,\n\t\t\t\tSome('#') => Comment,\n\t\t\t\tSome(c) => {\n\t\t\t\t\tword.push(c);\n\t\t\t\t\tUnquoted\n\t\t\t\t}\n\t\t\t},\n\t\t\tBackslash => match c {\n\t\t\t\tNone => {\n\t\t\t\t\tword.push('\\\\');\n\t\t\t\t\tflush!();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tSome('\\n') => Delimiter,\n\t\t\t\tSome(c) => {\n\t\t\t\t\tword.push(c);\n\t\t\t\t\tUnquoted\n\t\t\t\t}\n\t\t\t},\n\t\t\tUnquoted => match c {\n\t\t\t\tNone => {\n\t\t\t\t\tflush!();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tSome('\\'') => SingleQuoted,\n\t\t\t\tSome('\\\"') => DoubleQuoted,\n\t\t\t\tSome('\\\\') => UnquotedBackslash,\n\t\t\t\tSome('\\t') | Some(' ') | Some('\\n') => {\n\t\t\t\t\tflush!();\n\t\t\t\t\tDelimiter\n\t\t\t\t}\n\t\t\t\tSome(c) => {\n\t\t\t\t\tword.push(c);\n\t\t\t\t\tUnquoted\n\t\t\t\t}\n\t\t\t},\n\t\t\tUnquotedBackslash => match c {\n\t\t\t\tNone => {\n\t\t\t\t\tword.push('\\\\');\n\t\t\t\t\tflush!();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tSome('\\n') => Unquoted,\n\t\t\t\tSome(c) => {\n\t\t\t\t\tword.push(c);\n\t\t\t\t\tUnquoted\n\t\t\t\t}\n\t\t\t},\n\t\t\tSingleQuoted => match c {\n\t\t\t\tNone => return Err(SplitError::MissingSingleQuote),\n\t\t\t\tSome('\\'') => Unquoted,\n\t\t\t\tSome(c) => {\n\t\t\t\t\tword.push(c);\n\t\t\t\t\tSingleQuoted\n\t\t\t\t}\n\t\t\t},\n\t\t\tDoubleQuoted => match c {\n\t\t\t\tNone => return Err(SplitError::MissingDoubleQuote),\n\t\t\t\tSome('\\\"') => Unquoted,\n\t\t\t\tSome('\\\\') => DoubleQuotedBackslash,\n\t\t\t\tSome(c) => {\n\t\t\t\t\tword.push(c);\n\t\t\t\t\tDoubleQuoted\n\t\t\t\t}\n\t\t\t},\n\t\t\tDoubleQuotedBackslash => match c {\n\t\t\t\tNone => return Err(SplitError::MissingQuoteAfterSlash),\n\t\t\t\tSome('\\n') => DoubleQuoted,\n\t\t\t\tSome(c @ '$') | Some(c @ '`') | Some(c @ '\"') | Some(c @ '\\\\') => {\n\t\t\t\t\tword.push(c);\n\t\t\t\t\tDoubleQuoted\n\t\t\t\t}\n\t\t\t\tSome(c) => {\n\t\t\t\t\tword.push('\\\\');\n\t\t\t\t\tword.push(c);\n\t\t\t\t\tDoubleQuoted\n\t\t\t\t}\n\t\t\t},\n\t\t\tComment => match c {\n\t\t\t\tNone => break,\n\t\t\t\tSome('\\n') => Delimiter,\n\t\t\t\tSome(_) => Comment,\n\t\t\t},\n\t\t}\n\t}\n\n\tOk((words, None))\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\n\t#[test]\n\tfn test_escape_os_bytes() {\n\t\tlet cases: &[(&[u8], &[u8])] = &[\n\t\t\t(b\"\", br#\"''\"#),\n\t\t\t(b\" \", br#\"' '\"#),\n\t\t\t(b\"*\", br#\"'*'\"#),\n\t\t\t(b\"--aaa=bbb-ccc\", b\"--aaa=bbb-ccc\"),\n\t\t\t(br#\"--features=\"default\"\"#, br#\"'--features=\"default\"'\"#),\n\t\t\t(b\"linker=gcc -L/foo -Wl,bar\", br#\"'linker=gcc -L/foo -Wl,bar'\"#),\n\t\t\t(\n\t\t\t\tb\"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=/,.+\",\n\t\t\t\tb\"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=/,.+\",\n\t\t\t),\n\t\t\t(br#\"'!\\$`\\\\\\n \"#, br#\"''\\'''\\!'\\$`\\\\\\n '\"#),\n\t\t\t(&[0x66, 0x6f, 0x80, 0x6f], &[b'\\'', 0x66, 0x6f, 0x80, 0x6f, b'\\'']),\n\t\t];\n\n\t\tfor &(input, expected) in cases {\n\t\t\tlet escaped = escape_os_bytes(input);\n\t\t\tassert_eq!(escaped, expected, \"Failed to escape: {:?}\", String::from_utf8_lossy(input));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/shell/windows.rs",
    "content": "use std::borrow::Cow;\n\npub fn escape_os_bytes(b: &[u8]) -> Cow<'_, [u8]> {\n\tlet quote = needs_quotes(b);\n\tlet mut buf = Vec::with_capacity(b.len() + quote as usize * 2);\n\n\tif quote {\n\t\tbuf.push(b'\"');\n\t}\n\n\t// Loop through the string, escaping `\\` only if followed by `\"`.\n\t// And escaping `\"` by doubling them.\n\tlet mut backslashes: usize = 0;\n\tfor &c in b {\n\t\tif c == b'\\\\' {\n\t\t\tbackslashes += 1;\n\t\t} else {\n\t\t\tif c == b'\"' {\n\t\t\t\tbuf.extend((0..backslashes).map(|_| b'\\\\'));\n\t\t\t\tbuf.push(b'\"');\n\t\t\t} else if c == b'%' {\n\t\t\t\tbuf.extend_from_slice(b\"%%cd:~,\");\n\t\t\t} else if c == b'\\r' || c == b'\\n' {\n\t\t\t\tbuf.extend_from_slice(b\"%=%\");\n\t\t\t\tbackslashes = 0;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tbackslashes = 0;\n\t\t}\n\t\tbuf.push(c);\n\t}\n\n\tif quote {\n\t\tbuf.extend((0..backslashes).map(|_| b'\\\\'));\n\t\tbuf.push(b'\"');\n\t}\n\n\tbuf.into()\n}\n\n#[cfg(windows)]\npub fn escape_os_str(s: &std::ffi::OsStr) -> Cow<'_, std::ffi::OsStr> {\n\tuse crate::wtf8::FromWtf8Vec;\n\n\tmatch escape_os_bytes(s.as_encoded_bytes()) {\n\t\tCow::Borrowed(_) => Cow::Borrowed(s),\n\t\tCow::Owned(v) => std::ffi::OsString::from_wtf8_vec(v).expect(\"valid WTF-8\").into(),\n\t}\n}\n\nfn needs_quotes(arg: &[u8]) -> bool {\n\tstatic UNQUOTED: &[u8] = br\"#$*+-./:?@\\_\";\n\n\tif arg.is_empty() || arg.last() == Some(&b'\\\\') {\n\t\treturn true;\n\t}\n\n\tfor c in arg {\n\t\tif c.is_ascii_control() {\n\t\t\treturn true;\n\t\t} else if c.is_ascii() && !(c.is_ascii_alphanumeric() || UNQUOTED.contains(c)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\tfalse\n}\n\n#[cfg(windows)]\npub fn split(s: &str) -> std::io::Result<Vec<String>> {\n\tuse std::os::windows::ffi::OsStrExt;\n\n\tlet s: Vec<_> = std::ffi::OsStr::new(s).encode_wide().chain(std::iter::once(0)).collect();\n\tsplit_wide(&s)\n}\n\n#[cfg(windows)]\nfn split_wide(s: &[u16]) -> std::io::Result<Vec<String>> {\n\tuse std::mem::MaybeUninit;\n\n\tuse windows_sys::{Win32::{Foundation::LocalFree, UI::Shell::CommandLineToArgvW}, core::PCWSTR};\n\n\tunsafe extern \"C\" {\n\t\tfn wcslen(s: PCWSTR) -> usize;\n\t}\n\n\tlet mut argc = MaybeUninit::<i32>::uninit();\n\tlet argv_p = unsafe { CommandLineToArgvW(s.as_ptr(), argc.as_mut_ptr()) };\n\tif argv_p.is_null() {\n\t\treturn Err(std::io::Error::last_os_error());\n\t}\n\n\tlet argv = unsafe { std::slice::from_raw_parts(argv_p, argc.assume_init() as usize) };\n\tlet mut res = vec![];\n\tfor &arg in argv {\n\t\tlet len = unsafe { wcslen(arg) };\n\t\tres.push(String::from_utf16_lossy(unsafe { std::slice::from_raw_parts(arg, len) }));\n\t}\n\n\tunsafe { LocalFree(argv_p as _) };\n\tOk(res)\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\n\t#[test]\n\tfn test_escape_os_bytes() {\n\t\tlet cases: &[(&[u8], &[u8])] = &[\n\t\t\t// Empty string\n\t\t\t(b\"\", br#\"\"\"\"#),\n\t\t\t(br#\"\"\"\"#, br#\"\"\"\"\"\"\"\"#),\n\t\t\t// No escaping needed\n\t\t\t(b\"--aaa=bbb-ccc\", br#\"\"--aaa=bbb-ccc\"\"#),\n\t\t\t// Paths with spaces\n\t\t\t(br#\"\\path\\to\\my documents\\\"#, br#\"\"\\path\\to\\my documents\\\\\"\"#),\n\t\t\t// Strings with quotes\n\t\t\t(br#\"--features=\"default\"\"#, br#\"\"--features=\"\"default\"\"\"\"#),\n\t\t\t// Nested quotes\n\t\t\t(br#\"\"--features=\\\"default\\\"\"\"#, br#\"\"\"\"--features=\\\\\"\"default\\\\\"\"\"\"\"\"#),\n\t\t\t// Complex command\n\t\t\t(b\"linker=gcc -L/foo -Wl,bar\", br#\"\"linker=gcc -L/foo -Wl,bar\"\"#),\n\t\t\t// Variable expansion\n\t\t\t(b\"%APPDATA%.txt\", br#\"\"%%cd:~,%APPDATA%%cd:~,%.txt\"\"#),\n\t\t\t// Unicode characters\n\t\t\t(\"이것은 테스트\".as_bytes(), r#\"\"이것은 테스트\"\"#.as_bytes()),\n\t\t];\n\n\t\tfor &(input, expected) in cases {\n\t\t\tlet escaped = escape_os_bytes(input);\n\t\t\tassert_eq!(escaped, expected, \"Failed to escape: {:?}\", String::from_utf8_lossy(input));\n\t\t}\n\t}\n\n\t#[cfg(windows)]\n\t#[test]\n\tfn test_escape_os_str() {\n\t\tuse std::{ffi::OsString, os::windows::ffi::OsStringExt};\n\n\t\t#[rustfmt::skip]\n\t\tlet cases: &[(OsString, OsString)] = &[\n\t\t\t// Surrogate pairs and special characters\n\t\t\t(\n\t\t\t\tOsString::from_wide(&[0x1055, 0x006e, 0x0069, 0x0063, 0x006f, 0x0064, 0x0065]),\n\t\t\t\tOsString::from_wide(&[0x1055, 0x006e, 0x0069, 0x0063, 0x006f, 0x0064, 0x0065]),\n\t\t\t),\n\t\t\t// Surrogate pair with quotes\n\t\t\t(\n\t\t\t\tOsString::from_wide(&[0xd801, 0x006e, 0x0069, 0x0063, 0x006f, 0x0064, 0x0065]),\n\t\t\t\tOsString::from_wide(&[0xd801, 0x006e, 0x0069, 0x0063, 0x006f, 0x0064, 0x0065]),\n\t\t\t),\n\t\t];\n\n\t\tfor (input, expected) in cases {\n\t\t\tlet escaped = escape_os_str(&input);\n\t\t\tassert_eq!(&*escaped, expected, \"Failed to escape: {:?}\", input.to_string_lossy());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/source.rs",
    "content": "use serde::{Deserialize, Serialize};\n\n#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]\npub enum Source {\n\t#[default]\n\tUnknown,\n\n\tKey,\n\tEmit,\n\tRelay,\n\n\tInd,\n}\n\nimpl Source {\n\t#[inline]\n\tpub fn is_key(self) -> bool { self == Self::Key }\n\n\t#[inline]\n\tpub fn is_ind(self) -> bool { self == Self::Ind }\n}\n"
  },
  {
    "path": "yazi-shared/src/strand/buf.rs",
    "content": "use std::{borrow::Cow, ffi::OsString, hash::{Hash, Hasher}};\n\nuse anyhow::Result;\n\nuse crate::{path::PathDyn, strand::{AsStrand, Strand, StrandCow, StrandError, StrandKind}, wtf8::FromWtf8Vec};\n\n// --- StrandBuf\n#[derive(Clone, Debug, Eq)]\npub enum StrandBuf {\n\tOs(OsString),\n\tUtf8(String),\n\tBytes(Vec<u8>),\n}\n\nimpl Default for StrandBuf {\n\tfn default() -> Self { Self::Utf8(String::new()) }\n}\n\nimpl From<OsString> for StrandBuf {\n\tfn from(value: OsString) -> Self { Self::Os(value) }\n}\n\nimpl From<&str> for StrandBuf {\n\tfn from(value: &str) -> Self { Self::Utf8(value.to_owned()) }\n}\n\nimpl From<String> for StrandBuf {\n\tfn from(value: String) -> Self { Self::Utf8(value) }\n}\n\nimpl From<PathDyn<'_>> for StrandBuf {\n\tfn from(value: PathDyn) -> Self {\n\t\tmatch value {\n\t\t\tPathDyn::Os(p) => Self::Os(p.as_os_str().to_owned()),\n\t\t\tPathDyn::Unix(p) => Self::Bytes(p.as_bytes().to_owned()),\n\t\t}\n\t}\n}\n\nimpl From<StrandCow<'_>> for StrandBuf {\n\tfn from(value: StrandCow<'_>) -> Self { value.into_owned() }\n}\n\nimpl PartialEq for StrandBuf {\n\tfn eq(&self, other: &Self) -> bool { self.as_strand() == other.as_strand() }\n}\n\nimpl PartialEq<Strand<'_>> for StrandBuf {\n\tfn eq(&self, other: &Strand<'_>) -> bool { self.as_strand() == *other }\n}\n\nimpl Hash for StrandBuf {\n\tfn hash<H: Hasher>(&self, state: &mut H) { self.as_strand().hash(state); }\n}\n\nimpl StrandBuf {\n\tpub fn clear(&mut self) {\n\t\tmatch self {\n\t\t\tSelf::Os(buf) => buf.clear(),\n\t\t\tSelf::Utf8(buf) => buf.clear(),\n\t\t\tSelf::Bytes(buf) => buf.clear(),\n\t\t}\n\t}\n\n\t#[inline]\n\tpub unsafe fn from_encoded_bytes(kind: impl Into<StrandKind>, bytes: Vec<u8>) -> Self {\n\t\tmatch kind.into() {\n\t\t\tStrandKind::Utf8 => Self::Utf8(unsafe { String::from_utf8_unchecked(bytes) }),\n\t\t\tStrandKind::Os => Self::Os(unsafe { OsString::from_encoded_bytes_unchecked(bytes) }),\n\t\t\tStrandKind::Bytes => Self::Bytes(bytes),\n\t\t}\n\t}\n\n\tpub fn into_encoded_bytes(self) -> Vec<u8> {\n\t\tmatch self {\n\t\t\tSelf::Os(s) => s.into_encoded_bytes(),\n\t\t\tSelf::Utf8(s) => s.into_bytes(),\n\t\t\tSelf::Bytes(b) => b,\n\t\t}\n\t}\n\n\tpub fn into_string_lossy(self) -> String {\n\t\tmatch self {\n\t\t\tSelf::Os(s) => match s.to_string_lossy() {\n\t\t\t\tCow::Borrowed(_) => unsafe { String::from_utf8_unchecked(s.into_encoded_bytes()) },\n\t\t\t\tCow::Owned(s) => s,\n\t\t\t},\n\t\t\tSelf::Utf8(s) => s,\n\t\t\tSelf::Bytes(b) => match String::from_utf8_lossy(&b) {\n\t\t\t\tCow::Borrowed(_) => unsafe { String::from_utf8_unchecked(b) },\n\t\t\t\tCow::Owned(s) => s,\n\t\t\t},\n\t\t}\n\t}\n\n\tpub fn new(kind: impl Into<StrandKind>) -> Self { Self::with_str(kind, \"\") }\n\n\tpub fn push_str(&mut self, s: impl AsRef<str>) {\n\t\tlet s = s.as_ref();\n\t\tmatch self {\n\t\t\tSelf::Os(buf) => buf.push(s),\n\t\t\tSelf::Utf8(buf) => buf.push_str(s),\n\t\t\tSelf::Bytes(buf) => buf.extend(s.as_bytes()),\n\t\t}\n\t}\n\n\tpub fn reserve_exact(&mut self, additional: usize) {\n\t\tmatch self {\n\t\t\tSelf::Os(buf) => buf.reserve_exact(additional),\n\t\t\tSelf::Utf8(buf) => buf.reserve_exact(additional),\n\t\t\tSelf::Bytes(buf) => buf.reserve_exact(additional),\n\t\t}\n\t}\n\n\tpub fn try_push<T>(&mut self, s: T) -> Result<(), StrandError>\n\twhere\n\t\tT: AsStrand,\n\t{\n\t\tlet s = s.as_strand();\n\t\tOk(match self {\n\t\t\tSelf::Os(buf) => buf.push(s.as_os()?),\n\t\t\tSelf::Utf8(buf) => buf.push_str(s.as_utf8()?),\n\t\t\tSelf::Bytes(buf) => buf.extend(s.encoded_bytes()),\n\t\t})\n\t}\n\n\tpub fn with<K>(kind: K, bytes: Vec<u8>) -> Result<Self>\n\twhere\n\t\tK: Into<StrandKind>,\n\t{\n\t\tOk(match kind.into() {\n\t\t\tStrandKind::Utf8 => Self::Utf8(String::from_utf8(bytes)?),\n\t\t\tStrandKind::Os => Self::Os(OsString::from_wtf8_vec(bytes)?),\n\t\t\tStrandKind::Bytes => Self::Bytes(bytes),\n\t\t})\n\t}\n\n\tpub fn with_capacity<K>(kind: K, capacity: usize) -> Self\n\twhere\n\t\tK: Into<StrandKind>,\n\t{\n\t\tmatch kind.into() {\n\t\t\tStrandKind::Utf8 => Self::Utf8(String::with_capacity(capacity)),\n\t\t\tStrandKind::Os => Self::Os(OsString::with_capacity(capacity)),\n\t\t\tStrandKind::Bytes => Self::Bytes(Vec::with_capacity(capacity)),\n\t\t}\n\t}\n\n\tpub fn with_str<K, S>(kind: K, s: S) -> Self\n\twhere\n\t\tK: Into<StrandKind>,\n\t\tS: Into<String>,\n\t{\n\t\tlet s = s.into();\n\t\tmatch kind.into() {\n\t\t\tStrandKind::Utf8 => Self::Utf8(s),\n\t\t\tStrandKind::Os => Self::Os(s.into()),\n\t\t\tStrandKind::Bytes => Self::Bytes(s.into_bytes()),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/strand/conversion.rs",
    "content": "use std::{borrow::Cow, ffi::{OsStr, OsString}};\n\nuse crate::{path::{PathBufDyn, PathCow, PathDyn}, strand::{Strand, StrandBuf, StrandCow}, url::AsUrl};\n\n// --- AsStrand\npub trait AsStrand {\n\tfn as_strand(&self) -> Strand<'_>;\n}\n\nimpl AsStrand for [u8] {\n\tfn as_strand(&self) -> Strand<'_> { Strand::Bytes(self) }\n}\n\nimpl AsStrand for &[u8] {\n\tfn as_strand(&self) -> Strand<'_> { Strand::Bytes(self) }\n}\n\nimpl AsStrand for str {\n\tfn as_strand(&self) -> Strand<'_> { Strand::Utf8(self) }\n}\n\nimpl AsStrand for &str {\n\tfn as_strand(&self) -> Strand<'_> { Strand::Utf8(self) }\n}\n\nimpl AsStrand for String {\n\tfn as_strand(&self) -> Strand<'_> { Strand::Utf8(self) }\n}\n\nimpl AsStrand for &String {\n\tfn as_strand(&self) -> Strand<'_> { Strand::Utf8(self) }\n}\n\nimpl AsStrand for OsStr {\n\tfn as_strand(&self) -> Strand<'_> { Strand::Os(self) }\n}\n\nimpl AsStrand for &OsStr {\n\tfn as_strand(&self) -> Strand<'_> { Strand::Os(self) }\n}\n\nimpl AsStrand for OsString {\n\tfn as_strand(&self) -> Strand<'_> { Strand::Os(self) }\n}\n\nimpl AsStrand for &std::path::Path {\n\tfn as_strand(&self) -> Strand<'_> { Strand::Os(self.as_os_str()) }\n}\n\nimpl AsStrand for &std::path::PathBuf {\n\tfn as_strand(&self) -> Strand<'_> { Strand::Os(self.as_os_str()) }\n}\n\nimpl AsStrand for &typed_path::UnixPath {\n\tfn as_strand(&self) -> Strand<'_> { Strand::Bytes(self.as_bytes()) }\n}\n\nimpl AsStrand for crate::path::Components<'_> {\n\tfn as_strand(&self) -> Strand<'_> { self.strand() }\n}\n\nimpl AsStrand for Cow<'_, [u8]> {\n\tfn as_strand(&self) -> Strand<'_> { Strand::Bytes(self) }\n}\n\nimpl AsStrand for Cow<'_, OsStr> {\n\tfn as_strand(&self) -> Strand<'_> { Strand::Os(self) }\n}\n\nimpl AsStrand for PathDyn<'_> {\n\tfn as_strand(&self) -> Strand<'_> {\n\t\tmatch self {\n\t\t\tSelf::Os(p) => Strand::Os(p.as_os_str()),\n\t\t\tSelf::Unix(p) => Strand::Bytes(p.as_bytes()),\n\t\t}\n\t}\n}\n\nimpl AsStrand for PathBufDyn {\n\tfn as_strand(&self) -> Strand<'_> {\n\t\tmatch self {\n\t\t\tSelf::Os(p) => Strand::Os(p.as_os_str()),\n\t\t\tSelf::Unix(p) => Strand::Bytes(p.as_bytes()),\n\t\t}\n\t}\n}\n\nimpl AsStrand for &PathBufDyn {\n\tfn as_strand(&self) -> Strand<'_> { (**self).as_strand() }\n}\n\nimpl AsStrand for PathCow<'_> {\n\tfn as_strand(&self) -> Strand<'_> {\n\t\tmatch self {\n\t\t\tSelf::Borrowed(p) => p.as_strand(),\n\t\t\tSelf::Owned(p) => p.as_strand(),\n\t\t}\n\t}\n}\n\nimpl AsStrand for &PathCow<'_> {\n\tfn as_strand(&self) -> Strand<'_> { (**self).as_strand() }\n}\n\nimpl AsStrand for Strand<'_> {\n\tfn as_strand(&self) -> Strand<'_> { *self }\n}\n\nimpl AsStrand for StrandBuf {\n\tfn as_strand(&self) -> Strand<'_> {\n\t\tmatch self {\n\t\t\tSelf::Os(s) => Strand::Os(s),\n\t\t\tSelf::Utf8(s) => Strand::Utf8(s),\n\t\t\tSelf::Bytes(b) => Strand::Bytes(b),\n\t\t}\n\t}\n}\n\nimpl AsStrand for &StrandBuf {\n\tfn as_strand(&self) -> Strand<'_> { (**self).as_strand() }\n}\n\nimpl AsStrand for StrandCow<'_> {\n\tfn as_strand(&self) -> Strand<'_> {\n\t\tmatch self {\n\t\t\tStrandCow::Borrowed(s) => *s,\n\t\t\tStrandCow::Owned(s) => s.as_strand(),\n\t\t}\n\t}\n}\n\nimpl AsStrand for &StrandCow<'_> {\n\tfn as_strand(&self) -> Strand<'_> { (**self).as_strand() }\n}\n\n// --- ToStrand\npub trait ToStrand {\n\tfn to_strand(&self) -> StrandCow<'_>;\n}\n\nimpl ToStrand for String {\n\tfn to_strand(&self) -> StrandCow<'_> { self.as_strand().into() }\n}\n\nimpl<T> ToStrand for T\nwhere\n\tT: AsUrl,\n{\n\tfn to_strand(&self) -> StrandCow<'_> { self.as_url().components().strand() }\n}\n\n// --- IntoStrand\npub trait IntoStrand {\n\tfn into_strand(self) -> StrandBuf;\n}\n\nimpl IntoStrand for PathBufDyn {\n\tfn into_strand(self) -> StrandBuf {\n\t\tmatch self {\n\t\t\tSelf::Os(p) => StrandBuf::Os(p.into_os_string()),\n\t\t\tSelf::Unix(p) => StrandBuf::Bytes(p.into_vec()),\n\t\t}\n\t}\n}\n\nimpl IntoStrand for &PathBufDyn {\n\tfn into_strand(self) -> StrandBuf {\n\t\tmatch self {\n\t\t\tPathBufDyn::Os(p) => StrandBuf::Os(p.as_os_str().to_owned()),\n\t\t\tPathBufDyn::Unix(p) => StrandBuf::Bytes(p.as_bytes().to_owned()),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/strand/cow.rs",
    "content": "use std::{borrow::Cow, ffi::{OsStr, OsString}};\n\nuse anyhow::Result;\n\nuse crate::strand::{AsStrand, Strand, StrandBuf, StrandKind};\n\npub enum StrandCow<'a> {\n\tBorrowed(Strand<'a>),\n\tOwned(StrandBuf),\n}\n\nimpl Default for StrandCow<'_> {\n\tfn default() -> Self { Self::Borrowed(Strand::default()) }\n}\n\nimpl From<OsString> for StrandCow<'_> {\n\tfn from(value: OsString) -> Self { Self::Owned(StrandBuf::Os(value)) }\n}\n\nimpl<'a> From<Cow<'a, OsStr>> for StrandCow<'a> {\n\tfn from(value: Cow<'a, OsStr>) -> Self {\n\t\tmatch value {\n\t\t\tCow::Borrowed(s) => Self::Borrowed(Strand::Os(s)),\n\t\t\tCow::Owned(s) => Self::Owned(StrandBuf::Os(s)),\n\t\t}\n\t}\n}\n\nimpl<'a> From<Strand<'a>> for StrandCow<'a> {\n\tfn from(value: Strand<'a>) -> Self { Self::Borrowed(value) }\n}\n\nimpl From<StrandBuf> for StrandCow<'_> {\n\tfn from(value: StrandBuf) -> Self { Self::Owned(value) }\n}\n\nimpl<'a, T> From<&'a T> for StrandCow<'a>\nwhere\n\tT: ?Sized + AsStrand,\n{\n\tfn from(value: &'a T) -> Self { Self::Borrowed(value.as_strand()) }\n}\n\nimpl PartialEq<Strand<'_>> for StrandCow<'_> {\n\tfn eq(&self, other: &Strand) -> bool { self.as_strand() == *other }\n}\n\nimpl<'a> StrandCow<'a> {\n\tpub fn into_owned(self) -> StrandBuf {\n\t\tmatch self {\n\t\t\tSelf::Borrowed(s) => s.to_owned(),\n\t\t\tSelf::Owned(s) => s,\n\t\t}\n\t}\n\n\tpub fn into_string_lossy(self) -> String {\n\t\tmatch self {\n\t\t\tSelf::Borrowed(s) => s.to_string_lossy().into_owned(),\n\t\t\tSelf::Owned(s) => s.into_string_lossy(),\n\t\t}\n\t}\n\n\tpub fn with<K, T>(kind: K, bytes: T) -> Result<Self>\n\twhere\n\t\tK: Into<StrandKind>,\n\t\tT: Into<Cow<'a, [u8]>>,\n\t{\n\t\tOk(match bytes.into() {\n\t\t\tCow::Borrowed(b) => Strand::with(kind, b)?.into(),\n\t\t\tCow::Owned(b) => StrandBuf::with(kind, b)?.into(),\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/strand/error.rs",
    "content": "use thiserror::Error;\n\n// --- StrandError\n#[derive(Debug, Error)]\npub enum StrandError {\n\t#[error(\"conversion to OS string failed\")]\n\tAsOs,\n\t#[error(\"conversion to UTF-8 string failed\")]\n\tAsUtf8,\n}\n\nimpl From<StrandError> for std::io::Error {\n\tfn from(err: StrandError) -> Self { Self::other(err) }\n}\n"
  },
  {
    "path": "yazi-shared/src/strand/extensions.rs",
    "content": "use crate::strand::{AsStrand, Strand, StrandBuf, StrandLike, ToStrand};\n\n// --- StrandJoin\npub trait AsStrandJoin {\n\tfn join(self, sep: Strand) -> StrandBuf;\n}\n\nimpl<T> AsStrandJoin for T\nwhere\n\tT: IntoIterator,\n\tT::Item: AsStrand,\n{\n\tfn join(self, sep: Strand) -> StrandBuf {\n\t\tlet mut kind = sep.kind();\n\t\tlet mut buf = Vec::new();\n\t\tfor (i, item) in self.into_iter().enumerate() {\n\t\t\tif i > 0 {\n\t\t\t\tbuf.extend(sep.encoded_bytes());\n\t\t\t}\n\n\t\t\tlet s = item.as_strand();\n\t\t\tbuf.extend(s.encoded_bytes());\n\n\t\t\tif s.kind() > kind {\n\t\t\t\tkind = s.kind();\n\t\t\t}\n\t\t}\n\n\t\tunsafe { StrandBuf::from_encoded_bytes(kind, buf) }\n\t}\n}\n\n// --- ToStrandJoin\npub trait ToStrandJoin {\n\tfn join(self, sep: Strand) -> StrandBuf;\n}\n\nimpl<T> ToStrandJoin for T\nwhere\n\tT: IntoIterator,\n\tT::Item: ToStrand,\n{\n\tfn join(self, sep: Strand) -> StrandBuf {\n\t\tlet mut kind = sep.kind();\n\t\tlet mut buf = Vec::new();\n\t\tfor (i, item) in self.into_iter().enumerate() {\n\t\t\tif i > 0 {\n\t\t\t\tbuf.extend(sep.encoded_bytes());\n\t\t\t}\n\n\t\t\tlet s = item.to_strand();\n\t\t\tbuf.extend(s.encoded_bytes());\n\n\t\t\tif s.kind() > kind {\n\t\t\t\tkind = s.kind();\n\t\t\t}\n\t\t}\n\n\t\tunsafe { StrandBuf::from_encoded_bytes(kind, buf) }\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/strand/kind.rs",
    "content": "use crate::{path::PathKind, scheme::SchemeKind};\n\n#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]\npub enum StrandKind {\n\tUtf8  = 0,\n\tOs    = 1,\n\tBytes = 2,\n}\n\nimpl From<PathKind> for StrandKind {\n\tfn from(value: PathKind) -> Self {\n\t\tmatch value {\n\t\t\tPathKind::Os => Self::Os,\n\t\t\tPathKind::Unix => Self::Bytes,\n\t\t}\n\t}\n}\n\nimpl From<SchemeKind> for StrandKind {\n\tfn from(value: SchemeKind) -> Self {\n\t\tmatch value {\n\t\t\tSchemeKind::Regular => Self::Os,\n\t\t\tSchemeKind::Search => Self::Os,\n\t\t\tSchemeKind::Archive => Self::Os,\n\t\t\tSchemeKind::Sftp => Self::Bytes,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/strand/like.rs",
    "content": "use std::{borrow::Cow, ffi::OsStr, fmt::Display};\n\nuse crate::strand::{AsStrand, StrandBuf, StrandCow, StrandError, StrandKind};\n\n// --- StrandLike\npub trait StrandLike: AsStrand {\n\tfn as_os(&self) -> Result<&OsStr, StrandError> { self.as_strand().as_os() }\n\n\tfn as_utf8(&self) -> Result<&str, StrandError> { self.as_strand().as_utf8() }\n\n\t#[cfg(windows)]\n\tfn backslash_to_slash(&self) -> StrandCow<'_> { self.as_strand().backslash_to_slash() }\n\n\tfn contains(&self, x: impl AsStrand) -> bool { self.as_strand().contains(x) }\n\n\tfn display(&self) -> impl Display { self.as_strand().display() }\n\n\tfn encoded_bytes(&self) -> &[u8] { self.as_strand().encoded_bytes() }\n\n\tfn eq_ignore_ascii_case(&self, other: impl AsStrand) -> bool {\n\t\tself.as_strand().eq_ignore_ascii_case(other)\n\t}\n\n\tfn is_empty(&self) -> bool { self.as_strand().is_empty() }\n\n\tfn kind(&self) -> StrandKind { self.as_strand().kind() }\n\n\tfn len(&self) -> usize { self.as_strand().len() }\n\n\tfn starts_with(&self, needle: impl AsStrand) -> bool { self.as_strand().starts_with(needle) }\n\n\tfn starts_with_ignore_ascii_case(&self, needle: impl AsStrand) -> bool {\n\t\tself.as_strand().starts_with_ignore_ascii_case(needle)\n\t}\n\n\tfn to_owned(&self) -> StrandBuf { self.as_strand().to_owned() }\n\n\tfn to_str(&self) -> Result<&str, std::str::Utf8Error> { self.as_strand().to_str() }\n\n\tfn to_string_lossy(&self) -> Cow<'_, str> { self.as_strand().to_string_lossy() }\n}\n\nimpl<S> From<&S> for StrandBuf\nwhere\n\tS: StrandLike,\n{\n\tfn from(value: &S) -> Self { value.to_owned() }\n}\n\nimpl StrandLike for StrandBuf {}\nimpl StrandLike for StrandCow<'_> {}\n"
  },
  {
    "path": "yazi-shared/src/strand/mod.rs",
    "content": "yazi_macro::mod_flat!(buf conversion cow error extensions kind like strand view);\n"
  },
  {
    "path": "yazi-shared/src/strand/strand.rs",
    "content": "use std::{borrow::Cow, ffi::OsStr, fmt::Display};\n\nuse anyhow::Result;\n\nuse crate::{BytesExt, strand::{AsStrand, StrandBuf, StrandError, StrandKind}, wtf8::FromWtf8};\n\n// --- Strand\n#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd)]\npub enum Strand<'p> {\n\tOs(&'p OsStr),\n\tUtf8(&'p str),\n\tBytes(&'p [u8]),\n}\n\nimpl Default for Strand<'_> {\n\tfn default() -> Self { Self::Utf8(\"\") }\n}\n\nimpl<'a> From<&'a OsStr> for Strand<'a> {\n\tfn from(value: &'a OsStr) -> Self { Self::Os(value) }\n}\n\nimpl<'a> From<&'a str> for Strand<'a> {\n\tfn from(value: &'a str) -> Self { Self::Utf8(value) }\n}\n\nimpl<'a> From<&'a [u8]> for Strand<'a> {\n\tfn from(value: &'a [u8]) -> Self { Self::Bytes(value) }\n}\n\nimpl<'a> From<&'a StrandBuf> for Strand<'a> {\n\tfn from(value: &'a StrandBuf) -> Self {\n\t\tmatch value {\n\t\t\tStrandBuf::Os(s) => Self::Os(s),\n\t\t\tStrandBuf::Utf8(s) => Self::Utf8(s),\n\t\t\tStrandBuf::Bytes(s) => Self::Bytes(s),\n\t\t}\n\t}\n}\n\nimpl PartialEq for Strand<'_> {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tmatch *other {\n\t\t\tSelf::Os(s) => *self == s,\n\t\t\tSelf::Utf8(s) => *self == s,\n\t\t\tSelf::Bytes(b) => *self == b,\n\t\t}\n\t}\n}\n\nimpl PartialEq<&OsStr> for Strand<'_> {\n\tfn eq(&self, other: &&OsStr) -> bool {\n\t\tmatch *self {\n\t\t\tSelf::Os(s) => s == *other,\n\t\t\tSelf::Utf8(s) => s == *other,\n\t\t\tSelf::Bytes(b) => b == other.as_encoded_bytes(),\n\t\t}\n\t}\n}\n\nimpl PartialEq<&str> for Strand<'_> {\n\tfn eq(&self, other: &&str) -> bool {\n\t\tmatch *self {\n\t\t\tSelf::Os(s) => s == *other,\n\t\t\tSelf::Utf8(s) => s == *other,\n\t\t\tSelf::Bytes(b) => b == other.as_bytes(),\n\t\t}\n\t}\n}\n\nimpl PartialEq<&[u8]> for Strand<'_> {\n\tfn eq(&self, other: &&[u8]) -> bool {\n\t\tmatch *self {\n\t\t\tSelf::Os(s) => s.as_encoded_bytes() == *other,\n\t\t\tSelf::Utf8(s) => s.as_bytes() == *other,\n\t\t\tSelf::Bytes(b) => b == *other,\n\t\t}\n\t}\n}\n\nimpl<'a> Strand<'a> {\n\t#[inline]\n\tpub fn as_os(self) -> Result<&'a OsStr, StrandError> {\n\t\tmatch self {\n\t\t\tSelf::Os(s) => Ok(s),\n\t\t\tSelf::Utf8(s) => Ok(OsStr::new(s)),\n\t\t\tSelf::Bytes(b) => OsStr::from_wtf8(b).map_err(|_| StrandError::AsOs),\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn as_os_path(self) -> Result<&'a std::path::Path, StrandError> {\n\t\tself.as_os().map(std::path::Path::new)\n\t}\n\n\t#[inline]\n\tpub fn as_unix_path(self) -> &'a typed_path::UnixPath {\n\t\ttyped_path::UnixPath::new(self.encoded_bytes())\n\t}\n\n\t#[inline]\n\tpub fn as_utf8(self) -> Result<&'a str, StrandError> {\n\t\tmatch self {\n\t\t\tSelf::Os(s) => s.to_str().ok_or(StrandError::AsUtf8),\n\t\t\tSelf::Utf8(s) => Ok(s),\n\t\t\tSelf::Bytes(b) => str::from_utf8(b).map_err(|_| StrandError::AsUtf8),\n\t\t}\n\t}\n\n\t#[cfg(windows)]\n\tpub fn backslash_to_slash(self) -> super::StrandCow<'a> {\n\t\tlet bytes = self.encoded_bytes();\n\n\t\t// Fast path to skip if there are no backslashes\n\t\tlet skip_len = bytes.iter().take_while(|&&b| b != b'\\\\').count();\n\t\tif skip_len >= bytes.len() {\n\t\t\treturn self.into();\n\t\t}\n\n\t\tlet (skip, rest) = bytes.split_at(skip_len);\n\t\tlet mut out = Vec::new();\n\t\tout.reserve_exact(bytes.len());\n\t\tout.extend(skip);\n\n\t\tfor &b in rest {\n\t\t\tout.push(if b == b'\\\\' { b'/' } else { b });\n\t\t}\n\t\tunsafe { StrandBuf::from_encoded_bytes(self.kind(), out) }.into()\n\t}\n\n\tpub fn contains(self, x: impl AsStrand) -> bool {\n\t\tmemchr::memmem::find(self.encoded_bytes(), x.as_strand().encoded_bytes()).is_some()\n\t}\n\n\tpub fn display(self) -> impl Display {\n\t\tstruct D<'a>(Strand<'a>);\n\n\t\timpl<'a> Display for D<'a> {\n\t\t\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\t\t\tmatch self.0 {\n\t\t\t\t\tStrand::Os(s) => s.display().fmt(f),\n\t\t\t\t\tStrand::Utf8(s) => s.fmt(f),\n\t\t\t\t\tStrand::Bytes(b) => b.display().fmt(f),\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tD(self)\n\t}\n\n\t#[inline]\n\tpub fn encoded_bytes(self) -> &'a [u8] {\n\t\tmatch self {\n\t\t\tSelf::Os(s) => s.as_encoded_bytes(),\n\t\t\tSelf::Utf8(s) => s.as_bytes(),\n\t\t\tSelf::Bytes(b) => b,\n\t\t}\n\t}\n\n\tpub fn eq_ignore_ascii_case(self, other: impl AsStrand) -> bool {\n\t\tself.encoded_bytes().eq_ignore_ascii_case(other.as_strand().encoded_bytes())\n\t}\n\n\t#[inline]\n\tpub unsafe fn from_encoded_bytes(kind: impl Into<StrandKind>, bytes: &'a [u8]) -> Self {\n\t\tmatch kind.into() {\n\t\t\tStrandKind::Utf8 => Self::Utf8(unsafe { str::from_utf8_unchecked(bytes) }),\n\t\t\tStrandKind::Os => Self::Os(unsafe { OsStr::from_encoded_bytes_unchecked(bytes) }),\n\t\t\tStrandKind::Bytes => Self::Bytes(bytes),\n\t\t}\n\t}\n\n\tpub fn is_empty(self) -> bool { self.encoded_bytes().is_empty() }\n\n\tpub fn kind(self) -> StrandKind {\n\t\tmatch self {\n\t\t\tSelf::Utf8(_) => StrandKind::Utf8,\n\t\t\tSelf::Os(_) => StrandKind::Os,\n\t\t\tSelf::Bytes(_) => StrandKind::Bytes,\n\t\t}\n\t}\n\n\tpub fn len(self) -> usize { self.encoded_bytes().len() }\n\n\tpub fn starts_with(self, needle: impl AsStrand) -> bool {\n\t\tself.encoded_bytes().starts_with(needle.as_strand().encoded_bytes())\n\t}\n\n\tpub fn starts_with_ignore_ascii_case(self, needle: impl AsStrand) -> bool {\n\t\tlet haystack = self.encoded_bytes();\n\t\tlet needle = needle.as_strand().encoded_bytes();\n\t\thaystack.len() >= needle.len() && haystack[..needle.len()].eq_ignore_ascii_case(needle)\n\t}\n\n\tpub fn to_owned(self) -> StrandBuf {\n\t\tmatch self {\n\t\t\tSelf::Os(s) => StrandBuf::Os(s.to_owned()),\n\t\t\tSelf::Utf8(s) => StrandBuf::Utf8(s.to_owned()),\n\t\t\tSelf::Bytes(b) => StrandBuf::Bytes(b.to_owned()),\n\t\t}\n\t}\n\n\tpub fn to_str(self) -> Result<&'a str, std::str::Utf8Error> {\n\t\tstr::from_utf8(self.encoded_bytes())\n\t}\n\n\tpub fn to_string_lossy(self) -> Cow<'a, str> { String::from_utf8_lossy(self.encoded_bytes()) }\n\n\tpub fn with<K, S>(kind: K, strand: &'a S) -> Result<Self>\n\twhere\n\t\tK: Into<StrandKind>,\n\t\tS: ?Sized + AsStrand,\n\t{\n\t\tlet strand = strand.as_strand();\n\t\tOk(match kind.into() {\n\t\t\tStrandKind::Utf8 => Self::Utf8(strand.as_utf8()?),\n\t\t\tStrandKind::Os => Self::Os(strand.as_os()?),\n\t\t\tStrandKind::Bytes => Self::Bytes(strand.encoded_bytes()),\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/strand/view.rs",
    "content": "use std::ffi::OsStr;\n\n// --- AsStrandView\npub trait AsStrandView<'a, T> {\n\tfn as_strand_view(self) -> T;\n}\n\nimpl<'a> AsStrandView<'a, &'a OsStr> for &'a OsStr {\n\tfn as_strand_view(self) -> &'a OsStr { self }\n}\n\nimpl<'a> AsStrandView<'a, &'a OsStr> for &'a std::path::Path {\n\tfn as_strand_view(self) -> &'a OsStr { self.as_os_str() }\n}\n\nimpl<'a> AsStrandView<'a, &'a OsStr> for std::path::Components<'a> {\n\tfn as_strand_view(self) -> &'a OsStr { self.as_path().as_os_str() }\n}\n\nimpl<'a> AsStrandView<'a, &'a [u8]> for &'a [u8] {\n\tfn as_strand_view(self) -> &'a [u8] { self }\n}\n\nimpl<'a> AsStrandView<'a, &'a [u8]> for &'a typed_path::UnixPath {\n\tfn as_strand_view(self) -> &'a [u8] { self.as_bytes() }\n}\n\nimpl<'a> AsStrandView<'a, &'a [u8]> for typed_path::UnixComponents<'a> {\n\tfn as_strand_view(self) -> &'a [u8] { self.as_path::<typed_path::UnixEncoding>().as_bytes() }\n}\n"
  },
  {
    "path": "yazi-shared/src/sync_cell.rs",
    "content": "use std::{cell::Cell, fmt::{Debug, Display, Formatter}, ops::Deref};\n\nuse serde::{Deserialize, Deserializer, Serialize, Serializer};\n\n/// [`Cell`], but [`Sync`].\n///\n/// This is just a `Cell`, except it implements `Sync`\n/// if `T` implements `Sync`.\npub struct SyncCell<T: ?Sized>(Cell<T>);\n\nunsafe impl<T: ?Sized + Sync> Sync for SyncCell<T> {}\n\nimpl<T> SyncCell<T> {\n\t#[inline]\n\tpub const fn new(value: T) -> Self { Self(Cell::new(value)) }\n}\n\nimpl<T: Default> Default for SyncCell<T> {\n\tfn default() -> Self { Self::new(T::default()) }\n}\n\nimpl<T> Deref for SyncCell<T> {\n\ttype Target = Cell<T>;\n\n\t#[inline]\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl<T: Copy> Clone for SyncCell<T> {\n\t#[inline]\n\tfn clone(&self) -> Self { Self::new(self.get()) }\n}\n\nimpl<T: Copy + Debug> Debug for SyncCell<T> {\n\tfn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { Debug::fmt(&self.get(), f) }\n}\n\nimpl<T: Copy + Display> Display for SyncCell<T> {\n\tfn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { Display::fmt(&self.get(), f) }\n}\n\nimpl<T> Serialize for SyncCell<T>\nwhere\n\tT: Copy + Serialize,\n{\n\tfn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n\t\tself.0.serialize(serializer)\n\t}\n}\n\nimpl<'de, T> Deserialize<'de> for SyncCell<T>\nwhere\n\tT: Copy + Deserialize<'de>,\n{\n\tfn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {\n\t\tOk(Self::new(T::deserialize(deserializer)?))\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/terminal.rs",
    "content": "use std::io::Write;\n\nuse crossterm::execute;\n\n#[inline]\npub fn terminal_clear(mut w: impl Write) -> std::io::Result<()> {\n\texecute!(\n\t\tw,\n\t\tcrossterm::terminal::Clear(crossterm::terminal::ClearType::All),\n\t\tcrossterm::style::Print(\"\\n\")\n\t)\n}\n"
  },
  {
    "path": "yazi-shared/src/tests.rs",
    "content": "pub fn init_tests() {\n\tstatic INIT: std::sync::OnceLock<()> = std::sync::OnceLock::new();\n\n\tINIT.get_or_init(|| {\n\t\tcrate::init();\n\t});\n}\n"
  },
  {
    "path": "yazi-shared/src/throttle.rs",
    "content": "use std::{fmt::Debug, mem, sync::atomic::{AtomicU64, AtomicUsize, Ordering}, time::Duration};\n\nuse parking_lot::Mutex;\n\nuse crate::timestamp_us;\n\n#[derive(Debug)]\npub struct Throttle<T> {\n\ttotal:    AtomicUsize,\n\tinterval: Duration,\n\tlast:     AtomicU64,\n\tbuf:      Mutex<Vec<T>>,\n}\n\nimpl<T> Throttle<T> {\n\tpub fn new(total: usize, interval: Duration) -> Self {\n\t\tSelf {\n\t\t\ttotal: AtomicUsize::new(total),\n\t\t\tinterval,\n\t\t\tlast: AtomicU64::new(timestamp_us() - interval.as_micros() as u64),\n\t\t\tbuf: Default::default(),\n\t\t}\n\t}\n\n\tpub fn done<F>(&self, data: T, f: F)\n\twhere\n\t\tF: FnOnce(Vec<T>),\n\t{\n\t\tlet total = self.total.fetch_sub(1, Ordering::Relaxed);\n\t\tif total == 1 {\n\t\t\treturn self.flush(data, f);\n\t\t}\n\n\t\tlet last = self.last.load(Ordering::Relaxed);\n\t\tlet now = timestamp_us();\n\t\tif now > self.interval.as_micros() as u64 + last {\n\t\t\tself.last.store(now, Ordering::Relaxed);\n\t\t\treturn self.flush(data, f);\n\t\t}\n\n\t\tself.buf.lock().push(data);\n\t}\n\n\t#[inline]\n\tfn flush<F>(&self, data: T, f: F)\n\twhere\n\t\tF: FnOnce(Vec<T>),\n\t{\n\t\tlet mut buf = mem::take(&mut *self.buf.lock());\n\t\tbuf.push(data);\n\t\tf(buf)\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/time.rs",
    "content": "use std::time::{SystemTime, UNIX_EPOCH};\n\n#[inline]\npub fn timestamp_us() -> u64 {\n\tSystemTime::now().duration_since(UNIX_EPOCH).expect(\"Time went backwards\").as_micros() as _\n}\n"
  },
  {
    "path": "yazi-shared/src/translit/mod.rs",
    "content": "yazi_macro::mod_flat!(table traits);\n"
  },
  {
    "path": "yazi-shared/src/translit/table.rs",
    "content": "const TABLE_0: [&str; 496] = [\n\t\"A\",  // À 192\n\t\"A\",  // Á 193\n\t\"A\",  // Â 194\n\t\"A\",  // Ã 195\n\t\"A\",  // Ä 196\n\t\"A\",  // Å 197\n\t\"AE\", // Æ 198\n\t\"C\",  // Ç 199\n\t\"E\",  // È 200\n\t\"E\",  // É 201\n\t\"E\",  // Ê 202\n\t\"E\",  // Ë 203\n\t\"I\",  // Ì 204\n\t\"I\",  // Í 205\n\t\"I\",  // Î 206\n\t\"I\",  // Ï 207\n\t\"D\",  // Ð 208\n\t\"N\",  // Ñ 209\n\t\"O\",  // Ò 210\n\t\"O\",  // Ó 211\n\t\"O\",  // Ô 212\n\t\"O\",  // Õ 213\n\t\"O\",  // Ö 214\n\t\"×\",  // × 215\n\t\"O\",  // Ø 216\n\t\"U\",  // Ù 217\n\t\"U\",  // Ú 218\n\t\"U\",  // Û 219\n\t\"U\",  // Ü 220\n\t\"Y\",  // Ý 221\n\t\"T\",  // Þ 222\n\t\"ss\", // ß 223\n\t\"a\",  // à 224\n\t\"a\",  // á 225\n\t\"a\",  // â 226\n\t\"a\",  // ã 227\n\t\"a\",  // ä 228\n\t\"a\",  // å 229\n\t\"ae\", // æ 230\n\t\"c\",  // ç 231\n\t\"e\",  // è 232\n\t\"e\",  // é 233\n\t\"e\",  // ê 234\n\t\"e\",  // ë 235\n\t\"i\",  // ì 236\n\t\"i\",  // í 237\n\t\"i\",  // î 238\n\t\"i\",  // ï 239\n\t\"d\",  // ð 240\n\t\"n\",  // ñ 241\n\t\"o\",  // ò 242\n\t\"o\",  // ó 243\n\t\"o\",  // ô 244\n\t\"o\",  // õ 245\n\t\"o\",  // ö 246\n\t\"÷\",  // ÷ 247\n\t\"o\",  // ø 248\n\t\"u\",  // ù 249\n\t\"u\",  // ú 250\n\t\"u\",  // û 251\n\t\"u\",  // ü 252\n\t\"y\",  // ý 253\n\t\"t\",  // þ 254\n\t\"y\",  // ÿ 255\n\t\"A\",  // Ā 256\n\t\"a\",  // ā 257\n\t\"A\",  // Ă 258\n\t\"a\",  // ă 259\n\t\"A\",  // Ą 260\n\t\"a\",  // ą 261\n\t\"C\",  // Ć 262\n\t\"c\",  // ć 263\n\t\"C\",  // Ĉ 264\n\t\"c\",  // ĉ 265\n\t\"C\",  // Ċ 266\n\t\"c\",  // ċ 267\n\t\"C\",  // Č 268\n\t\"c\",  // č 269\n\t\"D\",  // Ď 270\n\t\"d\",  // ď 271\n\t\"D\",  // Đ 272\n\t\"d\",  // đ 273\n\t\"E\",  // Ē 274\n\t\"e\",  // ē 275\n\t\"E\",  // Ĕ 276\n\t\"e\",  // ĕ 277\n\t\"E\",  // Ė 278\n\t\"e\",  // ė 279\n\t\"E\",  // Ę 280\n\t\"e\",  // ę 281\n\t\"E\",  // Ě 282\n\t\"e\",  // ě 283\n\t\"G\",  // Ĝ 284\n\t\"g\",  // ĝ 285\n\t\"G\",  // Ğ 286\n\t\"g\",  // ğ 287\n\t\"G\",  // Ġ 288\n\t\"g\",  // ġ 289\n\t\"G\",  // Ģ 290\n\t\"g\",  // ģ 291\n\t\"H\",  // Ĥ 292\n\t\"h\",  // ĥ 293\n\t\"H\",  // Ħ 294\n\t\"h\",  // ħ 295\n\t\"I\",  // Ĩ 296\n\t\"i\",  // ĩ 297\n\t\"I\",  // Ī 298\n\t\"i\",  // ī 299\n\t\"I\",  // Ĭ 300\n\t\"i\",  // ĭ 301\n\t\"I\",  // Į 302\n\t\"i\",  // į 303\n\t\"I\",  // İ 304\n\t\"i\",  // ı 305\n\t\"IJ\", // Ĳ 306\n\t\"ij\", // ĳ 307\n\t\"J\",  // Ĵ 308\n\t\"j\",  // ĵ 309\n\t\"K\",  // Ķ 310\n\t\"k\",  // ķ 311\n\t\"k\",  // ĸ 312\n\t\"L\",  // Ĺ 313\n\t\"l\",  // ĺ 314\n\t\"L\",  // Ļ 315\n\t\"l\",  // ļ 316\n\t\"L\",  // Ľ 317\n\t\"l\",  // ľ 318\n\t\"L\",  // Ŀ 319\n\t\"l\",  // ŀ 320\n\t\"L\",  // Ł 321\n\t\"l\",  // ł 322\n\t\"N\",  // Ń 323\n\t\"n\",  // ń 324\n\t\"N\",  // Ņ 325\n\t\"n\",  // ņ 326\n\t\"N\",  // Ň 327\n\t\"n\",  // ň 328\n\t\"n\",  // ŉ 329\n\t\"N\",  // Ŋ 330\n\t\"n\",  // ŋ 331\n\t\"O\",  // Ō 332\n\t\"o\",  // ō 333\n\t\"O\",  // Ŏ 334\n\t\"o\",  // ŏ 335\n\t\"O\",  // Ő 336\n\t\"o\",  // ő 337\n\t\"OE\", // Œ 338\n\t\"oe\", // œ 339\n\t\"R\",  // Ŕ 340\n\t\"r\",  // ŕ 341\n\t\"R\",  // Ŗ 342\n\t\"r\",  // ŗ 343\n\t\"R\",  // Ř 344\n\t\"r\",  // ř 345\n\t\"S\",  // Ś 346\n\t\"s\",  // ś 347\n\t\"S\",  // Ŝ 348\n\t\"s\",  // ŝ 349\n\t\"S\",  // Ş 350\n\t\"s\",  // ş 351\n\t\"S\",  // Š 352\n\t\"s\",  // š 353\n\t\"T\",  // Ţ 354\n\t\"t\",  // ţ 355\n\t\"T\",  // Ť 356\n\t\"t\",  // ť 357\n\t\"T\",  // Ŧ 358\n\t\"t\",  // ŧ 359\n\t\"U\",  // Ũ 360\n\t\"u\",  // ũ 361\n\t\"U\",  // Ū 362\n\t\"u\",  // ū 363\n\t\"U\",  // Ŭ 364\n\t\"u\",  // ŭ 365\n\t\"U\",  // Ů 366\n\t\"u\",  // ů 367\n\t\"U\",  // Ű 368\n\t\"u\",  // ű 369\n\t\"U\",  // Ų 370\n\t\"u\",  // ų 371\n\t\"W\",  // Ŵ 372\n\t\"w\",  // ŵ 373\n\t\"Y\",  // Ŷ 374\n\t\"y\",  // ŷ 375\n\t\"Y\",  // Ÿ 376\n\t\"Z\",  // Ź 377\n\t\"z\",  // ź 378\n\t\"Z\",  // Ż 379\n\t\"z\",  // ż 380\n\t\"Z\",  // Ž 381\n\t\"z\",  // ž 382\n\t\"s\",  // ſ 383\n\t\"ƀ\",  // ƀ 384\n\t\"B\",  // Ɓ 385\n\t\"Ƃ\",  // Ƃ 386\n\t\"ƃ\",  // ƃ 387\n\t\"Ƅ\",  // Ƅ 388\n\t\"ƅ\",  // ƅ 389\n\t\"O\",  // Ɔ 390\n\t\"Ƈ\",  // Ƈ 391\n\t\"ƈ\",  // ƈ 392\n\t\"Ɖ\",  // Ɖ 393\n\t\"D\",  // Ɗ 394\n\t\"Ƌ\",  // Ƌ 395\n\t\"ƌ\",  // ƌ 396\n\t\"ƍ\",  // ƍ 397\n\t\"Ǝ\",  // Ǝ 398\n\t\"E\",  // Ə 399\n\t\"E\",  // Ɛ 400\n\t\"F\",  // Ƒ 401\n\t\"f\",  // ƒ 402\n\t\"Ɠ\",  // Ɠ 403\n\t\"Ɣ\",  // Ɣ 404\n\t\"ƕ\",  // ƕ 405\n\t\"Ɩ\",  // Ɩ 406\n\t\"Ɨ\",  // Ɨ 407\n\t\"K\",  // Ƙ 408\n\t\"k\",  // ƙ 409\n\t\"l\",  // ƚ 410\n\t\"l\",  // ƛ 411\n\t\"Ɯ\",  // Ɯ 412\n\t\"N\",  // Ɲ 413\n\t\"ƞ\",  // ƞ 414\n\t\"O\",  // Ɵ 415\n\t\"O\",  // Ơ 416\n\t\"o\",  // ơ 417\n\t\"Ƣ\",  // Ƣ 418\n\t\"ƣ\",  // ƣ 419\n\t\"Ƥ\",  // Ƥ 420\n\t\"ƥ\",  // ƥ 421\n\t\"Ʀ\",  // Ʀ 422\n\t\"Ƨ\",  // Ƨ 423\n\t\"ƨ\",  // ƨ 424\n\t\"Ʃ\",  // Ʃ 425\n\t\"ƪ\",  // ƪ 426\n\t\"ƫ\",  // ƫ 427\n\t\"Ƭ\",  // Ƭ 428\n\t\"ƭ\",  // ƭ 429\n\t\"Ʈ\",  // Ʈ 430\n\t\"U\",  // Ư 431\n\t\"u\",  // ư 432\n\t\"Ʊ\",  // Ʊ 433\n\t\"Ʋ\",  // Ʋ 434\n\t\"Y\",  // Ƴ 435\n\t\"y\",  // ƴ 436\n\t\"Z\",  // Ƶ 437\n\t\"z\",  // ƶ 438\n\t\"Ʒ\",  // Ʒ 439\n\t\"Ƹ\",  // Ƹ 440\n\t\"ƹ\",  // ƹ 441\n\t\"ƺ\",  // ƺ 442\n\t\"ƻ\",  // ƻ 443\n\t\"Ƽ\",  // Ƽ 444\n\t\"ƽ\",  // ƽ 445\n\t\"ƾ\",  // ƾ 446\n\t\"ƿ\",  // ƿ 447\n\t\"ǀ\",  // ǀ 448\n\t\"ǁ\",  // ǁ 449\n\t\"ǂ\",  // ǂ 450\n\t\"ǃ\",  // ǃ 451\n\t\"DZ\", // Ǆ 452\n\t\"Dz\", // ǅ 453\n\t\"dz\", // ǆ 454\n\t\"LJ\", // Ǉ 455\n\t\"Lj\", // ǈ 456\n\t\"lj\", // ǉ 457\n\t\"NJ\", // Ǌ 458\n\t\"Nj\", // ǋ 459\n\t\"nj\", // ǌ 460\n\t\"A\",  // Ǎ 461\n\t\"a\",  // ǎ 462\n\t\"I\",  // Ǐ 463\n\t\"i\",  // ǐ 464\n\t\"O\",  // Ǒ 465\n\t\"o\",  // ǒ 466\n\t\"U\",  // Ǔ 467\n\t\"u\",  // ǔ 468\n\t\"U\",  // Ǖ 469\n\t\"u\",  // ǖ 470\n\t\"U\",  // Ǘ 471\n\t\"u\",  // ǘ 472\n\t\"U\",  // Ǚ 473\n\t\"u\",  // ǚ 474\n\t\"U\",  // Ǜ 475\n\t\"u\",  // ǜ 476\n\t\"ǝ\",  // ǝ 477\n\t\"Ǟ\",  // Ǟ 478\n\t\"ǟ\",  // ǟ 479\n\t\"Ǡ\",  // Ǡ 480\n\t\"ǡ\",  // ǡ 481\n\t\"Ǣ\",  // Ǣ 482\n\t\"ǣ\",  // ǣ 483\n\t\"Ǥ\",  // Ǥ 484\n\t\"ǥ\",  // ǥ 485\n\t\"G\",  // Ǧ 486\n\t\"g\",  // ǧ 487\n\t\"Ǩ\",  // Ǩ 488\n\t\"ǩ\",  // ǩ 489\n\t\"O\",  // Ǫ 490\n\t\"o\",  // ǫ 491\n\t\"Ǭ\",  // Ǭ 492\n\t\"ǭ\",  // ǭ 493\n\t\"Ǯ\",  // Ǯ 494\n\t\"e\",  // ǯ 495\n\t\"j\",  // ǰ 496\n\t\"DZ\", // Ǳ 497\n\t\"Dz\", // ǲ 498\n\t\"dz\", // ǳ 499\n\t\"G\",  // Ǵ 500\n\t\"g\",  // ǵ 501\n\t\"Ƕ\",  // Ƕ 502\n\t\"Ƿ\",  // Ƿ 503\n\t\"N\",  // Ǹ 504\n\t\"n\",  // ǹ 505\n\t\"A\",  // Ǻ 506\n\t\"a\",  // ǻ 507\n\t\"AE\", // Ǽ 508\n\t\"ae\", // ǽ 509\n\t\"O\",  // Ǿ 510\n\t\"o\",  // ǿ 511\n\t\"Ȁ\",  // Ȁ 512\n\t\"ȁ\",  // ȁ 513\n\t\"Ȃ\",  // Ȃ 514\n\t\"ȃ\",  // ȃ 515\n\t\"Ȅ\",  // Ȅ 516\n\t\"ȅ\",  // ȅ 517\n\t\"Ȇ\",  // Ȇ 518\n\t\"ȇ\",  // ȇ 519\n\t\"Ȉ\",  // Ȉ 520\n\t\"ȉ\",  // ȉ 521\n\t\"Ȋ\",  // Ȋ 522\n\t\"ȋ\",  // ȋ 523\n\t\"Ȍ\",  // Ȍ 524\n\t\"ȍ\",  // ȍ 525\n\t\"Ȏ\",  // Ȏ 526\n\t\"ȏ\",  // ȏ 527\n\t\"Ȑ\",  // Ȑ 528\n\t\"ȑ\",  // ȑ 529\n\t\"Ȓ\",  // Ȓ 530\n\t\"ȓ\",  // ȓ 531\n\t\"Ȕ\",  // Ȕ 532\n\t\"ȕ\",  // ȕ 533\n\t\"Ȗ\",  // Ȗ 534\n\t\"ȗ\",  // ȗ 535\n\t\"S\",  // Ș 536\n\t\"s\",  // ș 537\n\t\"T\",  // Ț 538\n\t\"t\",  // ț 539\n\t\"Ȝ\",  // Ȝ 540\n\t\"ȝ\",  // ȝ 541\n\t\"Ȟ\",  // Ȟ 542\n\t\"ȟ\",  // ȟ 543\n\t\"Ƞ\",  // Ƞ 544\n\t\"ȡ\",  // ȡ 545\n\t\"Ȣ\",  // Ȣ 546\n\t\"ȣ\",  // ȣ 547\n\t\"Ȥ\",  // Ȥ 548\n\t\"ȥ\",  // ȥ 549\n\t\"Ȧ\",  // Ȧ 550\n\t\"ȧ\",  // ȧ 551\n\t\"Ȩ\",  // Ȩ 552\n\t\"ȩ\",  // ȩ 553\n\t\"Ȫ\",  // Ȫ 554\n\t\"ȫ\",  // ȫ 555\n\t\"Ȭ\",  // Ȭ 556\n\t\"ȭ\",  // ȭ 557\n\t\"Ȯ\",  // Ȯ 558\n\t\"ȯ\",  // ȯ 559\n\t\"Ȱ\",  // Ȱ 560\n\t\"ȱ\",  // ȱ 561\n\t\"Y\",  // Ȳ 562\n\t\"y\",  // ȳ 563\n\t\"ȴ\",  // ȴ 564\n\t\"ȵ\",  // ȵ 565\n\t\"ȶ\",  // ȶ 566\n\t\"j\",  // ȷ 567\n\t\"ȸ\",  // ȸ 568\n\t\"ȹ\",  // ȹ 569\n\t\"Ⱥ\",  // Ⱥ 570\n\t\"Ȼ\",  // Ȼ 571\n\t\"ȼ\",  // ȼ 572\n\t\"L\",  // Ƚ 573\n\t\"Ⱦ\",  // Ⱦ 574\n\t\"ȿ\",  // ȿ 575\n\t\"ɀ\",  // ɀ 576\n\t\"Ɂ\",  // Ɂ 577\n\t\"ɂ\",  // ɂ 578\n\t\"Ƀ\",  // Ƀ 579\n\t\"Ʉ\",  // Ʉ 580\n\t\"Ʌ\",  // Ʌ 581\n\t\"Ɇ\",  // Ɇ 582\n\t\"ɇ\",  // ɇ 583\n\t\"Ɉ\",  // Ɉ 584\n\t\"ɉ\",  // ɉ 585\n\t\"Ɋ\",  // Ɋ 586\n\t\"ɋ\",  // ɋ 587\n\t\"Ɍ\",  // Ɍ 588\n\t\"ɍ\",  // ɍ 589\n\t\"Ɏ\",  // Ɏ 590\n\t\"ɏ\",  // ɏ 591\n\t\"a\",  // ɐ 592\n\t\"a\",  // ɑ 593\n\t\"a\",  // ɒ 594\n\t\"b\",  // ɓ 595\n\t\"o\",  // ɔ 596\n\t\"c\",  // ɕ 597\n\t\"d\",  // ɖ 598\n\t\"d\",  // ɗ 599\n\t\"e\",  // ɘ 600\n\t\"e\",  // ə 601\n\t\"e\",  // ɚ 602\n\t\"o\",  // ɛ 603\n\t\"e\",  // ɜ 604\n\t\"e\",  // ɝ 605\n\t\"e\",  // ɞ 606\n\t\"j\",  // ɟ 607\n\t\"g\",  // ɠ 608\n\t\"g\",  // ɡ 609\n\t\"ɢ\",  // ɢ 610\n\t\"g\",  // ɣ 611\n\t\"ɤ\",  // ɤ 612\n\t\"h\",  // ɥ 613\n\t\"h\",  // ɦ 614\n\t\"h\",  // ɧ 615\n\t\"i\",  // ɨ 616\n\t\"i\",  // ɩ 617\n\t\"ɪ\",  // ɪ 618\n\t\"l\",  // ɫ 619\n\t\"l\",  // ɬ 620\n\t\"l\",  // ɭ 621\n\t\"le\", // ɮ 622\n\t\"m\",  // ɯ 623\n\t\"m\",  // ɰ 624\n\t\"m\",  // ɱ 625\n\t\"n\",  // ɲ 626\n\t\"n\",  // ɳ 627\n\t\"ɴ\",  // ɴ 628\n\t\"o\",  // ɵ 629\n\t\"OE\", // ɶ 630\n\t\"ɷ\",  // ɷ 631\n\t\"p\",  // ɸ 632\n\t\"r\",  // ɹ 633\n\t\"r\",  // ɺ 634\n\t\"r\",  // ɻ 635\n\t\"r\",  // ɼ 636\n\t\"r\",  // ɽ 637\n\t\"r\",  // ɾ 638\n\t\"r\",  // ɿ 639\n\t\"ʀ\",  // ʀ 640\n\t\"R\",  // ʁ 641\n\t\"s\",  // ʂ 642\n\t\"s\",  // ʃ 643\n\t\"j\",  // ʄ 644\n\t\"s\",  // ʅ 645\n\t\"s\",  // ʆ 646\n\t\"t\",  // ʇ 647\n\t\"t\",  // ʈ 648\n\t\"u\",  // ʉ 649\n\t\"u\",  // ʊ 650\n\t\"v\",  // ʋ 651\n\t\"v\",  // ʌ 652\n\t\"w\",  // ʍ 653\n\t\"y\",  // ʎ 654\n\t\"ʏ\",  // ʏ 655\n\t\"z\",  // ʐ 656\n\t\"z\",  // ʑ 657\n\t\"e\",  // ʒ 658\n\t\"e\",  // ʓ 659\n\t\"ʔ\",  // ʔ 660\n\t\"ʕ\",  // ʕ 661\n\t\"ʖ\",  // ʖ 662\n\t\"C\",  // ʗ 663\n\t\"o\",  // ʘ 664\n\t\"ʙ\",  // ʙ 665\n\t\"e\",  // ʚ 666\n\t\"G\",  // ʛ 667\n\t\"ʜ\",  // ʜ 668\n\t\"j\",  // ʝ 669\n\t\"k\",  // ʞ 670\n\t\"ʟ\",  // ʟ 671\n\t\"q\",  // ʠ 672\n\t\"ʡ\",  // ʡ 673\n\t\"ʢ\",  // ʢ 674\n\t\"dz\", // ʣ 675\n\t\"de\", // ʤ 676\n\t\"dz\", // ʥ 677\n\t\"ts\", // ʦ 678\n\t\"ts\", // ʧ 679\n\t\"tc\", // ʨ 680\n\t\"fn\", // ʩ 681\n\t\"ls\", // ʪ 682\n\t\"lz\", // ʫ 683\n\t\"W\",  // ʬ 684\n\t\"ʭ\",  // ʭ 685\n\t\"h\",  // ʮ 686\n\t\"h\",  // ʯ 687\n];\n\nconst TABLE_1: [&str; 246] = [\n\t\"B\",  // Ḅ 7684\n\t\"b\",  // ḅ 7685\n\t\"Ḇ\",  // Ḇ 7686\n\t\"ḇ\",  // ḇ 7687\n\t\"Ḉ\",  // Ḉ 7688\n\t\"ḉ\",  // ḉ 7689\n\t\"Ḋ\",  // Ḋ 7690\n\t\"ḋ\",  // ḋ 7691\n\t\"D\",  // Ḍ 7692\n\t\"d\",  // ḍ 7693\n\t\"D\",  // Ḏ 7694\n\t\"d\",  // ḏ 7695\n\t\"Ḑ\",  // Ḑ 7696\n\t\"ḑ\",  // ḑ 7697\n\t\"D\",  // Ḓ 7698\n\t\"d\",  // ḓ 7699\n\t\"Ḕ\",  // Ḕ 7700\n\t\"ḕ\",  // ḕ 7701\n\t\"Ḗ\",  // Ḗ 7702\n\t\"ḗ\",  // ḗ 7703\n\t\"Ḙ\",  // Ḙ 7704\n\t\"ḙ\",  // ḙ 7705\n\t\"Ḛ\",  // Ḛ 7706\n\t\"ḛ\",  // ḛ 7707\n\t\"Ḝ\",  // Ḝ 7708\n\t\"ḝ\",  // ḝ 7709\n\t\"Ḟ\",  // Ḟ 7710\n\t\"ḟ\",  // ḟ 7711\n\t\"G\",  // Ḡ 7712\n\t\"g\",  // ḡ 7713\n\t\"Ḣ\",  // Ḣ 7714\n\t\"ḣ\",  // ḣ 7715\n\t\"H\",  // Ḥ 7716\n\t\"h\",  // ḥ 7717\n\t\"Ḧ\",  // Ḧ 7718\n\t\"ḧ\",  // ḧ 7719\n\t\"Ḩ\",  // Ḩ 7720\n\t\"ḩ\",  // ḩ 7721\n\t\"H\",  // Ḫ 7722\n\t\"h\",  // ḫ 7723\n\t\"Ḭ\",  // Ḭ 7724\n\t\"ḭ\",  // ḭ 7725\n\t\"Ḯ\",  // Ḯ 7726\n\t\"ḯ\",  // ḯ 7727\n\t\"Ḱ\",  // Ḱ 7728\n\t\"ḱ\",  // ḱ 7729\n\t\"K\",  // Ḳ 7730\n\t\"k\",  // ḳ 7731\n\t\"K\",  // Ḵ 7732\n\t\"k\",  // ḵ 7733\n\t\"L\",  // Ḷ 7734\n\t\"l\",  // ḷ 7735\n\t\"L\",  // Ḹ 7736\n\t\"l\",  // ḹ 7737\n\t\"L\",  // Ḻ 7738\n\t\"l\",  // ḻ 7739\n\t\"L\",  // Ḽ 7740\n\t\"l\",  // ḽ 7741\n\t\"M\",  // Ḿ 7742\n\t\"m\",  // ḿ 7743\n\t\"M\",  // Ṁ 7744\n\t\"m\",  // ṁ 7745\n\t\"M\",  // Ṃ 7746\n\t\"m\",  // ṃ 7747\n\t\"N\",  // Ṅ 7748\n\t\"n\",  // ṅ 7749\n\t\"N\",  // Ṇ 7750\n\t\"n\",  // ṇ 7751\n\t\"N\",  // Ṉ 7752\n\t\"n\",  // ṉ 7753\n\t\"N\",  // Ṋ 7754\n\t\"n\",  // ṋ 7755\n\t\"Ṍ\",  // Ṍ 7756\n\t\"ṍ\",  // ṍ 7757\n\t\"Ṏ\",  // Ṏ 7758\n\t\"ṏ\",  // ṏ 7759\n\t\"Ṑ\",  // Ṑ 7760\n\t\"ṑ\",  // ṑ 7761\n\t\"Ṓ\",  // Ṓ 7762\n\t\"ṓ\",  // ṓ 7763\n\t\"Ṕ\",  // Ṕ 7764\n\t\"ṕ\",  // ṕ 7765\n\t\"Ṗ\",  // Ṗ 7766\n\t\"ṗ\",  // ṗ 7767\n\t\"R\",  // Ṙ 7768\n\t\"r\",  // ṙ 7769\n\t\"R\",  // Ṛ 7770\n\t\"r\",  // ṛ 7771\n\t\"R\",  // Ṝ 7772\n\t\"r\",  // ṝ 7773\n\t\"R\",  // Ṟ 7774\n\t\"r\",  // ṟ 7775\n\t\"S\",  // Ṡ 7776\n\t\"s\",  // ṡ 7777\n\t\"S\",  // Ṣ 7778\n\t\"s\",  // ṣ 7779\n\t\"Ṥ\",  // Ṥ 7780\n\t\"ṥ\",  // ṥ 7781\n\t\"Ṧ\",  // Ṧ 7782\n\t\"ṧ\",  // ṧ 7783\n\t\"Ṩ\",  // Ṩ 7784\n\t\"ṩ\",  // ṩ 7785\n\t\"Ṫ\",  // Ṫ 7786\n\t\"ṫ\",  // ṫ 7787\n\t\"T\",  // Ṭ 7788\n\t\"t\",  // ṭ 7789\n\t\"T\",  // Ṯ 7790\n\t\"t\",  // ṯ 7791\n\t\"T\",  // Ṱ 7792\n\t\"t\",  // ṱ 7793\n\t\"Ṳ\",  // Ṳ 7794\n\t\"ṳ\",  // ṳ 7795\n\t\"Ṵ\",  // Ṵ 7796\n\t\"ṵ\",  // ṵ 7797\n\t\"Ṷ\",  // Ṷ 7798\n\t\"ṷ\",  // ṷ 7799\n\t\"Ṹ\",  // Ṹ 7800\n\t\"ṹ\",  // ṹ 7801\n\t\"Ṻ\",  // Ṻ 7802\n\t\"ṻ\",  // ṻ 7803\n\t\"Ṽ\",  // Ṽ 7804\n\t\"ṽ\",  // ṽ 7805\n\t\"Ṿ\",  // Ṿ 7806\n\t\"ṿ\",  // ṿ 7807\n\t\"W\",  // Ẁ 7808\n\t\"w\",  // ẁ 7809\n\t\"W\",  // Ẃ 7810\n\t\"w\",  // ẃ 7811\n\t\"W\",  // Ẅ 7812\n\t\"w\",  // ẅ 7813\n\t\"Ẇ\",  // Ẇ 7814\n\t\"ẇ\",  // ẇ 7815\n\t\"Ẉ\",  // Ẉ 7816\n\t\"ẉ\",  // ẉ 7817\n\t\"Ẋ\",  // Ẋ 7818\n\t\"ẋ\",  // ẋ 7819\n\t\"Ẍ\",  // Ẍ 7820\n\t\"ẍ\",  // ẍ 7821\n\t\"Y\",  // Ẏ 7822\n\t\"y\",  // ẏ 7823\n\t\"Ẑ\",  // Ẑ 7824\n\t\"ẑ\",  // ẑ 7825\n\t\"Z\",  // Ẓ 7826\n\t\"z\",  // ẓ 7827\n\t\"Z\",  // Ẕ 7828\n\t\"z\",  // ẕ 7829\n\t\"h\",  // ẖ 7830\n\t\"t\",  // ẗ 7831\n\t\"ẘ\",  // ẘ 7832\n\t\"ẙ\",  // ẙ 7833\n\t\"ẚ\",  // ẚ 7834\n\t\"ẛ\",  // ẛ 7835\n\t\"ẜ\",  // ẜ 7836\n\t\"ẝ\",  // ẝ 7837\n\t\"SS\", // ẞ 7838\n\t\"ẟ\",  // ẟ 7839\n\t\"A\",  // Ạ 7840\n\t\"a\",  // ạ 7841\n\t\"A\",  // Ả 7842\n\t\"a\",  // ả 7843\n\t\"A\",  // Ấ 7844\n\t\"a\",  // ấ 7845\n\t\"A\",  // Ầ 7846\n\t\"a\",  // ầ 7847\n\t\"A\",  // Ẩ 7848\n\t\"a\",  // ẩ 7849\n\t\"A\",  // Ẫ 7850\n\t\"a\",  // ẫ 7851\n\t\"A\",  // Ậ 7852\n\t\"a\",  // ậ 7853\n\t\"A\",  // Ắ 7854\n\t\"a\",  // ắ 7855\n\t\"A\",  // Ằ 7856\n\t\"a\",  // ằ 7857\n\t\"A\",  // Ẳ 7858\n\t\"a\",  // ẳ 7859\n\t\"A\",  // Ẵ 7860\n\t\"a\",  // ẵ 7861\n\t\"A\",  // Ặ 7862\n\t\"a\",  // ặ 7863\n\t\"E\",  // Ẹ 7864\n\t\"e\",  // ẹ 7865\n\t\"E\",  // Ẻ 7866\n\t\"e\",  // ẻ 7867\n\t\"E\",  // Ẽ 7868\n\t\"e\",  // ẽ 7869\n\t\"E\",  // Ế 7870\n\t\"e\",  // ế 7871\n\t\"E\",  // Ề 7872\n\t\"e\",  // ề 7873\n\t\"E\",  // Ể 7874\n\t\"e\",  // ể 7875\n\t\"E\",  // Ễ 7876\n\t\"e\",  // ễ 7877\n\t\"E\",  // Ệ 7878\n\t\"e\",  // ệ 7879\n\t\"I\",  // Ỉ 7880\n\t\"i\",  // ỉ 7881\n\t\"I\",  // Ị 7882\n\t\"i\",  // ị 7883\n\t\"O\",  // Ọ 7884\n\t\"o\",  // ọ 7885\n\t\"O\",  // Ỏ 7886\n\t\"o\",  // ỏ 7887\n\t\"O\",  // Ố 7888\n\t\"o\",  // ố 7889\n\t\"O\",  // Ồ 7890\n\t\"o\",  // ồ 7891\n\t\"O\",  // Ổ 7892\n\t\"o\",  // ổ 7893\n\t\"O\",  // Ỗ 7894\n\t\"o\",  // ỗ 7895\n\t\"O\",  // Ộ 7896\n\t\"o\",  // ộ 7897\n\t\"O\",  // Ớ 7898\n\t\"o\",  // ớ 7899\n\t\"O\",  // Ờ 7900\n\t\"o\",  // ờ 7901\n\t\"O\",  // Ở 7902\n\t\"o\",  // ở 7903\n\t\"O\",  // Ỡ 7904\n\t\"o\",  // ỡ 7905\n\t\"O\",  // Ợ 7906\n\t\"o\",  // ợ 7907\n\t\"U\",  // Ụ 7908\n\t\"u\",  // ụ 7909\n\t\"U\",  // Ủ 7910\n\t\"u\",  // ủ 7911\n\t\"U\",  // Ứ 7912\n\t\"u\",  // ứ 7913\n\t\"U\",  // Ừ 7914\n\t\"u\",  // ừ 7915\n\t\"U\",  // Ử 7916\n\t\"u\",  // ử 7917\n\t\"U\",  // Ữ 7918\n\t\"u\",  // ữ 7919\n\t\"U\",  // Ự 7920\n\t\"u\",  // ự 7921\n\t\"Y\",  // Ỳ 7922\n\t\"y\",  // ỳ 7923\n\t\"Y\",  // Ỵ 7924\n\t\"y\",  // ỵ 7925\n\t\"Y\",  // Ỷ 7926\n\t\"y\",  // ỷ 7927\n\t\"Y\",  // Ỹ 7928\n\t\"y\",  // ỹ 7929\n];\n\nconst TABLE_2: [&str; 2] = [\n\t\"fi\", // ﬁ 64257\n\t\"fl\", // ﬂ 64258\n];\n\n#[inline(always)]\npub(super) fn lookup(c: char) -> Option<&'static str> {\n\tmatch c as u16 {\n\t\t192..=687 => unsafe { Some(TABLE_0.get_unchecked((c as u16 - 192) as usize)) },\n\t\t7684..=7929 => unsafe { Some(TABLE_1.get_unchecked((c as u16 - 7684) as usize)) },\n\t\t64257..=64258 => unsafe { Some(TABLE_2.get_unchecked((c as u16 - 64257) as usize)) },\n\t\t_ => None,\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/translit/traits.rs",
    "content": "use core::str;\nuse std::borrow::Cow;\n\npub trait Transliterator {\n\tfn transliterate(&self) -> Cow<'_, str>;\n}\n\nimpl Transliterator for &[u8] {\n\tfn transliterate(&self) -> Cow<'_, str> {\n\t\t// Fast path to skip over ASCII chars at the beginning of the string\n\t\tlet ascii_len = self.iter().take_while(|&&c| c < 0x7f).count();\n\t\tif ascii_len >= self.len() {\n\t\t\treturn Cow::Borrowed(unsafe { str::from_utf8_unchecked(self) });\n\t\t}\n\n\t\tlet (ascii, rest) = self.split_at(ascii_len);\n\n\t\t// Reserve a bit more space to avoid reallocations on longer transliterations\n\t\t// but instead of `+ 16` uses `| 15` to stay in the smallest allocation bucket\n\t\t// for short strings\n\t\tlet mut out = String::new();\n\t\tout.reserve_exact(self.len() | 15);\n\t\tout.push_str(unsafe { str::from_utf8_unchecked(ascii) });\n\n\t\tfor c in String::from_utf8_lossy(rest).chars() {\n\t\t\tif let Some(s) = super::lookup(c) {\n\t\t\t\tout.push_str(s);\n\t\t\t} else {\n\t\t\t\tout.push(c);\n\t\t\t}\n\t\t}\n\t\tCow::Owned(out)\n\t}\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\n\t#[test]\n\tfn test_transliterate() {\n\t\tassert_eq!(\"Æcœ\".as_bytes().transliterate(), \"AEcoe\");\n\t\tassert_eq!(\n\t\t\t\"ěřůøĉĝĥĵŝŭèùÿėįųāēīūļķņģőűëïąćęłńśźżõșțčďĺľňŕšťýžéíñóúüåäöçîşûğăâđêôơưáàãảạ\"\n\t\t\t\t.as_bytes()\n\t\t\t\t.transliterate(),\n\t\t\t\"eruocghjsueuyeiuaeiulkngoueiacelnszzostcdllnrstyzeinouuaaocisugaadeoouaaaaa\",\n\t\t);\n\t\tassert_eq!(\n\t\t\t\"áạàảãăắặằẳẵâấậầẩẫéẹèẻẽêếệềểễiíịìỉĩoóọòỏõôốộồổỗơớợờởỡúụùủũưứựừửữyýỵỳỷỹđ\"\n\t\t\t\t.as_bytes()\n\t\t\t\t.transliterate(),\n\t\t\t\"aaaaaaaaaaaaaaaaaeeeeeeeeeeeiiiiiioooooooooooooooooouuuuuuuuuuuyyyyyyd\",\n\t\t);\n\t\tassert_ne!(\n\t\t\t\"ěřůøĉĝĥĵŝŭèùÿėįųāēīūļķņģőűëïąćęłńśźżõșțčďĺľňŕšťýžéíñóúüåäöçîşûğăâđêôơưáàãảạﬁﬂ\"\n\t\t\t\t.as_bytes()\n\t\t\t\t.transliterate(),\n\t\t\t\"ěřůøĉĝĥĵŝŭèùÿėįųāēīūļķņģőűëïąćęłńśźżõșțčďĺľňŕšťýžéíñóúüåäöçîşûğăâđêôơưáàãảạfifl\"\n\t\t);\n\t\tassert_eq!(\n\t\t\t\"THEQUICKBROWNFOXJUMPEDOVERTHELAZYDOGthequickbrownfoxjumpedoverthelazydog\"\n\t\t\t\t.as_bytes()\n\t\t\t\t.transliterate(),\n\t\t\t\"THEQUICKBROWNFOXJUMPEDOVERTHELAZYDOGthequickbrownfoxjumpedoverthelazydog\"\n\t\t);\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/url/buf.rs",
    "content": "use std::{borrow::Cow, fmt::{Debug, Formatter}, hash::{Hash, Hasher}, path::{Path, PathBuf}, str::FromStr};\n\nuse anyhow::Result;\nuse serde::{Deserialize, Serialize};\n\nuse crate::{loc::LocBuf, path::{PathBufDyn, PathDynError, SetNameError}, pool::{InternStr, Pool, Symbol}, scheme::Scheme, strand::AsStrand, url::{AsUrl, Url, UrlCow, UrlLike}};\n\n#[derive(Clone, Eq)]\npub enum UrlBuf {\n\tRegular(LocBuf),\n\tSearch { loc: LocBuf, domain: Symbol<str> },\n\tArchive { loc: LocBuf, domain: Symbol<str> },\n\tSftp { loc: LocBuf<typed_path::UnixPathBuf>, domain: Symbol<str> },\n}\n\n// FIXME: remove\nimpl Default for UrlBuf {\n\tfn default() -> Self { Self::Regular(Default::default()) }\n}\n\nimpl From<&Self> for UrlBuf {\n\tfn from(url: &Self) -> Self { url.clone() }\n}\n\nimpl From<Url<'_>> for UrlBuf {\n\tfn from(url: Url<'_>) -> Self {\n\t\tmatch url {\n\t\t\tUrl::Regular(loc) => Self::Regular(loc.into()),\n\t\t\tUrl::Search { loc, domain } => Self::Search { loc: loc.into(), domain: domain.intern() },\n\t\t\tUrl::Archive { loc, domain } => Self::Archive { loc: loc.into(), domain: domain.intern() },\n\t\t\tUrl::Sftp { loc, domain } => Self::Sftp { loc: loc.into(), domain: domain.intern() },\n\t\t}\n\t}\n}\n\nimpl From<&Url<'_>> for UrlBuf {\n\tfn from(url: &Url<'_>) -> Self { (*url).into() }\n}\n\nimpl From<LocBuf> for UrlBuf {\n\tfn from(loc: LocBuf) -> Self { Self::Regular(loc) }\n}\n\nimpl From<PathBuf> for UrlBuf {\n\tfn from(path: PathBuf) -> Self { LocBuf::from(path).into() }\n}\n\nimpl From<&PathBuf> for UrlBuf {\n\tfn from(path: &PathBuf) -> Self { path.to_owned().into() }\n}\n\nimpl From<&Path> for UrlBuf {\n\tfn from(path: &Path) -> Self { path.to_path_buf().into() }\n}\n\nimpl FromStr for UrlBuf {\n\ttype Err = anyhow::Error;\n\n\tfn from_str(s: &str) -> Result<Self, Self::Err> { Ok(UrlCow::try_from(s)?.into_owned()) }\n}\n\nimpl TryFrom<String> for UrlBuf {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(value: String) -> Result<Self, Self::Error> {\n\t\tOk(UrlCow::try_from(value)?.into_owned())\n\t}\n}\n\nimpl TryFrom<(Scheme, PathBufDyn)> for UrlBuf {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(value: (Scheme, PathBufDyn)) -> Result<Self, Self::Error> {\n\t\tOk(UrlCow::try_from(value)?.into_owned())\n\t}\n}\n\nimpl AsRef<Self> for UrlBuf {\n\tfn as_ref(&self) -> &Self { self }\n}\n\nimpl<'a> From<&'a UrlBuf> for Cow<'a, UrlBuf> {\n\tfn from(url: &'a UrlBuf) -> Self { Cow::Borrowed(url) }\n}\n\nimpl From<UrlBuf> for Cow<'_, UrlBuf> {\n\tfn from(url: UrlBuf) -> Self { Cow::Owned(url) }\n}\n\nimpl From<Cow<'_, Self>> for UrlBuf {\n\tfn from(url: Cow<'_, Self>) -> Self { url.into_owned() }\n}\n\n// --- Eq\nimpl PartialEq for UrlBuf {\n\tfn eq(&self, other: &Self) -> bool { self.as_url() == other.as_url() }\n}\n\nimpl PartialEq<UrlBuf> for &UrlBuf {\n\tfn eq(&self, other: &UrlBuf) -> bool { self.as_url() == other.as_url() }\n}\n\nimpl PartialEq<Url<'_>> for UrlBuf {\n\tfn eq(&self, other: &Url) -> bool { self.as_url() == *other }\n}\n\nimpl PartialEq<Url<'_>> for &UrlBuf {\n\tfn eq(&self, other: &Url) -> bool { self.as_url() == *other }\n}\n\nimpl PartialEq<UrlCow<'_>> for UrlBuf {\n\tfn eq(&self, other: &UrlCow) -> bool { self.as_url() == other.as_url() }\n}\n\nimpl PartialEq<UrlCow<'_>> for &UrlBuf {\n\tfn eq(&self, other: &UrlCow) -> bool { self.as_url() == other.as_url() }\n}\n\n// --- Hash\nimpl Hash for UrlBuf {\n\tfn hash<H: Hasher>(&self, state: &mut H) { self.as_url().hash(state) }\n}\n\nimpl UrlBuf {\n\t#[inline]\n\tpub fn new() -> &'static Self {\n\t\tstatic U: UrlBuf = UrlBuf::Regular(LocBuf::empty());\n\t\t&U\n\t}\n\n\t#[inline]\n\tpub fn into_loc(self) -> PathBufDyn {\n\t\tmatch self {\n\t\t\tSelf::Regular(loc) => loc.into_inner().into(),\n\t\t\tSelf::Search { loc, .. } => loc.into_inner().into(),\n\t\t\tSelf::Archive { loc, .. } => loc.into_inner().into(),\n\t\t\tSelf::Sftp { loc, .. } => loc.into_inner().into(),\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn into_local(self) -> Option<PathBuf> {\n\t\tif self.kind().is_local() { self.into_loc().into_os().ok() } else { None }\n\t}\n\n\tpub fn try_set_name(&mut self, name: impl AsStrand) -> Result<(), SetNameError> {\n\t\tlet name = name.as_strand();\n\t\tOk(match self {\n\t\t\tSelf::Regular(loc) => loc.try_set_name(name.as_os()?)?,\n\t\t\tSelf::Search { loc, .. } => loc.try_set_name(name.as_os()?)?,\n\t\t\tSelf::Archive { loc, .. } => loc.try_set_name(name.as_os()?)?,\n\t\t\tSelf::Sftp { loc, .. } => loc.try_set_name(name.encoded_bytes())?,\n\t\t})\n\t}\n\n\tpub fn rebase(&self, base: &Path) -> Self {\n\t\tmatch self {\n\t\t\tSelf::Regular(loc) => Self::Regular(loc.rebase(base)),\n\t\t\tSelf::Search { loc, domain } => {\n\t\t\t\tSelf::Search { loc: loc.rebase(base), domain: domain.clone() }\n\t\t\t}\n\t\t\tSelf::Archive { loc, domain } => {\n\t\t\t\tSelf::Archive { loc: loc.rebase(base), domain: domain.clone() }\n\t\t\t}\n\t\t\tSelf::Sftp { loc, domain } => {\n\t\t\t\ttodo!();\n\t\t\t\t// Self::Sftp { loc: loc.rebase(base), domain: domain.clone() }\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl UrlBuf {\n\t#[inline]\n\tpub fn to_regular(&self) -> Result<Self, PathDynError> { Ok(self.as_url().as_regular()?.into()) }\n\n\t#[inline]\n\tpub fn into_regular(self) -> Result<Self, PathDynError> {\n\t\tOk(Self::Regular(self.into_loc().into_os()?.into()))\n\t}\n\n\t#[inline]\n\tpub fn into_search(self, domain: impl AsRef<str>) -> Result<Self, PathDynError> {\n\t\tOk(Self::Search {\n\t\t\tloc:    LocBuf::<PathBuf>::zeroed(self.into_loc().into_os()?),\n\t\t\tdomain: Pool::<str>::intern(domain),\n\t\t})\n\t}\n}\n\nimpl Debug for UrlBuf {\n\tfn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.as_url().fmt(f) }\n}\n\nimpl Serialize for UrlBuf {\n\tfn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n\t\tself.as_url().serialize(serializer)\n\t}\n}\n\nimpl<'de> Deserialize<'de> for UrlBuf {\n\tfn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n\twhere\n\t\tD: serde::Deserializer<'de>,\n\t{\n\t\tlet s = String::deserialize(deserializer)?;\n\t\tSelf::try_from(s).map_err(serde::de::Error::custom)\n\t}\n}\n\n// --- Tests\n#[cfg(test)]\nmod tests {\n\tuse anyhow::Result;\n\n\tuse super::*;\n\tuse crate::url::UrlLike;\n\n\t#[test]\n\tfn test_join() -> anyhow::Result<()> {\n\t\tcrate::init_tests();\n\t\tlet cases = [\n\t\t\t// Regular\n\t\t\t(\"/a\", \"b/c\", \"/a/b/c\"),\n\t\t\t// Search\n\t\t\t(\"search://kw//a\", \"b/c\", \"search://kw:2:2//a/b/c\"),\n\t\t\t(\"search://kw:2:2//a/b/c\", \"d/e\", \"search://kw:4:4//a/b/c/d/e\"),\n\t\t\t// Archive\n\t\t\t(\"archive:////a/b.zip\", \"c/d\", \"archive://:2:1//a/b.zip/c/d\"),\n\t\t\t(\"archive://:2:1//a/b.zip/c/d\", \"e/f\", \"archive://:4:1//a/b.zip/c/d/e/f\"),\n\t\t\t(\"archive://:2:2//a/b.zip/c/d\", \"e/f\", \"archive://:4:1//a/b.zip/c/d/e/f\"),\n\t\t\t// SFTP\n\t\t\t(\"sftp://remote//a\", \"b/c\", \"sftp://remote//a/b/c\"),\n\t\t\t(\"sftp://remote:1:1//a/b/c\", \"d/e\", \"sftp://remote//a/b/c/d/e\"),\n\t\t\t// Relative\n\t\t\t(\"search://kw\", \"b/c\", \"search://kw:2:2/b/c\"),\n\t\t\t(\"search://kw/\", \"b/c\", \"search://kw:2:2/b/c\"),\n\t\t];\n\n\t\tfor (base, path, expected) in cases {\n\t\t\tlet base: UrlBuf = base.parse()?;\n\t\t\t#[cfg(unix)]\n\t\t\tassert_eq!(format!(\"{:?}\", base.try_join(path)?), expected);\n\t\t\t#[cfg(windows)]\n\t\t\tassert_eq!(\n\t\t\t\tformat!(\"{:?}\", base.try_join(path)?).replace(r\"\\\", \"/\"),\n\t\t\t\texpected.replace(r\"\\\", \"/\")\n\t\t\t);\n\t\t}\n\n\t\tOk(())\n\t}\n\n\t#[test]\n\tfn test_parent() -> anyhow::Result<()> {\n\t\tcrate::init_tests();\n\t\tlet cases = [\n\t\t\t// Regular\n\t\t\t(\"/a\", Some(\"/\")),\n\t\t\t(\"/\", None),\n\t\t\t// Search\n\t\t\t(\"search://kw:2:2//a/b/c\", Some(\"search://kw:1:1//a/b\")),\n\t\t\t(\"search://kw:1:1//a/b\", Some(\"search://kw//a\")),\n\t\t\t(\"search://kw//a\", Some(\"/\")),\n\t\t\t// Archive\n\t\t\t(\"archive://:2:1//a/b.zip/c/d\", Some(\"archive://:1:1//a/b.zip/c\")),\n\t\t\t(\"archive://:1:1//a/b.zip/c\", Some(\"archive:////a/b.zip\")),\n\t\t\t(\"archive:////a/b.zip\", Some(\"/a\")),\n\t\t\t// SFTP\n\t\t\t(\"sftp://remote:3:1//a/b\", Some(\"sftp://remote//a\")),\n\t\t\t(\"sftp://remote:2:1//a\", Some(\"sftp://remote//\")),\n\t\t\t(\"sftp://remote:1:1//a\", Some(\"sftp://remote//\")),\n\t\t\t(\"sftp://remote//a\", Some(\"sftp://remote//\")),\n\t\t\t(\"sftp://remote:1//\", None),\n\t\t\t(\"sftp://remote//\", None),\n\t\t\t// Relative\n\t\t\t(\"search://kw:2:2/a/b\", Some(\"search://kw:1:1/a\")),\n\t\t\t(\"search://kw:1:1/a\", None),\n\t\t\t(\"search://kw/\", None),\n\t\t];\n\n\t\tfor (path, expected) in cases {\n\t\t\tlet path: UrlBuf = path.parse()?;\n\t\t\tassert_eq!(path.parent().map(|u| format!(\"{u:?}\")).as_deref(), expected);\n\t\t}\n\n\t\tOk(())\n\t}\n\n\t#[test]\n\tfn test_into_search() -> Result<()> {\n\t\tcrate::init_tests();\n\t\tconst S: char = std::path::MAIN_SEPARATOR;\n\n\t\tlet u: UrlBuf = \"/root\".parse()?;\n\t\tassert_eq!(format!(\"{u:?}\"), \"/root\");\n\n\t\tlet u = u.into_search(\"kw\")?;\n\t\tassert_eq!(format!(\"{u:?}\"), \"search://kw//root\");\n\t\tassert_eq!(format!(\"{:?}\", u.parent().unwrap()), \"/\");\n\n\t\tlet u = u.try_join(\"examples\")?;\n\t\tassert_eq!(format!(\"{u:?}\"), format!(\"search://kw:1:1//root{S}examples\"));\n\n\t\tlet u = u.try_join(\"README.md\")?;\n\t\tassert_eq!(format!(\"{u:?}\"), format!(\"search://kw:2:2//root{S}examples{S}README.md\"));\n\n\t\tlet u = u.parent().unwrap();\n\t\tassert_eq!(format!(\"{u:?}\"), format!(\"search://kw:1:1//root{S}examples\"));\n\n\t\tlet u = u.parent().unwrap();\n\t\tassert_eq!(format!(\"{u:?}\"), \"search://kw//root\");\n\n\t\tlet u = u.parent().unwrap();\n\t\tassert_eq!(format!(\"{u:?}\"), \"/\");\n\n\t\tOk(())\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/url/component.rs",
    "content": "use std::path::PrefixComponent;\n\nuse anyhow::Result;\n\nuse crate::{path::{self, PathDynError}, scheme::SchemeRef, strand::Strand};\n\n#[derive(Clone, Copy, Debug, PartialEq)]\npub enum Component<'a> {\n\tScheme(SchemeRef<'a>),\n\tPrefix(PrefixComponent<'a>),\n\tRootDir,\n\tCurDir,\n\tParentDir,\n\tNormal(Strand<'a>),\n}\n\nimpl<'a> From<path::Component<'a>> for Component<'a> {\n\tfn from(value: path::Component<'a>) -> Self {\n\t\tmatch value {\n\t\t\tpath::Component::Prefix(p) => Self::Prefix(p),\n\t\t\tpath::Component::RootDir => Self::RootDir,\n\t\t\tpath::Component::CurDir => Self::CurDir,\n\t\t\tpath::Component::ParentDir => Self::ParentDir,\n\t\t\tpath::Component::Normal(s) => Self::Normal(s),\n\t\t}\n\t}\n}\n\nimpl<'a> Component<'a> {\n\tpub fn downgrade(self) -> Option<path::Component<'a>> {\n\t\tSome(match self {\n\t\t\tSelf::Scheme(_) => None?,\n\t\t\tSelf::Prefix(p) => path::Component::Prefix(p),\n\t\t\tSelf::RootDir => path::Component::RootDir,\n\t\t\tSelf::CurDir => path::Component::CurDir,\n\t\t\tSelf::ParentDir => path::Component::ParentDir,\n\t\t\tSelf::Normal(s) => path::Component::Normal(s),\n\t\t})\n\t}\n}\n\n// impl<'a> FromIterator<Component<'a>> for Result<UrlBuf> {\n// \tfn from_iter<I: IntoIterator<Item = Component<'a>>>(iter: I) -> Self {\n// \t\tlet mut buf = PathBuf::new();\n// \t\tlet mut scheme = None;\n// \t\titer.into_iter().for_each(|c| match c {\n// \t\t\tComponent::Scheme(s) => scheme = Some(s),\n// \t\t\tComponent::Prefix(p) => buf.push(path::Component::Prefix(p)),\n// \t\t\tComponent::RootDir => buf.push(path::Component::RootDir),\n// \t\t\tComponent::CurDir => buf.push(path::Component::CurDir),\n// \t\t\tComponent::ParentDir => buf.push(path::Component::ParentDir),\n// \t\t\tComponent::Normal(s) => buf.push(path::Component::Normal(s)),\n// \t\t});\n\n// \t\tOk(if let Some(s) = scheme {\n// \t\t\tUrlCow::try_from((s, PathCow::Owned(PathBufDyn::Os(buf))))?.into_owned()\n// \t\t} else {\n// \t\t\tbuf.into()\n// \t\t})\n// \t}\n// }\n\nimpl<'a> FromIterator<Component<'a>> for Result<std::path::PathBuf, PathDynError> {\n\tfn from_iter<I: IntoIterator<Item = Component<'a>>>(iter: I) -> Self {\n\t\titer.into_iter().filter_map(|c| c.downgrade()).map(std::path::Component::try_from).collect()\n\t}\n}\n\nimpl<'a> FromIterator<Component<'a>> for Result<typed_path::UnixPathBuf, PathDynError> {\n\tfn from_iter<I: IntoIterator<Item = Component<'a>>>(iter: I) -> Self {\n\t\titer\n\t\t\t.into_iter()\n\t\t\t.filter_map(|c| c.downgrade())\n\t\t\t.map(typed_path::UnixComponent::try_from)\n\t\t\t.collect()\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/url/components.rs",
    "content": "use std::{borrow::Cow, ffi::{OsStr, OsString}, iter::FusedIterator, ops::Not};\n\nuse crate::{loc::Loc, path, scheme::{Encode as EncodeScheme, SchemeCow, SchemeRef}, strand::{StrandBuf, StrandCow}, url::{Component, Encode as EncodeUrl, Url}};\n\n#[derive(Clone)]\npub struct Components<'a> {\n\tinner:          path::Components<'a>,\n\turl:            Url<'a>,\n\tback_yields:    usize,\n\tscheme_yielded: bool,\n}\n\nimpl<'a> From<Url<'a>> for Components<'a> {\n\tfn from(value: Url<'a>) -> Self {\n\t\tSelf {\n\t\t\tinner:          value.loc().components(),\n\t\t\turl:            value,\n\t\t\tback_yields:    0,\n\t\t\tscheme_yielded: false,\n\t\t}\n\t}\n}\n\nimpl<'a> Components<'a> {\n\tpub fn covariant(&self, other: &Self) -> bool {\n\t\tmatch (self.scheme_yielded, other.scheme_yielded) {\n\t\t\t(true, true) => {}\n\t\t\t(false, false) if self.scheme().covariant(other.scheme()) => {}\n\t\t\t_ => return false,\n\t\t}\n\t\tself.inner == other.inner\n\t}\n\n\tpub fn os_str(&self) -> Cow<'a, OsStr> {\n\t\tlet Ok(os) = self.inner.strand().as_os() else {\n\t\t\treturn OsString::from(EncodeUrl(self.url()).to_string()).into();\n\t\t};\n\n\t\tif self.url.is_regular() || self.scheme_yielded {\n\t\t\treturn os.into();\n\t\t}\n\n\t\tlet mut s = OsString::from(EncodeScheme(self.url()).to_string());\n\t\ts.reserve_exact(os.len());\n\t\ts.push(os);\n\t\ts.into()\n\t}\n\n\tpub fn scheme(&self) -> SchemeRef<'a> {\n\t\tlet left = self.inner.clone().count();\n\n\t\tlet (uri, urn) = SchemeCow::retrieve_ports(self.url);\n\t\tlet (uri, urn) = (\n\t\t\turi.saturating_sub(self.back_yields).min(left),\n\t\t\turn.saturating_sub(self.back_yields).min(left),\n\t\t);\n\n\t\tmatch self.url {\n\t\t\tUrl::Regular(_) => SchemeRef::Regular { uri, urn },\n\t\t\tUrl::Search { domain, .. } => SchemeRef::Search { domain, uri, urn },\n\t\t\tUrl::Archive { domain, .. } => SchemeRef::Archive { domain, uri, urn },\n\t\t\tUrl::Sftp { domain, .. } => SchemeRef::Sftp { domain, uri, urn },\n\t\t}\n\t}\n\n\tpub fn strand(&self) -> StrandCow<'a> {\n\t\tlet s = self.inner.strand();\n\t\tif self.url.is_regular() || self.scheme_yielded {\n\t\t\treturn s.into();\n\t\t}\n\n\t\tlet mut buf = StrandBuf::with_str(s.kind(), EncodeScheme(self.url()).to_string());\n\t\tbuf.reserve_exact(s.len());\n\t\tbuf.try_push(s).expect(\"strand with same kind\");\n\t\tbuf.into()\n\t}\n\n\tpub fn url(&self) -> Url<'a> {\n\t\tlet path = self.inner.path();\n\t\tlet (uri, urn) = self.scheme().ports();\n\t\tmatch self.url {\n\t\t\tUrl::Regular(_) => Url::Regular(Loc::with(path.as_os().unwrap(), uri, urn).unwrap()),\n\t\t\tUrl::Search { domain, .. } => {\n\t\t\t\tUrl::Search { loc: Loc::with(path.as_os().unwrap(), uri, urn).unwrap(), domain }\n\t\t\t}\n\t\t\tUrl::Archive { domain, .. } => {\n\t\t\t\tUrl::Archive { loc: Loc::with(path.as_os().unwrap(), uri, urn).unwrap(), domain }\n\t\t\t}\n\t\t\tUrl::Sftp { domain, .. } => {\n\t\t\t\tUrl::Sftp { loc: Loc::with(path.as_unix().unwrap(), uri, urn).unwrap(), domain }\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl<'a> Iterator for Components<'a> {\n\ttype Item = Component<'a>;\n\n\tfn next(&mut self) -> Option<Self::Item> {\n\t\tif !self.scheme_yielded {\n\t\t\tself.scheme_yielded = true;\n\t\t\tSome(Component::Scheme(self.scheme()))\n\t\t} else {\n\t\t\tself.inner.next().map(Into::into)\n\t\t}\n\t}\n\n\tfn size_hint(&self) -> (usize, Option<usize>) {\n\t\tlet (min, max) = self.inner.size_hint();\n\t\tlet scheme = self.scheme_yielded.not() as usize;\n\n\t\t(min + scheme, max.map(|n| n + scheme))\n\t}\n}\n\nimpl<'a> DoubleEndedIterator for Components<'a> {\n\tfn next_back(&mut self) -> Option<Self::Item> {\n\t\tif let Some(c) = self.inner.next_back() {\n\t\t\tself.back_yields += 1;\n\t\t\tSome(c.into())\n\t\t} else if !self.scheme_yielded {\n\t\t\tself.scheme_yielded = true;\n\t\t\tSome(Component::Scheme(self.scheme()))\n\t\t} else {\n\t\t\tNone\n\t\t}\n\t}\n}\n\nimpl<'a> FusedIterator for Components<'a> {}\n\nimpl<'a> PartialEq for Components<'a> {\n\tfn eq(&self, other: &Self) -> bool {\n\t\tif self.inner != other.inner {\n\t\t\treturn false;\n\t\t}\n\t\tmatch (self.scheme_yielded, other.scheme_yielded) {\n\t\t\t(true, true) => true,\n\t\t\t(false, false) if self.scheme() == other.scheme() => true,\n\t\t\t_ => false,\n\t\t}\n\t}\n}\n\n// --- Tests\n#[cfg(test)]\nmod tests {\n\tuse anyhow::Result;\n\n\tuse crate::{scheme::SchemeRef, url::{Component, UrlBuf, UrlLike}};\n\n\t#[test]\n\tfn test_url() -> Result<()> {\n\t\tuse Component::*;\n\t\tuse SchemeRef as S;\n\n\t\tcrate::init_tests();\n\n\t\tlet search: UrlBuf = \"search://keyword//root/projects/yazi\".parse()?;\n\t\tassert_eq!(search.uri(), \"\");\n\t\tassert_eq!(search.scheme(), S::Search { domain: \"keyword\", uri: 0, urn: 0 });\n\n\t\tlet src = search.try_join(\"src\")?;\n\t\tassert_eq!(src.uri(), \"src\");\n\t\tassert_eq!(src.scheme(), S::Search { domain: \"keyword\", uri: 1, urn: 1 });\n\n\t\tlet main = src.try_join(\"main.rs\")?;\n\t\tassert_eq!(main.urn(), \"src/main.rs\");\n\t\tassert_eq!(main.scheme(), S::Search { domain: \"keyword\", uri: 2, urn: 2 });\n\n\t\tlet mut it = main.components();\n\t\tassert_eq!(it.url().scheme(), S::Search { domain: \"keyword\", uri: 2, urn: 2 });\n\t\tassert_eq!(it.next_back(), Some(Normal(\"main.rs\".into())));\n\t\tassert_eq!(it.url().scheme(), S::Search { domain: \"keyword\", uri: 1, urn: 1 });\n\t\tassert_eq!(it.next_back(), Some(Normal(\"src\".into())));\n\t\tassert_eq!(it.url().scheme(), S::Search { domain: \"keyword\", uri: 0, urn: 0 });\n\t\tassert_eq!(it.next_back(), Some(Normal(\"yazi\".into())));\n\t\tassert_eq!(it.url().scheme(), S::Search { domain: \"keyword\", uri: 0, urn: 0 });\n\n\t\tlet mut it = main.components();\n\t\tassert_eq!(it.next(), Some(Scheme(S::Search { domain: \"keyword\", uri: 2, urn: 2 })));\n\t\tassert_eq!(it.next(), Some(RootDir));\n\t\tassert_eq!(it.next(), Some(Normal(\"root\".into())));\n\t\tassert_eq!(it.next(), Some(Normal(\"projects\".into())));\n\t\tassert_eq!(it.next(), Some(Normal(\"yazi\".into())));\n\t\tassert_eq!(it.url().scheme(), S::Search { domain: \"keyword\", uri: 2, urn: 2 });\n\t\tassert_eq!(it.next(), Some(Normal(\"src\".into())));\n\t\tassert_eq!(it.url().scheme(), S::Search { domain: \"keyword\", uri: 1, urn: 1 });\n\t\tassert_eq!(it.next_back(), Some(Normal(\"main.rs\".into())));\n\t\tassert_eq!(it.url().scheme(), S::Search { domain: \"keyword\", uri: 0, urn: 0 });\n\t\tassert_eq!(it.next(), None);\n\t\tassert_eq!(it.url().scheme(), S::Search { domain: \"keyword\", uri: 0, urn: 0 });\n\n\t\tOk(())\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/url/cov.rs",
    "content": "use std::{hash::{Hash, Hasher}, ops::Deref, path::PathBuf};\n\nuse hashbrown::Equivalent;\nuse serde::{Deserialize, Serialize};\n\nuse crate::url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike};\n\n#[derive(Clone, Copy)]\npub struct UrlCov<'a>(Url<'a>);\n\nimpl<'a> Deref for UrlCov<'a> {\n\ttype Target = Url<'a>;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl<'a> From<&'a UrlBufCov> for UrlCov<'a> {\n\tfn from(value: &'a UrlBufCov) -> Self { Self(value.0.as_url()) }\n}\n\nimpl PartialEq<UrlBufCov> for UrlCov<'_> {\n\tfn eq(&self, other: &UrlBufCov) -> bool { self.0.covariant(&other.0) }\n}\n\nimpl Hash for UrlCov<'_> {\n\tfn hash<H: Hasher>(&self, state: &mut H) {\n\t\tself.0.loc().hash(state);\n\t\tif self.0.kind().is_virtual() {\n\t\t\tself.0.scheme().hash(state);\n\t\t}\n\t}\n}\n\nimpl Equivalent<UrlBufCov> for UrlCov<'_> {\n\tfn equivalent(&self, key: &UrlBufCov) -> bool { self == key }\n}\n\nimpl<'a> UrlCov<'a> {\n\t#[inline]\n\tpub fn new(url: impl Into<Url<'a>>) -> Self { Self(url.into()) }\n}\n\n// --- Buf\n#[derive(Clone, Debug, Deserialize, Eq, Serialize)]\npub struct UrlBufCov(pub UrlBuf);\n\nimpl Deref for UrlBufCov {\n\ttype Target = UrlBuf;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl From<UrlBufCov> for UrlBuf {\n\tfn from(value: UrlBufCov) -> Self { value.0 }\n}\n\nimpl From<&UrlBufCov> for UrlBuf {\n\tfn from(value: &UrlBufCov) -> Self { value.0.clone() }\n}\n\nimpl From<UrlBuf> for UrlBufCov {\n\tfn from(value: UrlBuf) -> Self { Self(value) }\n}\n\nimpl From<PathBuf> for UrlBufCov {\n\tfn from(value: PathBuf) -> Self { Self(UrlBuf::from(value)) }\n}\n\nimpl From<UrlCow<'_>> for UrlBufCov {\n\tfn from(value: UrlCow<'_>) -> Self { Self(value.into_owned()) }\n}\n\nimpl From<Url<'_>> for UrlBufCov {\n\tfn from(value: Url<'_>) -> Self { Self(value.to_owned()) }\n}\n\nimpl From<&UrlCov<'_>> for UrlBufCov {\n\tfn from(value: &UrlCov<'_>) -> Self { Self(UrlBuf::from(&value.0)) }\n}\n\nimpl<'a> From<&'a UrlBufCov> for Url<'a> {\n\tfn from(value: &'a UrlBufCov) -> Self { value.0.as_url() }\n}\n\nimpl Hash for UrlBufCov {\n\tfn hash<H: Hasher>(&self, state: &mut H) { UrlCov::from(self).hash(state) }\n}\n\nimpl PartialEq for UrlBufCov {\n\tfn eq(&self, other: &Self) -> bool { self.covariant(&other.0) }\n}\n\nimpl PartialEq<UrlBuf> for UrlBufCov {\n\tfn eq(&self, other: &UrlBuf) -> bool { self.covariant(other) }\n}\n"
  },
  {
    "path": "yazi-shared/src/url/cow.rs",
    "content": "use std::{borrow::Cow, hash::{Hash, Hasher}, path::PathBuf};\n\nuse anyhow::{Result, anyhow};\nuse serde::{Deserialize, Deserializer, Serialize};\n\nuse crate::{loc::{Loc, LocBuf}, path::{PathBufDyn, PathCow, PathDyn}, pool::SymbolCow, scheme::{AsScheme, Scheme, SchemeCow, SchemeKind, SchemeRef}, url::{AsUrl, Url, UrlBuf}};\n\n#[derive(Clone, Debug)]\npub enum UrlCow<'a> {\n\tRegular(LocBuf),\n\tSearch { loc: LocBuf, domain: SymbolCow<'a, str> },\n\tArchive { loc: LocBuf, domain: SymbolCow<'a, str> },\n\tSftp { loc: LocBuf<typed_path::UnixPathBuf>, domain: SymbolCow<'a, str> },\n\n\tRegularRef(Loc<'a>),\n\tSearchRef { loc: Loc<'a>, domain: SymbolCow<'a, str> },\n\tArchiveRef { loc: Loc<'a>, domain: SymbolCow<'a, str> },\n\tSftpRef { loc: Loc<'a, &'a typed_path::UnixPath>, domain: SymbolCow<'a, str> },\n}\n\n// FIXME: remove\nimpl Default for UrlCow<'_> {\n\tfn default() -> Self { Self::RegularRef(Default::default()) }\n}\n\nimpl<'a> From<Url<'a>> for UrlCow<'a> {\n\tfn from(value: Url<'a>) -> Self {\n\t\tmatch value {\n\t\t\tUrl::Regular(loc) => Self::RegularRef(loc),\n\t\t\tUrl::Search { loc, domain } => Self::SearchRef { loc, domain: domain.into() },\n\t\t\tUrl::Archive { loc, domain } => Self::ArchiveRef { loc, domain: domain.into() },\n\t\t\tUrl::Sftp { loc, domain } => Self::SftpRef { loc, domain: domain.into() },\n\t\t}\n\t}\n}\n\nimpl<'a, T> From<&'a T> for UrlCow<'a>\nwhere\n\tT: AsUrl + ?Sized,\n{\n\tfn from(value: &'a T) -> Self { value.as_url().into() }\n}\n\nimpl From<UrlBuf> for UrlCow<'_> {\n\tfn from(value: UrlBuf) -> Self {\n\t\tmatch value {\n\t\t\tUrlBuf::Regular(loc) => Self::Regular(loc),\n\t\t\tUrlBuf::Search { loc, domain } => Self::Search { loc, domain: domain.into() },\n\t\t\tUrlBuf::Archive { loc, domain } => Self::Archive { loc, domain: domain.into() },\n\t\t\tUrlBuf::Sftp { loc, domain } => Self::Sftp { loc, domain: domain.into() },\n\t\t}\n\t}\n}\n\nimpl From<PathBuf> for UrlCow<'_> {\n\tfn from(value: PathBuf) -> Self { UrlBuf::from(value).into() }\n}\n\nimpl From<UrlCow<'_>> for UrlBuf {\n\tfn from(value: UrlCow<'_>) -> Self { value.into_owned() }\n}\n\nimpl From<&UrlCow<'_>> for UrlBuf {\n\tfn from(value: &UrlCow<'_>) -> Self { value.as_url().into() }\n}\n\nimpl<'a> TryFrom<&'a [u8]> for UrlCow<'a> {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(value: &'a [u8]) -> Result<Self, Self::Error> { SchemeCow::parse(value)?.try_into() }\n}\n\nimpl TryFrom<Vec<u8>> for UrlCow<'_> {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {\n\t\tOk(UrlCow::try_from(value.as_slice())?.into_owned().into())\n\t}\n}\n\nimpl<'a> TryFrom<&'a str> for UrlCow<'a> {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(value: &'a str) -> Result<Self, Self::Error> { Self::try_from(value.as_bytes()) }\n}\n\nimpl TryFrom<String> for UrlCow<'_> {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(value: String) -> Result<Self, Self::Error> {\n\t\tOk(UrlCow::try_from(value.as_str())?.into_owned().into())\n\t}\n}\n\nimpl<'a> TryFrom<Cow<'a, str>> for UrlCow<'a> {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(value: Cow<'a, str>) -> Result<Self, Self::Error> {\n\t\tmatch value {\n\t\t\tCow::Borrowed(s) => UrlCow::try_from(s),\n\t\t\tCow::Owned(s) => UrlCow::try_from(s),\n\t\t}\n\t}\n}\n\nimpl<'a> TryFrom<(SchemeRef<'a>, PathDyn<'a>)> for UrlCow<'a> {\n\ttype Error = anyhow::Error;\n\n\tfn try_from((scheme, path): (SchemeRef<'a>, PathDyn<'a>)) -> Result<Self, Self::Error> {\n\t\t(SchemeCow::Borrowed(scheme), path).try_into()\n\t}\n}\n\nimpl<'a> TryFrom<(SchemeRef<'a>, PathCow<'a>)> for UrlCow<'a> {\n\ttype Error = anyhow::Error;\n\n\tfn try_from((scheme, path): (SchemeRef<'a>, PathCow<'a>)) -> Result<Self, Self::Error> {\n\t\t(SchemeCow::Borrowed(scheme), path).try_into()\n\t}\n}\n\nimpl TryFrom<(Scheme, PathBufDyn)> for UrlCow<'_> {\n\ttype Error = anyhow::Error;\n\n\tfn try_from((scheme, path): (Scheme, PathBufDyn)) -> Result<Self, Self::Error> {\n\t\t(SchemeCow::Owned(scheme), path).try_into()\n\t}\n}\n\nimpl<'a> TryFrom<(SchemeCow<'a>, PathCow<'a>)> for UrlCow<'a> {\n\ttype Error = anyhow::Error;\n\n\tfn try_from((scheme, path): (SchemeCow<'a>, PathCow<'a>)) -> Result<Self, Self::Error> {\n\t\tmatch path {\n\t\t\tPathCow::Borrowed(path) => (scheme, path).try_into(),\n\t\t\tPathCow::Owned(path) => (scheme, path).try_into(),\n\t\t}\n\t}\n}\n\nimpl<'a> TryFrom<(SchemeCow<'a>, PathDyn<'a>)> for UrlCow<'a> {\n\ttype Error = anyhow::Error;\n\n\tfn try_from((scheme, path): (SchemeCow<'a>, PathDyn<'a>)) -> Result<Self, Self::Error> {\n\t\tlet kind = scheme.as_scheme().kind();\n\t\tlet (uri, urn) = scheme.as_scheme().ports();\n\t\tlet domain = scheme.into_domain();\n\t\tOk(match kind {\n\t\t\tSchemeKind::Regular => Self::RegularRef(Loc::bare(path.as_os()?)),\n\t\t\tSchemeKind::Search => Self::SearchRef {\n\t\t\t\tloc:    Loc::with(path.as_os()?, uri, urn)?,\n\t\t\t\tdomain: domain.ok_or_else(|| anyhow!(\"missing domain for search scheme\"))?,\n\t\t\t},\n\t\t\tSchemeKind::Archive => Self::ArchiveRef {\n\t\t\t\tloc:    Loc::with(path.as_os()?, uri, urn)?,\n\t\t\t\tdomain: domain.ok_or_else(|| anyhow!(\"missing domain for archive scheme\"))?,\n\t\t\t},\n\t\t\tSchemeKind::Sftp => Self::SftpRef {\n\t\t\t\tloc:    Loc::with(path.as_unix()?, uri, urn)?,\n\t\t\t\tdomain: domain.ok_or_else(|| anyhow!(\"missing domain for sftp scheme\"))?,\n\t\t\t},\n\t\t})\n\t}\n}\n\nimpl<'a> TryFrom<(SchemeCow<'a>, PathBufDyn)> for UrlCow<'a> {\n\ttype Error = anyhow::Error;\n\n\tfn try_from((scheme, path): (SchemeCow<'a>, PathBufDyn)) -> Result<Self, Self::Error> {\n\t\tlet kind = scheme.as_scheme().kind();\n\t\tlet (uri, urn) = scheme.as_scheme().ports();\n\t\tlet domain = scheme.into_domain();\n\t\tOk(match kind {\n\t\t\tSchemeKind::Regular => Self::Regular(path.into_os()?.into()),\n\t\t\tSchemeKind::Search => Self::Search {\n\t\t\t\tloc:    LocBuf::<std::path::PathBuf>::with(path.try_into()?, uri, urn)?,\n\t\t\t\tdomain: domain.ok_or_else(|| anyhow!(\"missing domain for search scheme\"))?,\n\t\t\t},\n\t\t\tSchemeKind::Archive => Self::Archive {\n\t\t\t\tloc:    LocBuf::<std::path::PathBuf>::with(path.try_into()?, uri, urn)?,\n\t\t\t\tdomain: domain.ok_or_else(|| anyhow!(\"missing domain for archive scheme\"))?,\n\t\t\t},\n\t\t\tSchemeKind::Sftp => Self::Sftp {\n\t\t\t\tloc:    LocBuf::<typed_path::UnixPathBuf>::with(path.try_into()?, uri, urn)?,\n\t\t\t\tdomain: domain.ok_or_else(|| anyhow!(\"missing domain for sftp scheme\"))?,\n\t\t\t},\n\t\t})\n\t}\n}\n\n// --- Eq\nimpl PartialEq for UrlCow<'_> {\n\tfn eq(&self, other: &Self) -> bool { self.as_url() == other.as_url() }\n}\n\nimpl PartialEq<UrlBuf> for UrlCow<'_> {\n\tfn eq(&self, other: &UrlBuf) -> bool { self.as_url() == other.as_url() }\n}\n\nimpl Eq for UrlCow<'_> {}\n\n// --- Hash\nimpl Hash for UrlCow<'_> {\n\tfn hash<H: Hasher>(&self, state: &mut H) { self.as_url().hash(state); }\n}\n\nimpl<'a> UrlCow<'a> {\n\tpub fn is_owned(&self) -> bool {\n\t\tmatch self {\n\t\t\tSelf::Regular(_) | Self::Search { .. } | Self::Archive { .. } | Self::Sftp { .. } => true,\n\t\t\tSelf::RegularRef(_)\n\t\t\t| Self::SearchRef { .. }\n\t\t\t| Self::ArchiveRef { .. }\n\t\t\t| Self::SftpRef { .. } => false,\n\t\t}\n\t}\n\n\tpub fn into_owned(self) -> UrlBuf {\n\t\tmatch self {\n\t\t\tSelf::Regular(loc) => UrlBuf::Regular(loc),\n\t\t\tSelf::Search { loc, domain } => UrlBuf::Search { loc, domain: domain.into() },\n\t\t\tSelf::Archive { loc, domain } => UrlBuf::Archive { loc, domain: domain.into() },\n\t\t\tSelf::Sftp { loc, domain } => UrlBuf::Sftp { loc, domain: domain.into() },\n\n\t\t\tSelf::RegularRef(loc) => UrlBuf::Regular(loc.into()),\n\t\t\tSelf::SearchRef { loc, domain } => {\n\t\t\t\tUrlBuf::Search { loc: loc.into(), domain: domain.into() }\n\t\t\t}\n\t\t\tSelf::ArchiveRef { loc, domain } => {\n\t\t\t\tUrlBuf::Archive { loc: loc.into(), domain: domain.into() }\n\t\t\t}\n\t\t\tSelf::SftpRef { loc, domain } => UrlBuf::Sftp { loc: loc.into(), domain: domain.into() },\n\t\t}\n\t}\n\n\tpub fn into_scheme(self) -> SchemeCow<'a> {\n\t\tlet (uri, urn) = self.as_url().scheme().ports();\n\t\tmatch self {\n\t\t\tSelf::Regular(_) => Scheme::Regular { uri, urn }.into(),\n\t\t\tSelf::RegularRef(_) => SchemeRef::Regular { uri, urn }.into(),\n\t\t\tSelf::Search { domain, .. } | Self::SearchRef { domain, .. } => match domain {\n\t\t\t\tSymbolCow::Borrowed(domain) => SchemeRef::Search { domain, uri, urn }.into(),\n\t\t\t\tSymbolCow::Owned(domain) => Scheme::Search { domain, uri, urn }.into(),\n\t\t\t},\n\t\t\tSelf::Archive { domain, .. } | Self::ArchiveRef { domain, .. } => match domain {\n\t\t\t\tSymbolCow::Borrowed(domain) => SchemeRef::Archive { domain, uri, urn }.into(),\n\t\t\t\tSymbolCow::Owned(domain) => Scheme::Archive { domain, uri, urn }.into(),\n\t\t\t},\n\t\t\tSelf::Sftp { domain, .. } | Self::SftpRef { domain, .. } => match domain {\n\t\t\t\tSymbolCow::Borrowed(domain) => SchemeRef::Sftp { domain, uri, urn }.into(),\n\t\t\t\tSymbolCow::Owned(domain) => Scheme::Sftp { domain, uri, urn }.into(),\n\t\t\t},\n\t\t}\n\t}\n\n\tpub fn into_static(self) -> UrlCow<'static> {\n\t\tmatch self {\n\t\t\tUrlCow::Regular(loc) => UrlCow::Regular(loc),\n\t\t\tUrlCow::Search { loc, domain } => UrlCow::Search { loc, domain: domain.into_owned().into() },\n\t\t\tUrlCow::Archive { loc, domain } => {\n\t\t\t\tUrlCow::Archive { loc, domain: domain.into_owned().into() }\n\t\t\t}\n\t\t\tUrlCow::Sftp { loc, domain } => UrlCow::Sftp { loc, domain: domain.into_owned().into() },\n\n\t\t\tUrlCow::RegularRef(loc) => UrlCow::Regular(loc.into()),\n\t\t\tUrlCow::SearchRef { loc, domain } => {\n\t\t\t\tUrlCow::Search { loc: loc.into(), domain: domain.into_owned().into() }\n\t\t\t}\n\t\t\tUrlCow::ArchiveRef { loc, domain } => {\n\t\t\t\tUrlCow::Archive { loc: loc.into(), domain: domain.into_owned().into() }\n\t\t\t}\n\t\t\tUrlCow::SftpRef { loc, domain } => {\n\t\t\t\tUrlCow::Sftp { loc: loc.into(), domain: domain.into_owned().into() }\n\t\t\t}\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn to_owned(&self) -> UrlBuf { self.as_url().into() }\n}\n\nimpl Serialize for UrlCow<'_> {\n\tfn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n\twhere\n\t\tS: serde::Serializer,\n\t{\n\t\tself.as_url().serialize(serializer)\n\t}\n}\n\nimpl<'de> Deserialize<'de> for UrlCow<'_> {\n\tfn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n\twhere\n\t\tD: Deserializer<'de>,\n\t{\n\t\tUrlBuf::deserialize(deserializer).map(UrlCow::from)\n\t}\n}\n\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\tuse crate::url::UrlLike;\n\n\t#[test]\n\tfn test_parse() -> Result<()> {\n\t\tstruct Case {\n\t\t\turl:   &'static str,\n\t\t\turn:   &'static str,\n\t\t\turi:   &'static str,\n\t\t\ttrail: &'static str,\n\t\t\tbase:  &'static str,\n\t\t}\n\n\t\tlet cases = [\n\t\t\t// Regular\n\t\t\tCase {\n\t\t\t\turl:   \"/root/music/rock/song.mp3\",\n\t\t\t\turn:   \"song.mp3\",\n\t\t\t\turi:   \"song.mp3\",\n\t\t\t\ttrail: \"/root/music/rock/\",\n\t\t\t\tbase:  \"/root/music/rock/\",\n\t\t\t},\n\t\t\t// Search portal\n\t\t\tCase {\n\t\t\t\turl:   \"search://keyword//root/Documents/reports\",\n\t\t\t\turn:   \"\",\n\t\t\t\turi:   \"\",\n\t\t\t\ttrail: \"search://keyword//root/Documents/reports\",\n\t\t\t\tbase:  \"search://keyword//root/Documents/reports\",\n\t\t\t},\n\t\t\t// Search item\n\t\t\tCase {\n\t\t\t\turl:   \"search://keyword:2:2//root/Documents/reports/2023/summary.docx\",\n\t\t\t\turn:   \"2023/summary.docx\",\n\t\t\t\turi:   \"2023/summary.docx\",\n\t\t\t\ttrail: \"search://keyword//root/Documents/reports/\",\n\t\t\t\tbase:  \"search://keyword//root/Documents/reports/\",\n\t\t\t},\n\t\t\t// Archive portal\n\t\t\tCase {\n\t\t\t\turl:   \"archive://domain//root/Downloads/images.zip\",\n\t\t\t\turn:   \"\",\n\t\t\t\turi:   \"\",\n\t\t\t\ttrail: \"archive://domain//root/Downloads/images.zip\",\n\t\t\t\tbase:  \"archive://domain//root/Downloads/images.zip\",\n\t\t\t},\n\t\t\t// Archive item\n\t\t\tCase {\n\t\t\t\turl:   \"archive://domain:2:1//root/Downloads/images.zip/2025/city.jpg\",\n\t\t\t\turn:   \"city.jpg\",\n\t\t\t\turi:   \"2025/city.jpg\",\n\t\t\t\ttrail: \"archive://domain:1:1//root/Downloads/images.zip/2025/\",\n\t\t\t\tbase:  \"archive://domain//root/Downloads/images.zip/\",\n\t\t\t},\n\t\t\t// SFTP\n\t\t\tCase {\n\t\t\t\turl:   \"sftp://my-server//root/docs/report.pdf\",\n\t\t\t\turn:   \"report.pdf\",\n\t\t\t\turi:   \"report.pdf\",\n\t\t\t\ttrail: \"sftp://my-server//root/docs/\",\n\t\t\t\tbase:  \"sftp://my-server//root/docs/\",\n\t\t\t},\n\t\t];\n\n\t\tfor case in cases {\n\t\t\tlet url = UrlCow::try_from(case.url)?;\n\t\t\tassert_eq!(url.urn().to_str()?, case.urn);\n\t\t\tassert_eq!(url.uri().to_str()?, case.uri);\n\t\t\tassert_eq!(\n\t\t\t\tformat!(\"{:?}\", url.trail()),\n\t\t\t\tformat!(\"{:?}\", UrlCow::try_from(case.trail)?.as_url())\n\t\t\t);\n\t\t\tassert_eq!(\n\t\t\t\tformat!(\"{:?}\", url.base()),\n\t\t\t\tformat!(\"{:?}\", UrlCow::try_from(case.base)?.as_url())\n\t\t\t);\n\t\t}\n\n\t\tOk(())\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/url/display.rs",
    "content": "use crate::{scheme::Encode, url::Url};\n\npub struct Display<'a>(pub Url<'a>);\n\nimpl std::fmt::Display for Display<'_> {\n\tfn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n\t\tlet (kind, loc) = (self.0.kind(), self.0.loc());\n\t\tif kind.is_virtual() {\n\t\t\tEncode(self.0).fmt(f)?;\n\t\t}\n\t\tloc.display().fmt(f)\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/url/encode.rs",
    "content": "use std::fmt::{self, Display};\n\nuse percent_encoding::{CONTROLS, percent_encode};\n\nuse crate::url::Url;\n\n#[derive(Clone, Copy)]\npub struct Encode<'a>(pub Url<'a>);\n\nimpl Display for Encode<'_> {\n\tfn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\t\tuse crate::scheme::Encode as E;\n\n\t\tlet loc = percent_encode(self.0.loc().encoded_bytes(), CONTROLS);\n\t\tmatch self.0 {\n\t\t\tUrl::Regular(_) => write!(f, \"regular~://{loc}\"),\n\t\t\tUrl::Search { domain, .. } => {\n\t\t\t\twrite!(f, \"search~://{}{}/{loc}\", E::domain(domain), E::ports((*self).into()))\n\t\t\t}\n\t\t\tUrl::Archive { domain, .. } => {\n\t\t\t\twrite!(f, \"archive~://{}{}/{loc}\", E::domain(domain), E::ports((*self).into()))\n\t\t\t}\n\t\t\tUrl::Sftp { domain, .. } => {\n\t\t\t\twrite!(f, \"sftp~://{}{}/{loc}\", E::domain(domain), E::ports((*self).into()))\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/url/like.rs",
    "content": "use std::{borrow::Cow, ffi::OsStr, path::Path};\n\nuse anyhow::Result;\n\nuse crate::{path::{AsPathRef, EndsWithError, JoinError, PathDyn, StartsWithError, StripPrefixError}, scheme::{SchemeKind, SchemeRef}, strand::{AsStrand, Strand}, url::{AsUrl, Components, Display, Url, UrlBuf, UrlCow}};\n\npub trait UrlLike\nwhere\n\tSelf: AsUrl,\n{\n\tfn as_local(&self) -> Option<&Path> { self.as_url().as_local() }\n\n\tfn base(&self) -> Url<'_> { self.as_url().base() }\n\n\tfn components(&self) -> Components<'_> { self.as_url().into() }\n\n\tfn covariant(&self, other: impl AsUrl) -> bool { self.as_url().covariant(other) }\n\n\tfn display(&self) -> Display<'_> { Display(self.as_url()) }\n\n\tfn ext(&self) -> Option<Strand<'_>> { self.as_url().ext() }\n\n\tfn has_base(&self) -> bool { self.as_url().has_base() }\n\n\tfn has_root(&self) -> bool { self.as_url().has_root() }\n\n\tfn has_trail(&self) -> bool { self.as_url().has_trail() }\n\n\tfn is_absolute(&self) -> bool { self.as_url().is_absolute() }\n\n\tfn is_archive(&self) -> bool { self.as_url().is_archive() }\n\n\tfn is_internal(&self) -> bool { self.as_url().is_internal() }\n\n\tfn is_regular(&self) -> bool { self.as_url().is_regular() }\n\n\tfn is_search(&self) -> bool { self.as_url().is_search() }\n\n\tfn kind(&self) -> SchemeKind { self.as_url().kind() }\n\n\tfn loc(&self) -> PathDyn<'_> { self.as_url().loc() }\n\n\tfn name(&self) -> Option<Strand<'_>> { self.as_url().name() }\n\n\tfn os_str(&self) -> Cow<'_, OsStr> { self.components().os_str() }\n\n\tfn pair(&self) -> Option<(Url<'_>, PathDyn<'_>)> { self.as_url().pair() }\n\n\tfn parent(&self) -> Option<Url<'_>> { self.as_url().parent() }\n\n\tfn scheme(&self) -> SchemeRef<'_> { self.as_url().scheme() }\n\n\tfn stem(&self) -> Option<Strand<'_>> { self.as_url().stem() }\n\n\tfn trail(&self) -> Url<'_> { self.as_url().trail() }\n\n\tfn triple(&self) -> (PathDyn<'_>, PathDyn<'_>, PathDyn<'_>) { self.as_url().triple() }\n\n\tfn try_ends_with(&self, child: impl AsUrl) -> Result<bool, EndsWithError> {\n\t\tself.as_url().try_ends_with(child)\n\t}\n\n\tfn try_join(&self, path: impl AsStrand) -> Result<UrlBuf, JoinError> {\n\t\tself.as_url().try_join(path)\n\t}\n\n\tfn try_replace<'a>(&self, take: usize, path: impl AsPathRef<'a>) -> Result<UrlCow<'a>> {\n\t\tself.as_url().try_replace(take, path)\n\t}\n\n\tfn try_starts_with(&self, base: impl AsUrl) -> Result<bool, StartsWithError> {\n\t\tself.as_url().try_starts_with(base)\n\t}\n\n\tfn try_strip_prefix(&self, base: impl AsUrl) -> Result<PathDyn<'_>, StripPrefixError> {\n\t\tself.as_url().try_strip_prefix(base)\n\t}\n\n\tfn uri(&self) -> PathDyn<'_> { self.as_url().uri() }\n\n\tfn urn(&self) -> PathDyn<'_> { self.as_url().urn() }\n}\n\nimpl UrlLike for UrlBuf {}\nimpl UrlLike for UrlCow<'_> {}\n"
  },
  {
    "path": "yazi-shared/src/url/mod.rs",
    "content": "yazi_macro::mod_flat!(buf component components cov cow display encode like traits url);\n"
  },
  {
    "path": "yazi-shared/src/url/traits.rs",
    "content": "use std::path::{Path, PathBuf};\n\nuse crate::{loc::Loc, url::{Url, UrlBuf, UrlCow}};\n\n// --- AsUrl\npub trait AsUrl {\n\tfn as_url(&self) -> Url<'_>;\n}\n\nimpl AsUrl for Path {\n\t#[inline]\n\tfn as_url(&self) -> Url<'_> { Url::Regular(Loc::bare(self)) }\n}\n\nimpl AsUrl for &Path {\n\t#[inline]\n\tfn as_url(&self) -> Url<'_> { (*self).as_url() }\n}\n\nimpl AsUrl for PathBuf {\n\t#[inline]\n\tfn as_url(&self) -> Url<'_> { self.as_path().as_url() }\n}\n\nimpl AsUrl for &PathBuf {\n\t#[inline]\n\tfn as_url(&self) -> Url<'_> { (*self).as_path().as_url() }\n}\n\nimpl AsUrl for Url<'_> {\n\t#[inline]\n\tfn as_url(&self) -> Url<'_> { *self }\n}\n\nimpl AsUrl for UrlBuf {\n\t#[inline]\n\tfn as_url(&self) -> Url<'_> {\n\t\tmatch self {\n\t\t\tSelf::Regular(loc) => Url::Regular(loc.as_loc()),\n\t\t\tSelf::Search { loc, domain } => Url::Search { loc: loc.as_loc(), domain },\n\t\t\tSelf::Archive { loc, domain } => Url::Archive { loc: loc.as_loc(), domain },\n\t\t\tSelf::Sftp { loc, domain } => Url::Sftp { loc: loc.as_loc(), domain },\n\t\t}\n\t}\n}\n\nimpl AsUrl for &UrlBuf {\n\t#[inline]\n\tfn as_url(&self) -> Url<'_> { (**self).as_url() }\n}\n\nimpl AsUrl for &mut UrlBuf {\n\t#[inline]\n\tfn as_url(&self) -> Url<'_> { (**self).as_url() }\n}\n\nimpl AsUrl for UrlCow<'_> {\n\tfn as_url(&self) -> Url<'_> {\n\t\tmatch self {\n\t\t\tSelf::Regular(loc) => Url::Regular(loc.as_loc()),\n\t\t\tSelf::Search { loc, domain } => Url::Search { loc: loc.as_loc(), domain },\n\t\t\tSelf::Archive { loc, domain } => Url::Archive { loc: loc.as_loc(), domain },\n\t\t\tSelf::Sftp { loc, domain } => Url::Sftp { loc: loc.as_loc(), domain },\n\n\t\t\tSelf::RegularRef(loc) => Url::Regular(*loc),\n\t\t\tSelf::SearchRef { loc, domain } => Url::Search { loc: *loc, domain },\n\t\t\tSelf::ArchiveRef { loc, domain } => Url::Archive { loc: *loc, domain },\n\t\t\tSelf::SftpRef { loc, domain } => Url::Sftp { loc: *loc, domain },\n\t\t}\n\t}\n}\n\nimpl AsUrl for &UrlCow<'_> {\n\tfn as_url(&self) -> Url<'_> { (**self).as_url() }\n}\n\nimpl AsUrl for super::Components<'_> {\n\tfn as_url(&self) -> Url<'_> { self.url() }\n}\n\nimpl<'a, T> From<&'a T> for Url<'a>\nwhere\n\tT: AsUrl + ?Sized,\n{\n\tfn from(value: &'a T) -> Self { value.as_url() }\n}\n\nimpl<'a, T> From<&'a mut T> for Url<'a>\nwhere\n\tT: AsUrl + ?Sized,\n{\n\tfn from(value: &'a mut T) -> Self { value.as_url() }\n}\n"
  },
  {
    "path": "yazi-shared/src/url/url.rs",
    "content": "use std::{borrow::Cow, ffi::OsStr, fmt::{Debug, Formatter}, path::{Path, PathBuf}};\n\nuse anyhow::Result;\nuse hashbrown::Equivalent;\nuse serde::Serialize;\n\nuse super::Encode as EncodeUrl;\nuse crate::{loc::{Loc, LocBuf}, path::{AsPath, AsPathRef, EndsWithError, JoinError, PathBufDyn, PathDyn, PathDynError, PathLike, StartsWithError, StripPrefixError, StripSuffixError}, pool::{InternStr, Pool}, scheme::{Encode as EncodeScheme, SchemeCow, SchemeKind, SchemeRef}, strand::{AsStrand, Strand}, url::{AsUrl, Components, UrlBuf, UrlCow}};\n\n#[derive(Clone, Copy, Eq, Hash, PartialEq)]\npub enum Url<'a> {\n\tRegular(Loc<'a>),\n\tSearch { loc: Loc<'a>, domain: &'a str },\n\tArchive { loc: Loc<'a>, domain: &'a str },\n\tSftp { loc: Loc<'a, &'a typed_path::UnixPath>, domain: &'a str },\n}\n\n// --- Eq\nimpl PartialEq<UrlBuf> for Url<'_> {\n\tfn eq(&self, other: &UrlBuf) -> bool { *self == other.as_url() }\n}\n\n// --- Hash\nimpl Equivalent<UrlBuf> for Url<'_> {\n\tfn equivalent(&self, key: &UrlBuf) -> bool { self == key }\n}\n\n// --- Debug\nimpl Debug for Url<'_> {\n\tfn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n\t\tif self.is_regular() {\n\t\t\twrite!(f, \"{}\", self.loc().display())\n\t\t} else {\n\t\t\twrite!(f, \"{}{}\", EncodeScheme(*self), self.loc().display())\n\t\t}\n\t}\n}\n\nimpl Serialize for Url<'_> {\n\tfn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n\t\tlet (kind, loc) = (self.kind(), self.loc());\n\t\tmatch (kind == SchemeKind::Regular, loc.to_str()) {\n\t\t\t(true, Ok(s)) => serializer.serialize_str(s),\n\t\t\t(false, Ok(s)) => serializer.serialize_str(&format!(\"{}{s}\", EncodeScheme(*self))),\n\t\t\t(_, Err(_)) => serializer.collect_str(&EncodeUrl(*self)),\n\t\t}\n\t}\n}\n\nimpl<'a> Url<'a> {\n\t#[inline]\n\tpub fn as_local(self) -> Option<&'a Path> {\n\t\tself.loc().as_os().ok().filter(|_| self.kind().is_local())\n\t}\n\n\t#[inline]\n\tpub fn as_regular(self) -> Result<Self, PathDynError> {\n\t\tOk(Self::Regular(Loc::bare(self.loc().as_os()?)))\n\t}\n\n\tpub fn base(self) -> Self {\n\t\tmatch self {\n\t\t\tSelf::Regular(loc) => Self::Regular(Loc::bare(loc.base())),\n\t\t\tSelf::Search { loc, domain } => Self::Search { loc: Loc::zeroed(loc.base()), domain },\n\t\t\tSelf::Archive { loc, domain } => Self::Archive { loc: Loc::zeroed(loc.base()), domain },\n\t\t\tSelf::Sftp { loc, domain } => Self::Sftp { loc: Loc::bare(loc.base()), domain },\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn components(self) -> Components<'a> { Components::from(self) }\n\n\t#[inline]\n\tpub fn covariant(self, other: impl AsUrl) -> bool {\n\t\tlet other = other.as_url();\n\t\tself.loc() == other.loc() && self.scheme().covariant(other.scheme())\n\t}\n\n\t#[inline]\n\tpub fn ext(self) -> Option<Strand<'a>> {\n\t\tSome(match self {\n\t\t\tSelf::Regular(loc) => loc.extension()?.as_strand(),\n\t\t\tSelf::Search { loc, .. } => loc.extension()?.as_strand(),\n\t\t\tSelf::Archive { loc, .. } => loc.extension()?.as_strand(),\n\t\t\tSelf::Sftp { loc, .. } => loc.extension()?.as_strand(),\n\t\t})\n\t}\n\n\t#[inline]\n\tpub fn has_base(self) -> bool {\n\t\tmatch self {\n\t\t\tSelf::Regular(loc) => loc.has_base(),\n\t\t\tSelf::Search { loc, .. } => loc.has_base(),\n\t\t\tSelf::Archive { loc, .. } => loc.has_base(),\n\t\t\tSelf::Sftp { loc, .. } => loc.has_base(),\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn has_root(self) -> bool { self.loc().has_root() }\n\n\t#[inline]\n\tpub fn has_trail(self) -> bool {\n\t\tmatch self {\n\t\t\tSelf::Regular(loc) => loc.has_trail(),\n\t\t\tSelf::Search { loc, .. } => loc.has_trail(),\n\t\t\tSelf::Archive { loc, .. } => loc.has_trail(),\n\t\t\tSelf::Sftp { loc, .. } => loc.has_trail(),\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn is_absolute(self) -> bool { self.loc().is_absolute() }\n\n\t#[inline]\n\tpub fn is_archive(self) -> bool { matches!(self, Self::Archive { .. }) }\n\n\t#[inline]\n\tpub fn is_internal(self) -> bool {\n\t\tmatch self {\n\t\t\tSelf::Regular(_) | Self::Sftp { .. } => true,\n\t\t\tSelf::Search { .. } => !self.uri().is_empty(),\n\t\t\tSelf::Archive { .. } => false,\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn is_regular(self) -> bool { matches!(self, Self::Regular(_)) }\n\n\t#[inline]\n\tpub fn is_search(self) -> bool { matches!(self, Self::Search { .. }) }\n\n\t#[inline]\n\tpub fn kind(self) -> SchemeKind {\n\t\tmatch self {\n\t\t\tSelf::Regular(_) => SchemeKind::Regular,\n\t\t\tSelf::Search { .. } => SchemeKind::Search,\n\t\t\tSelf::Archive { .. } => SchemeKind::Archive,\n\t\t\tSelf::Sftp { .. } => SchemeKind::Sftp,\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn loc(self) -> PathDyn<'a> {\n\t\tmatch self {\n\t\t\tSelf::Regular(loc) => loc.as_path(),\n\t\t\tSelf::Search { loc, .. } => loc.as_path(),\n\t\t\tSelf::Archive { loc, .. } => loc.as_path(),\n\t\t\tSelf::Sftp { loc, .. } => loc.as_path(),\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn name(self) -> Option<Strand<'a>> {\n\t\tSome(match self {\n\t\t\tSelf::Regular(loc) => loc.file_name()?.as_strand(),\n\t\t\tSelf::Search { loc, .. } => loc.file_name()?.as_strand(),\n\t\t\tSelf::Archive { loc, .. } => loc.file_name()?.as_strand(),\n\t\t\tSelf::Sftp { loc, .. } => loc.file_name()?.as_strand(),\n\t\t})\n\t}\n\n\t#[inline]\n\tpub fn os_str(self) -> Cow<'a, OsStr> { self.components().os_str() }\n\n\t#[inline]\n\tpub fn pair(self) -> Option<(Self, PathDyn<'a>)> { Some((self.parent()?, self.urn())) }\n\n\tpub fn parent(self) -> Option<Self> {\n\t\tlet uri = self.uri();\n\n\t\tSome(match self {\n\t\t\t// Regular\n\t\t\tSelf::Regular(loc) => Self::regular(loc.parent()?),\n\n\t\t\t// Search\n\t\t\tSelf::Search { loc, .. } if uri.is_empty() => Self::regular(loc.parent()?),\n\t\t\tSelf::Search { loc, domain } => {\n\t\t\t\tSelf::Search { loc: Loc::new(loc.parent()?, loc.base(), loc.base()), domain }\n\t\t\t}\n\n\t\t\t// Archive\n\t\t\tSelf::Archive { loc, .. } if uri.is_empty() => Self::regular(loc.parent()?),\n\t\t\tSelf::Archive { loc, domain } if uri.components().nth(1).is_none() => {\n\t\t\t\tSelf::Archive { loc: Loc::zeroed(loc.parent()?), domain }\n\t\t\t}\n\t\t\tSelf::Archive { loc, domain } => {\n\t\t\t\tSelf::Archive { loc: Loc::floated(loc.parent()?, loc.base()), domain }\n\t\t\t}\n\n\t\t\t// SFTP\n\t\t\tSelf::Sftp { loc, domain } => Self::Sftp { loc: Loc::bare(loc.parent()?), domain },\n\t\t})\n\t}\n\n\t#[inline]\n\tpub fn regular<T: AsRef<Path> + ?Sized>(path: &'a T) -> Self {\n\t\tSelf::Regular(Loc::bare(path.as_ref()))\n\t}\n\n\t#[inline]\n\tpub fn scheme(self) -> SchemeRef<'a> {\n\t\tlet (uri, urn) = SchemeCow::retrieve_ports(self);\n\t\tmatch self {\n\t\t\tSelf::Regular(_) => SchemeRef::Regular { uri, urn },\n\t\t\tSelf::Search { domain, .. } => SchemeRef::Search { domain, uri, urn },\n\t\t\tSelf::Archive { domain, .. } => SchemeRef::Archive { domain, uri, urn },\n\t\t\tSelf::Sftp { domain, .. } => SchemeRef::Sftp { domain, uri, urn },\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn stem(self) -> Option<Strand<'a>> {\n\t\tSome(match self {\n\t\t\tSelf::Regular(loc) => loc.file_stem()?.as_strand(),\n\t\t\tSelf::Search { loc, .. } => loc.file_stem()?.as_strand(),\n\t\t\tSelf::Archive { loc, .. } => loc.file_stem()?.as_strand(),\n\t\t\tSelf::Sftp { loc, .. } => loc.file_stem()?.as_strand(),\n\t\t})\n\t}\n\n\t#[inline]\n\tpub fn to_owned(self) -> UrlBuf { self.into() }\n\n\tpub fn to_search(self, domain: impl AsRef<str>) -> Result<UrlBuf, PathDynError> {\n\t\tOk(UrlBuf::Search {\n\t\t\tloc:    LocBuf::<PathBuf>::zeroed(self.loc().to_os_owned()?),\n\t\t\tdomain: Pool::<str>::intern(domain),\n\t\t})\n\t}\n\n\tpub fn trail(self) -> Self {\n\t\tlet uri = self.uri();\n\t\tmatch self {\n\t\t\tSelf::Regular(loc) => Self::Regular(Loc::bare(loc.trail())),\n\n\t\t\tSelf::Search { loc, domain } if uri.is_empty() => {\n\t\t\t\tSelf::Search { loc: Loc::zeroed(loc.trail()), domain }\n\t\t\t}\n\t\t\tSelf::Search { loc, domain } => {\n\t\t\t\tSelf::Search { loc: Loc::new(loc.trail(), loc.base(), loc.base()), domain }\n\t\t\t}\n\n\t\t\tSelf::Archive { loc, domain } if uri.is_empty() => {\n\t\t\t\tSelf::Archive { loc: Loc::zeroed(loc.trail()), domain }\n\t\t\t}\n\t\t\tSelf::Archive { loc, domain } => {\n\t\t\t\tSelf::Archive { loc: Loc::new(loc.trail(), loc.base(), loc.base()), domain }\n\t\t\t}\n\n\t\t\tSelf::Sftp { loc, domain } => Self::Sftp { loc: Loc::bare(loc.trail()), domain },\n\t\t}\n\t}\n\n\tpub fn triple(self) -> (PathDyn<'a>, PathDyn<'a>, PathDyn<'a>) {\n\t\tmatch self {\n\t\t\tSelf::Regular(loc) | Self::Search { loc, .. } | Self::Archive { loc, .. } => {\n\t\t\t\tlet (base, rest, urn) = loc.triple();\n\t\t\t\t(base.as_path(), rest.as_path(), urn.as_path())\n\t\t\t}\n\t\t\tSelf::Sftp { loc, .. } => {\n\t\t\t\tlet (base, rest, urn) = loc.triple();\n\t\t\t\t(base.as_path(), rest.as_path(), urn.as_path())\n\t\t\t}\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn try_ends_with(self, child: impl AsUrl) -> Result<bool, EndsWithError> {\n\t\tlet child = child.as_url();\n\t\tOk(self.loc().try_ends_with(child.loc())? && self.scheme().covariant(child.scheme()))\n\t}\n\n\tpub fn try_join(self, path: impl AsStrand) -> Result<UrlBuf, JoinError> {\n\t\tlet joined = self.loc().try_join(path)?;\n\n\t\tOk(match self {\n\t\t\tSelf::Regular(_) => UrlBuf::Regular(joined.into_os()?.into()),\n\n\t\t\tSelf::Search { loc, domain } if joined.try_starts_with(loc.base())? => UrlBuf::Search {\n\t\t\t\tloc:    LocBuf::<PathBuf>::new(joined.try_into()?, loc.base(), loc.base()),\n\t\t\t\tdomain: domain.intern(),\n\t\t\t},\n\t\t\tSelf::Search { domain, .. } => UrlBuf::Search {\n\t\t\t\tloc:    LocBuf::<PathBuf>::zeroed(joined.into_os()?),\n\t\t\t\tdomain: domain.intern(),\n\t\t\t},\n\n\t\t\tSelf::Archive { loc, domain } if joined.try_starts_with(loc.base())? => UrlBuf::Archive {\n\t\t\t\tloc:    LocBuf::<PathBuf>::floated(joined.try_into()?, loc.base()),\n\t\t\t\tdomain: domain.intern(),\n\t\t\t},\n\t\t\tSelf::Archive { domain, .. } => UrlBuf::Archive {\n\t\t\t\tloc:    LocBuf::<PathBuf>::zeroed(joined.into_os()?),\n\t\t\t\tdomain: domain.intern(),\n\t\t\t},\n\n\t\t\tSelf::Sftp { domain, .. } => {\n\t\t\t\tUrlBuf::Sftp { loc: joined.into_unix()?.into(), domain: domain.intern() }\n\t\t\t}\n\t\t})\n\t}\n\n\tpub fn try_replace<'b>(self, take: usize, to: impl AsPathRef<'b>) -> Result<UrlCow<'b>> {\n\t\tself.try_replace_impl(take, to.as_path_ref())\n\t}\n\n\tfn try_replace_impl<'b>(self, take: usize, rep: PathDyn<'b>) -> Result<UrlCow<'b>> {\n\t\tlet b = rep.encoded_bytes();\n\t\tif take == 0 {\n\t\t\treturn UrlCow::try_from(b);\n\t\t} else if SchemeKind::parse(b)?.is_some() {\n\t\t\treturn UrlCow::try_from(b);\n\t\t}\n\n\t\tlet loc = self.loc();\n\t\tlet mut path = PathBufDyn::from_components(loc.kind(), loc.components().take(take - 1))?;\n\t\tpath.try_push(rep)?;\n\n\t\tlet url = match self {\n\t\t\tSelf::Regular(_) => UrlBuf::from(path.into_os()?),\n\n\t\t\tSelf::Search { loc, domain } if path.try_starts_with(loc.trail())? => UrlBuf::Search {\n\t\t\t\tloc:    LocBuf::<PathBuf>::new(path.into_os()?, loc.base(), loc.trail()),\n\t\t\t\tdomain: domain.intern(),\n\t\t\t},\n\t\t\tSelf::Archive { loc, domain } if path.try_starts_with(loc.trail())? => UrlBuf::Archive {\n\t\t\t\tloc:    LocBuf::<std::path::PathBuf>::new(path.into_os()?, loc.base(), loc.trail()),\n\t\t\t\tdomain: domain.intern(),\n\t\t\t},\n\t\t\tSelf::Sftp { loc, domain } if path.try_starts_with(loc.trail())? => UrlBuf::Sftp {\n\t\t\t\tloc:    LocBuf::<typed_path::UnixPathBuf>::new(path.into_unix()?, loc.base(), loc.trail()),\n\t\t\t\tdomain: domain.intern(),\n\t\t\t},\n\n\t\t\tSelf::Search { domain, .. } => UrlBuf::Search {\n\t\t\t\tloc:    LocBuf::<std::path::PathBuf>::saturated(path.into_os()?, self.kind()),\n\t\t\t\tdomain: domain.intern(),\n\t\t\t},\n\t\t\tSelf::Archive { domain, .. } => UrlBuf::Archive {\n\t\t\t\tloc:    LocBuf::<std::path::PathBuf>::saturated(path.into_os()?, self.kind()),\n\t\t\t\tdomain: domain.intern(),\n\t\t\t},\n\t\t\tSelf::Sftp { domain, .. } => UrlBuf::Sftp {\n\t\t\t\tloc:    LocBuf::<typed_path::UnixPathBuf>::saturated(path.into_unix()?, self.kind()),\n\t\t\t\tdomain: domain.intern(),\n\t\t\t},\n\t\t};\n\n\t\tOk(url.into())\n\t}\n\n\t#[inline]\n\tpub fn try_starts_with(self, base: impl AsUrl) -> Result<bool, StartsWithError> {\n\t\tlet base = base.as_url();\n\t\tOk(self.loc().try_starts_with(base.loc())? && self.scheme().covariant(base.scheme()))\n\t}\n\n\tpub fn try_strip_prefix(self, base: impl AsUrl) -> Result<PathDyn<'a>, StripPrefixError> {\n\t\tuse StripPrefixError::{Exotic, NotPrefix};\n\t\tuse Url as U;\n\n\t\tlet base = base.as_url();\n\t\tlet prefix = self.loc().try_strip_prefix(base.loc())?;\n\n\t\tmatch (self, base) {\n\t\t\t// Same scheme\n\t\t\t(U::Regular(_), U::Regular(_)) => Ok(prefix),\n\t\t\t(U::Search { .. }, U::Search { .. }) => Ok(prefix),\n\t\t\t(U::Archive { domain: a, .. }, U::Archive { domain: b, .. }) => {\n\t\t\t\tSome(prefix).filter(|_| a == b).ok_or(Exotic)\n\t\t\t}\n\t\t\t(U::Sftp { domain: a, .. }, U::Sftp { domain: b, .. }) => {\n\t\t\t\tSome(prefix).filter(|_| a == b).ok_or(Exotic)\n\t\t\t}\n\n\t\t\t// Both are local files\n\t\t\t(U::Regular(_), U::Search { .. }) => Ok(prefix),\n\t\t\t(U::Search { .. }, U::Regular(_)) => Ok(prefix),\n\n\t\t\t// Only the entry of archives is a local file\n\t\t\t(U::Regular(_), U::Archive { .. }) => {\n\t\t\t\tSome(prefix).filter(|_| base.uri().is_empty()).ok_or(NotPrefix)\n\t\t\t}\n\t\t\t(U::Search { .. }, U::Archive { .. }) => {\n\t\t\t\tSome(prefix).filter(|_| base.uri().is_empty()).ok_or(NotPrefix)\n\t\t\t}\n\t\t\t(U::Archive { .. }, U::Regular(_)) => {\n\t\t\t\tSome(prefix).filter(|_| self.uri().is_empty()).ok_or(NotPrefix)\n\t\t\t}\n\t\t\t(U::Archive { .. }, U::Search { .. }) => {\n\t\t\t\tSome(prefix).filter(|_| self.uri().is_empty()).ok_or(NotPrefix)\n\t\t\t}\n\n\t\t\t// Independent virtual file space\n\t\t\t(U::Regular(_), U::Sftp { .. }) => Err(Exotic),\n\t\t\t(U::Search { .. }, U::Sftp { .. }) => Err(Exotic),\n\t\t\t(U::Archive { .. }, U::Sftp { .. }) => Err(Exotic),\n\t\t\t(U::Sftp { .. }, U::Regular(_)) => Err(Exotic),\n\t\t\t(U::Sftp { .. }, U::Search { .. }) => Err(Exotic),\n\t\t\t(U::Sftp { .. }, U::Archive { .. }) => Err(Exotic),\n\t\t}\n\t}\n\n\tpub fn try_strip_suffix(self, other: impl AsUrl) -> Result<PathDyn<'a>, StripSuffixError> {\n\t\tuse StripSuffixError::{Exotic, NotSuffix};\n\t\tuse Url as U;\n\n\t\tlet other = other.as_url();\n\t\tlet suffix = self.loc().try_strip_suffix(other.loc())?;\n\n\t\tmatch (self, other) {\n\t\t\t// Same scheme\n\t\t\t(U::Regular(_), U::Regular(_)) => Ok(suffix),\n\t\t\t(U::Search { .. }, U::Search { .. }) => Ok(suffix),\n\t\t\t(U::Archive { domain: a, .. }, U::Archive { domain: b, .. }) => {\n\t\t\t\tSome(suffix).filter(|_| a == b).ok_or(Exotic)\n\t\t\t}\n\t\t\t(U::Sftp { domain: a, .. }, U::Sftp { domain: b, .. }) => {\n\t\t\t\tSome(suffix).filter(|_| a == b).ok_or(Exotic)\n\t\t\t}\n\n\t\t\t// Both are local files\n\t\t\t(U::Regular(_), U::Search { .. }) => Ok(suffix),\n\t\t\t(U::Search { .. }, U::Regular(_)) => Ok(suffix),\n\n\t\t\t// Only the entry of archives is a local file\n\t\t\t(U::Regular(_), U::Archive { .. }) => {\n\t\t\t\tSome(suffix).filter(|_| other.uri().is_empty()).ok_or(NotSuffix)\n\t\t\t}\n\t\t\t(U::Search { .. }, U::Archive { .. }) => {\n\t\t\t\tSome(suffix).filter(|_| other.uri().is_empty()).ok_or(NotSuffix)\n\t\t\t}\n\t\t\t(U::Archive { .. }, U::Regular(_)) => {\n\t\t\t\tSome(suffix).filter(|_| self.uri().is_empty()).ok_or(NotSuffix)\n\t\t\t}\n\t\t\t(U::Archive { .. }, U::Search { .. }) => {\n\t\t\t\tSome(suffix).filter(|_| self.uri().is_empty()).ok_or(NotSuffix)\n\t\t\t}\n\n\t\t\t// Independent virtual file space\n\t\t\t(U::Regular(_), U::Sftp { .. }) => Err(Exotic),\n\t\t\t(U::Search { .. }, U::Sftp { .. }) => Err(Exotic),\n\t\t\t(U::Archive { .. }, U::Sftp { .. }) => Err(Exotic),\n\t\t\t(U::Sftp { .. }, U::Regular(_)) => Err(Exotic),\n\t\t\t(U::Sftp { .. }, U::Search { .. }) => Err(Exotic),\n\t\t\t(U::Sftp { .. }, U::Archive { .. }) => Err(Exotic),\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn uri(self) -> PathDyn<'a> {\n\t\tmatch self {\n\t\t\tSelf::Regular(loc) => loc.uri().as_path(),\n\t\t\tSelf::Search { loc, .. } => loc.uri().as_path(),\n\t\t\tSelf::Archive { loc, .. } => loc.uri().as_path(),\n\t\t\tSelf::Sftp { loc, .. } => loc.uri().as_path(),\n\t\t}\n\t}\n\n\t#[inline]\n\tpub fn urn(self) -> PathDyn<'a> {\n\t\tmatch self {\n\t\t\tSelf::Regular(loc) => loc.urn().as_path(),\n\t\t\tSelf::Search { loc, .. } => loc.urn().as_path(),\n\t\t\tSelf::Archive { loc, .. } => loc.urn().as_path(),\n\t\t\tSelf::Sftp { loc, .. } => loc.urn().as_path(),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/utf8.rs",
    "content": "// https://tools.ietf.org/html/rfc3629\nconst UTF8_CHAR_WIDTH: &[u8; 256] = &[\n\t// 1  2  3  4  5  6  7  8  9  A  B  C  D  E  F\n\t1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0\n\t1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1\n\t1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2\n\t1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3\n\t1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4\n\t1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5\n\t1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6\n\t1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B\n\t0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C\n\t2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // D\n\t3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // E\n\t4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F\n];\n\n/// Given a first byte, determines how many bytes are in this UTF-8 character.\n#[must_use]\n#[inline]\npub const fn utf8_char_width(b: u8) -> usize { UTF8_CHAR_WIDTH[b as usize] as usize }\n\n/// Finds the closest `x` not exceeding `index` where [`is_char_boundary(x)`] is\n/// `true`.\n///\n/// This method can help you truncate a string so that it's still valid UTF-8,\n/// but doesn't exceed a given number of bytes. Note that this is done purely at\n/// the character level and can still visually split graphemes, even though the\n/// underlying characters aren't split. For example, the emoji 🧑‍🔬 (scientist)\n/// could be split so that the string only includes 🧑 (person) instead.\n#[inline]\npub fn floor_char_boundary(s: &str, index: usize) -> usize {\n\tif index >= s.len() {\n\t\ts.len()\n\t} else {\n\t\tlet lower_bound = index.saturating_sub(3);\n\t\tlet new_index =\n\t\t\ts.as_bytes()[lower_bound..=index].iter().rposition(|&b| is_utf8_char_boundary(b));\n\n\t\t// SAFETY: we know that the character boundary will be within four bytes\n\t\tunsafe { lower_bound + new_index.unwrap_unchecked() }\n\t}\n}\n\n#[inline]\nconst fn is_utf8_char_boundary(b: u8) -> bool {\n\t// This is bit magic equivalent to: b < 128 || b >= 192\n\t(b as i8) >= -0x40\n}\n"
  },
  {
    "path": "yazi-shared/src/wtf8/mod.rs",
    "content": "yazi_macro::mod_flat!(validator wtf8);\n"
  },
  {
    "path": "yazi-shared/src/wtf8/validator.rs",
    "content": "#[cfg(windows)]\npub(super) fn valid_wtf8(bytes: &[u8]) -> bool {\n\tlet mut i = 0;\n\twhile i < bytes.len() {\n\t\tlet b = bytes[i];\n\t\tif b < 0b1000_0000 {\n\t\t\t// ASCII\n\t\t\ti += 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// 2-byte: 110x_xxxx 10xx_xxxx\n\t\t// first byte must be >= 0b1100_0010 to forbid overlongs\n\t\tif (b & 0b1110_0000) == 0b1100_0000 {\n\t\t\tif b < 0b1100_0010 {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif i + 1 >= bytes.len() {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (bytes[i + 1] & 0b1100_0000) != 0b1000_0000 {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\ti += 2;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// 3-byte: 1110_xxxx 10xx_xxxx 10xx_xxxx\n\t\tif (b & 0b1111_0000) == 0b1110_0000 {\n\t\t\tif i + 2 >= bytes.len() {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tlet (b1, b2) = (bytes[i + 1], bytes[i + 2]);\n\t\t\tif (b1 & 0b1100_0000) != 0b1000_0000 || (b2 & 0b1100_0000) != 0b1000_0000 {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif b == 0b1110_0000 && b1 < 0b1010_0000 {\n\t\t\t\t// to forbid overlongs, second byte must be >= 0xA0\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\ti += 3;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// 4-byte: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx\n\t\tif (b & 0b1111_1000) == 0b1111_0000 {\n\t\t\tif b > 0b1111_0100 {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif i + 3 >= bytes.len() {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tlet (b1, b2, b3) = (bytes[i + 1], bytes[i + 2], bytes[i + 3]);\n\t\t\tif (b1 & 0b1100_0000) != 0b1000_0000\n\t\t\t\t|| (b2 & 0b1100_0000) != 0b1000_0000\n\t\t\t\t|| (b3 & 0b1100_0000) != 0b1000_0000\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif b == 0b1111_0000 && b1 < 0b1001_0000 {\n\t\t\t\t// to forbid overlongs for > U+FFFF, second byte must be >= 0x90\n\t\t\t\treturn false;\n\t\t\t} else if b == 0b1111_0100 && b1 > 0b1000_1111 {\n\t\t\t\t// to stay <= U+10FFFF, second byte must be <= 0x8F\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\ti += 4;\n\t\t\tcontinue;\n\t\t}\n\n\t\treturn false;\n\t}\n\ttrue\n}\n\n#[cfg(windows)]\n#[cfg(test)]\nmod tests {\n\tuse super::*;\n\n\t#[test]\n\tfn test_valid_wtf8() {\n\t\tlet cases: &[(&[u8], bool)] = &[\n\t\t\t// Valid ASCII\n\t\t\t(b\"hello\", true),\n\t\t\t// Valid 2-byte UTF-8\n\t\t\t(&[0xc2, 0xa0], true), // U+00A0\n\t\t\t// Invalid 2-byte: overlong encoding\n\t\t\t(&[0xc0, 0x80], false), // overlong for U+0000\n\t\t\t(&[0xc1, 0xbf], false), // overlong for U+007F\n\t\t\t// Valid 3-byte UTF-8\n\t\t\t(&[0xe0, 0xa0, 0x80], true), // U+0800\n\t\t\t// Invalid 3-byte: overlong encoding\n\t\t\t(&[0xe0, 0x9f, 0xbf], false), // overlong for U+07FF\n\t\t\t// WTF-8 specific: unpaired surrogates (should be valid in WTF-8)\n\t\t\t((&[0xed, 0xa0, 0x80]), true), // U+D800 = ED A0 80 (high surrogate)\n\t\t\t((&[0xed, 0xbf, 0xbf]), true), // U+DFFF = ED BF BF (low surrogate)\n\t\t\t// Valid 4-byte UTF-8\n\t\t\t((&[0xf0, 0x90, 0x80, 0x80]), true), // U+10000\n\t\t\t// Invalid 4-byte: overlong\n\t\t\t((&[0xf0, 0x8f, 0xbf, 0xbf]), false), // overlong for U+FFFF\n\t\t\t// Invalid 4-byte: beyond U+10FFFF\n\t\t\t((&[0xf4, 0x90, 0x80, 0x80]), false), // U+110000\n\t\t\t// Invalid continuation byte\n\t\t\t((&[0xc2, 0x00]), false),\n\t\t\t// Incomplete sequence\n\t\t\t((&[0xc2]), false),\n\t\t\t((&[0xe0, 0xa0]), false),\n\t\t\t((&[0xf0, 0x90, 0x80]), false),\n\t\t];\n\n\t\tfor &(input, expected) in cases {\n\t\t\tassert_eq!(valid_wtf8(input), expected, \"input: {:?}\", input);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-shared/src/wtf8/wtf8.rs",
    "content": "use std::ffi::{OsStr, OsString};\n\nuse anyhow::Result;\n\n// --- AsWtf8\npub trait AsWtf8 {\n\tfn as_wtf8(&self) -> &[u8];\n}\n\nimpl AsWtf8 for OsStr {\n\tfn as_wtf8(&self) -> &[u8] {\n\t\t#[cfg(unix)]\n\t\t{\n\t\t\tuse std::os::unix::ffi::OsStrExt;\n\t\t\tself.as_bytes()\n\t\t}\n\t\t#[cfg(windows)]\n\t\t{\n\t\t\tself.as_encoded_bytes()\n\t\t}\n\t}\n}\n\n// --- FromWtf8\npub trait FromWtf8 {\n\tfn from_wtf8(wtf8: &[u8]) -> Result<&Self>;\n}\n\nimpl FromWtf8 for OsStr {\n\tfn from_wtf8(wtf8: &[u8]) -> Result<&Self> {\n\t\t#[cfg(unix)]\n\t\t{\n\t\t\tuse std::os::unix::ffi::OsStrExt;\n\t\t\tOk(Self::from_bytes(wtf8))\n\t\t}\n\t\t#[cfg(windows)]\n\t\t{\n\t\t\tif super::valid_wtf8(wtf8) {\n\t\t\t\tOk(unsafe { Self::from_encoded_bytes_unchecked(wtf8) })\n\t\t\t} else {\n\t\t\t\tErr(anyhow::anyhow!(\"Invalid WTF-8 sequence\"))\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl FromWtf8 for std::path::Path {\n\tfn from_wtf8(wtf8: &[u8]) -> Result<&Self> { Ok(OsStr::from_wtf8(wtf8)?.as_ref()) }\n}\n\n// --- FromWtf8Vec\npub trait FromWtf8Vec {\n\tfn from_wtf8_vec(wtf8: Vec<u8>) -> Result<Self>\n\twhere\n\t\tSelf: Sized;\n}\n\nimpl FromWtf8Vec for OsString {\n\tfn from_wtf8_vec(wtf8: Vec<u8>) -> Result<Self> {\n\t\t#[cfg(unix)]\n\t\t{\n\t\t\tuse std::os::unix::ffi::OsStringExt;\n\t\t\tOk(Self::from_vec(wtf8))\n\t\t}\n\t\t#[cfg(windows)]\n\t\t{\n\t\t\tif super::valid_wtf8(&wtf8) {\n\t\t\t\tOk(unsafe { Self::from_encoded_bytes_unchecked(wtf8) })\n\t\t\t} else {\n\t\t\t\tErr(anyhow::anyhow!(\"Invalid WTF-8 sequence\"))\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl FromWtf8Vec for std::path::PathBuf {\n\tfn from_wtf8_vec(wtf8: Vec<u8>) -> Result<Self> { Ok(OsString::from_wtf8_vec(wtf8)?.into()) }\n}\n"
  },
  {
    "path": "yazi-shim/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-shim\"\ndescription            = \"Yazi crate shims\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[dependencies]\nyazi-macro = { path = \"../yazi-macro\", version = \"26.2.2\" }\n\n# External dependencies\ncrossterm = { workspace = true }\nratatui   = { workspace = true }\ntwox-hash = { workspace = true }\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\ncrossterm = { workspace = true, features = [ \"use-dev-tty\", \"libc\" ] }\n"
  },
  {
    "path": "yazi-shim/README.md",
    "content": "# yazi-shim\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-shim/src/crossterm/if.rs",
    "content": "pub struct If<T: crossterm::Command>(pub bool, pub T);\n\nimpl<T: crossterm::Command> crossterm::Command for If<T> {\n\tfn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {\n\t\tif self.0 { self.1.write_ansi(f) } else { Ok(()) }\n\t}\n\n\t#[cfg(windows)]\n\tfn execute_winapi(&self) -> std::io::Result<()> {\n\t\tif self.0 { self.1.execute_winapi() } else { Ok(()) }\n\t}\n\n\t#[cfg(windows)]\n\tfn is_ansi_code_supported(&self) -> bool { self.1.is_ansi_code_supported() }\n}\n"
  },
  {
    "path": "yazi-shim/src/crossterm/mod.rs",
    "content": "yazi_macro::mod_flat!(r#if restore_background restore_cursor set_background);\n"
  },
  {
    "path": "yazi-shim/src/crossterm/restore_background.rs",
    "content": "pub struct RestoreBackground;\n\nimpl crossterm::Command for RestoreBackground {\n\tfn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {\n\t\twrite!(f, \"\\x1b]111\\x1b\\\\\")\n\t}\n\n\t#[cfg(windows)]\n\tfn execute_winapi(&self) -> std::io::Result<()> { Ok(()) }\n}\n"
  },
  {
    "path": "yazi-shim/src/crossterm/restore_cursor.rs",
    "content": "use crossterm::cursor::SetCursorStyle;\n\npub struct RestoreCursor {\n\tpub shape: u8,\n\tpub blink: bool,\n}\n\nimpl crossterm::Command for RestoreCursor {\n\tfn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {\n\t\tlet (shape, shape_blink) = match self.shape {\n\t\t\tu8::MAX => (0, None),\n\t\t\tn => (n.max(1).div_ceil(2), Some(n.max(1) & 1 == 1)),\n\t\t};\n\n\t\tlet blink = shape_blink.unwrap_or(self.blink);\n\t\tOk(match shape {\n\t\t\t2 if blink => SetCursorStyle::BlinkingUnderScore.write_ansi(f)?,\n\t\t\t2 if !blink => SetCursorStyle::SteadyUnderScore.write_ansi(f)?,\n\t\t\t3 if blink => SetCursorStyle::BlinkingBar.write_ansi(f)?,\n\t\t\t3 if !blink => SetCursorStyle::SteadyBar.write_ansi(f)?,\n\t\t\t_ if blink => SetCursorStyle::DefaultUserShape.write_ansi(f)?,\n\t\t\t_ if !blink => SetCursorStyle::SteadyBlock.write_ansi(f)?,\n\t\t\t_ => unreachable!(),\n\t\t})\n\t}\n\n\t#[cfg(windows)]\n\tfn execute_winapi(&self) -> std::io::Result<()> { Ok(()) }\n}\n"
  },
  {
    "path": "yazi-shim/src/crossterm/set_background.rs",
    "content": "pub struct SetBackground<'a>(pub &'a str);\n\nimpl crossterm::Command for SetBackground<'_> {\n\tfn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {\n\t\tif self.0.is_empty() { Ok(()) } else { write!(f, \"\\x1b]11;{}\\x1b\\\\\", self.0) }\n\t}\n\n\t#[cfg(windows)]\n\tfn execute_winapi(&self) -> std::io::Result<()> { Ok(()) }\n}\n"
  },
  {
    "path": "yazi-shim/src/lib.rs",
    "content": "yazi_macro::mod_pub!(crossterm ratatui);\n\nyazi_macro::mod_flat!(twox);\n"
  },
  {
    "path": "yazi-shim/src/ratatui/mod.rs",
    "content": "yazi_macro::mod_flat!(paragraph);\n"
  },
  {
    "path": "yazi-shim/src/ratatui/paragraph.rs",
    "content": "use ratatui::{text::Text, widgets::{Paragraph, Wrap}};\n\npub fn line_count<'a, T, W, I>(text: T, width: u16, indent: I, wrap: W) -> usize\nwhere\n\tT: Into<Text<'a>>,\n\tI: AsRef<str>,\n\tW: Into<Option<Wrap>>,\n{\n\tline_count_impl(text.into(), width, indent.as_ref(), wrap.into())\n}\n\nfn line_count_impl(mut text: Text<'_>, mut width: u16, indent: &str, wrap: Option<Wrap>) -> usize {\n\twidth = width.max(1);\n\n\tlet Some(wrap) = wrap else {\n\t\treturn Paragraph::new(text).line_count(width);\n\t};\n\n\tif indent.len() == 1 {\n\t\treturn Paragraph::new(text).wrap(wrap).line_count(width);\n\t}\n\n\tlet extra = indent.len().saturating_sub(1);\n\tfor line in &mut text.lines {\n\t\tfor span in &mut line.spans {\n\t\t\tlet mut out = None::<String>;\n\t\t\tlet mut start = 0;\n\t\t\tfor (idx, b) in span.content.bytes().enumerate() {\n\t\t\t\tif b != b'\\t' {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tlet out = out.get_or_insert_with(|| String::with_capacity(span.content.len() + extra));\n\t\t\t\tif start < idx {\n\t\t\t\t\tout.push_str(unsafe { span.content.get_unchecked(start..idx) });\n\t\t\t\t}\n\n\t\t\t\tout.push_str(indent);\n\t\t\t\tstart = idx + 1;\n\t\t\t}\n\n\t\t\tif let Some(mut out) = out {\n\t\t\t\tif start < span.content.len() {\n\t\t\t\t\tout.push_str(unsafe { span.content.get_unchecked(start..) });\n\t\t\t\t}\n\t\t\t\tspan.content = out.into();\n\t\t\t}\n\t\t}\n\t}\n\n\tParagraph::new(text).wrap(wrap).line_count(width)\n}\n"
  },
  {
    "path": "yazi-shim/src/twox.rs",
    "content": "use std::hash::Hasher;\n\npub struct Twox128(twox_hash::XxHash3_128);\n\nimpl Default for Twox128 {\n\tfn default() -> Self { Self(twox_hash::XxHash3_128::new()) }\n}\n\nimpl Twox128 {\n\tpub fn finish_128(self) -> u128 { self.0.finish_128() }\n}\n\nimpl Hasher for Twox128 {\n\tfn write(&mut self, bytes: &[u8]) { self.0.write(bytes) }\n\n\tfn finish(&self) -> u64 { unimplemented!() }\n}\n"
  },
  {
    "path": "yazi-term/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-term\"\ndescription            = \"Yazi terminal extensions\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[dependencies]\nyazi-config   = { path = \"../yazi-config\", version = \"26.2.2\" }\nyazi-emulator = { path = \"../yazi-emulator\", version = \"26.2.2\" }\nyazi-macro    = { path = \"../yazi-macro\", version = \"26.2.2\" }\nyazi-shared   = { path = \"../yazi-shared\", version = \"26.2.2\" }\nyazi-shim     = { path = \"../yazi-shim\", version = \"26.2.2\" }\nyazi-tty      = { path = \"../yazi-tty\", version = \"26.2.2\" }\n\n# External dependencies\nanyhow    = { workspace = true }\ncrossterm = { workspace = true }\nratatui   = { workspace = true }\ntokio     = { workspace = true }\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\ncrossterm = { workspace = true, features = [ \"use-dev-tty\", \"libc\" ] }\n"
  },
  {
    "path": "yazi-term/README.md",
    "content": "# yazi-term\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-term/src/lib.rs",
    "content": "yazi_macro::mod_flat!(option semaphore state term);\n\npub fn init() { YIELD_TO_SUBPROCESS.init(tokio::sync::Semaphore::new(1)); }\n"
  },
  {
    "path": "yazi-term/src/option.rs",
    "content": "use yazi_config::{THEME, YAZI};\n\npub(super) struct TermOption {\n\tpub bg:    String,\n\tpub mouse: bool,\n}\n\nimpl Default for TermOption {\n\tfn default() -> Self {\n\t\tSelf { bg: THEME.app.bg_color(), mouse: !YAZI.mgr.mouse_events.get().is_empty() }\n\t}\n}\n"
  },
  {
    "path": "yazi-term/src/semaphore.rs",
    "content": "use tokio::sync::Semaphore;\nuse yazi_shared::RoCell;\n\npub static YIELD_TO_SUBPROCESS: RoCell<Semaphore> = RoCell::new();\n"
  },
  {
    "path": "yazi-term/src/state.rs",
    "content": "use crate::TermOption;\n\n#[derive(Clone, Copy)]\npub struct TermState {\n\tpub bg:           bool,\n\tpub csi_u:        bool,\n\tpub mouse:        bool,\n\tpub title:        bool,\n\tpub cursor_shape: u8,\n\tpub cursor_blink: bool,\n}\n\nimpl TermState {\n\tpub(super) const fn default() -> Self {\n\t\tSelf {\n\t\t\tbg:           false,\n\t\t\tcsi_u:        false,\n\t\t\tmouse:        false,\n\t\t\ttitle:        false,\n\t\t\tcursor_shape: 0,\n\t\t\tcursor_blink: false,\n\t\t}\n\t}\n\n\tpub(super) fn new(resp: &str, opt: &TermOption) -> Self {\n\t\tlet csi_u = resp.contains(\"\\x1b[?0u\");\n\n\t\tlet cursor_shape = resp\n\t\t\t.split_once(\"\\x1bP1$r\")\n\t\t\t.and_then(|(_, s)| s.bytes().next())\n\t\t\t.filter(|&b| matches!(b, b'0'..=b'6'))\n\t\t\t.map_or(u8::MAX, |b| b - b'0');\n\n\t\tlet cursor_blink = resp.contains(\"\\x1b[?12;1$y\");\n\n\t\tSelf {\n\t\t\tbg: !opt.bg.is_empty(),\n\t\t\tcsi_u,\n\t\t\tmouse: opt.mouse,\n\t\t\ttitle: false,\n\t\t\tcursor_shape,\n\t\t\tcursor_blink,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-term/src/term.rs",
    "content": "use std::{io, ops::Deref};\n\nuse anyhow::Result;\nuse crossterm::{event::{DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, EnableFocusChange, EnableMouseCapture, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, execute, queue, style::Print, terminal::{EnterAlternateScreen, LeaveAlternateScreen, SetTitle, disable_raw_mode, enable_raw_mode}};\nuse ratatui::{CompletedFrame, Frame, Terminal, backend::CrosstermBackend, buffer::Buffer, layout::Rect};\nuse yazi_emulator::{Emulator, Mux, TMUX};\nuse yazi_shared::SyncCell;\nuse yazi_shim::crossterm::{If, RestoreBackground, RestoreCursor, SetBackground};\nuse yazi_tty::{TTY, TtyWriter};\n\nuse crate::{TermOption, TermState};\n\npub static STATE: SyncCell<TermState> = SyncCell::new(TermState::default());\n\npub struct Term {\n\tinner:       Terminal<CrosstermBackend<TtyWriter<'static>>>,\n\tlast_area:   Rect,\n\tlast_buffer: Buffer,\n}\n\nimpl Term {\n\tpub fn start() -> Result<Self> {\n\t\tlet opt = TermOption::default();\n\t\tlet mut term = Self {\n\t\t\tinner:       Terminal::new(CrosstermBackend::new(TTY.writer()))?,\n\t\t\tlast_area:   Default::default(),\n\t\t\tlast_buffer: Default::default(),\n\t\t};\n\n\t\tenable_raw_mode()?;\n\t\tstatic FIRST: SyncCell<bool> = SyncCell::new(false);\n\t\tif FIRST.replace(true) && yazi_emulator::TMUX.get() {\n\t\t\tyazi_emulator::Mux::tmux_passthrough();\n\t\t}\n\n\t\texecute!(\n\t\t\tTTY.writer(),\n\t\t\tIf(!TMUX.get(), EnterAlternateScreen),\n\t\t\tPrint(\"\\x1bP$q q\\x1b\\\\\"), // Request cursor shape (DECRQSS query for DECSCUSR)\n\t\t\tPrint(\"\\x1b[?12$p\"),      // Request cursor blink status (DECRQM query for DECSET 12)\n\t\t\tPrint(\"\\x1b[?u\"),         // Request keyboard enhancement flags (CSI u)\n\t\t\tPrint(\"\\x1b[0c\"),         // Request device attributes\n\t\t\tIf(TMUX.get(), EnterAlternateScreen),\n\t\t\tSetBackground(&opt.bg), // Set app background\n\t\t\tEnableBracketedPaste,\n\t\t\tEnableFocusChange,\n\t\t\tIf(opt.mouse, EnableMouseCapture),\n\t\t)?;\n\n\t\tlet resp = Emulator::read_until_da1();\n\t\tMux::tmux_drain()?;\n\n\t\tSTATE.set(TermState::new(&resp, &opt));\n\t\tif STATE.get().csi_u {\n\t\t\t_ = queue!(\n\t\t\t\tTTY.writer(),\n\t\t\t\tPushKeyboardEnhancementFlags(\n\t\t\t\t\tKeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES\n\t\t\t\t\t\t| KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS,\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\n\t\tterm.inner.hide_cursor()?;\n\t\tterm.inner.clear()?;\n\t\tterm.inner.flush()?;\n\t\tOk(term)\n\t}\n\n\tfn stop(&mut self) -> Result<()> {\n\t\tlet state = STATE.get();\n\n\t\texecute!(\n\t\t\tTTY.writer(),\n\t\t\tIf(state.mouse, DisableMouseCapture),\n\t\t\tIf(state.bg, RestoreBackground),\n\t\t\tIf(state.csi_u, PopKeyboardEnhancementFlags),\n\t\t\tRestoreCursor { shape: state.cursor_shape, blink: state.cursor_blink },\n\t\t\tIf(state.title, SetTitle(\"\")),\n\t\t\tDisableFocusChange,\n\t\t\tDisableBracketedPaste,\n\t\t\tLeaveAlternateScreen,\n\t\t)?;\n\n\t\tself.inner.show_cursor()?;\n\t\tOk(disable_raw_mode()?)\n\t}\n\n\tpub fn goodbye(f: impl FnOnce() -> i32) -> ! {\n\t\tlet state = STATE.get();\n\n\t\texecute!(\n\t\t\tTTY.writer(),\n\t\t\tIf(state.mouse, DisableMouseCapture),\n\t\t\tIf(state.bg, RestoreBackground),\n\t\t\tIf(state.csi_u, PopKeyboardEnhancementFlags),\n\t\t\tRestoreCursor { shape: state.cursor_shape, blink: state.cursor_blink },\n\t\t\tIf(state.title, SetTitle(\"\")),\n\t\t\tDisableFocusChange,\n\t\t\tDisableBracketedPaste,\n\t\t\tLeaveAlternateScreen,\n\t\t\tcrossterm::cursor::Show\n\t\t)\n\t\t.ok();\n\n\t\tdisable_raw_mode().ok();\n\n\t\tstd::process::exit(f());\n\t}\n\n\tpub fn draw(&mut self, f: impl FnOnce(&mut Frame)) -> io::Result<CompletedFrame<'_>> {\n\t\tlet last = self.inner.draw(f)?;\n\n\t\tself.last_area = last.area;\n\t\tself.last_buffer = last.buffer.clone();\n\t\tOk(last)\n\t}\n\n\tpub fn draw_partial(&mut self, f: impl FnOnce(&mut Frame)) -> io::Result<CompletedFrame<'_>> {\n\t\tself.inner.draw(|frame| {\n\t\t\tlet buffer = frame.buffer_mut();\n\t\t\tfor y in self.last_area.top()..self.last_area.bottom() {\n\t\t\t\tfor x in self.last_area.left()..self.last_area.right() {\n\t\t\t\t\tlet mut cell = self.last_buffer[(x, y)].clone();\n\t\t\t\t\tcell.skip = false;\n\t\t\t\t\tbuffer[(x, y)] = cell;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tf(frame);\n\t\t})\n\t}\n\n\tpub fn can_partial(&mut self) -> bool {\n\t\tself.inner.autoresize().is_ok() && self.last_area == self.inner.get_frame().area()\n\t}\n}\n\nimpl Drop for Term {\n\tfn drop(&mut self) { self.stop().ok(); }\n}\n\nimpl Deref for Term {\n\ttype Target = Terminal<CrosstermBackend<TtyWriter<'static>>>;\n\n\tfn deref(&self) -> &Self::Target { &self.inner }\n}\n"
  },
  {
    "path": "yazi-tty/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-tty\"\ndescription            = \"Yazi TTY access layer\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[dependencies]\nyazi-macro  = { path = \"../yazi-macro\", version = \"26.2.2\" }\nyazi-shared = { path = \"../yazi-shared\", version = \"26.2.2\" }\n\n# External dependencies\nparking_lot = { workspace = true }\ntracing     = { workspace = true }\n\n[target.\"cfg(unix)\".dependencies]\nlibc = { workspace = true }\n\n[target.'cfg(windows)'.dependencies]\nwindows-sys = { version = \"0.61.2\", features = [ \"Win32_Globalization\", \"Win32_Storage_FileSystem\", \"Win32_System_IO\", \"Win32_System_Console\", \"Win32_System_Threading\", \"Win32_Security\" ] }\n"
  },
  {
    "path": "yazi-tty/README.md",
    "content": "# yazi-tty\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-tty/src/handle.rs",
    "content": "use std::{io::{Error, ErrorKind, Read, Write}, time::Duration};\n\nuse tracing::error;\n\npub struct Handle {\n\t#[cfg(unix)]\n\tinner:           std::os::fd::RawFd,\n\t#[cfg(windows)]\n\tinner:           std::os::windows::io::RawHandle,\n\tclose:           bool,\n\t#[cfg(windows)]\n\tout_utf8:        bool,\n\t#[cfg(windows)]\n\tincomplete_utf8: super::IncompleteUtf8,\n}\n\nimpl Drop for Handle {\n\tfn drop(&mut self) {\n\t\t#[cfg(unix)]\n\t\tif self.close {\n\t\t\tunsafe { libc::close(self.inner) };\n\t\t}\n\t\t#[cfg(windows)]\n\t\tif self.close {\n\t\t\tunsafe { windows_sys::Win32::Foundation::CloseHandle(self.inner) };\n\t\t}\n\t}\n}\n\nimpl Read for Handle {\n\tfn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {\n\t\t#[cfg(unix)]\n\t\t{\n\t\t\tuse std::os::{fd::IntoRawFd, unix::io::FromRawFd};\n\t\t\tlet mut f = unsafe { std::fs::File::from_raw_fd(self.inner) };\n\t\t\tlet result = f.read(buf);\n\t\t\t_ = f.into_raw_fd();\n\t\t\tresult\n\t\t}\n\t\t#[cfg(windows)]\n\t\t{\n\t\t\tuse std::os::windows::io::{FromRawHandle, IntoRawHandle};\n\t\t\tlet mut f = unsafe { std::fs::File::from_raw_handle(self.inner) };\n\t\t\tlet result = f.read(buf);\n\t\t\t_ = f.into_raw_handle();\n\t\t\tresult\n\t\t}\n\t}\n}\n\nimpl Write for Handle {\n\tfn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {\n\t\t#[cfg(unix)]\n\t\t{\n\t\t\tuse std::os::{fd::IntoRawFd, unix::io::FromRawFd};\n\t\t\tlet mut f = unsafe { std::fs::File::from_raw_fd(self.inner) };\n\t\t\tlet result = f.write(buf);\n\t\t\t_ = f.into_raw_fd();\n\t\t\tresult\n\t\t}\n\t\t#[cfg(windows)]\n\t\t{\n\t\t\tuse std::os::windows::io::{FromRawHandle, IntoRawHandle};\n\t\t\tif self.out_utf8 {\n\t\t\t\tlet mut f = unsafe { std::fs::File::from_raw_handle(self.inner) };\n\t\t\t\tlet result = f.write(buf);\n\t\t\t\t_ = f.into_raw_handle();\n\t\t\t\tresult\n\t\t\t} else {\n\t\t\t\tsuper::write_console_utf16(buf, &mut self.incomplete_utf8, self.inner)\n\t\t\t}\n\t\t}\n\t}\n\n\tfn flush(&mut self) -> std::io::Result<()> { Ok(()) }\n}\n\n#[cfg(unix)]\nimpl Handle {\n\tpub(super) fn new(out: bool) -> Self {\n\t\tuse std::{fs::OpenOptions, os::fd::IntoRawFd};\n\n\t\tuse libc::{STDIN_FILENO, STDOUT_FILENO};\n\n\t\tlet resort = Self { inner: if out { STDOUT_FILENO } else { STDIN_FILENO }, close: false };\n\t\tif unsafe { libc::isatty(resort.inner) } == 1 {\n\t\t\treturn resort;\n\t\t}\n\n\t\tmatch OpenOptions::new().read(!out).write(out).open(\"/dev/tty\") {\n\t\t\tOk(f) => Self { inner: f.into_raw_fd(), close: true },\n\t\t\tErr(err) => {\n\t\t\t\terror!(\"Failed to open /dev/tty, falling back to stdin/stdout: {err}\");\n\t\t\t\tresort\n\t\t\t}\n\t\t}\n\t}\n\n\tpub(super) fn poll(&mut self, timeout: Duration) -> std::io::Result<bool> {\n\t\tlet mut tv = libc::timeval {\n\t\t\ttv_sec:  timeout.as_secs() as libc::time_t,\n\t\t\ttv_usec: timeout.subsec_micros() as libc::suseconds_t,\n\t\t};\n\n\t\tlet result = unsafe {\n\t\t\tlet mut set: libc::fd_set = std::mem::zeroed();\n\t\t\tlibc::FD_ZERO(&mut set);\n\t\t\tlibc::FD_SET(self.inner, &mut set);\n\t\t\tlibc::select(self.inner + 1, &mut set, std::ptr::null_mut(), std::ptr::null_mut(), &mut tv)\n\t\t};\n\n\t\tmatch result {\n\t\t\t-1 => Err(Error::last_os_error()),\n\t\t\t0 => Ok(false),\n\t\t\t_ => Ok(true),\n\t\t}\n\t}\n\n\tpub(super) fn read_u8(&mut self) -> std::io::Result<u8> {\n\t\tlet mut b = 0;\n\t\tmatch unsafe { libc::read(self.inner, &mut b as *mut _ as *mut _, 1) } {\n\t\t\t-1 => Err(Error::last_os_error()),\n\t\t\t0 => Err(Error::from(ErrorKind::UnexpectedEof)),\n\t\t\t_ => Ok(b),\n\t\t}\n\t}\n}\n\n#[cfg(windows)]\nimpl Handle {\n\tpub(super) fn new(out: bool) -> Self {\n\t\tuse std::{io::{Error, stdin, stdout}, os::windows::io::AsRawHandle};\n\n\t\tuse windows_sys::Win32::{Foundation::{GENERIC_READ, GENERIC_WRITE, INVALID_HANDLE_VALUE}, Globalization::CP_UTF8, Storage::FileSystem::{CreateFileW, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING}, System::Console::GetConsoleOutputCP};\n\n\t\tlet name: Vec<u16> = if out { \"CONOUT$\\0\" } else { \"CONIN$\\0\" }.encode_utf16().collect();\n\t\tlet result = unsafe {\n\t\t\tCreateFileW(\n\t\t\t\tname.as_ptr(),\n\t\t\t\tGENERIC_READ | GENERIC_WRITE,\n\t\t\t\tFILE_SHARE_READ | FILE_SHARE_WRITE,\n\t\t\t\tstd::ptr::null_mut(),\n\t\t\t\tOPEN_EXISTING,\n\t\t\t\t0,\n\t\t\t\tstd::ptr::null_mut(),\n\t\t\t)\n\t\t};\n\n\t\tif result != INVALID_HANDLE_VALUE {\n\t\t\treturn Self {\n\t\t\t\tinner:           result,\n\t\t\t\tclose:           true,\n\t\t\t\tout_utf8:        unsafe { GetConsoleOutputCP() } == CP_UTF8,\n\t\t\t\tincomplete_utf8: Default::default(),\n\t\t\t};\n\t\t}\n\n\t\terror!(\n\t\t\t\"Failed to open {}, falling back to stdin/stdout: {}\",\n\t\t\tif out { \"CONOUT$\" } else { \"CONIN$\" },\n\t\t\tError::last_os_error()\n\t\t);\n\t\tSelf {\n\t\t\tinner:           if out { stdout().as_raw_handle() } else { stdin().as_raw_handle() },\n\t\t\tclose:           false,\n\t\t\tout_utf8:        unsafe { GetConsoleOutputCP() } == CP_UTF8,\n\t\t\tincomplete_utf8: Default::default(),\n\t\t}\n\t}\n\n\tpub(super) fn poll(&mut self, timeout: Duration) -> std::io::Result<bool> {\n\t\tuse windows_sys::Win32::{Foundation::{WAIT_FAILED, WAIT_OBJECT_0}, System::Threading::WaitForSingleObject};\n\n\t\tlet millis = timeout.as_millis();\n\t\tmatch unsafe { WaitForSingleObject(self.inner, millis as u32) } {\n\t\t\tWAIT_FAILED => Err(Error::last_os_error()),\n\t\t\tWAIT_OBJECT_0 => Ok(true),\n\t\t\t_ => Ok(false),\n\t\t}\n\t}\n\n\tpub(super) fn read_u8(&mut self) -> std::io::Result<u8> {\n\t\tuse windows_sys::Win32::Storage::FileSystem::ReadFile;\n\n\t\tlet mut buf = 0;\n\t\tlet mut bytes = 0;\n\t\tlet success = unsafe { ReadFile(self.inner, &mut buf, 1, &mut bytes, std::ptr::null_mut()) };\n\n\t\tif success == 0 {\n\t\t\treturn Err(Error::last_os_error());\n\t\t} else if bytes == 0 {\n\t\t\treturn Err(Error::from(ErrorKind::UnexpectedEof));\n\t\t}\n\t\tOk(buf)\n\t}\n}\n"
  },
  {
    "path": "yazi-tty/src/lib.rs",
    "content": "yazi_macro::mod_flat!(handle tty);\n\n#[cfg(windows)]\nyazi_macro::mod_flat!(windows);\n\npub static TTY: yazi_shared::RoCell<Tty> = yazi_shared::RoCell::new();\n\npub fn init() { TTY.with(<_>::default); }\n"
  },
  {
    "path": "yazi-tty/src/tty.rs",
    "content": "use std::{io::{BufWriter, Error, ErrorKind, Read, Write}, time::{Duration, Instant}};\n\nuse parking_lot::{Mutex, MutexGuard};\n\nuse super::Handle;\n\npub struct Tty {\n\tstdin:  Mutex<Handle>,\n\tstdout: Mutex<BufWriter<Handle>>,\n}\n\nimpl Default for Tty {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tstdin:  Mutex::new(Handle::new(false)),\n\t\t\tstdout: Mutex::new(BufWriter::new(Handle::new(true))),\n\t\t}\n\t}\n}\n\nimpl Tty {\n\tpub const fn reader(&self) -> TtyReader<'_> { TtyReader(&self.stdin) }\n\n\tpub const fn writer(&self) -> TtyWriter<'_> { TtyWriter(&self.stdout) }\n\n\tpub fn lockin(&self) -> MutexGuard<'_, Handle> { self.stdin.lock() }\n\n\tpub fn lockout(&self) -> MutexGuard<'_, BufWriter<Handle>> { self.stdout.lock() }\n\n\tpub fn read_until<P>(&self, timeout: Duration, predicate: P) -> (Vec<u8>, std::io::Result<()>)\n\twhere\n\t\tP: Fn(u8, &[u8]) -> bool,\n\t{\n\t\tlet mut buf: Vec<u8> = Vec::with_capacity(200);\n\t\tlet now = Instant::now();\n\n\t\tlet mut read = || {\n\t\t\tlet mut stdin = self.stdin.lock();\n\t\t\tloop {\n\t\t\t\tif now.elapsed() > timeout {\n\t\t\t\t\treturn Err(Error::from(ErrorKind::TimedOut));\n\t\t\t\t} else if !stdin.poll(Duration::from_millis(30))? {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tlet b = stdin.read_u8()?;\n\t\t\t\tbuf.push(b);\n\n\t\t\t\tif predicate(b, &buf) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tOk(())\n\t\t};\n\n\t\tlet result = read();\n\t\t(buf, result)\n\t}\n}\n\n// --- Reader\npub struct TtyReader<'a>(&'a Mutex<Handle>);\n\nimpl Read for TtyReader<'_> {\n\tfn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { self.0.lock().read(buf) }\n}\n\n// --- Writer\npub struct TtyWriter<'a>(&'a Mutex<BufWriter<Handle>>);\n\nimpl std::io::Write for TtyWriter<'_> {\n\tfn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { self.0.lock().write(buf) }\n\n\tfn flush(&mut self) -> std::io::Result<()> { self.0.lock().flush() }\n}\n\nimpl std::fmt::Write for TtyWriter<'_> {\n\tfn write_str(&mut self, s: &str) -> std::fmt::Result {\n\t\tself.0.lock().write_all(s.as_bytes()).map_err(|_| std::fmt::Error)\n\t}\n}\n"
  },
  {
    "path": "yazi-tty/src/windows.rs",
    "content": "// Copied from https://github.com/rust-lang/rust/blob/master/library/std/src/sys/pal/windows/stdio.rs\n\nuse std::{mem::MaybeUninit, os::windows::io::RawHandle, str};\n\nuse windows_sys::Win32::{Globalization::{CP_UTF8, MB_ERR_INVALID_CHARS, MultiByteToWideChar}, System::Console::WriteConsoleW};\nuse yazi_shared::{floor_char_boundary, utf8_char_width};\n\n// Apparently Windows doesn't handle large reads on stdin or writes to\n// stdout/stderr well (see #13304 for details).\n//\n// From MSDN (2011): \"The storage for this buffer is allocated from a shared\n// heap for the process that is 64 KB in size. The maximum size of the buffer\n// will depend on heap usage.\"\n//\n// We choose the cap at 8 KiB because libuv does the same, and it seems to be\n// acceptable so far.\nconst MAX_BUFFER_SIZE: usize = 8192;\n\n#[derive(Default)]\npub(super) struct IncompleteUtf8 {\n\tbytes: [u8; 4],\n\tlen:   u8,\n}\n\npub(super) fn write_console_utf16(\n\tdata: &[u8],\n\tincomplete_utf8: &mut IncompleteUtf8,\n\thandle: RawHandle,\n) -> std::io::Result<usize> {\n\tif incomplete_utf8.len > 0 {\n\t\tassert!(incomplete_utf8.len < 4, \"Unexpected number of bytes for incomplete UTF-8 codepoint.\");\n\t\tif data[0] >> 6 != 0b10 {\n\t\t\t// not a continuation byte - reject\n\t\t\tincomplete_utf8.len = 0;\n\t\t\treturn Err(std::io::Error::new(\n\t\t\t\tstd::io::ErrorKind::InvalidData,\n\t\t\t\t\"Windows stdio in console mode does not support writing non-UTF-8 byte sequences\",\n\t\t\t));\n\t\t}\n\t\tincomplete_utf8.bytes[incomplete_utf8.len as usize] = data[0];\n\t\tincomplete_utf8.len += 1;\n\t\tlet char_width = utf8_char_width(incomplete_utf8.bytes[0]);\n\t\tif (incomplete_utf8.len as usize) < char_width {\n\t\t\t// more bytes needed\n\t\t\treturn Ok(1);\n\t\t}\n\t\tlet s = str::from_utf8(&incomplete_utf8.bytes[0..incomplete_utf8.len as usize]);\n\t\tincomplete_utf8.len = 0;\n\t\tmatch s {\n\t\t\tOk(s) => {\n\t\t\t\tassert_eq!(char_width, s.len());\n\t\t\t\tlet written = write_valid_utf8_to_console(handle, s)?;\n\t\t\t\tassert_eq!(written, s.len()); // guaranteed by write_valid_utf8_to_console() for single codepoint writes\n\t\t\t\treturn Ok(1);\n\t\t\t}\n\t\t\tErr(_) => {\n\t\t\t\treturn Err(std::io::Error::new(\n\t\t\t\t\tstd::io::ErrorKind::InvalidData,\n\t\t\t\t\t\"Windows stdio in console mode does not support writing non-UTF-8 byte sequences\",\n\t\t\t\t));\n\t\t\t}\n\t\t}\n\t}\n\n\t// As the console is meant for presenting text, we assume bytes of `data` are\n\t// encoded as UTF-8, which needs to be encoded as UTF-16.\n\t//\n\t// If the data is not valid UTF-8 we write out as many bytes as are valid.\n\t// If the first byte is invalid it is either first byte of a multi-byte sequence\n\t// but the provided byte slice is too short or it is the first byte of an\n\t// invalid multi-byte sequence.\n\tlet len = std::cmp::min(data.len(), MAX_BUFFER_SIZE / 2);\n\tlet utf8 = match str::from_utf8(&data[..len]) {\n\t\tOk(s) => s,\n\t\tErr(ref e) if e.valid_up_to() == 0 => {\n\t\t\tlet first_byte_char_width = utf8_char_width(data[0]);\n\t\t\tif first_byte_char_width > 1 && data.len() < first_byte_char_width {\n\t\t\t\tincomplete_utf8.bytes[0] = data[0];\n\t\t\t\tincomplete_utf8.len = 1;\n\t\t\t\treturn Ok(1);\n\t\t\t} else {\n\t\t\t\treturn Err(std::io::Error::new(\n\t\t\t\t\tstd::io::ErrorKind::InvalidData,\n\t\t\t\t\t\"Windows stdio in console mode does not support writing non-UTF-8 byte sequences\",\n\t\t\t\t));\n\t\t\t}\n\t\t}\n\t\tErr(e) => str::from_utf8(&data[..e.valid_up_to()]).unwrap(),\n\t};\n\n\twrite_valid_utf8_to_console(handle, utf8)\n}\n\nfn write_valid_utf8_to_console(handle: RawHandle, utf8: &str) -> std::io::Result<usize> {\n\tdebug_assert!(!utf8.is_empty());\n\n\tlet mut utf16 = [MaybeUninit::<u16>::uninit(); MAX_BUFFER_SIZE / 2];\n\tlet utf8 = &utf8[..floor_char_boundary(utf8, utf16.len())];\n\n\tlet utf16: &[u16] = unsafe {\n\t\t// Note that this theoretically checks validity twice in the (most common) case\n\t\t// where the underlying byte sequence is valid utf-8 (given the check in\n\t\t// `write()`).\n\t\tlet result = MultiByteToWideChar(\n\t\t\tCP_UTF8,\n\t\t\tMB_ERR_INVALID_CHARS,\n\t\t\tutf8.as_ptr(),\n\t\t\tutf8.len() as i32,\n\t\t\tutf16.as_mut_ptr() as *mut _,\n\t\t\tutf16.len() as i32,\n\t\t);\n\t\tassert!(result != 0, \"Unexpected error in MultiByteToWideChar\");\n\n\t\t// Safety: MultiByteToWideChar initializes `result` values.\n\t\t&*(&utf16[..result as usize] as *const [MaybeUninit<u16>] as *const [u16])\n\t};\n\n\tlet mut written = write_u16s(handle, utf16)?;\n\n\t// Figure out how many bytes of as UTF-8 were written away as UTF-16.\n\tif written == utf16.len() {\n\t\tOk(utf8.len())\n\t} else {\n\t\t// Make sure we didn't end up writing only half of a surrogate pair (even though\n\t\t// the chance is tiny). Because it is not possible for user code to re-slice\n\t\t// `data` in such a way that a missing surrogate can be produced (and also\n\t\t// because of the UTF-8 validation above), write the missing surrogate out\n\t\t// now. Buffering it would mean we have to lie about the number of bytes\n\t\t// written.\n\t\tlet first_code_unit_remaining = utf16[written];\n\t\tif matches!(first_code_unit_remaining, 0xdcee..=0xdfff) {\n\t\t\t// low surrogate\n\t\t\t// We just hope this works, and give up otherwise\n\t\t\tlet _ = write_u16s(handle, &utf16[written..written + 1]);\n\t\t\twritten += 1;\n\t\t}\n\t\t// Calculate the number of bytes of `utf8` that were actually written.\n\t\tlet mut count = 0;\n\t\tfor ch in utf16[..written].iter() {\n\t\t\tcount += match ch {\n\t\t\t\t0x0000..=0x007f => 1,\n\t\t\t\t0x0080..=0x07ff => 2,\n\t\t\t\t0xdcee..=0xdfff => 1, // Low surrogate. We already counted 3 bytes for the other.\n\t\t\t\t_ => 3,\n\t\t\t};\n\t\t}\n\t\tdebug_assert!(String::from_utf16(&utf16[..written]).unwrap() == utf8[..count]);\n\t\tOk(count)\n\t}\n}\n\nfn write_u16s(handle: RawHandle, data: &[u16]) -> std::io::Result<usize> {\n\tdebug_assert!(data.len() < u32::MAX as usize);\n\tlet mut written = 0;\n\tlet result = unsafe {\n\t\tWriteConsoleW(handle, data.as_ptr(), data.len() as u32, &mut written, std::ptr::null_mut())\n\t};\n\tif result == 0 { Err(std::io::Error::last_os_error()) } else { Ok(written as usize) }\n}\n"
  },
  {
    "path": "yazi-vfs/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-vfs\"\ndescription            = \"Yazi virtual file system\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[dependencies]\nyazi-config = { path = \"../yazi-config\", version = \"26.2.2\" }\nyazi-fs     = { path = \"../yazi-fs\", version = \"26.2.2\" }\nyazi-macro  = { path = \"../yazi-macro\", version = \"26.2.2\" }\nyazi-sftp   = { path = \"../yazi-sftp\", version = \"26.2.2\" }\nyazi-shared = { path = \"../yazi-shared\", version = \"26.2.2\" }\n\n# External dependencies\nanyhow      = { workspace = true }\nchrono      = { workspace = true }\ndeadpool    = { version = \"0.13.0\", default-features = false, features = [ \"managed\", \"rt_tokio_1\" ] }\neither      = { workspace = true }\nfutures     = { workspace = true }\nhashbrown   = { workspace = true }\nparking_lot = { workspace = true }\nrussh       = { workspace = true }\ntokio       = { workspace = true }\ntracing     = { workspace = true }\ntyped-path  = { workspace = true }\n"
  },
  {
    "path": "yazi-vfs/README.md",
    "content": "# yazi-vfs\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-vfs/src/cha.rs",
    "content": "use std::io;\n\nuse yazi_fs::cha::{Cha, ChaKind};\nuse yazi_shared::url::AsUrl;\n\nuse crate::provider;\n\npub trait VfsCha: Sized {\n\tfn from_url(url: impl AsUrl) -> impl Future<Output = io::Result<Self>>;\n\n\tfn from_follow<U>(url: U, cha: Self) -> impl Future<Output = Self>\n\twhere\n\t\tU: AsUrl;\n}\n\nimpl VfsCha for Cha {\n\t#[inline]\n\tasync fn from_url(url: impl AsUrl) -> io::Result<Self> {\n\t\tlet url = url.as_url();\n\t\tOk(Self::from_follow(url, provider::symlink_metadata(url).await?).await)\n\t}\n\n\tasync fn from_follow<U>(url: U, mut cha: Self) -> Self\n\twhere\n\t\tU: AsUrl,\n\t{\n\t\tlet url = url.as_url();\n\t\tlet mut retain = cha.kind & (ChaKind::HIDDEN | ChaKind::SYSTEM);\n\n\t\tif cha.is_link() {\n\t\t\tretain |= ChaKind::FOLLOW;\n\t\t\tcha = provider::metadata(url).await.unwrap_or(cha);\n\t\t}\n\n\t\tcha.attach(retain)\n\t}\n}\n"
  },
  {
    "path": "yazi-vfs/src/file.rs",
    "content": "use anyhow::Result;\nuse yazi_fs::{File, cha::Cha};\nuse yazi_shared::url::{UrlBuf, UrlCow};\n\nuse crate::{VfsCha, provider};\n\npub trait VfsFile: Sized {\n\tfn new<'a>(url: impl Into<UrlCow<'a>>) -> impl Future<Output = Result<Self>>;\n\n\tfn from_follow(url: UrlBuf, cha: Cha) -> impl Future<Output = Self>;\n}\n\nimpl VfsFile for File {\n\t#[inline]\n\tasync fn new<'a>(url: impl Into<UrlCow<'a>>) -> Result<Self> {\n\t\tlet url = url.into();\n\t\tlet cha = provider::symlink_metadata(&url).await?;\n\t\tOk(Self::from_follow(url.into_owned(), cha).await)\n\t}\n\n\t#[inline]\n\tasync fn from_follow(url: UrlBuf, cha: Cha) -> Self {\n\t\tlet link_to = if cha.is_link() { provider::read_link(&url).await.ok() } else { None };\n\n\t\tlet cha = Cha::from_follow(&url, cha).await;\n\n\t\tSelf { url, cha, link_to }\n\t}\n}\n"
  },
  {
    "path": "yazi-vfs/src/files.rs",
    "content": "use std::io;\n\nuse tokio::{select, sync::mpsc::{self, UnboundedReceiver}};\nuse yazi_fs::{File, Files, FilesOp, cha::Cha, mounts::PARTITIONS, provider::{DirReader, FileHolder}};\nuse yazi_shared::url::UrlBuf;\n\nuse crate::{VfsCha, VfsFile, VfsFilesOp, provider::{self, DirEntry}};\n\npub trait VfsFiles {\n\tfn from_dir(dir: &UrlBuf) -> impl Future<Output = io::Result<UnboundedReceiver<File>>>;\n\n\tfn from_dir_bulk(dir: &UrlBuf) -> impl Future<Output = io::Result<Vec<File>>>;\n\n\tfn assert_stale(dir: &UrlBuf, cha: Cha) -> impl Future<Output = Option<Cha>>;\n}\n\nimpl VfsFiles for Files {\n\tasync fn from_dir(dir: &UrlBuf) -> std::io::Result<UnboundedReceiver<File>> {\n\t\tlet mut it = provider::read_dir(dir).await?;\n\t\tlet (tx, rx) = mpsc::unbounded_channel();\n\n\t\ttokio::spawn(async move {\n\t\t\twhile let Ok(Some(ent)) = it.next().await {\n\t\t\t\tselect! {\n\t\t\t\t\t_ = tx.closed() => break,\n\t\t\t\t\tresult = ent.metadata() => {\n\t\t\t\t\t\tlet url = ent.url();\n\t\t\t\t\t\t_ = tx.send(match result {\n\t\t\t\t\t\t\tOk(cha) => File::from_follow(url, cha).await,\n\t\t\t\t\t\t\tErr(_) => File::from_dummy(url, ent.file_type().await.ok())\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tOk(rx)\n\t}\n\n\tasync fn from_dir_bulk(dir: &UrlBuf) -> std::io::Result<Vec<File>> {\n\t\tlet mut it = provider::read_dir(dir).await?;\n\t\tlet mut entries = Vec::new();\n\t\twhile let Ok(Some(entry)) = it.next().await {\n\t\t\tentries.push(entry);\n\t\t}\n\n\t\tlet (first, rest) = entries.split_at(entries.len() / 3);\n\t\tlet (second, third) = rest.split_at(entries.len() / 3);\n\t\tasync fn go(entries: &[DirEntry]) -> Vec<File> {\n\t\t\tlet mut files = Vec::with_capacity(entries.len());\n\t\t\tfor ent in entries {\n\t\t\t\tlet url = ent.url();\n\t\t\t\tfiles.push(match ent.metadata().await {\n\t\t\t\t\tOk(cha) => File::from_follow(url, cha).await,\n\t\t\t\t\tErr(_) => File::from_dummy(url, ent.file_type().await.ok()),\n\t\t\t\t});\n\t\t\t}\n\t\t\tfiles\n\t\t}\n\n\t\tOk(\n\t\t\tfutures::future::join_all([go(first), go(second), go(third)])\n\t\t\t\t.await\n\t\t\t\t.into_iter()\n\t\t\t\t.flatten()\n\t\t\t\t.collect(),\n\t\t)\n\t}\n\n\tasync fn assert_stale(dir: &UrlBuf, cha: Cha) -> Option<Cha> {\n\t\tuse std::io::ErrorKind;\n\t\tmatch Cha::from_url(dir).await {\n\t\t\tOk(c) if !c.is_dir() => FilesOp::issue_error(dir, ErrorKind::NotADirectory).await,\n\t\t\tOk(c) if c.hits(cha) && !PARTITIONS.read().timeless(cha) => {}\n\t\t\tOk(c) => return Some(c),\n\t\t\tErr(e) => FilesOp::issue_error(dir, e).await,\n\t\t}\n\t\tNone\n\t}\n}\n"
  },
  {
    "path": "yazi-vfs/src/fns.rs",
    "content": "use std::io::{self};\n\nuse yazi_macro::ok_or_not_found;\nuse yazi_shared::{strand::{StrandBuf, StrandLike}, url::{AsUrl, UrlBuf, UrlLike}};\n\nuse crate::provider;\n\n#[inline]\npub async fn maybe_exists(url: impl AsUrl) -> bool {\n\tmatch provider::symlink_metadata(url).await {\n\t\tOk(_) => true,\n\t\tErr(e) => e.kind() != io::ErrorKind::NotFound,\n\t}\n}\n\n// TODO: deprecate\npub async fn unique_name<F>(u: UrlBuf, append: F) -> io::Result<UrlBuf>\nwhere\n\tF: Future<Output = bool>,\n{\n\tmatch provider::symlink_metadata(&u).await {\n\t\tOk(_) => _unique_name(u, append.await).await,\n\t\tErr(e) if e.kind() == io::ErrorKind::NotFound => Ok(u),\n\t\tErr(e) => Err(e),\n\t}\n}\n\nasync fn _unique_name(mut url: UrlBuf, append: bool) -> io::Result<UrlBuf> {\n\tlet Some(stem) = url.stem().map(|s| s.to_owned()) else {\n\t\treturn Err(io::Error::new(io::ErrorKind::InvalidInput, \"empty file stem\"));\n\t};\n\n\tlet dot_ext = match url.ext() {\n\t\tSome(e) => {\n\t\t\tlet mut s = StrandBuf::with_capacity(url.kind(), e.len() + 1);\n\t\t\ts.push_str(\".\");\n\t\t\ts.try_push(e)?;\n\t\t\ts\n\t\t}\n\t\tNone => StrandBuf::default(),\n\t};\n\n\tlet mut name = StrandBuf::with_capacity(url.kind(), stem.len() + dot_ext.len() + 5);\n\tfor i in 1u64.. {\n\t\tname.clear();\n\t\tname.try_push(&stem)?;\n\n\t\tif append {\n\t\t\tname.try_push(&dot_ext)?;\n\t\t\tname.push_str(format!(\"_{i}\"));\n\t\t} else {\n\t\t\tname.push_str(format!(\"_{i}\"));\n\t\t\tname.try_push(&dot_ext)?;\n\t\t}\n\n\t\turl.try_set_name(&name)?;\n\t\tok_or_not_found!(provider::symlink_metadata(&url).await, break);\n\t}\n\n\tOk(url)\n}\n\npub async fn unique_file(u: UrlBuf, is_dir: bool) -> io::Result<UrlBuf> {\n\tlet result = if is_dir {\n\t\tprovider::create_dir(&u).await\n\t} else {\n\t\tprovider::create_new(&u).await.map(|_| ())\n\t};\n\n\tmatch result {\n\t\tOk(()) => Ok(u),\n\t\tErr(e) if e.kind() == io::ErrorKind::AlreadyExists => _unique_file(u, is_dir).await,\n\t\tErr(e) => Err(e),\n\t}\n}\n\nasync fn _unique_file(mut url: UrlBuf, is_dir: bool) -> io::Result<UrlBuf> {\n\tlet Some(stem) = url.stem().map(|s| s.to_owned()) else {\n\t\treturn Err(io::Error::new(io::ErrorKind::InvalidInput, \"empty file stem\"));\n\t};\n\n\tlet dot_ext = match url.ext() {\n\t\tSome(e) => {\n\t\t\tlet mut s = StrandBuf::with_capacity(url.kind(), e.len() + 1);\n\t\t\ts.push_str(\".\");\n\t\t\ts.try_push(e)?;\n\t\t\ts\n\t\t}\n\t\tNone => StrandBuf::default(),\n\t};\n\n\tlet mut name = StrandBuf::with_capacity(url.kind(), stem.len() + dot_ext.len() + 5);\n\tfor i in 1u64.. {\n\t\tname.clear();\n\t\tname.try_push(&stem)?;\n\n\t\tif is_dir {\n\t\t\tname.try_push(&dot_ext)?;\n\t\t\tname.push_str(format!(\"_{i}\"));\n\t\t} else {\n\t\t\tname.push_str(format!(\"_{i}\"));\n\t\t\tname.try_push(&dot_ext)?;\n\t\t}\n\n\t\turl.try_set_name(&name)?;\n\t\tlet result = if is_dir {\n\t\t\tprovider::create_dir(&url).await\n\t\t} else {\n\t\t\tprovider::create_new(&url).await.map(|_| ())\n\t\t};\n\n\t\tmatch result {\n\t\t\tOk(()) => break,\n\t\t\tErr(e) if e.kind() == io::ErrorKind::AlreadyExists => {}\n\t\t\tErr(e) => Err(e)?,\n\t\t};\n\t}\n\n\tOk(url)\n}\n"
  },
  {
    "path": "yazi-vfs/src/lib.rs",
    "content": "yazi_macro::mod_pub!(provider);\n\nyazi_macro::mod_flat!(cha file files fns op);\n\npub fn init() { provider::init(); }\n"
  },
  {
    "path": "yazi-vfs/src/op.rs",
    "content": "use yazi_fs::FilesOp;\nuse yazi_shared::url::{UrlBuf, UrlLike};\n\nuse crate::maybe_exists;\n\npub trait VfsFilesOp {\n\tfn issue_error(cwd: &UrlBuf, kind: impl Into<yazi_fs::error::Error>) -> impl Future<Output = ()>;\n}\n\nimpl VfsFilesOp for FilesOp {\n\tasync fn issue_error(cwd: &UrlBuf, err: impl Into<yazi_fs::error::Error>) {\n\t\tlet err = err.into();\n\t\tif err.kind() != std::io::ErrorKind::NotFound {\n\t\t\tSelf::IOErr(cwd.clone(), err).emit();\n\t\t} else if maybe_exists(cwd).await {\n\t\t\tSelf::IOErr(cwd.clone(), err).emit();\n\t\t} else if let Some((p, n)) = cwd.pair() {\n\t\t\tSelf::Deleting(p.into(), [n.into()].into()).emit();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-vfs/src/provider/calculator.rs",
    "content": "use std::{collections::VecDeque, io, time::{Duration, Instant}};\n\nuse either::Either;\nuse yazi_fs::{cha::Cha, provider::{DirReader, FileHolder}};\nuse yazi_shared::url::{AsUrl, UrlBuf};\n\nuse super::ReadDir;\n\npub enum SizeCalculator {\n\tFile(Option<u64>, Cha),\n\tDir(VecDeque<Either<UrlBuf, ReadDir>>, Cha),\n}\n\nimpl SizeCalculator {\n\tpub async fn new<U>(url: U) -> io::Result<Self>\n\twhere\n\t\tU: AsUrl,\n\t{\n\t\tlet url = url.as_url();\n\t\tlet cha = super::symlink_metadata(url).await?;\n\t\tOk(if cha.is_dir() {\n\t\t\tSelf::Dir(VecDeque::from([Either::Left(url.to_owned())]), cha)\n\t\t} else {\n\t\t\tSelf::File(Some(cha.len), cha)\n\t\t})\n\t}\n\n\tpub fn cha(&self) -> Cha {\n\t\tmatch *self {\n\t\t\tSelf::File(_, cha) | Self::Dir(_, cha) => cha,\n\t\t}\n\t}\n\n\tpub async fn total<U>(url: U) -> io::Result<u64>\n\twhere\n\t\tU: AsUrl,\n\t{\n\t\tlet mut it = Self::new(url).await?;\n\t\tlet mut total = 0;\n\t\twhile let Some(n) = it.next().await? {\n\t\t\ttotal += n;\n\t\t}\n\t\tOk(total)\n\t}\n\n\tpub async fn next(&mut self) -> io::Result<Option<u64>> {\n\t\tOk(match self {\n\t\t\tSelf::File(size, _) => size.take(),\n\t\t\tSelf::Dir(buf, _) => Self::next_chunk(buf).await,\n\t\t})\n\t}\n\n\tasync fn next_chunk(buf: &mut VecDeque<Either<UrlBuf, ReadDir>>) -> Option<u64> {\n\t\tlet (mut i, mut size, now) = (0, 0, Instant::now());\n\t\tmacro_rules! pop_and_continue {\n\t\t\t() => {{\n\t\t\t\tbuf.pop_front();\n\t\t\t\tif buf.is_empty() {\n\t\t\t\t\treturn Some(size);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}};\n\t\t}\n\n\t\twhile i < 2000 && now.elapsed() < Duration::from_millis(100) {\n\t\t\ti += 1;\n\t\t\tlet front = buf.front_mut()?;\n\n\t\t\tif let Either::Left(p) = front {\n\t\t\t\t*front = match super::read_dir(p).await {\n\t\t\t\t\tOk(it) => Either::Right(it),\n\t\t\t\t\tErr(_) => pop_and_continue!(),\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tlet Ok(Some(ent)) = front.as_mut().right()?.next().await else {\n\t\t\t\tpop_and_continue!();\n\t\t\t};\n\n\t\t\tlet Ok(ft) = ent.file_type().await else { continue };\n\t\t\tif ft.is_dir() {\n\t\t\t\tbuf.push_back(Either::Left(ent.url()));\n\t\t\t} else if let Ok(cha) = ent.metadata().await {\n\t\t\t\tsize += cha.len;\n\t\t\t}\n\t\t}\n\t\tSome(size)\n\t}\n}\n"
  },
  {
    "path": "yazi-vfs/src/provider/copier.rs",
    "content": "use std::{io::{self, SeekFrom}, sync::{Arc, atomic::{AtomicU64, Ordering}}};\n\nuse futures::{StreamExt, TryStreamExt};\nuse tokio::{io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, BufReader, BufWriter}, select, sync::{mpsc, oneshot}};\nuse yazi_fs::provider::{Attrs, FileBuilder};\nuse yazi_shared::url::{Url, UrlBuf};\n\nuse crate::provider::{self, Gate};\n\nconst BUF_SIZE: usize = 512 * 1024;\nconst PER_CHUNK: u64 = 8 * 1024 * 1024;\n\npub(super) async fn copy_impl(from: Url<'_>, to: Url<'_>, attrs: Attrs) -> io::Result<u64> {\n\tlet src = provider::open(from).await?;\n\tlet dist = provider::create(to).await?;\n\n\tlet mut reader = BufReader::with_capacity(BUF_SIZE, src);\n\tlet mut writer = BufWriter::with_capacity(BUF_SIZE, dist);\n\tlet written = tokio::io::copy(&mut reader, &mut writer).await?;\n\n\twriter.flush().await?;\n\twriter.get_ref().set_attrs(attrs).await.ok();\n\twriter.shutdown().await.ok();\n\tOk(written)\n}\n\npub(super) fn copy_with_progress_impl(\n\tfrom: UrlBuf,\n\tto: UrlBuf,\n\tattrs: Attrs,\n) -> mpsc::Receiver<io::Result<u64>> {\n\tlet acc = Arc::new(AtomicU64::new(0));\n\tlet (from, to) = (Arc::new(from), Arc::new(to));\n\tlet (prog_tx, prog_rx) = mpsc::channel(10);\n\tlet (done_tx, mut done_rx) = oneshot::channel();\n\n\tlet (acc_, prog_tx_) = (acc.clone(), prog_tx.clone());\n\ttokio::spawn(async move {\n\t\tlet init = async {\n\t\t\tlet src = provider::open(&*from).await?;\n\t\t\tlet cha = src.metadata().await?;\n\n\t\t\tlet dist = provider::create(&*to).await?;\n\t\t\tdist.set_len(cha.len).await?;\n\t\t\tOk((cha, Some(src), Some(dist)))\n\t\t};\n\n\t\tlet (cha, mut src, mut dist) = match init.await {\n\t\t\tOk(r) => r,\n\t\t\tErr(e) => {\n\t\t\t\tprog_tx_.send(Err(e)).await.ok();\n\t\t\t\tdone_tx.send(()).ok();\n\t\t\t\treturn;\n\t\t\t}\n\t\t};\n\n\t\tlet chunks = cha.len.div_ceil(PER_CHUNK);\n\t\tlet it = futures::stream::iter(0..chunks)\n\t\t\t.map(|i| {\n\t\t\t\tlet acc_ = acc_.clone();\n\t\t\t\tlet (from, to) = (from.clone(), to.clone());\n\t\t\t\tlet (src, dist) = (src.take(), dist.take());\n\t\t\t\tasync move {\n\t\t\t\t\tlet offset = i * PER_CHUNK;\n\t\t\t\t\tlet take = cha.len.saturating_sub(offset).min(PER_CHUNK);\n\n\t\t\t\t\tlet mut src = BufReader::with_capacity(BUF_SIZE, match src {\n\t\t\t\t\t\tSome(f) => f,\n\t\t\t\t\t\tNone => provider::open(&*from).await?,\n\t\t\t\t\t});\n\t\t\t\t\tlet mut dist = BufWriter::with_capacity(BUF_SIZE, match dist {\n\t\t\t\t\t\tSome(f) => f,\n\t\t\t\t\t\tNone => Gate::default().write(true).open(&*to).await?,\n\t\t\t\t\t});\n\n\t\t\t\t\tsrc.seek(SeekFrom::Start(offset)).await?;\n\t\t\t\t\tdist.seek(SeekFrom::Start(offset)).await?;\n\n\t\t\t\t\tlet mut src = src.take(take);\n\t\t\t\t\tlet mut buf = vec![0u8; 65536];\n\t\t\t\t\tlet mut copied = 0u64;\n\t\t\t\t\tloop {\n\t\t\t\t\t\tlet n = src.read(&mut buf).await?;\n\t\t\t\t\t\tif n == 0 {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tdist.write_all(&buf[..n]).await?;\n\t\t\t\t\t\tcopied += n as u64;\n\t\t\t\t\t\tacc_.fetch_add(n as u64, Ordering::SeqCst);\n\t\t\t\t\t}\n\t\t\t\t\tdist.flush().await?;\n\n\t\t\t\t\tif copied != take {\n\t\t\t\t\t\tErr(io::Error::other(format!(\n\t\t\t\t\t\t\t\"short copy for chunk {i}: copied {copied} bytes, expected {take}\"\n\t\t\t\t\t\t)))\n\t\t\t\t\t} else if i == chunks - 1 {\n\t\t\t\t\t\tOk(Some(dist.into_inner()))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdist.shutdown().await.ok();\n\t\t\t\t\t\tOk(None)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t\t.buffer_unordered(4)\n\t\t\t.try_fold(None, |first, file| async { Ok(first.or(file)) });\n\n\t\tlet mut result = select! {\n\t\t\tr = it => r,\n\t\t\t_ = prog_tx_.closed() => return,\n\t\t};\n\t\tdone_tx.send(()).ok();\n\n\t\tlet n = acc_.swap(0, Ordering::SeqCst);\n\t\tif n > 0 {\n\t\t\tprog_tx_.send(Ok(n)).await.ok();\n\t\t}\n\n\t\tif let Ok(Some(file)) = &mut result {\n\t\t\tfile.set_attrs(attrs).await.ok();\n\t\t\tfile.shutdown().await.ok();\n\t\t}\n\n\t\tif let Err(e) = result {\n\t\t\tprog_tx_.send(Err(e)).await.ok();\n\t\t} else {\n\t\t\tprog_tx_.send(Ok(0)).await.ok();\n\t\t}\n\t});\n\n\ttokio::spawn(async move {\n\t\tloop {\n\t\t\tselect! {\n\t\t\t\t_ = &mut done_rx => break,\n\t\t\t\t_ = prog_tx.closed() => break,\n\t\t\t\t_ = tokio::time::sleep(std::time::Duration::from_secs(3)) => {},\n\t\t\t}\n\n\t\t\tlet n = acc.swap(0, Ordering::SeqCst);\n\t\t\tif n > 0 {\n\t\t\t\tprog_tx.send(Ok(n)).await.ok();\n\t\t\t}\n\t\t}\n\t});\n\n\tprog_rx\n}\n"
  },
  {
    "path": "yazi-vfs/src/provider/dir_entry.rs",
    "content": "use std::io;\n\nuse yazi_fs::{cha::{Cha, ChaType}, provider::FileHolder};\nuse yazi_shared::{path::PathBufDyn, strand::StrandCow, url::UrlBuf};\n\npub enum DirEntry {\n\tLocal(yazi_fs::provider::local::DirEntry),\n\tSftp(super::sftp::DirEntry),\n}\n\nimpl FileHolder for DirEntry {\n\tasync fn file_type(&self) -> io::Result<ChaType> {\n\t\tmatch self {\n\t\t\tSelf::Local(entry) => entry.file_type().await,\n\t\t\tSelf::Sftp(entry) => entry.file_type().await,\n\t\t}\n\t}\n\n\tasync fn metadata(&self) -> io::Result<Cha> {\n\t\tmatch self {\n\t\t\tSelf::Local(entry) => entry.metadata().await,\n\t\t\tSelf::Sftp(entry) => entry.metadata().await,\n\t\t}\n\t}\n\n\tfn name(&self) -> StrandCow<'_> {\n\t\tmatch self {\n\t\t\tSelf::Local(entry) => entry.name(),\n\t\t\tSelf::Sftp(entry) => entry.name(),\n\t\t}\n\t}\n\n\tfn path(&self) -> PathBufDyn {\n\t\tmatch self {\n\t\t\tSelf::Local(entry) => entry.path(),\n\t\t\tSelf::Sftp(entry) => entry.path(),\n\t\t}\n\t}\n\n\tfn url(&self) -> UrlBuf {\n\t\tmatch self {\n\t\t\tSelf::Local(entry) => entry.url(),\n\t\t\tSelf::Sftp(entry) => entry.url(),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-vfs/src/provider/gate.rs",
    "content": "use std::io;\n\nuse yazi_fs::provider::{Attrs, FileBuilder};\nuse yazi_shared::{scheme::SchemeKind, url::AsUrl};\n\n#[derive(Clone, Copy, Default)]\npub struct Gate {\n\tpub(super) append:     bool,\n\tpub(super) attrs:      Attrs,\n\tpub(super) create:     bool,\n\tpub(super) create_new: bool,\n\tpub(super) read:       bool,\n\tpub(super) truncate:   bool,\n\tpub(super) write:      bool,\n}\n\nimpl FileBuilder for Gate {\n\ttype File = super::RwFile;\n\n\tfn append(&mut self, append: bool) -> &mut Self {\n\t\tself.append = append;\n\t\tself\n\t}\n\n\tfn attrs(&mut self, attrs: Attrs) -> &mut Self {\n\t\tself.attrs = attrs;\n\t\tself\n\t}\n\n\tfn create(&mut self, create: bool) -> &mut Self {\n\t\tself.create = create;\n\t\tself\n\t}\n\n\tfn create_new(&mut self, create_new: bool) -> &mut Self {\n\t\tself.create_new = create_new;\n\t\tself\n\t}\n\n\tasync fn open<U>(&self, url: U) -> io::Result<Self::File>\n\twhere\n\t\tU: AsUrl,\n\t{\n\t\tlet url = url.as_url();\n\t\tOk(match url.kind() {\n\t\t\tSchemeKind::Regular | SchemeKind::Search => {\n\t\t\t\tself.build::<yazi_fs::provider::local::Gate>().open(url).await?.into()\n\t\t\t}\n\t\t\tSchemeKind::Archive => {\n\t\t\t\tErr(io::Error::new(io::ErrorKind::Unsupported, \"Unsupported filesystem: archive\"))?\n\t\t\t}\n\t\t\tSchemeKind::Sftp => self.build::<super::sftp::Gate>().open(url).await?.into(),\n\t\t})\n\t}\n\n\tfn read(&mut self, read: bool) -> &mut Self {\n\t\tself.read = read;\n\t\tself\n\t}\n\n\tfn truncate(&mut self, truncate: bool) -> &mut Self {\n\t\tself.truncate = truncate;\n\t\tself\n\t}\n\n\tfn write(&mut self, write: bool) -> &mut Self {\n\t\tself.write = write;\n\t\tself\n\t}\n}\n\nimpl Gate {\n\tfn build<T: FileBuilder>(self) -> T {\n\t\tlet mut gate = T::default();\n\t\tif self.append {\n\t\t\tgate.append(true);\n\t\t}\n\t\tgate.attrs(self.attrs);\n\t\tif self.create {\n\t\t\tgate.create(true);\n\t\t}\n\t\tif self.create_new {\n\t\t\tgate.create_new(true);\n\t\t}\n\t\tif self.read {\n\t\t\tgate.read(true);\n\t\t}\n\t\tif self.truncate {\n\t\t\tgate.truncate(true);\n\t\t}\n\t\tif self.write {\n\t\t\tgate.write(true);\n\t\t}\n\t\tgate\n\t}\n}\n"
  },
  {
    "path": "yazi-vfs/src/provider/mod.rs",
    "content": "yazi_macro::mod_pub!(sftp);\n\nyazi_macro::mod_flat!(calculator copier dir_entry gate provider providers read_dir rw_file);\n\npub(super) fn init() { sftp::init(); }\n"
  },
  {
    "path": "yazi-vfs/src/provider/provider.rs",
    "content": "use std::io;\n\nuse tokio::sync::mpsc;\nuse yazi_fs::{cha::Cha, provider::{Attrs, Capabilities, Provider, local::Local}};\nuse yazi_shared::{path::PathBufDyn, strand::AsStrand, url::{AsUrl, Url, UrlBuf, UrlCow}};\n\nuse super::{Providers, ReadDir, RwFile};\n\npub async fn absolute<'a, U>(url: &'a U) -> io::Result<UrlCow<'a>>\nwhere\n\tU: AsUrl,\n{\n\tProviders::new(url.as_url()).await?.absolute().await\n}\n\npub async fn calculate<U>(url: U) -> io::Result<u64>\nwhere\n\tU: AsUrl,\n{\n\tlet url = url.as_url();\n\tif let Some(path) = url.as_local() {\n\t\tyazi_fs::provider::local::SizeCalculator::total(path).await\n\t} else {\n\t\tsuper::SizeCalculator::total(url).await\n\t}\n}\n\npub async fn canonicalize<U>(url: U) -> io::Result<UrlBuf>\nwhere\n\tU: AsUrl,\n{\n\tProviders::new(url.as_url()).await?.canonicalize().await\n}\n\npub async fn capabilities<U>(url: U) -> io::Result<Capabilities>\nwhere\n\tU: AsUrl,\n{\n\tOk(Providers::new(url.as_url()).await?.capabilities())\n}\n\npub async fn casefold<U>(url: U) -> io::Result<UrlBuf>\nwhere\n\tU: AsUrl,\n{\n\tProviders::new(url.as_url()).await?.casefold().await\n}\n\npub async fn copy<U, V>(from: U, to: V, attrs: Attrs) -> io::Result<u64>\nwhere\n\tU: AsUrl,\n\tV: AsUrl,\n{\n\tlet (from, to) = (from.as_url(), to.as_url());\n\n\tmatch (from.kind().is_local(), to.kind().is_local()) {\n\t\t(true, true) => Local::new(from).await?.copy(to.loc(), attrs).await,\n\t\t(false, false) if from.scheme().covariant(to.scheme()) => {\n\t\t\tProviders::new(from).await?.copy(to.loc(), attrs).await\n\t\t}\n\t\t(true, false) | (false, true) | (false, false) => super::copy_impl(from, to, attrs).await,\n\t}\n}\n\npub async fn copy_with_progress<U, V, A>(\n\tfrom: U,\n\tto: V,\n\tattrs: A,\n) -> io::Result<mpsc::Receiver<Result<u64, io::Error>>>\nwhere\n\tU: AsUrl,\n\tV: AsUrl,\n\tA: Into<Attrs>,\n{\n\tlet (from, to) = (from.as_url(), to.as_url());\n\n\tmatch (from.kind().is_local(), to.kind().is_local()) {\n\t\t(true, true) => Local::new(from).await?.copy_with_progress(to.loc(), attrs),\n\t\t(false, false) if from.scheme().covariant(to.scheme()) => {\n\t\t\tProviders::new(from).await?.copy_with_progress(to.loc(), attrs)\n\t\t}\n\t\t(true, false) | (false, true) | (false, false) => {\n\t\t\tOk(super::copy_with_progress_impl(from.to_owned(), to.to_owned(), attrs.into()))\n\t\t}\n\t}\n}\n\npub async fn create<U>(url: U) -> io::Result<RwFile>\nwhere\n\tU: AsUrl,\n{\n\tProviders::new(url.as_url()).await?.create().await\n}\n\npub async fn create_dir<U>(url: U) -> io::Result<()>\nwhere\n\tU: AsUrl,\n{\n\tProviders::new(url.as_url()).await?.create_dir().await\n}\n\npub async fn create_dir_all<U>(url: U) -> io::Result<()>\nwhere\n\tU: AsUrl,\n{\n\tProviders::new(url.as_url()).await?.create_dir_all().await\n}\n\npub async fn create_new<U>(url: U) -> io::Result<RwFile>\nwhere\n\tU: AsUrl,\n{\n\tProviders::new(url.as_url()).await?.create_new().await\n}\n\npub async fn hard_link<U, V>(original: U, link: V) -> io::Result<()>\nwhere\n\tU: AsUrl,\n\tV: AsUrl,\n{\n\tlet (original, link) = (original.as_url(), link.as_url());\n\tif original.scheme().covariant(link.scheme()) {\n\t\tProviders::new(original).await?.hard_link(link.loc()).await\n\t} else {\n\t\tErr(io::Error::from(io::ErrorKind::CrossesDevices))\n\t}\n}\n\npub async fn identical<U, V>(a: U, b: V) -> io::Result<bool>\nwhere\n\tU: AsUrl,\n\tV: AsUrl,\n{\n\tif let (Some(a), Some(b)) = (a.as_url().as_local(), b.as_url().as_local()) {\n\t\tyazi_fs::provider::local::identical(a, b).await\n\t} else {\n\t\tErr(io::Error::new(io::ErrorKind::Unsupported, \"Unsupported filesystem\"))\n\t}\n}\n\npub async fn metadata<U>(url: U) -> io::Result<Cha>\nwhere\n\tU: AsUrl,\n{\n\tProviders::new(url.as_url()).await?.metadata().await\n}\n\npub async fn must_identical<U, V>(a: U, b: V) -> bool\nwhere\n\tU: AsUrl,\n\tV: AsUrl,\n{\n\tidentical(a, b).await.unwrap_or(false)\n}\n\npub async fn open<U>(url: U) -> io::Result<RwFile>\nwhere\n\tU: AsUrl,\n{\n\tProviders::new(url.as_url()).await?.open().await\n}\n\npub async fn read_dir<U>(url: U) -> io::Result<ReadDir>\nwhere\n\tU: AsUrl,\n{\n\tProviders::new(url.as_url()).await?.read_dir().await\n}\n\npub async fn read_link<U>(url: U) -> io::Result<PathBufDyn>\nwhere\n\tU: AsUrl,\n{\n\tProviders::new(url.as_url()).await?.read_link().await\n}\n\npub async fn remove_dir<U>(url: U) -> io::Result<()>\nwhere\n\tU: AsUrl,\n{\n\tProviders::new(url.as_url()).await?.remove_dir().await\n}\n\npub async fn remove_dir_all<U>(url: U) -> io::Result<()>\nwhere\n\tU: AsUrl,\n{\n\tProviders::new(url.as_url()).await?.remove_dir_all().await\n}\n\npub async fn remove_dir_clean<U>(url: U) -> io::Result<()>\nwhere\n\tU: AsUrl,\n{\n\tProviders::new(url.as_url()).await?.remove_dir_clean().await\n}\n\npub async fn remove_file<U>(url: U) -> io::Result<()>\nwhere\n\tU: AsUrl,\n{\n\tProviders::new(url.as_url()).await?.remove_file().await\n}\n\npub async fn rename<U, V>(from: U, to: V) -> io::Result<()>\nwhere\n\tU: AsUrl,\n\tV: AsUrl,\n{\n\tlet (from, to) = (from.as_url(), to.as_url());\n\tif from.scheme().covariant(to.scheme()) {\n\t\tProviders::new(from).await?.rename(to.loc()).await\n\t} else {\n\t\tErr(io::Error::from(io::ErrorKind::CrossesDevices))\n\t}\n}\n\npub async fn symlink<U, S, F>(link: U, original: S, is_dir: F) -> io::Result<()>\nwhere\n\tU: AsUrl,\n\tS: AsStrand,\n\tF: AsyncFnOnce() -> io::Result<bool>,\n{\n\tProviders::new(link.as_url()).await?.symlink(original, is_dir).await\n}\n\npub async fn symlink_dir<U, S>(link: U, original: S) -> io::Result<()>\nwhere\n\tU: AsUrl,\n\tS: AsStrand,\n{\n\tProviders::new(link.as_url()).await?.symlink_dir(original).await\n}\n\npub async fn symlink_file<U, S>(link: U, original: S) -> io::Result<()>\nwhere\n\tU: AsUrl,\n\tS: AsStrand,\n{\n\tProviders::new(link.as_url()).await?.symlink_file(original).await\n}\n\npub async fn symlink_metadata<U>(url: U) -> io::Result<Cha>\nwhere\n\tU: AsUrl,\n{\n\tProviders::new(url.as_url()).await?.symlink_metadata().await\n}\n\npub async fn trash<U>(url: U) -> io::Result<()>\nwhere\n\tU: AsUrl,\n{\n\tProviders::new(url.as_url()).await?.trash().await\n}\n\npub fn try_absolute<'a, U>(url: U) -> Option<UrlCow<'a>>\nwhere\n\tU: Into<UrlCow<'a>>,\n{\n\tlet url = url.into();\n\tmatch url.as_url() {\n\t\tUrl::Regular(_) | Url::Search { .. } => yazi_fs::provider::local::try_absolute(url),\n\t\tUrl::Archive { .. } => None, // TODO\n\t\tUrl::Sftp { .. } => crate::provider::sftp::try_absolute(url),\n\t}\n}\n\npub async fn write<U, C>(url: U, contents: C) -> io::Result<()>\nwhere\n\tU: AsUrl,\n\tC: AsRef<[u8]>,\n{\n\tProviders::new(url.as_url()).await?.write(contents).await\n}\n"
  },
  {
    "path": "yazi-vfs/src/provider/providers.rs",
    "content": "use std::io;\n\nuse tokio::sync::mpsc;\nuse yazi_fs::{cha::Cha, provider::{Attrs, Capabilities, Provider}};\nuse yazi_shared::{path::{AsPath, PathBufDyn}, strand::AsStrand, url::{Url, UrlBuf, UrlCow}};\n\n#[derive(Clone)]\npub(super) enum Providers<'a> {\n\tLocal(yazi_fs::provider::local::Local<'a>),\n\tSftp(super::sftp::Sftp<'a>),\n}\n\nimpl<'a> Provider for Providers<'a> {\n\ttype File = super::RwFile;\n\ttype Gate = super::Gate;\n\ttype Me<'b> = Providers<'b>;\n\ttype ReadDir = super::ReadDir;\n\ttype UrlCow = UrlCow<'a>;\n\n\tasync fn absolute(&self) -> io::Result<Self::UrlCow> {\n\t\tmatch self {\n\t\t\tSelf::Local(p) => p.absolute().await,\n\t\t\tSelf::Sftp(p) => p.absolute().await,\n\t\t}\n\t}\n\n\tasync fn canonicalize(&self) -> io::Result<UrlBuf> {\n\t\tmatch self {\n\t\t\tSelf::Local(p) => p.canonicalize().await,\n\t\t\tSelf::Sftp(p) => p.canonicalize().await,\n\t\t}\n\t}\n\n\tfn capabilities(&self) -> Capabilities {\n\t\tmatch self {\n\t\t\tSelf::Local(p) => p.capabilities(),\n\t\t\tSelf::Sftp(p) => p.capabilities(),\n\t\t}\n\t}\n\n\tasync fn casefold(&self) -> io::Result<UrlBuf> {\n\t\tmatch self {\n\t\t\tSelf::Local(p) => p.casefold().await,\n\t\t\tSelf::Sftp(p) => p.casefold().await,\n\t\t}\n\t}\n\n\tasync fn copy<P>(&self, to: P, attrs: Attrs) -> io::Result<u64>\n\twhere\n\t\tP: AsPath,\n\t{\n\t\tmatch self {\n\t\t\tSelf::Local(p) => p.copy(to, attrs).await,\n\t\t\tSelf::Sftp(p) => p.copy(to, attrs).await,\n\t\t}\n\t}\n\n\tfn copy_with_progress<P, A>(&self, to: P, attrs: A) -> io::Result<mpsc::Receiver<io::Result<u64>>>\n\twhere\n\t\tP: AsPath,\n\t\tA: Into<Attrs>,\n\t{\n\t\tmatch self {\n\t\t\tSelf::Local(p) => p.copy_with_progress(to, attrs),\n\t\t\tSelf::Sftp(p) => p.copy_with_progress(to, attrs),\n\t\t}\n\t}\n\n\tasync fn create(&self) -> io::Result<Self::File> {\n\t\tOk(match self {\n\t\t\tSelf::Local(p) => p.create().await?.into(),\n\t\t\tSelf::Sftp(p) => p.create().await?.into(),\n\t\t})\n\t}\n\n\tasync fn create_dir(&self) -> io::Result<()> {\n\t\tmatch self {\n\t\t\tSelf::Local(p) => p.create_dir().await,\n\t\t\tSelf::Sftp(p) => p.create_dir().await,\n\t\t}\n\t}\n\n\tasync fn create_dir_all(&self) -> io::Result<()> {\n\t\tmatch self {\n\t\t\tSelf::Local(p) => p.create_dir_all().await,\n\t\t\tSelf::Sftp(p) => p.create_dir_all().await,\n\t\t}\n\t}\n\n\tasync fn create_new(&self) -> io::Result<Self::File> {\n\t\tOk(match self {\n\t\t\tSelf::Local(p) => p.create_new().await?.into(),\n\t\t\tSelf::Sftp(p) => p.create_new().await?.into(),\n\t\t})\n\t}\n\n\tasync fn hard_link<P>(&self, to: P) -> io::Result<()>\n\twhere\n\t\tP: AsPath,\n\t{\n\t\tmatch self {\n\t\t\tSelf::Local(p) => p.hard_link(to).await,\n\t\t\tSelf::Sftp(p) => p.hard_link(to).await,\n\t\t}\n\t}\n\n\tasync fn metadata(&self) -> io::Result<Cha> {\n\t\tmatch self {\n\t\t\tSelf::Local(p) => p.metadata().await,\n\t\t\tSelf::Sftp(p) => p.metadata().await,\n\t\t}\n\t}\n\n\tasync fn new<'b>(url: Url<'b>) -> io::Result<Self::Me<'b>> {\n\t\tuse yazi_shared::scheme::SchemeKind as K;\n\n\t\tOk(match url.kind() {\n\t\t\tK::Regular | K::Search => Self::Me::Local(yazi_fs::provider::local::Local::new(url).await?),\n\t\t\tK::Archive => {\n\t\t\t\tErr(io::Error::new(io::ErrorKind::Unsupported, \"Unsupported filesystem: archive\"))?\n\t\t\t}\n\t\t\tK::Sftp => Self::Me::Sftp(super::sftp::Sftp::new(url).await?),\n\t\t})\n\t}\n\n\tasync fn open(&self) -> io::Result<Self::File> {\n\t\tOk(match self {\n\t\t\tSelf::Local(p) => p.open().await?.into(),\n\t\t\tSelf::Sftp(p) => p.open().await?.into(),\n\t\t})\n\t}\n\n\tasync fn read_dir(self) -> io::Result<Self::ReadDir> {\n\t\tOk(match self {\n\t\t\tSelf::Local(p) => Self::ReadDir::Local(p.read_dir().await?),\n\t\t\tSelf::Sftp(p) => Self::ReadDir::Sftp(p.read_dir().await?),\n\t\t})\n\t}\n\n\tasync fn read_link(&self) -> io::Result<PathBufDyn> {\n\t\tmatch self {\n\t\t\tSelf::Local(p) => p.read_link().await,\n\t\t\tSelf::Sftp(p) => p.read_link().await,\n\t\t}\n\t}\n\n\tasync fn remove_dir(&self) -> io::Result<()> {\n\t\tmatch self {\n\t\t\tSelf::Local(p) => p.remove_dir().await,\n\t\t\tSelf::Sftp(p) => p.remove_dir().await,\n\t\t}\n\t}\n\n\tasync fn remove_dir_all(&self) -> io::Result<()> {\n\t\tmatch self {\n\t\t\tSelf::Local(p) => p.remove_dir_all().await,\n\t\t\tSelf::Sftp(p) => p.remove_dir_all().await,\n\t\t}\n\t}\n\n\tasync fn remove_file(&self) -> io::Result<()> {\n\t\tmatch self {\n\t\t\tSelf::Local(p) => p.remove_file().await,\n\t\t\tSelf::Sftp(p) => p.remove_file().await,\n\t\t}\n\t}\n\n\tasync fn rename<P>(&self, to: P) -> io::Result<()>\n\twhere\n\t\tP: AsPath,\n\t{\n\t\tmatch self {\n\t\t\tSelf::Local(p) => p.rename(to).await,\n\t\t\tSelf::Sftp(p) => p.rename(to).await,\n\t\t}\n\t}\n\n\tasync fn symlink<S, F>(&self, original: S, is_dir: F) -> io::Result<()>\n\twhere\n\t\tS: AsStrand,\n\t\tF: AsyncFnOnce() -> io::Result<bool>,\n\t{\n\t\tmatch self {\n\t\t\tSelf::Local(p) => p.symlink(original, is_dir).await,\n\t\t\tSelf::Sftp(p) => p.symlink(original, is_dir).await,\n\t\t}\n\t}\n\n\tasync fn symlink_dir<S>(&self, original: S) -> io::Result<()>\n\twhere\n\t\tS: AsStrand,\n\t{\n\t\tmatch self {\n\t\t\tSelf::Local(p) => p.symlink_dir(original).await,\n\t\t\tSelf::Sftp(p) => p.symlink_dir(original).await,\n\t\t}\n\t}\n\n\tasync fn symlink_file<S>(&self, original: S) -> io::Result<()>\n\twhere\n\t\tS: AsStrand,\n\t{\n\t\tmatch self {\n\t\t\tSelf::Local(p) => p.symlink_file(original).await,\n\t\t\tSelf::Sftp(p) => p.symlink_file(original).await,\n\t\t}\n\t}\n\n\tasync fn symlink_metadata(&self) -> io::Result<Cha> {\n\t\tmatch self {\n\t\t\tSelf::Local(p) => p.symlink_metadata().await,\n\t\t\tSelf::Sftp(p) => p.symlink_metadata().await,\n\t\t}\n\t}\n\n\tasync fn trash(&self) -> io::Result<()> {\n\t\tmatch self {\n\t\t\tSelf::Local(p) => p.trash().await,\n\t\t\tSelf::Sftp(p) => p.trash().await,\n\t\t}\n\t}\n\n\tfn url(&self) -> Url<'_> {\n\t\tmatch self {\n\t\t\tSelf::Local(p) => p.url(),\n\t\t\tSelf::Sftp(p) => p.url(),\n\t\t}\n\t}\n\n\tasync fn write<C>(&self, contents: C) -> io::Result<()>\n\twhere\n\t\tC: AsRef<[u8]>,\n\t{\n\t\tmatch self {\n\t\t\tSelf::Local(p) => p.write(contents).await,\n\t\t\tSelf::Sftp(p) => p.write(contents).await,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-vfs/src/provider/read_dir.rs",
    "content": "use std::io;\n\nuse yazi_fs::provider::DirReader;\n\npub enum ReadDir {\n\tLocal(yazi_fs::provider::local::ReadDir),\n\tSftp(super::sftp::ReadDir),\n}\n\nimpl DirReader for ReadDir {\n\ttype Entry = super::DirEntry;\n\n\tasync fn next(&mut self) -> io::Result<Option<Self::Entry>> {\n\t\tOk(match self {\n\t\t\tSelf::Local(reader) => reader.next().await?.map(Self::Entry::Local),\n\t\t\tSelf::Sftp(reader) => reader.next().await?.map(Self::Entry::Sftp),\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-vfs/src/provider/rw_file.rs",
    "content": "use std::{io, pin::Pin};\n\nuse tokio::io::{AsyncRead, AsyncSeek, AsyncWrite};\nuse yazi_fs::provider::Attrs;\n\npub enum RwFile {\n\tTokio(tokio::fs::File),\n\tSftp(Box<yazi_sftp::fs::File>),\n}\n\nimpl From<tokio::fs::File> for RwFile {\n\tfn from(f: tokio::fs::File) -> Self { Self::Tokio(f) }\n}\n\nimpl From<yazi_sftp::fs::File> for RwFile {\n\tfn from(f: yazi_sftp::fs::File) -> Self { Self::Sftp(Box::new(f)) }\n}\n\nimpl RwFile {\n\t// FIXME: path\n\tpub async fn metadata(&self) -> io::Result<yazi_fs::cha::Cha> {\n\t\tOk(match self {\n\t\t\tSelf::Tokio(f) => yazi_fs::cha::Cha::new(\"// FIXME\", f.metadata().await?),\n\t\t\tSelf::Sftp(f) => super::sftp::Cha::try_from((\"// FIXME\".as_bytes(), &f.fstat().await?))?.0,\n\t\t})\n\t}\n\n\tpub async fn set_attrs(&self, attrs: Attrs) -> io::Result<()> {\n\t\tmatch self {\n\t\t\tSelf::Tokio(f) => {\n\t\t\t\tlet (perm, times) = (attrs.try_into(), attrs.try_into());\n\t\t\t\tif perm.is_err() && times.is_err() {\n\t\t\t\t\treturn Ok(());\n\t\t\t\t}\n\n\t\t\t\tlet std = f.try_clone().await?.into_std().await;\n\t\t\t\ttokio::task::spawn_blocking(move || {\n\t\t\t\t\tperm.map(|p| std.set_permissions(p)).ok();\n\t\t\t\t\ttimes.map(|t| std.set_times(t)).ok();\n\t\t\t\t})\n\t\t\t\t.await?;\n\t\t\t}\n\t\t\tSelf::Sftp(f) => {\n\t\t\t\tif let Ok(attrs) = super::sftp::Attrs(attrs).try_into() {\n\t\t\t\t\tf.fsetstat(&attrs).await?;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tOk(())\n\t}\n\n\tpub async fn set_len(&self, size: u64) -> io::Result<()> {\n\t\tOk(match self {\n\t\t\tSelf::Tokio(f) => f.set_len(size).await?,\n\t\t\tSelf::Sftp(f) => {\n\t\t\t\tf.fsetstat(&yazi_sftp::fs::Attrs { size: Some(size), ..Default::default() }).await?\n\t\t\t}\n\t\t})\n\t}\n}\n\nimpl AsyncRead for RwFile {\n\t#[inline]\n\tfn poll_read(\n\t\tmut self: Pin<&mut Self>,\n\t\tcx: &mut std::task::Context<'_>,\n\t\tbuf: &mut tokio::io::ReadBuf<'_>,\n\t) -> std::task::Poll<io::Result<()>> {\n\t\tmatch &mut *self {\n\t\t\tSelf::Tokio(f) => Pin::new(f).poll_read(cx, buf),\n\t\t\tSelf::Sftp(f) => Pin::new(f).poll_read(cx, buf),\n\t\t}\n\t}\n}\n\nimpl AsyncSeek for RwFile {\n\t#[inline]\n\tfn start_seek(mut self: Pin<&mut Self>, position: io::SeekFrom) -> io::Result<()> {\n\t\tmatch &mut *self {\n\t\t\tSelf::Tokio(f) => Pin::new(f).start_seek(position),\n\t\t\tSelf::Sftp(f) => Pin::new(f).start_seek(position),\n\t\t}\n\t}\n\n\t#[inline]\n\tfn poll_complete(\n\t\tmut self: Pin<&mut Self>,\n\t\tcx: &mut std::task::Context<'_>,\n\t) -> std::task::Poll<io::Result<u64>> {\n\t\tmatch &mut *self {\n\t\t\tSelf::Tokio(f) => Pin::new(f).poll_complete(cx),\n\t\t\tSelf::Sftp(f) => Pin::new(f).poll_complete(cx),\n\t\t}\n\t}\n}\n\nimpl AsyncWrite for RwFile {\n\t#[inline]\n\tfn poll_write(\n\t\tmut self: Pin<&mut Self>,\n\t\tcx: &mut std::task::Context<'_>,\n\t\tbuf: &[u8],\n\t) -> std::task::Poll<Result<usize, io::Error>> {\n\t\tmatch &mut *self {\n\t\t\tSelf::Tokio(f) => Pin::new(f).poll_write(cx, buf),\n\t\t\tSelf::Sftp(f) => Pin::new(f).poll_write(cx, buf),\n\t\t}\n\t}\n\n\t#[inline]\n\tfn poll_flush(\n\t\tmut self: Pin<&mut Self>,\n\t\tcx: &mut std::task::Context<'_>,\n\t) -> std::task::Poll<Result<(), io::Error>> {\n\t\tmatch &mut *self {\n\t\t\tSelf::Tokio(f) => Pin::new(f).poll_flush(cx),\n\t\t\tSelf::Sftp(f) => Pin::new(f).poll_flush(cx),\n\t\t}\n\t}\n\n\t#[inline]\n\tfn poll_shutdown(\n\t\tmut self: Pin<&mut Self>,\n\t\tcx: &mut std::task::Context<'_>,\n\t) -> std::task::Poll<Result<(), io::Error>> {\n\t\tmatch &mut *self {\n\t\t\tSelf::Tokio(f) => Pin::new(f).poll_shutdown(cx),\n\t\t\tSelf::Sftp(f) => Pin::new(f).poll_shutdown(cx),\n\t\t}\n\t}\n\n\t#[inline]\n\tfn poll_write_vectored(\n\t\tmut self: Pin<&mut Self>,\n\t\tcx: &mut std::task::Context<'_>,\n\t\tbufs: &[io::IoSlice<'_>],\n\t) -> std::task::Poll<Result<usize, io::Error>> {\n\t\tmatch &mut *self {\n\t\t\tSelf::Tokio(f) => Pin::new(f).poll_write_vectored(cx, bufs),\n\t\t\tSelf::Sftp(f) => Pin::new(f).poll_write_vectored(cx, bufs),\n\t\t}\n\t}\n\n\t#[inline]\n\tfn is_write_vectored(&self) -> bool {\n\t\tmatch self {\n\t\t\tSelf::Tokio(f) => f.is_write_vectored(),\n\t\t\tSelf::Sftp(f) => f.is_write_vectored(),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-vfs/src/provider/sftp/absolute.rs",
    "content": "use yazi_fs::CWD;\nuse yazi_shared::url::{UrlCow, UrlLike};\n\npub fn try_absolute<'a, U>(url: U) -> Option<UrlCow<'a>>\nwhere\n\tU: Into<UrlCow<'a>>,\n{\n\ttry_absolute_impl(url.into())\n}\n\nfn try_absolute_impl<'a>(url: UrlCow<'a>) -> Option<UrlCow<'a>> {\n\tif url.is_absolute() {\n\t\tSome(url)\n\t} else if let cwd = CWD.load()\n\t\t&& cwd.scheme().covariant(url.scheme())\n\t{\n\t\tSome(cwd.try_join(url.loc()).ok()?.into())\n\t} else {\n\t\tNone\n\t}\n}\n"
  },
  {
    "path": "yazi-vfs/src/provider/sftp/conn.rs",
    "content": "use std::{io, sync::Arc, time::{Duration, SystemTime}};\n\nuse russh::keys::PrivateKeyWithHashAlg;\nuse yazi_config::vfs::ServiceSftp;\nuse yazi_fs::provider::local::Local;\n\n#[derive(Clone, Copy)]\npub(super) struct Conn {\n\tpub(super) name:   &'static str,\n\tpub(super) config: &'static ServiceSftp,\n}\n\nmacro_rules! cfg_err {\n\t($($args:tt)*) => {\n\t\trussh::Error::InvalidConfig(format!($($args)*))\n\t};\n}\n\nimpl russh::client::Handler for Conn {\n\ttype Error = russh::Error;\n\n\tasync fn check_server_key(\n\t\t&mut self,\n\t\t_server_public_key: &russh::keys::PublicKey,\n\t) -> Result<bool, Self::Error> {\n\t\tOk(true)\n\t}\n}\n\nimpl deadpool::managed::Manager for Conn {\n\ttype Error = io::Error;\n\ttype Type = yazi_sftp::Operator;\n\n\tasync fn create(&self) -> Result<Self::Type, Self::Error> {\n\t\tlet channel = self.connect().await.map_err(|e| {\n\t\t\tio::Error::other(format!(\"Failed to connect to SFTP server `{}`: {e}\", self.name))\n\t\t})?;\n\n\t\tlet mut op = yazi_sftp::Operator::make(channel.into_stream());\n\t\top.init().await?;\n\t\tOk(op)\n\t}\n\n\tasync fn recycle(\n\t\t&self,\n\t\tobj: &mut Self::Type,\n\t\t_metrics: &deadpool::managed::Metrics,\n\t) -> deadpool::managed::RecycleResult<Self::Error> {\n\t\tif obj.is_closed() {\n\t\t\tErr(deadpool::managed::RecycleError::Message(\"Channel closed\".into()))\n\t\t} else {\n\t\t\tOk(())\n\t\t}\n\t}\n}\n\nimpl Conn {\n\tpub(super) async fn roll(self) -> io::Result<deadpool::managed::Object<Self>> {\n\t\tuse deadpool::managed::PoolError;\n\n\t\tlet pool = *super::CONN.lock().entry(self.config).or_insert_with(|| {\n\t\t\tBox::leak(Box::new(\n\t\t\t\tdeadpool::managed::Pool::builder(self)\n\t\t\t\t\t.runtime(deadpool::Runtime::Tokio1)\n\t\t\t\t\t.max_size(8)\n\t\t\t\t\t.create_timeout(Some(Duration::from_secs(45)))\n\t\t\t\t\t.build()\n\t\t\t\t\t.unwrap(),\n\t\t\t))\n\t\t});\n\n\t\tpool.get().await.map_err(|e| match e {\n\t\t\tPoolError::Timeout(_) => io::Error::new(io::ErrorKind::TimedOut, e.to_string()),\n\t\t\tPoolError::Backend(e) => e,\n\t\t\tPoolError::Closed | PoolError::NoRuntimeSpecified | PoolError::PostCreateHook(_) => {\n\t\t\t\tio::Error::other(e.to_string())\n\t\t\t}\n\t\t})\n\t}\n\n\tasync fn connect(self) -> Result<russh::Channel<russh::client::Msg>, russh::Error> {\n\t\tlet pref = Arc::new(russh::client::Config {\n\t\t\tinactivity_timeout: Some(std::time::Duration::from_secs(60)),\n\t\t\tkeepalive_interval: Some(std::time::Duration::from_secs(10)),\n\t\t\t..Default::default()\n\t\t});\n\n\t\tlet session = if self.config.password.is_some() {\n\t\t\tself.connect_by_password(pref).await\n\t\t} else if !self.config.key_file.as_os_str().is_empty()\n\t\t\t&& !self.config.cert_file.as_os_str().is_empty()\n\t\t{\n\t\t\tself.connect_by_key_and_cert(pref).await\n\t\t} else if !self.config.key_file.as_os_str().is_empty() {\n\t\t\tself.connect_by_key(pref).await\n\t\t} else {\n\t\t\tself.connect_by_agent(pref).await\n\t\t}?;\n\n\t\tlet channel = session.channel_open_session().await?;\n\t\tchannel.request_subsystem(true, \"sftp\").await?;\n\t\tOk(channel)\n\t}\n\n\tasync fn connect_by_password(\n\t\tself,\n\t\tpref: Arc<russh::client::Config>,\n\t) -> Result<russh::client::Handle<Self>, russh::Error> {\n\t\tlet Some(password) = &self.config.password else {\n\t\t\treturn Err(cfg_err!(\"Password not provided\"));\n\t\t};\n\n\t\tlet mut session =\n\t\t\trussh::client::connect(pref, (self.config.host.as_str(), self.config.port), self).await?;\n\n\t\tif session.authenticate_password(&self.config.user, password).await?.success() {\n\t\t\tOk(session)\n\t\t} else {\n\t\t\tErr(cfg_err!(\"Password authentication failed\"))\n\t\t}\n\t}\n\n\tasync fn connect_by_key(\n\t\tself,\n\t\tpref: Arc<russh::client::Config>,\n\t) -> Result<russh::client::Handle<Self>, russh::Error> {\n\t\tlet key_file = &self.config.key_file;\n\t\tif key_file.as_os_str().is_empty() {\n\t\t\treturn Err(cfg_err!(\"Key file not provided\"));\n\t\t};\n\n\t\tlet key = Local::regular(key_file)\n\t\t\t.read_to_string()\n\t\t\t.await\n\t\t\t.map_err(|e| cfg_err!(\"Failed to read key file: {e}\"))?;\n\t\tlet key = russh::keys::decode_secret_key(&key, self.config.key_passphrase.as_deref())?;\n\n\t\tlet mut session =\n\t\t\trussh::client::connect(pref, (self.config.host.as_str(), self.config.port), self).await?;\n\n\t\tlet result = session\n\t\t\t.authenticate_publickey(\n\t\t\t\t&self.config.user,\n\t\t\t\tPrivateKeyWithHashAlg::new(\n\t\t\t\t\tArc::new(key),\n\t\t\t\t\tsession.best_supported_rsa_hash().await?.flatten(),\n\t\t\t\t),\n\t\t\t)\n\t\t\t.await?;\n\n\t\tif result.success() { Ok(session) } else { Err(cfg_err!(\"Public key authentication failed\")) }\n\t}\n\n\tasync fn connect_by_key_and_cert(\n\t\tself,\n\t\tpref: Arc<russh::client::Config>,\n\t) -> Result<russh::client::Handle<Self>, russh::Error> {\n\t\tlet key_file = &self.config.key_file;\n\t\tif key_file.as_os_str().is_empty() {\n\t\t\treturn Err(cfg_err!(\"Key file not provided\"));\n\t\t};\n\n\t\tlet cert_file = &self.config.cert_file;\n\t\tif cert_file.as_os_str().is_empty() {\n\t\t\treturn Err(cfg_err!(\"Cert file not provided\"));\n\t\t};\n\n\t\t// Decode the key and cert files\n\t\tlet key = Local::regular(key_file)\n\t\t\t.read_to_string()\n\t\t\t.await\n\t\t\t.map_err(|e| cfg_err!(\"Failed to read key file: {e}\"))?;\n\t\tlet key = russh::keys::decode_secret_key(&key, self.config.key_passphrase.as_deref())?;\n\n\t\tlet cert = Local::regular(cert_file)\n\t\t\t.read_to_string()\n\t\t\t.await\n\t\t\t.map_err(|e| cfg_err!(\"Failed to read cert file: {e}\"))?;\n\t\tlet cert = russh::keys::Certificate::from_openssh(&cert)?;\n\n\t\t// Verify the certificate\n\t\tif !self.config.no_cert_verify {\n\t\t\tcert\n\t\t\t\t.verify_signature()\n\t\t\t\t.map_err(|e| cfg_err!(\"Certificate signature verification failed: {e}\"))?;\n\n\t\t\tlet now: chrono::DateTime<chrono::Local> = SystemTime::now().into();\n\t\t\tlet start: chrono::DateTime<chrono::Local> = cert.valid_after_time().into();\n\t\t\tlet end: chrono::DateTime<chrono::Local> = cert.valid_before_time().into();\n\t\t\tif now < start || now > end {\n\t\t\t\treturn Err(cfg_err!(\n\t\t\t\t\t\"Certificate is out of the validity range of '{}' to '{}'\",\n\t\t\t\t\tstart.to_rfc2822(),\n\t\t\t\t\tend.to_rfc2822()\n\t\t\t\t));\n\t\t\t}\n\t\t}\n\n\t\tlet mut session =\n\t\t\trussh::client::connect(pref, (self.config.host.as_str(), self.config.port), self).await?;\n\n\t\tif session.authenticate_openssh_cert(&self.config.user, Arc::new(key), cert).await?.success() {\n\t\t\tOk(session)\n\t\t} else {\n\t\t\tErr(cfg_err!(\"Public key with certificate authentication failed\"))\n\t\t}\n\t}\n\n\tasync fn connect_by_agent(\n\t\tself,\n\t\tpref: Arc<russh::client::Config>,\n\t) -> Result<russh::client::Handle<Self>, russh::Error> {\n\t\tlet identity_agent = &self.config.identity_agent;\n\t\tif identity_agent.as_os_str().is_empty() {\n\t\t\treturn Err(cfg_err!(\"Identity agent not provided\"));\n\t\t};\n\n\t\t#[cfg(unix)]\n\t\tlet mut agent = russh::keys::agent::client::AgentClient::connect_uds(identity_agent).await?;\n\t\t#[cfg(windows)]\n\t\tlet mut agent =\n\t\t\trussh::keys::agent::client::AgentClient::connect_named_pipe(identity_agent).await?;\n\n\t\tlet keys = agent.request_identities().await?;\n\t\tif keys.is_empty() {\n\t\t\treturn Err(cfg_err!(\"No keys found in SSH agent\"));\n\t\t}\n\n\t\tlet mut session =\n\t\t\trussh::client::connect(pref, (self.config.host.as_str(), self.config.port), self).await?;\n\n\t\tfor key in keys {\n\t\t\tlet hash_alg = session.best_supported_rsa_hash().await?.flatten();\n\t\t\tmatch session.authenticate_publickey_with(&self.config.user, key, hash_alg, &mut agent).await\n\t\t\t{\n\t\t\t\tOk(result) if result.success() => return Ok(session),\n\t\t\t\tOk(result) => tracing::debug!(\"Identity agent authentication failed: {result:?}\"),\n\t\t\t\tErr(e) => tracing::error!(\"Identity agent authentication error: {e}\"),\n\t\t\t}\n\t\t}\n\n\t\tErr(cfg_err!(\"Public key authentication via agent failed\"))\n\t}\n}\n"
  },
  {
    "path": "yazi-vfs/src/provider/sftp/gate.rs",
    "content": "use std::io;\n\nuse yazi_config::vfs::{ServiceSftp, Vfs};\nuse yazi_fs::provider::{Attrs, FileBuilder};\nuse yazi_sftp::fs::Flags;\nuse yazi_shared::url::{AsUrl, Url};\n\nuse crate::provider::sftp::Conn;\n\n#[derive(Clone, Copy, Default)]\npub struct Gate(crate::provider::Gate);\n\nimpl From<Gate> for Flags {\n\tfn from(Gate(g): Gate) -> Self {\n\t\tlet mut flags = Self::empty();\n\t\tif g.append {\n\t\t\tflags |= Self::APPEND;\n\t\t}\n\t\tif g.create {\n\t\t\tflags |= Self::CREATE;\n\t\t}\n\t\tif g.create_new {\n\t\t\tflags |= Self::CREATE | Self::EXCLUDE;\n\t\t}\n\t\tif g.read {\n\t\t\tflags |= Self::READ;\n\t\t}\n\t\tif g.truncate {\n\t\t\tflags |= Self::TRUNCATE;\n\t\t}\n\t\tif g.write {\n\t\t\tflags |= Self::WRITE;\n\t\t}\n\t\tflags\n\t}\n}\n\nimpl FileBuilder for Gate {\n\ttype File = yazi_sftp::fs::File;\n\n\tfn append(&mut self, append: bool) -> &mut Self {\n\t\tself.0.append(append);\n\t\tself\n\t}\n\n\tfn attrs(&mut self, attrs: Attrs) -> &mut Self {\n\t\tself.0.attrs(attrs);\n\t\tself\n\t}\n\n\tfn create(&mut self, create: bool) -> &mut Self {\n\t\tself.0.create(create);\n\t\tself\n\t}\n\n\tfn create_new(&mut self, create_new: bool) -> &mut Self {\n\t\tself.0.create_new(create_new);\n\t\tself\n\t}\n\n\tasync fn open<U>(&self, url: U) -> io::Result<Self::File>\n\twhere\n\t\tU: AsUrl,\n\t{\n\t\tlet url = url.as_url();\n\t\tlet (path, (name, config)) = match url {\n\t\t\tUrl::Sftp { loc, domain } => (*loc, Vfs::service::<&ServiceSftp>(domain).await?),\n\t\t\t_ => Err(io::Error::new(io::ErrorKind::InvalidInput, format!(\"Not an SFTP URL: {url:?}\")))?,\n\t\t};\n\n\t\tlet conn = Conn { name, config }.roll().await?;\n\t\tlet flags = Flags::from(*self);\n\t\tlet attrs = super::Attrs(self.0.attrs).try_into().unwrap_or_default();\n\n\t\tlet result = conn.open(path, flags, &attrs).await;\n\t\tif self.0.create_new\n\t\t\t&& let Err(yazi_sftp::Error::Status(status)) = &result\n\t\t\t&& status.is_failure()\n\t\t\t&& conn.lstat(path).await.is_ok()\n\t\t{\n\t\t\treturn Err(io::Error::from(io::ErrorKind::AlreadyExists));\n\t\t}\n\n\t\tOk(result?)\n\t}\n\n\tfn read(&mut self, read: bool) -> &mut Self {\n\t\tself.0.read(read);\n\t\tself\n\t}\n\n\tfn truncate(&mut self, truncate: bool) -> &mut Self {\n\t\tself.0.truncate(truncate);\n\t\tself\n\t}\n\n\tfn write(&mut self, write: bool) -> &mut Self {\n\t\tself.0.write(write);\n\t\tself\n\t}\n}\n"
  },
  {
    "path": "yazi-vfs/src/provider/sftp/metadata.rs",
    "content": "use std::{io, time::{Duration, UNIX_EPOCH}};\n\nuse yazi_fs::cha::ChaKind;\n\n// --- Attrs\npub(crate) struct Attrs(pub(crate) yazi_fs::provider::Attrs);\n\nimpl TryFrom<Attrs> for yazi_sftp::fs::Attrs {\n\ttype Error = ();\n\n\tfn try_from(value: Attrs) -> Result<Self, Self::Error> {\n\t\tlet attrs = Self {\n\t\t\tsize:     None,\n\t\t\tuid:      None,\n\t\t\tgid:      None,\n\t\t\tperm:     value.0.mode.map(|m| m.bits() as u32),\n\t\t\tatime:    value.0.atime_dur().map(|d| d.as_secs() as u32),\n\t\t\tmtime:    value.0.mtime_dur().map(|d| d.as_secs() as u32),\n\t\t\textended: Default::default(),\n\t\t};\n\n\t\tif attrs.is_empty() { Err(()) } else { Ok(attrs) }\n\t}\n}\n\n// --- Cha\npub(crate) struct Cha(pub(crate) yazi_fs::cha::Cha);\n\nimpl TryFrom<&yazi_sftp::fs::DirEntry> for Cha {\n\ttype Error = io::Error;\n\n\tfn try_from(ent: &yazi_sftp::fs::DirEntry) -> Result<Self, Self::Error> {\n\t\tlet mut cha = Self::try_from((ent.name(), ent.attrs()))?;\n\t\tcha.0.nlink = ent.nlink().unwrap_or_default();\n\t\tOk(cha)\n\t}\n}\n\nimpl TryFrom<(&[u8], &yazi_sftp::fs::Attrs)> for Cha {\n\ttype Error = io::Error;\n\n\tfn try_from((name, attrs): (&[u8], &yazi_sftp::fs::Attrs)) -> Result<Self, Self::Error> {\n\t\tlet kind = if name.starts_with(b\".\") { ChaKind::HIDDEN } else { ChaKind::empty() };\n\n\t\tOk(Self(yazi_fs::cha::Cha {\n\t\t\tkind,\n\t\t\tmode: ChaMode::try_from(attrs)?.0,\n\t\t\tlen: attrs.size.unwrap_or(0),\n\t\t\tatime: attrs.atime.and_then(|t| UNIX_EPOCH.checked_add(Duration::from_secs(t as u64))),\n\t\t\tbtime: None,\n\t\t\tctime: None,\n\t\t\tmtime: attrs.mtime.and_then(|t| UNIX_EPOCH.checked_add(Duration::from_secs(t as u64))),\n\t\t\tdev: 0,\n\t\t\tuid: attrs.uid.unwrap_or(0),\n\t\t\tgid: attrs.gid.unwrap_or(0),\n\t\t\tnlink: 0,\n\t\t}))\n\t}\n}\n\n// --- ChaMode\npub(super) struct ChaMode(pub(super) yazi_fs::cha::ChaMode);\n\nimpl TryFrom<&yazi_sftp::fs::Attrs> for ChaMode {\n\ttype Error = io::Error;\n\n\tfn try_from(attrs: &yazi_sftp::fs::Attrs) -> Result<Self, Self::Error> {\n\t\tyazi_fs::cha::ChaMode::try_from(attrs.perm.unwrap_or_default() as u16)\n\t\t\t.map(Self)\n\t\t\t.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))\n\t}\n}\n"
  },
  {
    "path": "yazi-vfs/src/provider/sftp/mod.rs",
    "content": "yazi_macro::mod_flat!(absolute conn gate metadata read_dir sftp);\n\nstatic CONN: yazi_shared::RoCell<\n\tparking_lot::Mutex<\n\t\thashbrown::HashMap<\n\t\t\t&'static yazi_config::vfs::ServiceSftp,\n\t\t\t&'static deadpool::managed::Pool<Conn>,\n\t\t>,\n\t>,\n> = yazi_shared::RoCell::new();\n\npub(super) fn init() { CONN.init(Default::default()); }\n"
  },
  {
    "path": "yazi-vfs/src/provider/sftp/read_dir.rs",
    "content": "use std::{io, sync::Arc};\n\nuse yazi_fs::provider::{DirReader, FileHolder};\nuse yazi_shared::{path::PathBufDyn, strand::StrandCow, url::{UrlBuf, UrlLike}};\n\nuse super::{Cha, ChaMode};\n\npub struct ReadDir {\n\tpub(super) dir:    Arc<UrlBuf>,\n\tpub(super) reader: yazi_sftp::fs::ReadDir,\n}\n\nimpl DirReader for ReadDir {\n\ttype Entry = DirEntry;\n\n\tasync fn next(&mut self) -> io::Result<Option<Self::Entry>> {\n\t\tOk(self.reader.next().await?.map(|entry| DirEntry { dir: self.dir.clone(), entry }))\n\t}\n}\n\n// --- Entry\npub struct DirEntry {\n\tdir:   Arc<UrlBuf>,\n\tentry: yazi_sftp::fs::DirEntry,\n}\n\nimpl FileHolder for DirEntry {\n\tasync fn file_type(&self) -> io::Result<yazi_fs::cha::ChaType> {\n\t\tOk(ChaMode::try_from(self.entry.attrs())?.0.into())\n\t}\n\n\tasync fn metadata(&self) -> io::Result<yazi_fs::cha::Cha> { Ok(Cha::try_from(&self.entry)?.0) }\n\n\tfn name(&self) -> StrandCow<'_> { self.entry.name().into() }\n\n\tfn path(&self) -> PathBufDyn { self.entry.path().into() }\n\n\tfn url(&self) -> UrlBuf {\n\t\tself.dir.try_join(self.entry.name()).expect(\"entry name is a valid component of the SFTP URL\")\n\t}\n}\n"
  },
  {
    "path": "yazi-vfs/src/provider/sftp/sftp.rs",
    "content": "use std::{io, sync::Arc};\n\nuse tokio::{io::{AsyncWriteExt, BufReader, BufWriter}, sync::mpsc::Receiver};\nuse yazi_config::vfs::{ServiceSftp, Vfs};\nuse yazi_fs::provider::{Capabilities, DirReader, FileHolder, Provider};\nuse yazi_sftp::fs::{Attrs, Flags};\nuse yazi_shared::{loc::LocBuf, path::{AsPath, PathBufDyn}, pool::InternStr, scheme::SchemeKind, strand::AsStrand, url::{Url, UrlBuf, UrlCow, UrlLike}};\n\nuse super::Cha;\nuse crate::provider::sftp::Conn;\n\n#[derive(Clone)]\npub struct Sftp<'a> {\n\turl:  Url<'a>,\n\tpath: &'a typed_path::UnixPath,\n\n\tname:   &'static str,\n\tconfig: &'static ServiceSftp,\n}\n\nimpl<'a> Provider for Sftp<'a> {\n\ttype File = yazi_sftp::fs::File;\n\ttype Gate = super::Gate;\n\ttype Me<'b> = Sftp<'b>;\n\ttype ReadDir = super::ReadDir;\n\ttype UrlCow = UrlCow<'a>;\n\n\tasync fn absolute(&self) -> io::Result<Self::UrlCow> {\n\t\tOk(if let Some(u) = super::try_absolute(self.url) {\n\t\t\tu\n\t\t} else {\n\t\t\tself.canonicalize().await?.into()\n\t\t})\n\t}\n\n\tasync fn canonicalize(&self) -> io::Result<UrlBuf> {\n\t\tOk(UrlBuf::Sftp {\n\t\t\tloc:    self.op().await?.realpath(self.path).await?.into(),\n\t\t\tdomain: self.name.intern(),\n\t\t})\n\t}\n\n\tfn capabilities(&self) -> Capabilities { Capabilities { symlink: true } }\n\n\tasync fn casefold(&self) -> io::Result<UrlBuf> {\n\t\tlet Some((parent, name)) = self.url.parent().zip(self.url.name()) else {\n\t\t\treturn Ok(self.url.to_owned());\n\t\t};\n\n\t\tif !self.symlink_metadata().await?.is_link() {\n\t\t\treturn Ok(match self.canonicalize().await?.name() {\n\t\t\t\tSome(name) => parent.try_join(name)?,\n\t\t\t\tNone => Err(io::Error::other(\"Cannot get filename\"))?,\n\t\t\t});\n\t\t}\n\n\t\tlet mut it = Self::new(parent).await?.read_dir().await?;\n\t\tlet mut similar = None;\n\t\twhile let Some(entry) = it.next().await? {\n\t\t\tlet s = entry.name();\n\t\t\tif !name.eq_ignore_ascii_case(&s) {\n\t\t\t\tcontinue;\n\t\t\t} else if s == name {\n\t\t\t\treturn Ok(entry.url());\n\t\t\t} else if similar.is_none() {\n\t\t\t\tsimilar = Some(s.into_owned());\n\t\t\t} else {\n\t\t\t\treturn Err(io::Error::from(io::ErrorKind::NotFound));\n\t\t\t}\n\t\t}\n\n\t\tsimilar\n\t\t\t.map(|n| parent.try_join(n))\n\t\t\t.transpose()?\n\t\t\t.ok_or_else(|| io::Error::from(io::ErrorKind::NotFound))\n\t}\n\n\tasync fn copy<P>(&self, to: P, attrs: yazi_fs::provider::Attrs) -> io::Result<u64>\n\twhere\n\t\tP: AsPath,\n\t{\n\t\tlet to = to.as_path().as_unix()?;\n\t\tlet attrs = super::Attrs(attrs).try_into().unwrap_or_default();\n\n\t\tlet op = self.op().await?;\n\t\tlet from = op.open(self.path, Flags::READ, &Attrs::default()).await?;\n\t\tlet to = op.open(to, Flags::WRITE | Flags::CREATE | Flags::TRUNCATE, &attrs).await?;\n\n\t\tlet mut reader = BufReader::with_capacity(524288, from);\n\t\tlet mut writer = BufWriter::with_capacity(524288, to);\n\t\tlet written = tokio::io::copy(&mut reader, &mut writer).await?;\n\n\t\twriter.flush().await?;\n\t\tif !attrs.is_empty() {\n\t\t\twriter.get_ref().fsetstat(&attrs).await.ok();\n\t\t}\n\n\t\twriter.shutdown().await.ok();\n\t\tOk(written)\n\t}\n\n\tfn copy_with_progress<P, A>(&self, to: P, attrs: A) -> io::Result<Receiver<io::Result<u64>>>\n\twhere\n\t\tP: AsPath,\n\t\tA: Into<yazi_fs::provider::Attrs>,\n\t{\n\t\tlet to = UrlBuf::Sftp {\n\t\t\tloc:    LocBuf::<typed_path::UnixPathBuf>::saturated(\n\t\t\t\tto.as_path().to_unix_owned()?,\n\t\t\t\tSchemeKind::Sftp,\n\t\t\t),\n\t\t\tdomain: self.name.intern(),\n\t\t};\n\t\tlet from = self.url.to_owned();\n\n\t\tOk(crate::provider::copy_with_progress_impl(from, to, attrs.into()))\n\t}\n\n\tasync fn create_dir(&self) -> io::Result<()> {\n\t\tlet op = self.op().await?;\n\t\tlet result = op.mkdir(self.path, Attrs::default()).await;\n\n\t\tif let Err(yazi_sftp::Error::Status(status)) = &result\n\t\t\t&& status.is_failure()\n\t\t\t&& op.lstat(self.path).await.is_ok()\n\t\t{\n\t\t\treturn Err(io::Error::from(io::ErrorKind::AlreadyExists));\n\t\t}\n\n\t\tOk(result?)\n\t}\n\n\tasync fn hard_link<P>(&self, to: P) -> io::Result<()>\n\twhere\n\t\tP: AsPath,\n\t{\n\t\tlet to = to.as_path().as_unix()?;\n\n\t\tOk(self.op().await?.hardlink(self.path, to).await?)\n\t}\n\n\tasync fn metadata(&self) -> io::Result<yazi_fs::cha::Cha> {\n\t\tlet attrs = self.op().await?.stat(self.path).await?;\n\t\tOk(Cha::try_from((self.path.file_name().unwrap_or_default(), &attrs))?.0)\n\t}\n\n\tasync fn new<'b>(url: Url<'b>) -> io::Result<Self::Me<'b>> {\n\t\tmatch url {\n\t\t\tUrl::Regular(_) | Url::Search { .. } | Url::Archive { .. } => {\n\t\t\t\tErr(io::Error::new(io::ErrorKind::InvalidInput, format!(\"Not a SFTP URL: {url:?}\")))\n\t\t\t}\n\t\t\tUrl::Sftp { loc, domain } => {\n\t\t\t\tlet (name, config) = Vfs::service::<&ServiceSftp>(domain).await?;\n\t\t\t\tOk(Self::Me { url, path: loc.as_inner(), name, config })\n\t\t\t}\n\t\t}\n\t}\n\n\tasync fn read_dir(self) -> io::Result<Self::ReadDir> {\n\t\tOk(Self::ReadDir {\n\t\t\tdir:    Arc::new(self.url.to_owned()),\n\t\t\treader: self.op().await?.read_dir(self.path).await?,\n\t\t})\n\t}\n\n\tasync fn read_link(&self) -> io::Result<PathBufDyn> {\n\t\tOk(self.op().await?.readlink(self.path).await?.into())\n\t}\n\n\tasync fn remove_dir(&self) -> io::Result<()> { Ok(self.op().await?.rmdir(self.path).await?) }\n\n\tasync fn remove_file(&self) -> io::Result<()> { Ok(self.op().await?.remove(self.path).await?) }\n\n\tasync fn rename<P>(&self, to: P) -> io::Result<()>\n\twhere\n\t\tP: AsPath,\n\t{\n\t\tlet to = to.as_path().as_unix()?;\n\t\tlet op = self.op().await?;\n\n\t\tmatch op.rename_posix(self.path, &to).await {\n\t\t\tOk(()) => {}\n\t\t\tErr(yazi_sftp::Error::Unsupported) => {\n\t\t\t\tmatch op.remove(&to).await.map_err(io::Error::from) {\n\t\t\t\t\tOk(()) => {}\n\t\t\t\t\tErr(e) if e.kind() == io::ErrorKind::NotFound => {}\n\t\t\t\t\tErr(e) => Err(e)?,\n\t\t\t\t}\n\t\t\t\top.rename(self.path, &to).await?;\n\t\t\t}\n\t\t\tErr(e) => Err(e)?,\n\t\t}\n\t\tOk(())\n\t}\n\n\tasync fn symlink<S, F>(&self, original: S, _is_dir: F) -> io::Result<()>\n\twhere\n\t\tS: AsStrand,\n\t\tF: AsyncFnOnce() -> io::Result<bool>,\n\t{\n\t\tlet original = original.as_strand().encoded_bytes();\n\n\t\tOk(self.op().await?.symlink(original, self.path).await?)\n\t}\n\n\tasync fn symlink_metadata(&self) -> io::Result<yazi_fs::cha::Cha> {\n\t\tlet attrs = self.op().await?.lstat(self.path).await?;\n\t\tOk(Cha::try_from((self.path.file_name().unwrap_or_default(), &attrs))?.0)\n\t}\n\n\tasync fn trash(&self) -> io::Result<()> {\n\t\tErr(io::Error::new(io::ErrorKind::Unsupported, \"Trash not supported\"))\n\t}\n\n\t#[inline]\n\tfn url(&self) -> Url<'_> { self.url }\n}\n\nimpl<'a> Sftp<'a> {\n\t#[inline]\n\tpub(super) async fn op(&self) -> io::Result<deadpool::managed::Object<Conn>> {\n\t\tConn { name: self.name, config: self.config }.roll().await\n\t}\n}\n"
  },
  {
    "path": "yazi-watcher/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-watcher\"\ndescription            = \"Yazi file watcher\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[dependencies]\nyazi-adapter = { path = \"../yazi-adapter\", version = \"26.2.2\" }\nyazi-dds     = { path = \"../yazi-dds\", version = \"26.2.2\" }\nyazi-fs      = { path = \"../yazi-fs\", version = \"26.2.2\" }\nyazi-macro   = { path = \"../yazi-macro\", version = \"26.2.2\" }\nyazi-proxy   = { path = \"../yazi-proxy\", version = \"26.2.2\" }\nyazi-shared  = { path = \"../yazi-shared\", version = \"26.2.2\" }\nyazi-vfs     = { path = \"../yazi-vfs\", version = \"26.2.2\" }\n\n# External dependencies\nfutures          = { workspace = true }\nhashbrown        = { workspace = true }\nnotify           = { version = \"8.2.0\", default-features = false, features = [ \"macos_fsevent\" ] }\nparking_lot      = { workspace = true }\npercent-encoding = { workspace = true }\ntokio            = { workspace = true }\ntokio-stream     = { workspace = true }\ntracing          = { workspace = true }\n"
  },
  {
    "path": "yazi-watcher/README.md",
    "content": "# yazi-watcher\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-watcher/src/backend.rs",
    "content": "use notify::Result;\nuse tokio::sync::mpsc;\n\nuse crate::{Reporter, WATCHED, Watchee, local::{self, LINKED, Linked}, remote};\n\npub(crate) struct Backend {\n\tlocal:               local::Local,\n\tremote:              remote::Remote,\n\tpub(super) reporter: Reporter,\n}\n\nimpl Backend {\n\tpub(crate) fn serve() -> Self {\n\t\t#[cfg(any(target_os = \"linux\", target_os = \"macos\"))]\n\t\tyazi_fs::mounts::Partitions::monitor(&yazi_fs::mounts::PARTITIONS, || {\n\t\t\tyazi_proxy::MgrProxy::watch();\n\t\t\tyazi_proxy::MgrProxy::refresh();\n\t\t\tyazi_macro::err!(yazi_dds::Pubsub::pub_after_mount())\n\t\t});\n\n\t\tlet (local_tx, local_rx) = mpsc::unbounded_channel();\n\t\tlet (remote_tx, remote_rx) = mpsc::unbounded_channel();\n\t\tlet reporter = Reporter { local_tx, remote_tx };\n\n\t\tSelf {\n\t\t\tlocal: local::Local::serve(local_rx, reporter.clone()),\n\t\t\tremote: remote::Remote::serve(remote_rx, reporter.clone()),\n\t\t\treporter,\n\t\t}\n\t}\n\n\tpub(super) fn watch(&mut self, watchee: &mut Watchee) -> Result<()> {\n\t\tmatch watchee {\n\t\t\tWatchee::Local(..) => self.local.watch(watchee),\n\t\t\tWatchee::Remote(_) => self.remote.watch(watchee),\n\t\t}\n\t}\n\n\tpub(super) fn unwatch(&mut self, watchee: &Watchee) -> Result<()> {\n\t\tmatch watchee {\n\t\t\tWatchee::Local(..) => self.local.unwatch(watchee),\n\t\t\tWatchee::Remote(_) => self.remote.unwatch(watchee),\n\t\t}\n\t}\n\n\tpub(super) async fn sync(self) -> Self {\n\t\tLinked::sync(&LINKED, &WATCHED).await;\n\t\tself\n\t}\n}\n"
  },
  {
    "path": "yazi-watcher/src/lib.rs",
    "content": "yazi_macro::mod_pub!(local remote);\n\nyazi_macro::mod_flat!(backend reporter watched watchee watcher);\n\npub static WATCHED: yazi_shared::RoCell<parking_lot::RwLock<Watched>> = yazi_shared::RoCell::new();\npub static WATCHER: yazi_shared::RoCell<tokio::sync::Semaphore> = yazi_shared::RoCell::new();\n\npub fn init() {\n\tWATCHED.with(<_>::default);\n\tWATCHER.init(tokio::sync::Semaphore::new(1));\n\n\tlocal::init();\n}\n"
  },
  {
    "path": "yazi-watcher/src/local/linked.rs",
    "content": "use std::{iter, ops::{Deref, DerefMut}, path::{Path, PathBuf}};\n\nuse hashbrown::HashMap;\nuse parking_lot::RwLock;\nuse yazi_shared::url::Url;\n\nuse crate::Watched;\n\n#[derive(Default)]\npub struct Linked(HashMap<PathBuf, PathBuf> /* from ==> to */);\n\nimpl Deref for Linked {\n\ttype Target = HashMap<PathBuf, PathBuf>;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl DerefMut for Linked {\n\tfn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }\n}\n\nimpl Linked {\n\tpub fn from_dir<'a, 'b, U>(&'a self, url: U) -> Box<dyn Iterator<Item = &'a Path> + 'b>\n\twhere\n\t\t'a: 'b,\n\t\tU: Into<Url<'b>>,\n\t{\n\t\tlet url: Url = url.into();\n\t\tlet Some(path) = url.as_local() else {\n\t\t\treturn Box::new(iter::empty());\n\t\t};\n\n\t\tif let Some(to) = self.get(path) {\n\t\t\tBox::new(self.iter().filter(move |(k, v)| *v == to && *k != path).map(|(k, _)| k.as_path()))\n\t\t} else {\n\t\t\tBox::new(self.iter().filter(move |(_, v)| *v == path).map(|(k, _)| k.as_path()))\n\t\t}\n\t}\n\n\tpub fn from_file(&self, url: Url) -> Vec<PathBuf> {\n\t\tlet Some(path) = url.as_local() else { return vec![] };\n\t\tif let Some((parent, name)) = path.parent().zip(path.file_name()) {\n\t\t\tself.from_dir(parent).map(|p| p.join(name)).collect()\n\t\t} else {\n\t\t\tvec![]\n\t\t}\n\t}\n\n\tpub(crate) async fn sync(linked: &'static RwLock<Self>, watched: &'static RwLock<Watched>) {\n\t\ttokio::task::spawn_blocking(move || {\n\t\t\tlet watched = watched.read();\n\n\t\t\t// Remove entries that are no longer watched\n\t\t\tlinked.write().retain(|from, _| watched.contains_path(from));\n\n\t\t\t// Update existing entries and remove broken links\n\t\t\tfor from in watched.paths() {\n\t\t\t\tmatch std::fs::canonicalize(from) {\n\t\t\t\t\tOk(to) if to != *from => _ = linked.write().entry_ref(from).insert(to),\n\t\t\t\t\tOk(_) => _ = linked.write().remove(from),\n\t\t\t\t\tErr(e) if e.kind() == std::io::ErrorKind::NotFound => _ = linked.write().remove(from),\n\t\t\t\t\tErr(e) => tracing::error!(\"Failed to canonicalize watched path {from:?}: {e:?}\"),\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.await\n\t\t.ok();\n\t}\n}\n"
  },
  {
    "path": "yazi-watcher/src/local/local.rs",
    "content": "use std::{path::Path, time::Duration};\n\nuse hashbrown::HashSet;\nuse notify::{PollWatcher, RecommendedWatcher, RecursiveMode, Result, Watcher};\nuse tokio::{pin, sync::mpsc::{self, UnboundedReceiver}};\nuse tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};\nuse tracing::error;\nuse yazi_fs::{File, FilesOp, mounts::PARTITIONS, provider::{self, Provider}};\nuse yazi_shared::url::{UrlBuf, UrlLike};\nuse yazi_vfs::VfsFile;\n\nuse crate::{Reporter, WATCHER, Watchee};\n\npub(crate) struct Local {\n\tprimary:     Option<RecommendedWatcher>,\n\talternative: PollWatcher,\n}\n\nimpl Local {\n\tpub(crate) fn serve(rx: mpsc::UnboundedReceiver<UrlBuf>, reporter: Reporter) -> Self {\n\t\ttokio::spawn(Self::changed(rx));\n\n\t\tlet config = notify::Config::default().with_poll_interval(Duration::from_secs(1));\n\t\tlet handler = move |res: Result<notify::Event>| {\n\t\t\tif let Ok(event) = res\n\t\t\t\t&& !event.kind.is_access()\n\t\t\t{\n\t\t\t\treporter.report(event.paths);\n\t\t\t}\n\t\t};\n\n\t\tlet primary = RecommendedWatcher::new(handler.clone(), config);\n\t\tlet alternative = PollWatcher::new(handler, config).unwrap();\n\n\t\tif let Err(e) = &primary {\n\t\t\terror!(\"Failed to initialize primary watcher: {e:?}\");\n\t\t}\n\n\t\tSelf { primary: primary.ok(), alternative }\n\t}\n\n\tpub(crate) fn watch(&mut self, watchee: &mut Watchee) -> Result<()> {\n\t\tlet (path, alt) =\n\t\t\twatchee.as_local_mut().ok_or_else(|| notify::Error::generic(\"Not a local watchee\"))?;\n\n\t\tif let Some(primary) = self.primary.as_mut().filter(|_| !*alt) {\n\t\t\tmatch primary.watch(path, RecursiveMode::NonRecursive) {\n\t\t\t\tOk(()) => return Ok(()),\n\t\t\t\tErr(e) => tracing::warn!(\"Failed to watch {path:?} with primary watcher: {e:?}\"),\n\t\t\t}\n\t\t}\n\n\t\ttracing::debug!(\"Watching {path:?} with alternative watcher\");\n\t\t*alt = true;\n\t\tself.alternative.watch(path, RecursiveMode::NonRecursive)\n\t}\n\n\tpub(crate) fn unwatch(&mut self, watchee: &Watchee) -> Result<()> {\n\t\tlet (path, alt) =\n\t\t\twatchee.as_local().ok_or_else(|| notify::Error::generic(\"Not a local watchee\"))?;\n\n\t\tlet result = if alt {\n\t\t\tself.alternative.unwatch(path)\n\t\t} else if let Some(primary) = &mut self.primary {\n\t\t\tprimary.unwatch(path)\n\t\t} else {\n\t\t\tOk(())\n\t\t};\n\n\t\tmatch result {\n\t\t\tOk(()) => Ok(()),\n\t\t\tErr(e) if matches!(e.kind, notify::ErrorKind::WatchNotFound) => Ok(()),\n\t\t\tErr(e) => Err(e)?,\n\t\t}\n\t}\n\n\tpub(crate) async fn soundless(path: &Path) -> bool {\n\t\tif cfg!(target_os = \"netbsd\") || yazi_adapter::WSL.get() {\n\t\t\treturn true;\n\t\t}\n\n\t\tmatch provider::local::Local::regular(path).metadata().await {\n\t\t\tOk(cha) => PARTITIONS.read().soundless(cha),\n\t\t\tErr(_) => true,\n\t\t}\n\t}\n\n\tasync fn changed(rx: UnboundedReceiver<UrlBuf>) {\n\t\t// TODO: revert this once a new notification is implemented\n\t\tlet rx = UnboundedReceiverStream::new(rx).chunks_timeout(1000, Duration::from_millis(250));\n\t\tpin!(rx);\n\n\t\twhile let Some(chunk) = rx.next().await {\n\t\t\tlet urls: HashSet<_> = chunk.into_iter().collect();\n\n\t\t\tlet _permit = WATCHER.acquire().await.unwrap();\n\t\t\tlet mut ops = Vec::with_capacity(urls.len());\n\n\t\t\tfor u in urls {\n\t\t\t\tlet Some((parent, urn)) = u.pair() else { continue };\n\t\t\t\tlet Ok(file) = File::new(&u).await else {\n\t\t\t\t\tops.push(FilesOp::Deleting(parent.into(), [urn.into()].into()));\n\t\t\t\t\tcontinue;\n\t\t\t\t};\n\n\t\t\t\tif let Some(p) = file.url.as_local()\n\t\t\t\t\t&& !provider::local::match_name_case(p).await\n\t\t\t\t{\n\t\t\t\t\tops.push(FilesOp::Deleting(parent.into(), [urn.into()].into()));\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tops.push(FilesOp::Upserting(parent.into(), [(urn.into(), file)].into()));\n\t\t\t}\n\n\t\t\tFilesOp::mutate(ops);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-watcher/src/local/mod.rs",
    "content": "yazi_macro::mod_flat!(linked local);\n\npub static LINKED: yazi_shared::RoCell<parking_lot::RwLock<Linked>> = yazi_shared::RoCell::new();\n\npub(super) fn init() { LINKED.with(<_>::default); }\n"
  },
  {
    "path": "yazi-watcher/src/remote/mod.rs",
    "content": "yazi_macro::mod_flat!(remote);\n"
  },
  {
    "path": "yazi-watcher/src/remote/remote.rs",
    "content": "use std::time::{Duration, SystemTime};\n\nuse hashbrown::HashMap;\nuse notify::Result;\nuse tokio::{pin, sync::mpsc::UnboundedReceiver};\nuse tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};\nuse yazi_fs::{File, FilesOp};\nuse yazi_proxy::MgrProxy;\nuse yazi_shared::url::{UrlBuf, UrlLike};\nuse yazi_vfs::VfsFile;\n\nuse crate::{Reporter, WATCHER, Watchee};\n\npub(crate) struct Remote;\n\nimpl Remote {\n\tpub(crate) fn serve(rx: UnboundedReceiver<(UrlBuf, bool)>, _reporter: Reporter) -> Self {\n\t\ttokio::spawn(Self::changed(rx));\n\n\t\tSelf\n\t}\n\n\tpub(crate) fn watch(&mut self, _watchee: &mut Watchee) -> Result<()> { Ok(()) }\n\n\tpub(crate) fn unwatch(&mut self, _watchee: &Watchee) -> Result<()> { Ok(()) }\n\n\tasync fn changed(rx: UnboundedReceiver<(UrlBuf, bool)>) {\n\t\tlet rx = UnboundedReceiverStream::new(rx).chunks_timeout(1000, Duration::from_millis(250));\n\t\tpin!(rx);\n\n\t\twhile let Some(chunk) = rx.next().await {\n\t\t\tlet mut urls = HashMap::with_capacity(chunk.len());\n\t\t\tfor (u, upload) in chunk {\n\t\t\t\turls.entry(u).and_modify(|b| *b |= upload).or_insert(upload);\n\t\t\t}\n\n\t\t\tlet _permit = WATCHER.acquire().await.unwrap();\n\n\t\t\tlet mut ops = Vec::with_capacity(urls.len());\n\t\t\tlet mut ups = Vec::with_capacity(urls.len());\n\n\t\t\tfor (u, upload) in urls {\n\t\t\t\tlet Some((parent, urn)) = u.pair() else { continue };\n\t\t\t\tlet Ok(mut file) = File::new(&u).await else {\n\t\t\t\t\tops.push(FilesOp::Deleting(parent.into(), [urn.into()].into()));\n\t\t\t\t\tcontinue;\n\t\t\t\t};\n\n\t\t\t\tlet is_file = file.is_file();\n\t\t\t\tfile.cha.ctime = Some(SystemTime::now());\n\n\t\t\t\tops.push(FilesOp::Upserting(parent.into(), [(urn.into(), file)].into()));\n\t\t\t\tif upload && is_file {\n\t\t\t\t\tups.push(u);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tFilesOp::mutate(ops);\n\t\t\tMgrProxy::upload(ups);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-watcher/src/reporter.rs",
    "content": "use std::borrow::Cow;\n\nuse percent_encoding::percent_decode;\nuse tokio::sync::mpsc;\nuse yazi_shared::{scheme::SchemeKind, url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike}};\n\nuse crate::{WATCHED, local::LINKED};\n\n#[derive(Clone)]\npub(crate) struct Reporter {\n\tpub(super) local_tx:  mpsc::UnboundedSender<UrlBuf>,\n\tpub(super) remote_tx: mpsc::UnboundedSender<(UrlBuf, bool)>,\n}\n\nimpl Reporter {\n\tpub(crate) fn report<'a, I>(&self, urls: I)\n\twhere\n\t\tI: IntoIterator,\n\t\tI::Item: Into<UrlCow<'a>>,\n\t{\n\t\tfor url in urls.into_iter().map(Into::into) {\n\t\t\tmatch url.as_url().kind() {\n\t\t\t\tSchemeKind::Regular | SchemeKind::Search => self.report_local(url),\n\t\t\t\tSchemeKind::Archive => {}\n\t\t\t\tSchemeKind::Sftp => self.report_remote(url),\n\t\t\t}\n\t\t}\n\t}\n\n\tfn report_local(&self, url: UrlCow) {\n\t\tlet Some((parent, urn)) = url.pair() else { return };\n\n\t\t// FIXME: LINKED should return Url instead of Path\n\t\tlet linked = LINKED.read();\n\t\tlet linked = linked.from_dir(parent).map(Url::regular);\n\n\t\tlet watched = WATCHED.read();\n\t\tfor parent in [parent].into_iter().chain(linked) {\n\t\t\tif watched.contains_url(parent) {\n\t\t\t\tself.local_tx.send(url.to_owned()).ok();\n\t\t\t\tself.local_tx.send(parent.to_owned()).ok();\n\t\t\t}\n\n\t\t\tif urn.ext().is_some_and(|e| e == \"%tmp\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Virtual caches\n\t\t\tlet Some(dir) = watched.find_by_cache(parent.loc()) else { continue };\n\t\t\tlet Some(name) = url.name() else { continue };\n\t\t\tif let Ok(u) = dir.try_join(Cow::from(percent_decode(name.encoded_bytes()))) {\n\t\t\t\tself.remote_tx.send((u, true)).ok();\n\t\t\t}\n\t\t\tself.remote_tx.send((dir, false)).ok();\n\t\t}\n\t}\n\n\tfn report_remote(&self, url: UrlCow) {\n\t\tlet Some(parent) = url.parent() else { return };\n\t\tif !WATCHED.read().contains_url(parent) {\n\t\t\treturn;\n\t\t}\n\n\t\tself.remote_tx.send((parent.to_owned(), false)).ok();\n\t\tself.remote_tx.send((url.into_owned(), false)).ok();\n\t}\n}\n"
  },
  {
    "path": "yazi-watcher/src/watched.rs",
    "content": "use std::{ops::{Deref, DerefMut}, path::Path};\n\nuse hashbrown::HashSet;\nuse percent_encoding::percent_decode_str;\nuse yazi_fs::{Xdg, path::PercentEncoding};\nuse yazi_shared::{path::{Component, PathBufDyn, PathDyn, PathLike}, pool::InternStr, scheme::SchemeKind, url::{AsUrl, UrlBuf}};\n\nuse crate::Watchee;\n\n#[derive(Debug, Default)]\npub struct Watched(HashSet<Watchee<'static>>);\n\nimpl Deref for Watched {\n\ttype Target = HashSet<Watchee<'static>>;\n\n\tfn deref(&self) -> &Self::Target { &self.0 }\n}\n\nimpl DerefMut for Watched {\n\tfn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }\n}\n\nimpl Watched {\n\tpub(super) fn contains_url(&self, url: impl AsUrl) -> bool {\n\t\tlet url = url.as_url();\n\t\tif url.as_local().is_some() {\n\t\t\tself.0.contains(&Watchee::Local(url.into(), false))\n\t\t\t\t|| self.0.contains(&Watchee::Local(url.into(), true))\n\t\t} else {\n\t\t\tself.0.contains(&Watchee::Remote(url.into()))\n\t\t}\n\t}\n\n\tpub(super) fn contains_path(&self, path: &Path) -> bool {\n\t\tself.0.iter().any(|watchee| watchee.as_url().as_local() == Some(path))\n\t}\n\n\tpub(super) fn paths(&self) -> impl Iterator<Item = &Path> {\n\t\tself.0.iter().filter_map(|watchee| watchee.as_url().as_local())\n\t}\n\n\tpub(super) fn find_by_cache(&self, cache: PathDyn) -> Option<UrlBuf> {\n\t\tlet mut it = cache.try_strip_prefix(Xdg::cache_dir()).ok()?.components();\n\n\t\t// Parse domain\n\t\tlet domain = it.next()?.as_normal()?.to_str().ok()?;\n\t\tlet domain = percent_decode_str(domain.strip_prefix(\"sftp-\")?).decode_utf8().ok()?.intern();\n\n\t\t// Parse path\n\t\tlet (path, abs) =\n\t\t\tif let Ok(p) = it.path().try_strip_prefix(\".%2F\") { (p, false) } else { (it.path(), true) };\n\t\tlet path = path.percent_decode(SchemeKind::Sftp).ok()?;\n\t\tlet path = PathBufDyn::from_components(\n\t\t\tSchemeKind::Sftp,\n\t\t\tSome(Component::RootDir).filter(|_| abs).into_iter().chain(path.components()),\n\t\t)\n\t\t.ok()?;\n\n\t\tlet url = UrlBuf::Sftp { loc: path.into_unix().ok()?.into(), domain };\n\t\tif self.contains_url(&url) { Some(url) } else { None }\n\t}\n}\n"
  },
  {
    "path": "yazi-watcher/src/watchee.rs",
    "content": "use std::{hash::Hash, path::Path};\n\nuse yazi_shared::url::{AsUrl, Url, UrlCow, UrlLike};\n\nuse crate::local::Local;\n\n#[derive(Debug, Eq, Hash, PartialEq)]\npub enum Watchee<'a> {\n\tLocal(UrlCow<'a>, bool),\n\tRemote(UrlCow<'a>),\n}\n\nimpl AsUrl for Watchee<'_> {\n\tfn as_url(&self) -> Url<'_> {\n\t\tmatch self {\n\t\t\tSelf::Local(url, _) => url.as_url(),\n\t\t\tSelf::Remote(url) => url.as_url(),\n\t\t}\n\t}\n}\n\nimpl<'a> Watchee<'a> {\n\tpub(super) fn as_local(&self) -> Option<(&Path, bool)> {\n\t\tSome(match self {\n\t\t\tSelf::Local(url, alt) => (url.as_local()?, *alt),\n\t\t\tSelf::Remote(_) => None?,\n\t\t})\n\t}\n\n\tpub(super) fn as_local_mut(&mut self) -> Option<(&Path, &mut bool)> {\n\t\tSome(match self {\n\t\t\tSelf::Local(url, alt) => (url.as_local()?, alt),\n\t\t\tSelf::Remote(_) => None?,\n\t\t})\n\t}\n\n\tpub(super) async fn new<U>(url: U) -> Self\n\twhere\n\t\tU: Into<UrlCow<'a>>,\n\t{\n\t\tlet url = url.into();\n\t\tif let Some(path) = url.as_local() {\n\t\t\tlet b = Local::soundless(path).await;\n\t\t\tSelf::Local(url, b)\n\t\t} else {\n\t\t\tSelf::Remote(url)\n\t\t}\n\t}\n\n\tpub(super) fn to_static(&self) -> Watchee<'static> {\n\t\tmatch self {\n\t\t\tSelf::Local(url, alt) => Watchee::Local(url.to_owned().into(), *alt),\n\t\t\tSelf::Remote(url) => Watchee::Remote(url.to_owned().into()),\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-watcher/src/watcher.rs",
    "content": "use futures::{StreamExt, stream};\nuse hashbrown::HashSet;\nuse tracing::error;\nuse yazi_fs::FsUrl;\nuse yazi_shared::{LastValue, url::{UrlBuf, UrlCow, UrlLike}};\n\nuse crate::{Reporter, WATCHED, Watchee, backend::Backend};\n\npub struct Watcher {\n\tlast:     LastValue<HashSet<UrlBuf>>,\n\treporter: Reporter,\n}\n\nimpl Watcher {\n\tpub fn serve() -> Self {\n\t\tlet last = LastValue::default();\n\n\t\tlet backend = Backend::serve();\n\t\tlet reporter = backend.reporter.clone();\n\n\t\ttokio::spawn(Self::watched(last.clone(), backend));\n\t\tSelf { last, reporter }\n\t}\n\n\tpub fn watch<'a, I>(&mut self, urls: I)\n\twhere\n\t\tI: IntoIterator,\n\t\tI::Item: Into<UrlCow<'a>>,\n\t{\n\t\tlet it = urls.into_iter();\n\t\tlet mut urls = HashSet::with_capacity(it.size_hint().0);\n\n\t\tfor url in it.map(Into::into) {\n\t\t\tif !url.is_absolute() {\n\t\t\t\tcontinue;\n\t\t\t} else if let Some(cache) = url.cache() {\n\t\t\t\turls.insert(cache.into());\n\t\t\t}\n\t\t\turls.insert(url.into_owned());\n\t\t}\n\n\t\tself.last.set(urls);\n\t}\n\n\tpub fn report<'a, I>(&self, urls: I)\n\twhere\n\t\tI: IntoIterator,\n\t\tI::Item: Into<UrlCow<'a>>,\n\t{\n\t\tself.reporter.report(urls);\n\t}\n\n\tasync fn watched(last: LastValue<HashSet<UrlBuf>>, mut backend: Backend) {\n\t\tloop {\n\t\t\tlet (to_unwatch, to_watch) = Self::diff(last.get().await).await;\n\n\t\t\tif !to_unwatch.is_empty() || !to_watch.is_empty() {\n\t\t\t\tbackend = Self::sync(backend, to_unwatch, to_watch).await;\n\t\t\t\tbackend = backend.sync().await;\n\t\t\t}\n\t\t}\n\t}\n\n\tasync fn diff(urls: HashSet<UrlBuf>) -> (Vec<Watchee<'static>>, HashSet<Watchee<'static>>) {\n\t\tlet mut new: HashSet<_> = stream::iter(urls).then(Watchee::new).collect().await;\n\t\tlet old = WATCHED.read();\n\n\t\tlet to_unwatch = old.difference(&new).map(Watchee::to_static).collect();\n\t\tnew.retain(|watchee| !old.contains(watchee));\n\n\t\t(to_unwatch, new)\n\t}\n\n\tasync fn sync(\n\t\tmut backend: Backend,\n\t\tto_unwatch: Vec<Watchee<'static>>,\n\t\tto_watch: HashSet<Watchee<'static>>,\n\t) -> Backend {\n\t\ttokio::task::spawn_blocking(move || {\n\t\t\tfor watchee in to_unwatch {\n\t\t\t\tmatch backend.unwatch(&watchee) {\n\t\t\t\t\tOk(()) => _ = WATCHED.write().remove(&watchee),\n\t\t\t\t\tErr(e) => error!(\"Unwatch failed: {e:?}\"),\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor mut watchee in to_watch {\n\t\t\t\tmatch backend.watch(&mut watchee) {\n\t\t\t\t\tOk(()) => _ = WATCHED.write().insert(watchee),\n\t\t\t\t\tErr(e) if matches!(e.kind, notify::ErrorKind::PathNotFound) => {}\n\t\t\t\t\tErr(e) => error!(\"Watch failed: {e:?}\"),\n\t\t\t\t}\n\t\t\t}\n\t\t\tbackend\n\t\t})\n\t\t.await\n\t\t.unwrap()\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/Cargo.toml",
    "content": "[package]\nname                   = \"yazi-widgets\"\ndescription            = \"Yazi user interface widgets\"\nversion.workspace      = true\nedition.workspace      = true\nlicense.workspace      = true\nauthors.workspace      = true\nhomepage.workspace     = true\nrepository.workspace   = true\nrust-version.workspace = true\n\n[lints]\nworkspace = true\n\n[dependencies]\nyazi-adapter = { path = \"../yazi-adapter\", version = \"26.2.2\" }\nyazi-config  = { path = \"../yazi-config\", version = \"26.2.2\" }\nyazi-macro   = { path = \"../yazi-macro\", version = \"26.2.2\" }\nyazi-shared  = { path = \"../yazi-shared\", version = \"26.2.2\" }\nyazi-tty     = { path = \"../yazi-tty\", version = \"26.2.2\" }\n\n# External dependencies\nanyhow        = { workspace = true }\nbase64        = { workspace = true }\ncrossterm     = { workspace = true }\nfutures       = { workspace = true }\nmlua          = { workspace = true }\nparking_lot   = { workspace = true }\nratatui       = { workspace = true }\ntokio         = { workspace = true }\nunicode-width = { workspace = true }\n\n[target.\"cfg(windows)\".dependencies]\nclipboard-win = \"5.4.1\"\n\n[target.'cfg(target_os = \"macos\")'.dependencies]\ncrossterm = { workspace = true, features = [ \"use-dev-tty\", \"libc\" ] }\n"
  },
  {
    "path": "yazi-widgets/README.md",
    "content": "# yazi-widgets\n\nThis crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.\n\n[source]: https://github.com/sxyazi/yazi\n"
  },
  {
    "path": "yazi-widgets/src/clear.rs",
    "content": "use std::sync::atomic::{AtomicBool, Ordering};\n\nuse ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};\nuse yazi_adapter::ADAPTOR;\n\npub static COLLISION: AtomicBool = AtomicBool::new(false);\n\n#[derive(Clone, Copy, Debug)]\npub struct Clear;\n\nimpl Widget for Clear {\n\tfn render(self, area: Rect, buf: &mut Buffer)\n\twhere\n\t\tSelf: Sized,\n\t{\n\t\tratatui::widgets::Clear.render(area, buf);\n\n\t\tlet Some(r) = ADAPTOR.get().shown_load().and_then(|r| overlap(area, r)) else {\n\t\t\treturn;\n\t\t};\n\n\t\tADAPTOR.get().image_erase(r).ok();\n\t\tCOLLISION.store(true, Ordering::Relaxed);\n\t\tfor y in r.top()..r.bottom() {\n\t\t\tfor x in r.left()..r.right() {\n\t\t\t\tbuf[(x, y)].set_skip(true);\n\t\t\t}\n\t\t}\n\t}\n}\n\nconst fn is_overlapping(a: Rect, b: Rect) -> bool {\n\ta.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y\n}\n\nfn overlap(a: Rect, b: Rect) -> Option<Rect> {\n\tif !is_overlapping(a, b) {\n\t\treturn None;\n\t}\n\n\tlet x = a.x.max(b.x);\n\tlet y = a.y.max(b.y);\n\tlet width = (a.x + a.width).min(b.x + b.width) - x;\n\tlet height = (a.y + a.height).min(b.y + b.height) - y;\n\tSome(Rect { x, y, width, height })\n}\n"
  },
  {
    "path": "yazi-widgets/src/clipboard.rs",
    "content": "use parking_lot::Mutex;\nuse yazi_shared::RoCell;\n\npub static CLIPBOARD: RoCell<Clipboard> = RoCell::new();\n\n#[derive(Default)]\npub struct Clipboard {\n\tcontent: Mutex<Vec<u8>>,\n}\n\nimpl Clipboard {\n\t#[cfg(unix)]\n\tpub async fn get(&self) -> Vec<u8> {\n\t\tuse tokio::process::Command;\n\t\tuse yazi_shared::in_ssh_connection;\n\n\t\tif in_ssh_connection() {\n\t\t\treturn self.content.lock().clone();\n\t\t}\n\n\t\tlet all = [\n\t\t\t(\"pbpaste\", &[][..]),\n\t\t\t(\"termux-clipboard-get\", &[]),\n\t\t\t(\"wl-paste\", &[\"-n\"]),\n\t\t\t(\"xclip\", &[\"-o\", \"-selection\", \"clipboard\"]),\n\t\t\t(\"xsel\", &[\"-ob\"]),\n\t\t];\n\n\t\tfor (bin, args) in all {\n\t\t\tlet Ok(output) = Command::new(bin).args(args).kill_on_drop(true).output().await else {\n\t\t\t\tcontinue;\n\t\t\t};\n\t\t\tif output.status.success() {\n\t\t\t\treturn output.stdout;\n\t\t\t}\n\t\t}\n\t\tself.content.lock().clone()\n\t}\n\n\t#[cfg(windows)]\n\tpub async fn get(&self) -> Vec<u8> {\n\t\tuse clipboard_win::get_clipboard_string;\n\n\t\tlet result = tokio::task::spawn_blocking(get_clipboard_string);\n\t\tif let Ok(Ok(s)) = result.await {\n\t\t\treturn s.into_bytes();\n\t\t}\n\n\t\tself.content.lock().clone()\n\t}\n\n\t#[cfg(unix)]\n\tpub async fn set(&self, s: impl AsRef<[u8]>) {\n\t\tuse std::process::Stdio;\n\n\t\tuse crossterm::execute;\n\t\tuse tokio::{io::AsyncWriteExt, process::Command};\n\t\tuse yazi_tty::TTY;\n\n\t\ts.as_ref().clone_into(&mut self.content.lock());\n\t\texecute!(TTY.writer(), osc52::SetClipboard::new(s.as_ref())).ok();\n\n\t\tlet all = [\n\t\t\t(\"pbcopy\", &[][..]),\n\t\t\t(\"termux-clipboard-set\", &[]),\n\t\t\t(\"wl-copy\", &[]),\n\t\t\t(\"xclip\", &[\"-selection\", \"clipboard\"]),\n\t\t\t(\"xsel\", &[\"-ib\"]),\n\t\t];\n\n\t\tfor (bin, args) in all {\n\t\t\tlet cmd = Command::new(bin)\n\t\t\t\t.args(args)\n\t\t\t\t.stdin(Stdio::piped())\n\t\t\t\t.stdout(Stdio::null())\n\t\t\t\t.stderr(Stdio::null())\n\t\t\t\t.kill_on_drop(true)\n\t\t\t\t.spawn();\n\n\t\t\tlet Ok(mut child) = cmd else { continue };\n\n\t\t\tlet mut stdin = child.stdin.take().unwrap();\n\t\t\tif stdin.write_all(s.as_ref()).await.is_err() {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tdrop(stdin);\n\n\t\t\tif child.wait().await.map(|s| s.success()).unwrap_or_default() {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t#[cfg(windows)]\n\tpub async fn set(&self, s: impl AsRef<[u8]>) {\n\t\tuse clipboard_win::set_clipboard_string;\n\n\t\tlet b = s.as_ref().to_owned();\n\t\t*self.content.lock() = b.clone();\n\n\t\ttokio::task::spawn_blocking(move || set_clipboard_string(&String::from_utf8_lossy(&b)))\n\t\t\t.await\n\t\t\t.ok();\n\t}\n}\n\n#[cfg(unix)]\nmod osc52 {\n\tuse base64::{Engine, engine::general_purpose};\n\n\t#[derive(Debug)]\n\tpub struct SetClipboard {\n\t\tcontent: String,\n\t}\n\n\timpl SetClipboard {\n\t\tpub fn new(content: &[u8]) -> Self {\n\t\t\tSelf { content: general_purpose::STANDARD.encode(content) }\n\t\t}\n\t}\n\n\timpl crossterm::Command for SetClipboard {\n\t\tfn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {\n\t\t\twrite!(f, \"\\x1b]52;c;{}\\x1b\\\\\", self.content)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/actor/actor.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, succ};\nuse yazi_shared::{data::Data, event::ActionCow};\n\nuse crate::input::{Input, InputMode};\n\nimpl Input {\n\tpub fn execute(&mut self, action: ActionCow) -> Result<Data> {\n\t\tmacro_rules! on {\n\t\t\t($name:ident) => {\n\t\t\t\tif action.name == stringify!($name) {\n\t\t\t\t\treturn act!($name, self, action);\n\t\t\t\t}\n\t\t\t};\n\t\t\t($name:ident, $alias:literal) => {\n\t\t\t\tif action.name == $alias {\n\t\t\t\t\treturn act!($name, self, action);\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\ton!(r#move, \"move\");\n\t\ton!(backward);\n\t\ton!(forward);\n\n\t\tmatch self.mode() {\n\t\t\tInputMode::Normal => {\n\t\t\t\ton!(insert);\n\t\t\t\ton!(visual);\n\t\t\t\ton!(replace);\n\n\t\t\t\ton!(delete);\n\t\t\t\ton!(yank);\n\t\t\t\ton!(paste);\n\n\t\t\t\ton!(undo);\n\t\t\t\ton!(redo);\n\n\t\t\t\ton!(casefy);\n\t\t\t}\n\t\t\tInputMode::Insert => {\n\t\t\t\ton!(visual);\n\n\t\t\t\ton!(backspace);\n\t\t\t\ton!(kill);\n\t\t\t}\n\t\t\tInputMode::Replace => {}\n\t\t}\n\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/actor/backspace.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_shared::data::Data;\n\nuse crate::input::{Input, parser::BackspaceOpt};\n\nimpl Input {\n\tpub fn backspace(&mut self, opt: BackspaceOpt) -> Result<Data> {\n\t\tlet snap = self.snap_mut();\n\t\tif !opt.under && snap.cursor < 1 {\n\t\t\tsucc!();\n\t\t} else if opt.under && snap.cursor >= snap.count() {\n\t\t\tsucc!();\n\t\t}\n\n\t\tif opt.under {\n\t\t\tsnap.value.remove(snap.idx(snap.cursor).unwrap());\n\t\t\tact!(r#move, self)?;\n\t\t} else {\n\t\t\tsnap.value.remove(snap.idx(snap.cursor - 1).unwrap());\n\t\t\tact!(r#move, self, -1)?;\n\t\t}\n\n\t\tself.flush_type();\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/actor/backward.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, succ};\nuse yazi_shared::{CharKind, data::Data};\n\nuse crate::input::{Input, parser::BackwardOpt};\n\nimpl Input {\n\tpub fn backward(&mut self, opt: BackwardOpt) -> Result<Data> {\n\t\tlet snap = self.snap();\n\t\tif snap.cursor == 0 {\n\t\t\treturn act!(r#move, self);\n\t\t}\n\n\t\tlet idx = snap.idx(snap.cursor).unwrap_or(snap.len());\n\t\tlet mut it = snap.value[..idx].chars().rev().enumerate();\n\t\tlet mut prev = CharKind::new(it.next().unwrap().1);\n\t\tfor (i, c) in it {\n\t\t\tlet k = CharKind::new(c);\n\t\t\tif prev != CharKind::Space && prev.vary(k, opt.far) {\n\t\t\t\treturn act!(r#move, self, -(i as isize));\n\t\t\t}\n\t\t\tprev = k;\n\t\t}\n\n\t\tif prev != CharKind::Space {\n\t\t\tact!(r#move, self, -(snap.len() as isize))?;\n\t\t}\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/actor/casefy.rs",
    "content": "use std::ops::Range;\n\nuse anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_shared::data::Data;\n\nuse crate::input::{Input, op::InputOp, parser::CasefyOpt};\n\nimpl Input {\n\tpub fn casefy(&mut self, opt: CasefyOpt) -> Result<Data> {\n\t\tlet snap = self.snap_mut();\n\t\tif !matches!(snap.op, InputOp::Select(_)) {\n\t\t\tsucc!();\n\t\t}\n\n\t\tlet range = snap.op.range(snap.cursor, true).unwrap();\n\t\tlet Range { start, end } = snap.idx(range.start)..snap.idx(range.end);\n\n\t\tlet (start, end) = (start.unwrap(), end.unwrap());\n\t\tlet casefied = opt.transform(&snap.value[start..end]);\n\n\t\tsnap.value.replace_range(start..end, &casefied);\n\t\tsnap.op = InputOp::None;\n\t\tsnap.cursor = range.start;\n\t\tself.snaps.tag(self.limit).then(|| self.flush_type());\n\n\t\tact!(r#move, self)?;\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/actor/complete.rs",
    "content": "use std::path::MAIN_SEPARATOR_STR;\n\nuse anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_shared::data::Data;\n\nuse crate::input::{Input, SEPARATOR, parser::CompleteOpt};\n\nimpl Input {\n\tpub fn complete(&mut self, opt: CompleteOpt) -> Result<Data> {\n\t\tlet (before, after) = self.partition();\n\t\tlet new = if let Some((prefix, _)) = before.rsplit_once(SEPARATOR) {\n\t\t\tformat!(\"{prefix}/{}{after}\", opt.completable()).replace(SEPARATOR, MAIN_SEPARATOR_STR)\n\t\t} else {\n\t\t\tformat!(\"{}{after}\", opt.completable()).replace(SEPARATOR, MAIN_SEPARATOR_STR)\n\t\t};\n\n\t\tlet snap = self.snap_mut();\n\t\tif new == snap.value {\n\t\t\tsucc!();\n\t\t}\n\n\t\tlet delta = new.chars().count() as isize - snap.count() as isize;\n\t\tsnap.value = new;\n\n\t\tact!(r#move, self, delta)?;\n\t\tself.flush_type();\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/actor/delete.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_shared::data::Data;\n\nuse crate::input::{Input, op::InputOp, parser::DeleteOpt};\n\nimpl Input {\n\tpub fn delete(&mut self, opt: DeleteOpt) -> Result<Data> {\n\t\tmatch self.snap().op {\n\t\t\tInputOp::None => {\n\t\t\t\tself.snap_mut().op = InputOp::Delete(opt.cut, opt.insert, self.snap().cursor);\n\t\t\t}\n\t\t\tInputOp::Select(start) => {\n\t\t\t\tself.snap_mut().op = InputOp::Delete(opt.cut, opt.insert, start);\n\t\t\t\trender!(self.handle_op(self.snap().cursor, true));\n\t\t\t\tact!(r#move, self)?;\n\t\t\t}\n\t\t\tInputOp::Delete(..) => {\n\t\t\t\tself.snap_mut().op = InputOp::Delete(opt.cut, opt.insert, 0);\n\t\t\t\tact!(r#move, self, self.snap().len() as isize)?;\n\t\t\t}\n\t\t\t_ => {}\n\t\t}\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/actor/escape.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, succ};\nuse yazi_shared::data::Data;\n\nuse crate::input::{Input, InputMode, op::InputOp};\n\nimpl Input {\n\tpub fn escape(&mut self, _: ()) -> Result<Data> {\n\t\tlet snap = self.snap_mut();\n\t\tmatch snap.mode {\n\t\t\tInputMode::Normal => {\n\t\t\t\tsnap.op = InputOp::None;\n\t\t\t}\n\t\t\tInputMode::Insert => {\n\t\t\t\tsnap.mode = InputMode::Normal;\n\t\t\t\tact!(r#move, self, -1)?;\n\t\t\t}\n\t\t\tInputMode::Replace => {\n\t\t\t\tsnap.mode = InputMode::Normal;\n\t\t\t}\n\t\t}\n\t\tself.snaps.tag(self.limit);\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/actor/forward.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::act;\nuse yazi_shared::{CharKind, data::Data};\n\nuse crate::input::{Input, op::InputOp, parser::ForwardOpt};\n\nimpl Input {\n\tpub fn forward(&mut self, opt: ForwardOpt) -> Result<Data> {\n\t\tlet snap = self.snap();\n\n\t\tlet mut it = snap.value.chars().skip(snap.cursor).enumerate();\n\t\tlet Some(mut prev) = it.next().map(|(_, c)| CharKind::new(c)) else {\n\t\t\treturn act!(r#move, self);\n\t\t};\n\n\t\tfor (i, c) in it {\n\t\t\tlet k = CharKind::new(c);\n\t\t\tlet b = if opt.end_of_word {\n\t\t\t\tprev != CharKind::Space && prev.vary(k, opt.far) && i != 1\n\t\t\t} else {\n\t\t\t\tk != CharKind::Space && k.vary(prev, opt.far)\n\t\t\t};\n\t\t\tif b && !matches!(snap.op, InputOp::None | InputOp::Select(_)) {\n\t\t\t\treturn act!(r#move, self, i as isize);\n\t\t\t} else if b {\n\t\t\t\treturn act!(\n\t\t\t\t\tr#move,\n\t\t\t\t\tself,\n\t\t\t\t\tif opt.end_of_word { i - snap.mode.delta() } else { i } as isize\n\t\t\t\t);\n\t\t\t}\n\t\t\tprev = k;\n\t\t}\n\n\t\tact!(r#move, self, snap.len() as isize)\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/actor/insert.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_shared::data::Data;\n\nuse crate::input::{Input, InputMode, op::InputOp, parser::InsertOpt};\n\nimpl Input {\n\tpub fn insert(&mut self, opt: InsertOpt) -> Result<Data> {\n\t\tlet snap = self.snap_mut();\n\t\tif snap.mode == InputMode::Normal {\n\t\t\tsnap.op = InputOp::None;\n\t\t\tsnap.mode = InputMode::Insert;\n\t\t} else {\n\t\t\tsucc!();\n\t\t}\n\n\t\tif opt.append {\n\t\t\tact!(r#move, self, 1)?;\n\t\t}\n\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/actor/kill.rs",
    "content": "use std::ops::RangeBounds;\n\nuse anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_shared::{CharKind, data::Data};\n\nuse crate::input::{Input, parser::KillOpt};\n\nimpl Input {\n\tpub fn kill(&mut self, opt: KillOpt) -> Result<Data> {\n\t\tlet snap = self.snap_mut();\n\t\tmatch opt.kind.as_ref() {\n\t\t\t\"all\" => self.kill_range(..),\n\t\t\t\"bol\" => {\n\t\t\t\tlet end = snap.idx(snap.cursor).unwrap_or(snap.len());\n\t\t\t\tself.kill_range(..end)\n\t\t\t}\n\t\t\t\"eol\" => {\n\t\t\t\tlet start = snap.idx(snap.cursor).unwrap_or(snap.len());\n\t\t\t\tself.kill_range(start..)\n\t\t\t}\n\t\t\t\"backward\" => {\n\t\t\t\tlet end = snap.idx(snap.cursor).unwrap_or(snap.len());\n\t\t\t\tlet start = end - Self::find_word_boundary(snap.value[..end].chars().rev());\n\t\t\t\tself.kill_range(start..end)\n\t\t\t}\n\t\t\t\"forward\" => {\n\t\t\t\tlet start = snap.idx(snap.cursor).unwrap_or(snap.len());\n\t\t\t\tlet end = start + Self::find_word_boundary(snap.value[start..].chars());\n\t\t\t\tself.kill_range(start..end)\n\t\t\t}\n\t\t\t_ => succ!(),\n\t\t}\n\t}\n\n\tfn kill_range(&mut self, range: impl RangeBounds<usize>) -> Result<Data> {\n\t\tlet snap = self.snap_mut();\n\t\tsnap.cursor = match range.start_bound() {\n\t\t\tstd::ops::Bound::Included(i) => *i,\n\t\t\tstd::ops::Bound::Excluded(_) => unreachable!(),\n\t\t\tstd::ops::Bound::Unbounded => 0,\n\t\t};\n\t\tif snap.value.drain(range).next().is_none() {\n\t\t\tsucc!();\n\t\t}\n\n\t\tact!(r#move, self)?;\n\t\tself.flush_type();\n\t\tsucc!(render!());\n\t}\n\n\t/// Searches for a word boundary and returns the movement in the cursor\n\t/// position.\n\t///\n\t/// A word boundary is where the [`CharKind`] changes.\n\t///\n\t/// If `skip_whitespace_first` is true, we skip initial whitespace.\n\t/// Otherwise, we skip whitespace after reaching a word boundary.\n\t///\n\t/// If `stop_before_boundary` is true, returns how many characters the cursor\n\t/// needs to move to be at the character *BEFORE* the word boundary, or until\n\t/// the end of the iterator.\n\t///\n\t/// Otherwise, returns how many characters to move to reach right *AFTER* the\n\t/// word boundary, or the end of the iterator.\n\tfn find_word_boundary(input: impl Iterator<Item = char> + Clone) -> usize {\n\t\tfn count_spaces(input: impl Iterator<Item = char>) -> usize {\n\t\t\t// Move until we don't see any more whitespace.\n\t\t\tinput.take_while(|&c| CharKind::new(c) == CharKind::Space).count()\n\t\t}\n\n\t\tfn count_characters(mut input: impl Iterator<Item = char>) -> usize {\n\t\t\t// Determine the current character class.\n\t\t\tlet first = match input.next() {\n\t\t\t\tSome(c) => CharKind::new(c),\n\t\t\t\tNone => return 0,\n\t\t\t};\n\n\t\t\t// Move until we see a different character class or the end of the iterator.\n\t\t\tinput.take_while(|&c| CharKind::new(c) == first).count() + 1\n\t\t}\n\n\t\tlet n = count_spaces(input.clone());\n\t\tlet n = n + count_characters(input.clone().skip(n));\n\t\tinput.take(n).fold(0, |acc, c| acc + c.len_utf8())\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/actor/mod.rs",
    "content": "yazi_macro::mod_flat!(actor backspace backward casefy complete delete escape forward insert kill paste r#move r#type redo replace undo visual yank);\n"
  },
  {
    "path": "yazi-widgets/src/input/actor/move.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{render, succ};\nuse yazi_shared::data::Data;\n\nuse crate::input::{Input, op::InputOp, parser::MoveOpt, snap::InputSnap};\n\nimpl Input {\n\tpub fn r#move(&mut self, opt: MoveOpt) -> Result<Data> {\n\t\tlet snap = self.snap();\n\t\tif opt.in_operating && snap.op == InputOp::None {\n\t\t\tsucc!();\n\t\t}\n\n\t\tlet o_cur = snap.cursor;\n\t\trender!(self.handle_op(opt.step.add(&snap.value, snap.cursor), false));\n\t\tlet n_cur = self.snap().cursor;\n\n\t\tlet (limit, snap) = (self.limit, self.snap_mut());\n\t\tif snap.value.is_empty() {\n\t\t\tsucc!(snap.offset = 0);\n\t\t}\n\n\t\tlet (o_off, scrolloff) = (snap.offset, 5.min(limit / 2));\n\t\tsnap.offset = if n_cur <= o_cur {\n\t\t\tlet it = snap.slice(0..n_cur).chars().rev().map(|c| if snap.obscure { '•' } else { c });\n\t\t\tlet pad = InputSnap::find_window(it, 0, scrolloff).end;\n\n\t\t\tif n_cur >= o_off { snap.offset.min(n_cur - pad) } else { n_cur - pad }\n\t\t} else {\n\t\t\tlet count = snap.count();\n\n\t\t\tlet it = snap.slice(n_cur..count).chars().map(|c| if snap.obscure { '•' } else { c });\n\t\t\tlet pad = InputSnap::find_window(it, 0, scrolloff + snap.mode.delta()).end;\n\n\t\t\tlet it = snap.slice(0..n_cur + pad).chars().rev().map(|c| if snap.obscure { '•' } else { c });\n\t\t\tlet max = InputSnap::find_window(it, 0, limit).end;\n\n\t\t\tif snap.width(o_off..n_cur) < limit as u16 {\n\t\t\t\tsnap.offset.max(n_cur + pad - max)\n\t\t\t} else {\n\t\t\t\tn_cur + pad - max\n\t\t\t}\n\t\t};\n\n\t\tif n_cur != o_cur {\n\t\t\tself.flush_trigger(false);\n\t\t}\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/actor/paste.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_shared::data::Data;\n\nuse crate::{CLIPBOARD, input::{Input, op::InputOp, parser::PasteOpt}};\n\nimpl Input {\n\tpub fn paste(&mut self, opt: PasteOpt) -> Result<Data> {\n\t\tif let Some(start) = self.snap().op.start() {\n\t\t\tself.snap_mut().op = InputOp::Delete(false, false, start);\n\t\t\tself.handle_op(self.snap().cursor, true);\n\t\t}\n\n\t\tlet s = futures::executor::block_on(CLIPBOARD.get());\n\t\tif s.is_empty() {\n\t\t\tsucc!();\n\t\t}\n\n\t\tact!(insert, self, !opt.before)?;\n\t\tself.type_str(&String::from_utf8_lossy(&s))?;\n\t\tact!(escape, self)?;\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/actor/redo.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render};\nuse yazi_shared::data::Data;\n\nuse crate::input::Input;\n\nimpl Input {\n\tpub fn redo(&mut self, _: ()) -> Result<Data> {\n\t\trender!(self.snaps.redo());\n\n\t\tact!(r#move, self)\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/actor/replace.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{render, succ};\nuse yazi_shared::{data::Data, replace_cow};\n\nuse crate::input::{Input, InputMode, op::InputOp};\n\nimpl Input {\n\tpub fn replace(&mut self, _: ()) -> Result<Data> {\n\t\tlet snap = self.snap_mut();\n\t\tif snap.mode == InputMode::Normal {\n\t\t\tsnap.op = InputOp::None;\n\t\t\tsnap.mode = InputMode::Replace;\n\t\t\trender!();\n\t\t}\n\t\tsucc!();\n\t}\n\n\tpub fn replace_str(&mut self, s: &str) -> Result<Data> {\n\t\tlet s = replace_cow(replace_cow(s, \"\\r\", \" \"), \"\\n\", \" \");\n\n\t\tlet snap = self.snap_mut();\n\t\tsnap.mode = InputMode::Normal;\n\n\t\tlet start = snap.idx(snap.cursor).unwrap();\n\t\tlet mut it = snap.value[start..].char_indices();\n\t\tmatch (it.next(), it.next()) {\n\t\t\t(None, _) => {}\n\t\t\t(Some(_), None) => snap.value.replace_range(start..snap.len(), &s),\n\t\t\t(Some(_), Some((len, _))) => snap.value.replace_range(start..start + len, &s),\n\t\t}\n\n\t\tself.snaps.tag(self.limit).then(|| self.flush_type());\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/actor/type.rs",
    "content": "use anyhow::Result;\nuse yazi_config::keymap::Key;\nuse yazi_macro::{act, render, succ};\nuse yazi_shared::{data::Data, replace_cow};\n\nuse crate::input::{Input, InputMode};\n\nimpl Input {\n\tpub fn r#type(&mut self, key: &Key) -> Result<bool> {\n\t\tlet Some(c) = key.plain() else { return Ok(false) };\n\n\t\tif self.mode() == InputMode::Insert {\n\t\t\tself.type_str(c.encode_utf8(&mut [0; 4]))?;\n\t\t\treturn Ok(true);\n\t\t} else if self.mode() == InputMode::Replace {\n\t\t\tself.replace_str(c.encode_utf8(&mut [0; 4]))?;\n\t\t\treturn Ok(true);\n\t\t}\n\n\t\tOk(false)\n\t}\n\n\tpub fn type_str(&mut self, s: &str) -> Result<Data> {\n\t\tlet s = replace_cow(replace_cow(s, \"\\r\", \" \"), \"\\n\", \" \");\n\n\t\tlet snap = self.snap_mut();\n\t\tif snap.cursor < 1 {\n\t\t\tsnap.value.insert_str(0, &s);\n\t\t} else {\n\t\t\tsnap.value.insert_str(snap.idx(snap.cursor).unwrap(), &s);\n\t\t}\n\n\t\tact!(r#move, self, s.chars().count() as isize)?;\n\t\tself.flush_type();\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/actor/undo.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_shared::data::Data;\n\nuse crate::input::{Input, InputMode, InputOp};\n\nimpl Input {\n\tpub fn undo(&mut self, _: ()) -> Result<Data> {\n\t\tif self.snap().op != InputOp::None {\n\t\t\tsucc!();\n\t\t}\n\n\t\tif !self.snaps.undo() {\n\t\t\tsucc!();\n\t\t}\n\n\t\tact!(r#move, self)?;\n\t\tif self.snap().mode == InputMode::Insert {\n\t\t\tact!(escape, self)?;\n\t\t}\n\n\t\tsucc!(render!());\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/actor/visual.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_shared::data::Data;\n\nuse crate::input::{Input, InputMode, op::InputOp};\n\nimpl Input {\n\tpub fn visual(&mut self, _: ()) -> Result<Data> {\n\t\tif self.snap().mode != InputMode::Normal {\n\t\t\tact!(escape, self)?;\n\t\t}\n\n\t\tlet snap = self.snap_mut();\n\t\tif let InputOp::Select(_) = snap.op {\n\t\t\tsucc!();\n\t\t}\n\n\t\tif !snap.value.is_empty() {\n\t\t\tsnap.op = InputOp::Select(snap.cursor);\n\t\t\trender!();\n\t\t}\n\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/actor/yank.rs",
    "content": "use anyhow::Result;\nuse yazi_macro::{act, render, succ};\nuse yazi_shared::data::Data;\n\nuse crate::input::{Input, op::InputOp};\n\nimpl Input {\n\tpub fn yank(&mut self, _: ()) -> Result<Data> {\n\t\tmatch self.snap().op {\n\t\t\tInputOp::None => {\n\t\t\t\tself.snap_mut().op = InputOp::Yank(self.snap().cursor);\n\t\t\t}\n\t\t\tInputOp::Select(start) => {\n\t\t\t\tself.snap_mut().op = InputOp::Yank(start);\n\t\t\t\trender!(self.handle_op(self.snap().cursor, true));\n\t\t\t\tact!(r#move, self)?;\n\t\t\t}\n\t\t\tInputOp::Yank(_) => {\n\t\t\t\tself.snap_mut().op = InputOp::Yank(0);\n\t\t\t\tact!(r#move, self, self.snap().len() as isize)?;\n\t\t\t}\n\t\t\t_ => {}\n\t\t}\n\t\tsucc!();\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/event.rs",
    "content": "use yazi_shared::Id;\n\n#[derive(Debug)]\npub enum InputEvent {\n\tSubmit(String),\n\tCancel(String),\n\n\tType(String),\n\tTrigger(String, Option<Id>),\n}\n\nimpl InputEvent {\n\tpub fn is_submit(&self) -> bool { matches!(self, Self::Submit(_)) }\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/input.rs",
    "content": "use std::{borrow::Cow, ops::Range};\n\nuse anyhow::Result;\nuse crossterm::cursor::SetCursorStyle;\nuse tokio::sync::mpsc;\nuse yazi_config::YAZI;\nuse yazi_macro::act;\nuse yazi_shared::Ids;\n\nuse super::{InputSnap, InputSnaps, mode::InputMode, op::InputOp};\nuse crate::{CLIPBOARD, input::{InputEvent, InputOpt, SEPARATOR}};\n\n#[derive(Default)]\npub struct Input {\n\tpub snaps:      InputSnaps,\n\tpub limit:      usize,\n\tpub obscure:    bool,\n\tpub realtime:   bool,\n\tpub completion: bool,\n\n\tpub tx:     Option<mpsc::UnboundedSender<InputEvent>>,\n\tpub ticket: Ids,\n}\n\nimpl Input {\n\tpub fn new(opt: InputOpt) -> Result<Self> {\n\t\tlet limit = opt.cfg.position.offset.width.saturating_sub(YAZI.input.border()) as usize;\n\t\tlet mut input = Self {\n\t\t\tsnaps: InputSnaps::new(opt.cfg.value, opt.cfg.obscure, limit),\n\t\t\tlimit,\n\t\t\tobscure: opt.cfg.obscure,\n\t\t\trealtime: opt.cfg.realtime,\n\t\t\tcompletion: opt.cfg.completion,\n\n\t\t\ttx: Some(opt.tx),\n\t\t\t..Default::default()\n\t\t};\n\n\t\tif let Some(cursor) = opt.cfg.cursor {\n\t\t\tinput.snap_mut().cursor = cursor;\n\t\t\tact!(r#move, input)?;\n\t\t}\n\n\t\tOk(input)\n\t}\n\n\tpub(super) fn handle_op(&mut self, cursor: usize, include: bool) -> bool {\n\t\tlet old = self.snap().clone();\n\t\tlet snap = self.snap_mut();\n\n\t\tmatch snap.op {\n\t\t\tInputOp::None | InputOp::Select(_) => {\n\t\t\t\tsnap.cursor = cursor;\n\t\t\t}\n\t\t\tInputOp::Delete(cut, insert, _) => {\n\t\t\t\tlet range = snap.op.range(cursor, include).unwrap();\n\t\t\t\tlet Range { start, end } = snap.idx(range.start)..snap.idx(range.end);\n\n\t\t\t\tlet drain = snap.value.drain(start.unwrap()..end.unwrap()).collect::<String>();\n\t\t\t\tif cut {\n\t\t\t\t\tfutures::executor::block_on(CLIPBOARD.set(&drain));\n\t\t\t\t}\n\n\t\t\t\tsnap.op = InputOp::None;\n\t\t\t\tsnap.mode = if insert { InputMode::Insert } else { InputMode::Normal };\n\t\t\t\tsnap.cursor = range.start;\n\t\t\t}\n\t\t\tInputOp::Yank(_) => {\n\t\t\t\tlet range = snap.op.range(cursor, include).unwrap();\n\t\t\t\tlet Range { start, end } = snap.idx(range.start)..snap.idx(range.end);\n\t\t\t\tlet yanked = &snap.value[start.unwrap()..end.unwrap()];\n\n\t\t\t\tsnap.op = InputOp::None;\n\t\t\t\tfutures::executor::block_on(CLIPBOARD.set(yanked));\n\t\t\t}\n\t\t};\n\n\t\tsnap.cursor = snap.count().saturating_sub(snap.mode.delta()).min(snap.cursor);\n\t\tif snap == &old {\n\t\t\treturn false;\n\t\t}\n\t\tif !matches!(old.op, InputOp::None | InputOp::Select(_)) {\n\t\t\tself.snaps.tag(self.limit).then(|| self.flush_type());\n\t\t}\n\t\ttrue\n\t}\n\n\tpub(super) fn flush_type(&mut self) {\n\t\tself.ticket.next();\n\t\tif let Some(tx) = self.tx.as_ref().filter(|_| self.realtime) {\n\t\t\ttx.send(InputEvent::Type(self.value().to_owned())).ok();\n\t\t}\n\n\t\tself.flush_trigger(true);\n\t}\n\n\tpub(super) fn flush_trigger(&self, force: bool) {\n\t\tif let Some(tx) = self.tx.as_ref().filter(|_| self.completion) {\n\t\t\ttx.send(InputEvent::Trigger(\n\t\t\t\tself.partition().0.to_owned(),\n\t\t\t\tSome(self.ticket.current()).filter(|_| !force),\n\t\t\t))\n\t\t\t.ok();\n\t\t}\n\t}\n}\n\nimpl Input {\n\tpub fn value(&self) -> &str { &self.snap().value }\n\n\tpub fn display(&self) -> Cow<'_, str> {\n\t\tif self.obscure {\n\t\t\t\"•\".repeat(self.snap().window(self.limit).len()).into()\n\t\t} else {\n\t\t\tself.snap().slice(self.snap().window(self.limit)).into()\n\t\t}\n\t}\n\n\tpub fn mode(&self) -> InputMode { self.snap().mode }\n\n\tpub fn cursor(&self) -> u16 { self.snap().width(self.snap().offset..self.snap().cursor) }\n\n\tpub fn cursor_shape(&self) -> SetCursorStyle {\n\t\tuse InputMode as M;\n\n\t\tmatch self.mode() {\n\t\t\tM::Normal if YAZI.input.cursor_blink => SetCursorStyle::BlinkingBlock,\n\t\t\tM::Normal if !YAZI.input.cursor_blink => SetCursorStyle::SteadyBlock,\n\t\t\tM::Insert if YAZI.input.cursor_blink => SetCursorStyle::BlinkingBar,\n\t\t\tM::Insert if !YAZI.input.cursor_blink => SetCursorStyle::SteadyBar,\n\t\t\tM::Replace if YAZI.input.cursor_blink => SetCursorStyle::BlinkingUnderScore,\n\t\t\tM::Replace if !YAZI.input.cursor_blink => SetCursorStyle::SteadyUnderScore,\n\t\t\tM::Normal | M::Insert | M::Replace => unreachable!(),\n\t\t}\n\t}\n\n\tpub fn selected(&self) -> Option<Range<u16>> {\n\t\tlet snap = self.snap();\n\t\tlet start = snap.op.start()?;\n\n\t\tlet (start, end) =\n\t\t\tif start < snap.cursor { (start, snap.cursor) } else { (snap.cursor + 1, start + 1) };\n\n\t\tlet win = snap.window(self.limit);\n\t\tlet Range { start, end } = start.max(win.start)..end.min(win.end);\n\n\t\tlet s = snap.width(snap.offset..start);\n\t\tSome(s..s + snap.width(start..end))\n\t}\n\n\tpub fn partition(&self) -> (&str, &str) {\n\t\tlet snap = self.snap();\n\t\tlet idx = snap.idx(snap.cursor).unwrap();\n\n\t\tif let Some(sep) = snap.value[idx..].find(SEPARATOR).map(|i| idx + i) {\n\t\t\t(&snap.value[..sep], &snap.value[sep + 1..])\n\t\t} else {\n\t\t\t(&snap.value, \"\")\n\t\t}\n\t}\n\n\tpub fn snap(&self) -> &InputSnap { self.snaps.current() }\n\n\tpub fn snap_mut(&mut self) -> &mut InputSnap { self.snaps.current_mut() }\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/mod.rs",
    "content": "yazi_macro::mod_pub!(actor parser);\n\nyazi_macro::mod_flat!(event input mode op opt separator snap snaps widget);\n"
  },
  {
    "path": "yazi-widgets/src/input/mode.rs",
    "content": "#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]\npub enum InputMode {\n\tNormal,\n\t#[default]\n\tInsert,\n\tReplace,\n}\n\nimpl InputMode {\n\t#[inline]\n\tpub(super) fn delta(&self) -> usize { (*self != Self::Insert) as usize }\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/op.rs",
    "content": "use std::ops::Range;\n\n#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]\npub enum InputOp {\n\t#[default]\n\tNone,\n\tSelect(usize),\n\tDelete(bool, bool, usize), // cut, insert, start\n\tYank(usize),\n}\n\nimpl InputOp {\n\t#[inline]\n\tpub(super) fn start(&self) -> Option<usize> {\n\t\tmatch self {\n\t\t\tSelf::None => None,\n\t\t\tSelf::Select(s) => Some(*s),\n\t\t\tSelf::Delete(.., s) => Some(*s),\n\t\t\tSelf::Yank(s) => Some(*s),\n\t\t}\n\t}\n\n\t#[inline]\n\tpub(super) fn range(&self, cursor: usize, include: bool) -> Option<Range<usize>> {\n\t\tself\n\t\t\t.start()\n\t\t\t.map(|s| if s <= cursor { (s, cursor) } else { (cursor, s) })\n\t\t\t.map(|(s, e)| s..e + include as usize)\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/opt.rs",
    "content": "use anyhow::bail;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse tokio::sync::mpsc;\nuse yazi_config::popup::InputCfg;\nuse yazi_shared::event::ActionCow;\n\nuse crate::input::InputEvent;\n\n#[derive(Debug)]\npub struct InputOpt {\n\tpub cfg: InputCfg,\n\tpub tx:  mpsc::UnboundedSender<InputEvent>,\n}\n\nimpl TryFrom<ActionCow> for InputOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tlet Some(cfg) = a.take_any(\"cfg\") else {\n\t\t\tbail!(\"Invalid 'cfg' in InputOpt\");\n\t\t};\n\n\t\tlet Some(tx) = a.take_any(\"tx\") else {\n\t\t\tbail!(\"Invalid 'tx' in InputOpt\");\n\t\t};\n\n\t\tOk(Self { cfg, tx })\n\t}\n}\n\nimpl FromLua for InputOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for InputOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/parser/backspace.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug, Default)]\npub struct BackspaceOpt {\n\tpub under: bool,\n}\n\nimpl From<ActionCow> for BackspaceOpt {\n\tfn from(a: ActionCow) -> Self { Self { under: a.bool(\"under\") } }\n}\n\nimpl From<bool> for BackspaceOpt {\n\tfn from(under: bool) -> Self { Self { under } }\n}\n\nimpl FromLua for BackspaceOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for BackspaceOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/parser/backward.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug)]\npub struct BackwardOpt {\n\tpub far: bool,\n}\n\nimpl From<ActionCow> for BackwardOpt {\n\tfn from(a: ActionCow) -> Self { Self { far: a.bool(\"far\") } }\n}\n\nimpl FromLua for BackwardOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for BackwardOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/parser/casefy.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug)]\npub struct CasefyOpt {\n\tpub upper: bool,\n}\n\nimpl From<ActionCow> for CasefyOpt {\n\tfn from(a: ActionCow) -> Self { Self { upper: a.str(0) == \"upper\" } }\n}\n\nimpl FromLua for CasefyOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for CasefyOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl CasefyOpt {\n\tpub fn transform(&self, s: &str) -> String {\n\t\tif self.upper { s.to_ascii_uppercase() } else { s.to_ascii_lowercase() }\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/parser/complete.rs",
    "content": "use std::path::MAIN_SEPARATOR_STR;\n\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{Id, event::ActionCow, strand::{StrandBuf, StrandLike}};\n\n#[derive(Debug)]\npub struct CompleteOpt {\n\tpub name:   StrandBuf,\n\tpub is_dir: bool,\n\tpub ticket: Id,\n}\n\nimpl TryFrom<ActionCow> for CompleteOpt {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {\n\t\tOk(Self {\n\t\t\tname:   a.take(\"name\")?,\n\t\t\tis_dir: a.bool(\"is_dir\"),\n\t\t\tticket: a.get(\"ticket\").unwrap_or_default(),\n\t\t})\n\t}\n}\n\nimpl FromLua for CompleteOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for CompleteOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl CompleteOpt {\n\tpub(crate) fn completable(&self) -> String {\n\t\tformat!(\"{}{}\", self.name.to_string_lossy(), if self.is_dir { MAIN_SEPARATOR_STR } else { \"\" })\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/parser/delete.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug)]\npub struct DeleteOpt {\n\tpub cut:    bool,\n\tpub insert: bool,\n}\n\nimpl From<ActionCow> for DeleteOpt {\n\tfn from(a: ActionCow) -> Self { Self { cut: a.bool(\"cut\"), insert: a.bool(\"insert\") } }\n}\n\nimpl FromLua for DeleteOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for DeleteOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/parser/forward.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug)]\npub struct ForwardOpt {\n\tpub far:         bool,\n\tpub end_of_word: bool,\n}\n\nimpl From<ActionCow> for ForwardOpt {\n\tfn from(a: ActionCow) -> Self { Self { far: a.bool(\"far\"), end_of_word: a.bool(\"end-of-word\") } }\n}\n\nimpl FromLua for ForwardOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for ForwardOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/parser/insert.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug)]\npub struct InsertOpt {\n\tpub append: bool,\n}\n\nimpl From<ActionCow> for InsertOpt {\n\tfn from(a: ActionCow) -> Self { Self { append: a.bool(\"append\") } }\n}\n\nimpl From<bool> for InsertOpt {\n\tfn from(append: bool) -> Self { Self { append } }\n}\n\nimpl FromLua for InsertOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for InsertOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/parser/kill.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{SStr, event::ActionCow};\n\n#[derive(Debug)]\npub struct KillOpt {\n\tpub kind: SStr,\n}\n\nimpl From<ActionCow> for KillOpt {\n\tfn from(mut a: ActionCow) -> Self { Self { kind: a.take_first().unwrap_or_default() } }\n}\n\nimpl FromLua for KillOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for KillOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/parser/mod.rs",
    "content": "yazi_macro::mod_flat!(backspace backward casefy complete delete forward insert kill paste r#move);\n"
  },
  {
    "path": "yazi-widgets/src/input/parser/move.rs",
    "content": "use std::{num::ParseIntError, str::FromStr};\n\nuse anyhow::bail;\nuse mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::{data::Data, event::ActionCow};\n\n#[derive(Debug, Default)]\npub struct MoveOpt {\n\tpub step:         MoveOptStep,\n\tpub in_operating: bool,\n}\n\nimpl From<ActionCow> for MoveOpt {\n\tfn from(a: ActionCow) -> Self {\n\t\tSelf { step: a.first().ok().unwrap_or_default(), in_operating: a.bool(\"in-operating\") }\n\t}\n}\n\nimpl From<isize> for MoveOpt {\n\tfn from(step: isize) -> Self { Self { step: step.into(), in_operating: false } }\n}\n\nimpl FromLua for MoveOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for MoveOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n\n// --- Step\n#[derive(Debug)]\npub enum MoveOptStep {\n\tOffset(isize),\n\tBol,\n\tEol,\n\tFirstChar,\n}\n\nimpl MoveOptStep {\n\tpub fn add(self, s: &str, cursor: usize) -> usize {\n\t\tmatch self {\n\t\t\tSelf::Offset(n) if n <= 0 => cursor.saturating_add_signed(n),\n\t\t\tSelf::Offset(n) => s.chars().count().min(cursor + n as usize),\n\t\t\tSelf::Bol => 0,\n\t\t\tSelf::Eol => s.chars().count(),\n\t\t\tSelf::FirstChar => {\n\t\t\t\ts.chars().enumerate().find(|(_, c)| !c.is_whitespace()).map_or(0, |(i, _)| i)\n\t\t\t}\n\t\t}\n\t}\n}\n\nimpl Default for MoveOptStep {\n\tfn default() -> Self { 0.into() }\n}\n\nimpl FromStr for MoveOptStep {\n\ttype Err = ParseIntError;\n\n\tfn from_str(s: &str) -> Result<Self, Self::Err> {\n\t\tOk(match s {\n\t\t\t\"bol\" => Self::Bol,\n\t\t\t\"eol\" => Self::Eol,\n\t\t\t\"first-char\" => Self::FirstChar,\n\t\t\ts => Self::Offset(s.parse()?),\n\t\t})\n\t}\n}\n\nimpl From<isize> for MoveOptStep {\n\tfn from(value: isize) -> Self { Self::Offset(value) }\n}\n\nimpl TryFrom<&Data> for MoveOptStep {\n\ttype Error = anyhow::Error;\n\n\tfn try_from(value: &Data) -> Result<Self, Self::Error> {\n\t\tOk(match value {\n\t\t\tData::String(s) => s.parse()?,\n\t\t\tData::Integer(i) => Self::from(*i as isize),\n\t\t\t_ => bail!(\"Invalid MoveOptStep data type: {value:?}\"),\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/parser/paste.rs",
    "content": "use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};\nuse yazi_shared::event::ActionCow;\n\n#[derive(Debug)]\npub struct PasteOpt {\n\tpub before: bool,\n}\n\nimpl From<ActionCow> for PasteOpt {\n\tfn from(a: ActionCow) -> Self { Self { before: a.bool(\"before\") } }\n}\n\nimpl FromLua for PasteOpt {\n\tfn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err(\"unsupported\".into_lua_err()) }\n}\n\nimpl IntoLua for PasteOpt {\n\tfn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err(\"unsupported\".into_lua_err()) }\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/separator.rs",
    "content": "#[cfg(windows)]\npub(super) const SEPARATOR: [char; 2] = ['/', '\\\\'];\n\n#[cfg(not(windows))]\npub(super) const SEPARATOR: char = std::path::MAIN_SEPARATOR;\n"
  },
  {
    "path": "yazi-widgets/src/input/snap.rs",
    "content": "use std::ops::Range;\n\nuse unicode_width::{UnicodeWidthChar, UnicodeWidthStr};\n\nuse super::{InputMode, InputOp};\n\n#[derive(Clone, Debug, Default, PartialEq, Eq)]\npub struct InputSnap {\n\tpub value: String,\n\n\tpub op: InputOp,\n\n\tpub mode:    InputMode,\n\tpub obscure: bool,\n\n\tpub offset: usize,\n\tpub cursor: usize,\n}\n\nimpl InputSnap {\n\tpub(super) fn new(value: String, obscure: bool, limit: usize) -> Self {\n\t\tlet mut snap = Self {\n\t\t\tvalue,\n\n\t\t\top: Default::default(),\n\n\t\t\tmode: Default::default(),\n\t\t\tobscure,\n\n\t\t\toffset: 0,\n\t\t\tcursor: usize::MAX,\n\t\t};\n\t\tsnap.resize(limit);\n\t\tsnap\n\t}\n\n\t#[inline]\n\tpub(super) fn resize(&mut self, limit: usize) {\n\t\tlet count = self.count();\n\t\tlet limit = if self.obscure {\n\t\t\tcount.min(limit)\n\t\t} else {\n\t\t\tSelf::find_window(self.value.chars().rev(), 0, limit).end\n\t\t};\n\n\t\tself.cursor = self.cursor.min(self.count().saturating_sub(self.mode.delta()));\n\t\tself.offset = if self.cursor < (self.offset + limit).min(count) {\n\t\t\tcount.saturating_sub(limit).min(self.offset)\n\t\t} else {\n\t\t\tcount.saturating_sub(limit).min(self.cursor.saturating_sub(limit) + 1)\n\t\t};\n\t}\n}\n\nimpl InputSnap {\n\tpub(super) fn len(&self) -> usize { self.value.len() }\n\n\tpub(super) fn count(&self) -> usize { self.value.chars().count() }\n\n\tpub(super) fn idx(&self, n: usize) -> Option<usize> {\n\t\tself\n\t\t\t.value\n\t\t\t.char_indices()\n\t\t\t.nth(n)\n\t\t\t.map(|(i, _)| i)\n\t\t\t.or_else(|| if n == self.count() { Some(self.len()) } else { None })\n\t}\n\n\tpub(super) fn slice(&self, range: Range<usize>) -> &str {\n\t\tlet (s, e) = (self.idx(range.start), self.idx(range.end));\n\t\t&self.value[s.unwrap()..e.unwrap()]\n\t}\n\n\tpub(super) fn width(&self, range: Range<usize>) -> u16 {\n\t\tif self.obscure { range.len() as u16 } else { self.slice(range).width() as u16 }\n\t}\n\n\tpub(super) fn window(&self, limit: usize) -> Range<usize> {\n\t\tSelf::find_window(\n\t\t\tself.value.chars().map(|c| if self.obscure { '•' } else { c }),\n\t\t\tself.offset,\n\t\t\tlimit,\n\t\t)\n\t}\n\n\tpub(super) fn find_window<T>(it: T, offset: usize, limit: usize) -> Range<usize>\n\twhere\n\t\tT: Iterator<Item = char>,\n\t{\n\t\tlet mut width = 0;\n\t\tlet mut range = None;\n\n\t\tfor (i, c) in it.enumerate().skip(offset) {\n\t\t\twidth += c.width().unwrap_or(0);\n\t\t\tif width > limit {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tmatch range {\n\t\t\t\tNone => range = Some(i..i + 1),\n\t\t\t\tSome(ref mut r) => r.end = i + 1,\n\t\t\t}\n\t\t}\n\t\trange.unwrap_or(0..0)\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/snaps.rs",
    "content": "use std::mem;\n\nuse super::InputSnap;\n\n#[derive(PartialEq, Eq)]\npub struct InputSnaps {\n\tidx:      usize,\n\tversions: Vec<InputSnap>,\n\tcurrent:  InputSnap,\n}\n\nimpl Default for InputSnaps {\n\tfn default() -> Self {\n\t\tSelf {\n\t\t\tidx:      0,\n\t\t\tversions: vec![InputSnap::new(String::new(), false, 0)],\n\t\t\tcurrent:  InputSnap::new(String::new(), false, 0),\n\t\t}\n\t}\n}\n\nimpl InputSnaps {\n\tpub fn new(value: String, obscure: bool, limit: usize) -> Self {\n\t\tlet current = InputSnap::new(value, obscure, limit);\n\t\tSelf { idx: 0, versions: vec![current.clone()], current }\n\t}\n\n\tpub(super) fn tag(&mut self, limit: usize) -> bool {\n\t\tif self.versions.len() <= self.idx {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Sync *current* cursor position to the *last* version:\n\t\t// \t\tSave offset/cursor/ect. of the *current* as the last version,\n\t\t// \t\twhile keeping the *last* value unchanged.\n\t\tlet value = mem::take(&mut self.versions[self.idx].value);\n\t\tself.versions[self.idx] = self.current.clone();\n\t\tself.versions[self.idx].value = value;\n\t\tself.versions[self.idx].resize(limit);\n\n\t\t// If the *current* value is the same as the *last* version\n\t\tif self.versions[self.idx].value == self.current.value {\n\t\t\treturn false;\n\t\t}\n\n\t\tself.versions.truncate(self.idx + 1);\n\t\tself.versions.push(self.current().clone());\n\t\tself.idx += 1;\n\t\ttrue\n\t}\n\n\tpub(super) fn undo(&mut self) -> bool {\n\t\tif self.idx == 0 {\n\t\t\treturn false;\n\t\t}\n\n\t\tself.idx -= 1;\n\t\tself.current = self.versions[self.idx].clone();\n\t\ttrue\n\t}\n\n\tpub(super) fn redo(&mut self) -> bool {\n\t\tif self.idx + 1 >= self.versions.len() {\n\t\t\treturn false;\n\t\t}\n\n\t\tself.idx += 1;\n\t\tself.current = self.versions[self.idx].clone();\n\t\ttrue\n\t}\n}\n\nimpl InputSnaps {\n\t#[inline]\n\tpub fn current(&self) -> &InputSnap { &self.current }\n\n\t#[inline]\n\tpub(super) fn current_mut(&mut self) -> &mut InputSnap { &mut self.current }\n}\n"
  },
  {
    "path": "yazi-widgets/src/input/widget.rs",
    "content": "use std::ops::Range;\n\nuse ratatui::{layout::Rect, text::Line, widgets::Widget};\nuse yazi_config::THEME;\n\nuse super::Input;\n\nimpl Widget for &Input {\n\tfn render(self, area: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)\n\twhere\n\t\tSelf: Sized,\n\t{\n\t\tcrate::Clear.render(area, buf);\n\n\t\tLine::styled(self.display(), THEME.input.value).render(area, buf);\n\n\t\tif let Some(Range { start, end }) = self.selected() {\n\t\t\tlet s = start.min(area.width);\n\t\t\tbuf.set_style(\n\t\t\t\tRect {\n\t\t\t\t\tx:      area.x + s,\n\t\t\t\t\ty:      area.y,\n\t\t\t\t\twidth:  (end - start).min(area.width - s),\n\t\t\t\t\theight: area.height.min(1),\n\t\t\t\t},\n\t\t\t\tTHEME.input.selected,\n\t\t\t);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/lib.rs",
    "content": "yazi_macro::mod_pub!(input);\n\nyazi_macro::mod_flat!(clear clipboard scrollable step);\n\npub fn init() { CLIPBOARD.with(<_>::default); }\n"
  },
  {
    "path": "yazi-widgets/src/scrollable.rs",
    "content": "use crate::Step;\n\npub trait Scrollable {\n\tfn total(&self) -> usize;\n\tfn limit(&self) -> usize;\n\tfn scrolloff(&self) -> usize { self.limit() / 2 }\n\tfn cursor_mut(&mut self) -> &mut usize;\n\tfn offset_mut(&mut self) -> &mut usize;\n\n\tfn scroll(&mut self, step: impl Into<Step>) -> bool {\n\t\tlet new = step.into().add(*self.cursor_mut(), self.total(), self.limit());\n\t\tif new > *self.cursor_mut() { self.next(new) } else { self.prev(new) }\n\t}\n\n\tfn next(&mut self, n_cur: usize) -> bool {\n\t\tlet (o_cur, o_off) = (*self.cursor_mut(), *self.offset_mut());\n\t\tlet (total, limit, scrolloff) = (self.total(), self.limit(), self.scrolloff());\n\n\t\tlet n_off = if n_cur < total.min(o_off + limit).saturating_sub(scrolloff) {\n\t\t\to_off.min(total.saturating_sub(1))\n\t\t} else {\n\t\t\ttotal.saturating_sub(limit).min(o_off + n_cur - o_cur)\n\t\t};\n\n\t\t*self.cursor_mut() = n_cur;\n\t\t*self.offset_mut() = n_off;\n\t\t(n_cur, n_off) != (o_cur, o_off)\n\t}\n\n\tfn prev(&mut self, n_cur: usize) -> bool {\n\t\tlet (o_cur, o_off) = (*self.cursor_mut(), *self.offset_mut());\n\n\t\tlet n_off = if n_cur < o_off + self.scrolloff() {\n\t\t\to_off.saturating_sub(o_cur - n_cur)\n\t\t} else {\n\t\t\tself.total().saturating_sub(1).min(o_off)\n\t\t};\n\n\t\t*self.cursor_mut() = n_cur;\n\t\t*self.offset_mut() = n_off;\n\t\t(n_cur, n_off) != (o_cur, o_off)\n\t}\n}\n"
  },
  {
    "path": "yazi-widgets/src/step.rs",
    "content": "use std::{num::ParseIntError, str::FromStr};\n\nuse yazi_shared::data::Data;\n\n#[derive(Clone, Copy, Debug)]\npub enum Step {\n\tTop,\n\tBot,\n\tPrev,\n\tNext,\n\tOffset(isize),\n\tPercent(i8),\n}\n\nimpl Default for Step {\n\tfn default() -> Self { Self::Offset(0) }\n}\n\nimpl From<isize> for Step {\n\tfn from(n: isize) -> Self { Self::Offset(n) }\n}\n\nimpl FromStr for Step {\n\ttype Err = ParseIntError;\n\n\tfn from_str(s: &str) -> Result<Self, Self::Err> {\n\t\tOk(match s {\n\t\t\t\"top\" => Self::Top,\n\t\t\t\"bot\" => Self::Bot,\n\t\t\t\"prev\" => Self::Prev,\n\t\t\t\"next\" => Self::Next,\n\t\t\ts if s.ends_with('%') => Self::Percent(s[..s.len() - 1].parse()?),\n\t\t\ts => Self::Offset(s.parse()?),\n\t\t})\n\t}\n}\n\nimpl TryFrom<&Data> for Step {\n\ttype Error = ParseIntError;\n\n\tfn try_from(value: &Data) -> Result<Self, Self::Error> {\n\t\tOk(match value {\n\t\t\tData::Integer(i) => Self::from(*i as isize),\n\t\t\tData::String(s) => s.parse()?,\n\t\t\t_ => \"\".parse()?,\n\t\t})\n\t}\n}\n\nimpl Step {\n\tpub fn add(self, pos: usize, len: usize, limit: usize) -> usize {\n\t\tif len == 0 {\n\t\t\treturn 0;\n\t\t}\n\n\t\tlet off = match self {\n\t\t\tSelf::Top => return 0,\n\t\t\tSelf::Bot => return len - 1,\n\t\t\tSelf::Prev => -1,\n\t\t\tSelf::Next => 1,\n\t\t\tSelf::Offset(n) => n,\n\t\t\tSelf::Percent(0) => 0,\n\t\t\tSelf::Percent(n) => n as isize * limit as isize / 100,\n\t\t};\n\n\t\tif matches!(self, Self::Prev | Self::Next) {\n\t\t\toff.saturating_add_unsigned(pos).rem_euclid(len as _) as _\n\t\t} else if off >= 0 {\n\t\t\tpos.saturating_add_signed(off)\n\t\t} else {\n\t\t\tpos.saturating_sub(off.unsigned_abs())\n\t\t}\n\t\t.min(len - 1)\n\t}\n}\n"
  }
]