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:
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 サーフェスを開く',
|
||||
|
||||
@@ -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 서피스 열기',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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': '導覽模型(選擇器中)',
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user