diff --git a/apps/array/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts b/apps/array/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts
index c57ba7566..0d34f07a6 100644
--- a/apps/array/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts
+++ b/apps/array/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts
@@ -1,4 +1,5 @@
import { toast } from "@renderer/utils/toast";
+import { useSettingsStore } from "@stores/settingsStore";
import { useEditor } from "@tiptap/react";
import { useCallback, useRef, useState } from "react";
import { usePromptHistoryStore } from "../stores/promptHistoryStore";
@@ -98,14 +99,24 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) {
editorProps: {
attributes: { class: EDITOR_CLASS },
handleKeyDown: (view, event) => {
- if (event.key === "Enter" && !event.shiftKey) {
- if (!view.editable) return false;
- const suggestionPopup = document.querySelector("[data-tippy-root]");
- if (suggestionPopup) return false;
- event.preventDefault();
- historyActions.reset();
- submitRef.current();
- return true;
+ if (event.key === "Enter") {
+ const sendMessagesWith =
+ useSettingsStore.getState().sendMessagesWith;
+ const isCmdEnterMode = sendMessagesWith === "cmd+enter";
+ const isSubmitKey = isCmdEnterMode
+ ? event.metaKey || event.ctrlKey
+ : !event.shiftKey;
+
+ if (isSubmitKey) {
+ if (!view.editable) return false;
+ const suggestionPopup =
+ document.querySelector("[data-tippy-root]");
+ if (suggestionPopup) return false;
+ event.preventDefault();
+ historyActions.reset();
+ submitRef.current();
+ return true;
+ }
}
if (
diff --git a/apps/array/src/renderer/features/settings/components/SettingsView.tsx b/apps/array/src/renderer/features/settings/components/SettingsView.tsx
index ccec761aa..7872eb07b 100644
--- a/apps/array/src/renderer/features/settings/components/SettingsView.tsx
+++ b/apps/array/src/renderer/features/settings/components/SettingsView.tsx
@@ -1,6 +1,9 @@
import { useAuthStore } from "@features/auth/stores/authStore";
import { FolderPicker } from "@features/folder-picker/components/FolderPicker";
-import { useSettingsStore } from "@features/settings/stores/settingsStore";
+import {
+ type SendMessagesWith,
+ useSettingsStore,
+} from "@features/settings/stores/settingsStore";
import { useMeQuery } from "@hooks/useMeQuery";
import { useProjectQuery } from "@hooks/useProjectQuery";
import { useSetHeaderContent } from "@hooks/useSetHeaderContent";
@@ -56,10 +59,12 @@ export function SettingsView() {
createPR,
cursorGlow,
desktopNotifications,
+ sendMessagesWith,
setAutoRunTasks,
setCreatePR,
setCursorGlow,
setDesktopNotifications,
+ setSendMessagesWith,
} = useSettingsStore();
const terminalLayoutMode = useTerminalLayoutStore(
(state) => state.terminalLayoutMode,
@@ -160,6 +165,18 @@ export function SettingsView() {
[terminalLayoutMode, setTerminalLayout],
);
+ const handleSendMessagesWithChange = useCallback(
+ (value: SendMessagesWith) => {
+ track(ANALYTICS_EVENTS.SETTING_CHANGED, {
+ setting_name: "send_messages_with",
+ new_value: value,
+ old_value: sendMessagesWith,
+ });
+ setSendMessagesWith(value);
+ },
+ [sendMessagesWith, setSendMessagesWith],
+ );
+
const handleWorktreeLocationChange = async (newLocation: string) => {
setLocalWorktreeLocation(newLocation);
try {
@@ -341,20 +358,48 @@ export function SettingsView() {
Chat
-
-
-
- Desktop notifications
-
-
- Show notifications when the agent finishes working on a task
-
+
+
+
+
+ Desktop notifications
+
+
+ Show notifications when the agent finishes working on a
+ task
+
+
+
+
+
+
+
+
+ Send messages with
+
+
+ Choose which key combination sends messages. Use
+ Shift+Enter for new lines.
+
+
+
+ handleSendMessagesWithChange(value as SendMessagesWith)
+ }
+ size="1"
+ >
+
+
+ Enter
+ ⌘ Enter
+
+
-
diff --git a/apps/array/src/renderer/features/settings/stores/settingsStore.ts b/apps/array/src/renderer/features/settings/stores/settingsStore.ts
index 7051ae7ba..59f05807b 100644
--- a/apps/array/src/renderer/features/settings/stores/settingsStore.ts
+++ b/apps/array/src/renderer/features/settings/stores/settingsStore.ts
@@ -5,6 +5,7 @@ import { persist } from "zustand/middleware";
export type DefaultRunMode = "local" | "cloud" | "last_used";
export type LocalWorkspaceMode = "worktree" | "root";
+export type SendMessagesWith = "enter" | "cmd+enter";
interface SettingsStore {
autoRunTasks: boolean;
@@ -16,6 +17,7 @@ interface SettingsStore {
defaultModel: string;
desktopNotifications: boolean;
cursorGlow: boolean;
+ sendMessagesWith: SendMessagesWith;
setAutoRunTasks: (autoRun: boolean) => void;
setDefaultRunMode: (mode: DefaultRunMode) => void;
@@ -26,6 +28,7 @@ interface SettingsStore {
setDefaultModel: (model: string) => void;
setDesktopNotifications: (enabled: boolean) => void;
setCursorGlow: (enabled: boolean) => void;
+ setSendMessagesWith: (mode: SendMessagesWith) => void;
}
export const useSettingsStore = create()(
@@ -40,6 +43,7 @@ export const useSettingsStore = create()(
defaultModel: DEFAULT_MODEL,
desktopNotifications: true,
cursorGlow: false,
+ sendMessagesWith: "enter",
setAutoRunTasks: (autoRun) => set({ autoRunTasks: autoRun }),
setDefaultRunMode: (mode) => set({ defaultRunMode: mode }),
@@ -52,6 +56,7 @@ export const useSettingsStore = create()(
setDesktopNotifications: (enabled) =>
set({ desktopNotifications: enabled }),
setCursorGlow: (enabled) => set({ cursorGlow: enabled }),
+ setSendMessagesWith: (mode) => set({ sendMessagesWith: mode }),
}),
{
name: "settings-storage",
diff --git a/apps/array/src/renderer/stores/settingsStore.ts b/apps/array/src/renderer/stores/settingsStore.ts
index 27340803c..a8b894f09 100644
--- a/apps/array/src/renderer/stores/settingsStore.ts
+++ b/apps/array/src/renderer/stores/settingsStore.ts
@@ -2,16 +2,21 @@ import { create } from "zustand";
import { trpcVanilla } from "../trpc";
export type TerminalLayoutMode = "split" | "tabbed";
+export type SendMessagesWith = "enter" | "cmd+enter";
interface SettingsState {
terminalLayoutMode: TerminalLayoutMode;
+ sendMessagesWith: SendMessagesWith;
isLoading: boolean;
loadTerminalLayout: () => Promise;
setTerminalLayout: (mode: TerminalLayoutMode) => Promise;
+ loadSendMessagesWith: () => Promise;
+ setSendMessagesWith: (mode: SendMessagesWith) => Promise;
}
export const useSettingsStore = create()((set) => ({
terminalLayoutMode: "split",
+ sendMessagesWith: "enter",
isLoading: true,
loadTerminalLayout: async () => {
@@ -37,4 +42,27 @@ export const useSettingsStore = create()((set) => ({
set({ terminalLayoutMode: mode });
} catch (_error) {}
},
+
+ loadSendMessagesWith: async () => {
+ try {
+ const mode = await trpcVanilla.secureStore.getItem.query({
+ key: "sendMessagesWith",
+ });
+ if (mode === "enter" || mode === "cmd+enter") {
+ set({ sendMessagesWith: mode });
+ }
+ } catch (_error) {
+ // Keep default value
+ }
+ },
+
+ setSendMessagesWith: async (mode: SendMessagesWith) => {
+ try {
+ await trpcVanilla.secureStore.setItem.query({
+ key: "sendMessagesWith",
+ value: mode,
+ });
+ set({ sendMessagesWith: mode });
+ } catch (_error) {}
+ },
}));