Merge pull request #2595 from openchamber/feat/ctrl-l-add-selection-to-chat-58c2

feat(ui): Ctrl/Cmd+L adds selected text to chat
This commit is contained in:
Serhii Dziupin
2026-08-04 11:33:51 +03:00
committed by GitHub
30 changed files with 528 additions and 3 deletions
@@ -16,6 +16,7 @@ import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory';
import { isVSCodeRuntime } from '@/lib/desktop';
import { useI18n } from '@/lib/i18n';
import { rangeToMarkdown, trimSelectionValue, wrapMarkdownSelectionForChat } from './selectionMarkdown';
import { focusChatInput } from '@/components/chat/composer/editor/dom';
interface TextSelectionMenuProps {
containerRef: React.RefObject<HTMLElement | null>;
@@ -309,6 +310,9 @@ export const TextSelectionMenu: React.FC<TextSelectionMenuProps> = ({ containerR
// Clear selection
window.getSelection()?.removeAllRanges();
queueMicrotask(() => {
focusChatInput();
});
}, [selectedTextMarkdown, setPendingInputText, hideMenu]);
const handleCreateNewSession = React.useCallback(async () => {
@@ -65,6 +65,12 @@ export const HelpDialog: React.FC = () => {
icon: "layout-left",
keys: '',
},
{
id: 'add_selection_to_chat',
descriptionKey: "helpDialog.item.addSelectionToChat",
icon: "add",
keys: '',
},
{
id: 'cycle_agent',
keys: '',
@@ -16,6 +16,7 @@ import { useDirectoryStore } from '@/stores/useDirectoryStore';
import { useProjectsStore } from '@/stores/useProjectsStore';
import { getCycledPrimaryAgentName } from '@/components/chat/mobileControlsUtils';
import { focusChatInput } from '@/components/chat/composer/editor/dom';
import { addSelectionToChat } from '@/lib/addSelectionToChat';
import { hasOpenDropdown } from './keyboard-shortcut-dom';
export const useKeyboardShortcuts = () => {
@@ -337,6 +338,12 @@ export const useKeyboardShortcuts = () => {
return;
}
if (eventMatchesShortcut(e, combo('add_selection_to_chat'))) {
e.preventDefault();
addSelectionToChat();
return;
}
if (eventMatchesShortcut(e, combo('toggle_sidebar'))) {
e.preventDefault();
const { isMobile, isSessionSwitcherOpen } = useUIStore.getState();
+6
View File
@@ -10,6 +10,7 @@ import { useThemeSystem } from '@/contexts/useThemeSystem';
import { sessionEvents } from '@/lib/sessionEvents';
import { createWorktreeSession } from '@/lib/worktreeSessionCreator';
import { showOpenCodeStatus } from '@/lib/openCodeStatus';
import { addSelectionToChat } from '@/lib/addSelectionToChat';
const getActiveElementSelectedText = (): string => {
if (typeof document === 'undefined') {
@@ -77,6 +78,7 @@ type MenuAction =
| 'toggle-terminal'
| 'toggle-terminal-expanded'
| 'copy'
| 'add-selection-to-chat'
| 'theme-light'
| 'theme-dark'
| 'theme-system'
@@ -278,6 +280,10 @@ export const useMenuActions = (
setThemeMode('system');
break;
case 'add-selection-to-chat':
addSelectionToChat();
break;
case 'toggle-sidebar':
toggleSidebar();
break;
@@ -0,0 +1,298 @@
import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test';
const focusChatInputCalls: number[] = [];
const pendingInputCalls: Array<{ text: string | null; mode?: string }> = [];
const activeMainTabCalls: string[] = [];
const sessionSwitcherCalls: boolean[] = [];
const codeMirrorDispatches: Array<{ selection: { anchor: number } }> = [];
type MockCodeMirrorView = {
state: {
selection: { main: { from: number; to: number } };
sliceDoc: (from: number, to: number) => string;
};
dispatch: (transaction: { selection: { anchor: number } }) => void;
};
let codeMirrorView: MockCodeMirrorView | null = null;
mock.module('@codemirror/view', () => ({
EditorView: {
findFromDOM: () => codeMirrorView,
},
}));
mock.module('@/components/chat/composer/editor/dom', () => ({
focusChatInput: () => {
focusChatInputCalls.push(1);
},
}));
mock.module('@/sync/input-store', () => ({
useInputStore: {
getState: () => ({
setPendingInputText: (text: string | null, mode?: string) => {
pendingInputCalls.push({ text, mode });
},
}),
},
}));
mock.module('@/stores/useUIStore', () => ({
useUIStore: {
getState: () => ({
setActiveMainTab: (tab: string) => {
activeMainTabCalls.push(tab);
},
setSessionSwitcherOpen: (open: boolean) => {
sessionSwitcherCalls.push(open);
},
}),
},
}));
const { addSelectionToChat, captureSelectionMarkdownForChat } = await import('./addSelectionToChat');
const originalDocument = globalThis.document;
const originalWindow = globalThis.window;
const installSelectionEnvironment = (options: {
activeElement?: Element | null;
focusedCodeMirror?: Element | null;
selection?: Selection | null;
} = {}) => {
const {
activeElement = null,
focusedCodeMirror = null,
selection = null,
} = options;
const documentLike = {
activeElement,
querySelector: (selector: string) => {
if (selector === '.cm-editor.cm-focused') {
return focusedCodeMirror;
}
return null;
},
};
const windowLike = {
getSelection: () => selection,
};
Object.defineProperty(globalThis, 'document', { value: documentLike, configurable: true });
Object.defineProperty(globalThis, 'window', { value: windowLike, configurable: true });
};
const clearCalls = () => {
focusChatInputCalls.length = 0;
pendingInputCalls.length = 0;
activeMainTabCalls.length = 0;
sessionSwitcherCalls.length = 0;
codeMirrorDispatches.length = 0;
codeMirrorView = null;
};
afterEach(() => {
Object.defineProperty(globalThis, 'document', { value: originalDocument, configurable: true });
Object.defineProperty(globalThis, 'window', { value: originalWindow, configurable: true });
});
describe('captureSelectionMarkdownForChat', () => {
beforeEach(() => {
clearCalls();
});
test('returns null when nothing is selected', () => {
installSelectionEnvironment();
expect(captureSelectionMarkdownForChat()).toBeNull();
});
test('captures a textarea selection outside the composer and collapses it', () => {
const textarea = {
tagName: 'TEXTAREA',
value: 'alpha beta gamma',
selectionStart: 6,
selectionEnd: 10,
closest: () => null,
} as unknown as HTMLTextAreaElement;
installSelectionEnvironment({ activeElement: textarea });
expect(captureSelectionMarkdownForChat()).toBe('```md\nbeta\n```');
expect(textarea.selectionStart).toBe(10);
expect(textarea.selectionEnd).toBe(10);
});
test('ignores selections inside the chat composer', () => {
const textarea = {
tagName: 'TEXTAREA',
value: 'draft text',
selectionStart: 0,
selectionEnd: 5,
closest: (selector: string) => (selector === '[data-chat-input="true"]' ? textarea : null),
} as unknown as HTMLTextAreaElement;
installSelectionEnvironment({ activeElement: textarea });
expect(captureSelectionMarkdownForChat()).toBeNull();
});
test('captures a focused CodeMirror selection outside the composer and collapses it', () => {
const focusedEditor = {
closest: () => null,
} as unknown as HTMLElement;
codeMirrorView = {
state: {
selection: { main: { from: 4, to: 11 } },
sliceDoc: (from: number, to: number) => 'const x'.slice(0, to - from),
},
dispatch: (transaction) => {
codeMirrorDispatches.push(transaction);
codeMirrorView!.state.selection.main = {
from: transaction.selection.anchor,
to: transaction.selection.anchor,
};
},
};
// sliceDoc should return the selected slice; use explicit text instead of slice math.
codeMirrorView.state.sliceDoc = () => 'const x';
installSelectionEnvironment({ focusedCodeMirror: focusedEditor });
expect(captureSelectionMarkdownForChat()).toBe('```\nconst x\n```');
expect(codeMirrorDispatches).toEqual([{ selection: { anchor: 11 } }]);
expect(codeMirrorView.state.selection.main).toEqual({ from: 11, to: 11 });
});
test('ignores a focused CodeMirror editor inside the chat composer', () => {
const focusedEditor = {
closest: (selector: string) => (selector === '[data-chat-input="true"]' ? focusedEditor : null),
} as unknown as HTMLElement;
codeMirrorView = {
state: {
selection: { main: { from: 0, to: 5 } },
sliceDoc: () => 'draft',
},
dispatch: (transaction) => {
codeMirrorDispatches.push(transaction);
},
};
installSelectionEnvironment({ focusedCodeMirror: focusedEditor });
expect(captureSelectionMarkdownForChat()).toBeNull();
expect(codeMirrorDispatches).toEqual([]);
});
test('captures a DOM selection from chat-message content and clears it', () => {
const parent = {
closest: (selector: string) => (selector === 'pre code' ? null : null),
};
const textNode = {
nodeType: 3,
parentElement: parent,
};
let rangeCount = 1;
let collapsed = false;
const selection = {
get rangeCount() {
return rangeCount;
},
get isCollapsed() {
return collapsed;
},
toString: () => 'Hello world',
getRangeAt: () => ({
commonAncestorContainer: textNode,
startContainer: textNode,
endContainer: textNode,
cloneContents: () => ({ childNodes: [] }),
}),
removeAllRanges: () => {
rangeCount = 0;
collapsed = true;
},
} as unknown as Selection;
installSelectionEnvironment({ selection });
expect(captureSelectionMarkdownForChat()).toBe('```md\nHello world\n```');
expect(selection.rangeCount).toBe(0);
expect(selection.isCollapsed).toBe(true);
});
test('ignores a DOM selection inside the chat composer', () => {
const composerHost = {};
const parent = {
closest: (selector: string) => (selector === '[data-chat-input="true"]' ? composerHost : null),
};
const textNode = {
nodeType: 3,
parentElement: parent,
};
const selection = {
rangeCount: 1,
isCollapsed: false,
toString: () => 'draft',
getRangeAt: () => ({
commonAncestorContainer: textNode,
startContainer: textNode,
endContainer: textNode,
cloneContents: () => ({ childNodes: [] }),
}),
removeAllRanges: () => undefined,
} as unknown as Selection;
installSelectionEnvironment({ selection });
expect(captureSelectionMarkdownForChat()).toBeNull();
});
});
describe('addSelectionToChat', () => {
beforeEach(() => {
clearCalls();
});
test('appends captured selection and focuses chat input', async () => {
const textarea = {
tagName: 'TEXTAREA',
value: 'selected',
selectionStart: 0,
selectionEnd: 8,
closest: () => null,
} as unknown as HTMLTextAreaElement;
installSelectionEnvironment({ activeElement: textarea });
expect(addSelectionToChat()).toBe(true);
expect(activeMainTabCalls).toEqual(['chat']);
expect(sessionSwitcherCalls).toEqual([false]);
expect(pendingInputCalls).toEqual([{ text: '```md\nselected\n```', mode: 'append' }]);
await Promise.resolve();
expect(focusChatInputCalls.length).toBe(1);
});
test('second capture after textarea collapse does not append again', () => {
const textarea = {
tagName: 'TEXTAREA',
value: 'selected',
selectionStart: 0,
selectionEnd: 8,
closest: () => null,
} as unknown as HTMLTextAreaElement;
installSelectionEnvironment({ activeElement: textarea });
expect(addSelectionToChat()).toBe(true);
expect(addSelectionToChat()).toBe(false);
expect(pendingInputCalls).toEqual([{ text: '```md\nselected\n```', mode: 'append' }]);
});
test('focuses chat input when nothing is selected', async () => {
installSelectionEnvironment();
expect(addSelectionToChat()).toBe(false);
expect(pendingInputCalls).toEqual([]);
expect(activeMainTabCalls).toEqual(['chat']);
await Promise.resolve();
expect(focusChatInputCalls.length).toBe(1);
});
});
+166
View File
@@ -0,0 +1,166 @@
import { EditorView } from '@codemirror/view';
import { focusChatInput } from '@/components/chat/composer/editor/dom';
import {
formatCodeSelectionMarkdown,
rangeToMarkdown,
trimSelectionValue,
wrapMarkdownSelectionForChat,
} from '@/components/chat/message/selectionMarkdown';
import { useInputStore } from '@/sync/input-store';
import { useUIStore } from '@/stores/useUIStore';
const CHAT_INPUT_HOST_SELECTOR = '[data-chat-input="true"]';
const isInsideChatComposer = (node: Node | null): boolean => {
if (!node) {
return false;
}
const asElement = node as Element;
const element = typeof asElement.closest === 'function'
? asElement
: (node as Node).parentElement;
return Boolean(element?.closest(CHAT_INPUT_HOST_SELECTOR));
};
const readTextControlSelection = (element: Element): string | null => {
if (isInsideChatComposer(element)) {
return null;
}
const tag = element.tagName?.toLowerCase();
if (tag === 'textarea') {
const control = element as HTMLTextAreaElement;
const start = control.selectionStart ?? 0;
const end = control.selectionEnd ?? 0;
const text = trimSelectionValue(control.value.slice(start, end));
if (!text) {
return null;
}
// Collapse so a duplicate menu delivery cannot append the same range twice.
control.selectionStart = end;
control.selectionEnd = end;
return text;
}
if (tag === 'input') {
const control = element as HTMLInputElement;
const type = control.type?.toLowerCase() ?? 'text';
if (!['text', 'search', 'url', 'tel', 'password'].includes(type)) {
return null;
}
const start = control.selectionStart ?? 0;
const end = control.selectionEnd ?? 0;
const text = trimSelectionValue(control.value.slice(start, end));
if (!text) {
return null;
}
control.selectionStart = end;
control.selectionEnd = end;
return text;
}
return null;
};
const captureActiveElementSelection = (): string | null => {
if (typeof document === 'undefined') {
return null;
}
const activeElement = document.activeElement;
if (!activeElement || typeof (activeElement as Element).tagName !== 'string') {
return null;
}
const text = readTextControlSelection(activeElement as Element);
return text ? wrapMarkdownSelectionForChat(text) : null;
};
const captureCodeMirrorSelection = (): string | null => {
if (typeof document === 'undefined') {
return null;
}
const focusedEditor = document.querySelector<HTMLElement>('.cm-editor.cm-focused');
if (!focusedEditor || isInsideChatComposer(focusedEditor)) {
return null;
}
const view = EditorView.findFromDOM(focusedEditor);
if (!view) {
return null;
}
const { from, to } = view.state.selection.main;
if (from === to) {
return null;
}
const text = trimSelectionValue(view.state.sliceDoc(from, to));
if (!text) {
return null;
}
view.dispatch({
selection: { anchor: to },
});
return formatCodeSelectionMarkdown(text);
};
const captureDomSelection = (): string | null => {
if (typeof window === 'undefined') {
return null;
}
const selection = window.getSelection();
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
return null;
}
const range = selection.getRangeAt(0);
if (isInsideChatComposer(range.commonAncestorContainer)) {
return null;
}
const plainText = trimSelectionValue(selection.toString());
if (!plainText) {
return null;
}
const markdown = rangeToMarkdown(range, plainText);
selection.removeAllRanges();
return wrapMarkdownSelectionForChat(markdown);
};
/**
* Capture the current non-composer selection as chat-ready markdown.
* Returns null when nothing usable is selected.
*/
export const captureSelectionMarkdownForChat = (): string | null => {
return captureCodeMirrorSelection()
?? captureActiveElementSelection()
?? captureDomSelection();
};
/**
* Append the current selection to the chat composer.
* When nothing is selected, focuses the chat input (Cursor-style Ctrl/Cmd+L).
* Returns true when selected text was appended.
*/
export const addSelectionToChat = (): boolean => {
const markdown = captureSelectionMarkdownForChat();
useUIStore.getState().setActiveMainTab('chat');
useUIStore.getState().setSessionSwitcherOpen(false);
if (markdown) {
useInputStore.getState().setPendingInputText(markdown, 'append');
}
queueMicrotask(() => {
focusChatInput();
});
return markdown !== null;
};
@@ -1038,6 +1038,7 @@ export const settingsDict = {
'settings.openchamber.keyboardShortcuts.action.open_settings.label': 'Einstellungen öffnen',
'settings.openchamber.keyboardShortcuts.action.toggle_terminal.label': 'Terminal-Dock umschalten',
'settings.openchamber.keyboardShortcuts.action.toggle_terminal_expanded.label': 'Terminal erweitert umschalten',
'settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label': 'Auswahl zum Chat hinzufügen',
'settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label': 'Seitenleiste umschalten',
'settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label': 'Rechte Seitenleiste umschalten',
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_git.label': 'Git-Tab der rechten Seitenleiste öffnen',
+1
View File
@@ -1532,6 +1532,7 @@ export const dict = {
'helpDialog.item.openCommandPalette': 'Befehlspalette öffnen',
'helpDialog.item.showKeyboardShortcuts': 'Tastaturkürzel anzeigen (dieses Dialogfeld)',
'helpDialog.item.toggleSessionSidebar': 'Sitzungs-Seitenleiste umschalten',
'helpDialog.item.addSelectionToChat': 'Auswahl zum Chat hinzufügen',
'helpDialog.item.cycleAgent': 'Agent wechseln (Chat-Eingabe)',
'helpDialog.item.openModelSelector': 'Modell-Auswahldialog öffnen',
'helpDialog.item.navigateModels': 'Modelle navigieren (in Auswahl)',
@@ -1103,6 +1103,7 @@ export const settingsDict = {
'settings.openchamber.keyboardShortcuts.action.open_settings.label': 'Open settings',
'settings.openchamber.keyboardShortcuts.action.toggle_terminal.label': 'Toggle terminal dock',
'settings.openchamber.keyboardShortcuts.action.toggle_terminal_expanded.label': 'Toggle terminal expanded',
'settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label': 'Add selection to chat',
'settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label': 'Toggle sidebar',
'settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label': 'Toggle context panel',
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_git.label': 'Open Git surface',
+1
View File
@@ -1679,6 +1679,7 @@ export const dict = {
'helpDialog.item.openCommandPalette': 'Open Command Palette',
'helpDialog.item.showKeyboardShortcuts': 'Show Keyboard Shortcuts (this dialog)',
'helpDialog.item.toggleSessionSidebar': 'Toggle Session Sidebar',
'helpDialog.item.addSelectionToChat': 'Add Selection to Chat',
'helpDialog.item.cycleAgent': 'Cycle Agent (chat input)',
'helpDialog.item.openModelSelector': 'Open Model Selector',
'helpDialog.item.navigateModels': 'Navigate Models (in picker)',
@@ -1070,6 +1070,7 @@ export const settingsDict = {
"settings.openchamber.keyboardShortcuts.action.open_settings.label": "Abrir configuración",
"settings.openchamber.keyboardShortcuts.action.toggle_terminal.label": "Mostrar u ocultar panel de terminal",
"settings.openchamber.keyboardShortcuts.action.toggle_terminal_expanded.label": "Expandir o contraer terminal",
"settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label": "Agregar selección al chat",
"settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label": "Mostrar u ocultar barra lateral",
"settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label": 'Alternar panel de contexto',
"settings.openchamber.keyboardShortcuts.action.open_right_sidebar_git.label": 'Abrir superficie de Git',
+1
View File
@@ -1657,6 +1657,7 @@ export const dict: Record<I18nKey, string> = {
"helpDialog.item.openCommandPalette": "Abrir paleta de comandos",
"helpDialog.item.showKeyboardShortcuts": "Mostrar atajos de teclado (este diálogo)",
"helpDialog.item.toggleSessionSidebar": "Mostrar u ocultar barra lateral de sesión",
"helpDialog.item.addSelectionToChat": "Agregar selección al chat",
"helpDialog.item.cycleAgent": "Cambiar agente (entrada de chat)",
"helpDialog.item.openModelSelector": "Abrir selector de modelos",
"helpDialog.item.navigateModels": "Navegar modelos (en selector)",
@@ -991,6 +991,7 @@ export const settingsDict = {
'settings.openchamber.keyboardShortcuts.action.open_settings.label': 'Ouvrir les paramètres',
'settings.openchamber.keyboardShortcuts.action.toggle_terminal.label': 'Basculer la station d\'accueil du terminal',
'settings.openchamber.keyboardShortcuts.action.toggle_terminal_expanded.label': 'Terminal à bascule étendu',
'settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label': 'Ajouter la sélection au chat',
'settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label': 'Basculer la barre latérale',
'settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label': 'Afficher/masquer le panneau de contexte',
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_git.label': 'Ouvrir la surface Git',
+1
View File
@@ -1492,6 +1492,7 @@ export const dict = {
'helpDialog.item.openCommandPalette': 'Ouvrir la palette de commandes',
'helpDialog.item.showKeyboardShortcuts': 'Afficher les raccourcis clavier (cette boîte de dialogue)',
'helpDialog.item.toggleSessionSidebar': 'Toggle la barre latérale de la session',
'helpDialog.item.addSelectionToChat': 'Ajouter la sélection au chat',
'helpDialog.item.cycleAgent': 'Agent de cycle (entrée de chat)',
'helpDialog.item.openModelSelector': 'Ouvrir le sélecteur de modèle',
'helpDialog.item.navigateModels': 'Naviguer dans les modèles (dans le sélecteur)',
@@ -1103,6 +1103,7 @@ export const settingsDict = {
'settings.openchamber.keyboardShortcuts.action.open_settings.label': '設定を開く',
'settings.openchamber.keyboardShortcuts.action.toggle_terminal.label': 'ターミナルドックの切替',
'settings.openchamber.keyboardShortcuts.action.toggle_terminal_expanded.label': 'ターミナル拡大の切替',
'settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label': '選択範囲をチャットに追加',
'settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label': 'サイドバーの切替',
'settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label': 'コンテキストパネルの表示切替',
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_git.label': 'Git サーフェスを開く',
+1
View File
@@ -1675,6 +1675,7 @@ export const dict: Record<I18nKey, string> = {
'helpDialog.item.openCommandPalette': 'コマンドパレットを開く',
'helpDialog.item.showKeyboardShortcuts': 'キーボードショートカットを表示(このダイアログ)',
'helpDialog.item.toggleSessionSidebar': 'セッションサイドバーの切り替え',
'helpDialog.item.addSelectionToChat': '選択範囲をチャットに追加',
'helpDialog.item.cycleAgent': 'エージェント切り替え(チャット入力)',
'helpDialog.item.openModelSelector': 'モデルセレクターを開く',
'helpDialog.item.navigateModels': 'モデルを移動(ピッカー内)',
@@ -1070,6 +1070,7 @@ export const settingsDict = {
'settings.openchamber.keyboardShortcuts.action.open_settings.label': '설정 열기',
'settings.openchamber.keyboardShortcuts.action.toggle_terminal.label': '터미널 dock 토글',
'settings.openchamber.keyboardShortcuts.action.toggle_terminal_expanded.label': '터미널 확장 토글',
'settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label': '선택 내용을 채팅에 추가',
'settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label': '사이드바 토글',
'settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label': '컨텍스트 패널 표시 전환',
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_git.label': 'Git 서피스 열기',
+1
View File
@@ -1681,6 +1681,7 @@ export const dict: Record<I18nKey, string> = {
'helpDialog.item.openCommandPalette': '명령 팔레트 열기',
'helpDialog.item.showKeyboardShortcuts': '키보드 단축키 보기(이 대화상자)',
'helpDialog.item.toggleSessionSidebar': '토글 세션 사이드바',
'helpDialog.item.addSelectionToChat': '선택 내용을 채팅에 추가',
'helpDialog.item.cycleAgent': '에이전트 순환(채팅 입력)',
'helpDialog.item.openModelSelector': '모델 선택기 열기',
'helpDialog.item.navigateModels': '모델 이동(선택기)',
@@ -825,6 +825,7 @@ export const settingsDict = {
'settings.openchamber.keyboardShortcuts.action.toggle_context_plan.label': 'Przełącz panel kontekstu planu',
'settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label': 'Przełącz panel kontekstu',
'settings.openchamber.keyboardShortcuts.action.toggle_services_menu.label': 'Przełącz menu usług',
'settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label': 'Dodaj zaznaczenie do czatu',
'settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label': 'Przełącz pasek boczny',
'settings.openchamber.keyboardShortcuts.action.toggle_terminal.label': 'Przełącz dokowanie terminala',
'settings.openchamber.keyboardShortcuts.action.toggle_terminal_expanded.label': 'Przełącz rozszerzony terminal',
+1
View File
@@ -2325,6 +2325,7 @@ export const dict: Record<I18nKey, string> = {
'helpDialog.item.toggleRightSidebar': 'Przełącz panel kontekstu',
'helpDialog.item.toggleServicesMenu': 'Przełącz menu usług',
'helpDialog.item.toggleSessionSidebar': 'Przełącz panel sesji',
'helpDialog.item.addSelectionToChat': 'Dodaj zaznaczenie do czatu',
'helpDialog.item.toggleTerminalDock': 'Przełącz dolny terminal',
'helpDialog.item.toggleTerminalExpanded': 'Przełącz rozszerzenie terminala',
'helpDialog.keyCombiner.or': 'lub',
@@ -1070,6 +1070,7 @@ export const settingsDict = {
"settings.openchamber.keyboardShortcuts.action.open_settings.label": "Abrir configurações",
"settings.openchamber.keyboardShortcuts.action.toggle_terminal.label": "Mostrar ou ocultar painel de terminal",
"settings.openchamber.keyboardShortcuts.action.toggle_terminal_expanded.label": "Expandir ou recolher terminal",
"settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label": "Adicionar seleção ao chat",
"settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label": "Mostrar ou ocultar barra lateral",
"settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label": 'Alternar painel de contexto',
"settings.openchamber.keyboardShortcuts.action.open_right_sidebar_git.label": 'Abrir superfície do Git',
@@ -1657,6 +1657,7 @@ export const dict: Record<I18nKey, string> = {
"helpDialog.item.openCommandPalette": "Abrir paleta de comandos",
"helpDialog.item.showKeyboardShortcuts": "Mostrar atalhos de teclado (este diálogo)",
"helpDialog.item.toggleSessionSidebar": "Mostrar ou ocultar barra lateral de sessão",
"helpDialog.item.addSelectionToChat": "Adicionar seleção ao chat",
"helpDialog.item.cycleAgent": "Alternar agente (entrada do chat)",
"helpDialog.item.openModelSelector": "Abrir seletor de modelos",
"helpDialog.item.navigateModels": "Navegar por modelos (no seletor)",
@@ -1070,6 +1070,7 @@ export const settingsDict = {
"settings.openchamber.keyboardShortcuts.action.open_settings.label": "Відкрити налаштування",
"settings.openchamber.keyboardShortcuts.action.toggle_terminal.label": "Перемкнути панель терміналу",
"settings.openchamber.keyboardShortcuts.action.toggle_terminal_expanded.label": "Розгорнути або згорнути термінал",
"settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label": "Додати виділення в чат",
"settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label": "Перемкнути бічну панель",
"settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label": 'Перемкнути контекстну панель',
"settings.openchamber.keyboardShortcuts.action.open_right_sidebar_git.label": 'Відкрити поверхню Git',
+1
View File
@@ -1657,6 +1657,7 @@ export const dict: Record<I18nKey, string> = {
"helpDialog.item.openCommandPalette": "Відкрити палітру команд",
"helpDialog.item.showKeyboardShortcuts": "Показати комбінації клавіш (це діалогове вікно)",
"helpDialog.item.toggleSessionSidebar": "Перемкнути бічну панель сесій",
"helpDialog.item.addSelectionToChat": "Додати виділення в чат",
"helpDialog.item.cycleAgent": "Перемкнути агента (введення в чат)",
"helpDialog.item.openModelSelector": "Відкрити засіб вибору моделі",
"helpDialog.item.navigateModels": "Навігація моделями (у засобі вибору)",
@@ -1070,6 +1070,7 @@ export const settingsDict = {
'settings.openchamber.keyboardShortcuts.action.open_settings.label': '打开设置',
'settings.openchamber.keyboardShortcuts.action.toggle_terminal.label': '切换终端停靠区',
'settings.openchamber.keyboardShortcuts.action.toggle_terminal_expanded.label': '切换终端展开',
'settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label': '将选中内容添加到聊天',
'settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label': '切换侧边栏',
'settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label': '切换上下文面板',
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_git.label': '打开 Git 界面',
@@ -1645,6 +1645,7 @@ export const dict: Record<I18nKey, string> = {
'helpDialog.item.openCommandPalette': '打开命令面板',
'helpDialog.item.showKeyboardShortcuts': '显示键盘快捷键(此对话框)',
'helpDialog.item.toggleSessionSidebar': '切换会话侧边栏',
'helpDialog.item.addSelectionToChat': '将选中内容添加到聊天',
'helpDialog.item.cycleAgent': '循环切换智能体(聊天输入)',
'helpDialog.item.openModelSelector': '打开模型选择器',
'helpDialog.item.navigateModels': '导航模型(选择器中)',
@@ -977,6 +977,7 @@
'settings.openchamber.keyboardShortcuts.action.open_settings.label': '開啟設定',
'settings.openchamber.keyboardShortcuts.action.toggle_terminal.label': '切換終端機停靠區',
'settings.openchamber.keyboardShortcuts.action.toggle_terminal_expanded.label': '切換終端機展開',
'settings.openchamber.keyboardShortcuts.action.add_selection_to_chat.label': '將選取內容加入聊天',
'settings.openchamber.keyboardShortcuts.action.toggle_sidebar.label': '切換側邊欄',
'settings.openchamber.keyboardShortcuts.action.toggle_right_sidebar.label': '切換上下文面板',
'settings.openchamber.keyboardShortcuts.action.open_right_sidebar_git.label': '開啟 Git 介面',
@@ -1649,6 +1649,7 @@ export const dict: Record<I18nKey, string> = {
'helpDialog.item.openCommandPalette': '開啟命令面板',
'helpDialog.item.showKeyboardShortcuts': '顯示鍵盤快速鍵(此對話方塊)',
'helpDialog.item.toggleSessionSidebar': '切換會話側邊欄',
'helpDialog.item.addSelectionToChat': '將選取內容加入聊天',
'helpDialog.item.cycleAgent': '循環切換 Agent(聊天輸入)',
'helpDialog.item.openModelSelector': '開啟模型選擇器',
'helpDialog.item.navigateModels': '導覽模型(選擇器中)',
+8 -1
View File
@@ -159,8 +159,15 @@ const SHORTCUT_ACTIONS: ReadonlyArray<ShortcutAction> = [
description: 'Toggle the files panel',
},
{
id: 'toggle_sidebar',
id: 'add_selection_to_chat',
defaultCombo: 'mod+l',
label: 'Add selection to chat',
description: 'Add the selected text to the chat input',
customizable: true,
},
{
id: 'toggle_sidebar',
defaultCombo: 'mod+alt+l',
label: 'Toggle sidebar',
description: 'Toggle the session sidebar',
customizable: true,