mirror of
https://github.com/anthropics/claude-code.git
synced 2026-09-29 16:59:25 +08:00
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:
@@ -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'
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,5 +1,6 @@
|
||||
export * from './current-pane'
|
||||
export * from './detail'
|
||||
export * from './inset-of'
|
||||
export * from './kit'
|
||||
export * from './layout'
|
||||
export * from './pane-actions'
|
||||
|
||||
@@ -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,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>
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user