File Explorer and Git
The two default plugins: packages the app installs on first launch, each one card with Back and Forward
Two plugins come with the app. File Explorer browses a folder and shows its files: the tree, a text editor (File), a viewer for images, PDF, audio and video (Preview), and Markdown (Rich Text or Source, with a read lock). Git shows a repository: its changes with Stage, Unstage and Commit, Fetch, Pull and Push, the history of every branch, and the diff of any change or commit. One card moves between these places, with Back and Forward as in a browser. Each is a herdr pane that runs a placeholder, as a browser pane does (Browser), and the app draws the card.
Both are plugin packages, explorer and git, that the app installs in
~/.config/ovvi/plugins/ on first launch
(The default plugins). The
user or an agent can edit them and press Reload Plugin. They use only the
public plugin API: the app draws the header rows from
their manifests, and the toast, the menus and Open With natively, and it
reads and writes files and runs git for them
(How the cards are built). They work the same on
This Mac and on remote machines, with the limits in
Remote machines.
The label
A plugin pane's herdr label is ovvi:<plugin>#<iid> <arg>
(Labels and instance ids). The argument is the place
the card shows, and it picks the card's page. A relaunch, a cold restart of
herdr or another device makes the card at that place. The instance state
file on the pane's machine keeps the rest: Git's commit message, the diff's
Side by Side, Word Wrap and its file list, and File Explorer's Show Hidden
Files, the tree beside a file and its width, File's Word Wrap and Markdown's
mode, lock and fold once the user changed them in the card
(State).
Settings
Each default plugin has settings on its page in Settings ▸ Plugins (Settings). A setting is the starting value for new cards. An open card follows a change at once, unless the user changed that value in the card's header: the card keeps its own choice.
| Plugin | Setting | Control | Default | What it changes |
|---|---|---|---|---|
| File Explorer | Show hidden files (hidden-files), "Names that start with a dot." | switch | off | row 2's Show Hidden Files in new trees |
| File Explorer | Dim files that Git ignores (ignored) | switch | on | whether the tree dims the files git ignores, in every tree at once |
| File Explorer | When a file opens (file-tree): Show only the file, Show the file with the tree | menu | Show only the file | whether a file shows with the tree beside it in a card with no choice of its own |
| File Explorer | Markdown opens in (markdown): Rich Text, Source | segmented | Rich Text | the mode of a Markdown file in a card with no mode of its own |
| File Explorer | Lock Markdown for reading (markdown-lock) | switch | on | the lock of a Markdown file in a card with no lock of its own |
| Git | Diff layout (diff-layout): Unified, Side by Side | segmented | Unified | row 2's layout in new diffs |
| Git | Wrap long lines (wrap) | switch | off | row 2's Word Wrap in new diffs |
| Git | Show the file list when a diff opens (file-list) | switch | off | whether a diff opens with its file list |
| Git | Commits in the history (commits) | number, 100 to 5,000, step 100 | 100 | the history's length; Show More adds as many again. The home reads again at a change |
| Plugin | Label argument | Page | Title | Tab |
|---|---|---|---|---|
explorer | a folder's absolute path, ending in / | the tree, rooted there | the folder's name | File Explorer ovvi |
explorer | a file's absolute path | File, Preview or Markdown, by the file's name | the file's name | File Explorer main.swift |
git | the repository's root, ending in / | the Git home | the root's name | Git ovvi |
git | the root with its /, a space, then uncommitted or a commit hash | a diff | Working Tree, or the commit's subject (the short hash until the page has read it) | Git Working Tree |
- The tab and the floating window read "‹name› ‹title›", the title in secondary text, as a terminal tab's "zsh ovvi" (Tab bar). A path with a control character, or over 1024 characters, makes no label, so nothing opens.
- The root's
/keeps a root whose last word looks like a hash (My Project abcdef0) a home. - The placeholder prints the plugin's name, the argument and "Shown in Ovvi" in the middle of the pane in herdr's TUI, and follows the label when it changes (In herdr's TUI).
- Each move in the card renames the pane to the new place, so a reload or a
card made again comes back there. A new argument from elsewhere (an
agent's
herdr pane rename) is a new place in the card, after the one it shows. A name that breaks the label turns the card back into a terminal. Reset Pane Name is disabled. - The card's program icon is an SF Symbol on the neutral tile. A File
Explorer's follows its page:
folder(the tree),doc.text(File),photo(Preview) ordoc.richtext(Markdown). Git's issource.branch, the app's own symbol: Material Design Icons'source-branch. The sidebar's branch uses it too.
Ways in
- File Explorer and Git are rows of the New pane
chooser, under Browser (their manifests'
chooser), and the commands Open File Explorer Here and Open Git Here (File menu, the card's menu, the palette). Both take the root of the git repository that the pane's folder is in (git rev-parse --show-toplevel), else the folder. File Explorer opens on the tree, and Git on its home. They replace the pane with Replace Pane's steps, as Browser does (Create). - Everything else opens from inside a card: a file from the tree, a diff from the Git home, a file from a diff or from a Markdown link (Opening).
Opening
Everywhere a plugin card opens a file, a folder, a diff or a link, the app's
one rule applies (Opening links and files):
a click opens in the card itself, as its next place (Back and
Forward), or in a new tab when the card cannot show it;
⌘-click in a new tab and ⌥-click to the side, both in the background;
⌘⌥-click opens a file, a folder or a link in the Mac's default app for it, outside Ovvi, as a terminal's links do
(a remote machine's file or folder shows a notice that it is not on this Mac). A commit and Git's home have no such app: there ⌘⌥-click
is ⌘-click. A right
click shows the app's menu of Open (Open as Root on a folder), Open
in New Tab and Open to the Side, then Rename on a tree row other
than .., and Copy Link on a Markdown link. In the tree, a double-click or ⌘O is
the click.
- A new tab opens right after the card's tab, on the same machine, also
from a floating window. Unless it opens in the background, the app selects
the new tab and gives its card the keyboard. The calls are
layout.applyfor a new tab with the labeled pane, thentab.move, because herdr adds the tab last. The bar shows the tab right after the card's from its first frame (New tabs, Writes are herdr calls). - To the side makes the pane in a new tab, then moves it beside the
card (
pane.move), as Replace Pane does, because herdr's split runs no command. The app hides the new tab until the pane is in place, so the card shows only there, with its own title. - A place of the other plugin (a file from a Git diff) always opens in a new tab or to the side, never in the card.
| From | Gesture | Opens |
|---|---|---|
| the tree: a file | double-click, ⌘O or ⌘↓ | the file, with its page |
| the tree: a folder | double-click, ⌘O, ⌘↓, ⌘-click, ⌥-click, or Open as Root | the tree rooted there |
| the tree beside a file: a file | double-click, ⌘O or ⌘↓ | the file, with its page; the tree stays |
| the tree beside a file: a folder | double-click, ⌘O, ⌘↓, or Open as Root | the tree beside the file, rooted there; the card stays |
| a folder in a file's breadcrumbs | click | the tree, with the file selected (The tree beside a file) |
| an HTML file's Open in Browser | click | the file's page in a browser pane, in a new tab |
| the Git home: a change | click | the Working Tree diff, scrolled to that file |
| the Git home: Working Tree | click | the Working Tree diff |
| a commit's page: a parent | click | that parent's diff |
| the Git home: a commit | double-click or Return (a click selects it) | the diff of that commit |
| a diff's Git Home | click | the Git home |
| a diff: a file header's Open File | click | the file in a File Explorer, in a new tab |
| a diff: a line | ⌘-click (⌥: to the side) | the file in a File Explorer, with the caret on that line |
| Markdown: a relative link or image | click | the file, with its page |
Markdown: an http or https link | click | a browser pane in a new tab |
- Which page opens a file, by its name alone (a remote file is not read
first), from the lists in the plugin's
places.json:.md,.markdownand.mdxopen Markdown. Images, PDF, audio and video open Preview. Known kinds that are not text (archives, Office documents, fonts, disk images) open Preview's info view. Everything else opens File, also a name with no extension (Makefile). File shows the info view for a file that is not UTF-8. - Another plugin with a pattern for a file takes it from File Explorer,
whose pattern is
*(Which plugin opens a file). - A landing (a line, a diff's file, the row to reveal) is used once, by the page's first load. It is not in the label, so a relaunch opens at the top.
- A browser pane opened from Markdown follows the browser's load rule: a page that is not approved or on the safe list asks Open this page? first (When a page loads by itself).
Back and Forward
- Back (
chevron.left, ⌃-) and Forward (chevron.right, ⌃⇧-) sit at the left of row 2 on every page. The app draws them and keeps the list, because both manifests say"history": true(One plugin, many places). They walk the card's own list of places, as a browser's history does. A new place clears the places after the current one. - Kept pages. Each card keeps the last five places it showed as pages (One plugin, many places). So Back and Forward show a place at once, as the user left it: its scroll, its open folders, its selection, its filter, its file list and its row 2 (File's Word Wrap follows the card's last choice). A kept page reads again only what changed on the machine while it was hidden, after it shows. A place not kept draws in a new page while the old one stays on screen.
- Back to the tree shows the row of the file it opened, and scrolls only when that row is out of view.
- A Git home drawn anew (not kept) lands at the change or the commit that the card came back from.
- ⌃- and ⌃⇧- (VS Code's and Sublime Text's Go Back and Go Forward) work in every view: the tree, a file and its editor, Markdown, a preview and Git's pages. No Ovvi key, macOS key binding or CodeMirror default uses them. A terminal reads ⌃- as undo, but these keys act only over a plugin card. The card takes them also with no place that way. ⌃⌘← and ⌃⌘→ (Xcode's) are not used: window managers take them. ⌘[ and ⌘] stay Select Previous Pane and Select Next Pane over every card. ⌘← and ⌘→ also go Back and Forward, as in a browser, except in a text field such as the editor, where they move the caret. A card made from its label (after a relaunch, or a dropped card shown again) has no list yet.
- Unsaved edits ask first. Back, Forward, an open in the card or a close (of the pane, its tab or its space) from a file with unsaved edits shows its tab and the toast "‹name› has unsaved edits." with Discard and Keep Editing. A new place from elsewhere drops the edits without a question.
The card
- Row 1 (36 pt), as on a terminal card: the page's symbol (16 pt, secondary), the title (cut in the middle when it does not fit), the Edited dot when there are unsaved edits, then the card buttons on the keyboard card and on a hovered card (Pane card).
- Row 2 (32 pt) holds Back and Forward, then the page's controls,
described per page below. The app draws it from the manifest's
header(Header): every place's controls start hidden, and the page shows its place's. So a new card shows only Back and Forward until its page runs, a moment. It starts at the row's left edge and ends where row 1 does. Its buttons are 22 × 22 pt in the card buttons' color. 2 pt stay under it, then the page starts its content 4 pt down: Back has 11 pt under it, as in a floating window. - Content. The page fills the card under the header, 10 pt in from the card's sides and bottom. The card's own fill shows through the page.
- The loader. Until the page drew its first rows or its file
(
card.ready()), the app's small spinner shows in the middle of the card, from 150 ms on. On first launch, while the app installs the plugin, the card shows "Installing ‹name›…". - The card's menu adds Reload Plugin (⌥⌘R) and Show Log (Build a plugin).
- A floated plugin pane keeps row 2 under the floating window's title row, from its left edge, 4 pt down from the card's top edge, so Back sits 11 pt from both edges. Every page starts its content 4 pt under row 2, so 11 pt stay under Back too (Rich Text's column keeps its 28 pt margin). The title row shows "‹name› ‹title›" and the Edited dot (Floating pane).
- Overview tile. The tile shows the card's last picture (Overview). A tile watches no file. The card catches up on what changed when it shows again.
- Kept and dropped. A card scrolled away or moved keeps its page, its edits, its scroll and its Back and Forward, up to the limit in What stays in memory.
- A plugin card takes the keyboard as a browser card does. Its page cannot take the keyboard by itself, also on a refresh or when its tab shows again (What each part does).
Edits, saves and reloads
File and Markdown edit the file. Both use the same editor and the same rules. The caret has the text's color: white in dark, black in light.
-
The Edited dot. A 7 pt dot in secondary after the title, centered on the title's x-height as in a document window, while the text differs from the text on disk. Its tooltip and VoiceOver say "Edited". Undo back to the disk's text removes it.
-
⌘S saves. It writes nothing when there are no edits. The save checks the file's hash (
cksum) from the last read or save. If the file on disk did not change since, the app writes a new file beside it and moves it over the old one, so a save is never half written. A link is written through and stays a link. A CRLF file saves as CRLF. -
A change on disk while the card shows: a clean file reloads in place. Only the changed middle of the text is replaced, so the caret and the scroll stay. A dirty file keeps the user's text and shows the toast.
-
The toast is one line on a glass capsule at the top of the card, 6 pt under the header, with one or two small round buttons. The text and the buttons are 8 pt apart. The buttons sit 7 pt inside the capsule's top, bottom and right edge, so their curves follow its curve. The last button is the default. It slides in over 0.15 s. A button's action ends it. A button that deletes or discards work is red: the main button with the toast's
destructiveoption in the plugin API.When Text Buttons the file changed on disk and the card is dirty "‹name› changed on disk." Reload, Keep Mine ⌘S, and the file changed on disk since the last read "‹name› changed on disk since you opened it." Overwrite, Reload the save failed "‹name› was not saved: ‹reason›" Cancel, Try Again Back, Forward, an open in the card or a close with unsaved edits "‹name› has unsaved edits." Discard (red), Keep Editing a Git write failed git's message, on one line Copy, OK a rename, or the status bar's Fetch, Pull or Push, failed the reason OK Git's Revert or Revert All the question (see Writes) Cancel, Revert (red) Keep Mine takes the disk's new hash, so the next ⌘S writes over that version without a question. Reload drops the edits.
-
Read only. A file over 4 MiB on a remote machine opens read only.
-
Not text, too large. A file that is not UTF-8, or one over 16 MiB, shows the info view: the system's icon for its type, its name, its kind and its size.
-
Preview reloads its file on a change too. The tree, the Git home and a Working Tree diff follow their folder, never a timer:
git diffcan rewrite.git/index.
Open With
File, Preview and Markdown have Open With at the right end of row 2 (the
manifest's openWith control). It is a button with the icon and the name of
the file's default app: a click opens the file in that app. Its menu lists the default
app first, then a divider, then the other apps that open the file, each with
its icon (NSWorkspace), then the other plugins that open the file. A pick
of a plugin turns the card into that plugin's card. A file with no default
app shows a plain "Open With" menu. The Mac's apps show on This Mac only: on
a remote machine, Open With shows only when another plugin opens the file.
A file's row 2
File, Preview and Markdown share one row 2, with the same order, spacing and 22 pt buttons:
- On the left: Back and Forward, File Tree (
sidebar.leftwhile the tree is hidden,rectangle.leadingthird.inset.filledwhile it shows beside the file, both in the card buttons' color), then the breadcrumbs. Each folder in them is a button, with the buttons' hover and the pointing hand: a click closes the file and leaves the tree (The tree beside a file); Back returns to the file. The file's own name, and File's scope after it, are text. - On the right: Preview's page and zoom, Word Wrap (File only),
Open in Browser (
globe, File on an HTML file on This Mac only: the page in a browser pane in a new tab, selected), the outline toggle, the mode and the lock (Markdown only), then Open With. - The breadcrumbs are the file's path, its parts with
›between them and the first part in secondary text. Inside a Git repository the path starts at the repository's root, with the root's name first (ovvi › Ovvi › App › OvviApp.swift). Outside one, it starts at the home folder (~ › Downloads › photo.png), else at/. The breadcrumbs are cut in the middle when the row is narrow.
File Explorer
The tree
The tree of the root (@pierre/trees), in the style of Finder's list and
Xcode's navigator: a thin disclosure chevron, folders in the Finder's folder
blue (the same in light and dark), files with an icon for their type in its
color (TypeScript, Swift, Markdown, JSON, images, and so on), 24 pt rows, the
selection in the accent at 24 %. A 4 pt gap sits under the header. A name too
long for its row is cut at its end with "…", as in VS Code, and its tooltip
shows it whole.
-
Row 2: Back and Forward, the root (it takes the rest of the row), then three icon buttons: Filter (
magnifyingglass, in the accent while the find bar shows), Collapse All and Show Hidden Files (eye). -
The root is a path field, like the browser's address. At rest it shows the root's path with the home as
~, cut at the start when it is long (…ode/ovvi). A click shows the whole path, selected. Return goes there, with.,..and//resolved: the tree rooted at that folder becomes the card's next place. A path that is no folder makes the field shake (a beep with Reduce Motion); the typed path stays. Esc puts the root back. A path that does not start with/(or~on This Mac) goes from the root, sosrcor..works, and an empty one is the root. The folder icon before the path is Choose Folder…: the folder panel on This Mac, a "Go to Folder" field on a remote machine (~is the home folder there). A folder's own menu has Open as Root. -
..is the tree's first row, except at/. It is a folder row like the others: a click selects it, a double-click (or ⌘O, or a VoiceOver press) makes the parent folder the root, and its menu has Open as Root. It scrolls away with the rows, and hides while the filter has text. -
Sticky folders. When the tree scrolls, each open folder above the view stays at its top, nested, until its last row scrolls past, as in Xcode's navigator. So the folders the rows are in always show. They draw on the card itself, with no band behind them, in Glass too. The tree's scroll view is masked under them, and the mask stays with the view, so no row shows through at any scroll speed. A copy of the stack draws over the mask, with the hover and the selection; a nested folder that the next one pushes up is cut where its parent covers it. The stack itself, under the mask, still takes the clicks.
-
The tree shows the root's listing first and lists a folder only when it opens.
.gitis hidden, and links to folders are folders. Files and folders whose name starts with.show only while Show Hidden Files is on. A new tree starts with the setting; a click on the eye is the card's own choice, and stays with the card. Git's letters come after the first rows, from onegit status, so a folder never opened still shows a dot for a change inside. A folder shows the dot only for a change ingit statusunder it, never for the files git ignores. A listing asks git only which of its own names git ignores (git check-ignore), and those are dimmed while the setting "Dim files that Git ignores" is on. Modified is the needs-you color, added, untracked and renamed the finished color, and deleted the error color. A root inside a repository, but not its root, shows no letters and dims nothing. -
An empty folder shows a folder icon and "This folder is empty" in the middle of the card.
-
The find bar floats at the tree's top right, as the terminal's ⌘F bar and the editor's do: a capsule with the field ("Filter"), the number of matches and a close button. The rows do not move under it. It opens with row 2's Filter, with ⌘F, or when the user types a letter in the tree, which goes into the field. Esc, the close button or a second click on Filter closes it and clears the filter. Return or ↓ in the field moves the keyboard to the matches.
-
The filter matches file names, case blind, and shows the matches with their folders open, at most 5,000. The first letter loads every path under the root: in a repository the files git does not ignore (
git ls-files), else every file, up to 8 MiB of names. When none match, the middle of the card shows a magnifying glass and "No matching files". A tree that draws anew starts with no filter. A kept one keeps its find bar and filter, and when it shows again the field does not take the keyboard. -
Opening. A double-click on a file shows it in the card. As in the Finder, a click on a folder's name only selects it, and a click on its chevron opens or closes it. So a double-click on a folder makes it the root, and the folder does not open first. ⌘ and ⌥ open as in Opening.
-
The selection. A click on no row clears the selection, and so does a click on a row 2 button. While the tree does not have the keyboard (the path field, the find bar or another card has it), the selection shows in gray, as in the Finder.
-
Keys. The tree has the keyboard when the card opens or gets the keyboard, never by itself (a refresh, a tab shown again). The row with the keyboard has a ring in the accent from the first arrow key on. A click hides the ring again. The keys follow Finder's list view:
Key Does Finder ↑ ↓ move; the first ↓ on a tree with no selected row goes to the first row, the first ↑ to the last the same → ← open and close a folder; ← on a closed row goes to its folder the same Return renames the selected row in place, its name selected without the extension: Return saves, Esc cancels. A name in use, an empty one or one with /keeps the old name and says why in a toast. A row's menu has Rename too (not..)the same ⌘O, ⌘↓ open the selected row: a folder becomes the root, a file shows in the card the same ⌘↑ the parent folder becomes the root the same ⌘← ⌘→, ⌃- ⌃⇧- Back and Forward ⌘[ ⌘] a letter opens the find bar with that letter jumps to the first name with it Space nothing: there is no preview card Quick Look ⌘⌫ nothing: the plugin API has no Trash Move to Trash ⌘D Split Right, the app's key Duplicate Esc closes the find bar the same in the search field ⇧↑ ⇧↓ extend the selection the same ⌘-click opens in a new tab (Opening) adds to the selection -
The tree and the letters update after each change under the root while the card shows, at most every 2 s; on This Mac a change that git ignores (a build's output) or in
.git/objectsupdates nothing. The letters and the branch come from onegit status(git.changes). The open folders list again. A root that cannot be listed shows "Can't list ‹root›" in the middle of the card, underfolder.badge.questionmark. Only a root that is gone empties a tree it listed: any other failure (a remote machine's dropped link, its 10 s bound) keeps the rows, and the next listing brings them up to date. -
The status bar. A 27 pt line under the tree (16 pt buttons), with 10 pt more above its text, so the space above the text and below it to the card's edge (the card's 10 pt inset) is the same. It hides only while the root cannot be listed:
- At the left, the folder: on a remote machine the machine's name first
(
server.rack), thenfolderand the root's own counts, "3 folders, 12 files", with "· 2 hidden" while Show Hidden Files is off and names that start with.hide. - At the right, only while the root is inside a Git repository: the
branch (
source.branch), the lines added (+12, the finished color) and removed (−3, the error color) since HEAD in the whole repository, fromgit diff-index --numstat HEAD, which never writes the index (untracked files count once added); none on a clean one. Then Pull (arrow.downand the count behind) and Push (arrow.upand the count ahead), each only when there is something to pull or push (a branch with no upstream has nothing to push here; Git's Push publishes it), and Fetch (arrow.triangle.2.circlepath, the same either way round, so it turns about its middle) always. One runs at a time, with all three off meanwhile and Fetch's arrow turning (not with Reduce Motion), and git's message shows in a toast when it fails. After a divider, Git (arrow.up.right.square, "Open in Git") opens Git's home on the repository: a click in a new tab, selected; ⌘-click in the background; ⌥-click to the side. - Outside a repository the right side is empty. The bar updates with the tree's letters, after each change under the root and after each of its own writes, with no polling of its own.
- At the left, the folder: on a remote machine the machine's name first
(
The tree beside a file
File, Preview and Markdown can show the tree at the left of the file, in the same card, as a text editor's sidebar does. Row 2's File Tree shows and hides it. The card keeps that choice for every file it shows; a card with no choice follows the setting "When a file opens" (Show only the file). Switch Between Tree and File (⇧⌘E, View menu and ⌘K, as VS Code's Show Explorer) moves the keyboard from the file to its row in the tree, showing the tree first when it is hidden, and from the tree back to the file.
- One tree for the card. It is the tree of The tree itself, the same rows, not a copy: its open folders, selection, scroll and filter stay as the card goes from the tree's page to a file and back, shows or hides the tree, and shows one file after another. Nothing draws again. It is 220 pt wide beside a file (drag its right edge for 140 pt up to 60 % of the card; the card keeps the width), with no status bar and no row 2 of its own: row 2 is the file's. The card keeps the trees of its last five roots, as it keeps pages.
- Its root is the tree's root the card came from, else the file's folder.
A double-click on a folder or on
.., ⌘↑, or Open as Root roots the tree there, and the card keeps its file. - A click selects a row only. A double-click on a file (or ⌘O, ⌘↓) shows it in the card, as the tree's page does, and the tree selects it. ⌘ and ⌥ open as in Opening. A file that the card shows from elsewhere (Back, a Markdown link) is selected too, when it is under the root.
- A folder in the breadcrumbs closes the file: the card shows the tree's page with the file selected. A folder inside the tree beside the file shows that tree, as it was (its open folders and scroll kept); a folder above it, or any folder while the card has no tree yet, shows a tree rooted at that folder. Unsaved edits ask first. On the tree's page the sidebar does not show.
File
An editable text view (CodeMirror 6).
- Row 2 (A file's row 2): the breadcrumbs end with the
scopes around the caret, outermost first, after a
curlybracesglyph (ovvi › Ovvi › App › OvviApp.swift › {} OvviApp). - Text: line numbers, the current line lit, bracket matching, the other
matches of the selection marked, undo and redo, and Tab to indent. The
selection is the accent at 35 %, in File and in every Markdown mode, so
the text stays readable in light and dark. Lines do not wrap, as in a code
editor: a long line scrolls sideways. Row 2's Word Wrap (
text.wrap) turns the wrap on and off for every file the card shows; a wrapped line goes on at its own indent, and the line at the top stays in place. - Colors for Swift, TypeScript, JavaScript, Python, Go, Rust, C, C++,
Java, Kotlin, Ruby, shell, JSON, YAML, TOML, HTML and CSS, by extension.
The colors are the diff's: Pierre Light and Pierre Dark, the default
Shiki themes of
@pierre/diffs(@pierre/theme). Each language loads the first time a card needs it. - Symbols come from the language's syntax tree (functions, classes, types, methods, JSON and YAML keys, CSS rules). Swift, Kotlin, Ruby, shell and TOML have no syntax tree in CodeMirror, so their symbol is the declarations above the caret with a smaller indent.
- Find (⌘F) opens the find bar, as the terminal's: a capsule at the top right of the text, over it, with the field, the match count ("3 of 12"), three toggles (Match Case, Whole Word, Regular Expression, in the accent while on), Find Previous, Find Next and Close. The first match from the selection is selected as you type. Return and ⇧Return, ⌘G and ⇧⌘G step through the matches. A match scrolls into view below the bar. Esc closes it. ⌥⌘F (Find and Replace…, a command of the plugin's in the Edit menu) adds a second row with the Replace field, Replace and All (Replace All); Return in the field replaces, ⌘Return replaces all. A read only file shows the find row only. Markdown has the same bar.
Preview
- Row 2 (A file's row 2): before Open With, the page as "3 of 12" for a PDF, and zoom for images and PDFs (Zoom to Fit, Actual Size, Zoom Out, Zoom In).
- Images (PNG, JPEG, GIF, WebP, HEIC, TIFF, BMP and SVG), centered and scrollable. Fit shrinks a large image to the card and never enlarges a small one. Zoom In and Zoom Out step by a factor of 1.25, from 5 % to 1600 %.
- PDF in pdf.js's viewer: pages draw as they scroll into view, Fit is the page's width, and the page number follows the scroll.
- Audio and video in WebKit's player. A video's full-screen button shows it full screen, as in the browser.
- Any other file, and one that WebKit or pdf.js cannot decode, shows the info view. So does a file over the 16 MiB cap.
Markdown
-
Row 2 (A file's row 2): at the right, before Open With, the outline toggle (
list.bullet.indent, only while the file has a heading), the mode and the lock, all three pill segments: the outline toggle is one segment, picked while the outline shows. The mode is two icon segments, Rich Text (doc.richtext) and Source (chevron.left.forwardslash.chevron.right), with their names as the tooltips; the segment not picked is gray, as the card buttons. The lock is two segments with words, Read (locked) and Edit (unlocked), drawn as the mode's. -
Two modes, each locked or not:
Mode Locked Unlocked Rich Text the file rendered, no caret the file rendered, editable, the caret's lines in Markdown Source the source as File draws code, beside the rendered page; nothing edits the same, the source editable The lock shows in both modes. Locked, nothing edits but a task box (below). The card keeps its mode, its lock and Source's fold: another Markdown file in the card, Back and Forward, and a relaunch show them as last left. A card whose user never chose opens in the settings' mode and lock (Rich Text, locked).
-
Rich Text is one view, locked or not. It is one editor (CodeMirror with Markdown, GitHub's flavor), drawn as rendered Markdown: a live preview. It is a 640 pt column, 28 pt under row 2, in the system font 1 pt larger than the text size. So the lock moves nothing on screen but the caret's line. A switch to or from Source keeps the line at the top in place.
-
Rich Text, locked, is the view with every line drawn and no caret. It draws CommonMark and GitHub's flavor (GFM). The fixture
resources/research/ui/markdown-syntax.mdholds every construct.- Marks are hidden: a heading's
#, a setext heading's underline, emphasis, a link's brackets and address, code ticks, quote marks, code fences, an indented code block's indent, and the backslash of an escape or a hard break. - Paragraphs flow. A line break in the source is a space, so the text wraps at the column's width. A hard break (two spaces or a backslash at the line's end) starts a new line.
- Headings are semibold, with no line under them: only a
---rule draws a line. h1 to h3 are 1.6, 1.3 and 1.1 times the body, h4 to h6 the body size (GitHub's larger sizes were tried; the user kept these), with a 1.25 line height. The space above a heading is 0.6 of its own size and the space below 0.2, so h1 has the most and h4 to h6 the least. A heading on the first line has no space above. Rich Text, locked or not, and Source's page look the same. - Lists. Bullets are
•. Numbers count up from the list's first one, so1. 1. 1.shows 1, 2, 3, and1)shows1.. Each list depth indents 1.5 em, and the bullet sits at the end of its indent. So the text, its wrapped lines and its later paragraphs start at one x. - Task boxes take the bullet's place: a top-level box starts at the text's left edge. A box is drawn by the page: 1.1 em square with a thin rounded border, centered on the line's lowercase letters. Ticked, it fills with the accent and shows a white check.
- Links. Inline links, reference links (
[text][ref],[ref][],[ref]), autolinks and bare addresses are links in the accent. A reference's definition line is hidden.www.addresses open withhttp://, e-mail addresses withmailto:. Images are not loaded: their alt text is a link to the image. - Footnotes. A reference (
[^note]) is a small raised number, by the order of the definitions. A click scrolls to its definition. A definition ([^note]: text, also when the text is only an address) stays where it is written, in smaller secondary text, after its number. - Quotes have a bar at the left for each depth, and secondary text. Each depth indents 12 pt.
- Code blocks have a rounded fill. A
---rule is a line. - HTML. Tables and HTML blocks are drawn by markdown-it. An element
that opens in one HTML block and closes in a later one (a
<details>with Markdown inside) is drawn as one. HTML comments draw nothing. Inline HTML elements of text (<kbd>,<sub>,<sup>,<b>,<a href>and the like) and<br>draw as themselves. Other inline tags show as written. Entities (©) show their character. - Tables size their columns to their content. Words wrap only when the table is wider than the column. A word wider than the column scrolls the table sideways. Code in a table uses the app's monospace. A table or an HTML block in a list item starts at the item's text.
- Front matter shows as YAML in 12 pt monospace, secondary.
- Copy gives the Markdown source of the selection, and Select All the whole file. For VoiceOver, a locked view is an article with headings, not a text box.
- Marks are hidden: a heading's
-
Task boxes. A click on a box ticks it or clears it: one character,
xor a space, changes in the file. Locked, the card saves it at once, as ⌘S does, with any edits not saved yet. Unlocked it is an edit like a typed one, saved with ⌘S. Undo takes it back. The boxes of a file that is read only are dimmed and do not change. -
Rich Text, unlocked, makes the view editable. The lines with the caret or the selection show their marks; every other line looks as locked. The caret's line is its own source line: the lines of its paragraph above and below it still flow. A table, an HTML block, a setext heading's underline and a hidden definition or comment show their source while the caret is in them. A click on a table or an HTML block puts the caret there. A click while locked and then ⌘E keeps the caret where the click was.
-
Source uses the code colors of File (Pierre Light and Pierre Dark): marks in the punctuation color, headings as tags, links and addresses in blue, code in the string color, quotes in the comment color, emphasis in italics and strong text in bold.
-
Source's page. Source and the rendered page split the card in halves, with a 1 pt divider between them. The page starts folded: Source opens alone until the user shows the page. The two scroll together by source line: the side that the user last scrolled, clicked or typed in leads. A tick in the page edits the source. A small button on the divider (
chevron.right, "Hide Preview") folds the page away, and Source takes the whole width; the button then sits at the right edge (chevron.left, "Show Preview"). -
⌘E (View ▸ Lock Editing, checked while locked) locks and unlocks, as the lock does. The find bar works locked too, without its Replace row (⌥⌘F shows the find row only); locking closes it. ⌘A selects the whole file in both modes, locked or not, as in the editor.
-
The outline is a 190 pt column at the left with the document's headings, indented by level. A file with no heading hides the toggle and closes the outline. The outline closes when the last heading goes. It slides in and out (0.25 s). A click scrolls to the heading and, unlocked, puts the caret on it.
#sectionlinks scroll to the heading with GitHub's id. A second heading of the same name is#section-1. -
Links open from Rich Text and Source's page (Opening). Unlocked, a link on the caret's line shows its source and a click puts the caret in it. A landing with a line scrolls to that line and puts the caret there; the lock stays as it was. A page that loads again has the outline closed.
-
Dirty, ⌘S, the reload and the toast are as in Edits, saves and reloads.
Git
The home
The repository's state on one page, in the app's style: rows as the
sidebar's (28 pt, radius 7), SF Symbols drawn in the text's color, the
app's fonts and Look colors. From the top:
- Row 2 (native): Back and Forward, the branch, Working Tree
(
plus.forwardslash.minus), then Fetch (arrow.clockwise), Pull (arrow.down, with the number of commits behind) and Push (arrow.up, with the number of commits ahead). Working Tree and the three are small bordered buttons with their symbol and word. When the row has no room for all four words, all four show their symbol alone. A button shows a spinner while its command runs. The three and the branch are disabled while one of them, a switch or Commit runs, and when git cannot read the repository. Working Tree is disabled only when git cannot read it. - The branch is a menu button with
source.branchand the checked-out branch. Its menu lists the local branches, the current one checked, then under "Remote" the remote branches that have no local branch. A pick runsgit switch <branch>, orgit switch --trackfor a remote one. - The commit message field ("Message") grows to 5 lines, then scrolls.
Commit beside it is on the accent when there is a message and a
change, else gray. ⌘↩ commits too. Commit takes the staged changes
(
git commit -m). With none staged, the button reads Commit All: it stages everything (git add -A), then commits. The message is in the card's state, so it stays while the card moves to a diff and back, and over a drop, a reload and a relaunch. It empties after the commit. - Staged Changes and Changes, each only when it has files: a header
with its count and the words Revert All and Unstage All or
Stage All, then one row per file: the file's icon by its type (File
Explorer's icons and colors, from the same
@pierre/treespackage), its name, its folder in secondary, the Revert (arrow.uturn.backward) and the Stage (plus) or Unstage (minus) buttons that show under the pointer, and its letter in the tree's colors. A click opens the Working Tree diff at that file. - No changes: with nothing to commit, the message, Commit and both lists
give way to one placeholder:
checkmark.circleand "No changes". - History, 26 pt under the changes, after a hairline: the last 100
commits (the setting "Commits in the history") of every branch, remote branch and tag,
and HEAD, as a graph, newest first (
git log --branches --remotes --tags HEAD --topo-order, not--all, which adds the stash and notes). Each row has its lanes and dot, its branch, remote branch (cloud) and tag (tag) capsules, the subject, the short hash and the date (a time today, else the day). The checked-out branch's capsule is on the accent, and HEAD's dot is a ring. The first parent stays in its child's lane, as ingit log --graph. Lane colors cycle through the accent, finished, working, needs-you and error colors. "No commits yet" before the first commit. A full page ends with Show More, a plain gray button in the middle, which reads as many again (git log -n 200after 100, and so on). The page ends 10 pt under the last row. - Selecting a commit works as in File Explorer. A click selects a commit: its row takes the accent at 24 %. A double-click or Return opens its diff in the card. ⌘-click opens it in a new tab and ⌥-click to the side. ↑ and ↓ move the selection, and the first arrow selects the first commit (↑ the last). After an arrow key, the selected row has a 1.5 pt ring in the accent, until the next click. A click anywhere else clears the selection. The arrows do nothing while the message field has the keyboard. A refresh keeps the selection where it is and does not scroll.
- Not a Git repository: when the root is in no repository
(
git.rootis null), the page showsfolder.badge.questionmark, "Not a Git repository", the folder's short path (~/…) and Initialize Repository, which runsgit initafter the click. When git cannot read a repository or cannot run, the page shows git's message in the middle. - The home reads again after each change under the root while the card
shows (on This Mac also
.git's own: a commit or a fetch from a terminal), and after each write. A change takes the changes and the head from onegit status --porcelain=v2 --branch(git.changes); the branches and the history read again only when git's own files change (git.watch:HEAD, the refs,config, as the sidebar's branch follows them), when the head moved, after a write or Show More, or after a read that failed. On This Mac a change that git ignores (a build's output) or in.git/objectsreads nothing, and the reads come at most every 2 s.
Writes
- git runs on the card's machine through its
Exec, asenv GIT_TERMINAL_PROMPT=0 git -C <root> …, so nothing can ask for a password. Writes run one at a time, in order, because git takesindex.lock. A network command (Fetch, Pull, Push, a switch, Commit) can take 120 s, the others 30 s. - A failed write shows git's message in the card's toast, with Copy and OK.
- Revert asks first in the card's toast, with Cancel and Revert:
"Revert changes to ‹name›? This cannot be undone." A new file (untracked,
or added in the index) is deleted, so the question names it: "Delete the
new file ‹name›? This cannot be undone." Revert All with more than one file
asks "Revert all ‹n› files? This deletes the new file ‹name› (or files ‹names›). This cannot
be undone." (three names at most, else two and "and ‹n› more"). Revert puts
each file back as
HEADhas it, in the index and the work tree, from either list, and the old path of a rename too. VoiceOver reads the two Revert All buttons as "Revert All Staged Changes" and "Revert All Changes".
| Control | Runs |
|---|---|
| Fetch | git fetch --all |
| Pull | git pull: the user's pull.rebase decides. Diverged branches with no setting fail with git's message |
| Push | git -c push.autoSetupRemote=true push: a branch with no upstream is published under its name (git 2.37 or later) |
| Stage, Stage All | git --literal-pathspecs add -A -- <path> (and the old path of a rename), git add -A |
| Unstage, Unstage All | git --literal-pathspecs reset -q -- <path>, git reset -q (also before the first commit) |
| Commit | git commit -m <message>; Commit All runs git add -A first |
| Revert, Revert All | git restore --staged --worktree --source=HEAD of the tracked paths, then git clean -fq of the untracked ones (git ls-files --others --exclude-standard), each with --literal-pathspecs. With a tracked path it fails before the first commit, with git's message, and deletes nothing |
| Initialize Repository | git init -q |
| a branch | git switch <branch>, git switch --track <remote branch> |
Diff
The changes of one source in one scroll (@pierre/diffs).
- Row 2: Back and Forward, Git Home (
house), thenUnified | Side by Side, Word Wrap (text.wrap, the app's symbol from Material Design Icons'wrap, a toggle in the accent while on; Side by Side always wraps, so it is disabled there), the file list button (sidebar.left, its title Hide File List or Show File List), Previous Change (chevron.up) and Next Change (chevron.down). There is no source pop-up: another source opens from the home. - A 4 pt gap sits between row 2 and the page, also while it scrolls: a file header sticks below it, and the file list's line starts below it.
- Working Tree (
uncommittedin the label) is the staged, unstaged and untracked changes againstHEAD(against the empty tree before the first commit), up to 16 MiB of patch. It is a live view: while the card shows, each change under the root (an agent's edits) asks for the patch again. Only the files whose text changed draw again: every other file keeps its box, and the scroll stays where it was. With no changes it showscheckmark.circleand "No changes" in the middle, as the home does, with no file list and its button off, and fills as files change. A commit shows against its first parent. - A commit's page starts with its subject (15 pt, semibold), its body,
then Author (name and email), Date (relative, then the date and time),
Commit (the short hash: a click copies the full one and says "Copied")
and Parent or Parents (each short hash opens that commit). The subject is
the card's and the tab's title (
document.title), and VoiceOver reads it as the card's name. The text can be selected. - The scroll. Each file is a box with 8 pt corners and a 1 px border in
the divider's color, 8 pt from the next box and from the sides. The box's
header is a 32 pt row with a hairline under it, opaque in the card's color
(
--card,Look.cardSolid, no blur) under a faint fill: a disclosure, the file's icon, the file's name in semibold, its folder in secondary ("from ‹old path›" for a rename),+n −min the finished and error colors, its letter, a Viewed box and Open File (not on a deleted file). In a card under 480 pt the folder and the counts give way. Viewed folds the file and dims its row in the list. A folded file shows its header alone, with all four corners round; a fold whose header sticks at the top scrolls back to the box's top. A refresh keeps each file's fold and Viewed while its text is the same. A Viewed file whose text changed opens again. A file new to the card with over 2,000 changed lines starts folded. Changes inside a line are marked by word. Added lines use the finished color at 10 %, removed lines the error color, and word marks 28 %. - The sticky header stays at the top of the view while its file is in
view, and the next file's header pushes it up. It is
position: stickyinside its box, so the system moves it as it scrolls and no script does. While it sticks it keeps the box's round top corners, on a square of the card's color, so no line shows in the corners. Each file's lines draw only near the view (@pierre/diffs'VirtualizerandVirtualizedFileDiff), but the box keeps the whole file's height. A box more than about 1,500 pt from the view takes an estimated height (20 pt a row) until it comes near. When a box above the view changes height (a refresh, a box that comes near), the file in view stays where it is. In a very fast scroll the lines can be blank for a frame; the header never moves. The scroll bar draws over the sticky header. - The file list is a column at the left (28 % of the card, 160 to
260 pt) in the home's row style: a title with the file count, then one row
per file with its icon, its name, its folder in secondary,
+n −mand its letter (M A D R, in the tree's colors). A click scrolls to the file. The file list button slides it out and in (0.25 s, the plugins' shared sidebar incard.css; at once with Reduce Motion). - Previous and next change (also ] and [ while the page has the
keyboard) step through the change blocks in order: each run of changed
lines, as editors step through changes. git's own hunks (
@@) are not the unit, because one hunk can hold several changes and be taller than the view. A step goes from the last change stepped to, or from the first change of the file picked in the list. After the user scrolls that change out of view (or changes the filter), it goes from the top of the view: ] to the first change at or below it, [ to the last one above it. A step opens a folded file and puts the change in the middle of the view. - The find bar (⌘F) filters the files, as the tree's does: a capsule with the field ("Filter"), the number of files that match and a close button. It floats at the top right under the sticky header, so the header's Viewed and Open File stay in reach. The filter matches the file's path, case blind, and hides the other files in the scroll and in the file list. Previous and next change step through the shown files only. When none match, the middle shows a magnifying glass and "No matching files". Esc or the close button closes the bar and clears the filter; the file in view stays where it is. Return gives the keyboard back to the diff, for ] and [, and keeps the filter; Esc there still closes the bar. A refresh keeps the filter. A move to a file the filter hides (a change in the Git home) clears the filter.
- ⌘-click on a line opens the file at that line in a new tab, ⌥-click to the side. On a removed line it opens the line after the change.
- "No changes" shows when the patch is empty. A failed first read (not a repository yet, a machine away) shows git's error and asks again at the next change in the work tree.
- A move to a place not kept (a commit from the home, Parent) reads the new patch and the commit's facts first and then draws them in one step: the place before stays until then, with no blank page between. A kept diff shows at once and reads its patch again only if the work tree changed while it was hidden.
- A new diff starts with the card's own Side by Side, Word Wrap and file list, else the settings' (unified, no wrap, the file list closed). When the user changes one of them in a diff, the card keeps that choice. These are saved in the state file, so they survive a drop, a reload and a relaunch. A kept diff keeps its own. Viewed marks last while the page is kept.
- A file with no hunks (binary, a mode change, a pure rename) shows its
header alone.
N unmodified linesrows do not expand. Look.cardSolidfollows the card: the keyboard card's is its own fill (a step brighter, but darker in Classic dark). It is exact in Classic. In Glass it is measured over a black window (dark#1F,#23on the keyboard card; light#F1,#F5, the same over a white one). A bright window or wallpaper behind lightens dark Glass's card by up to 18 levels, and the sticky file header then shows a little darker than the card.
Keys
Over a web card, ⌘ keys go to the menu first, as over a native text view. A
key that no menu item has reaches the page. Some items have their key only
over the card that uses it, because a disabled menu item swallows its key.
The table shows the defaults: the menu's keys and Back and Forward change in
Settings ▸ Keyboard Shortcuts, where
File Explorer's commands are a group of their own. The keys inside a page's
lists and fields (a Git diff's [ ], ⌘↩ in the commit field) are the page's.
| Keys | Command | When it has its key |
|---|---|---|
| ⌃- ⌃⇧-, ⌘← ⌘→ | Back, Forward in the card | always over the page: the card's view takes them before the page and the menu; an arrow key in a text field moves the caret there |
| ⌘S | Save (File menu): the editor's text to disk | only while File or Markdown has the keyboard |
| ⌘F | Find… (Edit menu): the editor's find bar; on the tree and a Git diff, its find bar (a filter of the files) | on File, Markdown, the tree and a Git diff. Disabled on Preview and the Git home |
| ⌥⌘F | Find and Replace… (Edit menu): the find bar with its Replace row | on File and Markdown; a read only file (Markdown locked) shows the find row only |
| ⌘G | Find Next (Edit menu); in a Git diff, Next Change | always. Over a terminal it steps the find bar |
| ⇧⌘G | Find Previous (Edit menu); in a Git diff, Previous Change | only while File, Markdown, the tree or a Git diff has the keyboard. Elsewhere ⇧⌘G is Go to Directory |
| ⌘E | Lock Editing (View menu, while a File Explorer card has the keyboard; disabled outside Markdown): locks and unlocks Markdown | only while Markdown has the keyboard |
| ⇧⌘E | Switch Between Tree and File (View menu): the keyboard to the file's row in the tree beside it, and back (The tree beside a file) | while File, Preview or Markdown has the keyboard |
| ⌥⌘R | Reload Plugin (the card's menu, View ▸ Pane) | over a plugin card |
| ⌘↩ | Commit | in the Git home's message field |
| ↑ ↓, Return | select a commit, open it | in the Git home, outside its message field |
| ] [ | Next Change, Previous Change | in a Git diff, outside its find bar |
} { (⇧] ⇧[ on a US keyboard) | the next file, the previous file: its top, unfolded as a pick in the file list does. ⇧⌘] ⇧⌘[ are Show Next Tab and Show Previous Tab | in a Git diff, outside its find bar |
| ⌘+ ⌘− ⌘0 | the app's text size. Cards keep their own size, as native ones do | always |
| ⇧⌘← ⇧⌘→ | Move Tab Left, Right | the page gets it first (select to the line's start or end). When the page does not take it, the tab moves |
- ⌘D, ⌘W and the other app keys work over a plugin card as over a terminal.
- A floating window's menu bar and palette offer Save, Lock Editing, Find, Find Next and Find Previous with the same keys.
- Replace Pane (⇧⌘R) gives a fresh shell with the chooser. Close, Replace Pane and the commands that change a pane's kind ask nothing while the pane runs only its placeholder. A close with unsaved edits shows its tab and the "‹name› has unsaved edits." toast first.
Remote machines
Every read, save, listing, watch and git command runs on the pane's machine
through its Exec, over the relay's one SSH session
(Commands). Nothing is installed on
the machine. The limits:
- No apps in Open With: the file is not on the Mac.
- A file over 4 MiB opens read only, because a save sends the whole file through the relay.
- Choose Folder… is a typed path, not the folder panel. A path that is no folder shows "No folder ‹path›".
- No file system events: while a card shows, the app checks every 2 s. A check that fails (a dropped link, its 10 s
bound) reports no change, and the first one that answers after it reports one. For a
file it compares the file's
cksum. For a folder (the tree, the Git home, a Working Tree diff) it compares the folder's entries, itsgit statusand the changed files' text. A hidden card checks nothing. - git comes from the machine's
PATH, and a remote machine's git always runs: one without git shows no git and roots File Explorer and Git at the pane's folder. When This Mac has git only as the stub without the developer tools, cards on This Mac run no git (Git status). - Pull and Push use the machine's own credentials. A key with a passphrase and no agent fails with ssh's message, because nothing can ask for it.
How the cards are built
- Packages. Each is a package in
~/.config/ovvi/plugins/<id>/: itspackage.jsonwith theovvikey, a lockfile, its ownnode_modules,src/and the builtdist/(File Explorer and Git in the same layout). - One web view per card. Each card is a
WKWebViewof its own, with its own WebContent process (Memory). - One page, many places. The card loads its plugin's page once. A move
is
card.go: the page draws the new place itself, in a hidden root over the old one, and swaps them once it drew, so the old frame stays until then. Each heavy place (File Explorer's four, Git's diff) is a module loaded withimport()on first use, so a tree card loads no CodeMirror or pdf.js, and a Git home loads no@pierre/diffs. Git's home is in the entry, so it opens at once. A place ends its handlers, watches and views when the card leaves it. The card keeps the keyboard if it had it. - CSS. The entry imports every place's CSS, because a bundler links no
CSS of a dynamic chunk. File Explorer scopes the tree's rules by the place
(
data-place), Git each place's by its container (#home,#diff). - Files stay checked.
files.readgives the file's hash, andfiles.writewithoverwrites only when the file on disk still has it.files.infogives the hash too, so File and Preview check it before a full read. Saves, hashes, watches and git run in HerdrKit'sFilesandGiton the card's machine (The plugin API). - SF Symbols in a page. The pages ask the app for the symbols they draw
(
ovvi.symbol), and use each PNG as a mask in the text's color. - The place rule is in
places.json, which the page and the app both read: the app picks the tab's icon, the page's accessibility identifier (files,file,previewormarkdown.<pane>.page; Git's isgit.<pane>.page) and which places take ⌘S and ⌘F from it. - Keys. The card's view takes and drops the keyboard as a browser card does, and sends ⌘ keys to the menu first (Keys).
- A crashed page loads again by itself, once per load (see
Errors). Unsaved edits are lost then. File and
Markdown keep each file's top line in the page's session
(
sessionStorage), which WebKit keeps when the page's process ends: the page loaded again, or the same file shown again in the card, starts at that line. A linked open's line wins over it, but only on its first load. - Parsing. CodeMirror parses the lines in view in slices of 50 ms until they are parsed, whatever has focus. Its own parser in idle time runs on a budget of the wall clock (3 s in 30 s) and starts a new budget only while the editor has focus. Without the slices, a page that the system pauses in the middle of a parse spends the budget at once, and a card without focus shows its Markdown raw.
- Inspect. A plugin card is not inspectable: a right-click on empty space in its page shows only Reload.
Not built
- Go to File in ⌘K, and search across a project.
- Stash, merge and rebase in Git.
- Quick Look for other file types: they show the info view.
- Expandable
N unmodified linesrows in a diff (they need each file's whole text). - A landing and Back and Forward kept over a relaunch, and Viewed marks saved.
- Images in Markdown's Rich Text, and a table view while it is unlocked.
- Markdown's footnotes gathered at the end of the page, as on GitHub, and
~sub~and^sup^, which GitHub does not draw either.
Choices
The decisions are D89 to D94 and D100 on the Decisions page.