Fixed an issue where editor shortcuts fail when using Option key combinations on macOS, due to macOS treating Option+Key as a different key input. #9116

pull/9124/head
Aditya Toshniwal 2025-09-01 15:03:16 +05:30
parent e09af0a0e9
commit 2dac2d0e21
4 changed files with 79 additions and 34 deletions

View File

@ -34,4 +34,5 @@ Bug fixes
*********
| `Issue #9090 <https://github.com/pgadmin-org/pgadmin4/issues/9090>`_ - Pin Paramiko to version 3.5.1 to fix the DSSKey error introduced in the latest release.
| `Issue #9095 <https://github.com/pgadmin-org/pgadmin4/issues/9095>`_ - Fixed an issue where pgAdmin config migration was failing while upgrading to v9.7.
| `Issue #9095 <https://github.com/pgadmin-org/pgadmin4/issues/9095>`_ - Fixed an issue where pgAdmin config migration was failing while upgrading to v9.7.
| `Issue #9116 <https://github.com/pgadmin-org/pgadmin4/issues/9116>`_ - Fixed an issue where editor shortcuts fail when using Option key combinations on macOS, due to macOS treating Option+Key as a different key input.

View File

@ -25,7 +25,7 @@ import CustomPropTypes from '../../custom_prop_types';
import FindDialog from './components/FindDialog';
import GotoDialog from './components/GotoDialog';
import usePreferences from '../../../../preferences/static/js/store';
import { toCodeMirrorKey } from '../../utils';
import { parseKeyEventValue, parseShortcutValue } from '../../utils';
const Root = styled('div')(() => ({
position: 'relative',
@ -103,39 +103,46 @@ export default function CodeMirror({className, currEditor, showCopyBtn=false, cu
}
};
const finalCustomKeyMap = useMemo(()=>[{
key: toCodeMirrorKey(editorPrefs.find), run: () => {
setShowFind(prevVal => [true, false, !prevVal[2]]);
// We're not using CodeMirror keymap and using any instead
// because on Mac, the alt key combination creates special
// chars and https://github.com/codemirror/view/commit/3cea8dba19845fe75bea4eae756c6103694f49f3
const customShortcuts = {
[parseShortcutValue(editorPrefs.find, true)]: () => {
setTimeout(()=>{
setShowFind(prevVal => [true, false, !prevVal[2]]);
}, 0);
},
preventDefault: true,
stopPropagation: true,
}, {
key: toCodeMirrorKey(editorPrefs.replace), run: () => {
setShowFind(prevVal => [true, true, !prevVal[2]]);
[parseShortcutValue(editorPrefs.replace, true)]: () => {
setTimeout(()=>{
setShowFind(prevVal => [true, true, !prevVal[2]]);
}, 0);
},
preventDefault: true,
stopPropagation: true,
}, {
key: toCodeMirrorKey(editorPrefs.goto_line_col), run: () => {
[parseShortcutValue(editorPrefs.goto_line_col, true)]: () => {
setShowGoto(true);
},
preventDefault: true,
stopPropagation: true,
}, {
key: toCodeMirrorKey(editorPrefs.comment), run: () => {
[parseShortcutValue(editorPrefs.comment, true)]: () => {
editor.current?.execCommand('toggleComment');
},
preventDefault: true,
stopPropagation: true,
},{
key: toCodeMirrorKey(editorPrefs.format_sql), run: formatSQL,
preventDefault: true,
stopPropagation: true,
},{
key: toCodeMirrorKey(preferences.auto_complete), run: startCompletion,
preventDefault: true,
},
...customKeyMap], [customKeyMap]);
[parseShortcutValue(editorPrefs.format_sql, true)]: formatSQL,
[parseShortcutValue(preferences.auto_complete, true)]: startCompletion,
};
const finalCustomKeyMap = useMemo(() => [
{
any: (view, e) => {
const eventStr = parseKeyEventValue(e, true);
const callback = customShortcuts[eventStr];
if(callback) {
e.preventDefault();
e.stopPropagation();
callback(view);
return true;
}
return false;
}
},
...customKeyMap
], [customKeyMap]);
const closeFind = () => {
setShowFind([false, false, false]);

View File

@ -18,7 +18,7 @@ import pgAdmin from 'sources/pgadmin';
import { isMac } from './keyboard_shortcuts';
import { WORKSPACES } from '../../browser/static/js/constants';
export function parseShortcutValue(obj) {
export function parseShortcutValue(obj, useKeyboardCode=false) {
let shortcut = '';
if (!obj){
return null;
@ -27,11 +27,11 @@ export function parseShortcutValue(obj) {
if (obj.shift) { shortcut += 'shift+'; }
if (isMac() && obj.ctrl_is_meta) { shortcut += 'meta+'; }
else if (obj.control) { shortcut += 'ctrl+'; }
shortcut += obj?.key.char?.toLowerCase();
shortcut += useKeyboardCode ? shortcutCharToCode(obj?.key.char) : obj?.key.char?.toLowerCase();
return shortcut;
}
export function parseKeyEventValue(e) {
export function parseKeyEventValue(e, useKeyboardCode=false) {
let shortcut = '';
if(!e) {
return null;
@ -40,7 +40,7 @@ export function parseKeyEventValue(e) {
if (e.shiftKey) { shortcut += 'shift+'; }
if (isMac() && e.metaKey) { shortcut += 'meta+'; }
else if (e.ctrlKey) { shortcut += 'ctrl+'; }
shortcut += e.key.toLowerCase();
shortcut += useKeyboardCode? e.code : e.key.toLowerCase();
return shortcut;
}
@ -49,6 +49,42 @@ export function isShortcutValue(obj) {
return [obj.alt, obj.control, obj?.key, obj?.key?.char].every((k)=>!_.isUndefined(k));
}
// Map shortcut character to key code
export function shortcutCharToCode(char) {
const punctuationMap = {
'`': 'Backquote',
'-': 'Minus',
'=': 'Equal',
'[': 'BracketLeft',
']': 'BracketRight',
'\\': 'Backslash',
';': 'Semicolon',
'\'': 'Quote',
',': 'Comma',
'.': 'Period',
'/': 'Slash',
' ': 'Space',
};
const mappedCode = punctuationMap[char.toLowerCase()];
if (mappedCode) {
return mappedCode;
}
// Fallback for alphanumeric keys (A-Z, 0-9)
const isAlphanumeric = /^[a-z0-9]$/i.test(char);
if (isAlphanumeric) {
if (char.length === 1 && /[a-zA-Z]/.test(char)) {
return `Key${char.toUpperCase()}`;
}
if (char.length === 1 && /[0-9]/.test(char)) {
return `Digit${char}`;
}
}
return char;
}
export function getEnterKeyHandler(clickHandler) {
return (e)=>{

View File

@ -15,7 +15,7 @@ import { LayoutDockerContext, LAYOUT_EVENTS } from '../../../../../../static/js/
import ConfirmSaveContent from '../../../../../../static/js/Dialogs/ConfirmSaveContent';
import gettext from 'sources/gettext';
import { isMac } from '../../../../../../static/js/keyboard_shortcuts';
import { checkTrojanSource, isShortcutValue, parseKeyEventValue, parseShortcutValue } from '../../../../../../static/js/utils';
import { checkTrojanSource, isShortcutValue, parseKeyEventValue, parseShortcutValue, shortcutCharToCode } from '../../../../../../static/js/utils';
import { usePgAdmin } from '../../../../../../static/js/PgAdminProvider';
import ConfirmPromotionContent from '../dialogs/ConfirmPromotionContent';
import ConfirmExecuteQueryContent from '../dialogs/ConfirmExecuteQueryContent';
@ -296,6 +296,7 @@ export default function Query({onTextSelect, setQtStatePartial}) {
// this function creates a key object from the shortcut preference
let key = {
keyCode: pref.key.key_code,
code: shortcutCharToCode(pref.key.char),
metaKey: false,
ctrlKey: pref.control,
shiftKey: pref.shift,