@jmoyers/harness 0.1.10 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -35
- package/package.json +31 -11
- package/packages/harness-ai/src/anthropic-protocol.ts +68 -68
- package/packages/harness-ai/src/stream-text.ts +13 -91
- package/packages/harness-ui/src/frame-primitives.ts +158 -0
- package/packages/harness-ui/src/index.ts +18 -0
- package/packages/harness-ui/src/interaction/conversation-input-forwarder.ts +221 -0
- package/packages/harness-ui/src/interaction/conversation-selection-input.ts +213 -0
- package/packages/harness-ui/src/interaction/global-shortcut-input.ts +172 -0
- package/{src/ui → packages/harness-ui/src/interaction}/input-preflight.ts +10 -12
- package/{src/ui → packages/harness-ui/src/interaction}/input-token-router.ts +120 -69
- package/packages/harness-ui/src/interaction/input.ts +420 -0
- package/packages/harness-ui/src/interaction/left-nav-input.ts +166 -0
- package/{src/ui → packages/harness-ui/src/interaction}/main-pane-pointer-input.ts +91 -23
- package/{src/ui → packages/harness-ui/src/interaction}/pointer-routing-input.ts +112 -48
- package/packages/harness-ui/src/interaction/rail-pointer-input.ts +62 -0
- package/packages/harness-ui/src/interaction/repository-fold-input.ts +118 -0
- package/packages/harness-ui/src/kit.ts +476 -0
- package/packages/harness-ui/src/layout.ts +238 -0
- package/{src/ui/modals/manager.ts → packages/harness-ui/src/modal-manager.ts} +94 -64
- package/{src/ui → packages/harness-ui/src}/screen.ts +53 -26
- package/packages/harness-ui/src/surface.ts +252 -0
- package/packages/harness-ui/src/text-layout.ts +210 -0
- package/packages/nim-core/src/contracts.ts +239 -0
- package/packages/nim-core/src/event-store.ts +299 -0
- package/packages/nim-core/src/events.ts +53 -0
- package/packages/nim-core/src/index.ts +9 -0
- package/packages/nim-core/src/provider-router.ts +129 -0
- package/packages/nim-core/src/providers/anthropic-driver.ts +291 -0
- package/packages/nim-core/src/runtime-factory.ts +49 -0
- package/packages/nim-core/src/runtime.ts +1797 -0
- package/packages/nim-core/src/session-store.ts +516 -0
- package/packages/nim-core/src/telemetry.ts +48 -0
- package/packages/nim-test-tui/src/index.ts +150 -0
- package/packages/nim-ui-core/src/index.ts +1 -0
- package/packages/nim-ui-core/src/projection.ts +87 -0
- package/scripts/codex-live-mux-runtime.ts +2 -3721
- package/scripts/control-plane-daemon.ts +24 -2
- package/scripts/harness-bin.js +5 -0
- package/scripts/harness-commands.ts +300 -0
- package/scripts/harness-runtime.ts +82 -0
- package/scripts/harness.ts +33 -3007
- package/scripts/nim-tui-smoke.ts +748 -0
- package/src/cli/auth/runtime.ts +948 -0
- package/src/cli/default-gateway-pointer.ts +193 -0
- package/src/cli/gateway/runtime.ts +1872 -0
- package/src/cli/parsing/flags.ts +23 -0
- package/src/cli/parsing/session.ts +42 -0
- package/src/cli/runtime/context.ts +193 -0
- package/src/cli/runtime-app/application.ts +392 -0
- package/src/cli/runtime-infra/gateway-control.ts +729 -0
- package/{scripts/harness-inspector.ts → src/cli/workflows/inspector.ts} +14 -11
- package/src/cli/workflows/runtime.ts +965 -0
- package/src/clients/tui/left-rail-interactions.ts +519 -0
- package/src/clients/tui/main-pane-interactions.ts +509 -0
- package/src/clients/tui/modal-input-routing.ts +71 -0
- package/src/clients/tui/render-snapshot-adapter.ts +88 -0
- package/src/clients/web/synced-selectors.ts +132 -0
- package/src/codex/live-session.ts +82 -29
- package/src/config/config-core.ts +361 -10
- package/src/config/harness-paths.ts +4 -7
- package/src/config/harness-runtime-migration.ts +142 -19
- package/src/config/harness.config.template.jsonc +33 -0
- package/src/config/secrets-core.ts +92 -4
- package/src/control-plane/agent-realtime-api.ts +82 -427
- package/src/control-plane/prompt/thread-title-namer.ts +49 -23
- package/src/control-plane/session-summary.ts +10 -81
- package/src/control-plane/status/reducer-base.ts +12 -12
- package/src/control-plane/status/reducers/claude-status-reducer.ts +3 -3
- package/src/control-plane/status/reducers/codex-status-reducer.ts +4 -4
- package/src/control-plane/status/reducers/cursor-status-reducer.ts +3 -3
- package/src/control-plane/stream-client.ts +12 -2
- package/src/control-plane/stream-command-parser.ts +83 -143
- package/src/control-plane/stream-protocol.ts +53 -37
- package/src/control-plane/stream-server-background.ts +18 -2
- package/src/control-plane/stream-server-command.ts +376 -69
- package/src/control-plane/stream-server-session-runtime.ts +3 -2
- package/src/control-plane/stream-server.ts +943 -80
- package/src/control-plane/stream-session-runtime-types.ts +41 -0
- package/src/{mux/live-mux/control-plane-records.ts → core/contracts/records.ts} +24 -97
- package/src/core/state/observed-stream-cursor.ts +43 -0
- package/src/core/state/synced-observed-state.ts +273 -0
- package/src/core/store/harness-synced-store.ts +81 -0
- package/src/diff/budget.ts +136 -0
- package/src/diff/build.ts +289 -0
- package/src/diff/chunker.ts +146 -0
- package/src/diff/git-invoke.ts +315 -0
- package/src/diff/git-parse.ts +472 -0
- package/src/diff/hash.ts +70 -0
- package/src/diff/index.ts +24 -0
- package/src/diff/normalize.ts +134 -0
- package/src/diff/types.ts +178 -0
- package/src/diff-ui/args.ts +346 -0
- package/src/diff-ui/commands.ts +123 -0
- package/src/diff-ui/finder.ts +94 -0
- package/src/diff-ui/highlight.ts +127 -0
- package/src/diff-ui/index.ts +2 -0
- package/src/diff-ui/model.ts +141 -0
- package/src/diff-ui/pager.ts +412 -0
- package/src/diff-ui/render.ts +337 -0
- package/src/diff-ui/runtime.ts +379 -0
- package/src/diff-ui/state.ts +224 -0
- package/src/diff-ui/types.ts +236 -0
- package/src/domain/conversations.ts +11 -7
- package/src/domain/workspace.ts +76 -4
- package/src/mux/control-plane-op-queue.ts +93 -7
- package/src/mux/conversation-rail.ts +28 -71
- package/src/mux/dual-pane-core.ts +13 -13
- package/src/mux/harness-core-ui.ts +313 -42
- package/src/mux/input-shortcuts.ts +22 -112
- package/src/mux/keybinding-catalog.ts +340 -0
- package/src/mux/keybinding-registry.ts +103 -0
- package/src/mux/live-mux/command-menu-open-in.ts +280 -0
- package/src/mux/live-mux/command-menu.ts +167 -4
- package/src/mux/live-mux/conversation-state.ts +13 -0
- package/src/mux/live-mux/directory-resolution.ts +1 -1
- package/src/mux/live-mux/git-parsing.ts +16 -0
- package/src/mux/live-mux/git-snapshot.ts +33 -2
- package/src/mux/live-mux/global-shortcut-handlers.ts +6 -0
- package/src/mux/live-mux/home-pane-drop.ts +1 -1
- package/src/mux/live-mux/home-pane-pointer.ts +10 -0
- package/src/mux/live-mux/input-forwarding.ts +59 -2
- package/src/mux/live-mux/left-nav-activation.ts +124 -7
- package/src/mux/live-mux/left-nav.ts +35 -0
- package/src/mux/live-mux/link-click.ts +292 -0
- package/src/mux/live-mux/modal-command-menu-handler.ts +46 -9
- package/src/mux/live-mux/modal-conversation-handlers.ts +5 -1
- package/src/mux/live-mux/modal-input-reducers.ts +106 -8
- package/src/mux/live-mux/modal-overlays.ts +210 -31
- package/src/mux/live-mux/modal-pointer.ts +3 -7
- package/src/mux/live-mux/modal-prompt-handlers.ts +107 -1
- package/src/mux/live-mux/modal-release-notes-handler.ts +111 -0
- package/src/mux/live-mux/modal-task-editor-handler.ts +16 -11
- package/src/mux/live-mux/pointer-routing.ts +5 -2
- package/src/mux/live-mux/project-pane-pointer.ts +8 -0
- package/src/mux/live-mux/rail-layout.ts +33 -30
- package/src/mux/live-mux/release-notes.ts +383 -0
- package/src/mux/live-mux/render-trace-analysis.ts +52 -7
- package/src/mux/live-mux/repository-folding.ts +3 -0
- package/src/mux/live-mux/selection.ts +0 -4
- package/src/mux/live-mux/session-diagnostics-paths.ts +21 -0
- package/src/mux/project-pane-github-review.ts +271 -0
- package/src/mux/render-frame.ts +4 -0
- package/src/mux/runtime-app/codex-live-mux-runtime.ts +5191 -0
- package/src/mux/task-composer.ts +21 -14
- package/src/mux/task-focused-pane.ts +118 -117
- package/src/mux/task-screen-keybindings.ts +19 -82
- package/src/mux/workspace-rail-model.ts +270 -104
- package/src/mux/workspace-rail.ts +45 -22
- package/src/pty/session-broker.ts +1 -1
- package/{scripts → src/recording}/terminal-recording-gif-lib.ts +2 -2
- package/src/services/control-plane.ts +50 -32
- package/src/services/conversation-lifecycle.ts +118 -87
- package/src/services/conversation-startup-hydration.ts +20 -12
- package/src/services/directory-hydration.ts +21 -16
- package/src/services/event-persistence.ts +7 -0
- package/src/services/left-rail-pointer-handler.ts +329 -0
- package/src/services/mux-ui-state-persistence.ts +5 -1
- package/src/services/recording.ts +34 -26
- package/src/services/runtime-command-menu-agent-tools.ts +1 -1
- package/src/services/runtime-control-actions.ts +79 -61
- package/src/services/runtime-control-plane-ops.ts +122 -83
- package/src/services/runtime-conversation-actions.ts +40 -26
- package/src/services/runtime-conversation-activation.ts +82 -30
- package/src/services/runtime-conversation-starter.ts +80 -48
- package/src/services/runtime-conversation-title-edit.ts +91 -80
- package/src/services/runtime-envelope-handler.ts +107 -105
- package/src/services/runtime-git-state.ts +42 -29
- package/src/services/runtime-layout-resize.ts +3 -1
- package/src/services/runtime-left-rail-render.ts +99 -63
- package/src/services/runtime-nim-cli-session.ts +438 -0
- package/src/services/runtime-nim-session.ts +705 -0
- package/src/services/runtime-nim-tool-bridge.ts +141 -0
- package/src/services/runtime-observed-event-projection-pipeline.ts +45 -0
- package/src/services/runtime-process-wiring.ts +29 -36
- package/src/services/runtime-project-pane-github-review-cache.ts +164 -0
- package/src/services/runtime-render-flush.ts +63 -70
- package/src/services/runtime-render-lifecycle.ts +65 -64
- package/src/services/runtime-render-orchestrator.ts +55 -45
- package/src/services/runtime-render-pipeline.ts +106 -103
- package/src/services/runtime-render-state.ts +62 -49
- package/src/services/runtime-repository-actions.ts +97 -70
- package/src/services/runtime-right-pane-render.ts +80 -53
- package/src/services/runtime-shutdown.ts +38 -35
- package/src/services/runtime-stream-subscriptions.ts +35 -27
- package/src/services/runtime-task-composer-persistence.ts +71 -59
- package/src/services/runtime-task-composer-snapshot.ts +14 -0
- package/src/services/runtime-task-editor-actions.ts +46 -29
- package/src/services/runtime-task-pane-actions.ts +220 -134
- package/src/services/runtime-task-pane-shortcuts.ts +323 -123
- package/src/services/runtime-workspace-observed-effect-queue.ts +25 -0
- package/src/services/runtime-workspace-observed-events.ts +33 -184
- package/src/services/runtime-workspace-observed-transition-policy.ts +228 -0
- package/src/services/session-diagnostics-store.ts +217 -0
- package/src/services/startup-background-resume.ts +26 -21
- package/src/services/startup-orchestrator.ts +16 -13
- package/src/services/startup-paint-tracker.ts +29 -21
- package/src/services/startup-persisted-conversation-queue.ts +19 -13
- package/src/services/startup-settled-gate.ts +25 -15
- package/src/services/startup-shutdown.ts +18 -22
- package/src/services/startup-state-hydration.ts +44 -34
- package/src/services/startup-visibility.ts +12 -4
- package/src/services/task-pane-selection-actions.ts +89 -72
- package/src/services/task-planning-hydration.ts +24 -18
- package/src/services/task-planning-observed-events.ts +50 -52
- package/src/services/workspace-observed-events.ts +66 -63
- package/src/storage/storage-lifecycle-core.ts +438 -0
- package/src/store/control-plane-store-normalize.ts +33 -242
- package/src/store/control-plane-store-types.ts +1 -35
- package/src/store/control-plane-store.ts +396 -56
- package/src/store/event-store.ts +397 -3
- package/src/terminal/snapshot-oracle.ts +207 -94
- package/src/ui/mux-theme.ts +112 -8
- package/src/ui/panes/home-gridfire.ts +40 -31
- package/src/ui/panes/home.ts +10 -2
- package/src/ui/panes/nim.ts +315 -0
- package/src/mux/live-mux/actions-task.ts +0 -115
- package/src/mux/live-mux/left-rail-actions.ts +0 -118
- package/src/mux/live-mux/left-rail-conversation-click.ts +0 -82
- package/src/mux/live-mux/left-rail-pointer.ts +0 -74
- package/src/mux/live-mux/task-pane-shortcuts.ts +0 -206
- package/src/services/runtime-directory-actions.ts +0 -164
- package/src/services/runtime-input-pipeline.ts +0 -50
- package/src/services/runtime-input-router.ts +0 -189
- package/src/services/runtime-main-pane-input.ts +0 -230
- package/src/services/runtime-modal-input.ts +0 -119
- package/src/services/runtime-navigation-input.ts +0 -197
- package/src/services/runtime-rail-input.ts +0 -278
- package/src/services/runtime-task-pane.ts +0 -62
- package/src/services/runtime-workspace-actions.ts +0 -158
- package/src/ui/conversation-input-forwarder.ts +0 -114
- package/src/ui/conversation-selection-input.ts +0 -103
- package/src/ui/global-shortcut-input.ts +0 -89
- package/src/ui/input.ts +0 -238
- package/src/ui/kit.ts +0 -509
- package/src/ui/left-nav-input.ts +0 -80
- package/src/ui/left-rail-pointer-input.ts +0 -148
- package/src/ui/repository-fold-input.ts +0 -91
- package/src/ui/surface.ts +0 -224
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { DiffUiFinderResult, DiffUiModel } from './types.ts';
|
|
2
|
+
|
|
3
|
+
function filePathFromModel(model: DiffUiModel, fileIndex: number): string {
|
|
4
|
+
const file = model.diff.files[fileIndex]!;
|
|
5
|
+
return file.newPath ?? file.oldPath ?? file.fileId;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function scoreFinderPath(query: string, path: string): number {
|
|
9
|
+
const trimmedQuery = query.trim().toLowerCase();
|
|
10
|
+
if (trimmedQuery.length === 0) {
|
|
11
|
+
return 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const candidate = path.toLowerCase();
|
|
15
|
+
let queryIndex = 0;
|
|
16
|
+
let score = 0;
|
|
17
|
+
let streak = 0;
|
|
18
|
+
let firstMatchIndex = -1;
|
|
19
|
+
|
|
20
|
+
for (let candidateIndex = 0; candidateIndex < candidate.length; candidateIndex += 1) {
|
|
21
|
+
if (queryIndex >= trimmedQuery.length) {
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const current = candidate[candidateIndex]!;
|
|
26
|
+
if (current !== trimmedQuery[queryIndex]) {
|
|
27
|
+
streak = 0;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (firstMatchIndex < 0) {
|
|
32
|
+
firstMatchIndex = candidateIndex;
|
|
33
|
+
}
|
|
34
|
+
streak += 1;
|
|
35
|
+
score += 10 + streak * 4;
|
|
36
|
+
|
|
37
|
+
const prev = candidateIndex > 0 ? candidate[candidateIndex - 1] : '/';
|
|
38
|
+
if (prev === '/' || prev === '-' || prev === '_' || prev === '.') {
|
|
39
|
+
score += 6;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
queryIndex += 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (queryIndex !== trimmedQuery.length) {
|
|
46
|
+
return Number.NEGATIVE_INFINITY;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (firstMatchIndex === 0) {
|
|
50
|
+
score += 12;
|
|
51
|
+
} else if (firstMatchIndex > 0) {
|
|
52
|
+
score += Math.max(0, 8 - firstMatchIndex);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
score -= Math.floor(candidate.length / 8);
|
|
56
|
+
return score;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function buildFinderResults(
|
|
60
|
+
model: DiffUiModel,
|
|
61
|
+
query: string,
|
|
62
|
+
maxResults = 200,
|
|
63
|
+
): readonly DiffUiFinderResult[] {
|
|
64
|
+
const trimmedQuery = query.trim();
|
|
65
|
+
const results: DiffUiFinderResult[] = [];
|
|
66
|
+
|
|
67
|
+
for (let fileIndex = 0; fileIndex < model.diff.files.length; fileIndex += 1) {
|
|
68
|
+
const file = model.diff.files[fileIndex]!;
|
|
69
|
+
const path = filePathFromModel(model, fileIndex);
|
|
70
|
+
const score = trimmedQuery.length === 0 ? 0 : scoreFinderPath(trimmedQuery, path);
|
|
71
|
+
if (score === Number.NEGATIVE_INFINITY) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
results.push({
|
|
75
|
+
fileIndex,
|
|
76
|
+
fileId: file.fileId,
|
|
77
|
+
path,
|
|
78
|
+
score,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
results.sort((left, right) => {
|
|
83
|
+
if (left.score !== right.score) {
|
|
84
|
+
return right.score - left.score;
|
|
85
|
+
}
|
|
86
|
+
const pathCompare = left.path.localeCompare(right.path);
|
|
87
|
+
if (pathCompare !== 0) {
|
|
88
|
+
return pathCompare;
|
|
89
|
+
}
|
|
90
|
+
return left.fileIndex - right.fileIndex;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return results.slice(0, Math.max(1, Math.floor(maxResults)));
|
|
94
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { DiffUiRenderTheme } from './types.ts';
|
|
2
|
+
|
|
3
|
+
type DiffUiSyntaxTokenRole = 'keyword' | 'string' | 'comment' | 'number';
|
|
4
|
+
|
|
5
|
+
interface DiffUiSyntaxToken {
|
|
6
|
+
readonly start: number;
|
|
7
|
+
readonly end: number;
|
|
8
|
+
readonly role: DiffUiSyntaxTokenRole;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const JAVASCRIPT_LIKE_LANGUAGES = new Set(['javascript', 'typescript', 'tsx', 'jsx', 'mjs', 'cjs']);
|
|
12
|
+
|
|
13
|
+
const KEYWORD_PATTERN =
|
|
14
|
+
/\b(async|await|break|case|catch|class|const|continue|default|else|export|extends|finally|for|function|if|import|interface|let|new|return|switch|throw|try|type|var|while)\b/gu;
|
|
15
|
+
const NUMBER_PATTERN = /\b\d+(?:\.\d+)?\b/gu;
|
|
16
|
+
const STRING_PATTERN = /'(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*"|`(?:\\.|[^`\\])*`/gu;
|
|
17
|
+
const COMMENT_PATTERN = /\/\/.*$/u;
|
|
18
|
+
|
|
19
|
+
function hasOverlap(tokens: readonly DiffUiSyntaxToken[], start: number, end: number): boolean {
|
|
20
|
+
for (const token of tokens) {
|
|
21
|
+
if (start < token.end && end > token.start) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function collectRegexMatches(
|
|
29
|
+
line: string,
|
|
30
|
+
regex: RegExp,
|
|
31
|
+
role: DiffUiSyntaxTokenRole,
|
|
32
|
+
tokens: DiffUiSyntaxToken[],
|
|
33
|
+
): void {
|
|
34
|
+
const cloned = new RegExp(regex.source, regex.flags);
|
|
35
|
+
let match = cloned.exec(line);
|
|
36
|
+
while (match !== null) {
|
|
37
|
+
const matched = match[0] ?? '';
|
|
38
|
+
const start = match.index;
|
|
39
|
+
const end = start + matched.length;
|
|
40
|
+
if (matched.length > 0 && !hasOverlap(tokens, start, end)) {
|
|
41
|
+
tokens.push({
|
|
42
|
+
start,
|
|
43
|
+
end,
|
|
44
|
+
role,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
match = cloned.exec(line);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isSyntaxLanguage(language: string | null): boolean {
|
|
52
|
+
if (language === null) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return JAVASCRIPT_LIKE_LANGUAGES.has(language.toLowerCase());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function tokenizeDiffUiSyntaxLine(
|
|
59
|
+
line: string,
|
|
60
|
+
language: string | null,
|
|
61
|
+
): readonly DiffUiSyntaxToken[] {
|
|
62
|
+
if (!isSyntaxLanguage(language)) {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const tokens: DiffUiSyntaxToken[] = [];
|
|
67
|
+
|
|
68
|
+
const commentMatch = line.match(COMMENT_PATTERN);
|
|
69
|
+
if (commentMatch !== null && commentMatch.index !== undefined) {
|
|
70
|
+
tokens.push({
|
|
71
|
+
start: commentMatch.index,
|
|
72
|
+
end: line.length,
|
|
73
|
+
role: 'comment',
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
collectRegexMatches(line, STRING_PATTERN, 'string', tokens);
|
|
78
|
+
collectRegexMatches(line, KEYWORD_PATTERN, 'keyword', tokens);
|
|
79
|
+
collectRegexMatches(line, NUMBER_PATTERN, 'number', tokens);
|
|
80
|
+
|
|
81
|
+
tokens.sort((left, right) => left.start - right.start || left.end - right.end);
|
|
82
|
+
return tokens;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function ansiForRole(role: DiffUiSyntaxTokenRole, theme: DiffUiRenderTheme): string {
|
|
86
|
+
if (role === 'keyword') {
|
|
87
|
+
return theme.syntaxKeywordAnsi;
|
|
88
|
+
}
|
|
89
|
+
if (role === 'string') {
|
|
90
|
+
return theme.syntaxStringAnsi;
|
|
91
|
+
}
|
|
92
|
+
if (role === 'comment') {
|
|
93
|
+
return theme.syntaxCommentAnsi;
|
|
94
|
+
}
|
|
95
|
+
return theme.syntaxNumberAnsi;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function renderSyntaxAnsiLine(input: {
|
|
99
|
+
readonly line: string;
|
|
100
|
+
readonly language: string | null;
|
|
101
|
+
readonly theme: DiffUiRenderTheme;
|
|
102
|
+
readonly colorEnabled: boolean;
|
|
103
|
+
readonly baseAnsi: string;
|
|
104
|
+
}): string {
|
|
105
|
+
if (!input.colorEnabled) {
|
|
106
|
+
return input.line;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const tokens = tokenizeDiffUiSyntaxLine(input.line, input.language);
|
|
110
|
+
if (tokens.length === 0) {
|
|
111
|
+
return input.line;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let output = '';
|
|
115
|
+
let cursor = 0;
|
|
116
|
+
for (const token of tokens) {
|
|
117
|
+
if (token.start > cursor) {
|
|
118
|
+
output += input.line.slice(cursor, token.start);
|
|
119
|
+
}
|
|
120
|
+
output += `${ansiForRole(token.role, input.theme)}${input.line.slice(token.start, token.end)}${input.baseAnsi}`;
|
|
121
|
+
cursor = token.end;
|
|
122
|
+
}
|
|
123
|
+
if (cursor < input.line.length) {
|
|
124
|
+
output += input.line.slice(cursor);
|
|
125
|
+
}
|
|
126
|
+
return output;
|
|
127
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { DiffFile, DiffHunk, DiffLine, NormalizedDiff } from '../diff/types.ts';
|
|
2
|
+
import type { DiffUiModel, DiffUiRowKind, DiffUiVirtualRow } from './types.ts';
|
|
3
|
+
|
|
4
|
+
function filePath(file: DiffFile): string {
|
|
5
|
+
return file.newPath ?? file.oldPath ?? file.fileId;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function lineKindToRowKind(line: DiffLine): DiffUiRowKind {
|
|
9
|
+
if (line.kind === 'add') {
|
|
10
|
+
return 'code-add';
|
|
11
|
+
}
|
|
12
|
+
if (line.kind === 'del') {
|
|
13
|
+
return 'code-del';
|
|
14
|
+
}
|
|
15
|
+
return 'code-context';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function unifiedPrefix(kind: DiffLine['kind']): string {
|
|
19
|
+
if (kind === 'add') {
|
|
20
|
+
return '+';
|
|
21
|
+
}
|
|
22
|
+
if (kind === 'del') {
|
|
23
|
+
return '-';
|
|
24
|
+
}
|
|
25
|
+
return ' ';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function buildCodeRow(input: {
|
|
29
|
+
readonly file: DiffFile;
|
|
30
|
+
readonly fileIndex: number;
|
|
31
|
+
readonly hunk: DiffHunk;
|
|
32
|
+
readonly hunkIndex: number;
|
|
33
|
+
readonly line: DiffLine;
|
|
34
|
+
}): DiffUiVirtualRow {
|
|
35
|
+
const rowKind = lineKindToRowKind(input.line);
|
|
36
|
+
const prefix = unifiedPrefix(input.line.kind);
|
|
37
|
+
const unified = `${prefix} ${input.line.text}`;
|
|
38
|
+
const left = input.line.kind === 'add' ? '' : input.line.text;
|
|
39
|
+
const right = input.line.kind === 'del' ? '' : input.line.text;
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
kind: rowKind,
|
|
43
|
+
unified,
|
|
44
|
+
left,
|
|
45
|
+
right,
|
|
46
|
+
fileId: input.file.fileId,
|
|
47
|
+
hunkId: input.hunk.hunkId,
|
|
48
|
+
fileIndex: input.fileIndex,
|
|
49
|
+
hunkIndex: input.hunkIndex,
|
|
50
|
+
language: input.file.language,
|
|
51
|
+
oldLine: input.line.oldLine,
|
|
52
|
+
newLine: input.line.newLine,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function buildDiffUiModel(diff: NormalizedDiff): DiffUiModel {
|
|
57
|
+
const rows: DiffUiVirtualRow[] = [];
|
|
58
|
+
const fileStartRows: number[] = [];
|
|
59
|
+
const hunkStartRows: number[] = [];
|
|
60
|
+
|
|
61
|
+
let globalHunkIndex = 0;
|
|
62
|
+
|
|
63
|
+
for (let fileIndex = 0; fileIndex < diff.files.length; fileIndex += 1) {
|
|
64
|
+
const file = diff.files[fileIndex]!;
|
|
65
|
+
fileStartRows.push(rows.length);
|
|
66
|
+
|
|
67
|
+
const path = filePath(file);
|
|
68
|
+
rows.push({
|
|
69
|
+
kind: 'file-header',
|
|
70
|
+
unified: `File ${fileIndex + 1}/${diff.files.length}: ${path} (+${file.additions} -${file.deletions})`,
|
|
71
|
+
left: path,
|
|
72
|
+
right: `+${file.additions} -${file.deletions}`,
|
|
73
|
+
fileId: file.fileId,
|
|
74
|
+
hunkId: null,
|
|
75
|
+
fileIndex,
|
|
76
|
+
hunkIndex: null,
|
|
77
|
+
language: file.language,
|
|
78
|
+
oldLine: null,
|
|
79
|
+
newLine: null,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
for (const hunk of file.hunks) {
|
|
83
|
+
hunkStartRows.push(rows.length);
|
|
84
|
+
const hunkIndex = globalHunkIndex;
|
|
85
|
+
globalHunkIndex += 1;
|
|
86
|
+
rows.push({
|
|
87
|
+
kind: 'hunk-header',
|
|
88
|
+
unified: hunk.header,
|
|
89
|
+
left: hunk.header,
|
|
90
|
+
right: hunk.header,
|
|
91
|
+
fileId: file.fileId,
|
|
92
|
+
hunkId: hunk.hunkId,
|
|
93
|
+
fileIndex,
|
|
94
|
+
hunkIndex,
|
|
95
|
+
language: file.language,
|
|
96
|
+
oldLine: hunk.oldStart,
|
|
97
|
+
newLine: hunk.newStart,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
for (const line of hunk.lines) {
|
|
101
|
+
rows.push(
|
|
102
|
+
buildCodeRow({
|
|
103
|
+
file,
|
|
104
|
+
fileIndex,
|
|
105
|
+
hunk,
|
|
106
|
+
hunkIndex,
|
|
107
|
+
line,
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (diff.coverage.truncated) {
|
|
115
|
+
rows.push({
|
|
116
|
+
kind: 'notice',
|
|
117
|
+
unified: `coverage truncated: reason=${diff.coverage.reason} skipped=${diff.coverage.skippedFiles} truncated=${diff.coverage.truncatedFiles}`,
|
|
118
|
+
left: '',
|
|
119
|
+
right: '',
|
|
120
|
+
fileId: null,
|
|
121
|
+
hunkId: null,
|
|
122
|
+
fileIndex: null,
|
|
123
|
+
hunkIndex: null,
|
|
124
|
+
language: null,
|
|
125
|
+
oldLine: null,
|
|
126
|
+
newLine: null,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
diff,
|
|
132
|
+
rows,
|
|
133
|
+
fileStartRows,
|
|
134
|
+
hunkStartRows,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function maxTopRowForModel(model: DiffUiModel, viewportHeight: number): number {
|
|
139
|
+
const bodyRows = Math.max(1, viewportHeight - 2);
|
|
140
|
+
return Math.max(0, model.rows.length - bodyRows);
|
|
141
|
+
}
|