Skip to content

Commit

Permalink
Refactor config
Browse files Browse the repository at this point in the history
Mainly an attempt to clear up what is configurable via git and what gets
loaded separately.
  • Loading branch information
banga committed Dec 31, 2023
1 parent 3bea4c7 commit fe74aff
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 101 deletions.
12 changes: 0 additions & 12 deletions src/config.ts

This file was deleted.

25 changes: 18 additions & 7 deletions src/context.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,43 @@
import * as shikiji from 'shikiji';
import { Config } from './config';
import { Config } from './getConfig';
import { FormattedString, T } from './formattedString';
import { ChalkInstance } from 'chalk';

/**
* Internal context object used to pass around config and config-derived
* constants.
*/
export type Context = Config & {
CHALK: ChalkInstance;
SPLIT_DIFFS: boolean;
SCREEN_WIDTH: number;
LINE_WIDTH: number;
BLANK_LINE: string;
HORIZONTAL_SEPARATOR: FormattedString;
HIGHLIGHTER?: shikiji.Highlighter;
};

export async function getContextForConfig(config: Config): Promise<Context> {
export async function getContextForConfig(
config: Config,
chalk: ChalkInstance,
screenWidth: number
): Promise<Context> {
const SCREEN_WIDTH = screenWidth;

// Only split diffs if there's enough room
const SPLIT_DIFFS = config.SCREEN_WIDTH >= config.MIN_LINE_WIDTH * 2;
const SPLIT_DIFFS = SCREEN_WIDTH >= config.MIN_LINE_WIDTH * 2;

let LINE_WIDTH: number;
if (SPLIT_DIFFS) {
LINE_WIDTH = Math.floor(config.SCREEN_WIDTH / 2);
LINE_WIDTH = Math.floor(SCREEN_WIDTH / 2);
} else {
LINE_WIDTH = config.SCREEN_WIDTH;
LINE_WIDTH = SCREEN_WIDTH;
}

const BLANK_LINE = ''.padStart(LINE_WIDTH);
const HORIZONTAL_SEPARATOR = T()
.fillWidth(config.SCREEN_WIDTH, '─')
.addSpan(0, config.SCREEN_WIDTH, config.BORDER_COLOR);
.fillWidth(SCREEN_WIDTH, '─')
.addSpan(0, SCREEN_WIDTH, config.BORDER_COLOR);

let HIGHLIGHTER = undefined;
if (config.SYNTAX_HIGHLIGHTING_THEME) {
Expand All @@ -39,6 +48,8 @@ export async function getContextForConfig(config: Config): Promise<Context> {
}
return {
...config,
CHALK: chalk,
SCREEN_WIDTH,
SPLIT_DIFFS,
LINE_WIDTH,
BLANK_LINE,
Expand Down
29 changes: 29 additions & 0 deletions src/getConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { GitConfig } from './getGitConfig';
import { Theme, loadTheme } from './themes';
import * as shikiji from 'shikiji';

export type Config = Theme & {
MIN_LINE_WIDTH: number;
WRAP_LINES: boolean;
HIGHLIGHT_LINE_CHANGES: boolean;
};

export const DEFAULT_THEME_NAME = 'dark';

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

export function getConfig(gitConfig: GitConfig): Config {
const theme = loadTheme(gitConfig.THEME_NAME ?? DEFAULT_THEME_NAME);

return {
...CONFIG_DEFAULTS,
...theme,
...gitConfig,
SYNTAX_HIGHLIGHTING_THEME: (theme.SYNTAX_HIGHLIGHTING_THEME ??
gitConfig.SYNTAX_HIGHLIGHTING_THEME) as shikiji.BundledTheme,
};
}
66 changes: 66 additions & 0 deletions src/getGitConfig.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { DEFAULT_THEME_NAME } from './getConfig';
import {
DEFAULT_MIN_LINE_WIDTH,
GitConfig,
getGitConfig,
} from './getGitConfig';

const DEFAULT_CONFIG: GitConfig = {
WRAP_LINES: true,
HIGHLIGHT_LINE_CHANGES: true,
MIN_LINE_WIDTH: DEFAULT_MIN_LINE_WIDTH,
THEME_NAME: DEFAULT_THEME_NAME,
};

describe('getGitConfig', () => {
test('empty', () => {
expect(getGitConfig('')).toEqual(DEFAULT_CONFIG);
});

test('full', () => {
expect(
getGitConfig(`
split-diffs.wrap-lines=false
split-diffs.highlight-line-changes=false
split-diffs.min-line-width=40
split-diffs.theme-name=arctic
split-diffs.syntax-highlighting-theme=dark-plus
`)
).toEqual({
WRAP_LINES: false,
HIGHLIGHT_LINE_CHANGES: false,
MIN_LINE_WIDTH: 40,
THEME_NAME: 'arctic',
SYNTAX_HIGHLIGHTING_THEME: 'dark-plus',
});
});

test('partial', () => {
expect(
getGitConfig(`
split-diffs.wrap-lines=true
split-diffs.syntax-highlighting-theme=nord
`)
).toEqual({
...DEFAULT_CONFIG,
WRAP_LINES: true,
SYNTAX_HIGHLIGHTING_THEME: 'nord',
});
});

test('invalid values', () => {
expect(
getGitConfig(`
split-diffs.wrap-lines=1
split-diffs.highlight-line-changes=1
split-diffs.min-line-width=bar
split-diffs.syntax-highlighting-theme=foo
split-diffs.theme-name=baz
`)
).toEqual({
...DEFAULT_CONFIG,
SYNTAX_HIGHLIGHTING_THEME: 'foo',
THEME_NAME: 'baz',
});
});
});
59 changes: 19 additions & 40 deletions src/getGitConfig.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import { ChalkInstance } from 'chalk';
import { exec } from 'child_process';
import * as util from 'util';
import { Config } from './config';
import { loadTheme } from './themes';
import * as shikiji from 'shikiji';
const execAsync = util.promisify(exec);
export type GitConfig = {
MIN_LINE_WIDTH: number;
WRAP_LINES: boolean;
HIGHLIGHT_LINE_CHANGES: boolean;
THEME_NAME: string;
SYNTAX_HIGHLIGHTING_THEME?: string;
};

export const DEFAULT_MIN_LINE_WIDTH = 80;
export const DEFAULT_THEME_NAME = 'dark';

const GIT_CONFIG_KEY_PREFIX = 'split-diffs';
const GIT_CONFIG_LINE_REGEX = new RegExp(
`${GIT_CONFIG_KEY_PREFIX}\.([^=]+)=(.*)`
);

async function getRawGitConfig() {
const { stdout } = await execAsync('git config -l');

function extractFromGitConfigString(configString: string) {
const rawConfig: Record<string, string> = {};
for (const line of stdout.trim().split('\n')) {
for (const line of configString.trim().split('\n')) {
const match = line.match(GIT_CONFIG_LINE_REGEX);
if (!match) {
continue;
Expand All @@ -26,30 +27,10 @@ async function getRawGitConfig() {
return rawConfig;
}

// TODO: Make this less manual
export async function getGitConfig(
screenWidth: number,
chalk: ChalkInstance
): Promise<Config> {
const rawConfig = await getRawGitConfig();

// Defaults to "dark"
const themeName = rawConfig['theme-name'] ?? 'dark';
const theme = loadTheme(themeName);

// Defaults to the theme's setting
const syntaxHighlightingTheme = (rawConfig['syntax-highlighting-theme'] ??
theme.SYNTAX_HIGHLIGHTING_THEME) as shikiji.BundledTheme;

// Defaults to true
const wrapLines = rawConfig['wrap-lines'] === 'false' ? false : true;

// Defaults to true
const highlightLineChanges =
rawConfig['highlight-line-changes'] === 'false' ? false : true;
export function getGitConfig(configString: string): GitConfig {
const rawConfig = extractFromGitConfigString(configString);

// Defaults to 80
let minLineWidth = 80;
let minLineWidth = DEFAULT_MIN_LINE_WIDTH;
try {
const parsedMinLineWidth = parseInt(rawConfig['min-line-width'], 10);
if (!isNaN(parsedMinLineWidth)) {
Expand All @@ -58,12 +39,10 @@ export async function getGitConfig(
} catch {}

return {
...theme,
CHALK: chalk,
SCREEN_WIDTH: screenWidth,
MIN_LINE_WIDTH: minLineWidth,
WRAP_LINES: wrapLines,
HIGHLIGHT_LINE_CHANGES: highlightLineChanges,
SYNTAX_HIGHLIGHTING_THEME: syntaxHighlightingTheme,
WRAP_LINES: rawConfig['wrap-lines'] !== 'false',
HIGHLIGHT_LINE_CHANGES: rawConfig['highlight-line-changes'] !== 'false',
THEME_NAME: rawConfig['theme-name'] ?? DEFAULT_THEME_NAME,
SYNTAX_HIGHLIGHTING_THEME: rawConfig['syntax-highlighting-theme'],
};
}
93 changes: 57 additions & 36 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,95 @@
import { Readable, Writable } from 'stream';
import { Config } from './config';
import { Config } from './getConfig';
import { getContextForConfig } from './context';
import { ThemeColorName } from './themes';
import { Theme, ThemeColorName } from './themes';
import { transformContentsStreaming } from './transformContentsStreaming';
import { ChalkInstance } from 'chalk';

const TEST_THEME = Object.fromEntries(
Object.keys(ThemeColorName).map((name) => [name, {}])
);
) as Theme;

const replaceColoredText = () => (text: string) => text.replace(/./g, '░');

// Provide a fake chalk implementation to make it easier to read snapshots
// @ts-expect-error
const TEST_CHALK = {
rgb: replaceColoredText,
bgRgb: replaceColoredText,
} as ChalkInstance;

const TEST_CONFIG: Config = {
// Provide a fake chalk implementation to make it easier to read snapshots
CHALK: {
// @ts-expect-error
rgb: replaceColoredText,
// @ts-expect-error
bgRgb: replaceColoredText,
},
SCREEN_WIDTH: 120,
MIN_LINE_WIDTH: 60,
WRAP_LINES: false,
HIGHLIGHT_LINE_CHANGES: false,
...TEST_THEME,
};

const CONFIG_OVERRIDES: Record<string, Partial<Config>> = {
type TestOverrides = { screenWidth: number; config: Partial<Config> };

const CONFIG_OVERRIDES: Record<string, TestOverrides> = {
splitWithoutWrapping: {
SCREEN_WIDTH: 80,
MIN_LINE_WIDTH: 40,
WRAP_LINES: false,
screenWidth: 80,
config: {
MIN_LINE_WIDTH: 40,
WRAP_LINES: false,
},
},
splitWithWrapping: {
SCREEN_WIDTH: 80,
MIN_LINE_WIDTH: 40,
WRAP_LINES: true,
screenWidth: 80,
config: {
MIN_LINE_WIDTH: 40,
WRAP_LINES: true,
},
},
unifiedWithWrapping: {
SCREEN_WIDTH: 80,
MIN_LINE_WIDTH: 80,
WRAP_LINES: true,
screenWidth: 80,
config: {
MIN_LINE_WIDTH: 80,
WRAP_LINES: true,
},
},
// This is in split mode
inlineChangesHighlighted: {
HIGHLIGHT_LINE_CHANGES: true,
DELETED_WORD_COLOR: { color: { r: 255, g: 0, b: 0, a: 255 } },
INSERTED_WORD_COLOR: { color: { r: 0, g: 255, b: 0, a: 255 } },
screenWidth: 120,
config: {
HIGHLIGHT_LINE_CHANGES: true,
DELETED_WORD_COLOR: { color: { r: 255, g: 0, b: 0, a: 255 } },
INSERTED_WORD_COLOR: { color: { r: 0, g: 255, b: 0, a: 255 } },
},
},
unifiedWithInlineChangesHighlighted: {
SCREEN_WIDTH: 80,
MIN_LINE_WIDTH: 80,
HIGHLIGHT_LINE_CHANGES: true,
DELETED_WORD_COLOR: { color: { r: 255, g: 0, b: 0, a: 255 } },
INSERTED_WORD_COLOR: { color: { r: 0, g: 255, b: 0, a: 255 } },
screenWidth: 80,
config: {
MIN_LINE_WIDTH: 80,
HIGHLIGHT_LINE_CHANGES: true,
DELETED_WORD_COLOR: { color: { r: 255, g: 0, b: 0, a: 255 } },
INSERTED_WORD_COLOR: { color: { r: 0, g: 255, b: 0, a: 255 } },
},
},
syntaxHighlighted: {
SCREEN_WIDTH: 80,
MIN_LINE_WIDTH: 40,
WRAP_LINES: false,
SYNTAX_HIGHLIGHTING_THEME: 'dark-plus',
screenWidth: 80,
config: {
MIN_LINE_WIDTH: 40,
WRAP_LINES: false,
SYNTAX_HIGHLIGHTING_THEME: 'dark-plus',
},
},
};

for (const [configName, configOverride] of Object.entries(CONFIG_OVERRIDES)) {
for (const [configName, { screenWidth, config }] of Object.entries(
CONFIG_OVERRIDES
)) {
async function transform(input: string): Promise<string> {
const testConfig: Config = {
...TEST_CONFIG,
...configOverride,
...config,
};
const context = await getContextForConfig(testConfig);
const context = await getContextForConfig(
testConfig,
TEST_CHALK,
screenWidth
);

let string = '';
await transformContentsStreaming(
Expand Down

0 comments on commit fe74aff

Please sign in to comment.