{"id":23763702,"url":"https://github.com/tomashubelbauer/open-source-encore-2023-05-23-vs-code","last_synced_at":"2026-01-26T02:37:44.693Z","repository":{"id":168734715,"uuid":"644509056","full_name":"TomasHubelbauer/open-source-encore-2023-05-23-vs-code","owner":"TomasHubelbauer","description":"A post about my investigation into how to add a VS Code feature I've so desired it already had for a long time.","archived":false,"fork":false,"pushed_at":"2023-05-23T17:54:00.000Z","size":7,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-01T16:40:22.617Z","etag":null,"topics":["open-source","vs-code","vscode"],"latest_commit_sha":null,"homepage":"https://hubelbauer.net/open-source-encore-2023-05-23-vs-code","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TomasHubelbauer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-05-23T17:01:36.000Z","updated_at":"2023-05-25T19:07:17.000Z","dependencies_parsed_at":null,"dependency_job_id":"091551a3-b190-4482-a04b-d6dd37170ddc","html_url":"https://github.com/TomasHubelbauer/open-source-encore-2023-05-23-vs-code","commit_stats":null,"previous_names":["tomashubelbauer/open-source-encore-2023-05-23-vs-code"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/TomasHubelbauer/open-source-encore-2023-05-23-vs-code","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TomasHubelbauer%2Fopen-source-encore-2023-05-23-vs-code","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TomasHubelbauer%2Fopen-source-encore-2023-05-23-vs-code/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TomasHubelbauer%2Fopen-source-encore-2023-05-23-vs-code/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TomasHubelbauer%2Fopen-source-encore-2023-05-23-vs-code/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TomasHubelbauer","download_url":"https://codeload.github.com/TomasHubelbauer/open-source-encore-2023-05-23-vs-code/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TomasHubelbauer%2Fopen-source-encore-2023-05-23-vs-code/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28765208,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-26T02:25:41.078Z","status":"ssl_error","status_checked_at":"2026-01-26T02:24:28.809Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["open-source","vs-code","vscode"],"created_at":"2024-12-31T22:13:20.392Z","updated_at":"2026-01-26T02:37:44.687Z","avatar_url":"https://github.com/TomasHubelbauer.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Open Source Encore: VS Code create path while renaming with `/`\n\nVS Code has a feature where when creating a new file, if its name contains `/`s,\nthe path the directory the files is being created in concatenated with the path\ngiven by the slashes gets created (if it doesn't already exist) and the new file\ngets placed in this final directory.\n\nFor years I have been missing the ability to do this when renaming files, too.\nGot a file in a directory and could use it being in a subdirectory and maybe\neven named something else?\nNot problem, just prepend the subdirectory name to the file name while renaming!\nThat was my dream for some long and today I set out to advocate for this feature\nto be included in VS Code and I felt pretty good about my justification seeing\nas the precedent was already there with the new file creation flow supporting\nthis behavior.\n\nI opened this VS Code issue to track my plea:\n\u003chttps://github.com/microsoft/vscode/issues/183203\u003e\n\nWanting to be a good netizen, I set out to figure out how to contribute this\nmyself so that the VS Code team is more inclined to take the idea in if it comes\nwith a working implementation.\n\nI've contributed bits to VS before, but it has been a long time and I wasn't\neven sure how to build and run it anymore.\nSo, the first step was to find the contribution guide.\nVS Code has \u003chttps://github.com/microsoft/vscode/blob/main/CONTRIBUTING.md\u003e but\nwhat is honestly rather odd is that there is so much prose and just a single\nline detailing what to do when meaning to contribute code!\nAnd that line is a link here:\n\u003chttps://github.com/microsoft/vscode/wiki/How-to-Contribute\u003e\n\nThis helped me figure out that I needed to install Node 18, global Yarn and when\nthat was done, I could finally run `yarn` to install the dependencies and build. \nI then switched over to the `yarn watch` command so I didn't need to pay any\nmind to building the code after making my changes.\n\nNext I used the `./scripts/code.sh` command to run the built VS Code binary and\ndo my testing there.\nPretty quickly though I realized that I can't access `console.log` outputs in\nthe output of this script easily (if at all) so I looked for a better way.\n\nThe scope of the VS Code codebase absolutely calls for bigger guns, so I found\na VS Code Debugger configuration simply called VS Code which also served to run\nthe built app, but in addition allowed me to attach to it and place breakpoints\nat points of interest which I was investigating.\n\nThis makes the research much quicker and allows for cool stuff like hover tips\nfor runtime types and other stuff which is extremely handy when the debugger is\nset up well.\nUnfortunately setting up debug configurations still isn't as easy as placing a\n`console.log` in the source code so I rarely bother to do it, but I absolutely\nappreciate that it is set up for VS Code and could not imagine working in any\nother way on such a massive codebase.\n\nAnyway, since I knew I wanted to replicate a feature already seen in the new\nfile creation workflow, I first started by investigating how that even worked\nend to end.\nI mapped all of the code sites that are involved and a bit about what they do.\nI paid particular interest to the part of this flow which takes care of the `/`\nhandling to make the path for the new file if its name contains slashes.\n\nI started off by looking for the \"New File...\" string in the VS Code codebase.\nI found several matches via full-text search so I renamed all of them to add a\nunique number at the end and then re-run the debugger.\nAfter hovering over the New File button in the Explorer pane I was able to find\nwhich of the ~seven previously identical strings I was looking at.\n\nThe text was in `src/vs/workbench/contrib/files/browser/views/explorerView.ts`\nand I saw that in its context of the `registerAction2` there was also a `run`\naction to go along with it.\n\nThis is a place a could place a breakpoint and start stepping over and into\nstuff from there.\n\n- `src/vs/workbench/services/commands/common/commandService.ts` `CommandService.executeCommand`\n\n  From here we go to `_tryExecuteCommand` on the same class.\n\n- `src/vs/workbench/services/commands/common/commandService.ts` `CommandService._tryExecuteCommand`\n\n  This fetches the command definition from `CommandsRegistry` and the command\n  definition then has a `handler` field on it which is the backing function of\n  the command.\n  We can hover over `.handler` when debugging that line and see the name of\n  the function that's there at runtime.\n  \n- `src/vs/workbench/contrib/files/browser/fileActions.ts` `openExplorerAndCreate`\n\n  This creates a new node in the Explorer view and marks it as editable which\n  changes the UI of the node's label to a text box which gets submitted when\n  the Enter key is pressed or when the field is blurred.\n  The handler for that is the `onFinish` and in this case it calls `onSuccess`\n  which determines if we're creating a file or a directory and passes the call\n  to the explorer service.\n  \n- `src/vs/workbench/contrib/files/browser/explorerService.ts` `ExplorerService.applyBulkEdit`\n- `src/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts` `BulkEditService.apply`\n- `src/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts` `BulkEdit.perform`\n- `src/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts` `BulkEdit._performFileEdits`\n- `src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts` `BulkFileEdits.apply`\n\n  We go through this jungle of abstractions which add extra bells and whistles\n  for undoability etc. and finally we come back to something specifically\n  related to the new file creation flow.\n\n- `src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts` `CreateOperation.perform`\n\n  `CreateOperation` implements applying `CreateEdit`s created above and passed\n  down to it.\n\n- `src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts` `WorkingCopyFileService.create`\n\n  This class takes care of the file operations at the level where stuff like\n  file participants are executed and ultimately the file system operations are\n  handled even one more level down.\n  \n- `src/vs/platform/files/common/fileService.ts` `FileService.createFile`\n\n  This service implements the actual persisting to the disk.\n  Through `createFile` we get to `writeFile` and in there we can see an\n  interesting call to `mkdirp`.\n\nThe `mkdirp` is a dead giveaway because the Unix command for creating a path\nis `mkdir -p` and the associated comment also notices this call creates the\npath recursively until all of the component folders exist.\n\nSo now we know how the file creation flow manages to handle the `/`s.\nIt uses `mkdir` to ensure the whole path exists before persisting the actual\nfile, so when a slash is a part of the files name, the directories are taken\ncare of.\n\nWe want the same for the file rename flow so I set out to inspect how that\nworked hoping a pattern would emerge and I would find a nice spot where to use\nthe newly found `mkdirp` function to the same effect.\n\nI started off with the \"Rename...\" label followed by distinguishing the search\nresults with numerical suffixes again to find the right one.\n\nThat ended up being in `src/vs/workbench/contrib/files/browser/fileActions.ts`\nnamed `TRIGGER_RENAME_LABEL` and the close-by `RENAME_ID` is shared with a\ndifferent part of the codebase here:\n`src/vs/workbench/contrib/files/browser/fileActions.contribution.ts`.\n\nIn this file, we see the `registerCommandAndKeybindingRule` call has `handler`\nwhich is the entry point for what happens when the rename context menu item is\npressed.\n\n- `src/vs/workbench/contrib/files/browser/fileActions.ts` `renameHandler`\n\n  This method pulls the same trick with setting the Explorer node to editable\n  state and waiting for the Enter press or the blur event to start processing\n  the new name.\n  \n  The rest seems pretty similar to what we've seen in the creation flow:\n  \n- `src/vs/workbench/contrib/files/browser/explorerService.ts` `ExplorerService.applyBulkEdit`\n- `src/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts` `BulkEditService.apply`\n- `src/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts` `BulkEdit.perform`\n- `src/vs/workbench/contrib/bulkEdit/browser/bulkEditService.ts` `BulkEdit._performFileEdits`\n- `src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts` `BulkFileEdits.apply`\n\n  So we do get a pattern here and the difference now is the branch which this\n  time maps `RenameEdit` to `RenameOperation`.\n\n- `src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts` `RenameOperation.perform`\n- `src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts` `WorkingCopyFileService.move`\n- `src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts` `WorkingCopyFileService.doMoveOrCopy`\n\nThis looks like the perfect space to use `mkdirp` and leave the rest of the\ninfrastructure to do its thing.\nAnd then I will finally get renames with slashes to work and will be able to\ncontribute the fix!\n\nWait, what is _that_?\n\n```typescript\nprivate async doMoveCopy(sourceProvider: IFileSystemProvider, source: URI, targetProvider: IFileSystemProvider, target: URI, mode: 'move' | 'copy', overwrite: boolean): Promise\u003c'move' | 'copy'\u003e {\n\tif (source.toString() === target.toString()) {\n\t\treturn mode; // simulate node.js behaviour here and do a no-op if paths match\n\t}\n\n\t// validation\n\tconst { exists, isSameResourceWithDifferentPathCase } = await this.doValidateMoveCopy(sourceProvider, source, targetProvider, target, mode, overwrite);\n\n\t// delete as needed (unless target is same resurce with different path case)\n\tif (exists \u0026\u0026 !isSameResourceWithDifferentPathCase \u0026\u0026 overwrite) {\n\t\tawait this.del(target, { recursive: true });\n\t}\n\n\t// create parent folders\n\tawait this.mkdirp(targetProvider, this.getExtUri(targetProvider).providerExtUri.dirname(target));\n```\n\nWhat is the `mkdirp` doing there?\nDid someone add this in an unreleased version of VS Code?\n\nI took a few moments to go back to VS Code and do a quick sanity check to make\nsure this wasn't already working in the released version.\n\nAnd. It. Was.\n\nI don't know why, but I did not think to test this didn't actually work before\nI even opened the issue.\nI remember being bothered by this for a long time but I did not think to check\nit wasn't fixed before I went to file the issue. :D\n\nAnyway, all's not lost, there is nothing to contribute but there was enough to\nbe learnt that might come in handy the next time I am looking to contribute to\nVS Code!\n\nAs one last bit, let's take a look at how long this has been in VS Code proper\nusing Git blame:\n\n\u003chttps://github.com/microsoft/vscode/blame/f043f49fc50ac2e984327f69ff9eb9718a396114/src/vs/platform/files/common/fileService.ts#L741\u003e\n\n_At least_ two fricking years. :)\nI could have been using this for two years now instead of instinctually\ncomplaning to myself that this doesn't work and doing the move and rename in\nseparate  steps. :D\n\nI closed the VS Code issue.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomashubelbauer%2Fopen-source-encore-2023-05-23-vs-code","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftomashubelbauer%2Fopen-source-encore-2023-05-23-vs-code","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomashubelbauer%2Fopen-source-encore-2023-05-23-vs-code/lists"}