{"id":15719334,"url":"https://github.com/shuang886/terminader","last_synced_at":"2025-05-13T02:35:10.098Z","repository":{"id":197306289,"uuid":"693870806","full_name":"shuang886/Terminader","owner":"shuang886","description":"Why are Finder and Terminal separate apps?","archived":false,"fork":false,"pushed_at":"2023-09-29T21:08:24.000Z","size":3602,"stargazers_count":96,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-01T06:02:20.691Z","etag":null,"topics":["finder","macos","terminal","ux"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/shuang886.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","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}},"created_at":"2023-09-19T22:06:09.000Z","updated_at":"2025-03-22T10:17:01.000Z","dependencies_parsed_at":null,"dependency_job_id":"fc447320-4feb-4a3f-b677-02fc5f686379","html_url":"https://github.com/shuang886/Terminader","commit_stats":null,"previous_names":["shuang886/terminader"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shuang886%2FTerminader","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shuang886%2FTerminader/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shuang886%2FTerminader/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shuang886%2FTerminader/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shuang886","download_url":"https://codeload.github.com/shuang886/Terminader/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253860333,"owners_count":21975241,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["finder","macos","terminal","ux"],"created_at":"2024-10-03T21:55:30.481Z","updated_at":"2025-05-13T02:35:10.068Z","avatar_url":"https://github.com/shuang886.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# \u003cimg alt=\"App icon\" src=\"https://github.com/shuang886/Terminader/assets/140762048/7a836c30-16ee-4daf-89b4-f476a7c0234e\" width=\"64\" align=\"center\"\u003e Terminader\n\n## Why are Finder and Terminal separate apps?\n\nThis is an experiment that challenges the status quo set literally decades\nago. Finder and Terminal are separate interfaces to the file system and\napplications, but should they be?\n\nTermina-der, half Terminal and half Finder, is my attempt at answering that\nquestion.\n\n\u003cimg width=\"779\" alt=\"main UI window with a grid of icons representing the current directory\" src=\"https://github.com/shuang886/Terminader/assets/140762048/0842a118-2e47-4e96-862a-861209f65eb7\"\u003e\n\n### Before we proceed\n\n\u003e **This is a toy, which means I didn't implement most of the destructive\nactions like deleting files. However, the command line shell is *live* and can\ndo whatever you type in it. Terminader is *not* sandboxed and it's *not* a\nsimulation.**\n\nFor the rest of this article, I'll refer to Terminader's command-line\ninterface as the CLI and its graphical interface as the GUI, so that the words\n\"Terminal\" and \"Finder\" can refer to the built-in macOS apps.\n\n### The current working directory\n\nThe first important realization is that both Finder and Terminal have a strong\nsense of the current working directory, so in Terminader the two interfaces\nshare it. Simply use the GUI/CLI button\n\u003cimg width=\"74\" alt=\"screenshot of button to toggle between GUI and CLI modes\" src=\"https://github.com/shuang886/Terminader/assets/140762048/23c09b4f-d245-44ba-8cc1-7cd2b9253a59\" align=\"center\"\u003e\nto flip between the two interfaces to the same current working directory.\n\nBut Finder provides a few other ways to move around:\n\n- The sidebar takes you to a configurable set of favorites and other\nlocations, and clicking on them simply changes the current working directory.\n\n- Folders you navigate into are added to a web browser-style history, where\nyou can easily go back and forward with a single click.\n\n- A picker (a ⌘-click in Finder) lets you navigate up to any of the current\nworking directory's parent directories.\n\nAll of these are replicated in Terminader and also change the current working\ndirectory on the CLI side. In addition, you can issue the enhanced `pwd back`\nor `pwd forward` CLI commands to move about the history.\n\n### Selection\n\nBoth Finder and Terminal support powerful ways to select files to act on.\nTerminal is especially powerful if a wildcard (e.g., `A*.txt` to select text\nfiles that start with \"A\") suits your needs, and Finder is powerful if your\nchoices are somewhat arbitrary.\n\n\u003cimg width=\"219\" alt=\"screenshot of paste button with a badge indicating three items had been selected\" src=\"https://github.com/shuang886/Terminader/assets/140762048/2d544933-b09d-4893-a0d9-19b75e441144\"\u003e\n\nTerminader has the best of both by also unifying selections. When you select\nfiles on the GUI side and then flip to the CLI side, the paste button\nindicates how many files have been selected, and would paste the list to your\ncommand line with one click. This is analogous to dragging selected files from\na Finder window into a Terminal.\n\nBut that's not the end of it. Terminader introduces two new commands, `select`\nand `deselect`, that can modify the selection using wildcards. So you might\n`select *.txt` on the CLI side, and then flip to the GUI side and deselect the\ntwo or three that you didn't actually want.\n\n## What about the Shell?\n\nThe astute reader will have pointed out by now that some of what I've been\ncalling features of \"Terminal\" are actually implemented in the Shell, yet\nanother piece of software. That is entirely correct, for our concepts to work\nwe need to break that wall too.\n\nModern terminal emulators are aware of certain contexts within the shells they\nspawn, such as the current working directory. But possibly to respect a user's\nchoice of shells, or perhaps in adherenence to the Unix philosophy of just\ndoing your own thing well, the integrations I'm aware of are relatively\nminimal and rely on gross escape sequences. As a result, we use little more of\nthe computer's capabilities than a VT100 terminal that shipped in 1978 did.\nLet's take this opportunity to rethink a few things, beyond just GUI-CLI\nintegration.\n\n### Why is stderr mixed in together with stdout?\n\nIf you've ever used a command-line program that actually emits output to\n`stderr`, it probably just clobbers the regular output to `stdout`, because\nterminal emulators just put them all on screen.\n\nTerminader puts `stderr` output in its own pane called \"Error\", and if a\nprogram only output to `stderr`, it would be mirrored in the regular \"Console\"\npane to serve as user feedback.\n\n### Why are unrelated commands all just dumped into a massive undifferentiated log?\n\nWe actually know what output correlates with which user command. This allows\nus to render them in a way that is distinct from the output of other commands.\n\nTerminader encloses the output from each user command in a rectangle. A green\nrectangle indicates that the command executed successfully (termination status\n0), while a red rectange indicates an error was encountered, and a cyan\nrectangle indicates the command is still running.\n\n\u003cimg width=\"779\" alt=\"screenshot of CLI with output of a command enclosed in a green rectangle\" src=\"https://github.com/shuang886/Terminader/assets/140762048/fb5868bc-5762-4174-9dd0-c7678c926361\"\u003e\n\n### So what?\n\nIt means you can filter the log and exclude the outputs of entire commands. It\nmeans we can automatically attach a timestamp to a command.\n\nIt means you can pop the output block out to its own window, to easily \"pin\"\nit without creating a new terminal pane.\n\nI didn't continue to flesh out this concept, but you should be able to save\nthe output to a file or send it to a printer after the fact, instead of either\nattempting a large click-and-drag selection, or re-running the program to\nredirect its output.\n\n### Why are we stuck with plain text?\n\nSure, `man` uses boldface and underline, and `ls` uses colors, but we're still\nleaving a lot of untapped potential. Modern terminal emulators have\n[invented](https://en.wikipedia.org/wiki/Sixel)\n[several](https://sw.kovidgoyal.net/kitty/graphics-protocol/)\n[ways](https://iterm2.com/documentation-images.html) to output graphics, but\nthey all rely on clunky escape sequences, and don't really solve more general\nproblems. It takes a [valiant\neffort](https://wezfurlong.org/wezterm/hyperlinks.html) to even allow a\nhyperlink to be clickable.\n\nTerminader allows an application to output\n[MIME](https://en.wikipedia.org/wiki/MIME). Output that begin with:\n\n```\n    MIME-Version: 1.0\n```\n\nare treated as MIME output, and the following MIME headers are honored:\n\n#### Content-Type: image/*\n\nThe image itself must be Base64-encoded because I couldn't figure out how to\nget the pipe to stop converting LF (line feed) characters into CR (carriage\nreturn) LF pairs and corrupting the image data. But otherwise if `NSImage` can\ndecode the format, it should display on screen.\n\nA new `cat` command uses this ability to display an image in the CLI.\n\n\u003cimg width=\"779\" alt=\"screenshot of cat command outputting an image\" src=\"https://github.com/shuang886/Terminader/assets/140762048/b7cbadef-ca14-4c40-a386-fdd82753e31e\"\u003e\n\n#### Content-Type: text/markdown\n\nMarkdown provides not only formatted text output, but also hyperlinks.\nTerminader includes a rudimentary new `ls` command that outputs hyperlinks\nthat are used to invoke contextual actions.\n\nThe new `cat` command treats files with an `.md` extension as Markdown\ndocuments.\n\n#### Content-Type: text/plain\n\nThis isn't really necessary because you could just not output the\n`MIME-Version` header, but it's there.\n\n#### Content-Transfer-Encoding:\n\nOnly `base64` is supported at this point.\n\n### What Next?\n\nMIME enables rich CLI output, and there are some interesting avenues to\nexplore:\n\n- Piping MIME output among command-line utilities, such as ImageMagick.\n\n- Avoiding [mojibake](https://en.wikipedia.org/wiki/Mojibake) by using\n`charset` to render text correctly.\n\nBut if you mean whether Terminader is likely to become a real tool, it\nprobably won't. SwiftUI is great at rapid prototyping (all this took me a bit\nover a week) but the real work of integrating the full features of Finder,\nTerminal, various shells, and many Unix tools is massive. Such a venture would\nalso likely need private Apple APIs, and likely will break constantly with new\nmacOS versions.\n\n## System Requirements\n\nIntel or Apple Silicon Mac running macOS 14 (Sonoma). If you cannot use\nSonoma, defining the `SUPPORT_IME` flag and making your own build should still\nmostly work.\n\n## Known Issues and Excuses\n\n- Interactive programs are not going to work. That includes `vi`, but also\nprograms like `top` that fairly randomly addresses the terminal screen.\n\n- Internal commands don't handle quotes and backslash-escapes.\n\n- Contextual actions in the CLI are invoked by clicking on the link. I can't\nfigure out how to make `AttributedText` links require a ⌘-click.\n\n- Contextual action pop-ups in the CLI aren't anchored to the link you clicked\non.\n\n- The \"Get Info\" contextual action opens a window and does nothing.\n\n- If you \"Show Tab Bar\", the tabs will have blank titles. SwiftUI doesn't seem\nto allow me to hide the window title but populate the tab title.\n\n- There's a long delay in the GUI when you select the first file. I can't\nquite get all the gestures to work simultaneously.\n\n- ⌘A to select all the files would be nice, but I can't get SwiftUI keyboard\nhandling to work quite right.\n\n- The CLI prompt can lose keyboard focus and stop accepting input but still\nhappily blink as if it had focus. You can click on the cursor area to give it\nback the focus.\n\n## Hack\n\nThe `scripts` directory holds scripts that are searched first, so they can be\nused to override real commands. For example, the `man` script overrides the\npager to return its entire output, but without losing the nice formatting.\n\n## Acknowledgements\n\n- [apple /\nswift-argument-parser](https://github.com/apple/swift-argument-parser) for\ncommand-line parsing.\n\n- [eonil / FSEvents](https://github.com/eonil/FSEvents) to monitor changes in\nthe current directory.\n\n- [gonzalezreal /\nswift-markdown-ui](https://github.com/gonzalezreal/swift-markdown-ui) for\nMarkdown rendering.\n\n- [ksemianov / WrappingHStack](https://github.com/ksemianov/WrappingHStack)\nfor the GUI grid.\n\n- Last but not least, friends I will not name for reasons have contributed\nimportant ideas, pointers, and encouragement.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshuang886%2Fterminader","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshuang886%2Fterminader","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshuang886%2Fterminader/lists"}