Repository navigation
Replies: 2 comments
More sources for the same destinationEverything in #453 is about the destination — a list of paths becomes a virtual pane — with the clipboard as the first source. A few more sources fit the same door, and I think they are worth naming now so the action is designed as "import from a source" rather than "paste from clipboard." Command outputMidnight Commander's External panelize is the precedent: run a command, read stdout, one path per line, show it as a panel. Every example in #453 — Details this adds on top of the clipboard version:
Saved listsA virtual pane that remembers its source can be saved and reopened: Finder smart folders, Directory Opus collections. With command output as a source, "saved search" is just "saved command." Flat viewTotal Commander's Branch View — every file under the current directory, flattened — is a virtual list whose source is a recursive walk. Worth considering as a built-in source rather than a separate feature. From outside XeFMA control CLI over the local socket planned for MCP ( |
Stages ①–③ shipped (#492–#499)A list of paths from three sources now opens as the pane, under Go → Open List. All three actions ship unbound: How the open questions were answered
§6's "the header would lie" is fixed: The command source (③)It runs through the shell in the pane's directory, with the
The file source (②)It opens the text file under the cursor (or the selected ones, merged), also available as Open as List on the context menu. Relative lines resolve against the list file's own folder. Byte-order marks decide the encoding first, UTF-16 included, since that is what Windows PowerShell 5.1's Deliberately left out
Still open④: reading a native file list (CF_HDROP / |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Split out of #446. That thread argues the plugin-API side: a registry so a search engine can be driven from XeFM's own dialog. Working through Everything made it clear that the cheaper and more general answer is not a plugin API at all — it is letting a list of paths from anywhere become a pane.
Everything already has a search UI that is better at searching Everything's index than anything XeFM would build: as-you-type over the index, its whole query language (
size:,dm:,ext:,!,|,<>), its own history and sorting. The division of labour writes itself — search there, work here — and it needs no contract between the two, only a list of paths.The same door serves
find,fd,rg -l,git ls-files, a build log, a spreadsheet column, or a list someone pasted in chat.1. This is the missing inverse of an action that already ships
Three of the four pieces exist:
copy_paths_to_clipboard(app.py:5074) — one full path per linePanel.get_clipboard()(panel.py:2578→backend.py:794)_feed_search_results(app.py:4307)copy_paths_to_clipboardalready decided what a file list looks like when it leaves XeFM. Reading one back in is its inverse, andfile_list_manager.py:44-48already says the destination holds:2. Why this is worth doing on its own merits
Deliberately echoing #342's framing, because the same test applies.
find . -name '*.py' | pbcopyon macOS, today, in the normal loop.pane["virtual"]gets published.What it does not give is Migemo over an external index — #432's other half. The search happens in the other tool's box, where XeFM's matcher is not. The honest mitigation is a small action that puts the Migemo regex for a typed query on the clipboard, to be pasted into Everything's
regex:. Clunky, but it is one action and it does not pretend.3. The boundary with #342
#342 sets its own test:
Both read the clipboard, both are about paths, and they must not blur:
_exit_virtual,app.py:2800)Different verbs, and they should not share a key. #342 names "Explorer / Finder interop" as one of its three reasons, which is the same transport this needs — so the two want the same PuiKit work (§5) for opposite purposes.
There are two clipboard flavours, and the distinction matters here more than anywhere:
copy_paths_to_clipboardwrites, and what Everything's "Copy Full Name to Clipboard" writes.NSFilenamesPboardTypeon macOS) — what Explorer, Finder, and Everything's plain Ctrl+C write.Import should accept both. Only the first is reachable today.
4. Transports, in order of what they cost
es.exe <query>,rg -l,git ls-filesexternal_programs.pyalready exists. This is where the headless half of #446 actually lands1 and 2 are the whole feature for most purposes. 3 overlaps #446 and is the better home for engines that have no GUI.
5. PuiKit: the read side of a file-list clipboard does not exist
Worth stating precisely, because the asymmetry is total.
get_clipboard()returns plain text only (backend.py:794). There is noget_clipboard_files().clipboard_richis a write-side capability:set_clipboard_rich(text, html=…, rtf=…)(backend.py:808). macOS advertises it, Windows does not, web is "security-limited" (capability.py:101)._win32_dragdrop.pyexists precisely for that, "exposing exactly one format (CF_HDROP over a realDROPFILESglobal memory)" — but only as a drag source forbegin_file_drag(backend.py:982). It has never had to read one.os_drag_dropis likewise a source capability. There is no drop target anywhere.So: plain-text import needs nothing from PuiKit. Importing an Explorer/Finder file copy and accepting a drag into the pane are the same underlying work — "read a file list from the OS" — and it is a PuiKit change, per backend, with the Windows half needing hands-on verification as usual. It is also the work #342 wants for its own interop.
That suggests the same staging #378 used: ship the part that needs no PuiKit change first, and let the richer transport arrive behind a capability.
6. What the destination already handles, and what it does not
Handles. Sorting, filtering and post-operation reconciliation on a virtual pane (
file_list_manager.py:44-48). Pruning entries that no longer exist, on every refresh (file_list_manager.py:62). Backing out with ⌫ (_exit_virtual,app.py:2800). Relative-name display against a root (file_pane.py:358, #383).Does not.
kindfinally gets a second value._feed_search_resultswrites"kind": "search"(app.py:4338) and nothing has ever read it — the same shape as the ten capability methods with no callers in User-defined virtual folders — a scheme registry, and the base class that has to ship with it #426 §5. It was written for exactly this.The header would lie.
app.py:789-791readsvirtual["mode"]directly and renders⌕ "query" — N results (filename). An imported list is neither a filename nor a content search, and has no query.roothas no answer, androot=Noneis not the one. Imported paths span drives.rootfeeds the relative-name column, the sort'srel_root(Option to use directory name + filename when sorting virtual file list (search result), not only filename #383), the cwd handed to external programs (app.py:4627), andapp.py:6607. My first thought wasroot=None→ full paths in the name column, and that is simply wrong about the code:name_key.rel_name(name_key.py:52-68) "falls back to the basename when there is no root". Withroot=Nonean imported list renders as barea.txtrows — precisely the "location-less name that says nothing about which of several hits it was" that "Copy Name(s)" command shoud copy relative path in virtual file list #433 removed.Better, and derived from the data rather than from where the user happens to be standing: use the imported set's own common ancestor as
root.rg -linside one repository gives the repository, so the pane showssrc/main.py. A list all under one folder gives that folder. Everything results across drives give nothing, and that is the honest answer. It degrades gracefully — one outlier drags the ancestor up to a drive root — and it givesapp.py:4627's cwd something better thanNone.Where there is no common ancestor the pane still has to show whole paths, and
rel_namehas only two states (relative to a root, or basename). So this needs a third: the branch belongs infile_pane.py:358/:605, choosingstr(f), not inname_key— the basename fallback is load-bearing for ordinary directory panes.There is no cap.
_RESULT_CAP = 1000belongs to the search dialog, not to the pane. Everything can select a million rows.7. The format, and parsing what arrives
What XeFM writes: absolute, verbatim.
copy_paths_to_clipboardalready chose absolute, and it is the only defensible choice — the clipboard is a cross-application bus that carries no context, so a relative path in it is unresolvable, and paths leaving XeFM get pasted into shells and editors that resolve against their cwd.copy_names_to_clipboardalso already settled the encoding question, and the reasoning transfers directly to import:So: normalize for display, address the filesystem with exactly what arrived. NFC-normalizing before the existence check loses decomposed paths on macOS.
What XeFM accepts: either. Asymmetric on purpose, and the anchor for a relative path differs by transport:
.gitignore/ response-file conventionThis is the one place the feature can be silently wrong:
src/main.pyresolving against the wrong repository finds a real file that is not the one meant. It cannot be prevented, only reported — so the status line should say how many entries were resolved relatively, not just how many landed.The clipboard holds whatever was in it. The rest of the rule should be dull on purpose:
Deliberately not doing: guessing a delimiter, parsing CSV columns, accepting globs, or reading URLs. A list of paths is a list of paths; anything cleverer belongs in the tool that produced it. (Everything's
.efuexport is CSV and would be a separate, explicit reader under transport 2 — it carries size and dates, which is the §8 note below.)8. One thing worth getting right early: attributes, not bare paths
Feeding bare paths means the pane stats every entry. For a list of a hundred thousand files spanning several drives that is the whole cost of the feature.
filters.pyalready promises the opposite for a pane's own listing:Several transports can supply them: Everything's
.efuexport carries size and dates,Everything3_AddSearchPropertyRequestreturns them,find -printfcan print them. So the import path should accept(path, attrs)where a transport has them and fall back to statting lazily where it does not — the same shapelistdir_attrswas added for. This also decides whether a config-definedFILTERSpredicate over an imported list is free or ruinous.9. Stages
kindhonoured in the header; theroot=Nonedecision.efureader that keeps size and datesexternal_programs.pyNSFilenamesPboardType), then accept a drop① is a small, self-contained change and settles the two questions (
kind,root) that #446 would otherwise have to settle blind.Open questions
rootwhen the set has no common ancestor. Whole paths in the name column is clear (§6), but the external-program cwd is not — fall back to the directory the pane was showing before the import? And is a computed common ancestor ever surprising: a list of two files from opposite ends of a drive gets the drive root, which is correct and useless.⌕ "query" — N results. A file or command import could name its source; a clipboard one cannot.All reactions