config: add delta config and enhance lazygit with Dracula theme

- Extract delta configuration to dedicated managed config file
- Update lazygit with full Dracula theme and quality-of-life improvements
- Remove banner comments from config files for cleaner format
- Update git-related tool scripts (install-fonts, share utility)
This commit is contained in:
2026-01-03 19:26:36 -06:00
parent ce9afed824
commit 91e58db0d9
6 changed files with 626 additions and 402 deletions
+57
View File
@@ -0,0 +1,57 @@
# Delta Configuration - Dracula Theme
[delta]
# Core Settings
navigate = true # n/N to jump between files
side-by-side = false # Unified diff (better for small changes)
line-numbers = true # Show line numbers
syntax-theme = Dracula # Match Dracula theme
paging = never # Let lazygit handle paging
# Display Quality
true-color = always
hyperlinks = true
hyperlinks-file-link-format = "file://{path}"
# Line Number Styling (clearer format)
line-numbers-left-format = "{nm:>4} ⋮" # Old file line
line-numbers-right-format = "{np:>4} │" # New file line
line-numbers-left-style = "#6272a4" # Dracula comment
line-numbers-right-style = "#6272a4"
line-numbers-minus-style = "#ff5555" # Red for removed
line-numbers-plus-style = "#50fa7b" # Green for added
line-numbers-zero-style = "#6272a4" # Comment for unchanged
# Hunk Headers (the @@ lines)
hunk-header-style = file line-number syntax
hunk-header-decoration-style = "#bd93f9" box # Purple box
hunk-header-line-number-style = "#f1fa8c" # Yellow line numbers
# File Headers
file-style = "#ff79c6" bold # Pink filename
file-decoration-style = "#ff79c6" ul # Underline
# Diff Styling (subtle backgrounds)
minus-style = syntax "#3f2d3d" # Subtle red bg for deletions
minus-emph-style = syntax "#5f3746" # Stronger red for changed parts
minus-non-emph-style = syntax auto
plus-style = syntax "#273a2e" # Subtle green bg for additions
plus-emph-style = syntax "#2f4f39" # Stronger green for changed parts
plus-non-emph-style = syntax auto
# Whitespace Handling
whitespace-error-style = "#ff5555" reverse # Highlight trailing whitespace
# Merge Conflict Styling
merge-conflict-begin-symbol = "⌃"
merge-conflict-end-symbol = "⌄"
merge-conflict-ours-diff-header-style = "#f1fa8c" bold
merge-conflict-theirs-diff-header-style = "#f1fa8c" bold italic
[interactive]
diffFilter = delta --color-only --features=interactive
[delta "interactive"]
keep-plus-minus-markers = false
[diff]
colorMoved = default
+130 -16
View File
@@ -1,23 +1,137 @@
gui: gui:
# Dracula Theme - Official Colors
theme: theme:
activeBorderColor: activeBorderColor:
- "#E97387" - "#ff79c6" # Pink
- bold - bold
inactiveBorderColor: inactiveBorderColor:
- "#a5adce" - "#6272a4" # Comment
optionsTextColor:
- "#8caaee"
selectedLineBgColor:
- "#ECCFD3"
cherryPickedCommitBgColor:
- "#51576d"
cherryPickedCommitFgColor:
- "#CFF2E2"
unstagedChangesColor:
- "#E97387"
defaultFgColor:
- "#6B6B6B"
searchingActiveBorderColor: searchingActiveBorderColor:
- "#e5c890" - "#8be9fd" # Cyan
- bold
optionsTextColor:
- "#bd93f9" # Purple
selectedLineBgColor:
- "#44475a" # Current Line
inactiveViewSelectedLineBgColor:
- bold
cherryPickedCommitFgColor:
- "#8be9fd" # Cyan
cherryPickedCommitBgColor:
- "#44475a" # Current Line
markedBaseCommitFgColor:
- "#f1fa8c" # Yellow
markedBaseCommitBgColor:
- "#44475a" # Current Line
unstagedChangesColor:
- "#ff5555" # Red
defaultFgColor:
- "#f8f8f2" # Foreground
authorColors: authorColors:
"*": "#babbf1" "*": "#bd93f9" # Unified purple for all authors
# Nerd Fonts
nerdFontsVersion: "3"
showFileIcons: true
# Visual Preferences
border: "rounded"
animateExplosion: true
# Info Density (Balanced)
showListFooter: true
showFileTree: true
showCommandLog: true
showBottomLine: true
showPanelJumps: true
commitHashLength: 8
showBranchCommitHash: false
showDivergenceFromBaseBranch: "arrowAndNumber"
# Disable Annoying Popups
skipDiscardChangeWarning: true
skipStashWarning: true
skipRewordInEditorWarning: true
showRandomTip: false
# Window Sizing
scrollHeight: 8
sidePanelWidth: 0.3333
expandFocusedSidePanel: false
# Commit Display
commitAuthorShortLength: 2
commitAuthorLongLength: 17
git:
# Paging (use delta)
pagers:
- colorArg: always
pager: delta
# Auto-behaviors
autoFetch: true
autoRefresh: true
fetchAll: true
autoStageResolvedConflicts: true
# Commit Settings
commit:
signOff: false
autoWrapCommitMessage: true
autoWrapWidth: 72
# Log Display
log:
order: "topo-order"
showGraph: "always"
showWholeGraph: false
# Parsing
parseEmoji: true
# Copy Behavior
truncateCopiedCommitHashesTo: 12
os:
edit: "micro {{filename}}"
editAtLine: "micro {{filename}}:{{line}}"
editAtLineAndWait: "micro {{filename}}:{{line}}"
editInTerminal: true
update:
method: "never"
confirmOnQuit: false
quitOnTopLevelReturn: false
disableStartupPopups: true
promptToReturnFromSubprocess: false
refresher:
refreshInterval: 2 # Fast refresh for file changes
fetchInterval: 60 # Fetch from remote every 60s
customCommands:
- key: "a"
context: "commits"
description: "Show CI status for commit"
command: 'bash -c "gh run list --commit {{.SelectedCommit.Sha}} --json conclusion,status,name,updatedAt --jq ''if length == 0 then \"No CI runs found\" else .[] | \"\\(.name): \\(if .conclusion == \"success\" then \"✓\" elif .conclusion == \"failure\" then \"✗\" elif .status == \"in_progress\" then \"⏳\" else \"?\" end) \\(.conclusion // .status)\" end''"'
loadingText: "Checking CI status..."
- key: "A"
context: "commits"
description: "View CI logs for commit"
command: 'bash -c "RUN_ID=$(gh run list --commit {{.SelectedCommit.Sha}} --json databaseId -q ''.[0].databaseId''); if [ -n \"$RUN_ID\" ]; then gh run view $RUN_ID --log; else echo \"No CI runs found for this commit\"; fi"'
loadingText: "Loading CI logs..."
- key: "a"
context: "localBranches"
description: "Show CI status for branch"
command: 'bash -c "gh run list --branch {{.SelectedLocalBranch.Name}} -L 1 --json conclusion,status,name,updatedAt --jq ''if length == 0 then \"No CI runs for this branch\" else .[0] | \"Latest: \\(.name) - \\(if .conclusion == \"success\" then \"✓ Passed\" elif .conclusion == \"failure\" then \"✗ Failed\" elif .status == \"in_progress\" then \"⏳ Running\" else .status end) (\\(.updatedAt))\" end''"'
loadingText: "Checking CI status..."
- key: "A"
context: "localBranches"
description: "View latest CI run for branch"
command: 'bash -c "RUN_ID=$(gh run list --branch {{.SelectedLocalBranch.Name}} -L 1 --json databaseId -q ''.[0].databaseId''); if [ -n \"$RUN_ID\" ]; then gh run view $RUN_ID --log; else echo \"No CI runs found for this branch\"; fi"'
loadingText: "Loading CI logs..."
- key: "w"
context: "files"
description: "Commit staged changes without pre-commit hooks"
prompts:
- type: "input"
title: "Commit message"
key: "message"
command: 'git commit --no-verify -m "{{.Form.message}}"'
- key: "o"
context: "commits"
description: "Open commit in GitHub"
command: "gh browse {{.SelectedCommit.Sha}}"
- key: "o"
context: "files"
description: "Open file in GitHub"
command: "gh browse {{.SelectedFile.Name}}"
- key: "<c-r>"
context: "localBranches"
description: "Create PR for branch"
command: "gh pr create --fill --web"
-12
View File
@@ -11,10 +11,6 @@
--> -->
<fontconfig> <fontconfig>
<!-- ============================================================
Global Rendering Settings
============================================================ -->
<!-- Enable antialiasing for smooth edges --> <!-- Enable antialiasing for smooth edges -->
<match target="font"> <match target="font">
<edit name="antialias" mode="assign"> <edit name="antialias" mode="assign">
@@ -87,10 +83,6 @@
</edit> </edit>
</match> </match>
<!-- ============================================================
Default Font Aliases
============================================================ -->
<!-- Sans-serif: Inter with Noto Sans fallback --> <!-- Sans-serif: Inter with Noto Sans fallback -->
<alias> <alias>
<family>sans-serif</family> <family>sans-serif</family>
@@ -194,10 +186,6 @@
</prefer> </prefer>
</alias> </alias>
<!-- ============================================================
Font-Specific Tweaks
============================================================ -->
<!-- <!--
Geist Mono: Use slight hinting for clean rendering Geist Mono: Use slight hinting for clean rendering
Geist Mono has good weight distribution Geist Mono has good weight distribution
+3 -5
View File
@@ -59,11 +59,9 @@
editor = micro editor = micro
pager = delta pager = delta
[interactive] # Delta configuration
diffFilter = delta --color-only [include]
path = {{ .chezmoi.sourceDir }}/.managed/git/delta-config
[delta]
navigate = true # use n and N to move between diff sections
[merge] [merge]
conflictStyle = zdiff3 conflictStyle = zdiff3
+25 -57
View File
@@ -18,10 +18,6 @@ import { join, basename } from "node:path";
import { parseArgs } from "node:util"; import { parseArgs } from "node:util";
import { $ } from "bun"; import { $ } from "bun";
// ============================================================================
// Types
// ============================================================================
interface GoogleFont { interface GoogleFont {
id: string; id: string;
family: string; family: string;
@@ -65,13 +61,16 @@ interface FuseResult<T> {
score?: number; score?: number;
} }
// ============================================================================
// Constants
// ============================================================================
const FONTS_DIR = join(homedir(), ".local", "share", "fonts"); const FONTS_DIR = join(homedir(), ".local", "share", "fonts");
// Meta-config location: chezmoi source dir, not deployed to filesystem // Meta-config location: chezmoi source dir, not deployed to filesystem
const CONFIG_PATH = join(homedir(), ".local", "share", "chezmoi", "meta", "fonts.toml"); const CONFIG_PATH = join(
homedir(),
".local",
"share",
"chezmoi",
"meta",
"fonts.toml",
);
const API_BASE = "https://gwfh.mranftl.com/api"; const API_BASE = "https://gwfh.mranftl.com/api";
const CACHE_FILE = join(homedir(), ".cache", "font-catalog.json"); const CACHE_FILE = join(homedir(), ".cache", "font-catalog.json");
const CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days const CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
@@ -124,10 +123,6 @@ const CYAN = "\x1b[36m";
const RESET = "\x1b[0m"; const RESET = "\x1b[0m";
const BOLD = "\x1b[1m"; const BOLD = "\x1b[1m";
// ============================================================================
// Logging
// ============================================================================
const log = { const log = {
info: (msg: string) => console.log(`${BLUE}[info]${RESET} ${msg}`), info: (msg: string) => console.log(`${BLUE}[info]${RESET} ${msg}`),
success: (msg: string) => console.log(`${GREEN}[ok]${RESET} ${msg}`), success: (msg: string) => console.log(`${GREEN}[ok]${RESET} ${msg}`),
@@ -136,10 +131,6 @@ const log = {
step: (msg: string) => console.log(`${CYAN}>>>${RESET} ${msg}`), step: (msg: string) => console.log(`${CYAN}>>>${RESET} ${msg}`),
}; };
// ============================================================================
// Simple Fuzzy Matching (no external deps)
// ============================================================================
function fuzzyScore(needle: string, haystack: string): number { function fuzzyScore(needle: string, haystack: string): number {
const n = needle.toLowerCase(); const n = needle.toLowerCase();
const h = haystack.toLowerCase(); const h = haystack.toLowerCase();
@@ -181,7 +172,7 @@ function fuzzySearch<T>(
query: string, query: string,
getKey: (item: T) => string, getKey: (item: T) => string,
threshold = 0.3, threshold = 0.3,
limit = 5 limit = 5,
): FuseResult<T>[] { ): FuseResult<T>[] {
const results: FuseResult<T>[] = []; const results: FuseResult<T>[] = [];
@@ -197,13 +188,9 @@ function fuzzySearch<T>(
.slice(0, limit); .slice(0, limit);
} }
// ============================================================================
// API Functions
// ============================================================================
async function fetchWithRetry( async function fetchWithRetry(
url: string, url: string,
retries = 3 retries = 3,
): Promise<Response | null> { ): Promise<Response | null> {
for (let i = 0; i < retries; i++) { for (let i = 0; i < retries; i++) {
try { try {
@@ -267,14 +254,7 @@ async function fetchFontDetails(fontId: string): Promise<FontDetails | null> {
return response.json(); return response.json();
} }
// ============================================================================ async function downloadFont(url: string, destPath: string): Promise<boolean> {
// Font Installation
// ============================================================================
async function downloadFont(
url: string,
destPath: string
): Promise<boolean> {
try { try {
const response = await fetchWithRetry(url); const response = await fetchWithRetry(url);
if (!response) return false; if (!response) return false;
@@ -288,10 +268,6 @@ async function downloadFont(
} }
} }
// ============================================================================
// GitHub Font Installation (Iosevka, etc.)
// ============================================================================
interface GitHubRelease { interface GitHubRelease {
tag_name: string; tag_name: string;
assets: Array<{ assets: Array<{
@@ -330,7 +306,9 @@ async function installGitHubFont(fontName: string): Promise<boolean> {
if (!asset) { if (!asset) {
log.error(`No matching asset found for ${fontName} in ${release.tag_name}`); log.error(`No matching asset found for ${fontName} in ${release.tag_name}`);
log.info(`Available assets: ${release.assets.map((a) => a.name).join(", ")}`); log.info(
`Available assets: ${release.assets.map((a) => a.name).join(", ")}`,
);
return false; return false;
} }
@@ -364,7 +342,9 @@ async function installGitHubFont(fontName: string): Promise<boolean> {
rmSync(zipPath, { force: true }); rmSync(zipPath, { force: true });
const files = readdirSync(fontDir).filter((f) => f.endsWith(".ttf")); const files = readdirSync(fontDir).filter((f) => f.endsWith(".ttf"));
log.success(`Installed ${fontName}: ${files.length} files (${release.tag_name})`); log.success(
`Installed ${fontName}: ${files.length} files (${release.tag_name})`,
);
return true; return true;
} }
@@ -398,7 +378,7 @@ function weightToName(weight: number): string {
async function installFont( async function installFont(
fontName: string, fontName: string,
catalog: GoogleFont[] catalog: GoogleFont[],
): Promise<boolean> { ): Promise<boolean> {
// Fuzzy search for the font // Fuzzy search for the font
const results = fuzzySearch(catalog, fontName, (f) => f.family, 0.3, 5); const results = fuzzySearch(catalog, fontName, (f) => f.family, 0.3, 5);
@@ -413,7 +393,7 @@ async function installFont(
fontName, fontName,
(name) => name, (name) => name,
0.3, 0.3,
3 3,
); );
if (githubMatches.length > 0) { if (githubMatches.length > 0) {
const suggestions = githubMatches.map((r) => r.item).join(", "); const suggestions = githubMatches.map((r) => r.item).join(", ");
@@ -427,7 +407,7 @@ async function installFont(
fontName, fontName,
(f) => f.family, (f) => f.family,
0.2, 0.2,
5 5,
); );
if (looseSuggestions.length > 0) { if (looseSuggestions.length > 0) {
const suggestions = looseSuggestions.map((r) => r.item.family).join(", "); const suggestions = looseSuggestions.map((r) => r.item.family).join(", ");
@@ -442,7 +422,7 @@ async function installFont(
// Warn if not an exact match // Warn if not an exact match
if (font.family.toLowerCase() !== fontName.toLowerCase()) { if (font.family.toLowerCase() !== fontName.toLowerCase()) {
log.warn( log.warn(
`"${fontName}" matched to "${font.family}" (score: ${(bestMatch.score ?? 0).toFixed(2)})` `"${fontName}" matched to "${font.family}" (score: ${(bestMatch.score ?? 0).toFixed(2)})`,
); );
} }
@@ -500,7 +480,7 @@ async function installFont(
if (successCount > 0) { if (successCount > 0) {
log.success( log.success(
`Installed ${font.family}: ${successCount} files` + `Installed ${font.family}: ${successCount} files` +
(failCount > 0 ? ` (${failCount} failed)` : "") (failCount > 0 ? ` (${failCount} failed)` : ""),
); );
return true; return true;
} else { } else {
@@ -513,10 +493,6 @@ async function installFont(
} }
} }
// ============================================================================
// Configuration
// ============================================================================
async function loadConfig(): Promise<FontConfig> { async function loadConfig(): Promise<FontConfig> {
if (!existsSync(CONFIG_PATH)) { if (!existsSync(CONFIG_PATH)) {
throw new Error(`Config not found: ${CONFIG_PATH}`); throw new Error(`Config not found: ${CONFIG_PATH}`);
@@ -572,10 +548,6 @@ async function loadConfig(): Promise<FontConfig> {
return config; return config;
} }
// ============================================================================
// Main Commands
// ============================================================================
async function listFonts(): Promise<void> { async function listFonts(): Promise<void> {
const catalog = await fetchFontCatalog(); const catalog = await fetchFontCatalog();
@@ -612,7 +584,7 @@ async function searchFonts(query: string): Promise<void> {
query, query,
(name) => name, (name) => name,
0.2, 0.2,
10 10,
); );
if (results.length === 0 && githubResults.length === 0) { if (results.length === 0 && githubResults.length === 0) {
@@ -627,7 +599,7 @@ async function searchFonts(query: string): Promise<void> {
const fontName = result.item; const fontName = result.item;
const score = ((result.score ?? 0) * 100).toFixed(0); const score = ((result.score ?? 0) * 100).toFixed(0);
console.log( console.log(
` ${GREEN}${fontName}${RESET} (monospace) - via GitHub [${score}% match]` ` ${GREEN}${fontName}${RESET} (monospace) - via GitHub [${score}% match]`,
); );
} }
@@ -637,7 +609,7 @@ async function searchFonts(query: string): Promise<void> {
const score = ((result.score ?? 0) * 100).toFixed(0); const score = ((result.score ?? 0) * 100).toFixed(0);
const variants = Array.isArray(font.variants) ? font.variants.length : "?"; const variants = Array.isArray(font.variants) ? font.variants.length : "?";
console.log( console.log(
` ${GREEN}${font.family}${RESET} (${font.category}) - ${variants} variants [${score}% match]` ` ${GREEN}${font.family}${RESET} (${font.category}) - ${variants} variants [${score}% match]`,
); );
} }
console.log(); console.log();
@@ -711,10 +683,6 @@ async function installFromConfig(): Promise<void> {
} }
} }
// ============================================================================
// Entry Point
// ============================================================================
async function main(): Promise<void> { async function main(): Promise<void> {
const { values, positionals } = parseArgs({ const { values, positionals } = parseArgs({
args: Bun.argv.slice(2), args: Bun.argv.slice(2),
File diff suppressed because it is too large Load Diff