Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ You can disable this with the `highlight-line-changes` setting:
git config split-diffs.highlight-line-changes false
```

### Unified diffs for one-sided files

A file that only has additions, or only has deletions, leaves one half of a split diff empty. To show such files as unified diffs, and keep split diffs for files that have both, enable the `unify-one-sided-diffs` setting:

```
git config split-diffs.unify-one-sided-diffs true
```

This decides per file rather than per hunk, so each file's hunks are held back until the whole file has been read.

### Enable scrolling in the terminal

```sh
Expand Down
749 changes: 749 additions & 0 deletions src/__snapshots__/index.test.ts.snap

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/getConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ export type Config = Theme & {
MIN_LINE_WIDTH: number;
WRAP_LINES: boolean;
HIGHLIGHT_LINE_CHANGES: boolean;
UNIFY_ONE_SIDED_DIFFS: boolean;
};

export const CONFIG_DEFAULTS: Omit<Config, keyof Theme> = {
MIN_LINE_WIDTH: 80,
WRAP_LINES: true,
HIGHLIGHT_LINE_CHANGES: true,
UNIFY_ONE_SIDED_DIFFS: false,
};

export function getConfig(gitConfig: GitConfig): Config {
Expand Down
4 changes: 4 additions & 0 deletions src/getGitConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
const DEFAULT_CONFIG: GitConfig = {
WRAP_LINES: true,
HIGHLIGHT_LINE_CHANGES: true,
UNIFY_ONE_SIDED_DIFFS: false,
MIN_LINE_WIDTH: DEFAULT_MIN_LINE_WIDTH,
THEME_NAME: DEFAULT_THEME_NAME,
THEME_DIRECTORY: DEFAULT_THEME_DIRECTORY,
Expand All @@ -24,6 +25,7 @@ describe('getGitConfig', () => {
getGitConfig(`
split-diffs.wrap-lines=false
split-diffs.highlight-line-changes=false
split-diffs.unify-one-sided-diffs=true
split-diffs.min-line-width=40
split-diffs.theme-name=arctic
split-diffs.theme-directory=/tmp
Expand All @@ -32,6 +34,7 @@ split-diffs.syntax-highlighting-theme=dark-plus
).toEqual({
WRAP_LINES: false,
HIGHLIGHT_LINE_CHANGES: false,
UNIFY_ONE_SIDED_DIFFS: true,
MIN_LINE_WIDTH: 40,
THEME_NAME: 'arctic',
THEME_DIRECTORY: '/tmp',
Expand All @@ -57,6 +60,7 @@ split-diffs.syntax-highlighting-theme=nord
getGitConfig(`
split-diffs.wrap-lines=1
split-diffs.highlight-line-changes=1
split-diffs.unify-one-sided-diffs=1
split-diffs.min-line-width=bar
split-diffs.syntax-highlighting-theme=foo
split-diffs.theme-name=baz
Expand Down
2 changes: 2 additions & 0 deletions src/getGitConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type GitConfig = {
MIN_LINE_WIDTH: number;
WRAP_LINES: boolean;
HIGHLIGHT_LINE_CHANGES: boolean;
UNIFY_ONE_SIDED_DIFFS: boolean;
THEME_DIRECTORY: string;
THEME_NAME: string;
SYNTAX_HIGHLIGHTING_THEME?: string;
Expand Down Expand Up @@ -53,6 +54,7 @@ export function getGitConfig(configString: string): GitConfig {
MIN_LINE_WIDTH: minLineWidth,
WRAP_LINES: rawConfig['wrap-lines'] !== 'false',
HIGHLIGHT_LINE_CHANGES: rawConfig['highlight-line-changes'] !== 'false',
UNIFY_ONE_SIDED_DIFFS: rawConfig['unify-one-sided-diffs'] === 'true',
THEME_DIRECTORY:
rawConfig['theme-directory'] ?? DEFAULT_THEME_DIRECTORY,
THEME_NAME: rawConfig['theme-name'] ?? DEFAULT_THEME_NAME,
Expand Down
54 changes: 54 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const TEST_CONFIG: Config = {
MIN_LINE_WIDTH: 60,
WRAP_LINES: false,
HIGHLIGHT_LINE_CHANGES: false,
UNIFY_ONE_SIDED_DIFFS: false,
...TEST_THEME,
};

Expand Down Expand Up @@ -56,6 +57,10 @@ const CONFIG_OVERRIDES: Record<string, TestOverrides> = {
DELETED_WORD_COLOR: { color: { r: 255, g: 0, b: 0, a: 255 } },
INSERTED_WORD_COLOR: { color: { r: 0, g: 255, b: 0, a: 255 } },
},
unifyOneSidedDiffs: {
MIN_LINE_WIDTH: 60,
UNIFY_ONE_SIDED_DIFFS: true,
},
syntaxHighlighted: {
MIN_LINE_WIDTH: 60,
WRAP_LINES: false,
Expand Down Expand Up @@ -522,6 +527,55 @@ index 095ee29..439621e 100644
).toMatchSnapshot();
});

test('commit with one-sided and two-sided files', async function () {
expect(
await transform(
config,
`
commit 1111111111111111111111111111111111111111
Author: Shrey Banga <banga.shrey@gmail.com>
Date: Sun Apr 11 15:25:34 2021 -0700

Mix of changes

diff --git a/added.md b/added.md
index 1111111..2222222 100644
--- a/added.md
+++ b/added.md
@@ -1,2 +1,3 @@
first
+inserted
second
@@ -10,2 +11,3 @@
tenth
+another insert
eleventh
diff --git a/mixed.md b/mixed.md
index 3333333..4444444 100644
--- a/mixed.md
+++ b/mixed.md
@@ -1,3 +1,3 @@
first
+only inserted in this hunk
second
-third
@@ -10,3 +10,2 @@
tenth
-only deleted in this hunk
eleventh
diff --git a/deleted.md b/deleted.md
index 5555555..6666666 100644
--- a/deleted.md
+++ b/deleted.md
@@ -1,3 +1,2 @@
first
-removed
second
`
)
).toMatchSnapshot();
});

test('merge commit with 2 parents', async function () {
// Source: the TypeScript repo
expect(
Expand Down
6 changes: 4 additions & 2 deletions src/iterFormatHunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export async function* iterFormatHunk(
context: Context,
diffType: 'unified-diff' | 'combined-diff',
hunkHeaderLine: string,
hunkParts: HunkPart[]
hunkParts: HunkPart[],
forceUnified: boolean = false
): AsyncIterable<FormattedString> {
const { HUNK_HEADER_COLOR, SCREEN_WIDTH, MIN_LINE_WIDTH } = context;

Expand All @@ -37,7 +38,8 @@ export async function* iterFormatHunk(
);

// Only split diffs if there's enough room
const splitDiffs = SCREEN_WIDTH >= MIN_LINE_WIDTH * hunkParts.length;
const splitDiffs =
!forceUnified && SCREEN_WIDTH >= MIN_LINE_WIDTH * hunkParts.length;

if (splitDiffs) {
yield* iterFormatHunkSplit(context, hunkParts, changes);
Expand Down
39 changes: 38 additions & 1 deletion src/iterSideBySideDiffs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ async function* iterSideBySideDiffsFormatted(
context: Context,
lines: AsyncIterable<string>
): AsyncIterable<FormattedString> {
const { HORIZONTAL_SEPARATOR } = context;
const { HORIZONTAL_SEPARATOR, UNIFY_ONE_SIDED_DIFFS } = context;

let state: State = 'unknown';

Expand All @@ -56,13 +56,43 @@ async function* iterSideBySideDiffsFormatted(
let hunkParts: HunkPart[] = [];
let hunkHeaderLine: string = '';
async function* yieldHunk(diffType: 'unified-diff' | 'combined-diff') {
if (diffType === 'unified-diff' && UNIFY_ONE_SIDED_DIFFS) {
// Whether a file is one-sided is only known once all its hunks
// have been read, so hold them until the file ends.
fileHunks.push({ hunkHeaderLine, hunkParts });
return;
}
yield* iterFormatHunk(context, diffType, hunkHeaderLine, hunkParts);
for (const hunkPart of hunkParts) {
hunkPart.startLineNo = -1;
hunkPart.lines = [];
}
}

// Hunks of the current file, held back when UNIFY_ONE_SIDED_DIFFS is set
let fileHunks: { hunkHeaderLine: string; hunkParts: HunkPart[] }[] = [];
async function* yieldFileHunks() {
const hasPrefix = (lines: (string | null)[], prefix: string) =>
lines.some((line) => line?.startsWith(prefix));
const hasDeletions = fileHunks.some(({ hunkParts: [partA] }) =>
hasPrefix(partA.lines, '-')
);
const hasInsertions = fileHunks.some(({ hunkParts: [, partB] }) =>
hasPrefix(partB.lines, '+')
);
const isOneSided = !(hasDeletions && hasInsertions);
for (const { hunkHeaderLine, hunkParts } of fileHunks) {
yield* iterFormatHunk(
context,
'unified-diff',
hunkHeaderLine,
hunkParts,
isOneSided
);
}
fileHunks = [];
}

async function* flushPending() {
if (state === 'unified-diff' || state === 'combined-diff') {
yield* yieldFileName();
Expand Down Expand Up @@ -108,6 +138,12 @@ async function* iterSideBySideDiffsFormatted(
// Handle state starts
if (nextState) {
yield* flushPending();
if (
nextState !== 'unified-diff-hunk-header' &&
nextState !== 'unified-diff-hunk-body'
) {
yield* yieldFileHunks();
}

switch (nextState) {
case 'commit-header':
Expand Down Expand Up @@ -309,6 +345,7 @@ async function* iterSideBySideDiffsFormatted(
}

yield* flushPending();
yield* yieldFileHunks();
}

export async function* iterSideBySideDiffs(
Expand Down
1 change: 1 addition & 0 deletions src/previewTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const CONFIG = {
MIN_LINE_WIDTH: 40,
WRAP_LINES: true,
HIGHLIGHT_LINE_CHANGES: true,
UNIFY_ONE_SIDED_DIFFS: false,
};

async function previewTheme(
Expand Down