mods/diff: a helper takes the whole shape its callers hold, and the pane's inset kit has a constructor

This commit is contained in:
poteat
2026-09-11 10:46:48 -07:00
parent 46ea2a528d
commit 97f86b94e8
13 changed files with 68 additions and 55 deletions
@@ -9,18 +9,18 @@ import { stampIfFile } from '../stamp-if-file'
* path is never listed, descended or stat'ed: session work (the engine
* lstats); a path the listing budget never reached is the caller's to place.
*
* @param probe the fetch's stamp probe over the working tree's top
* @param sessionStartMs when the session began
* @param context the fetch's stamp probe over the working tree's top, and
* when the session began
* @param paths repository-relative paths, `/`-separated as git prints them
* @returns each path's Dating
*/
export async function datingsOf(
probe: Types.StampProbe,
sessionStartMs: number,
context: Types.DatingContext,
paths: readonly string[],
): Promise<ReadonlyMap<string, Types.Dating>> {
const { sessionStartMs } = context.deps
const stamps: readonly Types.Stamp[] = await Promise.all(
paths.map(path => stampIfFile(probe, path)),
paths.map(path => stampIfFile(context.stamps, path)),
)
function datingOf(stamp: Types.Stamp): Types.Dating {
@@ -17,8 +17,7 @@ export async function tagPreSession(
files: readonly Types.FileStat[],
): Promise<readonly Types.FileStat[]> {
const datings = await datingsOf(
context.stamps,
context.deps.sessionStartMs,
context,
files.map(file => file.path),
)
@@ -22,11 +22,7 @@ export async function untrackedRowsOf(
place: UntrackedPlace,
): Promise<readonly Types.FileStat[]> {
const probedPaths = paths.slice(0, Limits.MAX_UNTRACKED_PROBES)
const datings = await datingsOf(
context.stamps,
context.deps.sessionStartMs,
probedPaths,
)
const datings = await datingsOf(context, probedPaths)
const probed = probedPaths.map(path => ({
path,
isPreSession: datings.get(path) !== 'session',
@@ -1,9 +1,14 @@
import type Backend from '../../backend'
/**
* The pane's word for a fetch whose untracked listing the backend did not
* give whole: the tracked rows stand, the new files are not counted.
*
* @param lister the program that lists untracked files (BackendWords)
* @param words the backend's words, naming the program that lists them
* @returns the note
*/
export const untrackedWithheldTextOf = (lister: string) =>
`Untracked files unavailable (${lister} could not list them); not counted`
export const untrackedWithheldTextOf = (
words: Pick<Backend.BackendWords, 'lister'>,
) =>
`Untracked files unavailable (${words.lister} could not list them); ` +
'not counted'
+9 -11
View File
@@ -1,6 +1,5 @@
import type Backend from '../backend'
import type Git from '../git'
import { modeLabelOf } from './mode-label-of'
import type { PaneModel } from './pane-model'
/**
* The dim line under the header naming what the diff compares against
@@ -10,18 +9,17 @@ import { modeLabelOf } from './mode-label-of'
* with an ellipsis while its fetch is pending; on an unborn HEAD with
* rows, what the rows are instead.
*
* @param requested the mode the person picked
* @param data the last good fetch
* @param model the mode the person picked, the last good fetch, and the
* backend's words
* @param filesCount the header's session file count
* @param words the backend's words
* @returns the line, or null
*/
export function baseLabelOf(
requested: Git.BaseMode,
data: Git.DiffData | null,
model: Pick<PaneModel, 'requestedMode' | 'data' | 'words'>,
filesCount: number,
words: Pick<Backend.BackendWords, 'base'>,
): string | null {
const { requestedMode, data } = model
if (!data) {
return null
}
@@ -32,15 +30,15 @@ export function baseLabelOf(
return hasRows ? 'no commits yet — showing staged and new files' : null
}
const isPending = requested !== data.mode
const isSettledSession = !isPending && requested === 'session'
const isPending = requestedMode !== data.mode
const isSettledSession = !isPending && requestedMode === 'session'
if (isSettledSession) {
return null
}
const phase = isPending ? 'pending' : 'settled'
const label = modeLabelOf(requested, data.source, phase, words)
const label = modeLabelOf(model, data.source, phase)
return isPending ? `${label}…` : label
}
+4 -2
View File
@@ -24,7 +24,9 @@ export function emptyStateOf(
if (!data) {
return {
headline: 'Diff unavailable',
hint: `Couldn't read the ${words.diffCommand} — it will retry on the next change`,
hint:
`Couldn't read the ${words.diffCommand} — it will retry on the ` +
'next change',
}
}
@@ -35,7 +37,7 @@ export function emptyStateOf(
if (data.isUntrackedWithheld) {
return {
headline: 'No tracked changes',
hint: Names.untrackedWithheldTextOf(words.lister),
hint: Names.untrackedWithheldTextOf(words),
}
}
@@ -1,5 +1,5 @@
import type Backend from '../../backend'
import type Git from '../../git'
import type { PaneModel } from '../pane-model'
/**
* A requested mode's name for the base line.
@@ -8,21 +8,19 @@ import type Git from '../../git'
* while none is known. A working-tree diff names its own base (`HEAD`, a
* short sha); anything else falls back to the backend's word for it.
*
* @param requested the mode the person picked
* @param model the mode the person picked, and the backend's words
* @param source what the data on screen compares
* @param phase `pending` while the requested mode's fetch has not landed
* @param words the backend's words
* @returns the label without its pending ellipsis
*/
export function modeLabelOf(
requested: Git.BaseMode,
model: Pick<PaneModel, 'requestedMode' | 'words'>,
source: Git.DiffSource,
phase: 'pending' | 'settled',
words: Pick<Backend.BackendWords, 'base'>,
) {
const base = source.kind === 'working-tree' ? source.base : words.base
const base = source.kind === 'working-tree' ? source.base : model.words.base
switch (requested) {
switch (model.requestedMode) {
case 'session':
return 'this session'
case 'uncommitted':
@@ -54,12 +54,7 @@ export function currentPane(
? PaneState.headerTotalsOf(data, partition)
: PaneState.ZERO_TOTALS
const empty = PaneState.emptyStateOf(data, totals.filesCount, model.words)
const baseLabel = PaneState.baseLabelOf(
model.requestedMode,
data,
totals.filesCount,
model.words,
)
const baseLabel = PaneState.baseLabelOf(model, totals.filesCount)
const selected = PaneState.selectionOf(
PaneState.listedOf(partition, preSession),
model.selectedPath,
@@ -103,7 +98,7 @@ export function currentPane(
totals.notShown > 0 ? `${totals.notShown} not shown` : null,
)
const untrackedNote = noteOf(
isUntrackedNoted ? Names.untrackedWithheldTextOf(model.words.lister) : null,
isUntrackedNoted ? Names.untrackedWithheldTextOf(model.words) : null,
)
const detail = selected
? [
@@ -132,7 +127,7 @@ export function currentPane(
top: Sections.present([
header,
noteOf(baseLabel),
Sections.todoBar(kit, model.todos),
Sections.todoBar(kit, model),
noiseToggle,
notShownNote,
untrackedNote,
@@ -147,7 +142,7 @@ export function currentPane(
{Sections.present([
header,
noteOf(baseLabel),
Sections.todoBar(kit, model.todos),
Sections.todoBar(kit, model),
Sections.controlsView(kit, model),
noiseToggle,
...listed.map(line =>
+1
View File
@@ -1,5 +1,6 @@
export * from './current-pane'
export * from './detail'
export * from './inset-of'
export * from './kit'
export * from './layout'
export * from './pane-actions'
+3
View File
@@ -0,0 +1,3 @@
export * from './inset-of.js'
export * as default from '.'
@@ -0,0 +1,15 @@
import Limits from '../../limits'
import type { Kit } from '../kit'
/**
* A drawing's kit inside the docked pane's padding: the built-in's blank
* last column and its blank row above the header taken off.
*
* @param kit the drawing's kit at the pane's full size
* @returns the kit its body draws with
*/
export const insetOf = (kit: Kit): Kit => ({
...kit,
columns: Math.max(1, kit.columns - Limits.PANE_RIGHT_PAD_COLUMNS),
rows: Math.max(0, kit.rows - Limits.PANE_TOP_PAD_ROWS),
})
+7 -8
View File
@@ -7,6 +7,7 @@ import Limits from '../limits'
import Names from '../names'
import PaneState from '../pane-state'
import { currentPane } from './current-pane'
import { insetOf } from './inset-of'
import type { Kit } from './kit'
import Sections from './sections'
import { turnPane } from './turn-pane'
@@ -37,16 +38,14 @@ export function paneView(
return <Box>{Sections.dimNote(kit, Names.RESIZE_TERMINAL_TEXT)}</Box>
}
const right = Limits.PANE_RIGHT_PAD_COLUMNS
const top = Limits.PANE_TOP_PAD_ROWS
const inset: Kit = {
...kit,
columns: Math.max(1, kit.columns - right),
rows: Math.max(0, kit.rows - top),
}
const inset = insetOf(kit)
return (
<Box flexDirection="column" paddingTop={top} paddingRight={right}>
<Box
flexDirection="column"
paddingTop={Limits.PANE_TOP_PAD_ROWS}
paddingRight={Limits.PANE_RIGHT_PAD_COLUMNS}
>
{isCurrent ? currentPane(inset, model) : turnPane(inset, model, turn)}
</Box>
)
+5 -3
View File
@@ -3,7 +3,7 @@
/* @jsxFrag Fragment */
import type { RenderElement } from 'claude-code'
import type Todos from '../../todos'
import type PaneState from '../../pane-state'
import type { Kit } from '../kit'
import { TODO_BAR_CELLS } from './todo-bar-cells'
import { TODO_BAR_RESERVE } from './todo-bar-reserve'
@@ -13,13 +13,15 @@ import { TODO_BAR_RESERVE } from './todo-bar-reserve'
* a todo list exists (ReplDiffSidebar's ProgressBar row).
*
* @param kit the elements and the width
* @param todos completed over total
* @param model the pane's state, holding its todos' completed over total
* @returns the row, or null without todos
*/
export function todoBar(
kit: Kit,
todos: Todos.TodoProgress,
model: Pick<PaneState.PaneModel, 'todos'>,
): RenderElement | null {
const { todos } = model
if (todos.total === 0) {
return null
}