diff --git a/changes/rofi-prompt-spacing.md b/changes/rofi-prompt-spacing.md new file mode 100644 index 00000000..74c9f814 --- /dev/null +++ b/changes/rofi-prompt-spacing.md @@ -0,0 +1,4 @@ +type: fixed +area: launcher + +- Rofi menu prompts now keep a space between the prompt text and the input field instead of running into the search placeholder. diff --git a/launcher/commands/history-command.ts b/launcher/commands/history-command.ts index 594f370b..352bbee5 100644 --- a/launcher/commands/history-command.ts +++ b/launcher/commands/history-command.ts @@ -7,6 +7,7 @@ import { collectVideos, findRofiTheme, formatPickerLaunchError, + formatRofiPrompt, showFzfMenu, showRofiMenu, } from '../picker.js'; @@ -199,7 +200,16 @@ function showRofiIndexMenu( themePath: string | null, icons: Array = [], ): number { - const rofiArgs = ['-dmenu', '-i', '-matching', 'fuzzy', '-format', 'i', '-p', prompt]; + const rofiArgs = [ + '-dmenu', + '-i', + '-matching', + 'fuzzy', + '-format', + 'i', + '-p', + formatRofiPrompt(prompt), + ]; const hasIcons = icons.some(Boolean); if (hasIcons) rofiArgs.push('-show-icons'); if (themePath) { diff --git a/launcher/picker.test.ts b/launcher/picker.test.ts index fd47a62d..281d1d1f 100644 --- a/launcher/picker.test.ts +++ b/launcher/picker.test.ts @@ -3,7 +3,22 @@ import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import os from 'node:os'; -import { findRofiTheme } from './picker'; +import { findRofiTheme, formatRofiPrompt } from './picker'; + +// ── formatRofiPrompt: spacing between prompt and input field ────────────────── + +test('formatRofiPrompt appends a single trailing space', () => { + assert.equal(formatRofiPrompt('Select Video'), 'Select Video '); +}); + +test('formatRofiPrompt collapses existing trailing whitespace to one space', () => { + assert.equal(formatRofiPrompt('Watch History '), 'Watch History '); +}); + +test('formatRofiPrompt leaves an empty prompt empty', () => { + assert.equal(formatRofiPrompt(''), ''); + assert.equal(formatRofiPrompt(' '), ''); +}); // ── findRofiTheme: Linux packaged path discovery ────────────────────────────── diff --git a/launcher/picker.ts b/launcher/picker.ts index dc731114..c35e3205 100644 --- a/launcher/picker.ts +++ b/launcher/picker.ts @@ -17,13 +17,22 @@ export function escapeShellSingle(value: string): string { return `'${value.replace(/'/g, `'\\''`)}'`; } +/** + * Rofi renders the prompt flush against the input field, so keep exactly one + * trailing space to separate them. + */ +export function formatRofiPrompt(prompt: string): string { + const trimmed = prompt.trimEnd(); + return trimmed ? `${trimmed} ` : ''; +} + export function showRofiFlatMenu( items: string[], prompt: string, initialQuery = '', themePath: string | null = null, ): string { - const args = ['-dmenu', '-i', '-matching', 'fuzzy', '-p', prompt]; + const args = ['-dmenu', '-i', '-matching', 'fuzzy', '-p', formatRofiPrompt(prompt)]; if (themePath) { args.push('-theme', themePath); } else { @@ -110,7 +119,7 @@ export async function promptOptionalJellyfinSearch( themePath: string | null = null, ): Promise { if (useRofi && commandExists('rofi')) { - const rofiArgs = ['-dmenu', '-i', '-p', 'Jellyfin Search (optional)']; + const rofiArgs = ['-dmenu', '-i', '-p', formatRofiPrompt('Jellyfin Search (optional)')]; if (themePath) { rofiArgs.push('-theme', themePath); } else { @@ -157,7 +166,7 @@ function showRofiIconMenu( themePath: string | null = null, ): number { if (entries.length === 0) return -1; - const rofiArgs = ['-dmenu', '-i', '-show-icons', '-format', 'i', '-p', prompt]; + const rofiArgs = ['-dmenu', '-i', '-show-icons', '-format', 'i', '-p', formatRofiPrompt(prompt)]; if (initialQuery) rofiArgs.push('-filter', initialQuery); if (themePath) { rofiArgs.push('-theme', themePath); @@ -391,7 +400,7 @@ export function showRofiMenu( '-dmenu', '-i', '-p', - 'Select Video ', + formatRofiPrompt('Select Video'), '-show-icons', '-theme-str', 'configuration { font: "Noto Sans CJK JP Regular 8";}',