Add option to append trailing space after pasted text (#405)

This commit is contained in:
Danny Smith 2025-12-05 10:37:33 +00:00 committed by GitHub
parent 244a99e623
commit 246feda45d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 61 additions and 1 deletions

View file

@ -234,6 +234,13 @@ pub fn paste(text: String, app_handle: AppHandle) -> Result<(), String> {
let settings = get_settings(&app_handle);
let paste_method = settings.paste_method;
// Append trailing space if setting is enabled
let text = if settings.append_trailing_space {
format!("{} ", text)
} else {
text
};
info!("Using paste method: {:?}", paste_method);
// Perform the paste operation

View file

@ -253,6 +253,7 @@ pub fn run() {
shortcut::suspend_binding,
shortcut::resume_binding,
shortcut::change_mute_while_recording_setting,
shortcut::change_append_trailing_space_setting,
shortcut::change_update_checks_setting,
trigger_update_check,
commands::cancel_operation,

View file

@ -285,6 +285,8 @@ pub struct AppSettings {
pub post_process_selected_prompt_id: Option<String>,
#[serde(default)]
pub mute_while_recording: bool,
#[serde(default)]
pub append_trailing_space: bool,
}
fn default_model() -> String {
@ -483,6 +485,7 @@ pub fn get_default_settings() -> AppSettings {
post_process_prompts: default_post_process_prompts(),
post_process_selected_prompt_id: None,
mute_while_recording: false,
append_trailing_space: false,
}
}

View file

@ -693,6 +693,16 @@ pub fn change_mute_while_recording_setting(app: AppHandle, enabled: bool) -> Res
Ok(())
}
#[tauri::command]
#[specta::specta]
pub fn change_append_trailing_space_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
let mut settings = settings::get_settings(&app);
settings.append_trailing_space = enabled;
settings::write_settings(&app, settings);
Ok(())
}
/// Determine whether a shortcut string contains at least one non-modifier key.
/// We allow single non-modifier keys (e.g. "f5" or "space") but disallow
/// modifier-only combos (e.g. "ctrl" or "ctrl+shift").

View file

@ -244,6 +244,14 @@ async changeMuteWhileRecordingSetting(enabled: boolean) : Promise<Result<null, s
else return { status: "error", error: e as any };
}
},
async changeAppendTrailingSpaceSetting(enabled: boolean) : Promise<Result<null, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("change_append_trailing_space_setting", { enabled }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async changeUpdateChecksSetting(enabled: boolean) : Promise<Result<null, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("change_update_checks_setting", { enabled }) };
@ -602,7 +610,7 @@ async isLaptop() : Promise<Result<boolean, string>> {
/** user-defined types **/
export type AppSettings = { bindings: Partial<{ [key in string]: ShortcutBinding }>; push_to_talk: boolean; audio_feedback: boolean; audio_feedback_volume?: number; sound_theme?: SoundTheme; start_hidden?: boolean; autostart_enabled?: boolean; update_checks_enabled?: boolean; selected_model?: string; always_on_microphone?: boolean; selected_microphone?: string | null; clamshell_microphone?: string | null; selected_output_device?: string | null; translate_to_english?: boolean; selected_language?: string; overlay_position?: OverlayPosition; debug_mode?: boolean; log_level?: LogLevel; custom_words?: string[]; model_unload_timeout?: ModelUnloadTimeout; word_correction_threshold?: number; history_limit?: string; recording_retention_period?: RecordingRetentionPeriod; paste_method?: PasteMethod; clipboard_handling?: ClipboardHandling; post_process_enabled?: boolean; post_process_provider_id?: string; post_process_providers?: PostProcessProvider[]; post_process_api_keys?: Partial<{ [key in string]: string }>; post_process_models?: Partial<{ [key in string]: string }>; post_process_prompts?: LLMPrompt[]; post_process_selected_prompt_id?: string | null; mute_while_recording?: boolean }
export type AppSettings = { bindings: Partial<{ [key in string]: ShortcutBinding }>; push_to_talk: boolean; audio_feedback: boolean; audio_feedback_volume?: number; sound_theme?: SoundTheme; start_hidden?: boolean; autostart_enabled?: boolean; update_checks_enabled?: boolean; selected_model?: string; always_on_microphone?: boolean; selected_microphone?: string | null; clamshell_microphone?: string | null; selected_output_device?: string | null; translate_to_english?: boolean; selected_language?: string; overlay_position?: OverlayPosition; debug_mode?: boolean; log_level?: LogLevel; custom_words?: string[]; model_unload_timeout?: ModelUnloadTimeout; word_correction_threshold?: number; history_limit?: string; recording_retention_period?: RecordingRetentionPeriod; paste_method?: PasteMethod; clipboard_handling?: ClipboardHandling; post_process_enabled?: boolean; post_process_provider_id?: string; post_process_providers?: PostProcessProvider[]; post_process_api_keys?: Partial<{ [key in string]: string }>; post_process_models?: Partial<{ [key in string]: string }>; post_process_prompts?: LLMPrompt[]; post_process_selected_prompt_id?: string | null; mute_while_recording?: boolean; append_trailing_space?: boolean }
export type AudioDevice = { index: string; name: string; is_default: boolean }
export type BindingResponse = { success: boolean; binding: ShortcutBinding | null; error: string | null }
export type ClipboardHandling = "dont_modify" | "copy_to_clipboard"

View file

@ -0,0 +1,27 @@
import React from "react";
import { ToggleSwitch } from "../ui/ToggleSwitch";
import { useSettings } from "../../hooks/useSettings";
interface AppendTrailingSpaceProps {
descriptionMode?: "inline" | "tooltip";
grouped?: boolean;
}
export const AppendTrailingSpace: React.FC<AppendTrailingSpaceProps> =
React.memo(({ descriptionMode = "tooltip", grouped = false }) => {
const { getSetting, updateSetting, isUpdating } = useSettings();
const enabled = getSetting("append_trailing_space") ?? false;
return (
<ToggleSwitch
checked={enabled}
onChange={(enabled) => updateSetting("append_trailing_space", enabled)}
isUpdating={isUpdating("append_trailing_space")}
label="Append Trailing Space"
description="Automatically add a space at the end of transcribed text, making it easier to dictate multiple sentences in a row."
descriptionMode={descriptionMode}
grouped={grouped}
/>
);
});

View file

@ -9,6 +9,7 @@ import { AlwaysOnMicrophone } from "../AlwaysOnMicrophone";
import { SoundPicker } from "../SoundPicker";
import { PostProcessingToggle } from "../PostProcessingToggle";
import { MuteWhileRecording } from "../MuteWhileRecording";
import { AppendTrailingSpace } from "../AppendTrailingSpace";
import { RecordingRetentionPeriodSelector } from "../RecordingRetentionPeriod";
import { ClamshellMicrophoneSelector } from "../ClamshellMicrophoneSelector";
import { HandyShortcut } from "../HandyShortcut";
@ -40,6 +41,7 @@ export const DebugSettings: React.FC = () => {
<ClamshellMicrophoneSelector descriptionMode="tooltip" grouped={true} />
<PostProcessingToggle descriptionMode="tooltip" grouped={true} />
<MuteWhileRecording descriptionMode="tooltip" grouped={true} />
<AppendTrailingSpace descriptionMode="tooltip" grouped={true} />
{/* Cancel shortcut is disabled on Linux due to instability with dynamic shortcut registration */}
{!isLinux && (
<HandyShortcut

View file

@ -121,6 +121,8 @@ const settingUpdaters: {
commands.setPostProcessSelectedPrompt(value as string),
mute_while_recording: (value) =>
commands.changeMuteWhileRecordingSetting(value as boolean),
append_trailing_space: (value) =>
commands.changeAppendTrailingSpaceSetting(value as boolean),
log_level: (value) => commands.setLogLevel(value as any),
};