Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 25 additions & 15 deletions packages/bruno-app/src/components/CodeEditor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -17,14 +18,15 @@ 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;
window.JSHINT = JSHINT;

const TAB_SIZE = 2;

export default class CodeEditor extends React.Component {
class CodeEditor extends React.Component {
constructor(props) {
super(props);

Expand All @@ -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 = () => {
Expand Down Expand Up @@ -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 });
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -355,3 +359,9 @@ export default class CodeEditor extends React.Component {
}
};
}

const mapStateToProps = (state) => ({
commentToggleKey: getCodeMirrorKeyForAction('toggleComment', state.app.preferences?.keyBindings)
});

Comment on lines +363 to +366
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The app has a preferences.keybindingsEnabled toggle (used by HotkeysProvider/useKeybinding to disable shortcuts), but CodeEditor will still bind the comment-toggle shortcut even when keybindings are disabled. Since toggleComment is now part of KEY_BINDING_SECTIONS, consider also selecting keybindingsEnabled here and returning null for commentToggleKey when disabled so the preference toggle consistently disables this shortcut too.

Suggested change
const mapStateToProps = (state) => ({
commentToggleKey: getCodeMirrorKeyForAction('toggleComment', state.app.preferences?.keyBindings)
});
const mapStateToProps = (state) => {
const keybindingsEnabled = state.app.preferences?.keybindingsEnabled;
return {
commentToggleKey: keybindingsEnabled === false
? null
: getCodeMirrorKeyForAction('toggleComment', state.app.preferences?.keyBindings)
};
};

Copilot uses AI. Check for mistakes.
export default connect(mapStateToProps, null, null, { forwardRef: true })(CodeEditor);
Comment on lines +363 to +367
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeEditor is now wrapped with react-redux connect, which means it cannot be rendered outside a Redux <Provider>. There is an existing Jest test that renders <CodeEditor /> without a store; this change will break that test (and any other standalone usage). Consider exporting the unconnected class (named export) for tests, or updating tests/consumers to wrap in a Provider with a minimal store.

Copilot uses AI. Check for mistakes.
33 changes: 33 additions & 0 deletions packages/bruno-app/src/providers/Hotkeys/keyMappings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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');
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getCodeMirrorKeyForAction uses navigator.platform directly. This duplicates OS detection logic used elsewhere (utils/common/platform.isMacOS) and will throw if navigator is unavailable (e.g., certain test/runtime contexts). Consider switching to isMacOS() (or at least guarding with typeof navigator !== 'undefined') so this helper is safe and consistent across the app.

Suggested change
const isMac = navigator.platform.toLowerCase().includes('mac');
const platform = typeof navigator !== 'undefined' && typeof navigator.platform === 'string' ? navigator.platform : '';
const isMac = platform.toLowerCase().includes('mac');

Copilot uses AI. Check for mistakes.
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 = [];
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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('-');
Comment thread
jespermeller marked this conversation as resolved.
};
Loading