[
  {
    "path": ".biomeignore",
    "content": "out/\nnode_modules/\n**/*.d.ts\n.vscode-test/\n*.vsix\n"
  },
  {
    "path": ".claude/settings.local.json",
    "content": "{\n  \"permissions\": {\n    \"allow\": [\n      \"Bash(npm run pretest:*)\",\n      \"Bash(grep:*)\",\n      \"Bash(npm test)\",\n      \"Bash(VSCODE_TEST_ELECTRON_PATH= npm test)\",\n      \"Bash(mkdir:*)\"\n    ],\n    \"deny\": []\n  }\n}"
  },
  {
    "path": ".devcontainer/Dockerfile",
    "content": "#-------------------------------------------------------------------------------------------------------------\n# Copyright (c) Microsoft Corporation. All rights reserved.\n# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.\n#-------------------------------------------------------------------------------------------------------------\n\nFROM node:19\n\n# Avoid warnings by switching to noninteractive\nENV DEBIAN_FRONTEND=noninteractive\n\n# Configure apt and install packages\nRUN apt-get update \\\n    && apt-get -y install --no-install-recommends apt-utils 2>&1 \\\n    #\n    # Verify git and needed tools are installed\n    && apt-get install -y git procps \\    \n    #\n    # Remove outdated npm from /opt and install via package \n    # so it can be easily updated via apt-get upgrade npm\n    && rm -rf /opt/npm-* \\\n    && rm -f /usr/local/bin/npm \\\n    && rm -f /usr/local/bin/npmpkg \\\n    && apt-get install -y curl apt-transport-https lsb-release \\\n    && curl -sS https://dl.npmpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/pubkey.gpg | apt-key add - 2>/dev/null \\\n    && echo \"deb https://dl.npmpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/ stable main\" | tee /etc/apt/sources.list.d/npm.list \\\n    && apt-get update \\\n    && apt-get -y install --no-install-recommends npm \\\n    #\n    # Install tslint and typescript globally\n    && npm install -g tslint typescript \\\n    #\n    # Clean up\n    && apt-get autoremove -y \\\n    && apt-get clean -y \\\n    && rm -rf /var/lib/apt/lists/*\n\n# Switch back to dialog for any ad-hoc use of apt-get\nENV DEBIAN_FRONTEND=dialog\n"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "// See https://aka.ms/vscode-remote/devcontainer.json for format details.\n{\n\t\"name\": \"Node.js 8 & TypeScript\",\n\t\"dockerFile\": \"Dockerfile\",\n\t\"extensions\": [\n\t\t\"ms-vscode.vscode-typescript-tslint-plugin\",\n\t\t\"sleistner.vscode-fileutils\"\n\t]\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nmax_line_length = 120\ninsert_final_newline = true\ntrim_trailing_whitespace = true\nindent_style = space\nindent_size = 4\n\n[{*.yml, *.yaml, *.sh, package.json}]\nindent_size = 2\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".eslintignore",
    "content": "\"**/*.js\"\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributing\n\nContributing is easy:\n\n* You can report bugs and request features using the [issues page][issues].\n\n[issues]: https://github.com/sleistner/vscode-fileutils/issues\n\n\nWe love pull requests from everyone:\n\n* Fork the project\n* Download source code and install dependencies\n```bash\ngit clone git@github.com:your-username/vscode-fileutils.git\ncd vscode-fileutils\nnpm install\ncode .\n```\n* Make the respective code changes.\n* Go to the debugger in VS Code, choose `Launch Extension` and click run. You can test your changes.\n* Choose `Launch Tests` to run the tests.\n* Push to your fork and [submit a pull request][pr].\n\n[pr]: https://github.com/sleistner/vscode-fileutils/compare/\n\nAt this point you're waiting on us. We like to at least comment on pull requests\nas soon as possible. We may suggest some changes or improvements or alternatives.\n\n\n**Important:** Release and changleog update are executed as TravisCI job.\n\nPlease write commit messages considering Angular Commit Message Conventions.\n* https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#commits\n* https://blog.greenkeeper.io/introduction-to-semantic-release-33f73b117c8\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Desktop (please complete the following information):**\n- VSCode Version:\n- OS Version:\n- FileUtils Extension Version:\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "# Description\n\nPlease include a summary of the change and which issue is fixed. \nPlease also include relevant motivation and context. \nList any dependencies that are required for this change.\n\nFixes # (issue)\n\n# Checklist:\n\n- [ ] My code follows the style guidelines of this project\n- [ ] I have performed a self-review of my own code\n- [ ] I have made corresponding changes to the documentation\n- [ ] My changes generate no new warnings\n- [ ] I have added tests that prove my fix is effective or that my feature works\n- [ ] New and existing unit tests pass locally with my changes\n- [ ] Any dependent changes have been merged and published in downstream modules\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "name: CI/CD\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\n  release:\n    types:\n      - published\n\nconcurrency:\n  group: ci-fileutils-${{ github.head_ref || github.run_id }}\n  cancel-in-progress: true\n\njobs:\n  lint-test:\n    name: Lint, Test\n    strategy:\n      matrix:\n        os:\n          - ubuntu-latest\n          - macos-latest\n    runs-on: ${{ matrix.os }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          persist-credentials: false\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: 22.x\n          cache: \"npm\"\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Run code analysis\n        run: npm run lint\n        if: runner.os == 'Linux'\n\n      - name: Run tests on Linux\n        run: |\n          sudo apt-get --assume-yes install libsecret-1-0 xclip;\n          /usr/bin/Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &\n          xvfb-run -a npm run test\n        env:\n          DISPLAY: \":99.0\"\n        if: runner.os == 'Linux'\n\n      - name: Run tests on macOS\n        run: npm run test\n        if: runner.os != 'Linux'\n\n  release:\n    name: Release\n    runs-on: ubuntu-latest\n    needs: [lint-test]\n    if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          persist-credentials: false\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: 22.x\n          cache: \"npm\"\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Run semantic-release\n        run: npm run semantic-release\n        env:\n          GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}\n          VSCE_PAT: ${{ secrets.VSCE_PAT }}\n          OVSX_PAT: ${{ secrets.OVSX_PAT }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directory\nnode_modules\n\n# Optional npm cache directory\n.npm\n\n# Optional REPL history\n.node_repl_history\n\nout\n\n.vscode-test\n.idea\n\n*.vsix\n\ntmp\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\nnpm run lint && npm run test\n"
  },
  {
    "path": ".node-version",
    "content": "22\n"
  },
  {
    "path": ".releaserc",
    "content": "{\n    \"branch\": \"master\",\n    \"plugins\": [\n        \"@semantic-release/commit-analyzer\",\n        \"@semantic-release/release-notes-generator\",\n        \"@semantic-release/npm\",\n        \"@semantic-release/changelog\",\n        \"@semantic-release/git\",\n        \"@semantic-release/github\"\n    ],\n    \"prepare\": [\n        \"@semantic-release/npm\",\n        \"@semantic-release/changelog\",\n        \"@semantic-release/git\",\n        {\n            \"path\": \"semantic-release-vsce\",\n            \"packageVsix\": \"sleistner.vscode-fileutils.vsix\"\n        }\n    ],\n    \"publish\": [\n        \"semantic-release-vsce\",\n        {\n            \"path\": \"@semantic-release/github\",\n            \"assets\": \"sleistner.vscode-fileutils.vsix\"\n        }\n    ]\n}\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n    // See http://go.microsoft.com/fwlink/?LinkId=827846\n    // for the documentation about the extensions.json format\n    \"recommendations\": [\n        \"dbaeumer.vscode-eslint\",\n        \"editorconfig.editorconfig\",\n        \"esbenp.prettier-vscode\",\n        \"connor4312.esbuild-problem-matchers\"\n    ]\n}\n"
  },
  {
    "path": ".vscode/launch.json",
    "content": "// A launch configuration that compiles the extension and then opens it inside a new window\n// Use IntelliSense to learn about possible attributes.\n// Hover to view descriptions of existing attributes.\n// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n{\n    \"version\": \"0.2.0\",\n    \"configurations\": [\n        {\n            \"name\": \"Launch Extension\",\n            \"type\": \"extensionHost\",\n            \"request\": \"launch\",\n            \"runtimeExecutable\": \"${execPath}\",\n            \"args\": [\"--extensionDevelopmentPath=${workspaceFolder}\", \"--folder-uri=${workspaceFolder}/tmp\"],\n            \"outFiles\": [\"${workspaceFolder}/out/src/**/*.js\"],\n            \"preLaunchTask\": \"npm: watch\"\n        },\n        {\n            \"name\": \"Launch Tests\",\n            \"type\": \"extensionHost\",\n            \"request\": \"launch\",\n            \"runtimeExecutable\": \"${execPath}\",\n            \"args\": [\n                \"${workspaceFolder}/test\",\n                \"--extensionDevelopmentPath=${workspaceFolder}\",\n                \"--extensionTestsPath=${workspaceFolder}/out/test\"\n            ],\n            \"outFiles\": [\"${workspaceFolder}/out/test/**/*.js\"],\n            \"preLaunchTask\": \"npm: tsc:watch\"\n        }\n    ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "// Place your settings in this file to overwrite default and user settings.\n{\n    \"files.exclude\": {\n        \"out\": false // set this to true to hide the \"out\" folder with the compiled JS files\n    },\n    \"search.exclude\": {\n        \"out\": true // set this to false to include \"out\" folder in search results\n    },\n    \"editor.codeActionsOnSave\": {\n        \"source.organizeImports\": true\n    }\n}\n"
  },
  {
    "path": ".vscode/tasks.json",
    "content": "{\r\n    \"version\": \"2.0.0\",\r\n    \"tasks\": [\r\n        {\r\n            \"type\": \"npm\",\r\n            \"script\": \"watch\",\r\n            \"group\": {\r\n                \"kind\": \"build\",\r\n                \"isDefault\": true\r\n            },\r\n            \"problemMatcher\": \"$esbuild-watch\",\r\n            \"isBackground\": true,\r\n            \"label\": \"npm: watch\"\r\n        },\r\n        {\r\n            \"type\": \"npm\",\r\n            \"script\": \"tsc:watch\",\r\n            \"isBackground\": true,\r\n            \"presentation\": {\r\n                \"reveal\": \"never\"\r\n            },\r\n            \"group\": {\r\n                \"kind\": \"build\",\r\n                \"isDefault\": false\r\n            },\r\n            \"problemMatcher\": [\"$tsc-watch\"],\r\n            \"label\": \"npm: tsc:watch\"\r\n        }\r\n    ]\r\n}\r\n"
  },
  {
    "path": ".vscodeignore",
    "content": "*\r\n*/**\r\n\r\n!images/icon.*\r\n!README.md\r\n!CHANGELOG.md\r\n!LICENSE\r\n!out/extension.js\r\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## [3.10.3](https://github.com/sleistner/vscode-fileutils/compare/v3.10.2...v3.10.3) (2023-07-22)\n\n\n### Bug Fixes\n\n* **deps:** update dependency fast-glob to v3.3.1 ([5581fa3](https://github.com/sleistner/vscode-fileutils/commit/5581fa33c582fc43bcc2f951570db60b367b9928))\n\n## [3.10.2](https://github.com/sleistner/vscode-fileutils/compare/v3.10.1...v3.10.2) (2023-06-30)\n\n\n### Bug Fixes\n\n* **deps:** update dependency fast-glob to v3.3.0 ([ad9f56d](https://github.com/sleistner/vscode-fileutils/commit/ad9f56d5a07abc9d9a0a48cc4a03ea3fcd8b4e35))\n\n## [3.10.1](https://github.com/sleistner/vscode-fileutils/compare/v3.10.0...v3.10.1) (2023-03-21)\n\n\n### Bug Fixes\n\n* rename commands to comply with VSCode style guides ([72f6843](https://github.com/sleistner/vscode-fileutils/commit/72f6843c02d6cbec5d5588a27ef8097e1130e68e))\n\n# [3.10.0](https://github.com/sleistner/vscode-fileutils/compare/v3.9.3...v3.10.0) (2023-01-30)\n\n\n### Features\n\n* **Settings:** add option to disable context menus ([f3b1431](https://github.com/sleistner/vscode-fileutils/commit/f3b143134f62337a1082ecf30b4588e7dcfab7ae))\n\n## [3.9.3](https://github.com/sleistner/vscode-fileutils/compare/v3.9.2...v3.9.3) (2023-01-30)\n\n\n### Bug Fixes\n\n* **NewFileController:** show workspace selector when relative to root ([e3fcf96](https://github.com/sleistner/vscode-fileutils/commit/e3fcf962ec74f5b803da963ba9bdfe4676f83aeb))\n\n## [3.9.2](https://github.com/sleistner/vscode-fileutils/compare/v3.9.1...v3.9.2) (2023-01-28)\n\n\n### Bug Fixes\n\n* **MoveFileController:** disable brace expansion ([8874f26](https://github.com/sleistner/vscode-fileutils/commit/8874f2664c62035e31ad61656fa18d018f5fc36a))\n\n## [3.9.1](https://github.com/sleistner/vscode-fileutils/compare/v3.9.0...v3.9.1) (2023-01-25)\n\n\n### Bug Fixes\n\n* **build:** include changelog ([550e190](https://github.com/sleistner/vscode-fileutils/commit/550e19025a52bd842e76021045a2786c4f4a8758))\n\n# [3.9.0](https://github.com/sleistner/vscode-fileutils/compare/v3.8.0...v3.9.0) (2023-01-25)\n\n\n### Bug Fixes\n\n* **RenameFileController:** always append base path ([cd6f352](https://github.com/sleistner/vscode-fileutils/commit/cd6f35276331f5ff8b73dbd88443f7f0952b6cc8))\n\n\n### Features\n\n* add inputBox pathTypeIndicator setting ([3390544](https://github.com/sleistner/vscode-fileutils/commit/3390544f5f75ac6dc481827b485a3113447d4b3b))\n* **inputBox:** add path representation configuration ([82e7364](https://github.com/sleistner/vscode-fileutils/commit/82e7364b67551388a1cc58ac1ee8122975f835aa))\n\n# [3.8.0](https://github.com/sleistner/vscode-fileutils/compare/v3.7.0...v3.8.0) (2023-01-23)\n\n\n### Features\n\n* **DuplicateFile:** add typeahead support ([44ac603](https://github.com/sleistner/vscode-fileutils/commit/44ac603dd241eb61e3732172ffc6cfe80555e0c0))\n* **MoveFile:** add typeahead support ([0e3e0ca](https://github.com/sleistner/vscode-fileutils/commit/0e3e0ca1f5886926758987b594461d57980f750c))\n* **NewFile:** add typeahead setting ([764f614](https://github.com/sleistner/vscode-fileutils/commit/764f614e8e7a8b8d350bd3ad68f785c695bf5e01))\n* **NewFolder:** add dedicated typeahead setting ([6d4359a](https://github.com/sleistner/vscode-fileutils/commit/6d4359a5f7bb4689b22d2ba7b3762c9b602839fb))\n\n# [3.7.0](https://github.com/sleistner/vscode-fileutils/compare/v3.6.0...v3.7.0) (2023-01-23)\n\n\n### Features\n\n* publish to open vsx repository ([f0d643f](https://github.com/sleistner/vscode-fileutils/commit/f0d643f92aae168505ca750cf9a020cb5610840e))\n\n# [3.6.0](https://github.com/sleistner/vscode-fileutils/compare/v3.5.0...v3.6.0) (2023-01-23)\n\n\n### Bug Fixes\n\n* **TreeWalker:** replace workspace.findFiles in favor of fast-glob ([37c5078](https://github.com/sleistner/vscode-fileutils/commit/37c50781b4025e31c2023ea568aa78b4ad66714d))\n\n## [3.5.1](https://github.com/sleistner/vscode-fileutils/compare/v3.5.0...v3.5.1) (2023-01-02)\n\n\n### Bug Fixes\n\n* **ci:** update semantic release ([6c12e92](https://github.com/sleistner/vscode-fileutils/commit/6c12e92409a9a04d92193781ad1fdb8e17c99ea1))\n\n# [3.5.0](https://github.com/sleistner/vscode-fileutils/compare/v3.4.6...v3.5.0) (2022-01-18)\n\n\n### Features\n\n* **ci:** enable github actions ([3eead61](https://github.com/sleistner/vscode-fileutils/commit/3eead61d04d6adf1632503d29939e2c150147d87))\n\n\n## [3.4.6](https://github.com/sleistner/vscode-fileutils/compare/v3.4.5...v3.4.6) (2022-01-14)\n\n\n### Bug Fixes\n\n* trigger gh actions release pipeline ([d9a59c9](https://github.com/sleistner/vscode-fileutils/commit/d9a59c9f974ceb2be6407aed64131e5761acb48a))\n\n## [3.4.5](https://github.com/sleistner/vscode-fileutils/compare/v3.4.4...v3.4.5) (2021-02-22)\n\n\n### Bug Fixes\n\n* **deps:** update dependency brace-expansion to v2.0.1 ([dd094d0](https://github.com/sleistner/vscode-fileutils/commit/dd094d0))\n\n## [3.4.4](https://github.com/sleistner/vscode-fileutils/compare/v3.4.3...v3.4.4) (2021-02-01)\n\n\n### Bug Fixes\n\n* prefer uri over current editor ([e63b27f](https://github.com/sleistner/vscode-fileutils/commit/e63b27f))\n\n## [3.4.3](https://github.com/sleistner/vscode-fileutils/compare/v3.4.2...v3.4.3) (2021-01-06)\n\n\n### Bug Fixes\n\n* **NewFileController:** properly brace expand backslash paths ([ff95aae](https://github.com/sleistner/vscode-fileutils/commit/ff95aae))\n\n## [3.4.2](https://github.com/sleistner/vscode-fileutils/compare/v3.4.1...v3.4.2) (2020-11-17)\n\n\n### Bug Fixes\n\n* **build:** include README ([b724700](https://github.com/sleistner/vscode-fileutils/commit/b724700))\n\n## [3.4.1](https://github.com/sleistner/vscode-fileutils/compare/v3.4.0...v3.4.1) (2020-11-08)\n\n\n### Bug Fixes\n\n* **build:** include node_modules ([a28b0da](https://github.com/sleistner/vscode-fileutils/commit/a28b0da))\n\n# [3.4.0](https://github.com/sleistner/vscode-fileutils/compare/v3.3.3...v3.4.0) (2020-11-06)\n\n\n### Bug Fixes\n\n* **readme:** trigger release ([9314428](https://github.com/sleistner/vscode-fileutils/commit/9314428))\n\n\n### Features\n\n* **NewFileCommand:** add support for brace expansion ([5e06afc](https://github.com/sleistner/vscode-fileutils/commit/5e06afc))\n\n## [3.3.3](https://github.com/sleistner/vscode-fileutils/compare/v3.3.2...v3.3.3) (2020-10-26)\n\n\n### Bug Fixes\n\n* **New Folder or File Relative to Current View:** cancel execution if no editor is open ([858fea6](https://github.com/sleistner/vscode-fileutils/commit/858fea6))\n\n## [3.3.2](https://github.com/sleistner/vscode-fileutils/compare/v3.3.1...v3.3.2) (2020-10-26)\n\n\n### Bug Fixes\n\n* **package:** update extension main file entry ([4892f84](https://github.com/sleistner/vscode-fileutils/commit/4892f84))\n\n## [3.3.1](https://github.com/sleistner/vscode-fileutils/compare/v3.3.0...v3.3.1) (2020-10-25)\n\n\n### Bug Fixes\n\n* **duplicate:** prevent directories to be opened as document ([dc1c9f0](https://github.com/sleistner/vscode-fileutils/commit/dc1c9f0))\n\n# [3.3.0](https://github.com/sleistner/vscode-fileutils/compare/v3.2.0...v3.3.0) (2020-10-25)\n\n\n### Features\n\n* **menus:** add file releated commands to tab and editor context ([a8b748e](https://github.com/sleistner/vscode-fileutils/commit/a8b748e))\n\n# [3.2.0](https://github.com/sleistner/vscode-fileutils/compare/v3.1.1...v3.2.0) (2020-10-25)\n\n\n### Features\n\n* update icon ([5c2156b](https://github.com/sleistner/vscode-fileutils/commit/5c2156b))\n\n## [3.1.1](https://github.com/sleistner/vscode-fileutils/compare/v3.1.0...v3.1.1) (2020-10-23)\n\n\n### Bug Fixes\n\n* **Rename, Move:** keep file in editor group ([5478345](https://github.com/sleistner/vscode-fileutils/commit/5478345))\n\n# [3.1.0](https://github.com/sleistner/vscode-fileutils/compare/v3.0.1...v3.1.0) (2020-10-18)\n\n\n### Features\n\n* **move/rename:** trigger update imports when moving file ([7a40237](https://github.com/sleistner/vscode-fileutils/commit/7a40237))\n\n## [3.0.1](https://github.com/sleistner/vscode-fileutils/compare/v3.0.0...v3.0.1) (2020-01-15)\n\n\n### Bug Fixes\n\n* **FileItem:** ensure file exists before deleting it ([7a44326](https://github.com/sleistner/vscode-fileutils/commit/7a44326))\n\n# [3.0.0](https://github.com/sleistner/vscode-fileutils/compare/v2.14.9...v3.0.0) (2019-09-03)\n\n\n### Bug Fixes\n\n* **TreeWalker:** handle large directory structures safely ([c419c78](https://github.com/sleistner/vscode-fileutils/commit/c419c78))\n\n\n### BREAKING CHANGES\n\n* **TreeWalker:** The configuration option \"typeahead.exclude\" has been\nremoved in favour of VS Code native \"files.exclude\" option.\n\n## [2.14.9](https://github.com/sleistner/vscode-fileutils/compare/v2.14.8...v2.14.9) (2019-08-26)\n\n\n### Bug Fixes\n\n* **RemoveFileCommand:** ensure only delete file tab was closed ([557e794](https://github.com/sleistner/vscode-fileutils/commit/557e794))\n\n## [2.14.8](https://github.com/sleistner/vscode-fileutils/compare/v2.14.7...v2.14.8) (2019-08-26)\n\n\n### Bug Fixes\n\n* **NewFileCommand:** show quickpick on large directory structures ([8c8c537](https://github.com/sleistner/vscode-fileutils/commit/8c8c537))\n\n## [2.14.7](https://github.com/sleistner/vscode-fileutils/compare/v2.14.6...v2.14.7) (2019-08-23)\n\n\n### Bug Fixes\n\n* **NewFileCommand:** show folder selector ([38fb33f](https://github.com/sleistner/vscode-fileutils/commit/38fb33f))\n\n## [2.14.6](https://github.com/sleistner/vscode-fileutils/compare/v2.14.5...v2.14.6) (2019-08-20)\n\n\n### Bug Fixes\n\n* missing callback in remote environments ([63ef29a](https://github.com/sleistner/vscode-fileutils/commit/63ef29a))\n\n## [2.14.5](https://github.com/sleistner/vscode-fileutils/compare/v2.14.4...v2.14.5) (2019-06-03)\n\n\n### Bug Fixes\n\n* **CopyFileName:** forward and process tab uri ([68ae985](https://github.com/sleistner/vscode-fileutils/commit/68ae985))\n\n## [2.14.4](https://github.com/sleistner/vscode-fileutils/compare/v2.14.3...v2.14.4) (2019-05-29)\n\n\n### Bug Fixes\n\n* **FileItem:** update trash import ([850dfff](https://github.com/sleistner/vscode-fileutils/commit/850dfff))\n* **package:** update trash to version 5.0.0 ([51f7017](https://github.com/sleistner/vscode-fileutils/commit/51f7017))\n\n## [2.14.3](https://github.com/sleistner/vscode-fileutils/compare/v2.14.2...v2.14.3) (2019-05-29)\n\n\n### Bug Fixes\n\n* **contribution:** reorder conext menu items ([2883402](https://github.com/sleistner/vscode-fileutils/commit/2883402))\n\n## [2.14.2](https://github.com/sleistner/vscode-fileutils/compare/v2.14.1...v2.14.2) (2019-05-29)\n\n\n### Bug Fixes\n\n* **package:** update fs-extra to version 8.0.0 ([86ff0b9](https://github.com/sleistner/vscode-fileutils/commit/86ff0b9))\n\n## [2.14.1](https://github.com/sleistner/vscode-fileutils/compare/v2.14.0...v2.14.1) (2019-05-29)\n\n\n### Bug Fixes\n\n* icon position ([a273e32](https://github.com/sleistner/vscode-fileutils/commit/a273e32))\n\n# [2.14.0](https://github.com/sleistner/vscode-fileutils/compare/v2.13.7...v2.14.0) (2019-05-29)\n\n\n### Features\n\n* **editor/title/context:** add rename, remove and copy command ([bb0482e](https://github.com/sleistner/vscode-fileutils/commit/bb0482e))\n\n## [2.13.7](https://github.com/sleistner/vscode-fileutils/compare/v2.13.6...v2.13.7) (2019-04-20)\n\n\n### Bug Fixes\n\n* icon color ([21f4eb4](https://github.com/sleistner/vscode-fileutils/commit/21f4eb4))\n\n## [2.13.6](https://github.com/sleistner/vscode-fileutils/compare/v2.13.5...v2.13.6) (2019-04-20)\n\n\n### Bug Fixes\n\n* **NewFileCommand:** prompt to select workspace ([8335975](https://github.com/sleistner/vscode-fileutils/commit/8335975))\n\n## [2.13.4](https://github.com/sleistner/vscode-fileutils/compare/v2.13.3...v2.13.4) (2019-01-03)\n\n\n### Bug Fixes\n\n* **README:** remove unsupported category ([4a13e08](https://github.com/sleistner/vscode-fileutils/commit/4a13e08))\n\n## [2.13.3](https://github.com/sleistner/vscode-fileutils/compare/v2.13.2...v2.13.3) (2018-11-11)\n\n\n### Bug Fixes\n\n* **releaserc:** enable release notes plugin ([b88a7c6](https://github.com/sleistner/vscode-fileutils/commit/b88a7c6))\n* **releaserc:** enable release notes plugin ([eabac50](https://github.com/sleistner/vscode-fileutils/commit/eabac50))\n\n## 2.13.0 (2018-11-10)\n\n### Features\n- `File: Rename`\n- `File: Move`\n[iliashkolyar](https://github.com/iliashkolyar) Add configuration to support whether to close old tabs [PR#67](https://github.com/sleistner/vscode-fileutils/pull/67)\n\n## 2.12.0 (2018-11-02)\n\n### Fixes\n\n- [iliashkolyar](https://github.com/iliashkolyar) Support file operations on non-textual files [PR#63](https://github.com/sleistner/vscode-fileutils/pull/63)\n\n### Features\n\n- `File: Copy Name Of Active File` [iliashkolyar](https://github.com/iliashkolyar) Support copy name of active file [PR#61](https://github.com/sleistner/vscode-fileutils/pull/61)\n\n## 2.10.3 (2018-06-15)\n\n### Fixes\n\n- `File: New File`, Show quick pick view only if more than 1 choice available.\n\n## 2.10.0 (2018-06-14)\n\n### Features\n\n- `File: New File`, Autocomplete paths when creating a new file.\n[PR#48](https://github.com/sleistner/vscode-fileutils/pull/48)\nInspired and heavily borrowed from [https://github.com/patbenatar/vscode-advanced-new-file](https://github.com/patbenatar/vscode-advanced-new-file)\n\n## 2.9.0 (2018-05-24)\n\n### Features\n\n- `File: New File`, Adding a trailing / to the supplied target name causes the creation of a new directory.\n[PR#25](https://github.com/sleistner/vscode-fileutils/pull/25)\n\n## 2.8.1 (2018-02-25)\n\n### Fixes\n\n- Extension can not be loaded due to missing dependency.\n\n## 2.8.0 (2018-02-25)\n\n### Features\n\n- `File: Delete`, Add configuration `fileutils.delete.useTrash` in order to move files to trash.\n- `File: Delete`, Add configuration `fileutils.delete.confirm` to toggle confirmation dialog.\n\n## 2.7.1 (2017-10-25)\n\n### Fixes:\n\n- Renaming and other actions move editor to first group\n\n## 2.7.0 (2017-10-05)\n\n### Features:\n\n - [lazyc97](https://github.com/lazyc97) Select filename when inputbox shows up [PR#23](https://github.com/sleistner/vscode-fileutils/pull/23)\n\n## 2.6.1 (2017-06-12)\n\n### Fixes:\n\n - Keyboard shortcuts failed to execute\n\n## 2.4.1 (2017-03-06)\n\n### Features:\n\n - Enable modal confirmation dialogs\n\n## 2.3.4 (2017-03-06)\n\n### Fixes:\n\n - File-New File or Folder failed to execute\n\n## 2.3.3 (2017-01-12)\n\n### Fixes:\n\n - File-Duplicate from the context menu doesn't work on Windows\n\n## 2.3.1 (2016-10-14)\n\n### Features:\n\n  - file browser context menu\n\n        Duplicate\n\n  - file editor context menu\n\n        Duplicate\n\n  - file editor context menu\n\n        Move\n\n## 2.0.0 (2016-07-18)\n\n### Features:\n\n  - file browser context menu\n\n        Move\n\n    Moves the selected file or directory.\n\n    _(Also creates nested directories)_\n\n### Breaking Changes:\n\n  - command prefix `extensions` has been renamed to `fileutils`\n\n## 1.1.0 (2016-05-04)\n\n### Features:\n\n  - command\n\n        File: New File Relative to Current View\n\n    Adds a new file relative to file open in active editor.\n\n    _(Also creates nested directories)_\n\n  - command\n\n        File: New File Relative to Project Root\n\n    Adds a new file relative to project root.\n\n    _(Also creates nested directories)_\n\n  - command\n\n        File: New Folder Relative to Current View\n\n    Adds a new directory relative to file open in active editor.\n\n    _(Also creates nested directories)_\n\n  - command\n\n        File: New Folder Relative to Project Root\n\n    Adds a new directory relative to project root.\n\n    _(Also creates nested directories)_\n\n## 1.0.0 (2016-05-03)\n\nFeatures:\n\n  - command\n\n        File: Rename\n\n    Renames the file open in active editor.\n\n    _(Also creates nested directories)_\n\n  - command\n\n        File: Move\n\n    Moves the file open in active editor.\n\n    _(Also creates nested directories)_\n\n  - command\n\n        File: Duplicate\n\n    Duplicates the file open in active editor.\n\n    _(Also creates nested directories)_\n\n  - command\n\n        File: Remove\n\n    Deletes the file open in active editor.\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016 Steffen Leistner\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": "# File Utils - Visual Studio Code Extension\n\n[![Known Vulnerabilities](https://snyk.io/test/github/sleistner/vscode-fileutils/badge.svg)](https://snyk.io/test/github/sleistner/vscode-fileutils)\n[![Renovate](https://img.shields.io/badge/renovate-enabled-brightgreen.svg)](https://renovatebot.com)\n[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)\n\n---\n![icon](images/icon-96x96.png)\n\nA convenient way of creating, duplicating, moving, renaming, deleting files and directories.\n\n_Inspired by [Sidebar Enhancements](https://github.com/titoBouzout/SideBarEnhancements) for Sublime._\n\n## How to use\n\n![demo](images/demo.gif)\n\n### Using the context menu\n\n- Right click on a file or folder in the Explorer pane or in a file tab.\n- Select one of those command: `Copy Name`, `Duplicate...`, `Move...`, `Rename...`, `Delete...`.\n- For comand `Copy name`, the file name is copied to the clipboard.\n- For other command, answer the additional info requested:\n  - Duplicate: update path to duplicate near where the command palette is displayed.\n  - Move: update path to move to near where the command palette is displayed.\n  - Rename: update name in Explorer pane or near where the command palette is displayed\n  - Delete: answer Visual Studio Code delete dialog.\n\n## Using the command palette\n\n- Bring up the command palette, and select \"File Utils: \".\n- Select one of the commands mentioned below.\n- Press [Enter] to confirm, or [Escape] to cancel.\n\n![howto](images/howto.png)\n\n### Brace Expansion\n\n> Brace expansion is a mechanism by which arbitrary strings may be generated.\n\nExample file name input\n\n```bash\n/tmp/{a,b,c}/index.{cpp,ts,scss}\n```\n\nwill generate the following files\n\n```bash\n➜  tree /tmp\n/tmp\n├── a\n│   ├── index.cpp\n│   ├── index.scss\n│   └── index.ts\n├── b\n│   ├── index.cpp\n│   ├── index.scss\n│   └── index.ts\n└── c\n    ├── index.cpp\n    ├── index.scss\n    └── index.ts\n```\n\n### Note\n\nNon-existent folders are created automatically.\n\n## Changelog\n\n- [https://github.com/sleistner/vscode-fileutils/blob/master/CHANGELOG.md](https://github.com/sleistner/vscode-fileutils/blob/master/CHANGELOG.md)\n\n## How to contribute\n\n- [https://github.com/sleistner/vscode-fileutils/blob/master/CONTRIBUTING.md](https://github.com/sleistner/vscode-fileutils/blob/master/CONTRIBUTING.md)\n\n## Disclaimer\n\n**Important:** This extension due to the nature of it's purpose will create\nfiles on your hard drive and if necessary create the respective folder structure.\nWhile it should not override any files during this process, I'm not giving any guarantees\nor take any responsibility in case of lost data.\n\n## Contributors\n\n- [Steffen Leistner](https://github.com/sleistner)\n- [Ilia Shkolyar](https://github.com/iliashkolyar)\n\n## License\n\nMIT\n\n## Credits\n\n### Icon\n\n- [Janosch, Green Tropical Waters - Utilities Icon](https://iconarchive.com/show/tropical-waters-folders-icons-by-janosch500/Utilities-icon.html)\n"
  },
  {
    "path": "biome.json",
    "content": "{\n\t\"$schema\": \"https://biomejs.dev/schemas/2.1.4/schema.json\",\n\t\"vcs\": {\n\t\t\"enabled\": true,\n\t\t\"clientKind\": \"git\",\n\t\t\"useIgnoreFile\": true\n\t},\n\t\"files\": {\n\t\t\"includes\": [\"src/**/*\", \"test/**/*\"],\n\t\t\"ignoreUnknown\": false\n\t},\n\t\"formatter\": {\n\t\t\"enabled\": true,\n\t\t\"indentStyle\": \"space\",\n\t\t\"indentWidth\": 4,\n\t\t\"lineWidth\": 120\n\t},\n\t\"linter\": {\n\t\t\"enabled\": true,\n\t\t\"rules\": {\n\t\t\t\"recommended\": true,\n\t\t\t\"style\": {\n\t\t\t\t\"useBlockStatements\": \"off\",\n\t\t\t\t\"useNodejsImportProtocol\": \"off\"\n\t\t\t},\n\t\t\t\"suspicious\": {\n\t\t\t\t\"noExplicitAny\": \"warn\"\n\t\t\t}\n\t\t}\n\t},\n\t\"javascript\": {\n\t\t\"formatter\": {\n\t\t\t\"quoteStyle\": \"double\",\n\t\t\t\"trailingCommas\": \"es5\",\n\t\t\t\"semicolons\": \"always\"\n\t\t}\n\t},\n\t\"assist\": {\n\t\t\"enabled\": true,\n\t\t\"actions\": {\n\t\t\t\"source\": {\n\t\t\t\t\"organizeImports\": \"on\"\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "main.ts",
    "content": "export { activate } from \"./src/extension\";\n"
  },
  {
    "path": "package.json",
    "content": "{\n    \"name\": \"vscode-fileutils\",\n    \"displayName\": \"File Utils\",\n    \"description\": \"A convenient way of creating, duplicating, moving, renaming and deleting files and directories.\",\n    \"version\": \"3.10.3\",\n    \"private\": true,\n    \"license\": \"MIT\",\n    \"publisher\": \"sleistner\",\n    \"engines\": {\n        \"node\": \">=22.0.0\",\n        \"vscode\": \"^1.74.0\"\n    },\n    \"categories\": [\n        \"Other\"\n    ],\n    \"keywords\": [\n        \"utils\",\n        \"files\",\n        \"move\",\n        \"duplicate\",\n        \"rename\"\n    ],\n    \"icon\": \"images/icon.png\",\n    \"galleryBanner\": {\n        \"color\": \"#1c2237\",\n        \"theme\": \"dark\"\n    },\n    \"bugs\": {\n        \"url\": \"https://github.com/sleistner/vscode-fileutils/issues\"\n    },\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"https://github.com/sleistner/vscode-fileutils.git\"\n    },\n    \"homepage\": \"https://github.com/sleistner/vscode-fileutils/blob/master/README.md\",\n    \"main\": \"./out/extension.js\",\n    \"contributes\": {\n        \"commands\": [\n            {\n                \"command\": \"fileutils.renameFile\",\n                \"category\": \"File Utils\",\n                \"title\": \"Rename...\"\n            },\n            {\n                \"command\": \"fileutils.moveFile\",\n                \"category\": \"File Utils\",\n                \"title\": \"Move...\"\n            },\n            {\n                \"command\": \"fileutils.duplicateFile\",\n                \"category\": \"File Utils\",\n                \"title\": \"Duplicate...\"\n            },\n            {\n                \"command\": \"fileutils.removeFile\",\n                \"category\": \"File Utils\",\n                \"title\": \"Delete\"\n            },\n            {\n                \"command\": \"fileutils.newFile\",\n                \"category\": \"File Utils\",\n                \"title\": \"New File Relative to Current View...\"\n            },\n            {\n                \"command\": \"fileutils.newFileAtRoot\",\n                \"category\": \"File Utils\",\n                \"title\": \"New File Relative to Project Root...\"\n            },\n            {\n                \"command\": \"fileutils.newFolder\",\n                \"category\": \"File Utils\",\n                \"title\": \"New Folder Relative to Current View...\"\n            },\n            {\n                \"command\": \"fileutils.newFolderAtRoot\",\n                \"category\": \"File Utils\",\n                \"title\": \"New Folder Relative to Project Root...\"\n            },\n            {\n                \"command\": \"fileutils.copyFileName\",\n                \"category\": \"File Utils\",\n                \"title\": \"Copy Name\"\n            }\n        ],\n        \"menus\": {\n            \"explorer/context\": [\n                {\n                    \"command\": \"fileutils.moveFile\",\n                    \"group\": \"7_modification\",\n                    \"when\": \"config.fileutils.menus.context.explorer =~ /moveFile/\"\n                },\n                {\n                    \"command\": \"fileutils.duplicateFile\",\n                    \"group\": \"7_modification\",\n                    \"when\": \"config.fileutils.menus.context.explorer =~ /duplicateFile/\"\n                },\n                {\n                    \"command\": \"fileutils.newFileAtRoot\",\n                    \"group\": \"2_workspace\",\n                    \"when\": \"config.fileutils.menus.context.explorer =~ /newFileAtRoot/\"\n                },\n                {\n                    \"command\": \"fileutils.newFolderAtRoot\",\n                    \"group\": \"2_workspace\",\n                    \"when\": \"config.fileutils.menus.context.explorer =~ /newFolderAtRoot/\"\n                },\n                {\n                    \"command\": \"fileutils.copyFileName\",\n                    \"group\": \"6_copypath\",\n                    \"when\": \"config.fileutils.menus.context.explorer =~ /copyFileName/\"\n                }\n            ],\n            \"editor/context\": [\n                {\n                    \"command\": \"fileutils.copyFileName\",\n                    \"group\": \"1_copypath\",\n                    \"when\": \"config.fileutils.menus.context.editor =~ /copyFileName/ && resourceScheme != output\"\n                },\n                {\n                    \"command\": \"fileutils.renameFile\",\n                    \"group\": \"1_modification@1\",\n                    \"when\": \"config.fileutils.menus.context.editor =~ /renameFile/ && resourceScheme != output\"\n                },\n                {\n                    \"command\": \"fileutils.moveFile\",\n                    \"group\": \"1_modification@2\",\n                    \"when\": \"config.fileutils.menus.context.editor =~ /moveFile/ && resourceScheme != output\"\n                },\n                {\n                    \"command\": \"fileutils.duplicateFile\",\n                    \"group\": \"1_modification@3\",\n                    \"when\": \"config.fileutils.menus.context.editor =~ /duplicateFile/ && resourceScheme != output\"\n                },\n                {\n                    \"command\": \"fileutils.removeFile\",\n                    \"group\": \"1_modification@4\",\n                    \"when\": \"config.fileutils.menus.context.editor =~ /removeFile/ && resourceScheme != output\"\n                }\n            ],\n            \"editor/title/context\": [\n                {\n                    \"command\": \"fileutils.copyFileName\",\n                    \"group\": \"1_copypath\",\n                    \"when\": \"config.fileutils.menus.context.editorTitle =~ /copyFileName/\"\n                },\n                {\n                    \"command\": \"fileutils.renameFile\",\n                    \"group\": \"1_modification@1\",\n                    \"when\": \"config.fileutils.menus.context.editorTitle =~ /renameFile/\"\n                },\n                {\n                    \"command\": \"fileutils.moveFile\",\n                    \"group\": \"1_modification@2\",\n                    \"when\": \"config.fileutils.menus.context.editorTitle =~ /moveFile/\"\n                },\n                {\n                    \"command\": \"fileutils.duplicateFile\",\n                    \"group\": \"1_modification@3\",\n                    \"when\": \"config.fileutils.menus.context.editorTitle =~ /duplicateFile/\"\n                },\n                {\n                    \"command\": \"fileutils.removeFile\",\n                    \"group\": \"1_modification@4\",\n                    \"when\": \"config.fileutils.menus.context.editorTitle =~ /removeFile/\"\n                }\n            ]\n        },\n        \"configuration\": {\n            \"type\": \"object\",\n            \"title\": \"File Utils\",\n            \"properties\": {\n                \"fileutils.typeahead.enabled\": {\n                    \"type\": \"boolean\",\n                    \"default\": true,\n                    \"description\": \"Controls whether to show a directory selector for new file and new folder command.\",\n                    \"markdownDeprecationMessage\": \"**Deprecated**: Please use `#fileutils.newFile.typeahead.enabled#` or `#fileutils.newFolder.typeahead.enabled#` instead.\",\n                    \"deprecationMessage\": \"Deprecated: Please use fileutils.newFile.typeahead.enabled or fileutils.newFolder.typeahead.enabled instead.\"\n                },\n                \"fileutils.duplicateFile.typeahead.enabled\": {\n                    \"type\": \"boolean\",\n                    \"default\": false,\n                    \"description\": \"Controls whether to show a directory selector for the duplicate file command.\"\n                },\n                \"fileutils.moveFile.typeahead.enabled\": {\n                    \"type\": \"boolean\",\n                    \"default\": false,\n                    \"description\": \"Controls whether to show a directory selector for the move file command.\"\n                },\n                \"fileutils.newFile.typeahead.enabled\": {\n                    \"type\": \"boolean\",\n                    \"default\": true,\n                    \"description\": \"Controls whether to show a directory selector for the new file command.\"\n                },\n                \"fileutils.newFolder.typeahead.enabled\": {\n                    \"type\": \"boolean\",\n                    \"default\": true,\n                    \"description\": \"Controls whether to show a directory selector for new folder command.\"\n                },\n                \"fileutils.inputBox.pathType\": {\n                    \"type\": \"string\",\n                    \"default\": \"root\",\n                    \"enum\": [\n                        \"root\",\n                        \"workspace\"\n                    ],\n                    \"enumDescriptions\": [\n                        \"Absolute file path of the opened workspace or folder (e.g. /Users/Development/myWorkspace)\",\n                        \"Relative file path of the opened workspace or folder (e.g. /myWorkspace)\"\n                    ],\n                    \"description\": \"Controls the path that is shown in the input box.\"\n                },\n                \"fileutils.inputBox.pathTypeIndicator\": {\n                    \"type\": \"string\",\n                    \"default\": \"@\",\n                    \"maxLength\": 50,\n                    \"description\": \"Controls the indicator that is shown in the input box when the path type is workspace. This setting only has an effect when 'fileutils.inputBox.pathType' is set to 'workspace'.\",\n                    \"markdownDescription\": \"Controls the indicator that is shown in the input box when the path type is workspace. \\n\\nThis setting only has an effect when `#fileutils.inputBox.pathType#` is set to `workspace`.\\n\\nFor example, if the path type is `workspace` and the indicator is `@`, the path will be shown as `@/myWorkspace`.\"\n                },\n                \"fileutils.menus.context.explorer\": {\n                    \"type\": \"array\",\n                    \"default\": [\n                        \"moveFile\",\n                        \"duplicateFile\",\n                        \"newFileAtRoot\",\n                        \"newFolderAtRoot\",\n                        \"copyFileName\"\n                    ],\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"enum\": [\n                            \"moveFile\",\n                            \"duplicateFile\",\n                            \"newFileAtRoot\",\n                            \"newFolderAtRoot\",\n                            \"copyFileName\"\n                        ],\n                        \"enumDescriptions\": [\n                            \"Move\",\n                            \"Duplicate\",\n                            \"New File Relative to Project Root\",\n                            \"New Folder Relative to Project Root\",\n                            \"Copy Name\"\n                        ]\n                    },\n                    \"uniqueItems\": true,\n                    \"description\": \"Controls whether to show the command in the explorer context menu.\",\n                    \"order\": 90\n                },\n                \"fileutils.menus.context.editor\": {\n                    \"type\": \"array\",\n                    \"default\": [\n                        \"renameFile\",\n                        \"moveFile\",\n                        \"duplicateFile\",\n                        \"removeFile\",\n                        \"copyFileName\"\n                    ],\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"enum\": [\n                            \"renameFile\",\n                            \"moveFile\",\n                            \"duplicateFile\",\n                            \"removeFile\",\n                            \"copyFileName\"\n                        ],\n                        \"enumDescriptions\": [\n                            \"Rename\",\n                            \"Move\",\n                            \"Duplicate\",\n                            \"Remove\",\n                            \"Copy Name\"\n                        ]\n                    },\n                    \"uniqueItems\": true,\n                    \"description\": \"Controls whether to show the command in the editor context menu.\",\n                    \"order\": 100\n                },\n                \"fileutils.menus.context.editorTitle\": {\n                    \"type\": \"array\",\n                    \"default\": [\n                        \"renameFile\",\n                        \"moveFile\",\n                        \"duplicateFile\",\n                        \"removeFile\",\n                        \"copyFileName\"\n                    ],\n                    \"items\": {\n                        \"type\": \"string\",\n                        \"enum\": [\n                            \"renameFile\",\n                            \"moveFile\",\n                            \"duplicateFile\",\n                            \"removeFile\",\n                            \"copyFileName\"\n                        ],\n                        \"enumDescriptions\": [\n                            \"Rename\",\n                            \"Move\",\n                            \"Duplicate\",\n                            \"Remove\",\n                            \"Copy Name\"\n                        ]\n                    },\n                    \"uniqueItems\": true,\n                    \"description\": \"Controls whether to show the command in the editor title context menu.\",\n                    \"order\": 110\n                }\n            }\n        }\n    },\n    \"scripts\": {\n        \"vscode:prepublish\": \"npm run -S esbuild-base -- --minify\",\n        \"esbuild-base\": \"npm run clean && esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node\",\n        \"watch\": \"scripts/dev-env && npm run -S esbuild-base -- --sourcemap --watch\",\n        \"tsc:watch\": \"tsc -watch -p ./\",\n        \"pretest\": \"tsc -p ./\",\n        \"test\": \"node ./out/test/runTest.js\",\n        \"lint\": \"biome check src test\",\n        \"lint:fix\": \"biome check --write src test\",\n        \"format\": \"biome format src test\",\n        \"format:write\": \"biome format --write src test\",\n        \"semantic-release\": \"semantic-release\",\n        \"prepare\": \"[ ! -x ./node_modules/.bin/husky ] && exit 0; husky install\",\n        \"clean\": \"rimraf out\"\n    },\n    \"devDependencies\": {\n        \"@biomejs/biome\": \"^2.1.4\",\n        \"@semantic-release/changelog\": \"6.0.3\",\n        \"@semantic-release/git\": \"10.0.1\",\n        \"@tsconfig/node22\": \"^22.0.2\",\n        \"@types/bluebird\": \"^3.5.42\",\n        \"@types/bluebird-retry\": \"^0.11.8\",\n        \"@types/brace-expansion\": \"^1.1.2\",\n        \"@types/chai\": \"^4.3.20\",\n        \"@types/mocha\": \"^10.0.10\",\n        \"@types/node\": \"^24.2.1\",\n        \"@types/sinon\": \"^17.0.4\",\n        \"@types/sinon-chai\": \"^3.2.12\",\n        \"@types/vscode\": \"^1.102.0\",\n        \"@vscode/test-electron\": \"^2.5.2\",\n        \"bluebird\": \"3.7.2\",\n        \"bluebird-retry\": \"0.11.0\",\n        \"chai\": \"^4.5.0\",\n        \"esbuild\": \"^0.27.0\",\n        \"husky\": \"^9.1.7\",\n        \"mocha\": \"^11.7.1\",\n        \"rimraf\": \"^6.0.1\",\n        \"semantic-release\": \"^24.2.7\",\n        \"semantic-release-vsce\": \"^6.0.11\",\n        \"sinon\": \"^21.0.0\",\n        \"sinon-chai\": \"^3.7.0\",\n        \"typescript\": \"^5.9.2\"\n    },\n    \"dependencies\": {\n        \"brace-expansion\": \"^4.0.1\",\n        \"fast-glob\": \"^3.3.3\"\n    }\n}\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n    \"extends\": [\n        \"config:base\"\n    ],\n    \"ignoreDeps\": [\"@types/node\", \"@types/vscode\"],\n    \"packageRules\": [\n        {\n          \"updateTypes\": [\"minor\", \"patch\", \"pin\", \"digest\"],\n          \"automerge\": true\n        }\n    ]\n}\n"
  },
  {
    "path": "scripts/dev-env",
    "content": "#!/usr/bin/env bash\n\nrm -rf ./tmp\nmkdir -p ./tmp/{app,workspace,scripts}\ntouch ./tmp/{app,workspace,scripts}/{foo,bar,baz}.ts\n"
  },
  {
    "path": "src/FileItem.ts",
    "content": "import * as fs from \"fs\";\nimport * as path from \"path\";\nimport { Uri, WorkspaceEdit, workspace } from \"vscode\";\n\nfunction assertTargetPath(targetPath: Uri | undefined): asserts targetPath is Uri {\n    if (targetPath === undefined) {\n        throw new Error(\"Missing target path\");\n    }\n}\n\nexport class FileItem {\n    private SourcePath: Uri;\n    private TargetPath: Uri | undefined;\n\n    constructor(\n        sourcePath: Uri | string,\n        targetPath?: Uri | string,\n        private IsDir: boolean = false\n    ) {\n        this.SourcePath = this.toUri(sourcePath);\n        if (targetPath !== undefined) {\n            this.TargetPath = this.toUri(targetPath);\n        }\n    }\n\n    get name(): string {\n        return path.basename(this.SourcePath.path);\n    }\n\n    get path(): Uri {\n        return this.SourcePath;\n    }\n\n    get targetPath(): Uri | undefined {\n        return this.TargetPath;\n    }\n\n    get exists(): boolean {\n        if (this.targetPath === undefined) {\n            return false;\n        }\n        return fs.existsSync(this.targetPath.fsPath);\n    }\n\n    get isDir(): boolean {\n        return this.IsDir;\n    }\n\n    public async move(): Promise<FileItem> {\n        assertTargetPath(this.targetPath);\n\n        const edit = new WorkspaceEdit();\n        edit.renameFile(this.path, this.targetPath, { overwrite: true });\n        const result = await workspace.applyEdit(edit);\n\n        if (!result) {\n            throw new Error(`Failed to move file \"${this.targetPath.fsPath}.\"`);\n        }\n\n        this.SourcePath = this.targetPath;\n        return this;\n    }\n\n    public async duplicate(): Promise<FileItem> {\n        assertTargetPath(this.targetPath);\n\n        try {\n            await workspace.fs.copy(this.path, this.targetPath, { overwrite: true });\n            return new FileItem(this.targetPath, undefined, this.isDir);\n        } catch (error) {\n            throw new Error(`Failed to duplicate file \"${this.targetPath.fsPath}. (${error})\"`);\n        }\n    }\n\n    public async remove(): Promise<FileItem> {\n        const edit = new WorkspaceEdit();\n        edit.deleteFile(this.path, { recursive: true, ignoreIfNotExists: true });\n        const result = await workspace.applyEdit(edit);\n\n        if (!result) {\n            throw new Error(`Failed to delete file \"${this.path.fsPath}.\"`);\n        }\n\n        return this;\n    }\n\n    public async create(mkDir?: boolean): Promise<FileItem> {\n        assertTargetPath(this.targetPath);\n\n        if (this.exists) {\n            await workspace.fs.delete(this.targetPath, { recursive: true });\n        }\n\n        if (mkDir === true || this.isDir) {\n            await workspace.fs.createDirectory(this.targetPath);\n        } else {\n            await workspace.fs.writeFile(this.targetPath, new Uint8Array());\n        }\n\n        return new FileItem(this.targetPath, undefined, this.isDir);\n    }\n\n    private toUri(uriOrString: Uri | string): Uri {\n        return uriOrString instanceof Uri ? uriOrString : Uri.file(uriOrString);\n    }\n}\n"
  },
  {
    "path": "src/command/BaseCommand.ts",
    "content": "import type { Uri } from \"vscode\";\nimport type { FileController } from \"../controller\";\nimport type { FileItem } from \"../FileItem\";\nimport type { Command, CommandConstructorOptions } from \"./Command\";\n\ninterface ExecuteControllerOptions {\n    openFileInEditor?: boolean;\n}\n\nexport abstract class BaseCommand<T extends FileController> implements Command {\n    constructor(\n        protected controller: T,\n        readonly options?: CommandConstructorOptions\n    ) {}\n\n    public abstract execute(uri?: Uri): Promise<void>;\n\n    protected async executeController(\n        fileItem: FileItem | undefined,\n        options?: ExecuteControllerOptions\n    ): Promise<void> {\n        if (fileItem) {\n            const result = await this.controller.execute({ fileItem });\n            if (options?.openFileInEditor) {\n                await this.controller.openFileInEditor(result);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/command/Command.ts",
    "content": "import type { Uri } from \"vscode\";\n\nexport interface CommandConstructorOptions {\n    relativeToRoot?: boolean;\n}\n\nexport interface Command {\n    execute(uri?: Uri): Promise<void>;\n}\n"
  },
  {
    "path": "src/command/CopyFileNameCommand.ts",
    "content": "import type { Uri } from \"vscode\";\nimport type { CopyFileNameController } from \"../controller/CopyFileNameController\";\nimport { BaseCommand } from \"./BaseCommand\";\n\nexport class CopyFileNameCommand extends BaseCommand<CopyFileNameController> {\n    public async execute(uri?: Uri): Promise<void> {\n        const dialogOptions = { uri };\n        const fileItem = await this.controller.showDialog(dialogOptions);\n        await this.executeController(fileItem);\n    }\n}\n"
  },
  {
    "path": "src/command/DuplicateFileCommand.ts",
    "content": "import type { Uri } from \"vscode\";\nimport type { MoveFileController } from \"../controller/MoveFileController\";\nimport { getConfiguration } from \"../lib/config\";\nimport { BaseCommand } from \"./BaseCommand\";\n\nexport class DuplicateFileCommand extends BaseCommand<MoveFileController> {\n    public async execute(uri?: Uri): Promise<void> {\n        const typeahead = getConfiguration(\"duplicateFile.typeahead.enabled\") === true;\n        const dialogOptions = { prompt: \"Duplicate As\", uri, typeahead };\n        const fileItem = await this.controller.showDialog(dialogOptions);\n        await this.executeController(fileItem, { openFileInEditor: !fileItem?.isDir });\n    }\n}\n"
  },
  {
    "path": "src/command/MoveFileCommand.ts",
    "content": "import type { Uri } from \"vscode\";\nimport type { MoveFileController } from \"../controller/MoveFileController\";\nimport { getConfiguration } from \"../lib/config\";\nimport { BaseCommand } from \"./BaseCommand\";\n\nexport class MoveFileCommand extends BaseCommand<MoveFileController> {\n    public async execute(uri?: Uri): Promise<void> {\n        const typeahead = getConfiguration(\"moveFile.typeahead.enabled\") === true;\n        const dialogOptions = { prompt: \"New Location\", uri, typeahead };\n        const fileItem = await this.controller.showDialog(dialogOptions);\n        await this.executeController(fileItem);\n    }\n}\n"
  },
  {
    "path": "src/command/NewFileCommand.ts",
    "content": "import type { NewFileController } from \"../controller/NewFileController\";\nimport { getConfiguration } from \"../lib/config\";\nimport { BaseCommand } from \"./BaseCommand\";\n\nexport class NewFileCommand extends BaseCommand<NewFileController> {\n    public async execute(): Promise<void> {\n        const typeahead = this.typeahead;\n        const relativeToRoot = this.options?.relativeToRoot ?? false;\n        const dialogOptions = { prompt: \"File Name\", relativeToRoot, typeahead };\n        const fileItems = await this.controller.showDialog(dialogOptions);\n\n        if (fileItems) {\n            const executions = [...fileItems].map(async (fileItem) => {\n                const result = await this.controller.execute({ fileItem });\n                await this.controller.openFileInEditor(result);\n            });\n            await Promise.all(executions);\n        }\n    }\n\n    protected get typeahead(): boolean {\n        return (getConfiguration(\"newFile.typeahead.enabled\") ?? getConfiguration(\"typeahead.enabled\")) === true;\n    }\n}\n"
  },
  {
    "path": "src/command/NewFolderCommand.ts",
    "content": "import { getConfiguration } from \"../lib/config\";\nimport { NewFileCommand } from \"./NewFileCommand\";\n\nexport class NewFolderCommand extends NewFileCommand {\n    public async execute(): Promise<void> {\n        const typeahead = this.typeahead;\n        const relativeToRoot = this.options?.relativeToRoot ?? false;\n        const dialogOptions = { prompt: \"Folder Name\", relativeToRoot, typeahead };\n        const fileItems = await this.controller.showDialog(dialogOptions);\n\n        if (fileItems) {\n            const executions = [...fileItems].map(async (fileItem) => {\n                await this.controller.execute({ fileItem, isDir: true });\n            });\n            await Promise.all(executions);\n        }\n    }\n\n    protected get typeahead(): boolean {\n        return (getConfiguration(\"newFolder.typeahead.enabled\") ?? getConfiguration(\"typeahead.enabled\")) === true;\n    }\n}\n"
  },
  {
    "path": "src/command/RemoveFileCommand.ts",
    "content": "import type { Uri } from \"vscode\";\nimport type { RemoveFileController } from \"../controller\";\nimport { BaseCommand } from \"./BaseCommand\";\n\nexport class RemoveFileCommand extends BaseCommand<RemoveFileController> {\n    public async execute(uri?: Uri): Promise<void> {\n        const fileItem = await this.controller.showDialog({ uri });\n        await this.executeController(fileItem);\n    }\n}\n"
  },
  {
    "path": "src/command/RenameFileCommand.ts",
    "content": "import type { Uri } from \"vscode\";\nimport type { RenameFileController } from \"../controller/RenameFileController\";\nimport { BaseCommand } from \"./BaseCommand\";\n\nexport class RenameFileCommand extends BaseCommand<RenameFileController> {\n    public async execute(uri?: Uri): Promise<void> {\n        const dialogOptions = { prompt: \"New Name\", uri };\n        const fileItem = await this.controller.showDialog(dialogOptions);\n        await this.executeController(fileItem);\n    }\n}\n"
  },
  {
    "path": "src/command/index.ts",
    "content": "export { Command } from \"./Command\";\nexport { CopyFileNameCommand } from \"./CopyFileNameCommand\";\nexport { DuplicateFileCommand } from \"./DuplicateFileCommand\";\nexport { MoveFileCommand } from \"./MoveFileCommand\";\nexport { NewFileCommand } from \"./NewFileCommand\";\nexport { NewFolderCommand } from \"./NewFolderCommand\";\nexport { RemoveFileCommand } from \"./RemoveFileCommand\";\nexport { RenameFileCommand } from \"./RenameFileCommand\";\n"
  },
  {
    "path": "src/controller/BaseFileController.ts",
    "content": "import path from \"path\";\nimport {\n    commands,\n    type ExtensionContext,\n    env,\n    type InputBoxOptions,\n    type TextEditor,\n    Uri,\n    type WorkspaceFolder,\n    window,\n    workspace,\n} from \"vscode\";\nimport type { FileItem } from \"../FileItem\";\nimport { Cache } from \"../lib/Cache\";\nimport { getConfiguration } from \"../lib/config\";\nimport type { DialogOptions, ExecuteOptions, FileController, SourcePathOptions } from \"./FileController\";\nimport { TypeAheadController } from \"./TypeAheadController\";\n\nenum InputBoxPathType {\n    Root = \"root\",\n    Workspace = \"workspace\",\n}\n\ntype TargetPathInputBoxOptions = InputBoxOptions & Required<Pick<InputBoxOptions, \"value\">>;\n\nexport interface TargetPathInputBoxValueOptions extends DialogOptions {\n    workspaceFolderPath?: string;\n    pathType: InputBoxPathType;\n}\n\nexport abstract class BaseFileController implements FileController {\n    constructor(protected context: ExtensionContext) {}\n\n    public abstract showDialog(options?: DialogOptions): Promise<FileItem | FileItem[] | undefined>;\n\n    public abstract execute(options: ExecuteOptions): Promise<FileItem>;\n\n    private get pathTypeIndicator(): string {\n        return getConfiguration(\"inputBox.pathTypeIndicator\") ?? \"\";\n    }\n\n    public async openFileInEditor(fileItem: FileItem): Promise<TextEditor | undefined> {\n        if (fileItem.isDir) {\n            return;\n        }\n\n        const textDocument = await workspace.openTextDocument(fileItem.path);\n        if (!textDocument) {\n            throw new Error(\"Could not open file!\");\n        }\n\n        const editor = await window.showTextDocument(textDocument);\n        if (!editor) {\n            throw new Error(\"Could not show document!\");\n        }\n\n        return editor;\n    }\n\n    public async closeCurrentFileEditor(): Promise<unknown> {\n        return commands.executeCommand(\"workbench.action.closeActiveEditor\");\n    }\n\n    protected async getTargetPath(sourcePath: string, options: DialogOptions): Promise<string | undefined> {\n        const { prompt } = options;\n\n        const pathType = this.getInputBoxPathType();\n        const workspaceFolderPath = await this.getWorkspaceFolderPath();\n        const value = await this.getTargetPathInputBoxValue(sourcePath, {\n            ...options,\n            workspaceFolderPath,\n            pathType,\n        });\n\n        const targetPath = await this.showTargetPathInputBox({\n            prompt,\n            value,\n        });\n\n        const shouldRestoreAbsolutePath = targetPath && workspaceFolderPath && pathType === InputBoxPathType.Workspace;\n\n        if (shouldRestoreAbsolutePath) {\n            return path.join(\n                workspaceFolderPath,\n                targetPath.replace(new RegExp(`^(${this.pathTypeIndicator}|${workspaceFolderPath})`, \"g\"), \"\")\n            );\n        }\n\n        return targetPath;\n    }\n\n    protected async showTargetPathInputBox(options: TargetPathInputBoxOptions): Promise<string | undefined> {\n        const { prompt, value } = options;\n        const valueSelection = this.getFilenameSelection(value);\n\n        return await window.showInputBox({\n            prompt,\n            value,\n            valueSelection,\n            ignoreFocusOut: true,\n        });\n    }\n\n    private getInputBoxPathType(): InputBoxPathType {\n        const pathType: InputBoxPathType | undefined = getConfiguration(\"inputBox.pathType\");\n\n        if (pathType && Object.values(InputBoxPathType).includes(pathType)) {\n            return pathType;\n        }\n        return InputBoxPathType.Root;\n    }\n\n    protected async getTargetPathInputBoxValue(\n        sourcePath: string,\n        options: TargetPathInputBoxValueOptions\n    ): Promise<string> {\n        const { workspaceFolderPath, pathType } = options;\n\n        if (pathType === InputBoxPathType.Workspace && workspaceFolderPath) {\n            return sourcePath.replace(workspaceFolderPath, this.pathTypeIndicator);\n        }\n        return sourcePath;\n    }\n\n    protected getFilenameSelection(value: string): [number, number] {\n        return [value.length, value.length];\n    }\n\n    public async getSourcePath({ ignoreIfNotExists, uri }: SourcePathOptions = {}): Promise<string> {\n        if (uri?.fsPath) {\n            return uri.fsPath;\n        }\n        // Attempting to get the fileName from the activeTextEditor.\n        // Works for text files only.\n        const activeEditor = window.activeTextEditor;\n        if (activeEditor?.document?.fileName) {\n            return activeEditor.document.fileName;\n        }\n\n        // No activeTextEditor means that we don't have an active file or\n        // the active file is a non-text file (e.g. binary files such as images).\n        // Since there is no actual API to differentiate between the scenarios, we try to retrieve\n        // the path for a non-textual file before throwing an error.\n        const sourcePath = await this.getSourcePathForNonTextFile();\n        if (!sourcePath && ignoreIfNotExists !== true) {\n            throw new Error();\n        }\n\n        return sourcePath;\n    }\n\n    protected getCache(namespace: string): Cache {\n        return new Cache(this.context.globalState, namespace);\n    }\n\n    protected async ensureWritableFile(fileItem: FileItem): Promise<FileItem> {\n        if (!fileItem.exists) {\n            return fileItem;\n        }\n\n        if (fileItem.targetPath === undefined) {\n            throw new Error(\"Missing target path\");\n        }\n\n        const message = `File '${fileItem.targetPath.path}' already exists.`;\n        const action = \"Overwrite\";\n        const overwrite = await window.showInformationMessage(message, { modal: true }, action);\n        if (overwrite) {\n            return fileItem;\n        }\n        throw new Error();\n    }\n\n    private async getSourcePathForNonTextFile(): Promise<string> {\n        // Since there is no API to get details of non-textual files, the following workaround is performed:\n        // 1. Saving the original clipboard data to a local variable.\n        const originalClipboardData = await env.clipboard.readText();\n\n        // 2. Populating the clipboard with an empty string\n        await env.clipboard.writeText(\"\");\n\n        // 3. Calling the copyPathOfActiveFile that populates the clipboard with the source path of the active file.\n        // If there is no active file - the clipboard will not be populated and it will stay with the empty string.\n        await commands.executeCommand(\"workbench.action.files.copyPathOfActiveFile\");\n\n        // 4. Get the clipboard data after the API call\n        const postAPICallClipboardData = await env.clipboard.readText();\n\n        // 5. Return the saved original clipboard data to the clipboard so this method\n        // will not interfere with the clipboard's content.\n        await env.clipboard.writeText(originalClipboardData);\n\n        // 6. Return the clipboard data from the API call (which could be an empty string if it failed).\n        return postAPICallClipboardData;\n    }\n\n    protected async getWorkspaceFolderPath(relativeToRoot?: boolean): Promise<string | undefined>;\n    protected async getWorkspaceFolderPath(): Promise<string | undefined> {\n        const workspaceFolder = await this.selectWorkspaceFolder();\n        return workspaceFolder?.uri.fsPath;\n    }\n\n    protected async selectWorkspaceFolder(): Promise<WorkspaceFolder | undefined> {\n        if (workspace.workspaceFolders && workspace.workspaceFolders.length === 1) {\n            return workspace.workspaceFolders[0];\n        }\n\n        const sourcePath = await this.getSourcePath({ ignoreIfNotExists: true });\n        const uri = Uri.file(sourcePath);\n        return workspace.getWorkspaceFolder(uri) || window.showWorkspaceFolderPick();\n    }\n\n    protected get isMultiRootWorkspace(): boolean {\n        return workspace.workspaceFolders !== undefined && workspace.workspaceFolders.length > 1;\n    }\n\n    protected async getFileSourcePathAtRoot(rootPath: string, options: SourcePathOptions): Promise<string> {\n        const { relativeToRoot = false, typeahead } = options;\n        let sourcePath = rootPath;\n\n        if (typeahead) {\n            const cache = this.getCache(`workspace:${sourcePath}`);\n            const typeAheadController = new TypeAheadController(cache, relativeToRoot);\n            sourcePath = await typeAheadController.showDialog(sourcePath);\n        }\n\n        if (!sourcePath) {\n            throw new Error();\n        }\n\n        return sourcePath;\n    }\n}\n"
  },
  {
    "path": "src/controller/CopyFileNameController.ts",
    "content": "import { env } from \"vscode\";\nimport { FileItem } from \"../FileItem\";\nimport { BaseFileController } from \"./BaseFileController\";\nimport type { DialogOptions, ExecuteOptions } from \"./FileController\";\n\nexport class CopyFileNameController extends BaseFileController {\n    public async showDialog(options: DialogOptions): Promise<FileItem> {\n        const { uri } = options;\n        const sourcePath = await this.getSourcePath({ uri });\n\n        if (!sourcePath) {\n            throw new Error();\n        }\n        return new FileItem(sourcePath);\n    }\n\n    public async execute(options: ExecuteOptions): Promise<FileItem> {\n        await env.clipboard.writeText(options.fileItem.name);\n        return options.fileItem;\n    }\n}\n"
  },
  {
    "path": "src/controller/DuplicateFileController.ts",
    "content": "import type { FileItem } from \"../FileItem\";\nimport type { ExecuteOptions } from \"./FileController\";\nimport { MoveFileController } from \"./MoveFileController\";\n\nexport class DuplicateFileController extends MoveFileController {\n    public async execute(options: ExecuteOptions): Promise<FileItem> {\n        const { fileItem } = options;\n        await this.ensureWritableFile(fileItem);\n        return fileItem.duplicate();\n    }\n}\n"
  },
  {
    "path": "src/controller/FileController.ts",
    "content": "import type { TextEditor, Uri } from \"vscode\";\nimport type { FileItem } from \"../FileItem\";\n\nexport interface DialogOptions {\n    prompt?: string;\n    uri?: Uri;\n    typeahead?: boolean;\n}\n\nexport interface ExecuteOptions {\n    fileItem: FileItem;\n}\n\nexport interface SourcePathOptions {\n    relativeToRoot?: boolean;\n    ignoreIfNotExists?: boolean;\n    uri?: Uri;\n    typeahead?: boolean;\n}\n\nexport interface FileController {\n    showDialog(options?: DialogOptions): Promise<FileItem | FileItem[] | undefined>;\n    execute(options: ExecuteOptions): Promise<FileItem>;\n    openFileInEditor(fileItem: FileItem): Promise<TextEditor | undefined>;\n    closeCurrentFileEditor(): Promise<unknown>;\n    getSourcePath(options?: SourcePathOptions): Promise<string>;\n}\n"
  },
  {
    "path": "src/controller/MoveFileController.ts",
    "content": "import * as path from \"path\";\nimport { FileType, Uri, workspace } from \"vscode\";\nimport { FileItem } from \"../FileItem\";\nimport { BaseFileController, type TargetPathInputBoxValueOptions } from \"./BaseFileController\";\nimport type { DialogOptions, ExecuteOptions } from \"./FileController\";\n\nexport class MoveFileController extends BaseFileController {\n    public async showDialog(options: DialogOptions): Promise<FileItem | undefined> {\n        const { uri } = options;\n        const sourcePath = await this.getSourcePath({ uri });\n\n        if (!sourcePath) {\n            throw new Error();\n        }\n\n        const targetPath = await this.getTargetPath(sourcePath, options);\n\n        if (targetPath) {\n            const isDir = (await workspace.fs.stat(Uri.file(sourcePath))).type === FileType.Directory;\n            return new FileItem(sourcePath, targetPath, isDir);\n        }\n    }\n\n    public async execute(options: ExecuteOptions): Promise<FileItem> {\n        const { fileItem } = options;\n        await this.ensureWritableFile(fileItem);\n        return fileItem.move();\n    }\n\n    protected async getTargetPathInputBoxValue(\n        sourcePath: string,\n        options: TargetPathInputBoxValueOptions\n    ): Promise<string> {\n        const value = await this.getFullTargetPathInputBoxValue(sourcePath, options);\n        return super.getTargetPathInputBoxValue(value, options);\n    }\n\n    private async getFullTargetPathInputBoxValue(\n        sourcePath: string,\n        options: TargetPathInputBoxValueOptions\n    ): Promise<string> {\n        const { typeahead, workspaceFolderPath } = options;\n\n        if (!typeahead) {\n            return sourcePath;\n        }\n\n        if (!workspaceFolderPath) {\n            throw new Error();\n        }\n\n        const rootPath = await this.getFileSourcePathAtRoot(workspaceFolderPath, { relativeToRoot: true, typeahead });\n        const fileName = path.basename(sourcePath);\n\n        return path.join(rootPath, fileName);\n    }\n\n    protected getFilenameSelection(value: string): [number, number] {\n        const basename = path.basename(value);\n        const start = value.length - basename.length;\n        const dot = basename.lastIndexOf(\".\");\n        const exclusiveEndIndex = dot <= 0 ? value.length : start + dot;\n\n        return [start, exclusiveEndIndex];\n    }\n}\n"
  },
  {
    "path": "src/controller/NewFileController.ts",
    "content": "import expand from \"brace-expansion\";\nimport * as path from \"path\";\nimport { window } from \"vscode\";\nimport { FileItem } from \"../FileItem\";\nimport { BaseFileController, type TargetPathInputBoxValueOptions } from \"./BaseFileController\";\nimport type { DialogOptions, ExecuteOptions, SourcePathOptions } from \"./FileController\";\n\nexport interface NewFileDialogOptions extends Omit<DialogOptions, \"uri\"> {\n    relativeToRoot?: boolean;\n}\n\nexport interface NewFileExecuteOptions extends ExecuteOptions {\n    isDir?: boolean;\n}\n\nexport class NewFileController extends BaseFileController {\n    public async showDialog(options: NewFileDialogOptions): Promise<FileItem[] | undefined> {\n        const { relativeToRoot = false, typeahead } = options;\n        const sourcePath = await this.getNewFileSourcePath({ relativeToRoot, typeahead });\n        const targetPath = await this.getTargetPath(sourcePath, options);\n\n        if (!targetPath) {\n            return;\n        }\n\n        return expand(targetPath.replace(/\\\\/g, \"/\")).map((filePath) => {\n            const realPath = path.resolve(sourcePath, filePath);\n            const isDir = filePath.endsWith(\"/\");\n            return new FileItem(sourcePath, realPath, isDir);\n        });\n    }\n\n    public async execute(options: NewFileExecuteOptions): Promise<FileItem> {\n        const { fileItem, isDir = false } = options;\n        await this.ensureWritableFile(fileItem);\n        try {\n            return fileItem.create(isDir);\n        } catch {\n            throw new Error(`Error creating file '${fileItem.path}'.`);\n        }\n    }\n\n    protected async getTargetPathInputBoxValue(\n        sourcePath: string,\n        options: TargetPathInputBoxValueOptions\n    ): Promise<string> {\n        const value = path.join(sourcePath, path.sep);\n        return super.getTargetPathInputBoxValue(value, options);\n    }\n\n    public async getNewFileSourcePath({ relativeToRoot, typeahead }: SourcePathOptions): Promise<string> {\n        const rootPath = await this.getRootPath(relativeToRoot === true);\n\n        if (!rootPath) {\n            throw new Error();\n        }\n\n        return this.getFileSourcePathAtRoot(rootPath, { relativeToRoot, typeahead });\n    }\n\n    private async getRootPath(relativeToRoot: boolean): Promise<string | undefined> {\n        if (relativeToRoot) {\n            return this.getWorkspaceFolderPath(relativeToRoot);\n        }\n        return path.dirname(await this.getSourcePath());\n    }\n\n    protected async getWorkspaceFolderPath(relativeToRoot: boolean): Promise<string | undefined> {\n        const requiresWorkspaceFolderPick = relativeToRoot && this.isMultiRootWorkspace;\n        if (requiresWorkspaceFolderPick) {\n            const workspaceFolder = await window.showWorkspaceFolderPick();\n            return workspaceFolder?.uri.fsPath;\n        }\n\n        return super.getWorkspaceFolderPath();\n    }\n}\n"
  },
  {
    "path": "src/controller/RemoveFileController.ts",
    "content": "import * as path from \"path\";\nimport { window, workspace } from \"vscode\";\nimport { FileItem } from \"../FileItem\";\nimport { BaseFileController } from \"./BaseFileController\";\nimport type { DialogOptions, ExecuteOptions } from \"./FileController\";\n\nexport class RemoveFileController extends BaseFileController {\n    public async showDialog(options: DialogOptions): Promise<FileItem | undefined> {\n        const { uri } = options;\n        const sourcePath = await this.getSourcePath({ uri });\n\n        if (!sourcePath) {\n            throw new Error();\n        }\n\n        if (this.confirmDelete === false) {\n            return new FileItem(sourcePath);\n        }\n\n        const message = `Are you sure you want to delete '${path.basename(sourcePath)}'?`;\n        const action = \"Move to Trash\";\n        const remove = await window.showInformationMessage(message, { modal: true }, action);\n        if (remove) {\n            return new FileItem(sourcePath);\n        }\n    }\n\n    public async execute(options: ExecuteOptions): Promise<FileItem> {\n        const { fileItem } = options;\n        try {\n            await fileItem.remove();\n        } catch (_e) {\n            throw new Error(`Error deleting file '${fileItem.path}'.`);\n        }\n        return fileItem;\n    }\n\n    private get confirmDelete(): boolean {\n        return workspace.getConfiguration(\"explorer\", null).get(\"confirmDelete\") === true;\n    }\n}\n"
  },
  {
    "path": "src/controller/RenameFileController.ts",
    "content": "import * as path from \"path\";\nimport type { DialogOptions } from \"./FileController\";\nimport { MoveFileController } from \"./MoveFileController\";\n\nexport class RenameFileController extends MoveFileController {\n    protected async getTargetPath(sourcePath: string, options: DialogOptions): Promise<string | undefined> {\n        const { prompt } = options;\n        const value = path.basename(sourcePath);\n        const targetPath = await this.showTargetPathInputBox({ prompt, value });\n\n        if (targetPath) {\n            const basePath = path.dirname(sourcePath);\n            return path.join(basePath, targetPath.replace(basePath, \"\"));\n        }\n    }\n}\n"
  },
  {
    "path": "src/controller/TypeAheadController.ts",
    "content": "import * as path from \"path\";\nimport { type QuickPickItem, window } from \"vscode\";\nimport type { Cache } from \"../lib/Cache\";\nimport { TreeWalker } from \"../lib/TreeWalker\";\n\nasync function waitForIOEvents(): Promise<void> {\n    return new Promise((resolve) => setImmediate(resolve));\n}\n\nconst ROOT_PATH = \"/\";\n\nexport class TypeAheadController {\n    constructor(\n        private cache: Cache,\n        private relativeToRoot: boolean = false\n    ) {}\n\n    public async showDialog(sourcePath: string): Promise<string> {\n        const items = await this.buildQuickPickItems(sourcePath);\n\n        const item = items.length === 1 ? items[0] : await this.showQuickPick(items);\n\n        if (!item) {\n            throw new Error();\n        }\n\n        const selection = item.label;\n        this.cache.put(\"last\", selection);\n\n        return path.join(sourcePath, selection);\n    }\n\n    private async buildQuickPickItems(sourcePath: string): Promise<QuickPickItem[]> {\n        const lastEntry: string = this.cache.get(\"last\");\n        const header = this.buildQuickPickItemsHeader(lastEntry);\n\n        const directories = (await this.getDirectoriesAtSourcePath(sourcePath))\n            .filter((directory) => directory !== lastEntry && directory !== ROOT_PATH)\n            .map((directory) => this.buildQuickPickItem(directory));\n\n        if (directories.length === 0 && header.length === 1) {\n            return header;\n        }\n\n        return [...header, ...directories];\n    }\n\n    private async getDirectoriesAtSourcePath(sourcePath: string): Promise<string[]> {\n        await waitForIOEvents();\n        const treeWalker = new TreeWalker();\n        return treeWalker.directories(sourcePath);\n    }\n\n    private buildQuickPickItemsHeader(lastEntry: string | undefined): QuickPickItem[] {\n        const items = [\n            this.buildQuickPickItem(ROOT_PATH, `- ${this.relativeToRoot ? \"workspace root\" : \"current file\"}`),\n        ];\n\n        if (lastEntry && lastEntry !== ROOT_PATH) {\n            items.push(this.buildQuickPickItem(lastEntry, \"- last selection\"));\n        }\n\n        return items;\n    }\n\n    private buildQuickPickItem(label: string, description?: string | undefined): QuickPickItem {\n        return { description, label };\n    }\n\n    private async showQuickPick(items: readonly QuickPickItem[]) {\n        const hint = \"larger projects may take a moment to load\";\n        const placeHolder = `First, select an existing path to create relative to (${hint})`;\n        return window.showQuickPick(items, { placeHolder, ignoreFocusOut: true });\n    }\n}\n"
  },
  {
    "path": "src/controller/index.ts",
    "content": "export { CopyFileNameController } from \"./CopyFileNameController\";\nexport { DuplicateFileController } from \"./DuplicateFileController\";\nexport { FileController } from \"./FileController\";\nexport { MoveFileController } from \"./MoveFileController\";\nexport { NewFileController } from \"./NewFileController\";\nexport { RemoveFileController } from \"./RemoveFileController\";\n"
  },
  {
    "path": "src/extension.ts",
    "content": "import * as vscode from \"vscode\";\nimport {\n    type Command,\n    CopyFileNameCommand,\n    DuplicateFileCommand,\n    MoveFileCommand,\n    NewFileCommand,\n    NewFolderCommand,\n    RemoveFileCommand,\n    RenameFileCommand,\n} from \"./command\";\nimport {\n    CopyFileNameController,\n    DuplicateFileController,\n    MoveFileController,\n    NewFileController,\n    RemoveFileController,\n} from \"./controller\";\nimport { RenameFileController } from \"./controller/RenameFileController\";\n\nfunction handleError(err: Error) {\n    if (err?.message) {\n        vscode.window.showErrorMessage(err.message);\n    }\n    return err;\n}\n\nfunction register(context: vscode.ExtensionContext, command: Command, commandName: string) {\n    const proxy = (...args: never[]) => command.execute(...args).catch(handleError);\n    const disposable = vscode.commands.registerCommand(`fileutils.${commandName}`, proxy);\n\n    context.subscriptions.push(disposable);\n}\n\nexport function activate(context: vscode.ExtensionContext): void {\n    const copyFileNameController = new CopyFileNameController(context);\n    const duplicateFileController = new DuplicateFileController(context);\n    const moveFileController = new MoveFileController(context);\n    const newFileController = new NewFileController(context);\n    const removeFileController = new RemoveFileController(context);\n    const renameFileController = new RenameFileController(context);\n\n    register(context, new CopyFileNameCommand(copyFileNameController), \"copyFileName\");\n    register(context, new DuplicateFileCommand(duplicateFileController), \"duplicateFile\");\n    register(context, new MoveFileCommand(moveFileController), \"moveFile\");\n    register(context, new NewFileCommand(newFileController, { relativeToRoot: true }), \"newFileAtRoot\");\n    register(context, new NewFileCommand(newFileController), \"newFile\");\n    register(context, new NewFolderCommand(newFileController, { relativeToRoot: true }), \"newFolderAtRoot\");\n    register(context, new NewFolderCommand(newFileController), \"newFolder\");\n    register(context, new RemoveFileCommand(removeFileController), \"removeFile\");\n    register(context, new RenameFileCommand(renameFileController), \"renameFile\");\n}\n"
  },
  {
    "path": "src/lib/Cache.ts",
    "content": "import type * as vscode from \"vscode\";\n\nexport class Cache {\n    private cache: { [key: string]: unknown };\n\n    constructor(\n        private storage: vscode.Memento,\n        private namespace: string\n    ) {\n        this.cache = storage.get(this.namespace, {});\n    }\n\n    public put(key: string, value: unknown): void {\n        this.cache[key] = value;\n        this.storage.update(this.namespace, this.cache);\n    }\n\n    public get<T>(key: string, defaultValue?: unknown): T {\n        return (key in this.cache ? this.cache[key] : defaultValue) as T;\n    }\n}\n"
  },
  {
    "path": "src/lib/TreeWalker.ts",
    "content": "import glob from \"fast-glob\";\nimport * as path from \"path\";\nimport { workspace } from \"vscode\";\n\ninterface ExtendedProcess {\n    noAsar: boolean;\n}\n\nexport class TreeWalker {\n    public async directories(sourcePath: string): Promise<string[]> {\n        try {\n            this.ensureFailSafeFileLookup();\n            const files = await glob(\"**\", {\n                cwd: sourcePath,\n                onlyDirectories: true,\n                ignore: this.getExcludePatterns(),\n            });\n            return files.map((file) => path.join(path.sep, file)).sort();\n        } catch (err) {\n            const details = (err as Error).message;\n            throw new Error(`Unable to list subdirectories for directory \"${sourcePath}\". Details: (${details})`);\n        }\n    }\n\n    private getExcludePatterns(): string[] {\n        const exclude = new Set([\n            ...Object.keys(workspace.getConfiguration(\"search.exclude\")),\n            ...Object.keys(workspace.getConfiguration(\"files.exclude\")),\n        ]);\n        return Array.from(exclude);\n    }\n\n    private ensureFailSafeFileLookup() {\n        (process as unknown as ExtendedProcess).noAsar = true;\n    }\n}\n"
  },
  {
    "path": "src/lib/config.ts",
    "content": "import { workspace } from \"vscode\";\n\nexport function getConfiguration<T>(key: string): T | undefined {\n    return workspace.getConfiguration(\"fileutils\", null).get(key);\n}\n"
  },
  {
    "path": "test/command/CopyFileNameCommand.test.ts",
    "content": "import { expect } from \"chai\";\nimport { env } from \"vscode\";\nimport { CopyFileNameCommand } from \"../../src/command\";\nimport { CopyFileNameController } from \"../../src/controller\";\nimport { FileItem } from \"../../src/FileItem\";\nimport * as helper from \"../helper\";\n\ndescribe(CopyFileNameCommand.name, () => {\n    const clipboardInitialTestData = \"SOME_TEXT\";\n    const subject = new CopyFileNameCommand(new CopyFileNameController(helper.createExtensionContext()));\n\n    beforeEach(helper.beforeEach);\n\n    afterEach(helper.afterEach);\n\n    describe(\"as command\", () => {\n        afterEach(async () => {\n            await env.clipboard.writeText(clipboardInitialTestData);\n        });\n\n        describe(\"with open text document\", () => {\n            beforeEach(async () => helper.openDocument(helper.editorFile1));\n\n            afterEach(async () => helper.closeAllEditors());\n\n            it(\"should put the file name to the clipboard\", async () => {\n                await subject.execute();\n                const clipboardData = await env.clipboard.readText();\n                expect(clipboardData).to.equal(new FileItem(helper.editorFile1).name);\n            });\n        });\n\n        describe(\"without an open text document\", () => {\n            beforeEach(async () => {\n                await helper.closeAllEditors();\n                await env.clipboard.writeText(clipboardInitialTestData);\n            });\n\n            it(\"should ignore the command call and not change the clipboard data\", async () => {\n                try {\n                    await subject.execute();\n                    expect.fail(\"must fail\");\n                } catch (_e) {\n                    const clipboardData = await env.clipboard.readText();\n                    expect(clipboardData).to.equal(clipboardInitialTestData);\n                }\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/command/DuplicateFileCommand.test.ts",
    "content": "import { expect } from \"chai\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport { Uri, window, workspace } from \"vscode\";\nimport { DuplicateFileCommand } from \"../../src/command/DuplicateFileCommand\";\nimport { DuplicateFileController } from \"../../src/controller\";\nimport * as helper from \"../helper\";\n\ndescribe(DuplicateFileCommand.name, () => {\n    const subject = new DuplicateFileCommand(new DuplicateFileController(helper.createExtensionContext()));\n\n    beforeEach(async () => {\n        await helper.beforeEach();\n        helper.createGetConfigurationStub({ \"duplicateFile.typeahead.enabled\": false, \"inputBox.path\": \"root\" });\n    });\n\n    afterEach(helper.afterEach);\n\n    describe(\"as command\", () => {\n        describe(\"with open text document\", () => {\n            beforeEach(async () => {\n                await helper.openDocument(helper.editorFile1);\n                helper.createShowInputBoxStub().resolves(helper.targetFile.path);\n                helper.createShowQuickPickStub().resolves({ label: \"/\", description: \"\" });\n            });\n\n            afterEach(async () => {\n                await helper.closeAllEditors();\n            });\n\n            helper.protocol.it(\"should prompt for file destination\", subject, \"Duplicate As\");\n            helper.protocol.it(\"should duplicate current file to destination\", subject);\n            helper.protocol.describe(\"with target file in non-existent nested directory\", subject);\n            helper.protocol.describe(\"when target destination exists\", subject);\n            helper.protocol.it(\"should open target file as active editor\", subject);\n\n            helper.protocol.describe(\"typeahead configuration\", subject, {\n                command: \"duplicateFile\",\n                items: helper.quickPick.typeahead.items.workspace,\n            });\n\n            helper.protocol.describe(\"inputBox configuration\", subject, {\n                editorFile: helper.editorFile1,\n            });\n        });\n\n        helper.protocol.describe(\"without an open text document\", subject);\n    });\n\n    describe(\"as context menu\", () => {\n        describe(\"with selected file\", () => {\n            beforeEach(async () => helper.createShowInputBoxStub().resolves(helper.targetFile.path));\n\n            helper.protocol.it(\"should prompt for file destination\", subject, \"Duplicate As\");\n            helper.protocol.it(\"should duplicate current file to destination\", subject, helper.editorFile1);\n            helper.protocol.it(\"should open target file as active editor\", subject, helper.editorFile1);\n        });\n\n        describe(\"with selected directory\", () => {\n            const sourceDirectory = Uri.file(path.resolve(helper.tmpDir.path, \"duplicate-source-dir\"));\n            const targetDirectory = Uri.file(path.resolve(helper.tmpDir.path, \"duplicate-target-dir\"));\n\n            beforeEach(async () => {\n                await workspace.fs.createDirectory(sourceDirectory);\n                helper.createShowInputBoxStub().resolves(targetDirectory.path);\n            });\n\n            afterEach(async () => {\n                await workspace.fs.delete(sourceDirectory, { recursive: true, useTrash: false });\n                await workspace.fs.delete(targetDirectory, { recursive: true, useTrash: false });\n            });\n\n            it(\"should prompt for file destination\", async () => {\n                await subject.execute(sourceDirectory);\n                const value = sourceDirectory.path;\n                const valueSelection = [value.length - (value.split(path.sep).pop() as string).length, value.length];\n                const prompt = \"Duplicate As\";\n                expect(window.showInputBox).to.have.been.calledWithExactly({\n                    prompt,\n                    value,\n                    valueSelection,\n                    ignoreFocusOut: true,\n                });\n            });\n\n            it(\"should duplicate current file to destination\", async () => {\n                await subject.execute(sourceDirectory);\n                const message = `${targetDirectory} does not exist`;\n                expect(fs.existsSync(targetDirectory.fsPath), message).to.be.true;\n            });\n\n            it(\"should not open target file as active editor\", async () => {\n                await subject.execute(sourceDirectory);\n                expect(window.activeTextEditor?.document?.fileName).not.to.equal(targetDirectory.path);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/command/MoveFileCommand.test.ts",
    "content": "import { MoveFileCommand } from \"../../src/command\";\nimport { MoveFileController } from \"../../src/controller\";\nimport * as helper from \"../helper\";\n\ndescribe(MoveFileCommand.name, () => {\n    const subject = new MoveFileCommand(new MoveFileController(helper.createExtensionContext()));\n\n    beforeEach(async () => {\n        await helper.beforeEach();\n        helper.createGetConfigurationStub({ \"moveFile.typeahead.enabled\": false, \"inputBox.path\": \"root\" });\n    });\n\n    afterEach(helper.afterEach);\n\n    describe(\"as command\", () => {\n        describe(\"with open text document\", () => {\n            beforeEach(async () => {\n                await helper.openDocument(helper.editorFile1);\n                helper.createShowInputBoxStub().resolves(helper.targetFile.path);\n                helper.createShowQuickPickStub().resolves({ label: \"/\", description: \"\" });\n            });\n\n            afterEach(async () => {\n                await helper.closeAllEditors();\n            });\n\n            helper.protocol.it(\"should prompt for file destination\", subject, \"New Location\");\n            helper.protocol.it(\"should move current file to destination\", subject);\n            helper.protocol.describe(\"with target file in non-existent nested directory\", subject);\n\n            helper.protocol.describe(\"typeahead configuration\", subject, {\n                command: \"moveFile\",\n                items: helper.quickPick.typeahead.items.workspace,\n            });\n\n            helper.protocol.describe(\"inputBox configuration\", subject, {\n                editorFile: helper.editorFile1,\n            });\n        });\n\n        helper.protocol.describe(\"without an open text document\", subject);\n    });\n\n    describe(\"as context menu\", () => {\n        beforeEach(async () => helper.createShowInputBoxStub().resolves(helper.targetFile.path));\n\n        helper.protocol.it(\"should prompt for file destination\", subject, \"New Location\");\n        helper.protocol.it(\"should move current file to destination\", subject, helper.editorFile1);\n    });\n});\n"
  },
  {
    "path": "test/command/NewFileCommand.test.ts",
    "content": "import { expect } from \"chai\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport { Uri, window, workspace } from \"vscode\";\nimport { NewFileCommand } from \"../../src/command\";\nimport { NewFileController } from \"../../src/controller\";\nimport * as helper from \"../helper\";\n\ndescribe(NewFileCommand.name, () => {\n    beforeEach(async () => {\n        await helper.beforeEach();\n        helper.createGetConfigurationStub({ \"newFile.typeahead.enabled\": false, \"inputBox.path\": \"root\" });\n    });\n\n    afterEach(helper.afterEach);\n\n    describe('when \"relativeToRoot\" is \"false\"', async () => {\n        const subject = new NewFileCommand(new NewFileController(helper.createExtensionContext()));\n\n        beforeEach(async () => {\n            await helper.openDocument(helper.editorFile1);\n            helper.createShowInputBoxStub().resolves(path.basename(helper.targetFile.path));\n            helper.createShowQuickPickStub().resolves({ label: \"/\", description: \"\" });\n        });\n\n        afterEach(async () => {\n            await helper.closeAllEditors();\n        });\n\n        it(\"should prompt for file destination\", async () => {\n            await subject.execute();\n            const prompt = \"File Name\";\n            const value = path.join(path.dirname(helper.editorFile1.path), path.sep);\n            const valueSelection = [value.length, value.length];\n            expect(window.showInputBox).to.have.been.calledWithExactly({\n                prompt,\n                value,\n                valueSelection,\n                ignoreFocusOut: true,\n            });\n        });\n\n        helper.protocol.describe(\"typeahead configuration\", subject, {\n            command: \"newFile\",\n            items: helper.quickPick.typeahead.items.currentFile,\n        });\n\n        helper.protocol.describe(\"inputBox configuration\", subject, {\n            editorFile: helper.editorFile1,\n            expectedPath: \"\",\n        });\n\n        it(\"should create the file at destination\", async () => {\n            await subject.execute();\n            const message = `${helper.targetFile.path} does not exist`;\n            expect(fs.existsSync(helper.targetFile.fsPath), message).to.be.true;\n        });\n\n        describe(\"file path ends with path separator\", () => {\n            beforeEach(async () => {\n                const fileName = path.basename(helper.targetFile.fsPath) + path.sep;\n                helper.createShowInputBoxStub().resolves(fileName);\n            });\n\n            it(\"should create the directory at destination\", async () => {\n                await subject.execute();\n                const message = `${helper.targetFile.path} must be a directory`;\n                expect(fs.statSync(helper.targetFile.fsPath).isDirectory(), message).to.be.true;\n            });\n        });\n\n        describe(\"file path contains dot and backslash path separator\", () => {\n            beforeEach(async () => {\n                const fileName = helper.targetFileWithDot.fsPath.replace(/\\//g, \"\\\\\");\n                helper.createShowInputBoxStub().resolves(fileName);\n            });\n\n            it(\"should create the file at destination\", async () => {\n                await subject.execute();\n                const message = `${helper.targetFileWithDot.path} does not exist`;\n                expect(fs.existsSync(helper.targetFileWithDot.fsPath), message).to.be.true;\n            });\n        });\n\n        helper.protocol.describe(\"with target file in non-existent nested directory\", subject);\n        helper.protocol.describe(\"when target destination exists\", subject, { overwriteFileContent: \"\" });\n        helper.protocol.it(\"should open target file as active editor\", subject);\n    });\n\n    describe('when \"relativeToRoot\" is \"true\"', () => {\n        const subject = new NewFileCommand(new NewFileController(helper.createExtensionContext()), {\n            relativeToRoot: true,\n        });\n\n        beforeEach(async () => {\n            helper.createShowInputBoxStub().callsFake(async (options) => {\n                if (options.value) {\n                    return path.join(options.value, \"filename.txt\");\n                }\n            });\n            helper.createShowQuickPickStub().resolves({ label: \"/\", description: \"\" });\n        });\n\n        describe(\"with one workspace\", () => {\n            beforeEach(async () => {\n                helper.createWorkspaceFoldersStub(helper.workspaceFolderA);\n                helper.createGetWorkspaceFolderStub();\n            });\n\n            it(\"should select first workspace\", async () => {\n                await subject.execute();\n                expect(workspace.getWorkspaceFolder).to.have.not.been.called;\n\n                const prompt = \"File Name\";\n                const value = path.join(helper.workspacePathA, path.sep);\n                const valueSelection = [value.length, value.length];\n                expect(window.showInputBox).to.have.been.calledWithExactly({\n                    prompt,\n                    value,\n                    valueSelection,\n                    ignoreFocusOut: true,\n                });\n            });\n\n            helper.protocol.describe(\"typeahead configuration\", subject, {\n                command: \"newFile\",\n                items: helper.quickPick.typeahead.items.workspace,\n            });\n        });\n\n        describe(\"with multiple workspaces\", () => {\n            beforeEach(async () => {\n                helper.createWorkspaceFoldersStub(helper.workspaceFolderA, helper.workspaceFolderB);\n                helper.createStubObject(window, \"showWorkspaceFolderPick\").resolves(helper.workspaceFolderB);\n            });\n\n            afterEach(async () => {\n                helper.restoreObject(window.showWorkspaceFolderPick);\n            });\n\n            it(\"should show workspace selector\", async () => {\n                await subject.execute();\n                expect(window.showWorkspaceFolderPick).to.have.been.called;\n\n                const prompt = \"File Name\";\n                const value = path.join(helper.workspaceFolderB.uri.fsPath, path.sep);\n                const valueSelection = [value.length, value.length];\n                expect(window.showInputBox).to.have.been.calledWithExactly({\n                    prompt,\n                    value,\n                    valueSelection,\n                    ignoreFocusOut: true,\n                });\n            });\n\n            describe(\"with open document\", () => {\n                beforeEach(async () => {\n                    helper.createGetWorkspaceFolderStub().returns(helper.workspaceFolderB);\n                    await helper.openDocument(helper.editorFile1);\n                    await subject.execute();\n                });\n\n                afterEach(async () => {\n                    await helper.closeAllEditors();\n                });\n\n                it(\"should show workspace selector\", async () => {\n                    expect(window.showWorkspaceFolderPick).to.have.been.called;\n                });\n\n                it(\"should select workspace for open file\", async () => {\n                    expect(workspace.getWorkspaceFolder).to.have.been.calledWith(Uri.file(helper.editorFile1.fsPath));\n                });\n            });\n\n            helper.protocol.describe(\"typeahead configuration\", subject, {\n                command: \"newFile\",\n                items: helper.quickPick.typeahead.items.workspace,\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/command/RemoveFileCommand.test.ts",
    "content": "import { expect } from \"chai\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport { window } from \"vscode\";\nimport { RemoveFileCommand } from \"../../src/command\";\nimport { RemoveFileController } from \"../../src/controller\";\nimport * as helper from \"../helper\";\n\ndescribe(RemoveFileCommand.name, () => {\n    const subject = new RemoveFileCommand(new RemoveFileController(helper.createExtensionContext()));\n\n    beforeEach(helper.beforeEach);\n\n    afterEach(helper.afterEach);\n\n    describe(\"as command\", () => {\n        describe(\"with open text document\", () => {\n            beforeEach(async () => {\n                await helper.openDocument(helper.editorFile1);\n                helper.createShowInformationMessageStub().resolves(helper.targetFile.path);\n                helper.createGetConfigurationStub({});\n            });\n\n            afterEach(async () => {\n                await helper.closeAllEditors();\n            });\n\n            describe(\"configuration\", () => {\n                describe('when \"explorer.confirmDelete\" is \"true\"', () => {\n                    beforeEach(async () => {\n                        helper.createGetConfigurationStub({ confirmDelete: true });\n                    });\n\n                    it(\"should show a confirmation dialog\", async () => {\n                        await subject.execute();\n                        const message = `Are you sure you want to delete '${path.basename(helper.editorFile1.path)}'?`;\n                        const action = \"Move to Trash\";\n                        const options = { modal: true };\n                        expect(window.showInformationMessage).to.have.been.calledWith(message, options, action);\n                    });\n                });\n\n                describe('when \"explorer.confirmDelete\" is \"false\"', () => {\n                    beforeEach(async () => {\n                        helper.createGetConfigurationStub({ confirmDelete: false });\n                    });\n\n                    it(\"should delete the file without confirmation\", async () => {\n                        await subject.execute();\n                        const message = `${helper.editorFile1.path} does not exist`;\n                        expect(window.showInformationMessage).to.have.not.been.called;\n                        expect(fs.existsSync(helper.editorFile1.fsPath), message).to.be.false;\n                    });\n                });\n            });\n\n            describe('when answered with \"Move to Trash\"', () => {\n                it(\"should delete the file\", async () => {\n                    await subject.execute();\n                    const message = `${helper.editorFile1.path} does exist`;\n                    expect(fs.existsSync(helper.editorFile1.fsPath), message).to.be.false;\n                });\n            });\n\n            describe('when answered with \"Cancel\"', () => {\n                beforeEach(async () => {\n                    helper.createGetConfigurationStub({ confirmDelete: true });\n                    helper.createShowInformationMessageStub().resolves(false);\n                });\n\n                it(\"should leave the file untouched\", async () => {\n                    try {\n                        await subject.execute();\n                        expect.fail(\"Must fail\");\n                    } catch (_e) {\n                        const message = `${helper.editorFile1.path} does not exist`;\n                        expect(fs.existsSync(helper.editorFile1.fsPath), message).to.be.true;\n                    }\n                });\n            });\n\n            describe(\"prefer uri over current editor\", () => {\n                beforeEach(async () => {\n                    helper.createGetConfigurationStub({ confirmDelete: false });\n                });\n\n                it(\"should delete the file without confirmation\", async () => {\n                    await subject.execute(helper.editorFile2);\n                    const message = `${helper.editorFile2.path} does not exist`;\n                    expect(fs.existsSync(helper.editorFile2.fsPath), message).to.be.false;\n                });\n            });\n        });\n\n        describe(\"without an open text document\", () => {\n            beforeEach(async () => {\n                await helper.closeAllEditors();\n                helper.createShowInformationMessageStub();\n            });\n\n            it(\"should ignore the command call\", async () => {\n                try {\n                    await subject.execute();\n                    expect.fail(\"Must fail\");\n                } catch {\n                    expect(window.showInformationMessage).to.have.not.been.called;\n                }\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/command/RenameFileCommand.test.ts",
    "content": "import { expect } from \"chai\";\nimport * as path from \"path\";\nimport { Uri, window } from \"vscode\";\nimport { RenameFileCommand } from \"../../src/command\";\nimport { RenameFileController } from \"../../src/controller/RenameFileController\";\nimport * as helper from \"../helper\";\n\ndescribe(RenameFileCommand.name, () => {\n    const subject = new RenameFileCommand(new RenameFileController(helper.createExtensionContext()));\n\n    beforeEach(async () => {\n        await helper.beforeEach();\n    });\n\n    afterEach(helper.afterEach);\n\n    describe(\"as command\", () => {\n        describe(\"with open text document\", () => {\n            beforeEach(async () => {\n                await helper.openDocument(helper.editorFile1);\n                helper.createShowInputBoxStub().resolves(helper.targetFile.path);\n            });\n\n            afterEach(async () => {\n                await helper.closeAllEditors();\n            });\n\n            it(\"should prompt for file destination\", async () => {\n                await subject.execute();\n                const prompt = \"New Name\";\n                const value = path.basename(helper.editorFile1.fsPath);\n                const valueSelection = [value.length - 9, value.length - 3];\n                expect(window.showInputBox).to.have.been.calledWithExactly({\n                    prompt,\n                    value,\n                    valueSelection,\n                    ignoreFocusOut: true,\n                });\n            });\n\n            helper.protocol.it(\"should move current file to destination\", subject);\n            helper.protocol.describe(\"with target file in non-existent nested directory\", subject);\n            helper.protocol.it(\"should open target file as active editor\", subject);\n\n            describe(\"prefer uri over current editor\", () => {\n                beforeEach(async () => {\n                    const targetFile = Uri.file(path.resolve(`${helper.editorFile2.fsPath}.tmp`));\n                    helper.createShowInputBoxStub().resolves(targetFile.path);\n                });\n\n                it(\"should prompt for file destination\", async () => {\n                    await subject.execute(helper.editorFile2);\n                    const prompt = \"New Name\";\n                    const value = path.basename(helper.editorFile2.fsPath);\n                    const valueSelection = [value.length - 9, value.length - 3];\n                    expect(window.showInputBox).to.have.been.calledWithExactly({\n                        prompt,\n                        value,\n                        valueSelection,\n                        ignoreFocusOut: true,\n                    });\n                });\n            });\n        });\n\n        describe(\"without an open text document\", () => {\n            beforeEach(async () => {\n                await helper.closeAllEditors();\n                helper.createShowInputBoxStub();\n            });\n\n            it(\"should ignore the command call\", async () => {\n                try {\n                    await subject.execute();\n                    expect.fail(\"Must fail\");\n                } catch {\n                    expect(window.showInputBox).to.have.not.been.called;\n                }\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/fixtures/file-1.rb",
    "content": "class FileOne; end"
  },
  {
    "path": "test/fixtures/file-2.rb",
    "content": "class FileTwo; end"
  },
  {
    "path": "test/helper/callbacks.ts",
    "content": "import { existsSync } from \"fs\";\nimport path from \"path\";\nimport { Uri, workspace } from \"vscode\";\nimport {\n    editorFile1,\n    editorFile2,\n    fixtureFile1,\n    fixtureFile2,\n    tmpDir,\n    workspaceFolderA,\n    workspaceFolderB,\n} from \"./environment\";\nimport {\n    restoreExecuteCommand,\n    restoreGetConfiguration,\n    restoreGetWorkspaceFolder,\n    restoreShowInformationMessage,\n    restoreShowInputBox,\n    restoreShowQuickPick,\n    restoreShowWorkspaceFolderPick,\n    restoreWorkspaceFolders,\n} from \"./stubs\";\n\nexport async function beforeEach(): Promise<void> {\n    if (existsSync(tmpDir.fsPath)) {\n        await workspace.fs.delete(tmpDir, { recursive: true, useTrash: false });\n    }\n    await workspace.fs.copy(fixtureFile1, editorFile1, { overwrite: true });\n    await workspace.fs.copy(fixtureFile2, editorFile2, { overwrite: true });\n\n    await workspace.fs.createDirectory(Uri.file(path.resolve(tmpDir.fsPath, \"dir-1\")));\n    await workspace.fs.createDirectory(Uri.file(path.resolve(tmpDir.fsPath, \"dir-2\")));\n\n    await workspace.fs.createDirectory(workspaceFolderA.uri);\n    await workspace.fs.createDirectory(workspaceFolderB.uri);\n\n    await workspace.fs.createDirectory(Uri.file(path.resolve(workspaceFolderA.uri.fsPath, \"dir-1\")));\n    await workspace.fs.createDirectory(Uri.file(path.resolve(workspaceFolderA.uri.fsPath, \"dir-2\")));\n\n    await workspace.fs.createDirectory(Uri.file(path.resolve(workspaceFolderB.uri.fsPath, \"dir-1\")));\n    await workspace.fs.createDirectory(Uri.file(path.resolve(workspaceFolderB.uri.fsPath, \"dir-2\")));\n}\n\nexport async function afterEach(): Promise<void> {\n    if (existsSync(tmpDir.fsPath)) {\n        await workspace.fs.delete(tmpDir, { recursive: true, useTrash: false });\n    }\n    restoreExecuteCommand();\n    restoreGetConfiguration();\n    restoreGetWorkspaceFolder();\n    restoreShowInformationMessage();\n    restoreShowInputBox();\n    restoreShowQuickPick();\n    restoreShowWorkspaceFolderPick();\n    restoreWorkspaceFolders();\n}\n"
  },
  {
    "path": "test/helper/environment.ts",
    "content": "import * as os from \"os\";\nimport * as path from \"path\";\nimport { Uri, type WorkspaceFolder } from \"vscode\";\n\nexport const rootDir = path.resolve(__dirname, \"..\", \"..\", \"..\");\nexport const tmpDir = Uri.file(path.resolve(os.tmpdir(), \"vscode-fileutils-test\"));\n\nexport const fixtureFile1 = Uri.file(path.resolve(rootDir, \"test\", \"fixtures\", \"file-1.rb\"));\nexport const fixtureFile2 = Uri.file(path.resolve(rootDir, \"test\", \"fixtures\", \"file-2.rb\"));\n\nexport const editorFile1 = Uri.file(path.resolve(tmpDir.fsPath, \"file-1.rb\"));\nexport const editorFile2 = Uri.file(path.resolve(tmpDir.fsPath, \"file-2.rb\"));\n\nexport const targetFile = Uri.file(path.resolve(`${editorFile1.fsPath}.tmp`));\nexport const targetFileWithDot = Uri.file(path.resolve(tmpDir.fsPath, \".eslintrc.json\"));\n\nexport const workspacePathA = path.join(tmpDir.fsPath, \"workspaceA\");\nexport const workspacePathB = path.join(tmpDir.fsPath, \"workspaceB\");\n\nexport const workspaceFolderA: WorkspaceFolder = { uri: Uri.file(workspacePathA), name: \"a\", index: 0 };\nexport const workspaceFolderB: WorkspaceFolder = { uri: Uri.file(workspacePathB), name: \"b\", index: 1 };\n"
  },
  {
    "path": "test/helper/functions.ts",
    "content": "import retry from \"bluebird-retry\";\nimport { TextDecoder } from \"util\";\nimport { commands, type ExtensionContext, type Uri, window, workspace } from \"vscode\";\n\nconst textDecoder = new TextDecoder(\"utf-8\");\n\nexport async function readFile(file: Uri): Promise<string> {\n    return textDecoder.decode(await workspace.fs.readFile(file));\n}\n\nexport function createExtensionContext(): ExtensionContext {\n    const context = {\n        globalState: {\n            get() {\n                return {};\n            },\n            async update(): Promise<void> {\n                return;\n            },\n        },\n    };\n    return context as unknown as ExtensionContext;\n}\n\nexport async function openDocument(document: Uri): Promise<void> {\n    const tryOpenDocument = async () => {\n        const textDocument = await workspace.openTextDocument(document);\n        await window.showTextDocument(textDocument);\n    };\n    await retry(() => tryOpenDocument(), { max_tries: 4, interval: 500 });\n}\n\nexport async function closeAllEditors(): Promise<void> {\n    await commands.executeCommand(\"workbench.action.closeAllEditors\");\n}\n"
  },
  {
    "path": "test/helper/index.ts",
    "content": "import { use } from \"chai\";\nimport * as mocha from \"mocha\";\nimport sinonChai from \"sinon-chai\";\nimport type { Command } from \"../../src/command\";\nimport { steps } from \"./steps\";\nimport type { Rest } from \"./steps/types\";\n\nexport * from \"./callbacks\";\nexport * from \"./environment\";\nexport * from \"./functions\";\nexport * from \"./stubs\";\n\nuse(sinonChai);\n\nexport const protocol = {\n    describe(name: string, subject: Command, ...rest: Rest): mocha.Suite {\n        const step = steps.describe[name](subject, ...rest);\n        return mocha.describe(name, step);\n    },\n    it(name: string, subject: Command, ...rest: Rest): mocha.Test {\n        const step = steps.it[name](subject, ...rest);\n        return mocha.it(name, step);\n    },\n};\n\nexport const quickPick = {\n    typeahead: {\n        items: {\n            workspace: [\n                { description: \"- workspace root\", label: \"/\" },\n                { description: undefined, label: \"/dir-1\" },\n                { description: undefined, label: \"/dir-2\" },\n            ],\n            currentFile: [\n                { description: \"- current file\", label: \"/\" },\n                { description: undefined, label: \"/dir-1\" },\n                { description: undefined, label: \"/dir-2\" },\n                { description: undefined, label: \"/workspaceA\" },\n                { description: undefined, label: \"/workspaceA/dir-1\" },\n                { description: undefined, label: \"/workspaceA/dir-2\" },\n                { description: undefined, label: \"/workspaceB\" },\n                { description: undefined, label: \"/workspaceB/dir-1\" },\n                { description: undefined, label: \"/workspaceB/dir-2\" },\n            ],\n        },\n        options: {\n            placeHolder:\n                \"First, select an existing path to create relative to (larger projects may take a moment to load)\",\n            ignoreFocusOut: true,\n        },\n    },\n};\n"
  },
  {
    "path": "test/helper/steps/describe.ts",
    "content": "import { expect } from \"chai\";\nimport * as mocha from \"mocha\";\nimport * as path from \"path\";\nimport sinon from \"sinon\";\nimport { type QuickPickItem, Uri, window, workspace } from \"vscode\";\nimport type { Command } from \"../../../src/command\";\nimport { quickPick } from \"..\";\nimport { editorFile2, targetFile, tmpDir, workspaceFolderA } from \"../environment\";\nimport { closeAllEditors, readFile } from \"../functions\";\nimport {\n    createGetConfigurationStub,\n    createGetWorkspaceFolderStub,\n    createShowInformationMessageStub,\n    createShowInputBoxStub,\n    createWorkspaceFoldersStub,\n} from \"../stubs\";\nimport type { FuncVoid, Step } from \"./types\";\n\nexport const describe: Step = {\n    \"with target file in non-existent nested directory\"(subject: Command): FuncVoid {\n        return () => {\n            const targetDir = path.resolve(tmpDir.fsPath, \"level-1\", \"level-2\", \"level-3\");\n\n            mocha.beforeEach(async () => createShowInputBoxStub().resolves(path.resolve(targetDir, \"file.rb\")));\n\n            mocha.it(\"should create nested directories\", async () => {\n                await subject.execute();\n                const textEditor = window.activeTextEditor;\n                expect(textEditor);\n\n                const dirname = path.dirname(textEditor?.document.fileName ?? \"\");\n                const directories: string[] = dirname.split(path.sep);\n\n                expect(directories.pop()).to.equal(\"level-3\");\n                expect(directories.pop()).to.equal(\"level-2\");\n                expect(directories.pop()).to.equal(\"level-1\");\n            });\n        };\n    },\n    \"when target destination exists\"(subject: Command, config?: Record<string, unknown>): FuncVoid {\n        return () => {\n            mocha.beforeEach(async () => {\n                await workspace.fs.copy(editorFile2, targetFile, { overwrite: true });\n                createShowInformationMessageStub().resolves({ title: \"placeholder\" });\n            });\n\n            mocha.it(\"should prompt with confirmation dialog to overwrite destination file\", async () => {\n                await subject.execute();\n                const message = `File '${targetFile.path}' already exists.`;\n                const action = \"Overwrite\";\n                const options = { modal: true };\n                expect(window.showInformationMessage).to.have.been.calledWith(message, options, action);\n            });\n\n            mocha.describe('when answered with \"Overwrite\"', () => {\n                mocha.it(\"should overwrite the existig file\", async () => {\n                    await subject.execute();\n                    const fileContent = await readFile(targetFile);\n                    const expectedFileContent =\n                        config && \"overwriteFileContent\" in config ? config.overwriteFileContent : \"class FileOne; end\";\n                    expect(fileContent).to.equal(expectedFileContent);\n                });\n            });\n\n            mocha.describe('when answered with \"Cancel\"', () => {\n                mocha.beforeEach(async () => createShowInformationMessageStub().resolves(false));\n\n                mocha.it(\"should leave existing file untouched\", async () => {\n                    try {\n                        await subject.execute();\n                        expect.fail(\"must fail\");\n                    } catch (_e) {\n                        const fileContent = await readFile(targetFile);\n                        expect(fileContent).to.equal(\"class FileTwo; end\");\n                    }\n                });\n            });\n        };\n    },\n    \"without an open text document\"(subject: Command): FuncVoid {\n        return () => {\n            mocha.beforeEach(async () => {\n                await closeAllEditors();\n                createShowInputBoxStub();\n            });\n\n            mocha.it(\"should ignore the command call\", async () => {\n                try {\n                    await subject.execute();\n                    expect.fail(\"must fail\");\n                } catch {\n                    expect(window.showInputBox).to.have.not.been.called;\n                }\n            });\n        };\n    },\n    \"typeahead configuration\"(subject: Command, options: { command: string; items: QuickPickItem[] }): FuncVoid {\n        const { command, items } = options;\n        return () => {\n            mocha.describe(`when \"${command}.typeahead.enabled\" is \"true\"`, () => {\n                mocha.beforeEach(async () => {\n                    createGetConfigurationStub({ [`${command}.typeahead.enabled`]: true });\n                    createWorkspaceFoldersStub(workspaceFolderA);\n                });\n\n                mocha.it(\"should show the quick pick dialog\", async () => {\n                    await subject.execute();\n                    expect(window.showQuickPick).to.have.been.calledOnceWith(\n                        sinon.match(items),\n                        sinon.match(quickPick.typeahead.options)\n                    );\n                });\n            });\n\n            mocha.describe(`when \"${command}.typeahead.enabled\" is \"false\"`, () => {\n                mocha.beforeEach(async () => {\n                    createGetConfigurationStub({ [`${command}.typeahead.enabled`]: false });\n                });\n\n                mocha.it(\"should not show the quick pick dialog\", async () => {\n                    await subject.execute();\n                    expect(window.showQuickPick).to.have.not.been.called;\n                });\n            });\n        };\n    },\n    \"inputBox configuration\"(subject: Command, options: { editorFile: Uri; expectedPath?: string }): FuncVoid {\n        const { editorFile, expectedPath } = options;\n        const runs = [\n            { pathType: \"workspace\", pathTypeIndicator: \"@\" },\n            { pathType: \"workspace\", pathTypeIndicator: \"\" },\n            { pathType: \"workspace\", pathTypeIndicator: \":\" },\n            { pathType: \"workspace\", pathTypeIndicator: \" \" },\n        ];\n\n        return () => {\n            runs.forEach(({ pathType, pathTypeIndicator }) => {\n                mocha.describe(\n                    `when \"inputBox.pathType\" is \"${pathType}\" and \"inputBox.pathTypeIndicator\" is \"${pathTypeIndicator}\"`,\n                    () => {\n                        mocha.beforeEach(async () => {\n                            createGetConfigurationStub({\n                                \"inputBox.pathType\": pathType,\n                                \"inputBox.pathTypeIndicator\": pathTypeIndicator,\n                            });\n\n                            const workspaceFolder = path.dirname(editorFile.path);\n                            createWorkspaceFoldersStub({\n                                uri: Uri.file(workspaceFolder),\n                                name: \"workspace-a\",\n                                index: 0,\n                            });\n                            createGetWorkspaceFolderStub().returns(workspaceFolder);\n                        });\n\n                        mocha.it(\"should show the quick pick dialog\", async () => {\n                            await subject.execute();\n\n                            const expectedValue = `${pathTypeIndicator}/${\n                                expectedPath ?? path.basename(editorFile.path)\n                            }`;\n\n                            expect(window.showInputBox).to.have.been.calledOnceWith({\n                                prompt: sinon.match.string,\n                                value: sinon.match(expectedValue),\n                                valueSelection: sinon.match.array,\n                                ignoreFocusOut: true,\n                            });\n                        });\n                    }\n                );\n            });\n        };\n    },\n};\n"
  },
  {
    "path": "test/helper/steps/index.ts",
    "content": "import { describe } from \"./describe\";\nimport { it } from \"./it\";\nimport type { Step } from \"./types\";\n\nexport const steps: Record<string, Step> = {\n    describe,\n    it,\n};\n"
  },
  {
    "path": "test/helper/steps/it.ts",
    "content": "import { expect } from \"chai\";\nimport * as fs from \"fs\";\nimport { type Uri, window } from \"vscode\";\nimport type { Command } from \"../../../src/command\";\nimport { editorFile1, targetFile } from \"../environment\";\nimport type { FuncVoid, Step } from \"./types\";\n\nexport const it: Step = {\n    \"should open target file as active editor\"(subject: Command, uri?: Uri): FuncVoid {\n        return async () => {\n            await subject.execute(uri);\n            expect(window.activeTextEditor?.document.fileName).to.equal(targetFile.path);\n        };\n    },\n    \"should move current file to destination\"(subject: Command, uri?: Uri): FuncVoid {\n        return async () => {\n            await subject.execute(uri);\n            const message = `${targetFile} does not exist`;\n            expect(fs.existsSync(targetFile.fsPath), message).to.be.true;\n        };\n    },\n    \"should prompt for file destination\"(subject: Command, prompt: string): FuncVoid {\n        return async () => {\n            await subject.execute(editorFile1);\n            const value = editorFile1.path;\n            const valueSelection = [value.length - 9, value.length - 3];\n            expect(window.showInputBox).to.have.been.calledWithExactly({\n                prompt,\n                value,\n                valueSelection,\n                ignoreFocusOut: true,\n            });\n        };\n    },\n};\n\nit[\"should duplicate current file to destination\"] = it[\"should move current file to destination\"];\n"
  },
  {
    "path": "test/helper/steps/types.ts",
    "content": "import type { Command } from \"../../../src/command\";\n\nexport type FuncVoid = () => void;\n// biome-ignore lint/suspicious/noExplicitAny: Test framework needs flexible parameter types\nexport type Rest = any;\nexport interface Step {\n    [key: string]: (subject: Command, ...rest: Rest[]) => FuncVoid;\n}\n"
  },
  {
    "path": "test/helper/stubs.ts",
    "content": "import * as sinon from \"sinon\";\nimport { commands, type WorkspaceFolder, window, workspace } from \"vscode\";\n\nexport function createGetWorkspaceFolderStub(): sinon.SinonStub {\n    return createStubObject(workspace, \"getWorkspaceFolder\");\n}\n\nexport function restoreGetWorkspaceFolder(): void {\n    restoreObject(workspace.getWorkspaceFolder);\n}\n\nexport function createWorkspaceFoldersStub(...workspaceFolders: WorkspaceFolder[]): sinon.SinonStub {\n    return createStubObject(workspace, \"workspaceFolders\").get(() => workspaceFolders);\n}\n\nexport function restoreWorkspaceFolders(): void {\n    restoreObject(workspace.workspaceFolders);\n}\n\nexport function createExecuteCommandStub(): sinon.SinonStub {\n    return createStubObject(commands, \"executeCommand\");\n}\n\nexport function restoreExecuteCommand(): void {\n    restoreObject(commands.executeCommand);\n}\n\nexport function createGetConfigurationStub(keys: Record<string, string | boolean>): sinon.SinonStub {\n    const config = { get: (key: string) => keys[key] };\n    return createStubObject(workspace, \"getConfiguration\").returns(config);\n}\n\nexport function restoreGetConfiguration(): void {\n    restoreObject(workspace.getConfiguration);\n}\n\nexport function createShowInputBoxStub(): sinon.SinonStub {\n    return createStubObject(window, \"showInputBox\");\n}\n\nexport function restoreShowInputBox(): void {\n    restoreObject(window.showInputBox);\n}\n\nexport function createShowQuickPickStub(): sinon.SinonStub {\n    return createStubObject(window, \"showQuickPick\");\n}\n\nexport function restoreShowQuickPick(): void {\n    restoreObject(window.showQuickPick);\n}\n\nexport function createShowWorkspaceFolderPickStub(): sinon.SinonStub {\n    return createStubObject(window, \"showWorkspaceFolderPick\");\n}\n\nexport function restoreShowWorkspaceFolderPick(): void {\n    restoreObject(window.showWorkspaceFolderPick);\n}\n\nexport function createShowInformationMessageStub(): sinon.SinonStub {\n    return createStubObject(window, \"showInformationMessage\");\n}\n\nexport function restoreShowInformationMessage(): void {\n    restoreObject(window.showInformationMessage);\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: Handler needs to work with various VS Code API objects\ntype Handler = any;\n\nexport function createStubObject(handler: Handler, functionName: string): sinon.SinonStub {\n    const target: sinon.SinonStub | undefined = handler[functionName];\n    const stub: sinon.SinonStub = target && \"restore\" in target ? target : sinon.stub(handler, functionName);\n\n    return stub;\n}\n\nexport function restoreObject(object: unknown): void {\n    const stub = object as sinon.SinonStub;\n    if (stub?.restore) {\n        stub.restore();\n    }\n}\n"
  },
  {
    "path": "test/index.ts",
    "content": "import glob from \"fast-glob\";\nimport Mocha from \"mocha\";\nimport * as path from \"path\";\n\nexport async function run(): Promise<void> {\n    const mocha = new Mocha({\n        reporter: \"list\",\n        ui: \"bdd\",\n        color: true,\n    });\n\n    const testsRoot = path.resolve(__dirname, \"..\");\n    const files = await glob(\"**/**.test.js\", { cwd: testsRoot });\n\n    console.log(\"Number of test files to run:\", files.length);\n    // Add files to the test suite\n    files.forEach((file) => mocha.addFile(path.resolve(testsRoot, file)));\n\n    // Run the mocha test\n    return new Promise((resolve, reject) => {\n        try {\n            mocha.run((failures: number) => {\n                if (failures > 0) {\n                    reject(new Error(`${failures} tests failed.`));\n                } else {\n                    resolve();\n                }\n            });\n        } catch (err) {\n            reject(err);\n        }\n    });\n}\n"
  },
  {
    "path": "test/runTest.ts",
    "content": "import { runTests } from \"@vscode/test-electron\";\nimport * as path from \"path\";\n\nasync function main() {\n    try {\n        // The folder containing the Extension Manifest package.json\n        // Passed to `--extensionDevelopmentPath`\n        const extensionDevelopmentPath = path.resolve(__dirname, \"../../\");\n\n        // The path to test runner\n        // Passed to --extensionTestsPath\n        const extensionTestsPath = path.resolve(__dirname, \"./index\");\n\n        // Download VS Code, unzip it and run the integration test\n        await runTests({\n            extensionDevelopmentPath,\n            extensionTestsPath,\n            launchArgs: [\"--disable-extensions\"],\n        });\n    } catch (_err) {\n        // tslint:disable-next-line: no-console\n        console.error(\"Failed to run tests\");\n        process.exit(1);\n    }\n}\n\nmain();\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\r\n    \"extends\": \"@tsconfig/node22/tsconfig.json\",\r\n    \"compilerOptions\": {\r\n        \"rootDir\": \".\",\r\n        \"outDir\": \"out\",\r\n\r\n        \"sourceMap\": true,\r\n\r\n        \"removeComments\": true,\r\n        \"resolveJsonModule\": true,\r\n        \"experimentalDecorators\": true,\r\n        \"emitDecoratorMetadata\": true,\r\n        \"allowSyntheticDefaultImports\": true\r\n    },\r\n    \"exclude\": [\r\n        \"node_modules\",\r\n        \".vscode-test\"\r\n    ]\r\n}\r\n"
  }
]