surshar / claude-code
Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.
Built by Anthropic · github.com/anthropics
| Mmods/diff/README.md |
| Mmods/diff/tests/fixtures/index.ts |
| Amods/diff/tests/fixtures/wheel-over-list.ts |
| Mmods/diff/tests/register.test.ts |
| Mmods/diff/tests/views.test.ts |
| @@ -5,8 +5,9 @@ | ||
| 5 | 5 | beneath, and closes it again; each toggle leaves `Diff panel shown` or |
| 6 | 6 | `Diff panel hidden` in the transcript. The header, the file list and its |
| 7 | 7 | toggles stay put while the wheel moves the hunks under them three rows a |
| 8 | -tick (the plugin answers the pane's `ui.scroll` itself); a row's click puts | |
| 9 | -that file's hunks at the top; past eight rows the list scrolls under the | |
| 8 | +tick, or the list a file a tick while the wheel is over a list longer than | |
| 9 | +its eight rows (the plugin answers the pane's `ui.scroll` itself); a row's | |
| 10 | +click puts that file's hunks at the top; the list also scrolls under the | |
| 10 | 11 | built-in's list keys (`ctrl+up`/`ctrl+down`, `opt+up`/`opt+down`), and |
| 11 | 12 | `ctrl+x b` moves the comparison base on, as the built-in's chord does: both |
| 12 | 13 | through Buttons that declare the engine's own actions. The pane refreshes |
| @@ -19,9 +20,11 @@ | ||
| 19 | 20 | Under the fullscreen layout a terminal under 110 columns gets the |
| 20 | 21 | built-in's line asking for a wider one and nothing opens. Without that |
| 21 | 22 | layout (`CLAUDE_CODE_NO_FLICKER=0`, which `/diff` learns from the command's |
| 22 | -`presentation`) the pane opens inline at any width, focused, in the | |
| 23 | -built-in dialog's shape: the title, the count, the file rows, the key | |
| 24 | -hints; the arrows walk the rows, Enter shows that file's hunks alone, | |
| 23 | +`presentation`) the pane opens inline at any width, focused and as tall | |
| 24 | +as its content (the open's `rows`), in the built-in dialog's shape: the | |
| 25 | +title, the count, five file rows at a time between `more files` rows that | |
| 26 | +page them, the key hints; the arrows walk the rows, Enter shows that | |
| 27 | +file's hunks alone, | |
| 25 | 28 | Escape backs out to the list and then closes, leaving `Diff dialog |
| 26 | 29 | dismissed`; toasts are held while it is up. A file's ask button arms that |
| 27 | 30 | file: its hunks ride the next prompt as context, once. |
| @@ -48,7 +51,7 @@ | ||
| 48 | 51 | | `ui.render` of `Pane` | Draws the pane: docked, the header, base line, source picker, file list and toggles over the window of hunks; inline, the dialog. | |
| 49 | 52 | | `command.run` of `diff` | Opens or closes the pane (focused and closing on Escape without the fullscreen layout), says which, and remembers the choice. | |
| 50 | 53 | | `ui.close` of the pane | Backs out of the dialog's detail view instead of closing; else remembers the person's close as `/diff`'s. | |
| 51 | -| `ui.scroll` of the pane | Docked, moves the hunks under the pinned header and list (three rows a wheel tick, a page a page key) and keeps the engine's window still. | | |
| 54 | +| `ui.scroll` of the pane | Docked, moves the hunks under the pinned header and list (three rows a wheel tick, a page a page key), or the list when the wheel is over it, and keeps the engine's window still. | | |
| 52 | 55 | | `command.run` of `clear`, `resume` | Closes the pane and forgets the session's state. | |
| 53 | 56 | | `tool.call` of `Edit`, `Write`, `NotebookEdit` | After the edit, refreshes an open pane; the session's first successful edit opens it. | |
| 54 | 57 | | `tool.call` of `Bash`, `PowerShell` | After the command, refreshes an open pane. | |
| @@ -24,6 +24,7 @@ | ||
| 24 | 24 | export * from './starts-session.js' |
| 25 | 25 | export * from './text-of.js' |
| 26 | 26 | export * from './two-files.js' |
| 27 | +export * from './wheel-over-list.js' | |
| 27 | 28 | export * from './wheel-tick.js' |
| 28 | 29 | |
| 29 | 30 | export * as default from '.' |
| @@ -1,0 +1,12 @@ | ||
| 1 | +import type { UiScrollInput } from 'claude-code' | |
| 2 | + | |
| 3 | +import { WHEEL_TICK } from './wheel-tick.js' | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * The same wheel tick with the pointer over the docked list's third row | |
| 7 | + * (top pad, header, the list's margin, then the rows). | |
| 8 | + */ | |
| 9 | +export const WHEEL_OVER_LIST: UiScrollInput = { | |
| 10 | + ...WHEEL_TICK, | |
| 11 | + pointer: { column: 10, row: 5 }, | |
| 12 | +} |
| @@ -148,15 +148,14 @@ | ||
| 148 | 148 | |
| 149 | 149 | expect(await $.command.run(Fixtures.DIALOG_DIFF)).toEqual({}) |
| 150 | 150 | |
| 151 | - expect(world.opened).toEqual([ | |
| 152 | - { | |
| 153 | - id: 'diff', | |
| 154 | - title: 'Diff', | |
| 155 | - holdToasts: true, | |
| 156 | - focus: true, | |
| 157 | - closeOnEscape: true, | |
| 158 | - }, | |
| 159 | - ]) | |
| 151 | + expect(world.opened[0]).toEqual({ | |
| 152 | + id: 'diff', | |
| 153 | + title: 'Diff', | |
| 154 | + holdToasts: true, | |
| 155 | + closeOnEscape: true, | |
| 156 | + rows: expect.any(Number), | |
| 157 | + focus: true, | |
| 158 | + }) | |
| 160 | 159 | |
| 161 | 160 | expect(await $.command.run(Fixtures.DIALOG_DIFF)).toEqual({ |
| 162 | 161 | text: 'Diff dialog dismissed', |
| @@ -66,8 +66,26 @@ | ||
| 66 | 66 | |
| 67 | 67 | expect(after).toContain('-const v = 0') |
| 68 | 68 | expect(after).toContain('10 files changed +10 -10') |
| 69 | + expect(after, 'the list stays').not.toContain('more above') | |
| 69 | 70 | }) |
| 70 | 71 | |
| 72 | + test('docked, the wheel over a long list moves the list', async ($, on) => { | |
| 73 | + const world = Fixtures.inRepository(on, Fixtures.MANY_FILES) | |
| 74 | + | |
| 75 | + await $.session.start(Fixtures.SESSION) | |
| 76 | + await $.command.run(Fixtures.DIFF) | |
| 77 | + await world.clock.advance(Fixtures.SETTLE_MS) | |
| 78 | + await $.ui.render(Fixtures.PANE) | |
| 79 | + | |
| 80 | + expect(await $.ui.scroll(Fixtures.WHEEL_OVER_LIST)).toEqual({}) | |
| 81 | + | |
| 82 | + const after = Fixtures.textOf(await $.ui.render(Fixtures.PANE)) | |
| 83 | + | |
| 84 | + expect(after).toContain('\u2191 1 more above') | |
| 85 | + expect(after).toContain('file8.ts') | |
| 86 | + expect(after.split('file0.ts'), 'the body stays').toHaveLength(2) | |
| 87 | + }) | |
| 88 | + | |
| 71 | 89 | test('past eight files the docked list scrolls by key', async ($, on) => { |
| 72 | 90 | const world = Fixtures.inRepository(on, Fixtures.MANY_FILES) |
| 73 | 91 | |
| @@ -129,6 +147,14 @@ | ||
| 129 | 147 | expect(drawn).toContain( |
| 130 | 148 | '\u2191/\u2193 to select \u00b7 Enter to view \u00b7 Esc to close', |
| 131 | 149 | ) |
| 150 | + | |
| 151 | + expect(world.opened.at(-1), 'sized to its rows once listed').toEqual({ | |
| 152 | + id: 'diff', | |
| 153 | + title: 'Diff', | |
| 154 | + holdToasts: true, | |
| 155 | + closeOnEscape: true, | |
| 156 | + rows: 8, | |
| 157 | + }) | |
| 132 | 158 | }) |
| 133 | 159 | |
| 134 | 160 | test('narrow under the fullscreen layout, the resize line', async ($, on) => { |