@kabmo-labs/kabmo 0.8.0 → 0.9.0
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 +7 -0
- package/dist/client/index.html +1 -1
- package/dist/client/index.js +314 -314
- package/dist/client/static/js/{index-qwpvd9fv.js → index-bpptfkz2.js} +922 -892
- package/dist/client/static/js/index-k315p1e8.css +1 -0
- package/package.json +9 -1
- package/src/client/components/common/code-surface/documents.test.ts +120 -0
- package/src/client/components/common/code-surface/documents.ts +201 -0
- package/src/client/components/common/code-surface/extensions.test.ts +110 -0
- package/src/client/components/common/code-surface/extensions.ts +162 -0
- package/src/client/components/common/code-surface/handle.ts +102 -0
- package/src/client/components/common/code-surface/index.tsx +253 -314
- package/src/client/components/common/code-surface/keymap.test.ts +133 -0
- package/src/client/components/common/code-surface/keymap.ts +267 -0
- package/src/client/components/common/code-surface/marks.ts +74 -0
- package/src/client/components/common/code-surface/shiki.ts +234 -0
- package/src/client/components/common/code-surface/sync.test.ts +185 -0
- package/src/client/components/common/code-surface/sync.ts +58 -0
- package/src/client/components/common/code-surface/theme.test.ts +202 -0
- package/src/client/components/common/code-surface/theme.ts +111 -0
- package/src/client/components/common/editor-view-buttons/index.test.tsx +148 -0
- package/src/client/components/common/editor-view-buttons/index.tsx +109 -0
- package/src/client/components/common/shell-update-button/Modal.tsx +163 -0
- package/src/client/components/common/shell-update-button/index.test.tsx +251 -0
- package/src/client/components/common/shell-update-button/index.tsx +125 -0
- package/src/client/components/common/toolbar-row/index.test.tsx +166 -0
- package/src/client/components/common/toolbar-row/index.tsx +189 -0
- package/src/client/components/layout/desktop-layout/WorkspacePanels.tsx +41 -78
- package/src/client/components/layout/desktop-layout/index.tsx +47 -67
- package/src/client/components/layout/desktop-layout/resizer/index.tsx +6 -3
- package/src/client/components/layout/session-sidebar/Footer.tsx +8 -15
- package/src/client/components/mobile/LayoutWrapper.tsx +1 -0
- package/src/client/components/mobile/mobile-right-sidebar/FullDiff.tsx +3 -0
- package/src/client/components/mobile/mobile-right-sidebar/FullEditor.tsx +63 -38
- package/src/client/components/mobile/mobile-right-sidebar/OutlineButton.test.tsx +137 -0
- package/src/client/components/mobile/mobile-right-sidebar/OutlineButton.tsx +14 -3
- package/src/client/components/mobile/mobile-session-sidebar/Footer.tsx +9 -21
- package/src/client/components/settings/Modal.tsx +8 -0
- package/src/client/components/settings/categories/AppearanceSettings.tsx +3 -0
- package/src/client/components/settings/categories/appearance-settings/EditorThemeSection.test.tsx +167 -0
- package/src/client/components/settings/categories/appearance-settings/EditorThemeSection.tsx +184 -0
- package/src/client/components/settings/categories/project-settings/WorktreeSettings.tsx +8 -2
- package/src/client/components/workspace/chat-timeline/EmptyWorkspacePrompt.tsx +16 -8
- package/src/client/components/workspace/chat-timeline/SystemNotice.tsx +9 -4
- package/src/client/components/workspace/chat-timeline/TaskResultContent.tsx +9 -7
- package/src/client/components/workspace/chat-timeline/target-picker/WorkspacePicker.test.tsx +132 -0
- package/src/client/components/workspace/chat-timeline/target-picker/WorkspacePicker.tsx +120 -0
- package/src/client/components/workspace/chat-timeline/target-picker/WorktreePicker.test.tsx +319 -0
- package/src/client/components/workspace/chat-timeline/target-picker/WorktreePicker.tsx +285 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/index.test.ts +2 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/hashline-patch/index.tsx +34 -29
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/AstEdit.tsx +11 -9
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Bash.tsx +10 -7
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Edit.tsx +25 -8
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Eval.tsx +8 -11
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/FileMatches.test.ts +114 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/FileMatches.tsx +111 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Read.tsx +7 -7
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/SearchTool.tsx +9 -59
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/ArtifactBody.tsx +2 -7
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/DiffView.test.ts +27 -28
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/DiffView.tsx +39 -26
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/ExcerptCode.tsx +8 -8
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/HighlightedCode.tsx +35 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/JsonCodeBlock.tsx +4 -4
- package/src/client/components/workspace/diff-panel/MergeDiffView.tsx +179 -0
- package/src/client/components/workspace/diff-panel/ThemedMergeDiffView.tsx +24 -0
- package/src/client/components/workspace/diff-panel/Toolbar.tsx +224 -186
- package/src/client/components/workspace/diff-panel/ToolbarSegments.tsx +98 -0
- package/src/client/components/workspace/diff-panel/index.tsx +76 -145
- package/src/client/components/workspace/diff-panel/merge.ts +315 -0
- package/src/client/components/workspace/editor/Body.tsx +4 -7
- package/src/client/components/workspace/editor/CommandPalette.tsx +2 -2
- package/src/client/components/workspace/editor/FileDocument.tsx +58 -63
- package/src/client/components/workspace/editor/FilePane.tsx +4 -7
- package/src/client/components/workspace/editor/FindWidget.tsx +7 -2
- package/src/client/components/workspace/editor/Toolbar.tsx +220 -123
- package/src/client/components/workspace/editor/index.tsx +7 -9
- package/src/client/components/workspace/file-explorer/OutlineSection.test.tsx +18 -0
- package/src/client/components/workspace/file-explorer/OutlineSection.tsx +10 -3
- package/src/client/components/workspace/search-panel/Body.tsx +1 -1
- package/src/client/components/workspace/worktree-modal/index.test.tsx +118 -0
- package/src/client/hooks/chat/omp/reconnect.test.ts +338 -0
- package/src/client/hooks/chat/omp/reconnect.ts +187 -0
- package/src/client/hooks/chat/omp/run-liveness.test.ts +71 -0
- package/src/client/hooks/chat/omp/run-liveness.ts +65 -0
- package/src/client/hooks/chat/omp/session-list.activity.test.ts +114 -0
- package/src/client/hooks/chat/omp/session-list.test-util.ts +164 -0
- package/src/client/hooks/chat/omp/session-list.test.ts +30 -150
- package/src/client/hooks/chat/omp/session-list.tsx +69 -9
- package/src/client/hooks/chat/timeline/generating.ts +19 -2
- package/src/client/hooks/chat/timeline/index.ts +19 -24
- package/src/client/hooks/chat/timeline/lost-run.test.ts +329 -0
- package/src/client/hooks/chat/timeline/lost-run.ts +177 -0
- package/src/client/hooks/chat/timeline/session-load.test-util.ts +205 -0
- package/src/client/hooks/chat/timeline/session-load.test.ts +61 -162
- package/src/client/hooks/chat/timeline/session-load.ts +69 -37
- package/src/client/hooks/chat/timeline/session-switch.test.ts +65 -0
- package/src/client/hooks/chat/timeline/session-switch.ts +63 -0
- package/src/client/hooks/code/deferred-highlight.test.ts +182 -0
- package/src/client/hooks/code/deferred-highlight.ts +129 -0
- package/src/client/hooks/editor/find-state.ts +1 -1
- package/src/client/hooks/editor/theme.test.ts +206 -0
- package/src/client/hooks/editor/theme.ts +82 -0
- package/src/client/hooks/editor/use-editor-drop.test.ts +101 -0
- package/src/client/hooks/editor/use-editor-drop.ts +67 -14
- package/src/client/hooks/editor/use-editor-find.ts +7 -7
- package/src/client/hooks/editor/use-outline-view.test.ts +182 -0
- package/src/client/hooks/editor/use-outline-view.ts +94 -7
- package/src/client/hooks/editor/use-scroll-restore.test.ts +55 -2
- package/src/client/hooks/editor/use-scroll-restore.ts +124 -4
- package/src/client/hooks/editor/use-search-reveal.ts +1 -1
- package/src/client/hooks/ui/settings-modal.ts +56 -0
- package/src/client/hooks/ui/updates/shell-download.test.ts +2 -0
- package/src/client/hooks/ui/updates/shell-download.ts +55 -14
- package/src/client/hooks/ui/updates/shell-offer.test.ts +26 -0
- package/src/client/hooks/ui/updates/shell-update.test.ts +140 -0
- package/src/client/hooks/ui/updates/shell-update.ts +119 -0
- package/src/client/hooks/workspace/diff-file.ts +29 -10
- package/src/client/hooks/workspace/diff-git-actions.ts +147 -0
- package/src/client/hooks/workspace/editor-view.ts +61 -0
- package/src/client/hooks/workspace/file-tabs.test.ts +62 -5
- package/src/client/hooks/workspace/file-tabs.ts +24 -11
- package/src/client/hooks/workspace/navbar-session-actions.ts +72 -0
- package/src/client/hooks/workspace/worktree/index.ts +20 -5
- package/src/client/lib/desktop/bridge.ts +45 -0
- package/src/client/lib/search-reveal.ts +1 -1
- package/src/client/main.tsx +5 -1
- package/src/client/tailwind.css +37 -8
- package/src/server/lib/auth/guard.test.ts +28 -0
- package/src/server/lib/auth/guard.ts +10 -0
- package/src/server/lib/fs/worktree/create.ts +2 -3
- package/src/server/lib/fs/worktree/list.ts +13 -0
- package/src/server/lib/fs/worktree/remove.ts +3 -3
- package/src/server/lib/lifecycle/claim-port.test.ts +156 -0
- package/src/server/lib/lifecycle/port-guard.test.ts +20 -164
- package/src/server/lib/lifecycle/port-guard.ts +39 -7
- package/src/server/lib/lifecycle/probe-health.test.ts +166 -0
- package/src/server/lib/lifecycle/probe.test.ts +77 -164
- package/src/server/lib/lifecycle/probe.ts +121 -13
- package/src/server/lib/omp/rpc/frame-fold-signals.test.ts +112 -0
- package/src/server/lib/omp/rpc/frame-fold.ts +10 -5
- package/src/server/lib/omp/session/sidebar-activity.server.test.ts +162 -0
- package/src/server/lib/omp/session/sidebar-activity.server.ts +70 -0
- package/src/server/lib/omp/session/telemetry/compute.ts +13 -1
- package/src/server/lib/omp/session/telemetry.test.ts +33 -0
- package/src/server/lib/realtime/signals.server.ts +10 -0
- package/src/server/lib/realtime/topics/catalog.server.ts +4 -1
- package/src/server/lib/realtime/topics.server.ts +16 -0
- package/src/server/lib/updates/shell-assets.ts +94 -3
- package/src/server/lib/updates/shell-cache.ts +105 -0
- package/src/server/lib/updates/shell-pages.test.ts +33 -2
- package/src/server/lib/updates/shell-pages.ts +17 -5
- package/src/server/lib/updates/shell-release-desktop.test.ts +166 -0
- package/src/server/lib/updates/shell-release.test.ts +10 -27
- package/src/server/lib/updates/shell-release.ts +107 -111
- package/src/server/routes/fs/git-merge.test.ts +11 -1
- package/src/server/routes/fs/read.test.ts +24 -7
- package/src/server/routes/fs/scope.test.ts +15 -10
- package/src/server/routes/fs/scope.ts +3 -14
- package/src/server/routes/updates/android.ts +6 -1
- package/src/server/routes/updates/desktop.ts +29 -7
- package/src/server/routes/updates/ios.ts +5 -1
- package/src/server/routes/worktrees/list.ts +11 -1
- package/src/server/routes/worktrees/routes.test.ts +17 -0
- package/src/shared/lib/chat/timeline/omp-callbacks.test.ts +36 -0
- package/src/shared/lib/chat/timeline/omp-callbacks.ts +18 -0
- package/src/shared/lib/chat/timeline/run-signals.test.ts +2 -0
- package/src/shared/lib/code/editor/comment-syntax.test.ts +37 -0
- package/src/shared/lib/code/editor/comment-syntax.ts +55 -0
- package/src/shared/lib/code/editor/find-replace.test.ts +134 -0
- package/src/shared/lib/code/editor/{find.ts → find-replace.ts} +23 -107
- package/src/shared/lib/code/editor/{find.test.ts → find-scan.test.ts} +19 -73
- package/src/shared/lib/code/editor/find-scan.ts +177 -0
- package/src/shared/lib/code/editor/keymap.ts +3 -3
- package/src/shared/lib/code/editor/lines.test.ts +7 -55
- package/src/shared/lib/code/editor/lines.ts +8 -48
- package/src/shared/lib/code/editor/marks.test.ts +4 -5
- package/src/shared/lib/code/editor/marks.ts +13 -25
- package/src/shared/lib/code/editor/theme-loader.ts +97 -0
- package/src/shared/lib/code/editor/themes.generated.ts +156 -0
- package/src/shared/lib/code/editor/themes.test.ts +165 -0
- package/src/shared/lib/code/editor/themes.ts +165 -0
- package/src/shared/lib/code/highlight-memo.test.ts +103 -0
- package/src/shared/lib/code/highlight-memo.ts +149 -0
- package/src/shared/lib/code/highlighter-warmup.ts +112 -0
- package/src/shared/lib/code/highlighter.ts +3 -1
- package/src/shared/lib/code/idle.ts +37 -0
- package/src/shared/lib/code/outline/rows.test.ts +96 -0
- package/src/shared/lib/code/outline/rows.ts +94 -0
- package/src/shared/lib/code/syntax-highlight.test.ts +1 -79
- package/src/shared/lib/code/syntax-highlight.ts +2 -3
- package/src/shared/lib/fs/file-kind-classify.test.ts +25 -11
- package/src/shared/lib/fs/file-kind.ts +47 -13
- package/src/shared/lib/fs/file-url.test.ts +52 -0
- package/src/shared/lib/fs/file-url.ts +37 -0
- package/src/shared/lib/fs/patch-sides.test.ts +114 -0
- package/src/shared/lib/fs/patch-sides.ts +139 -0
- package/src/shared/lib/realtime/protocol.ts +16 -0
- package/src/shared/lib/ui/toolbar-overflow.test.ts +83 -0
- package/src/shared/lib/ui/toolbar-overflow.ts +86 -0
- package/src/shared/lib/updates/shell-arch.ts +54 -0
- package/src/shared/lib/updates/shell-ua.ts +4 -4
- package/src/shared/lib/workspace/editor-view-mode.test.ts +70 -0
- package/src/shared/lib/workspace/editor-view-mode.ts +48 -0
- package/src/shared/lib/workspace/panel-widths.test.ts +30 -0
- package/src/shared/lib/workspace/panel-widths.ts +25 -5
- package/src/shared/lib/workspace/session-state/cache.ts +159 -0
- package/src/shared/lib/workspace/session-state/harness.test-util.ts +55 -0
- package/src/shared/lib/workspace/session-state/merge.ts +100 -0
- package/src/shared/lib/workspace/session-state/store-load.test.ts +206 -0
- package/src/shared/lib/workspace/session-state/store.test.ts +17 -139
- package/src/shared/lib/workspace/session-state/store.ts +92 -130
- package/src/shared/lib/workspace/workspace-panel-widths.ts +124 -0
- package/src/shared/types/realtime.ts +29 -0
- package/src/shared/types/settings/state.ts +10 -0
- package/src/test-support/loopback.ts +183 -0
- package/dist/client/static/js/index-gtbpewr7.css +0 -1
- package/src/client/components/common/code-editor/handler.test.ts +0 -274
- package/src/client/components/common/code-editor/handler.ts +0 -175
- package/src/client/components/common/code-editor/index.tsx +0 -314
- package/src/client/components/common/code-surface/measure.ts +0 -148
- package/src/client/components/workspace/chat-timeline/target-picker/index.test.tsx +0 -227
- package/src/client/components/workspace/chat-timeline/target-picker/index.tsx +0 -260
- package/src/client/components/workspace/diff-panel/SplitView.tsx +0 -209
- package/src/client/components/workspace/diff-panel/UnifiedView.tsx +0 -150
- package/src/client/hooks/editor/scroll-container.test.ts +0 -102
- package/src/client/hooks/editor/scroll-container.ts +0 -26
- package/src/client/hooks/editor/use-code-window.ts +0 -100
- package/src/client/hooks/editor/use-editor-history.test.ts +0 -216
- package/src/client/hooks/editor/use-editor-history.ts +0 -134
- package/src/client/hooks/editor/use-occurrences.test.ts +0 -236
- package/src/client/hooks/editor/use-occurrences.ts +0 -98
- package/src/shared/lib/code/editor/commands.test.ts +0 -155
- package/src/shared/lib/code/editor/commands.ts +0 -260
- package/src/shared/lib/code/editor/edits.test.ts +0 -100
- package/src/shared/lib/code/editor/edits.ts +0 -122
- package/src/shared/lib/code/editor/history.test.ts +0 -118
- package/src/shared/lib/code/editor/history.ts +0 -101
- package/src/shared/lib/code/editor/layers.test.ts +0 -77
- package/src/shared/lib/code/editor/layers.ts +0 -67
- package/src/shared/lib/code/editor/line-edits.test.ts +0 -131
- package/src/shared/lib/code/editor/line-edits.ts +0 -159
- package/src/shared/lib/code/editor/occurrences.test.ts +0 -117
- package/src/shared/lib/code/editor/occurrences.ts +0 -111
- package/src/shared/lib/code/editor/reset-css.test.ts +0 -35
- package/src/shared/lib/code/editor/reset-css.ts +0 -32
- package/src/shared/lib/code/editor/text-commands.test.ts +0 -178
- package/src/shared/lib/code/editor/text-commands.ts +0 -154
- package/src/shared/lib/code/lazy-window.test.ts +0 -161
- package/src/shared/lib/code/lazy-window.ts +0 -156
- package/src/shared/lib/code/windowed-highlight.ts +0 -114
- package/src/shared/lib/fs/diff-parser.test.ts +0 -116
- package/src/shared/lib/fs/diff-parser.ts +0 -213
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Line geometry and windowing policy for the lazy editor surface.
|
|
8
|
-
*
|
|
9
|
-
* Shiki tokenization is linear in document size (≈0.14 ms per line), so a
|
|
10
|
-
* whole-file highlight costs ~0.8 s at 7 000 lines — and the editor pays it on
|
|
11
|
-
* every keystroke, on top of one gutter node and one wrapped-row measurement
|
|
12
|
-
* per line. A long document therefore renders only the lines the reader can
|
|
13
|
-
* actually see, and this module is the DOM-free half of that: px offsets per
|
|
14
|
-
* line, the viewport → line-range mapping, and the blank space the unrendered
|
|
15
|
-
* lines take up so the caret never leaves its row.
|
|
16
|
-
*
|
|
17
|
-
* The browser measurements this needs (row height, per-line wrapped heights)
|
|
18
|
-
* stay in the surface, which feeds them in as a `LineGeometry`.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/** Documents at or below this size render whole — windowing them saves less than it costs. */
|
|
22
|
-
export const WHOLE_DOCUMENT_MAX_LINES = 300;
|
|
23
|
-
export const WHOLE_DOCUMENT_MAX_CHARS = 20_000;
|
|
24
|
-
|
|
25
|
-
/** Lines kept beyond the viewport so a fast scroll cannot outrun the window. */
|
|
26
|
-
export const WINDOW_OVERSCAN = 24;
|
|
27
|
-
|
|
28
|
-
/** Lines rendered before the first measurement lands; the surface corrects this in a layout effect. */
|
|
29
|
-
export const INITIAL_WINDOW_LINES = 200;
|
|
30
|
-
|
|
31
|
-
/** Half-open line range `[start, end)`. */
|
|
32
|
-
export interface LineWindow {
|
|
33
|
-
start: number;
|
|
34
|
-
end: number;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** Blank space (px) the unrendered lines occupy above and below a window. */
|
|
38
|
-
export interface WindowSpace {
|
|
39
|
-
top: number;
|
|
40
|
-
bottom: number;
|
|
41
|
-
/** The window reaches the document's last line, so it keeps the editor's trailing line break. */
|
|
42
|
-
breakAtEnd: boolean;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** A windowed render request: the lines to draw plus the space the rest of the document takes. */
|
|
46
|
-
export interface CodeWindow extends LineWindow, WindowSpace {}
|
|
47
|
-
|
|
48
|
-
/** Per-line vertical layout of a document, in px. */
|
|
49
|
-
export interface LineGeometry {
|
|
50
|
-
readonly lineCount: number;
|
|
51
|
-
/** Height (px) of a single visual row. */
|
|
52
|
-
readonly rowHeight: number;
|
|
53
|
-
/** Height (px) of every line together. */
|
|
54
|
-
readonly totalHeight: number;
|
|
55
|
-
/** Y offset (px) of the top edge of line `index`; `lineCount` is the document's end. */
|
|
56
|
-
offsetOf(index: number): number;
|
|
57
|
-
/** Index of the line covering `y`, clamped to the document. */
|
|
58
|
-
lineAt(y: number): number;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function clamp(value: number, min: number, max: number): number {
|
|
62
|
-
return value < min ? min : value > max ? max : value;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** Whether a document is long enough that rendering all of it would be felt. */
|
|
66
|
-
export function shouldWindow(lineCount: number, charCount: number): boolean {
|
|
67
|
-
return lineCount > WHOLE_DOCUMENT_MAX_LINES || charCount > WHOLE_DOCUMENT_MAX_CHARS;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** Word wrap off: every line is exactly one row, so no measurement is involved. */
|
|
71
|
-
export function uniformGeometry(rowHeight: number, lineCount: number): LineGeometry {
|
|
72
|
-
return {
|
|
73
|
-
lineCount,
|
|
74
|
-
rowHeight,
|
|
75
|
-
totalHeight: rowHeight * lineCount,
|
|
76
|
-
offsetOf: (index) => rowHeight * clamp(index, 0, lineCount),
|
|
77
|
-
lineAt: (y) => clamp(Math.floor(y / rowHeight), 0, Math.max(lineCount - 1, 0)),
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Word wrap on: heights are the measured wrapped heights, one entry per line.
|
|
83
|
-
* Returns `null` when they do not describe every line, which leaves the caller
|
|
84
|
-
* on the whole-document path rather than spacing lines with a wrong geometry.
|
|
85
|
-
*/
|
|
86
|
-
export function measuredGeometry(heights: number[], rowHeight: number, lineCount: number): LineGeometry | null {
|
|
87
|
-
if (lineCount === 0 || heights.length !== lineCount) return null;
|
|
88
|
-
const offsets = new Float64Array(lineCount + 1);
|
|
89
|
-
for (let i = 0; i < lineCount; i++) offsets[i + 1] = offsets[i] + heights[i];
|
|
90
|
-
const total = offsets[lineCount];
|
|
91
|
-
return {
|
|
92
|
-
lineCount,
|
|
93
|
-
rowHeight,
|
|
94
|
-
totalHeight: total,
|
|
95
|
-
offsetOf: (index) => offsets[clamp(index, 0, lineCount)],
|
|
96
|
-
lineAt: (y) => {
|
|
97
|
-
if (y < 0) return 0;
|
|
98
|
-
if (total <= 0 || y >= total) return lineCount - 1;
|
|
99
|
-
// Last line whose top edge sits at or above `y`.
|
|
100
|
-
let low = 0;
|
|
101
|
-
let high = lineCount - 1;
|
|
102
|
-
while (low < high) {
|
|
103
|
-
const mid = (low + high + 1) >> 1;
|
|
104
|
-
if (offsets[mid] <= y) low = mid;
|
|
105
|
-
else high = mid - 1;
|
|
106
|
-
}
|
|
107
|
-
return low;
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/** The lines a viewport band (`top`, `height`) covers, plus overscan on both sides. */
|
|
113
|
-
export function resolveWindow(
|
|
114
|
-
geometry: LineGeometry,
|
|
115
|
-
top: number,
|
|
116
|
-
height: number,
|
|
117
|
-
overscan: number = WINDOW_OVERSCAN,
|
|
118
|
-
): LineWindow {
|
|
119
|
-
const first = geometry.lineAt(top);
|
|
120
|
-
const last = geometry.lineAt(top + Math.max(height, 0));
|
|
121
|
-
return {
|
|
122
|
-
start: Math.max(0, first - overscan),
|
|
123
|
-
end: Math.min(geometry.lineCount, Math.max(last + 1, first + 1) + overscan),
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/** What a freshly opened document renders before its viewport has been read. */
|
|
128
|
-
export function firstWindow(lineCount: number): LineWindow {
|
|
129
|
-
return { start: 0, end: clamp(INITIAL_WINDOW_LINES, 1, Math.max(lineCount, 1)) };
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/** Keep a window inside the document it belongs to (the state may predate a file switch). */
|
|
133
|
-
export function clampWindow(window: LineWindow, lineCount: number): LineWindow {
|
|
134
|
-
const start = clamp(window.start, 0, Math.max(lineCount - 1, 0));
|
|
135
|
-
return { start, end: clamp(window.end, start + 1, Math.max(lineCount, 1)) };
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export function sameWindow(a: LineWindow | null, b: LineWindow | null): boolean {
|
|
139
|
-
return a === b || (a !== null && b !== null && a.start === b.start && a.end === b.end);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/** Space the unrendered lines take around `window`, and whether it ends the document. */
|
|
143
|
-
export function windowSpace(geometry: LineGeometry, window: LineWindow): WindowSpace {
|
|
144
|
-
const top = geometry.offsetOf(window.start);
|
|
145
|
-
return {
|
|
146
|
-
top,
|
|
147
|
-
bottom: Math.max(0, geometry.totalHeight - geometry.offsetOf(window.end)),
|
|
148
|
-
breakAtEnd: window.end >= geometry.lineCount,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/** Clamp a window to its document and resolve the space it leaves around itself. */
|
|
153
|
-
export function codeWindow(geometry: LineGeometry, window: LineWindow): CodeWindow {
|
|
154
|
-
const clamped = clampWindow(window, geometry.lineCount);
|
|
155
|
-
return { ...clamped, ...windowSpace(geometry, clamped) };
|
|
156
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { LineWindow } from '@/shared/lib/code/lazy-window';
|
|
7
|
-
import type { FindMatch } from '@/shared/lib/code/editor/find';
|
|
8
|
-
import { lineStartOffset } from '@/shared/lib/code/editor/lines';
|
|
9
|
-
import { highlightLines } from '@/shared/lib/code/syntax-highlight';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Lines tokenized *above* the window and dropped from the result. They only
|
|
13
|
-
* seed the grammar state, so a window that starts inside a block comment or a
|
|
14
|
-
* template literal still tokenizes as a comment / template literal. Tokenizing
|
|
15
|
-
* them costs a fraction of the window itself; carrying the whole document as
|
|
16
|
-
* context would defeat the point of windowing at all.
|
|
17
|
-
*/
|
|
18
|
-
const CONTEXT_LINES = 40;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Lines at or above this length render as plain text. A minified bundle is one
|
|
22
|
-
* line of hundreds of KB, which the tokenizer would chew through on every
|
|
23
|
-
* scroll; the cap keeps that bounded, exactly like Shiki's own
|
|
24
|
-
* `tokenizeMaxLineLength` bail-out.
|
|
25
|
-
*/
|
|
26
|
-
const MAX_LINE_CHARS = 12_000;
|
|
27
|
-
|
|
28
|
-
export interface HighlightCodeWindowOptions {
|
|
29
|
-
/** Lines of grammar context above the window. */
|
|
30
|
-
contextLines?: number;
|
|
31
|
-
/** Length at which a line falls back to plain text. */
|
|
32
|
-
maxLineLength?: number;
|
|
33
|
-
/**
|
|
34
|
-
* Find matches in DOCUMENT offsets. Only those inside the rendered window are
|
|
35
|
-
* passed on, re-based to the sliced source the tokenizer sees.
|
|
36
|
-
*/
|
|
37
|
-
marks?: readonly FindMatch[];
|
|
38
|
-
/** Index of the current match within `marks`. */
|
|
39
|
-
currentMark?: number;
|
|
40
|
-
/** Marks from this index on are extra ⌘D selections rather than find matches. */
|
|
41
|
-
occurrenceFrom?: number;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Shiki markup for lines `[window.start, window.end)` of `code`, joined with
|
|
46
|
-
* newlines so it can be dropped straight into the editor's `<pre>`. The caller
|
|
47
|
-
* reserves the space of the lines it is not given (`lazy-window`'s
|
|
48
|
-
* `windowSpace`), which is what keeps the caret over its own row.
|
|
49
|
-
*
|
|
50
|
-
* The rows are wrapped in `.shiki` — the app colours tokens through the
|
|
51
|
-
* `.shiki span { color: var(--shiki-light) }` rule (`tailwind.css`), so markup
|
|
52
|
-
* that skipped the wrapper would render in the plain ink colour. Every other
|
|
53
|
-
* consumer of `highlightLines` carries the same class.
|
|
54
|
-
*
|
|
55
|
-
* Editing is unaffected: the `<textarea>` above the highlight layer always
|
|
56
|
-
* holds the full document — only the markup is windowed.
|
|
57
|
-
*/
|
|
58
|
-
export function highlightCodeWindow(
|
|
59
|
-
code: string,
|
|
60
|
-
language: string,
|
|
61
|
-
window: LineWindow,
|
|
62
|
-
options: HighlightCodeWindowOptions = {},
|
|
63
|
-
): string {
|
|
64
|
-
const { contextLines = CONTEXT_LINES, maxLineLength = MAX_LINE_CHARS } = options;
|
|
65
|
-
const lines = code.split('\n');
|
|
66
|
-
const start = Math.min(Math.max(window.start, 0), lines.length);
|
|
67
|
-
const end = Math.min(Math.max(window.end, start), lines.length);
|
|
68
|
-
if (start === end) return '';
|
|
69
|
-
|
|
70
|
-
const contextStart = Math.max(0, start - contextLines);
|
|
71
|
-
const slice = lines.slice(contextStart, end).join('\n');
|
|
72
|
-
const base = lineStartOffset(code, contextStart);
|
|
73
|
-
const sliceEnd = base + slice.length;
|
|
74
|
-
const { marks, currentMark, occurrenceFrom } = rebaseMarks(
|
|
75
|
-
options.marks,
|
|
76
|
-
options.currentMark ?? -1,
|
|
77
|
-
options.occurrenceFrom ?? -1,
|
|
78
|
-
base,
|
|
79
|
-
sliceEnd,
|
|
80
|
-
);
|
|
81
|
-
const rows = highlightLines(slice, language, { maxLineLength, marks, currentMark, occurrenceFrom });
|
|
82
|
-
const offset = start - contextStart;
|
|
83
|
-
return `<span class="shiki">${rows.slice(offset, offset + (end - start)).join('\n')}</span>`;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* The marks inside `[base, sliceEnd)`, shifted to slice coordinates, with the
|
|
88
|
-
* current-match index remapped — the index points into the full list, so the
|
|
89
|
-
* rows below the window cannot simply reuse it.
|
|
90
|
-
*/
|
|
91
|
-
function rebaseMarks(
|
|
92
|
-
marks: readonly FindMatch[] | undefined,
|
|
93
|
-
currentMark: number,
|
|
94
|
-
occurrenceFrom: number,
|
|
95
|
-
base: number,
|
|
96
|
-
sliceEnd: number,
|
|
97
|
-
): { marks: FindMatch[]; currentMark: number; occurrenceFrom: number } {
|
|
98
|
-
if (!marks || marks.length === 0) return { marks: [], currentMark: -1, occurrenceFrom: -1 };
|
|
99
|
-
const kept: FindMatch[] = [];
|
|
100
|
-
let remapped = -1;
|
|
101
|
-
let remappedOccurrenceFrom = -1;
|
|
102
|
-
for (let index = 0; index < marks.length; index++) {
|
|
103
|
-
const mark = marks[index];
|
|
104
|
-
if (mark.end <= base) continue;
|
|
105
|
-
if (mark.start >= sliceEnd) break;
|
|
106
|
-
if (index === currentMark) remapped = kept.length;
|
|
107
|
-
// The first surviving occurrence keeps the split, wherever it landed: a
|
|
108
|
-
// window whose opening lines held only matches still has to paint the
|
|
109
|
-
// selections below them differently.
|
|
110
|
-
if (remappedOccurrenceFrom === -1 && occurrenceFrom >= 0 && index >= occurrenceFrom) remappedOccurrenceFrom = kept.length;
|
|
111
|
-
kept.push({ start: mark.start - base, end: Math.min(mark.end, sliceEnd) - base });
|
|
112
|
-
}
|
|
113
|
-
return { marks: kept, currentMark: remapped, occurrenceFrom: remappedOccurrenceFrom };
|
|
114
|
-
}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* The whitespace toggle's contract. Trimming the displayed text left a
|
|
8
|
-
* whitespace-only rewrite painted red/green and counted in `+n/-n`, so the
|
|
9
|
-
* toggle changed the glyphs and nothing else — these cases pin the behavior a
|
|
10
|
-
* reader actually expects from it.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { describe, expect, test } from 'bun:test';
|
|
14
|
-
|
|
15
|
-
import { parseUnifiedDiff } from '@/shared/lib/fs/diff-parser';
|
|
16
|
-
|
|
17
|
-
const WHITESPACE_ONLY = [
|
|
18
|
-
'diff --git a/ws.ts b/ws.ts',
|
|
19
|
-
'index 46378b4..c824424 100644',
|
|
20
|
-
'--- a/ws.ts',
|
|
21
|
-
'+++ b/ws.ts',
|
|
22
|
-
'@@ -1,3 +1,3 @@',
|
|
23
|
-
' function ws() {',
|
|
24
|
-
'- return 1;',
|
|
25
|
-
'+ return 1;',
|
|
26
|
-
' }',
|
|
27
|
-
'',
|
|
28
|
-
].join('\n');
|
|
29
|
-
|
|
30
|
-
const REAL_CHANGE = [
|
|
31
|
-
'diff --git a/main.ts b/main.ts',
|
|
32
|
-
'--- a/main.ts',
|
|
33
|
-
'+++ b/main.ts',
|
|
34
|
-
'@@ -1,2 +1,2 @@',
|
|
35
|
-
' export const a = 1;',
|
|
36
|
-
'-export function f() { return a; }',
|
|
37
|
-
'+export function f() { return a + 1; }',
|
|
38
|
-
'',
|
|
39
|
-
].join('\n');
|
|
40
|
-
|
|
41
|
-
const MIXED = [
|
|
42
|
-
'diff --git a/mixed.ts b/mixed.ts',
|
|
43
|
-
'--- a/mixed.ts',
|
|
44
|
-
'+++ b/mixed.ts',
|
|
45
|
-
'@@ -1,3 +1,3 @@',
|
|
46
|
-
'- const a = 1;',
|
|
47
|
-
'+ const a = 1;',
|
|
48
|
-
'- const b = 2;',
|
|
49
|
-
'+ const b = 3;',
|
|
50
|
-
'',
|
|
51
|
-
].join('\n');
|
|
52
|
-
|
|
53
|
-
describe('parseUnifiedDiff', () => {
|
|
54
|
-
test('counts and marks a whitespace-only rewrite by default', () => {
|
|
55
|
-
const { lines, additions, deletions } = parseUnifiedDiff(WHITESPACE_ONLY);
|
|
56
|
-
|
|
57
|
-
expect(additions).toBe(1);
|
|
58
|
-
expect(deletions).toBe(1);
|
|
59
|
-
expect(lines.filter((l) => l.type === 'add')).toHaveLength(1);
|
|
60
|
-
expect(lines.filter((l) => l.type === 'del')).toHaveLength(1);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('folds a whitespace-only rewrite into context when ignoring whitespace', () => {
|
|
64
|
-
const { lines, splitRows, additions, deletions } = parseUnifiedDiff(WHITESPACE_ONLY, { ignoreWhitespace: true });
|
|
65
|
-
|
|
66
|
-
// The counts the toolbar renders must match what is on screen.
|
|
67
|
-
expect(additions).toBe(0);
|
|
68
|
-
expect(deletions).toBe(0);
|
|
69
|
-
expect(lines.some((l) => l.type === 'add' || l.type === 'del')).toBe(false);
|
|
70
|
-
|
|
71
|
-
const folded = lines.find((l) => l.type === 'context' && l.oldLineNumber === 2);
|
|
72
|
-
expect(folded?.newLineNumber).toBe(2);
|
|
73
|
-
expect(folded?.text).toBe(' return 1;');
|
|
74
|
-
|
|
75
|
-
// Split view reads the same decision — it is built by the same flush.
|
|
76
|
-
const row = splitRows.find((r) => r.left?.lineNumber === 2);
|
|
77
|
-
expect(row?.left?.type).toBe('context');
|
|
78
|
-
expect(row?.right?.type).toBe('context');
|
|
79
|
-
expect(row?.left?.text).toBe(' return 1;');
|
|
80
|
-
expect(row?.right?.text).toBe(' return 1;');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test('keeps a real change when ignoring whitespace', () => {
|
|
84
|
-
const { lines, additions, deletions } = parseUnifiedDiff(REAL_CHANGE, { ignoreWhitespace: true });
|
|
85
|
-
|
|
86
|
-
expect(additions).toBe(1);
|
|
87
|
-
expect(deletions).toBe(1);
|
|
88
|
-
expect(lines.find((l) => l.type === 'del')?.text).toBe('export function f() { return a; }');
|
|
89
|
-
expect(lines.find((l) => l.type === 'add')?.text).toBe('export function f() { return a + 1; }');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('folds only the whitespace pair of a mixed hunk', () => {
|
|
93
|
-
const { lines, additions, deletions } = parseUnifiedDiff(MIXED, { ignoreWhitespace: true });
|
|
94
|
-
|
|
95
|
-
// Line 1 differs by indentation only; line 2 is a genuine edit.
|
|
96
|
-
expect(additions).toBe(1);
|
|
97
|
-
expect(deletions).toBe(1);
|
|
98
|
-
expect(lines.filter((l) => l.type === 'del').map((l) => l.text)).toEqual([' const b = 2;']);
|
|
99
|
-
expect(lines.filter((l) => l.type === 'add').map((l) => l.text)).toEqual([' const b = 3;']);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test('emits unified lines in diff order (deletions before additions)', () => {
|
|
103
|
-
const { lines } = parseUnifiedDiff(REAL_CHANGE);
|
|
104
|
-
const kinds = lines.map((l) => l.type);
|
|
105
|
-
|
|
106
|
-
// A git diff ends with a newline, which `split` turns into one trailing
|
|
107
|
-
// empty context line — pre-existing, and the same row the view has always
|
|
108
|
-
// drawn under the last change.
|
|
109
|
-
expect(kinds.slice(0, 4)).toEqual(['meta', 'context', 'del', 'add']);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test('returns empty results for an empty diff', () => {
|
|
113
|
-
expect(parseUnifiedDiff('', { ignoreWhitespace: true })).toEqual({ lines: [], splitRows: [], additions: 0, deletions: 0 });
|
|
114
|
-
expect(parseUnifiedDiff(' \n', { ignoreWhitespace: false })).toEqual({ lines: [], splitRows: [], additions: 0, deletions: 0 });
|
|
115
|
-
});
|
|
116
|
-
});
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
export interface DiffLine {
|
|
2
|
-
type: 'add' | 'del' | 'context' | 'meta';
|
|
3
|
-
text: string;
|
|
4
|
-
oldLineNumber?: number;
|
|
5
|
-
newLineNumber?: number;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface SplitDiffRow {
|
|
9
|
-
left?: { type: 'del' | 'context'; text: string; lineNumber: number };
|
|
10
|
-
right?: { type: 'add' | 'context'; text: string; lineNumber: number };
|
|
11
|
-
isMeta?: boolean;
|
|
12
|
-
metaText?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** A buffered `-`/`+` line, before it is decided whether it is a real change. */
|
|
16
|
-
interface PendingChange {
|
|
17
|
-
text: string;
|
|
18
|
-
lineNumber: number;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Parse a unified diff into the flat line list (Unified view) and the paired
|
|
23
|
-
* row list (Split view), plus the addition/deletion counts the toolbar shows.
|
|
24
|
-
*
|
|
25
|
-
* Both outputs are produced by the same flush, so a decision made here cannot
|
|
26
|
-
* land in one view and not the other.
|
|
27
|
-
*
|
|
28
|
-
* With `ignoreWhitespace`, a `-`/`+` pair whose text differs only in whitespace
|
|
29
|
-
* is folded back into context: the change is still in the file, but the reader
|
|
30
|
-
* asked not to be shown formatting noise. Merely trimming the displayed text —
|
|
31
|
-
* which is what this used to do — left the row painted red/green and counted in
|
|
32
|
-
* `+n/-n`, so the toggle changed the glyphs and nothing else.
|
|
33
|
-
*/
|
|
34
|
-
export interface ParseDiffOptions {
|
|
35
|
-
/** Fold `-`/`+` pairs that differ only in whitespace back into context. */
|
|
36
|
-
ignoreWhitespace?: boolean;
|
|
37
|
-
/**
|
|
38
|
-
* Which view the caller renders. Both views are built from one flush so a
|
|
39
|
-
* decision cannot land in one and not the other, but building the one nobody
|
|
40
|
-
* shows doubled the allocations on a full-context diff — an 80k-line file
|
|
41
|
-
* produced ~60k discarded `SplitDiffRow` objects. `'both'` is the default so
|
|
42
|
-
* the counts and the shared flush stay exactly as they were.
|
|
43
|
-
*/
|
|
44
|
-
views?: 'unified' | 'split' | 'both';
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function parseUnifiedDiff(rawDiff: string, options: ParseDiffOptions = {}): {
|
|
48
|
-
lines: DiffLine[];
|
|
49
|
-
splitRows: SplitDiffRow[];
|
|
50
|
-
additions: number;
|
|
51
|
-
deletions: number;
|
|
52
|
-
} {
|
|
53
|
-
const ignoreWhitespace = options.ignoreWhitespace ?? false;
|
|
54
|
-
const views = options.views ?? 'both';
|
|
55
|
-
const wantUnified = views !== 'split';
|
|
56
|
-
const wantSplit = views !== 'unified';
|
|
57
|
-
const lines: DiffLine[] = [];
|
|
58
|
-
const splitRows: SplitDiffRow[] = [];
|
|
59
|
-
let additions = 0;
|
|
60
|
-
let deletions = 0;
|
|
61
|
-
|
|
62
|
-
if (!rawDiff || !rawDiff.trim()) {
|
|
63
|
-
return { lines, splitRows, additions, deletions };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const rawLines = rawDiff.split(/\r?\n/);
|
|
67
|
-
const hasHunk = rawLines.some((l) => l.startsWith('@@'));
|
|
68
|
-
const linesToProcess = hasHunk
|
|
69
|
-
? rawLines
|
|
70
|
-
: [`@@ -1,${rawLines.length} +1,${rawLines.length} @@`, ...rawLines];
|
|
71
|
-
|
|
72
|
-
let oldCounter = 1;
|
|
73
|
-
let newCounter = 1;
|
|
74
|
-
|
|
75
|
-
// Consecutive deletions and additions, paired up in split view.
|
|
76
|
-
let delBuffer: PendingChange[] = [];
|
|
77
|
-
let addBuffer: PendingChange[] = [];
|
|
78
|
-
|
|
79
|
-
const flushBuffers = () => {
|
|
80
|
-
const maxLen = Math.max(delBuffer.length, addBuffer.length);
|
|
81
|
-
// A whitespace-only rewrite is decided per pair, but the flat line list
|
|
82
|
-
// keeps unified-diff order (every `-` of the group, then every `+`), which
|
|
83
|
-
// is what the original loop produced as it walked the raw diff.
|
|
84
|
-
const folded = new Array<boolean>(maxLen).fill(false);
|
|
85
|
-
if (ignoreWhitespace) {
|
|
86
|
-
for (let i = 0; i < maxLen; i++) {
|
|
87
|
-
const del = delBuffer[i];
|
|
88
|
-
const add = addBuffer[i];
|
|
89
|
-
folded[i] = Boolean(del && add && del.text.trim() === add.text.trim());
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (wantUnified) {
|
|
94
|
-
for (let i = 0; i < delBuffer.length; i++) {
|
|
95
|
-
const del = delBuffer[i];
|
|
96
|
-
const add = addBuffer[i];
|
|
97
|
-
if (folded[i] && add) {
|
|
98
|
-
// Show it as unchanged on both sides: the change is still in the file,
|
|
99
|
-
// the reader asked not to be shown formatting noise.
|
|
100
|
-
lines.push({
|
|
101
|
-
type: 'context',
|
|
102
|
-
// The new form, so the row reads as the file's current state.
|
|
103
|
-
text: add.text,
|
|
104
|
-
oldLineNumber: del.lineNumber,
|
|
105
|
-
newLineNumber: add.lineNumber,
|
|
106
|
-
});
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
lines.push({ type: 'del', text: del.text, oldLineNumber: del.lineNumber });
|
|
110
|
-
}
|
|
111
|
-
for (let i = 0; i < addBuffer.length; i++) {
|
|
112
|
-
const add = addBuffer[i];
|
|
113
|
-
if (folded[i]) continue;
|
|
114
|
-
lines.push({ type: 'add', text: add.text, newLineNumber: add.lineNumber });
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (wantSplit) {
|
|
119
|
-
for (let i = 0; i < maxLen; i++) {
|
|
120
|
-
const del = delBuffer[i];
|
|
121
|
-
const add = addBuffer[i];
|
|
122
|
-
if (folded[i] && del && add) {
|
|
123
|
-
splitRows.push({
|
|
124
|
-
left: { type: 'context', text: del.text, lineNumber: del.lineNumber },
|
|
125
|
-
right: { type: 'context', text: add.text, lineNumber: add.lineNumber },
|
|
126
|
-
});
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
splitRows.push({
|
|
130
|
-
left: del ? { type: 'del', text: del.text, lineNumber: del.lineNumber } : undefined,
|
|
131
|
-
right: add ? { type: 'add', text: add.text, lineNumber: add.lineNumber } : undefined,
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
delBuffer = [];
|
|
136
|
-
addBuffer = [];
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
for (const rawLine of linesToProcess) {
|
|
140
|
-
if (
|
|
141
|
-
rawLine.startsWith('diff --git') ||
|
|
142
|
-
rawLine.startsWith('index ') ||
|
|
143
|
-
rawLine.startsWith('---') ||
|
|
144
|
-
rawLine.startsWith('+++') ||
|
|
145
|
-
rawLine.startsWith('new file mode') ||
|
|
146
|
-
rawLine.startsWith('deleted file mode') ||
|
|
147
|
-
rawLine.startsWith('similarity index') ||
|
|
148
|
-
rawLine.startsWith('old mode') ||
|
|
149
|
-
rawLine.startsWith('new mode') ||
|
|
150
|
-
rawLine.startsWith('\\ No newline')
|
|
151
|
-
) {
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (rawLine.startsWith('@@')) {
|
|
156
|
-
flushBuffers();
|
|
157
|
-
const match = rawLine.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
|
158
|
-
if (match) {
|
|
159
|
-
oldCounter = parseInt(match[1], 10);
|
|
160
|
-
newCounter = parseInt(match[2], 10);
|
|
161
|
-
}
|
|
162
|
-
lines.push({ type: 'meta', text: rawLine });
|
|
163
|
-
splitRows.push({ isMeta: true, metaText: rawLine });
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (rawLine.startsWith('+')) {
|
|
168
|
-
const text = rawLine.slice(1);
|
|
169
|
-
additions++;
|
|
170
|
-
addBuffer.push({ text, lineNumber: newCounter });
|
|
171
|
-
newCounter++;
|
|
172
|
-
} else if (rawLine.startsWith('-')) {
|
|
173
|
-
const text = rawLine.slice(1);
|
|
174
|
-
deletions++;
|
|
175
|
-
delBuffer.push({ text, lineNumber: oldCounter });
|
|
176
|
-
oldCounter++;
|
|
177
|
-
} else {
|
|
178
|
-
flushBuffers();
|
|
179
|
-
const text = rawLine.startsWith(' ') ? rawLine.slice(1) : rawLine;
|
|
180
|
-
if (wantUnified) {
|
|
181
|
-
lines.push({
|
|
182
|
-
type: 'context',
|
|
183
|
-
text,
|
|
184
|
-
oldLineNumber: oldCounter,
|
|
185
|
-
newLineNumber: newCounter,
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
if (wantSplit) {
|
|
189
|
-
splitRows.push({
|
|
190
|
-
left: { type: 'context', text, lineNumber: oldCounter },
|
|
191
|
-
right: { type: 'context', text, lineNumber: newCounter },
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
oldCounter++;
|
|
195
|
-
newCounter++;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
flushBuffers();
|
|
200
|
-
|
|
201
|
-
if (ignoreWhitespace) {
|
|
202
|
-
// The counts were tallied before the pairs could be folded, so re-derive
|
|
203
|
-
// them from what is actually shown.
|
|
204
|
-
additions = 0;
|
|
205
|
-
deletions = 0;
|
|
206
|
-
for (const line of lines) {
|
|
207
|
-
if (line.type === 'add') additions++;
|
|
208
|
-
else if (line.type === 'del') deletions++;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return { lines, splitRows, additions, deletions };
|
|
213
|
-
}
|