From 4adfeab70d4da096aa17a7a6206651b98ac7f04d Mon Sep 17 00:00:00 2001 From: pravesh-sharma Date: Mon, 9 Jun 2025 16:02:15 +0530 Subject: [PATCH 1/5] Added support to customize query tool edit menu keyboard shortcuts. #2659 --- .../js/components/ReactCodeMirror/index.jsx | 30 +++- .../js/components/QueryToolComponent.jsx | 50 ------ .../static/js/components/sections/Query.jsx | 145 ++++++++---------- .../sqleditor/utils/query_tool_preferences.py | 114 ++++++++++++++ 4 files changed, 202 insertions(+), 137 deletions(-) diff --git a/web/pgadmin/static/js/components/ReactCodeMirror/index.jsx b/web/pgadmin/static/js/components/ReactCodeMirror/index.jsx index 85861db3a19..b40299d7935 100644 --- a/web/pgadmin/static/js/components/ReactCodeMirror/index.jsx +++ b/web/pgadmin/static/js/components/ReactCodeMirror/index.jsx @@ -7,11 +7,12 @@ // ////////////////////////////////////////////////////////////// -import React, { useCallback, useMemo, useRef, useState, useEffect } from 'react'; +import React, { useCallback, useMemo, useRef, useState, useEffect, useContext } from 'react'; import { styled } from '@mui/material/styles'; import FileCopyRoundedIcon from '@mui/icons-material/FileCopyRounded'; import CheckRoundedIcon from '@mui/icons-material/CheckRounded'; import PropTypes from 'prop-types'; +import { startCompletion } from '@codemirror/autocomplete'; import gettext from 'sources/gettext'; import { PgIconButton } from '../Buttons'; @@ -22,6 +23,10 @@ import Editor from './components/Editor'; 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 { QueryToolEventsContext } from '../../../../tools/sqleditor/static/js/components/QueryToolComponent'; +import { QUERY_TOOL_EVENTS } from '../../../../tools/sqleditor/static/js/components/QueryToolConstants'; const Root = styled('div')(() => ({ position: 'relative', @@ -64,25 +69,42 @@ export default function CodeMirror({className, currEditor, showCopyBtn=false, cu const [[showFind, isReplace, findKey], setShowFind] = useState([false, false, false]); const [showGoto, setShowGoto] = useState(false); const [showCopy, setShowCopy] = useState(false); + const preferences = usePreferences().getPreferencesForModule('sqleditor'); + const eventBus = useContext(QueryToolEventsContext); const finalCustomKeyMap = useMemo(()=>[{ - key: 'Mod-f', run: () => { + key: toCodeMirrorKey(preferences.find), run: () => { setShowFind(prevVal => [true, false, !prevVal[2]]); }, preventDefault: true, stopPropagation: true, }, { - key: 'Mod-Alt-f', run: () => { + key: toCodeMirrorKey(preferences.replace), run: () => { setShowFind(prevVal => [true, true, !prevVal[2]]); }, preventDefault: true, stopPropagation: true, }, { - key: 'Mod-l', run: () => { + key: toCodeMirrorKey(preferences.gotolinecol), run: () => { setShowGoto(true); }, preventDefault: true, stopPropagation: true, + }, { + key: toCodeMirrorKey(preferences.comment), run: () => { + editor.current?.execCommand('toggleComment'); + }, + preventDefault: true, + stopPropagation: true, + },{ + key: toCodeMirrorKey(preferences.format_sql), run: () => { + eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_FORMAT_SQL); + }, + preventDefault: true, + stopPropagation: true, + },{ + key: toCodeMirrorKey(preferences.autocomplete), run: startCompletion, + preventDefault: true, }, ...customKeyMap], [customKeyMap]); diff --git a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx index 89452006191..9375453bf8a 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx @@ -92,36 +92,6 @@ function setPanelTitle(docker, panelId, title, qtState, dirty=false) { } const FIXED_PREF = { - find: { - 'control': true, - ctrl_is_meta: true, - 'shift': false, - 'alt': false, - 'key': { - 'key_code': 70, - 'char': 'F', - }, - }, - replace: { - 'control': true, - ctrl_is_meta: true, - 'shift': false, - 'alt': true, - 'key': { - 'key_code': 70, - 'char': 'F', - }, - }, - gotolinecol: { - 'control': true, - ctrl_is_meta: true, - 'shift': false, - 'alt': false, - 'key': { - 'key_code': 76, - 'char': 'L', - }, - }, indent: { 'control': false, 'shift': false, @@ -140,26 +110,6 @@ const FIXED_PREF = { 'char': 'Tab', }, }, - comment: { - 'control': true, - ctrl_is_meta: true, - 'shift': false, - 'alt': false, - 'key': { - 'key_code': 191, - 'char': '/', - }, - }, - format_sql: { - 'control': true, - ctrl_is_meta: true, - 'shift': false, - 'alt': false, - 'key': { - 'key_code': 75, - 'char': 'k', - }, - }, }; export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedNodeInfo, qtPanelDocker, qtPanelId, eventBusObj}) { diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx index fd81247a9d3..cd8818f7e99 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx @@ -10,7 +10,7 @@ import React, {useContext, useCallback, useEffect, useMemo, useState } from 'rea import { format } from 'sql-formatter'; import { QueryToolContext, QueryToolEventsContext } from '../QueryToolComponent'; import CodeMirror from '../../../../../../static/js/components/ReactCodeMirror'; -import { PANELS, QUERY_TOOL_EVENTS} from '../QueryToolConstants'; +import { PANELS, QUERY_TOOL_EVENTS, MODAL_DIALOGS } from '../QueryToolConstants'; import url_for from 'sources/url_for'; import { LayoutDockerContext, LAYOUT_EVENTS } from '../../../../../../static/js/helpers/Layout'; import ConfirmSaveContent from '../../../../../../static/js/Dialogs/ConfirmSaveContent'; @@ -24,10 +24,8 @@ import ConfirmExecuteQueryContent from '../dialogs/ConfirmExecuteQueryContent'; import usePreferences from '../../../../../../preferences/static/js/store'; import { getTitle } from '../../sqleditor_title'; import PropTypes from 'prop-types'; -import { MODAL_DIALOGS } from '../QueryToolConstants'; -import { useApplicationState } from '../../../../../../settings/static/ApplicationStateProvider'; +import { useApplicationState, getToolData } from '../../../../../../settings/static/ApplicationStateProvider'; import { useDelayDebounce } from '../../../../../../static/js/custom_hooks'; -import { getToolData } from '../../../../../../settings/static/ApplicationStateProvider'; async function registerAutocomplete(editor, api, transId) { @@ -235,24 +233,6 @@ export default function Query({onTextSelect, setQtStatePartial}) { }); }); - eventBus.registerListener(QUERY_TOOL_EVENTS.EDITOR_EXEC_CMD, (cmd='')=>{ - if(cmd == 'gotoLineCol') { - editor.current?.focus(); - let key = { - keyCode: 76, metaKey: false, ctrlKey: true, shiftKey: false, altKey: false, - }; - if(isMac()) { - key.metaKey = true; - key.ctrlKey = false; - key.shiftKey = false; - key.altKey = false; - } - editor.current?.fireDOMEvent(new KeyboardEvent('keydown', key)); - } else { - editor.current?.execCommand(cmd); - } - }); - eventBus.registerListener(QUERY_TOOL_EVENTS.COPY_TO_EDITOR, (text)=>{ editor.current?.setValue(text); eventBus.fireEvent(QUERY_TOOL_EVENTS.FOCUS_PANEL, PANELS.QUERY); @@ -262,20 +242,6 @@ export default function Query({onTextSelect, setQtStatePartial}) { }, 250); }); - eventBus.registerListener(QUERY_TOOL_EVENTS.EDITOR_FIND_REPLACE, (replace=false)=>{ - editor.current?.focus(); - let key = { - keyCode: 70, metaKey: false, ctrlKey: true, shiftKey: false, altKey: replace, - }; - if(isMac()) { - key.metaKey = true; - key.ctrlKey = false; - key.shiftKey = false; - key.altKey = replace; - } - editor.current?.fireDOMEvent(new KeyboardEvent('keydown', key)); - }); - eventBus.registerListener(QUERY_TOOL_EVENTS.EDITOR_SET_SQL, (value, focus=true)=>{ focus && editor.current?.focus(); editor.current?.setValue(value, !queryToolCtx.params.is_query_tool); @@ -283,40 +249,7 @@ export default function Query({onTextSelect, setQtStatePartial}) { eventBus.registerListener(QUERY_TOOL_EVENTS.TRIGGER_QUERY_CHANGE, ()=>{ change(); }); - - eventBus.registerListener(QUERY_TOOL_EVENTS.TRIGGER_FORMAT_SQL, ()=>{ - let selection = true, sql = editor.current?.getSelection(); - let sqlEditorPref = preferencesStore.getPreferencesForModule('sqleditor'); - /* New library does not support capitalize casing - so if a user has set capitalize casing we will - use preserve casing which is default for the library. - */ - let formatPrefs = { - language: 'postgresql', - keywordCase: sqlEditorPref.keyword_case === 'capitalize' ? 'preserve' : sqlEditorPref.keyword_case, - identifierCase: sqlEditorPref.identifier_case === 'capitalize' ? 'preserve' : sqlEditorPref.identifier_case, - dataTypeCase: sqlEditorPref.data_type_case, - functionCase: sqlEditorPref.function_case, - logicalOperatorNewline: sqlEditorPref.logical_operator_new_line, - expressionWidth: sqlEditorPref.expression_width, - linesBetweenQueries: sqlEditorPref.lines_between_queries, - tabWidth: sqlEditorPref.tab_size, - useTabs: !sqlEditorPref.use_spaces, - denseOperators: !sqlEditorPref.spaces_around_operators, - newlineBeforeSemicolon: sqlEditorPref.new_line_before_semicolon - }; - if(sql == '') { - sql = editor.current.getValue(); - selection = false; - } - let formattedSql = format(sql,formatPrefs); - if(selection) { - editor.current.replaceSelection(formattedSql, 'around'); - } else { - editor.current.setValue(formattedSql); - } - }); - + eventBus.registerListener(QUERY_TOOL_EVENTS.CHANGE_EOL, (lineSep)=>{ // Set the new EOL character in the editor. editor.current?.setEOL(lineSep); @@ -417,12 +350,62 @@ export default function Query({onTextSelect, setQtStatePartial}) { } }; + const unregisterEditorExecCmd = eventBus.registerListener(QUERY_TOOL_EVENTS.EDITOR_EXEC_CMD, (cmd='')=>{ + if(cmd == 'gotoLineCol') { + editor.current?.focus(); + let shortcut = queryToolCtx.preferences.sqleditor.gotolinecol; + let key = { + keyCode: shortcut.key.key_code, + metaKey: shortcut.ctrl_is_meta, + ctrlKey: shortcut.control, + shiftKey: shortcut.shift, + altKey: shortcut.alt, + }; + if(isMac()) { + key.metaKey = true; + key.ctrlKey = false; + } + editor.current?.fireDOMEvent(new KeyboardEvent('keydown', key)); + } else { + editor.current?.execCommand(cmd); + } + }); + + const unregisterFindReplace = eventBus.registerListener(QUERY_TOOL_EVENTS.EDITOR_FIND_REPLACE, (replace=false)=>{ + let findShortcut = queryToolCtx.preferences.sqleditor.find; + let replaceShortcut = queryToolCtx.preferences.sqleditor.replace; + let key ={}; + editor.current?.focus(); + if (!replace) { + key = { + keyCode: findShortcut.key.key_code, + metaKey: findShortcut.ctrl_is_meta, + ctrlKey: findShortcut.control, + shiftKey: findShortcut.shift, + altKey: findShortcut.alt, + }; + } else { + key.keyCode = replaceShortcut.key.key_code; + key.metaKey = replaceShortcut.ctrl_is_meta; + key.ctrlKey = replaceShortcut.control; + key.shiftKey = replaceShortcut.shift; + key.altKey = replaceShortcut.alt; + } + if(isMac()) { + key.metaKey = true; + key.ctrlKey = false; + } + editor.current?.fireDOMEvent(new KeyboardEvent('keydown', key)); + }); + const unregisterFormatSQL = eventBus.registerListener(QUERY_TOOL_EVENTS.TRIGGER_FORMAT_SQL, formatSQL); const unregisterWarn = eventBus.registerListener(QUERY_TOOL_EVENTS.WARN_SAVE_TEXT_CLOSE, warnSaveTextClose); return ()=>{ unregisterFormatSQL(); unregisterWarn(); + unregisterEditorExecCmd(); + unregisterFindReplace(); }; }, [queryToolCtx.preferences]); @@ -532,7 +515,7 @@ export default function Query({onTextSelect, setQtStatePartial}) { const shortcutOverrideKeys = useMemo( ()=>{ // omit CM internal shortcuts - const queryToolPref = _.omit(queryToolCtx.preferences.sqleditor, ['indent', 'unindent', 'comment']); + const queryToolPref = _.omit(queryToolCtx.preferences.sqleditor, ['indent', 'unindent']); const queryToolShortcuts = Object.values(queryToolPref) .filter((p)=>isShortcutValue(p)) .map((p)=>parseShortcutValue(p)); @@ -541,18 +524,14 @@ export default function Query({onTextSelect, setQtStatePartial}) { any: (_v, e)=>{ const eventStr = parseKeyEventValue(e); if(queryToolShortcuts.includes(eventStr)) { - if((isMac() && eventStr == 'meta+k') || eventStr == 'ctrl+k') { - eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_FORMAT_SQL); - } else { - queryToolCtx.mainContainerRef?.current?.dispatchEvent(new KeyboardEvent('keydown', { - which: e.which, - keyCode: e.keyCode, - altKey: e.altKey, - shiftKey: e.shiftKey, - ctrlKey: e.ctrlKey, - metaKey: e.metaKey, - })); - } + queryToolCtx.mainContainerRef?.current?.dispatchEvent(new KeyboardEvent('keydown', { + which: e.which, + keyCode: e.keyCode, + altKey: e.altKey, + shiftKey: e.shiftKey, + ctrlKey: e.ctrlKey, + metaKey: e.metaKey, + })); e.preventDefault(); e.stopPropagation(); return true; diff --git a/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py b/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py index 13c9bbd70ff..d4eb4ce99d3 100644 --- a/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py +++ b/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py @@ -873,6 +873,120 @@ def register_query_tool_preferences(self): fields=shortcut_fields ) + self.preference.register( + 'keyboard_shortcuts', + 'find', + gettext('Find'), + 'keyboardshortcut', + { + 'alt': False, + 'shift': False, + 'control': True, + 'ctrl_is_meta': True, + 'key': { + 'key_code': 70, + 'char': 'F' + } + }, + category_label=PREF_LABEL_KEYBOARD_SHORTCUTS, + fields=shortcut_fields + ) + + self.preference.register( + 'keyboard_shortcuts', + 'replace', + gettext('Replace'), + 'keyboardshortcut', + { + 'alt': True, + 'shift': False, + 'control': True, + 'ctrl_is_meta': True, + 'key': { + 'key_code': 70, + 'char': 'F' + } + }, + category_label=PREF_LABEL_KEYBOARD_SHORTCUTS, + fields=shortcut_fields + ) + + self.preference.register( + 'keyboard_shortcuts', + 'goto_line_col', + gettext('Go to line/column'), + 'keyboardshortcut', + { + 'alt': False, + 'shift': False, + 'control': True, + 'ctrl_is_meta': True, + 'key': { + 'key_code': 76, + 'char': 'L' + } + }, + category_label=PREF_LABEL_KEYBOARD_SHORTCUTS, + fields=shortcut_fields + ) + + self.preference.register( + 'keyboard_shortcuts', + 'comment', + gettext('Toggle comment'), + 'keyboardshortcut', + { + 'alt': False, + 'shift': False, + 'control': True, + 'ctrl_is_meta': True, + 'key': { + 'key_code': 191, + 'char': '/' + } + }, + category_label=PREF_LABEL_KEYBOARD_SHORTCUTS, + fields=shortcut_fields + ) + + self.preference.register( + 'keyboard_shortcuts', + 'format_sql', + gettext('Format SQL'), + 'keyboardshortcut', + { + 'alt': False, + 'shift': False, + 'control': True, + 'ctrl_is_meta': True, + 'key': { + 'key_code': 75, + 'char': 'K' + } + }, + category_label=PREF_LABEL_KEYBOARD_SHORTCUTS, + fields=shortcut_fields + ) + + self.preference.register( + 'keyboard_shortcuts', + 'auto_complete', + gettext('Auto complete'), + 'keyboardshortcut', + { + 'alt': False, + 'shift': False, + 'control': True, + 'ctrl_is_meta': False, + 'key': { + 'key_code': 32, + 'char': 'Space' + } + }, + category_label=PREF_LABEL_KEYBOARD_SHORTCUTS, + fields=shortcut_fields + ) + self.keyword_case = self.preference.register( 'editor', 'keyword_case', gettext("Keyword case"), 'radioModern', 'upper', From 15c17d17e0b70eb1d4bb580745778d7893184258 Mon Sep 17 00:00:00 2001 From: pravesh-sharma Date: Tue, 10 Jun 2025 15:28:58 +0530 Subject: [PATCH 2/5] Fixed js test case failure --- .../static/js/components/ReactCodeMirror/index.jsx | 14 ++++++-------- .../static/js/components/sections/Query.jsx | 1 + 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/web/pgadmin/static/js/components/ReactCodeMirror/index.jsx b/web/pgadmin/static/js/components/ReactCodeMirror/index.jsx index b40299d7935..cb65f9ee01a 100644 --- a/web/pgadmin/static/js/components/ReactCodeMirror/index.jsx +++ b/web/pgadmin/static/js/components/ReactCodeMirror/index.jsx @@ -7,7 +7,7 @@ // ////////////////////////////////////////////////////////////// -import React, { useCallback, useMemo, useRef, useState, useEffect, useContext } from 'react'; +import React, { useCallback, useMemo, useRef, useState, useEffect } from 'react'; import { styled } from '@mui/material/styles'; import FileCopyRoundedIcon from '@mui/icons-material/FileCopyRounded'; import CheckRoundedIcon from '@mui/icons-material/CheckRounded'; @@ -25,7 +25,6 @@ import FindDialog from './components/FindDialog'; import GotoDialog from './components/GotoDialog'; import usePreferences from '../../../../preferences/static/js/store'; import { toCodeMirrorKey } from '../../utils'; -import { QueryToolEventsContext } from '../../../../tools/sqleditor/static/js/components/QueryToolComponent'; import { QUERY_TOOL_EVENTS } from '../../../../tools/sqleditor/static/js/components/QueryToolConstants'; const Root = styled('div')(() => ({ @@ -64,14 +63,12 @@ CopyButton.propTypes = { }; -export default function CodeMirror({className, currEditor, showCopyBtn=false, customKeyMap=[], onTextSelect, ...props}) { +export default function CodeMirror({className, currEditor, showCopyBtn=false, customKeyMap=[], onTextSelect, eventBus, ...props}) { const editor = useRef(); const [[showFind, isReplace, findKey], setShowFind] = useState([false, false, false]); const [showGoto, setShowGoto] = useState(false); const [showCopy, setShowCopy] = useState(false); const preferences = usePreferences().getPreferencesForModule('sqleditor'); - const eventBus = useContext(QueryToolEventsContext); - const finalCustomKeyMap = useMemo(()=>[{ key: toCodeMirrorKey(preferences.find), run: () => { setShowFind(prevVal => [true, false, !prevVal[2]]); @@ -85,7 +82,7 @@ export default function CodeMirror({className, currEditor, showCopyBtn=false, cu preventDefault: true, stopPropagation: true, }, { - key: toCodeMirrorKey(preferences.gotolinecol), run: () => { + key: toCodeMirrorKey(preferences.goto_line_col), run: () => { setShowGoto(true); }, preventDefault: true, @@ -98,7 +95,7 @@ export default function CodeMirror({className, currEditor, showCopyBtn=false, cu stopPropagation: true, },{ key: toCodeMirrorKey(preferences.format_sql), run: () => { - eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_FORMAT_SQL); + eventBus?.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_FORMAT_SQL); }, preventDefault: true, stopPropagation: true, @@ -170,5 +167,6 @@ CodeMirror.propTypes = { className: CustomPropTypes.className, showCopyBtn: PropTypes.bool, customKeyMap: PropTypes.array, - onTextSelect:PropTypes.func + onTextSelect:PropTypes.func, + eventBus: PropTypes.object, }; diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx index cd8818f7e99..4fb8064dc8a 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx @@ -554,6 +554,7 @@ export default function Query({onTextSelect, setQtStatePartial}) { customKeyMap={shortcutOverrideKeys} onTextSelect={onTextSelect} disabled={queryToolCtx.editor_disabled} + eventBus={eventBus} />; } From 78d1fc0f48a24f48e377417343a67a82ea3d4da4 Mon Sep 17 00:00:00 2001 From: pravesh-sharma Date: Wed, 11 Jun 2025 16:04:53 +0530 Subject: [PATCH 3/5] Implemeted review comments. --- .../static/js/components/sections/MainToolBar.jsx | 2 +- .../static/js/components/sections/Query.jsx | 14 ++++++++------ .../sqleditor/utils/query_tool_preferences.py | 10 +++++----- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/MainToolBar.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/MainToolBar.jsx index d87b4a838e6..e44f8b7e2d5 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/MainToolBar.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/MainToolBar.jsx @@ -598,7 +598,7 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros, onAddT onClick={()=>{eventBus.fireEvent(QUERY_TOOL_EVENTS.EDITOR_FIND_REPLACE, false);}}>{gettext('Find')} {eventBus.fireEvent(QUERY_TOOL_EVENTS.EDITOR_FIND_REPLACE, true);}}>{gettext('Replace')} - {executeCmd('gotoLineCol');}}>{gettext('Go to Line/Column')} Date: Thu, 12 Jun 2025 17:39:39 +0530 Subject: [PATCH 4/5] Implemented review comments --- .../js/components/ReactCodeMirror/index.jsx | 42 +++++++++-- .../js/components/QueryToolConstants.js | 1 - .../js/components/sections/MainToolBar.jsx | 11 +-- .../static/js/components/sections/Query.jsx | 75 +++++++------------ .../sqleditor/utils/query_tool_preferences.py | 4 +- 5 files changed, 66 insertions(+), 67 deletions(-) diff --git a/web/pgadmin/static/js/components/ReactCodeMirror/index.jsx b/web/pgadmin/static/js/components/ReactCodeMirror/index.jsx index cb65f9ee01a..e3c12a03e71 100644 --- a/web/pgadmin/static/js/components/ReactCodeMirror/index.jsx +++ b/web/pgadmin/static/js/components/ReactCodeMirror/index.jsx @@ -13,6 +13,7 @@ import FileCopyRoundedIcon from '@mui/icons-material/FileCopyRounded'; import CheckRoundedIcon from '@mui/icons-material/CheckRounded'; import PropTypes from 'prop-types'; import { startCompletion } from '@codemirror/autocomplete'; +import { format } from 'sql-formatter'; import gettext from 'sources/gettext'; import { PgIconButton } from '../Buttons'; @@ -25,7 +26,6 @@ import FindDialog from './components/FindDialog'; import GotoDialog from './components/GotoDialog'; import usePreferences from '../../../../preferences/static/js/store'; import { toCodeMirrorKey } from '../../utils'; -import { QUERY_TOOL_EVENTS } from '../../../../tools/sqleditor/static/js/components/QueryToolConstants'; const Root = styled('div')(() => ({ position: 'relative', @@ -63,12 +63,45 @@ CopyButton.propTypes = { }; -export default function CodeMirror({className, currEditor, showCopyBtn=false, customKeyMap=[], onTextSelect, eventBus, ...props}) { +export default function CodeMirror({className, currEditor, showCopyBtn=false, customKeyMap=[], onTextSelect, ...props}) { const editor = useRef(); const [[showFind, isReplace, findKey], setShowFind] = useState([false, false, false]); const [showGoto, setShowGoto] = useState(false); const [showCopy, setShowCopy] = useState(false); const preferences = usePreferences().getPreferencesForModule('sqleditor'); + + const formatSQL = (view)=>{ + let selection = true, sql = view.getSelection(); + /* New library does not support capitalize casing + so if a user has set capitalize casing we will + use preserve casing which is default for the library. + */ + let formatPrefs = { + language: 'postgresql', + keywordCase: preferences.keyword_case === 'capitalize' ? 'preserve' : preferences.keyword_case, + identifierCase: preferences.identifier_case === 'capitalize' ? 'preserve' : preferences.identifier_case, + dataTypeCase: preferences.data_type_case, + functionCase: preferences.function_case, + logicalOperatorNewline: preferences.logical_operator_new_line, + expressionWidth: preferences.expression_width, + linesBetweenQueries: preferences.lines_between_queries, + tabWidth: preferences.tab_size, + useTabs: !preferences.use_spaces, + denseOperators: !preferences.spaces_around_operators, + newlineBeforeSemicolon: preferences.new_line_before_semicolon + }; + if(sql == '') { + sql = view.getValue(); + selection = false; + } + let formattedSql = format(sql,formatPrefs); + if(selection) { + view.replaceSelection(formattedSql); + } else { + view.setValue(formattedSql); + } + }; + const finalCustomKeyMap = useMemo(()=>[{ key: toCodeMirrorKey(preferences.find), run: () => { setShowFind(prevVal => [true, false, !prevVal[2]]); @@ -94,9 +127,7 @@ export default function CodeMirror({className, currEditor, showCopyBtn=false, cu preventDefault: true, stopPropagation: true, },{ - key: toCodeMirrorKey(preferences.format_sql), run: () => { - eventBus?.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_FORMAT_SQL); - }, + key: toCodeMirrorKey(preferences.format_sql), run: formatSQL, preventDefault: true, stopPropagation: true, },{ @@ -168,5 +199,4 @@ CodeMirror.propTypes = { showCopyBtn: PropTypes.bool, customKeyMap: PropTypes.array, onTextSelect:PropTypes.func, - eventBus: PropTypes.object, }; diff --git a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolConstants.js b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolConstants.js index 3b41efcf200..a60d2bf11a9 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolConstants.js +++ b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolConstants.js @@ -26,7 +26,6 @@ export const QUERY_TOOL_EVENTS = { TRIGGER_INCLUDE_EXCLUDE_FILTER: 'TRIGGER_INCLUDE_EXCLUDE_FILTER', TRIGGER_REMOVE_FILTER: 'TRIGGER_REMOVE_FILTER', TRIGGER_SET_LIMIT: 'TRIGGER_SET_LIMIT', - TRIGGER_FORMAT_SQL: 'TRIGGER_FORMAT_SQL', TRIGGER_GRAPH_VISUALISER: 'TRIGGER_GRAPH_VISUALISER', TRIGGER_SELECT_ALL: 'TRIGGER_SELECT_ALL', TRIGGER_SAVE_QUERY_TOOL_DATA: 'TRIGGER_SAVE_QUERY_TOOL_DATA', diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/MainToolBar.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/MainToolBar.jsx index e44f8b7e2d5..da0e64ad50a 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/MainToolBar.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/MainToolBar.jsx @@ -291,9 +291,6 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros, onAddT setLimit(e.target.value); eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_SET_LIMIT,e.target.value); }; - const formatSQL=()=>{ - eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_FORMAT_SQL); - }; const toggleCase=()=>{ eventBus.fireEvent(QUERY_TOOL_EVENTS.EDITOR_TOGGLE_CASE); }; @@ -444,12 +441,6 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros, onAddT callback: ()=>{clearQuery();} } }, - { - shortcut: queryToolPref.format_sql, - options: { - callback: ()=>{formatSQL();} - } - }, ], containerRef); /* Macro shortcuts */ @@ -612,7 +603,7 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros, onAddT {gettext('Clear Query')} - {gettext('Format SQL')} + {executeCmd('formatSql');}}>{gettext('Format SQL')} { @@ -318,57 +316,41 @@ export default function Query({onTextSelect, setQtStatePartial}) { ), {id:modalId}); }; - const formatSQL = ()=>{ - let selection = true, sql = editor.current?.getSelection(); - /* New library does not support capitalize casing - so if a user has set capitalize casing we will - use preserve casing which is default for the library. - */ - let formatPrefs = { - language: 'postgresql', - keywordCase: queryToolPref.keyword_case === 'capitalize' ? 'preserve' : queryToolPref.keyword_case, - identifierCase: queryToolPref.identifier_case === 'capitalize' ? 'preserve' : queryToolPref.identifier_case, - dataTypeCase: queryToolPref.data_type_case, - functionCase: queryToolPref.function_case, - logicalOperatorNewline: queryToolPref.logical_operator_new_line, - expressionWidth: queryToolPref.expression_width, - linesBetweenQueries: queryToolPref.lines_between_queries, - tabWidth: queryToolPref.tab_size, - useTabs: !queryToolPref.use_spaces, - denseOperators: !queryToolPref.spaces_around_operators, - newlineBeforeSemicolon: queryToolPref.new_line_before_semicolon - }; - if(sql == '') { - sql = editor.current.getValue(); - selection = false; - } - let formattedSql = format(sql,formatPrefs); - if(selection) { - editor.current.replaceSelection(formattedSql, 'around'); - } else { - editor.current.setValue(formattedSql); - } - }; - const unregisterEditorExecCmd = eventBus.registerListener(QUERY_TOOL_EVENTS.EDITOR_EXEC_CMD, (cmd='')=>{ - if(cmd == 'gotoLineCol') { - editor.current?.focus(); - let shortcut = queryToolCtx.preferences.sqleditor.gotolinecol; - let key = { - keyCode: shortcut.key.key_code, - metaKey: shortcut.ctrl_is_meta, - ctrlKey: shortcut.control, - shiftKey: shortcut.shift, - altKey: shortcut.alt, + let key = {}, gotolinecol = queryToolCtx.preferences.sqleditor.goto_line_col, + formatSql = queryToolCtx.preferences.sqleditor.format_sql; + switch(cmd) { + case 'gotoLineCol': + key = { + keyCode: gotolinecol.key.key_code, + metaKey: gotolinecol.ctrl_is_meta, + ctrlKey: gotolinecol.control, + shiftKey: gotolinecol.shift, + altKey: gotolinecol.alt, }; if(isMac()) { key.metaKey = true; key.ctrlKey = false; } - editor.current?.fireDOMEvent(new KeyboardEvent('keydown', key)); - } else { + break; + case 'formatSql': + key = { + keyCode: formatSql.key.key_code, + metaKey: formatSql.ctrl_is_meta, + ctrlKey: formatSql.control, + shiftKey: formatSql.shift, + altKey: formatSql.alt, + }; + if(isMac()) { + key.metaKey = true; + key.ctrlKey = false; + } + break; + default: editor.current?.execCommand(cmd); + return; } + editor.current?.fireDOMEvent(new KeyboardEvent('keydown', key)); }); const unregisterFindReplace = eventBus.registerListener(QUERY_TOOL_EVENTS.EDITOR_FIND_REPLACE, (replace=false)=>{ @@ -400,11 +382,9 @@ export default function Query({onTextSelect, setQtStatePartial}) { editor.current?.fireDOMEvent(new KeyboardEvent('keydown', key)); }); - const unregisterFormatSQL = eventBus.registerListener(QUERY_TOOL_EVENTS.TRIGGER_FORMAT_SQL, formatSQL); const unregisterWarn = eventBus.registerListener(QUERY_TOOL_EVENTS.WARN_SAVE_TEXT_CLOSE, warnSaveTextClose); return ()=>{ - unregisterFormatSQL(); unregisterWarn(); unregisterEditorExecCmd(); unregisterFindReplace(); @@ -556,7 +536,6 @@ export default function Query({onTextSelect, setQtStatePartial}) { customKeyMap={shortcutOverrideKeys} onTextSelect={onTextSelect} disabled={queryToolCtx.editor_disabled} - eventBus={eventBus} />; } diff --git a/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py b/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py index ea4641e35a7..aa5312d87a9 100644 --- a/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py +++ b/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py @@ -561,7 +561,7 @@ def register_query_tool_preferences(self): self.preference.register( 'keyboard_shortcuts', 'download_results', - gettext('Download Results'), + gettext('Download results'), 'keyboardshortcut', { 'alt': False, @@ -615,7 +615,7 @@ def register_query_tool_preferences(self): self.preference.register( 'keyboard_shortcuts', 'switch_panel', - gettext('Switch Panel'), + gettext('Switch panel'), 'keyboardshortcut', { 'alt': True, From 60665b742f57739a37b918b3f468d6a3d6d7093e Mon Sep 17 00:00:00 2001 From: pravesh-sharma Date: Thu, 12 Jun 2025 18:02:21 +0530 Subject: [PATCH 5/5] Added common logic for key creation --- .../static/js/components/sections/Query.jsx | 60 +++++++------------ 1 file changed, 20 insertions(+), 40 deletions(-) diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx index 501bc4168a7..9b5e0aa92ea 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx @@ -316,35 +316,31 @@ export default function Query({onTextSelect, setQtStatePartial}) { ), {id:modalId}); }; + const createKeyObjectFromShortcut = (pref)=>{ + // this function creates a key object from the shortcut preference + let key = { + keyCode: pref.key.key_code, + metaKey: pref.ctrl_is_meta, + ctrlKey: pref.control, + shiftKey: pref.shift, + altKey: pref.alt, + }; + if(isMac()) { + key.metaKey = true; + key.ctrlKey = false; + } + return key; + }; + const unregisterEditorExecCmd = eventBus.registerListener(QUERY_TOOL_EVENTS.EDITOR_EXEC_CMD, (cmd='')=>{ let key = {}, gotolinecol = queryToolCtx.preferences.sqleditor.goto_line_col, formatSql = queryToolCtx.preferences.sqleditor.format_sql; switch(cmd) { case 'gotoLineCol': - key = { - keyCode: gotolinecol.key.key_code, - metaKey: gotolinecol.ctrl_is_meta, - ctrlKey: gotolinecol.control, - shiftKey: gotolinecol.shift, - altKey: gotolinecol.alt, - }; - if(isMac()) { - key.metaKey = true; - key.ctrlKey = false; - } + key = createKeyObjectFromShortcut(gotolinecol); break; case 'formatSql': - key = { - keyCode: formatSql.key.key_code, - metaKey: formatSql.ctrl_is_meta, - ctrlKey: formatSql.control, - shiftKey: formatSql.shift, - altKey: formatSql.alt, - }; - if(isMac()) { - key.metaKey = true; - key.ctrlKey = false; - } + key = createKeyObjectFromShortcut(formatSql); break; default: editor.current?.execCommand(cmd); @@ -359,25 +355,9 @@ export default function Query({onTextSelect, setQtStatePartial}) { let key ={}; editor.current?.focus(); if (!replace) { - key = { - keyCode: findShortcut.key.key_code, - metaKey: findShortcut.ctrl_is_meta, - ctrlKey: findShortcut.control, - shiftKey: findShortcut.shift, - altKey: findShortcut.alt, - }; + key = createKeyObjectFromShortcut(findShortcut); } else { - key = { - keyCode: replaceShortcut.key.key_code, - metaKey: replaceShortcut.ctrl_is_meta, - ctrlKey: replaceShortcut.control, - shiftKe: replaceShortcut.shift, - altKey: replaceShortcut.alt, - }; - } - if(isMac()) { - key.metaKey = true; - key.ctrlKey = false; + key = createKeyObjectFromShortcut(replaceShortcut); } editor.current?.fireDOMEvent(new KeyboardEvent('keydown', key)); });