From 15b6486511023d5940fab1b338e5b7e632dfa4f8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Sep 2025 21:47:54 +0000 Subject: [PATCH 1/2] Initial plan From ba92b3404ae42bc17c7686060db363f67dec0cb9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Sep 2025 21:56:45 +0000 Subject: [PATCH 2/2] Implement path expansion support for theme-directory configuration Co-authored-by: kud <655838+kud@users.noreply.github.com> --- README.md | 13 +++++ src/getGitConfig.test.ts | 101 +++++++++++++++++++++++++++++++++++++++ src/getGitConfig.ts | 26 +++++++++- 3 files changed, 138 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0963aeb..91367b4 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,19 @@ git config split-diffs.min-line-width 40 This defaults to `80`, so screens below `160` characters will display unified diffs. Set it to `0` to always show split diffs. +### Custom theme directory + +You can specify a custom directory for themes using the `theme-directory` setting. This supports path expansion for convenient configuration: + +``` +git config split-diffs.theme-directory ~/my/themes/split-diff +``` + +The following expansions are supported: +- `~` and `~/` expand to your home directory +- `$HOME`, `${HOME}`, `$USER`, and other environment variables are expanded +- Relative paths are resolved to absolute paths + ## Themes You can pick between several [themes](themes/): diff --git a/src/getGitConfig.test.ts b/src/getGitConfig.test.ts index 559a9f8..2becabf 100644 --- a/src/getGitConfig.test.ts +++ b/src/getGitConfig.test.ts @@ -67,4 +67,105 @@ split-diffs.theme-name=baz THEME_NAME: 'baz', }); }); + + describe('theme-directory path expansion', () => { + const originalEnv = process.env; + + beforeEach(() => { + // Set up test environment variables + process.env = { + ...originalEnv, + TEST_HOME: '/home/testuser', + TEST_USER: 'testuser', + TEST_CUSTOM: 'custom_value', + }; + }); + + afterEach(() => { + // Restore original environment + process.env = originalEnv; + }); + + test('expands $TEST_HOME environment variable', () => { + const result = getGitConfig(` +split-diffs.theme-directory=$TEST_HOME/my/themes/split-diff + `); + expect(result.THEME_DIRECTORY).toBe('/home/testuser/my/themes/split-diff'); + }); + + test('expands ${TEST_HOME} environment variable with braces', () => { + const result = getGitConfig(` +split-diffs.theme-directory=\${TEST_HOME}/my/themes/split-diff + `); + expect(result.THEME_DIRECTORY).toBe('/home/testuser/my/themes/split-diff'); + }); + + test('expands $TEST_USER environment variable in path', () => { + const result = getGitConfig(` +split-diffs.theme-directory=/Users/$TEST_USER/my/themes/split-diff + `); + expect(result.THEME_DIRECTORY).toBe('/Users/testuser/my/themes/split-diff'); + }); + + test('expands ${TEST_USER} environment variable with braces', () => { + const result = getGitConfig(` +split-diffs.theme-directory=/Users/\${TEST_USER}/my/themes/split-diff + `); + expect(result.THEME_DIRECTORY).toBe('/Users/testuser/my/themes/split-diff'); + }); + + test('expands multiple environment variables', () => { + const result = getGitConfig(` +split-diffs.theme-directory=$TEST_HOME/\${TEST_USER}/themes/\${TEST_CUSTOM} + `); + expect(result.THEME_DIRECTORY).toBe('/home/testuser/testuser/themes/custom_value'); + }); + + test('handles missing environment variables as empty string', () => { + const result = getGitConfig(` +split-diffs.theme-directory=$TEST_HOME/\${NONEXISTENT}/themes + `); + expect(result.THEME_DIRECTORY).toBe('/home/testuser/themes'); + }); + + test('returns absolute path for relative input', () => { + const result = getGitConfig(` +split-diffs.theme-directory=./relative/path + `); + expect(result.THEME_DIRECTORY).toContain('/relative/path'); + expect(result.THEME_DIRECTORY.startsWith('./')).toBe(false); + }); + + test('leaves absolute paths unchanged (except expansion)', () => { + const result = getGitConfig(` +split-diffs.theme-directory=/absolute/path + `); + expect(result.THEME_DIRECTORY).toBe('/absolute/path'); + }); + + test('does not expand when theme-directory is not set', () => { + const result = getGitConfig(` +split-diffs.theme-name=arctic + `); + expect(result.THEME_DIRECTORY).toBe(DEFAULT_THEME_DIRECTORY); + }); + + test('expands ~ to actual home directory', () => { + const result = getGitConfig(` +split-diffs.theme-directory=~ + `); + // Should expand to the actual home directory + expect(result.THEME_DIRECTORY).not.toBe('~'); + expect(result.THEME_DIRECTORY).toBeTruthy(); + }); + + test('expands ~/ to actual home directory with path', () => { + const result = getGitConfig(` +split-diffs.theme-directory=~/my/themes/split-diff + `); + // Should expand to the actual home directory + path + expect(result.THEME_DIRECTORY).not.toContain('~'); + expect(result.THEME_DIRECTORY).toContain('/my/themes/split-diff'); + }); + }); }); diff --git a/src/getGitConfig.ts b/src/getGitConfig.ts index 5c0107c..56f428a 100644 --- a/src/getGitConfig.ts +++ b/src/getGitConfig.ts @@ -1,3 +1,4 @@ +import os from 'os'; import path from 'path'; import { fileURLToPath } from 'url'; @@ -23,6 +24,26 @@ const GIT_CONFIG_LINE_REGEX = new RegExp( `${GIT_CONFIG_KEY_PREFIX}\\.([^=]+)=(.*)` ); +function expandPath(p: string): string { + let expanded = p; + + // Expand ~ and ~/ to user home directory + if (expanded === '~' || expanded.startsWith('~/')) { + expanded = expanded.replace(/^~/, os.homedir()); + } + + // Expand $VAR and ${VAR} environment variables + expanded = expanded.replace(/\$\{([^}]+)\}/g, (_, varName) => { + return process.env[varName] || ''; + }); + expanded = expanded.replace(/\$([A-Z_][A-Z0-9_]*)/g, (_, varName) => { + return process.env[varName] || ''; + }); + + // Return absolute path + return path.resolve(expanded); +} + function extractFromGitConfigString(configString: string) { const rawConfig: Record = {}; for (const line of configString.trim().split('\n')) { @@ -53,8 +74,9 @@ export function getGitConfig(configString: string): GitConfig { MIN_LINE_WIDTH: minLineWidth, WRAP_LINES: rawConfig['wrap-lines'] !== 'false', HIGHLIGHT_LINE_CHANGES: rawConfig['highlight-line-changes'] !== 'false', - THEME_DIRECTORY: - rawConfig['theme-directory'] ?? DEFAULT_THEME_DIRECTORY, + THEME_DIRECTORY: rawConfig['theme-directory'] + ? expandPath(rawConfig['theme-directory']) + : DEFAULT_THEME_DIRECTORY, THEME_NAME: rawConfig['theme-name'] ?? DEFAULT_THEME_NAME, SYNTAX_HIGHLIGHTING_THEME: rawConfig['syntax-highlighting-theme'], };