Skip to content

Commit 8841dac

Browse files
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
1 parent e09af0a commit 8841dac

File tree

4 files changed

+73
-32
lines changed

4 files changed

+73
-32
lines changed

docs/en_US/release_notes_9_8.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,5 @@ Bug fixes
3434
*********
3535

3636
| `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.
37-
| `Issue #9095 <https://github.com/pgadmin-org/pgadmin4/issues/9095>`_ - Fixed an issue where pgAdmin config migration was failing while upgrading to v9.7.
37+
| `Issue #9095 <https://github.com/pgadmin-org/pgadmin4/issues/9095>`_ - Fixed an issue where pgAdmin config migration was failing while upgrading to v9.7.
38+
| `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.

web/pgadmin/static/js/components/ReactCodeMirror/index.jsx

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import CustomPropTypes from '../../custom_prop_types';
2525
import FindDialog from './components/FindDialog';
2626
import GotoDialog from './components/GotoDialog';
2727
import usePreferences from '../../../../preferences/static/js/store';
28-
import { toCodeMirrorKey } from '../../utils';
28+
import { parseKeyEventValue, parseShortcutValue } from '../../utils';
2929

3030
const Root = styled('div')(() => ({
3131
position: 'relative',
@@ -103,39 +103,42 @@ export default function CodeMirror({className, currEditor, showCopyBtn=false, cu
103103
}
104104
};
105105

106-
const finalCustomKeyMap = useMemo(()=>[{
107-
key: toCodeMirrorKey(editorPrefs.find), run: () => {
106+
// We're not using CodeMirror keymap and using any instead
107+
// because on Mac, the alt key combination creates special
108+
// chars and https://github.com/codemirror/view/commit/3cea8dba19845fe75bea4eae756c6103694f49f3
109+
const customShortcuts = {
110+
[parseShortcutValue(editorPrefs.find, true)]: () => {
108111
setShowFind(prevVal => [true, false, !prevVal[2]]);
109112
},
110-
preventDefault: true,
111-
stopPropagation: true,
112-
}, {
113-
key: toCodeMirrorKey(editorPrefs.replace), run: () => {
113+
[parseShortcutValue(editorPrefs.replace, true)]: () => {
114114
setShowFind(prevVal => [true, true, !prevVal[2]]);
115115
},
116-
preventDefault: true,
117-
stopPropagation: true,
118-
}, {
119-
key: toCodeMirrorKey(editorPrefs.goto_line_col), run: () => {
116+
[parseShortcutValue(editorPrefs.goto_line_col, true)]: () => {
120117
setShowGoto(true);
121118
},
122-
preventDefault: true,
123-
stopPropagation: true,
124-
}, {
125-
key: toCodeMirrorKey(editorPrefs.comment), run: () => {
119+
[parseShortcutValue(editorPrefs.comment, true)]: () => {
126120
editor.current?.execCommand('toggleComment');
127121
},
128-
preventDefault: true,
129-
stopPropagation: true,
130-
},{
131-
key: toCodeMirrorKey(editorPrefs.format_sql), run: formatSQL,
132-
preventDefault: true,
133-
stopPropagation: true,
134-
},{
135-
key: toCodeMirrorKey(preferences.auto_complete), run: startCompletion,
136-
preventDefault: true,
137-
},
138-
...customKeyMap], [customKeyMap]);
122+
[parseShortcutValue(editorPrefs.format_sql, true)]: formatSQL,
123+
[parseShortcutValue(preferences.auto_complete, true)]: startCompletion,
124+
};
125+
126+
const finalCustomKeyMap = useMemo(() => [
127+
{
128+
any: (view, e) => {
129+
const eventStr = parseKeyEventValue(e, true);
130+
const callback = customShortcuts[eventStr];
131+
if(callback) {
132+
callback(view);
133+
e.preventDefault();
134+
e.stopPropagation();
135+
return true;
136+
}
137+
return false;
138+
}
139+
},
140+
...customKeyMap
141+
], [customKeyMap]);
139142

140143
const closeFind = () => {
141144
setShowFind([false, false, false]);

web/pgadmin/static/js/utils.js

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import pgAdmin from 'sources/pgadmin';
1818
import { isMac } from './keyboard_shortcuts';
1919
import { WORKSPACES } from '../../browser/static/js/constants';
2020

21-
export function parseShortcutValue(obj) {
21+
export function parseShortcutValue(obj, useKeyboardCode=false) {
2222
let shortcut = '';
2323
if (!obj){
2424
return null;
@@ -27,11 +27,11 @@ export function parseShortcutValue(obj) {
2727
if (obj.shift) { shortcut += 'shift+'; }
2828
if (isMac() && obj.ctrl_is_meta) { shortcut += 'meta+'; }
2929
else if (obj.control) { shortcut += 'ctrl+'; }
30-
shortcut += obj?.key.char?.toLowerCase();
30+
shortcut += useKeyboardCode ? shortcutCharToCode(obj?.key.char) : obj?.key.char?.toLowerCase();
3131
return shortcut;
3232
}
3333

34-
export function parseKeyEventValue(e) {
34+
export function parseKeyEventValue(e, useKeyboardCode=false) {
3535
let shortcut = '';
3636
if(!e) {
3737
return null;
@@ -40,7 +40,7 @@ export function parseKeyEventValue(e) {
4040
if (e.shiftKey) { shortcut += 'shift+'; }
4141
if (isMac() && e.metaKey) { shortcut += 'meta+'; }
4242
else if (e.ctrlKey) { shortcut += 'ctrl+'; }
43-
shortcut += e.key.toLowerCase();
43+
shortcut += useKeyboardCode? e.code : e.key.toLowerCase();
4444
return shortcut;
4545
}
4646

@@ -49,6 +49,42 @@ export function isShortcutValue(obj) {
4949
return [obj.alt, obj.control, obj?.key, obj?.key?.char].every((k)=>!_.isUndefined(k));
5050
}
5151

52+
// Map shortcut character to key code
53+
export function shortcutCharToCode(char) {
54+
const punctuationMap = {
55+
'`': 'Backquote',
56+
'-': 'Minus',
57+
'=': 'Equal',
58+
'[': 'BracketLeft',
59+
']': 'BracketRight',
60+
'\\': 'Backslash',
61+
';': 'Semicolon',
62+
'\'': 'Quote',
63+
',': 'Comma',
64+
'.': 'Period',
65+
'/': 'Slash',
66+
' ': 'Space',
67+
};
68+
69+
const mappedCode = punctuationMap[char.toLowerCase()];
70+
if (mappedCode) {
71+
return mappedCode;
72+
}
73+
74+
// Fallback for alphanumeric keys (A-Z, 0-9)
75+
const isAlphanumeric = /^[a-z0-9]$/i.test(char);
76+
if (isAlphanumeric) {
77+
if (char.length === 1 && /[a-zA-Z]/.test(char)) {
78+
return `Key${char.toUpperCase()}`;
79+
}
80+
if (char.length === 1 && /[0-9]/.test(char)) {
81+
return `Digit${char}`;
82+
}
83+
}
84+
85+
return char;
86+
}
87+
5288

5389
export function getEnterKeyHandler(clickHandler) {
5490
return (e)=>{

web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { LayoutDockerContext, LAYOUT_EVENTS } from '../../../../../../static/js/
1515
import ConfirmSaveContent from '../../../../../../static/js/Dialogs/ConfirmSaveContent';
1616
import gettext from 'sources/gettext';
1717
import { isMac } from '../../../../../../static/js/keyboard_shortcuts';
18-
import { checkTrojanSource, isShortcutValue, parseKeyEventValue, parseShortcutValue } from '../../../../../../static/js/utils';
18+
import { checkTrojanSource, isShortcutValue, parseKeyEventValue, parseShortcutValue, shortcutCharToCode } from '../../../../../../static/js/utils';
1919
import { usePgAdmin } from '../../../../../../static/js/PgAdminProvider';
2020
import ConfirmPromotionContent from '../dialogs/ConfirmPromotionContent';
2121
import ConfirmExecuteQueryContent from '../dialogs/ConfirmExecuteQueryContent';
@@ -296,6 +296,7 @@ export default function Query({onTextSelect, setQtStatePartial}) {
296296
// this function creates a key object from the shortcut preference
297297
let key = {
298298
keyCode: pref.key.key_code,
299+
code: shortcutCharToCode(pref.key.char),
299300
metaKey: false,
300301
ctrlKey: pref.control,
301302
shiftKey: pref.shift,

0 commit comments

Comments
 (0)