diff --git a/packages/bruno-app/src/components/CodeEditor/index.js b/packages/bruno-app/src/components/CodeEditor/index.js index b0421424c45..cffccf989e2 100644 --- a/packages/bruno-app/src/components/CodeEditor/index.js +++ b/packages/bruno-app/src/components/CodeEditor/index.js @@ -6,6 +6,7 @@ */ import React, { createRef } from 'react'; +import { connect } from 'react-redux'; import { isEqual, escapeRegExp } from 'lodash'; import { defineCodeMirrorBrunoVariablesMode } from 'utils/common/codemirror'; import { setupAutoComplete, showRootHints } from 'utils/codemirror/autocomplete'; @@ -17,6 +18,7 @@ import { getAllVariables } from 'utils/collections'; import { setupLinkAware } from 'utils/codemirror/linkAware'; import { setupLintErrorTooltip } from 'utils/codemirror/lint-errors'; import CodeMirrorSearch from 'components/CodeMirrorSearch/index'; +import { getCodeMirrorKeyForAction } from 'providers/Hotkeys/keyMappings'; const CodeMirror = require('codemirror'); window.jsonlint = jsonlint; @@ -24,7 +26,7 @@ window.JSHINT = JSHINT; const TAB_SIZE = 2; -export default class CodeEditor extends React.Component { +class CodeEditor extends React.Component { constructor(props) { super(props); @@ -48,6 +50,14 @@ export default class CodeEditor extends React.Component { }; } + toggleComment = () => { + if (['application/ld+json', 'application/json'].includes(this.props.mode)) { + this.editor.toggleComment({ lineComment: '//', blockComment: '/*' }); + } else { + this.editor.toggleComment(); + } + }; + componentDidMount() { const variables = getAllVariables(this.props.collection, this.props.item); const runShortcut = () => { @@ -111,20 +121,7 @@ export default class CodeEditor extends React.Component { 'Cmd-Y': 'foldAll', 'Ctrl-I': 'unfoldAll', 'Cmd-I': 'unfoldAll', - 'Ctrl-/': () => { - if (['application/ld+json', 'application/json'].includes(this.props.mode)) { - this.editor.toggleComment({ lineComment: '//', blockComment: '/*' }); - } else { - this.editor.toggleComment(); - } - }, - 'Cmd-/': () => { - if (['application/ld+json', 'application/json'].includes(this.props.mode)) { - this.editor.toggleComment({ lineComment: '//', blockComment: '/*' }); - } else { - this.editor.toggleComment(); - } - }, + ...(this.props.commentToggleKey ? { [this.props.commentToggleKey]: this.toggleComment } : {}), 'Esc': () => { if (this.state.searchBarVisible) { this.setState({ searchBarVisible: false }); @@ -280,6 +277,13 @@ export default class CodeEditor extends React.Component { this.editor.setOption('readOnly', this.props.readOnly); } + if (this.props.commentToggleKey !== prevProps.commentToggleKey && this.editor) { + const extraKeys = { ...(this.editor.getOption('extraKeys') || {}) }; + if (prevProps.commentToggleKey) delete extraKeys[prevProps.commentToggleKey]; + if (this.props.commentToggleKey) extraKeys[this.props.commentToggleKey] = this.toggleComment; + this.editor.setOption('extraKeys', extraKeys); + } + this.ignoreChangeEvent = false; } @@ -355,3 +359,9 @@ export default class CodeEditor extends React.Component { } }; } + +const mapStateToProps = (state) => ({ + commentToggleKey: getCodeMirrorKeyForAction('toggleComment', state.app.preferences?.keyBindings) +}); + +export default connect(mapStateToProps, null, null, { forwardRef: true })(CodeEditor); diff --git a/packages/bruno-app/src/providers/Hotkeys/keyMappings.js b/packages/bruno-app/src/providers/Hotkeys/keyMappings.js index 998592d8e4c..3ed6d5dafc1 100644 --- a/packages/bruno-app/src/providers/Hotkeys/keyMappings.js +++ b/packages/bruno-app/src/providers/Hotkeys/keyMappings.js @@ -69,6 +69,12 @@ export const KEY_BINDING_SECTIONS = [ openTerminal: { mac: 'command+bind+t', windows: 'ctrl+bind+t', name: 'Open in Terminal' } // D } }, + { + heading: 'Editor', + bindings: { + toggleComment: { mac: 'command+bind+/', windows: 'ctrl+bind+/', name: 'Toggle Comment' } + } + }, { heading: 'Others', bindings: { @@ -171,3 +177,30 @@ export const getKeyBindingsForActionAllOS = (action, userKeyBindings) => { // console.log('[keyMappings] getKeyBindingsForActionAllOS:', action, '->', combos); return combos.length > 0 ? combos : null; }; + +const CM_MODIFIERS = { command: 'Cmd', ctrl: 'Ctrl', alt: 'Alt', shift: 'Shift' }; +const CM_MODIFIER_ORDER = ['Shift', 'Cmd', 'Ctrl', 'Alt']; +const CM_SPECIAL_KEYS = { + enter: 'Enter', esc: 'Esc', escape: 'Esc', space: 'Space', tab: 'Tab', + backspace: 'Backspace', delete: 'Delete', insert: 'Insert', + arrowup: 'Up', arrowdown: 'Down', arrowleft: 'Left', arrowright: 'Right', + pageup: 'PageUp', pagedown: 'PageDown', home: 'Home', end: 'End' +}; + +export const getCodeMirrorKeyForAction = (action, userKeyBindings) => { + const binding = getMergedKeyBindings(userKeyBindings)[action]; + if (!binding) return null; + const isMac = navigator.platform.toLowerCase().includes('mac'); + const platformBinding = isMac ? binding.mac : binding.windows; + if (typeof platformBinding !== 'string' || !platformBinding.trim()) return null; + const parts = platformBinding.split('+bind+').filter(Boolean); + const mods = [], keys = []; + for (const p of parts) { + const lower = p.toLowerCase(); + const cm = CM_MODIFIERS[lower]; + cm ? mods.push(cm) : keys.push(CM_SPECIAL_KEYS[lower] || p); + } + if (!keys.length) return null; + mods.sort((a, b) => CM_MODIFIER_ORDER.indexOf(a) - CM_MODIFIER_ORDER.indexOf(b)); + return [...mods, ...keys].join('-'); +};