From 3c9858fad830d91a78b23a87c1bfe6f9d4465a41 Mon Sep 17 00:00:00 2001 From: loganbrinsmead Date: Fri, 26 Sep 2025 13:06:50 -0700 Subject: [PATCH 1/2] WIP: Adding correction Hotkey to Harper --- packages/chrome-plugin/src/ProtocolClient.ts | 15 +++- .../chrome-plugin/src/background/index.ts | 34 +++++++++ .../chrome-plugin/src/options/Options.svelte | 72 ++++++++++++++++++- packages/chrome-plugin/src/protocol.ts | 24 +++++++ .../lint-framework/src/lint/LintFramework.ts | 41 +++++++++++ 5 files changed, 184 insertions(+), 2 deletions(-) diff --git a/packages/chrome-plugin/src/ProtocolClient.ts b/packages/chrome-plugin/src/ProtocolClient.ts index c1dfc4de8..1de696def 100644 --- a/packages/chrome-plugin/src/ProtocolClient.ts +++ b/packages/chrome-plugin/src/ProtocolClient.ts @@ -1,7 +1,7 @@ import type { Dialect, LintConfig } from 'harper.js'; import type { UnpackedLint } from 'lint-framework'; import { LRUCache } from 'lru-cache'; -import type { ActivationKey } from './protocol'; +import type { ActivationKey, Hotkey } from './protocol'; export default class ProtocolClient { private static readonly lintCache = new LRUCache>({ @@ -70,6 +70,19 @@ export default class ProtocolClient { return (await chrome.runtime.sendMessage({ kind: 'getActivationKey' })).key; } + public static async getHotkey(): Promise { + return (await chrome.runtime.sendMessage({ kind: 'getHotkey' })).hotkey; + } + + public static async setHotkey(hotkey: Hotkey): Promise { + let modifiers = hotkey.modifiers; + let hotkeyCopy = { + modifiers: [...modifiers], // Create a new array + key: hotkey.key + }; + await chrome.runtime.sendMessage({ kind: 'setHotkey', hotkey: hotkeyCopy }); + } + public static async setActivationKey(key: ActivationKey): Promise { await chrome.runtime.sendMessage({ kind: 'setActivationKey', key }); } diff --git a/packages/chrome-plugin/src/background/index.ts b/packages/chrome-plugin/src/background/index.ts index 57323a9a6..a789c8cb8 100644 --- a/packages/chrome-plugin/src/background/index.ts +++ b/packages/chrome-plugin/src/background/index.ts @@ -2,9 +2,11 @@ import { BinaryModule, Dialect, type LintConfig, LocalLinter } from 'harper.js'; import { unpackLint } from 'lint-framework'; import { ActivationKey, + type Hotkey, type AddToUserDictionaryRequest, createUnitResponse, type GetActivationKeyRequest, + type GetHotkeyResponse, type GetActivationKeyResponse, type GetConfigRequest, type GetConfigResponse, @@ -23,6 +25,7 @@ import { type Request, type Response, type SetActivationKeyRequest, + type SetHotkeyRequest, type SetConfigRequest, type SetDefaultStatusRequest, type SetDialectRequest, @@ -139,6 +142,10 @@ function handleRequest(message: Request): Promise { return handleGetActivationKey(); case 'setActivationKey': return handleSetActivationKey(message); + case 'getHotkey': + return handleGetHotkey(); + case 'setHotkey': + return handleSetHotkey(message); case 'openOptions': chrome.runtime.openOptionsPage(); return createUnitResponse(); @@ -263,6 +270,24 @@ async function handleSetActivationKey(req: SetActivationKeyRequest): Promise { + const hotkey = await getHotkey(); + + return { kind: 'getHotkey', hotkey }; +} + +async function handleSetHotkey(req: SetHotkeyRequest): Promise { + // Create a plain object to avoid proxy cloning issues + const hotkey = { + modifiers: [...req.hotkey.modifiers], + key: req.hotkey.key + }; + await setHotkey(hotkey); + + return createUnitResponse(); +} + + /** Set the lint configuration inside the global `linter` and in permanent storage. */ async function setLintConfig(lintConfig: LintConfig): Promise { await linter.setLintConfig(lintConfig); @@ -305,10 +330,19 @@ async function getActivationKey(): Promise { return resp.activationKey; } +async function getHotkey(): Promise { + const resp = await chrome.storage.local.get({ hotkey: { modifiers: ['Ctrl'], key: 'e' } }); + return resp.hotkey; +} + async function setActivationKey(key: ActivationKey) { await chrome.storage.local.set({ activationKey: key }); } +async function setHotkey(hotkey: Hotkey) { + await chrome.storage.local.set({ hotkey: hotkey }); +} + function initializeLinter(dialect: Dialect) { linter = new LocalLinter({ binary: new BinaryModule(chrome.runtime.getURL('./wasm/harper_wasm_bg.wasm')), diff --git a/packages/chrome-plugin/src/options/Options.svelte b/packages/chrome-plugin/src/options/Options.svelte index 2b08aaf4c..f33ac07aa 100644 --- a/packages/chrome-plugin/src/options/Options.svelte +++ b/packages/chrome-plugin/src/options/Options.svelte @@ -4,6 +4,7 @@ import { Dialect, type LintConfig } from 'harper.js'; import logo from '/logo.png'; import ProtocolClient from '../ProtocolClient'; import { ActivationKey } from '../protocol'; +import type { Modifier, Hotkey } from '../protocol'; let lintConfig: LintConfig = $state({}); let lintDescriptions: Record = $state({}); @@ -13,6 +14,8 @@ let dialect = $state(Dialect.American); let defaultEnabled = $state(false); let activationKey: ActivationKey = $state(ActivationKey.Off); let userDict = $state(''); +let modifyHotkeyButton: Button; +let hotkey : Hotkey = $state({modifiers: ['Ctrl'], key: 'e'}); $effect(() => { ProtocolClient.setLintConfig(lintConfig); @@ -31,7 +34,6 @@ $effect(() => { }); $effect(() => { - console.log('hit'); ProtocolClient.setUserDictionary(stringToDict(userDict)); }); @@ -55,6 +57,16 @@ ProtocolClient.getActivationKey().then((d) => { activationKey = d; }); +ProtocolClient.getHotkey().then((d) => { + // Ensure we have a plain object, not a Proxy + hotkey = { + modifiers: [...d.modifiers], + key: d.key + }; + buttonText = `Hotkey: ${d.modifiers.join('+')}+${d.key}`; +}); + + ProtocolClient.getUserDictionary().then((d) => { userDict = dictToString(d.toSorted()); }); @@ -116,6 +128,52 @@ async function exportEnabledDomainsCSV() { } } +let buttonText = $state('Set Hotkey'); +let isBlue = $state(false); // modify color of hotkey button once it is pressed +function startHotkeyCapture(modifyHotkeyButton: Button) { + + const handleKeydown = (event: KeyboardEvent) => { + event.preventDefault(); + + const modifiers: Modifier[] = []; + if (event.ctrlKey) modifiers.push('Ctrl'); + if (event.shiftKey) modifiers.push('Shift'); + if (event.altKey) modifiers.push('Alt'); + + let key = event.key; + + if (key !== 'Control' && key !== 'Shift' && key !== 'Alt') { + if (modifiers.length === 0) { + return; + } + buttonText = `Hotkey: ${modifiers.join('+')}+${key}`; + // Create a plain object to avoid proxy cloning issues + const newHotkey = { + modifiers: [...modifiers], + key: key + }; + hotkey = newHotkey; + + // Call ProtocolClient directly with the plain object to avoid proxy issues + ProtocolClient.setHotkey(newHotkey); + + // Remove listener + window.removeEventListener('keydown', handleKeydown); + + // change button color + isBlue = !isBlue; + } + + } + + // Add temporary key listener + window.addEventListener('keydown', handleKeydown); + + + }; + + + // Import removed @@ -179,6 +237,18 @@ async function exportEnabledDomainsCSV() { +
+
+
+ Apply Last Suggestion Hotkey + Hotkey to apply the most likely suggestion to previously incorrect lint. +
+