Skip to content
Merged
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
33 changes: 33 additions & 0 deletions configs/shared/fzf.zsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# FZF
# Note: home-directory branch returns nothing (no empty line) to avoid
# cluttering the picker when running fzf from $HOME.
export FZF_DEFAULT_COMMAND='
if [[ "$PWD" != "$HOME" ]]; then
fd --type d --hidden --follow \
--base-directory . \
--exclude node_modules --exclude .git --exclude dist --exclude output --exclude tmp \
2>/dev/null;
fd --type f --hidden --follow \
--base-directory . \
--exclude node_modules --exclude .git --exclude dist --exclude output --exclude tmp \
2>/dev/null
fi
'

export FZF_CTRL_T_COMMAND=$FZF_DEFAULT_COMMAND

export FZF_CTRL_T_OPTS="
--height 100%
--header '[C-/] toggle preview | [Alt-j/k] scroll preview'
--preview 'if [ -f {} ]; then
bat --color=always --style=plain --line-range :300 {};
elif [ -d {} ]; then
eza -L 2 -T --git-ignore {} 2>/dev/null | head -20;
fi'
--preview-window=right:50%:wrap
--bind 'ctrl-/:toggle-preview'
--bind 'alt-j:preview-down'
--bind 'alt-k:preview-up'
--bind 'ctrl-d:preview-page-down'
--bind 'ctrl-u:preview-page-up'
"
6 changes: 5 additions & 1 deletion configs/shared/prompt.zsh
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# ============================================================================
# Prompt / theme loading
# Prompt / theme loading — must run last
# ============================================================================

# For zinit-based setups: load p10k as the final plugin so it wraps everything
(( ${+functions[zinit]} )) && { zinit ice depth"1"; zinit light romkatv/powerlevel10k; }

# Apply p10k configuration
[[ -f ~/.p10k.zsh ]] && source ~/.p10k.zsh
35 changes: 2 additions & 33 deletions configs/shared/tools.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,8 @@
# External tool configuration and initialization
# ============================================================================

# FZF
# Note: home-directory branch returns nothing (no empty line) to avoid
# cluttering the picker when running fzf from $HOME.
export FZF_DEFAULT_COMMAND='
if [[ "$PWD" != "$HOME" ]]; then
fd --type d --hidden --follow \
--base-directory . \
--exclude node_modules --exclude .git --exclude dist --exclude output --exclude tmp \
2>/dev/null;
fd --type f --hidden --follow \
--base-directory . \
--exclude node_modules --exclude .git --exclude dist --exclude output --exclude tmp \
2>/dev/null
fi
'

export FZF_CTRL_T_COMMAND=$FZF_DEFAULT_COMMAND

export FZF_CTRL_T_OPTS="
--height 100%
--header '[C-/] toggle preview | [Alt-j/k] scroll preview'
--preview 'if [ -f {} ]; then
bat --color=always --style=plain --line-range :300 {};
elif [ -d {} ]; then
eza -L 2 -T --git-ignore {} 2>/dev/null | head -20;
fi'
--preview-window=right:50%:wrap
--bind 'ctrl-/:toggle-preview'
--bind 'alt-j:preview-down'
--bind 'alt-k:preview-up'
--bind 'ctrl-d:preview-page-down'
--bind 'ctrl-u:preview-page-up'
"
# FZF configuration (env vars for fd-based search and preview bindings)
[[ -f "${ZDOTDIR:-$HOME/.config/zsh}/shared/fzf.zsh" ]] && source "${ZDOTDIR:-$HOME/.config/zsh}/shared/fzf.zsh"

_zsh_tools_cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/zsh"
[[ -d "$_zsh_tools_cache_dir" ]] || mkdir -p "$_zsh_tools_cache_dir"
Expand Down
2 changes: 0 additions & 2 deletions configs/zinit-plugins
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,5 @@

zinit load 'zsh-users/zsh-autosuggestions'
zinit load 'zsh-users/zsh-syntax-highlighting'
zinit ice depth"1"
zinit light romkatv/powerlevel10k

# <<< suitup zinit-plugins <<<
6 changes: 2 additions & 4 deletions src/append.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,8 @@ const BLOCKS = [
group: "Advanced",
marker: "suitup/fzf-config",
apply() {
const toolsContent = readFileSync(join(CONFIGS_DIR, "shared", "tools.zsh"), "utf-8");
// Extract only FZF-related config (everything before "# Tool initialization")
const fzfPart = toolsContent.split("# Tool initialization")[0].trim();
const block = `\n# >>> suitup/fzf-config >>>\n${fzfPart}\n# <<< suitup/fzf-config <<<\n`;
const fzfContent = readFileSync(join(CONFIGS_DIR, "shared", "fzf.zsh"), "utf-8");
const block = `\n# >>> suitup/fzf-config >>>\n${fzfContent.trim()}\n# <<< suitup/fzf-config <<<\n`;
return appendIfMissing(ZSHRC, block, "suitup/fzf-config");
},
},
Expand Down
2 changes: 1 addition & 1 deletion src/steps/zsh-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export async function setupZshConfig({ home } = {}) {
}

// Copy shared configs (skip if already exist)
const sharedFiles = ["tools.zsh", "prompt.zsh"];
const sharedFiles = ["tools.zsh", "fzf.zsh", "prompt.zsh"];
for (const file of sharedFiles) {
const copied = copyIfNotExists(join(CONFIGS_DIR, "shared", file), join(zshConfig, "shared", file));
if (!copied) p.log.info(`Skipped shared/${file} (already exists)`);
Expand Down
70 changes: 70 additions & 0 deletions tests/append.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { mkdtempSync, readFileSync, writeFileSync, rmSync, existsSync } from "no
import { join } from "node:path";
import { tmpdir } from "node:os";
import { appendIfMissing, ensureDir, readFileSafe } from "../src/utils/fs.js";
import { CONFIGS_DIR } from "../src/constants.js";

describe("Append mode utilities", () => {
let sandbox;
Expand Down Expand Up @@ -86,3 +87,72 @@ describe("Append mode utilities", () => {
expect(content).toContain("# base");
});
});

describe("fzf-config block", () => {
let sandbox;
let zshrcPath;

beforeEach(() => {
sandbox = mkdtempSync(join(tmpdir(), "suitup-test-"));
zshrcPath = join(sandbox, ".zshrc");
});

afterEach(() => {
rmSync(sandbox, { recursive: true, force: true });
});

test("configs/shared/fzf.zsh exists as a standalone file", () => {
const fzfFile = join(CONFIGS_DIR, "shared", "fzf.zsh");
expect(existsSync(fzfFile)).toBe(true);
});

test("fzf.zsh contains expected FZF environment variables", () => {
const fzfContent = readFileSync(join(CONFIGS_DIR, "shared", "fzf.zsh"), "utf-8");
expect(fzfContent).toContain("FZF_DEFAULT_COMMAND");
expect(fzfContent).toContain("FZF_CTRL_T_COMMAND");
expect(fzfContent).toContain("FZF_CTRL_T_OPTS");
});

test("fzf.zsh does not contain tool-init cache helpers (those belong in tools.zsh)", () => {
const fzfContent = readFileSync(join(CONFIGS_DIR, "shared", "fzf.zsh"), "utf-8");
expect(fzfContent).not.toContain("_source_cached_tool_init");
expect(fzfContent).not.toContain("_zsh_tools_cache_dir");
});

test("tools.zsh no longer embeds FZF env vars directly", () => {
const toolsContent = readFileSync(join(CONFIGS_DIR, "shared", "tools.zsh"), "utf-8");
expect(toolsContent).not.toContain("FZF_DEFAULT_COMMAND=");
expect(toolsContent).not.toContain("FZF_CTRL_T_OPTS=");
});

test("fzf-config block appends fzf.zsh content directly (no brittle string splitting)", () => {
writeFileSync(zshrcPath, "# base\n", "utf-8");

const fzfContent = readFileSync(join(CONFIGS_DIR, "shared", "fzf.zsh"), "utf-8").trim();
const block = `\n# >>> suitup/fzf-config >>>\n${fzfContent}\n# <<< suitup/fzf-config <<<\n`;

const result = appendIfMissing(zshrcPath, block, "suitup/fzf-config");

expect(result).toBe(true);
const written = readFileSync(zshrcPath, "utf-8");
expect(written).toContain("FZF_DEFAULT_COMMAND");
expect(written).toContain("FZF_CTRL_T_OPTS");
expect(written).toContain("# >>> suitup/fzf-config >>>");
expect(written).toContain("# <<< suitup/fzf-config <<<");
});

test("fzf-config block is idempotent — double append does not duplicate", () => {
writeFileSync(zshrcPath, "# base\n", "utf-8");

const fzfContent = readFileSync(join(CONFIGS_DIR, "shared", "fzf.zsh"), "utf-8").trim();
const block = `\n# >>> suitup/fzf-config >>>\n${fzfContent}\n# <<< suitup/fzf-config <<<\n`;

appendIfMissing(zshrcPath, block, "suitup/fzf-config");
appendIfMissing(zshrcPath, block, "suitup/fzf-config");

const written = readFileSync(zshrcPath, "utf-8");
const matches = written.match(/suitup\/fzf-config/g);
// Should appear exactly twice (open + close marker), not four
expect(matches.length).toBe(2);
});
});
29 changes: 25 additions & 4 deletions tests/configs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,19 @@ describe("Static config templates", () => {
expect(content).toContain("zinit");
expect(content).toContain("zsh-autosuggestions");
expect(content).toContain("zsh-syntax-highlighting");
// p10k is loaded last in prompt.zsh, not here
expect(content).not.toContain("powerlevel10k");
});

test("shared/prompt.zsh loads p10k theme last (after all other plugins)", () => {
const content = readFileSync(join(CONFIGS_DIR, "shared", "prompt.zsh"), "utf-8");
expect(content).toContain("powerlevel10k");
expect(content).toContain("zinit light romkatv/powerlevel10k");
expect(content).toContain("~/.p10k.zsh");
// p10k theme load must appear before .p10k.zsh source
const themeIdx = content.indexOf("zinit light romkatv/powerlevel10k");
const configIdx = content.indexOf("~/.p10k.zsh");
expect(themeIdx).toBeLessThan(configIdx);
});

test("config.vim file exists", () => {
Expand All @@ -70,7 +82,7 @@ describe("Static config templates", () => {
});

test("shared config files exist", () => {
for (const file of ["tools.zsh", "prompt.zsh"]) {
for (const file of ["tools.zsh", "prompt.zsh", "fzf.zsh"]) {
expect(existsSync(join(CONFIGS_DIR, "shared", file))).toBe(true);
}
});
Expand All @@ -94,13 +106,20 @@ describe("Static config templates", () => {
}
});

test("shared/tools.zsh uses fd instead of rg for FZF_DEFAULT_COMMAND", () => {
test("shared/fzf.zsh uses fd instead of rg for FZF_DEFAULT_COMMAND", () => {
const fzfContent = readFileSync(join(CONFIGS_DIR, "shared", "fzf.zsh"), "utf-8");
expect(fzfContent).toContain("fd --type");
expect(fzfContent).toContain("FZF_DEFAULT_COMMAND");
expect(fzfContent).toContain("FZF_CTRL_T_COMMAND");
expect(fzfContent).toContain("FZF_CTRL_T_OPTS");
});

test("shared/tools.zsh contains tool-init helpers and sources fzf.zsh", () => {
const content = readFileSync(join(CONFIGS_DIR, "shared", "tools.zsh"), "utf-8");
expect(content).toContain("fd --type");
expect(content).toContain("fnm");
expect(content).toContain("atuin");
expect(content).toContain("zoxide");
expect(content).toContain("command -v");
expect(content).toContain("fzf.zsh");
});

test("all .zsh config files pass syntax check", () => {
Expand All @@ -110,6 +129,7 @@ describe("Static config templates", () => {
"core/paths.zsh",
"core/options.zsh",
"shared/tools.zsh",
"shared/fzf.zsh",
"shared/prompt.zsh",
"local/machine.zsh",
];
Expand Down Expand Up @@ -144,6 +164,7 @@ describe("Static config templates", () => {
"core/paths.zsh",
"core/options.zsh",
"shared/tools.zsh",
"shared/fzf.zsh",
"shared/prompt.zsh",
"zshrc.template",
"zshrc-omz.template",
Expand Down
7 changes: 7 additions & 0 deletions tests/setup.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ describe("Setup simulation in sandbox", () => {
expect(content).toContain("suitup/aliases");
expect(content).toContain("shared/prompt.zsh");
expect(content).toContain("_zsh_report");

// prompt.zsh (which loads p10k last) must come after zinit-plugins
const pluginsIdx = content.indexOf("suitup/zinit-plugins");
const promptIdx = content.indexOf("shared/prompt.zsh");
const reportIdx = content.indexOf("_zsh_report");
expect(pluginsIdx).toBeLessThan(promptIdx);
expect(promptIdx).toBeLessThan(reportIdx);
});

test("omz template has Oh My Zsh structure", () => {
Expand Down
4 changes: 4 additions & 0 deletions tests/zsh-config-steps.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ describe("zsh-config step", () => {
expect(existsSync(join(sandbox.path, ".config", "zsh", "core", "paths.zsh"))).toBe(true);
expect(existsSync(join(sandbox.path, ".config", "zsh", "core", "options.zsh"))).toBe(true);
expect(existsSync(join(sandbox.path, ".config", "zsh", "shared", "tools.zsh"))).toBe(true);
expect(existsSync(join(sandbox.path, ".config", "zsh", "shared", "fzf.zsh"))).toBe(true);
expect(existsSync(join(sandbox.path, ".config", "zsh", "shared", "prompt.zsh"))).toBe(true);
expect(existsSync(join(sandbox.path, ".config", "zsh", "local", "machine.zsh"))).toBe(true);
});
Expand All @@ -48,11 +49,14 @@ describe("zsh-config step", () => {

const perf = readFileSync(join(sandbox.path, ".config", "zsh", "core", "perf.zsh"), "utf-8");
const tools = readFileSync(join(sandbox.path, ".config", "zsh", "shared", "tools.zsh"), "utf-8");
const fzf = readFileSync(join(sandbox.path, ".config", "zsh", "shared", "fzf.zsh"), "utf-8");

expect(perf).toContain("EPOCHREALTIME");
expect(perf).toContain("_record_stage_duration");
expect(tools).toContain("_source_cached_tool_init");
expect(tools).toContain("$_zsh_tools_cache_dir");
expect(fzf).toContain("FZF_DEFAULT_COMMAND");
expect(fzf).toContain("FZF_CTRL_T_OPTS");
});

test("skips existing config files without overwriting", async () => {
Expand Down
Loading