Documentation
¶
Overview ¶
Package attach is the Ctrl+T file picker: a path being typed, the directory it names, and what is in it.
It is the command palette's twin rather than the dialog's. Same fixed width, same anchor, same selection marker, same key-hint footer, no buttons anywhere — the palette collects a command and this collects a path, and everything on screen is derived from what has been typed the way a shell derives completions. It replaced the last centred box with an OK button in the client; see docs/tui-2.0.md, divergence 49.
The package does no sending. It reports a path and a send mode; the app stages them on the composer.
Index ¶
- Constants
- func IsImage(name string) bool
- func ResolvePath(text string) (string, bool)
- func UnquotePath(text string) string
- type Action
- type Entry
- type Model
- func (m Model) AsPhoto() bool
- func (m Model) Below() int
- func (m Model) Chosen() (path string, asPhoto bool, ok bool)
- func (m *Model) Close()
- func (m Model) IsVisible() bool
- func (m Model) Matches() []Entry
- func (m *Model) Open(fallback string)
- func (m Model) Paste(text string) Model
- func (m Model) Selected() (Entry, bool)
- func (m *Model) SetRoles(r theme.Roles)
- func (m Model) Typed() string
- func (m Model) Update(msg tea.KeyPressMsg) (Model, Action)
- func (m Model) View() string
- func (m Model) Window() (rows []Entry, top int)
Constants ¶
const Width = 60
Width is the overlay's fixed width in cells, the palette's exactly. Fixed for the palette's reason: a 200-column terminal should not stretch a listing of short filenames across the whole screen.
Variables ¶
This section is empty.
Functions ¶
func IsImage ¶
IsImage reports whether this client would send the file as a photo.
It defers to the clipboard's answer rather than keeping a second list. Ctrl+V has been sending images for several releases and that list is the one proven against the send path — it excludes TIFF on purpose, because Telegram's InputMediaUploadedPhoto rejects it, and a picker that offered photo mode for a .tif would promise a send that fails.
Exported because a file dropped on the composer with no picker open is staged directly, and it has to send the same way it would have if it had been picked.
func ResolvePath ¶
ResolvePath is the whole answer to "is this paste a file, and which one": the path a dropped or pasted string names, ready to hand to the composer, and whether it unambiguously names one at all.
One function rather than a predicate beside a converter. The two were separate for one round and the app used the predicate's answer with the converter's output — so a "~/shot.png" drop passed the check, which expands the tilde, and staged the literal string, which does not exist. A question and its answer computed by different code is a pair that can disagree.
The test is "unambiguously a path" rather than "possibly a path", because a paste that merely resembles one must not silently become an attachment instead of the message somebody meant to send: one line, rooted or home-relative or a URL, naming a file that is actually there.
func UnquotePath ¶
UnquotePath turns what a terminal delivers when a file is dropped on it into a path a stat call will find.
Dragging a file onto a terminal is how people already hand a path to a program, and it is the natural gesture for this surface — typing a path is the part nobody wants to do. But a drop does not arrive as keystrokes: the terminal pastes it, escaped the way a SHELL would need it, because what it is really doing is typing a command line for you. Three spellings are in use:
/Users/a/My\ Files/x.png iTerm2, Terminal.app — backslash-escaped '/Users/a/My Files/x.png' quoted, single or double file:///Users/a/My%20Files/ a URL, from several Linux terminals
Getting this wrong fails on exactly the files people drag: the ones with a space in the name. Everything else already works by being typed.
A multi-file drop delivers several paths separated by spaces, which is ambiguous against a single path that contains one. Only the first is taken, and only where the separation is unambiguous — a URL list — which is the honest reading while decision 5 keeps one staged attachment.
Types ¶
type Entry ¶
type Entry struct {
// Name is the entry's own name. A directory carries no trailing slash
// here — the view adds one, and completion adds one, but a name that
// sometimes ends in a separator is a name every comparison has to
// remember to normalise.
Name string
// Dir is set for a directory, INCLUDING a symlink that resolves to one:
// what matters here is whether Enter can go into it. A broken link is
// not a directory, because nothing can be entered.
Dir bool
// Size is the file's size in bytes; Items is a directory's entry count,
// -1 when the directory could not be read to count them, and 0 before
// anything has tried (see countInto, which only counts what is drawn).
Size int64
Items int
// ModTime is what the mtime column shows.
ModTime time.Time
// Image is set for a file this client would send as a photo. It is the
// only thing the send-mode toggle applies to.
Image bool
// contains filtered or unexported fields
}
Entry is one row of a directory listing.
type Model ¶
type Model struct {
// contains filtered or unexported fields
}
Model is the picker overlay.
func (Model) AsPhoto ¶
AsPhoto reports how the cursored file would send.
An image sends as a photo unless the reader said otherwise, which is the defect this component was built to fix: the prompt it replaces passed a hardcoded false, so Ctrl+T always attached as a document while Ctrl+V attached the same image as a photo. Anything that is not an image can only be a document, and the toggle says so rather than lying.
func (Model) Chosen ¶
Chosen is the absolute path of the cursored file and how to send it. ok is false unless the cursor is on a file — a directory is somewhere to go, not something to attach.
func (*Model) Close ¶
func (m *Model) Close()
Close hides the picker, keeping the directory for next time.
It resets nothing else, because Open resets everything else: the path, the cursor and the send mode are all set from m.dir on the way back in. Doing it here as well would be two places that have to agree about what a fresh picker looks like, and mutation testing could not tell them apart — which is the tell that one of them was not doing anything.
func (*Model) Open ¶
Open shows the picker.
fallback is where to begin when the picker has nowhere else to be — the configured download directory, so the place the client saves to is the place it offers back. It is a FALLBACK and not a destination: once the picker has been somewhere, that is where it reopens, because attaching three files from one folder should not mean walking there three times. A caller wanting to move it should hand the path to Paste.
func (Model) Paste ¶
Paste replaces the path with a dropped or pasted one.
Replaces rather than appends: a dropped path is absolute and complete, and appending it to whatever was already typed produces a path that exists nowhere. See UnquotePath for what a terminal actually delivers.
func (Model) Update ¶
func (m Model) Update(msg tea.KeyPressMsg) (Model, Action)
Update handles a keypress while the picker owns input.
Movement is the arrows and nothing else, which is the palette's rule and the palette's reason: this is a text surface, so every printable key has to reach the path or a file called "jack" could not be typed. One spelling per action — the emacs chords were taken off the palette for being a second way to do one thing, and adding them back here would put the pair straight back out of step.
func (Model) View ¶
View renders the overlay. Every content line is exactly Width cells, so the caller can place it without the frame shearing.