Skip to content

Commit b5825b4

Browse files
authored
feat: Implement ability to choose key combination to send messages (#517)
1 parent 6607ee3 commit b5825b4

File tree

4 files changed

+111
-22
lines changed

4 files changed

+111
-22
lines changed

apps/array/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { toast } from "@renderer/utils/toast";
2+
import { useSettingsStore } from "@stores/settingsStore";
23
import { useEditor } from "@tiptap/react";
34
import { useCallback, useRef, useState } from "react";
45
import { usePromptHistoryStore } from "../stores/promptHistoryStore";
@@ -98,14 +99,24 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) {
9899
editorProps: {
99100
attributes: { class: EDITOR_CLASS },
100101
handleKeyDown: (view, event) => {
101-
if (event.key === "Enter" && !event.shiftKey) {
102-
if (!view.editable) return false;
103-
const suggestionPopup = document.querySelector("[data-tippy-root]");
104-
if (suggestionPopup) return false;
105-
event.preventDefault();
106-
historyActions.reset();
107-
submitRef.current();
108-
return true;
102+
if (event.key === "Enter") {
103+
const sendMessagesWith =
104+
useSettingsStore.getState().sendMessagesWith;
105+
const isCmdEnterMode = sendMessagesWith === "cmd+enter";
106+
const isSubmitKey = isCmdEnterMode
107+
? event.metaKey || event.ctrlKey
108+
: !event.shiftKey;
109+
110+
if (isSubmitKey) {
111+
if (!view.editable) return false;
112+
const suggestionPopup =
113+
document.querySelector("[data-tippy-root]");
114+
if (suggestionPopup) return false;
115+
event.preventDefault();
116+
historyActions.reset();
117+
submitRef.current();
118+
return true;
119+
}
109120
}
110121

111122
if (

apps/array/src/renderer/features/settings/components/SettingsView.tsx

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { useAuthStore } from "@features/auth/stores/authStore";
22
import { FolderPicker } from "@features/folder-picker/components/FolderPicker";
3-
import { useSettingsStore } from "@features/settings/stores/settingsStore";
3+
import {
4+
type SendMessagesWith,
5+
useSettingsStore,
6+
} from "@features/settings/stores/settingsStore";
47
import { useMeQuery } from "@hooks/useMeQuery";
58
import { useProjectQuery } from "@hooks/useProjectQuery";
69
import { useSetHeaderContent } from "@hooks/useSetHeaderContent";
@@ -56,10 +59,12 @@ export function SettingsView() {
5659
createPR,
5760
cursorGlow,
5861
desktopNotifications,
62+
sendMessagesWith,
5963
setAutoRunTasks,
6064
setCreatePR,
6165
setCursorGlow,
6266
setDesktopNotifications,
67+
setSendMessagesWith,
6368
} = useSettingsStore();
6469
const terminalLayoutMode = useTerminalLayoutStore(
6570
(state) => state.terminalLayoutMode,
@@ -160,6 +165,18 @@ export function SettingsView() {
160165
[terminalLayoutMode, setTerminalLayout],
161166
);
162167

168+
const handleSendMessagesWithChange = useCallback(
169+
(value: SendMessagesWith) => {
170+
track(ANALYTICS_EVENTS.SETTING_CHANGED, {
171+
setting_name: "send_messages_with",
172+
new_value: value,
173+
old_value: sendMessagesWith,
174+
});
175+
setSendMessagesWith(value);
176+
},
177+
[sendMessagesWith, setSendMessagesWith],
178+
);
179+
163180
const handleWorktreeLocationChange = async (newLocation: string) => {
164181
setLocalWorktreeLocation(newLocation);
165182
try {
@@ -341,20 +358,48 @@ export function SettingsView() {
341358
<Flex direction="column" gap="3">
342359
<Heading size="3">Chat</Heading>
343360
<Card>
344-
<Flex align="center" justify="between">
345-
<Flex direction="column" gap="1">
346-
<Text size="1" weight="medium">
347-
Desktop notifications
348-
</Text>
349-
<Text size="1" color="gray">
350-
Show notifications when the agent finishes working on a task
351-
</Text>
361+
<Flex direction="column" gap="4">
362+
<Flex align="center" justify="between">
363+
<Flex direction="column" gap="1">
364+
<Text size="1" weight="medium">
365+
Desktop notifications
366+
</Text>
367+
<Text size="1" color="gray">
368+
Show notifications when the agent finishes working on a
369+
task
370+
</Text>
371+
</Flex>
372+
<Switch
373+
checked={desktopNotifications}
374+
onCheckedChange={setDesktopNotifications}
375+
size="1"
376+
/>
377+
</Flex>
378+
379+
<Flex align="center" justify="between">
380+
<Flex direction="column" gap="1">
381+
<Text size="1" weight="medium">
382+
Send messages with
383+
</Text>
384+
<Text size="1" color="gray">
385+
Choose which key combination sends messages. Use
386+
Shift+Enter for new lines.
387+
</Text>
388+
</Flex>
389+
<Select.Root
390+
value={sendMessagesWith}
391+
onValueChange={(value) =>
392+
handleSendMessagesWithChange(value as SendMessagesWith)
393+
}
394+
size="1"
395+
>
396+
<Select.Trigger />
397+
<Select.Content>
398+
<Select.Item value="enter">Enter</Select.Item>
399+
<Select.Item value="cmd+enter">⌘ Enter</Select.Item>
400+
</Select.Content>
401+
</Select.Root>
352402
</Flex>
353-
<Switch
354-
checked={desktopNotifications}
355-
onCheckedChange={setDesktopNotifications}
356-
size="1"
357-
/>
358403
</Flex>
359404
</Card>
360405
</Flex>

apps/array/src/renderer/features/settings/stores/settingsStore.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { persist } from "zustand/middleware";
55

66
export type DefaultRunMode = "local" | "cloud" | "last_used";
77
export type LocalWorkspaceMode = "worktree" | "root";
8+
export type SendMessagesWith = "enter" | "cmd+enter";
89

910
interface SettingsStore {
1011
autoRunTasks: boolean;
@@ -16,6 +17,7 @@ interface SettingsStore {
1617
defaultModel: string;
1718
desktopNotifications: boolean;
1819
cursorGlow: boolean;
20+
sendMessagesWith: SendMessagesWith;
1921

2022
setAutoRunTasks: (autoRun: boolean) => void;
2123
setDefaultRunMode: (mode: DefaultRunMode) => void;
@@ -26,6 +28,7 @@ interface SettingsStore {
2628
setDefaultModel: (model: string) => void;
2729
setDesktopNotifications: (enabled: boolean) => void;
2830
setCursorGlow: (enabled: boolean) => void;
31+
setSendMessagesWith: (mode: SendMessagesWith) => void;
2932
}
3033

3134
export const useSettingsStore = create<SettingsStore>()(
@@ -40,6 +43,7 @@ export const useSettingsStore = create<SettingsStore>()(
4043
defaultModel: DEFAULT_MODEL,
4144
desktopNotifications: true,
4245
cursorGlow: false,
46+
sendMessagesWith: "enter",
4347

4448
setAutoRunTasks: (autoRun) => set({ autoRunTasks: autoRun }),
4549
setDefaultRunMode: (mode) => set({ defaultRunMode: mode }),
@@ -52,6 +56,7 @@ export const useSettingsStore = create<SettingsStore>()(
5256
setDesktopNotifications: (enabled) =>
5357
set({ desktopNotifications: enabled }),
5458
setCursorGlow: (enabled) => set({ cursorGlow: enabled }),
59+
setSendMessagesWith: (mode) => set({ sendMessagesWith: mode }),
5560
}),
5661
{
5762
name: "settings-storage",

apps/array/src/renderer/stores/settingsStore.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@ import { create } from "zustand";
22
import { trpcVanilla } from "../trpc";
33

44
export type TerminalLayoutMode = "split" | "tabbed";
5+
export type SendMessagesWith = "enter" | "cmd+enter";
56

67
interface SettingsState {
78
terminalLayoutMode: TerminalLayoutMode;
9+
sendMessagesWith: SendMessagesWith;
810
isLoading: boolean;
911
loadTerminalLayout: () => Promise<void>;
1012
setTerminalLayout: (mode: TerminalLayoutMode) => Promise<void>;
13+
loadSendMessagesWith: () => Promise<void>;
14+
setSendMessagesWith: (mode: SendMessagesWith) => Promise<void>;
1115
}
1216

1317
export const useSettingsStore = create<SettingsState>()((set) => ({
1418
terminalLayoutMode: "split",
19+
sendMessagesWith: "enter",
1520
isLoading: true,
1621

1722
loadTerminalLayout: async () => {
@@ -37,4 +42,27 @@ export const useSettingsStore = create<SettingsState>()((set) => ({
3742
set({ terminalLayoutMode: mode });
3843
} catch (_error) {}
3944
},
45+
46+
loadSendMessagesWith: async () => {
47+
try {
48+
const mode = await trpcVanilla.secureStore.getItem.query({
49+
key: "sendMessagesWith",
50+
});
51+
if (mode === "enter" || mode === "cmd+enter") {
52+
set({ sendMessagesWith: mode });
53+
}
54+
} catch (_error) {
55+
// Keep default value
56+
}
57+
},
58+
59+
setSendMessagesWith: async (mode: SendMessagesWith) => {
60+
try {
61+
await trpcVanilla.secureStore.setItem.query({
62+
key: "sendMessagesWith",
63+
value: mode,
64+
});
65+
set({ sendMessagesWith: mode });
66+
} catch (_error) {}
67+
},
4068
}));

0 commit comments

Comments
 (0)