From d3ede3151ab27c8edfd3d55b74e996d4bea3805c Mon Sep 17 00:00:00 2001 From: Aditya Toshniwal Date: Wed, 21 Feb 2024 11:15:25 +0530 Subject: [PATCH] Upgrade CodeMirror from version 5 to 6. #7097 --- web/package.json | 4 +- .../servers/pgagent/static/css/pga_job.css | 3 - web/pgadmin/browser/static/js/browser.js | 14 +- web/pgadmin/browser/static/scss/_browser.scss | 3 +- .../browser/templates/browser/css/node.css | 2 +- .../browser/templates/browser/index.html | 2 +- .../dashboard/static/css/dashboard.css | 8 - web/pgadmin/misc/sql/static/js/SQL.jsx | 8 +- web/pgadmin/static/bundle/codemirror.js | 58 -- web/pgadmin/static/css/style.css | 4 - web/pgadmin/static/js/Theme/dark.js | 22 +- web/pgadmin/static/js/Theme/high_contrast.js | 22 +- web/pgadmin/static/js/Theme/index.jsx | 2 + .../js/Theme/overrides/codemirror.override.js | 129 ++++ web/pgadmin/static/js/Theme/standard.js | 24 +- .../addon/fold/pgadmin-sqlfoldcode.js | 131 ---- .../js/codemirror/extension/centre_on_line.js | 16 - .../static/js/components/CodeMirror.jsx | 587 ------------------ .../static/js/components/FormComponents.jsx | 22 +- .../ReactCodeMirror/CustomEditorView.js | 150 +++++ .../js/components/ReactCodeMirror/Editor.jsx | 432 +++++++++++++ .../components/ReactCodeMirror/FindDialog.jsx | 203 ++++++ .../extensions/activeLineMarker.js | 33 + .../extensions/breakpointGutter.js | 67 ++ .../ReactCodeMirror/extensions/dialect.js | 14 + .../ReactCodeMirror/extensions/errorMarker.js | 35 ++ .../extensions/highlighting.js | 41 ++ .../js/components/ReactCodeMirror/index.jsx | 33 + .../static/scss/_codemirror.overrides.scss | 208 ------- web/pgadmin/static/scss/_pgadmin.style.scss | 4 - web/pgadmin/static/scss/pgadmin.scss | 1 - .../js/components/DebuggerComponent.jsx | 148 +---- .../static/js/components/DebuggerEditor.jsx | 44 +- .../debugger/templates/debugger/direct.html | 2 +- .../tools/erd/templates/erd/index.html | 2 +- .../static/scss/_grant_wizard.scss | 11 - .../templates/schema_diff/index.html | 2 +- .../static/js/components/sections/Query.jsx | 312 ++-------- .../js/components/sections/QueryHistory.jsx | 2 +- .../sqleditor/templates/sqleditor/index.html | 2 +- .../query_tool_auto_complete_tests.py | 21 +- .../feature_tests/table_ddl_feature_test.py | 2 +- .../test_copy_sql_to_query_tool.py | 21 +- .../xss_checks_panels_and_query_tool_test.py | 4 +- web/regression/feature_utils/locators.py | 7 +- web/regression/feature_utils/pgadmin_page.py | 9 +- .../SchemaView/SchemaDialogView.spec.js | 6 +- .../__mocks__/bundled_codemirror.js | 39 -- .../javascript/components/CodeMirror.spec.js | 82 ++- web/regression/javascript/setup-jest.js | 14 + web/webpack.config.js | 1 - web/webpack.shim.js | 1 - web/yarn.lock | 162 ++++- 53 files changed, 1565 insertions(+), 1611 deletions(-) delete mode 100644 web/pgadmin/static/bundle/codemirror.js create mode 100644 web/pgadmin/static/js/Theme/overrides/codemirror.override.js delete mode 100644 web/pgadmin/static/js/codemirror/addon/fold/pgadmin-sqlfoldcode.js delete mode 100644 web/pgadmin/static/js/codemirror/extension/centre_on_line.js delete mode 100644 web/pgadmin/static/js/components/CodeMirror.jsx create mode 100644 web/pgadmin/static/js/components/ReactCodeMirror/CustomEditorView.js create mode 100644 web/pgadmin/static/js/components/ReactCodeMirror/Editor.jsx create mode 100644 web/pgadmin/static/js/components/ReactCodeMirror/FindDialog.jsx create mode 100644 web/pgadmin/static/js/components/ReactCodeMirror/extensions/activeLineMarker.js create mode 100644 web/pgadmin/static/js/components/ReactCodeMirror/extensions/breakpointGutter.js create mode 100644 web/pgadmin/static/js/components/ReactCodeMirror/extensions/dialect.js create mode 100644 web/pgadmin/static/js/components/ReactCodeMirror/extensions/errorMarker.js create mode 100644 web/pgadmin/static/js/components/ReactCodeMirror/extensions/highlighting.js create mode 100644 web/pgadmin/static/js/components/ReactCodeMirror/index.jsx delete mode 100644 web/pgadmin/static/scss/_codemirror.overrides.scss delete mode 100644 web/regression/javascript/__mocks__/bundled_codemirror.js diff --git a/web/package.json b/web/package.json index 65a7ec005..4dcfe8176 100644 --- a/web/package.json +++ b/web/package.json @@ -76,6 +76,7 @@ "dependencies": { "@babel/plugin-proposal-class-properties": "^7.10.4", "@babel/preset-react": "^7.12.13", + "@codemirror/lang-sql": "^6.5.5", "@date-io/core": "^1.3.6", "@date-io/date-fns": "1.x", "@emotion/sheet": "^1.0.1", @@ -102,7 +103,7 @@ "chartjs-plugin-zoom": "^2.0.1", "classnames": "^2.2.6", "closest": "^0.0.1", - "codemirror": "^5.59.2", + "codemirror": "^6.0.1", "convert-units": "^2.3.4", "cssnano": "^5.0.2", "dagre": "^0.8.4", @@ -174,6 +175,7 @@ "bundle": "cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=2048 yarn run bundle:dev", "test:js-once": "yarn run linter && yarn run jest --maxWorkers=50%", "test:js": "yarn run test:js-once --watch", + "test:js-file": "yarn run test:js-once -t", "test:js-coverage": "yarn run test:js-once --collect-coverage", "test:feature": "yarn run bundle && python regression/runtests.py --pkg feature_tests", "test": "yarn run test:js-once && yarn run bundle && python regression/runtests.py", diff --git a/web/pgadmin/browser/server_groups/servers/pgagent/static/css/pga_job.css b/web/pgadmin/browser/server_groups/servers/pgagent/static/css/pga_job.css index 1f1060fcb..af8a30b22 100644 --- a/web/pgadmin/browser/server_groups/servers/pgagent/static/css/pga_job.css +++ b/web/pgadmin/browser/server_groups/servers/pgagent/static/css/pga_job.css @@ -1,6 +1,3 @@ div[role=tabpanel] > .pgadmin-control-group.form-group.c.jscexceptions { min-height: 400px; } -div[role=tabpanel] >.pgadmin-control-group.jstcode .CodeMirror-sizer{ - height: 400px; -} diff --git a/web/pgadmin/browser/static/js/browser.js b/web/pgadmin/browser/static/js/browser.js index 847d1a045..39c38f6d3 100644 --- a/web/pgadmin/browser/static/js/browser.js +++ b/web/pgadmin/browser/static/js/browser.js @@ -17,17 +17,14 @@ import usePreferences, { setupPreferenceBroadcast } from '../../../preferences/s import checkNodeVisibility from '../../../static/js/check_node_visibility'; define('pgadmin.browser', [ - 'sources/gettext', 'sources/url_for', 'sources/pgadmin', 'bundled_codemirror', + 'sources/gettext', 'sources/url_for', 'sources/pgadmin', 'sources/csrf', 'pgadmin.authenticate.kerberos', 'pgadmin.browser.utils', 'pgadmin.browser.messages', 'pgadmin.browser.node', 'pgadmin.browser.collection', 'pgadmin.browser.activity', - 'sources/codemirror/addon/fold/pgadmin-sqlfoldcode', 'pgadmin.browser.keyboard', 'sources/tree/pgadmin_tree_save_state', ], function( - gettext, url_for, pgAdmin, codemirror, csrfToken, Kerberos, + gettext, url_for, pgAdmin, csrfToken, Kerberos, ) { - let CodeMirror = codemirror.default; - let pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {}; let select_object_msg = gettext('Please select an object in the tree view.'); @@ -1766,13 +1763,6 @@ define('pgadmin.browser', [ }, }); - /* Remove paste event mapping from CodeMirror's emacsy KeyMap binding - * specific to Mac LineNumber:5797 - lib/Codemirror.js - * It is preventing default paste event(Cmd-V) from triggering - * in runtime. - */ - delete CodeMirror.keyMap.emacsy['Ctrl-V']; - // Use spaces instead of tab if (pgBrowser.utils.useSpaces == 'True') { pgAdmin.Browser.editor_shortcut_keys.Tab = 'insertSoftTab'; diff --git a/web/pgadmin/browser/static/scss/_browser.scss b/web/pgadmin/browser/static/scss/_browser.scss index 34ac6e981..91b48aecb 100644 --- a/web/pgadmin/browser/static/scss/_browser.scss +++ b/web/pgadmin/browser/static/scss/_browser.scss @@ -7,8 +7,7 @@ code, kbd, pre, -samp, -.CodeMirror pre { +samp { font-family: $font-family-editor !important; } diff --git a/web/pgadmin/browser/templates/browser/css/node.css b/web/pgadmin/browser/templates/browser/css/node.css index 737df0894..4e7edf298 100644 --- a/web/pgadmin/browser/templates/browser/css/node.css +++ b/web/pgadmin/browser/templates/browser/css/node.css @@ -1,7 +1,7 @@ {% if file_name is not defined %} {% set file_name=node_type %} {% endif %} -.icon-{{file_name}} { +.icon-{{file_name}}, .cm-autocomplete-option-{{file_name}} .cm-completionIcon { background-image: url('{{ url_for('NODE-%s.static' % node_type, filename='img/%s.svg' % file_name )}}') !important; background-repeat: no-repeat; background-size: 20px !important; diff --git a/web/pgadmin/browser/templates/browser/index.html b/web/pgadmin/browser/templates/browser/index.html index a35836e15..2952a396b 100644 --- a/web/pgadmin/browser/templates/browser/index.html +++ b/web/pgadmin/browser/templates/browser/index.html @@ -16,7 +16,7 @@ window.hookConsole = function(callback) { } try { require( -['sources/generated/app.bundle', 'sources/generated/codemirror', 'sources/generated/browser_nodes'], +['sources/generated/app.bundle', 'sources/generated/browser_nodes'], function() { }, function() { diff --git a/web/pgadmin/dashboard/static/css/dashboard.css b/web/pgadmin/dashboard/static/css/dashboard.css index efaa3800f..09c5ce47a 100644 --- a/web/pgadmin/dashboard/static/css/dashboard.css +++ b/web/pgadmin/dashboard/static/css/dashboard.css @@ -35,14 +35,6 @@ font-size: inherit; } -#server_activity .CodeMirror, -#database_activity .CodeMirror, -#server_activity .CodeMirror-scroll, -#database_activity .CodeMirror-scroll { - height: auto; - max-height:100px; -} - .dashboard-hidden { display: none; } diff --git a/web/pgadmin/misc/sql/static/js/SQL.jsx b/web/pgadmin/misc/sql/static/js/SQL.jsx index 125d8f68d..5347848af 100644 --- a/web/pgadmin/misc/sql/static/js/SQL.jsx +++ b/web/pgadmin/misc/sql/static/js/SQL.jsx @@ -13,7 +13,7 @@ import gettext from 'sources/gettext'; import PropTypes from 'prop-types'; import getApiInstance from 'sources/api_instance'; import { makeStyles } from '@material-ui/core/styles'; -import CodeMirror from '../../../../static/js/components/CodeMirror'; +import CodeMirror from '../../../../static/js/components/ReactCodeMirror'; import Loader from 'sources/components/Loader'; import withStandardTabInfo from '../../../../static/js/helpers/withStandardTabInfo'; import { BROWSER_PANELS } from '../../../../browser/static/js/constants'; @@ -24,7 +24,6 @@ const useStyles = makeStyles((theme) => ({ height: '100% !important', width: '100% !important', background: theme.palette.grey[400], - overflow: 'auto !important', minHeight: '100%', minWidth: '100%', }, @@ -99,10 +98,7 @@ function SQL({nodeData, node, treeNodeInfo, isActive, isStale, setIsStale}) { className={classes.textArea} value={nodeSQL} readonly={true} - options={{ - lineNumbers: true, - mode: 'text/x-pgsql', - }} + showCopyBtn /> ); diff --git a/web/pgadmin/static/bundle/codemirror.js b/web/pgadmin/static/bundle/codemirror.js deleted file mode 100644 index afec3005d..000000000 --- a/web/pgadmin/static/bundle/codemirror.js +++ /dev/null @@ -1,58 +0,0 @@ -///////////////////////////////////////////////////////////// -// -// pgAdmin 4 - PostgreSQL Tools -// -// Copyright (C) 2013 - 2024, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -////////////////////////////////////////////////////////////// - -import CodeMirror from 'codemirror/lib/codemirror'; -import 'codemirror/mode/sql/sql'; -import 'codemirror/addon/selection/mark-selection'; -import 'codemirror/addon/selection/active-line'; -import 'codemirror/addon/fold/foldcode'; -import 'codemirror/addon/fold/foldgutter'; -import 'codemirror/addon/hint/show-hint'; -import 'codemirror/addon/hint/sql-hint'; -import 'codemirror/addon/scroll/simplescrollbars'; -import 'codemirror/addon/dialog/dialog'; -import 'codemirror/addon/search/search'; -import 'codemirror/addon/search/searchcursor'; -import 'codemirror/addon/search/jump-to-line'; -import 'codemirror/addon/edit/matchbrackets'; -import 'codemirror/addon/edit/closebrackets'; -import 'codemirror/addon/comment/comment'; -import 'sources/codemirror/addon/fold/pgadmin-sqlfoldcode'; -import 'sources/codemirror/extension/centre_on_line'; - -let cmds = CodeMirror.commands; -cmds.focusOut = function(){ - event.stopPropagation(); - document.activeElement.blur(); - if(event.currentTarget.hasOwnProperty('parents') && event.currentTarget.parents().find('.sql-code-control')) { - // for code mirror in dialogs - event.currentTarget.parents().find('.sql-code-control').focus(); - } -}; - -CodeMirror.defineInitHook(function (codeMirror) { - codeMirror.addKeyMap({ - Tab: function (cm) { - if(cm.somethingSelected()){ - cm.execCommand('indentMore'); - } - else { - if (cm.getOption('indentWithTabs')) { - cm.replaceSelection('\t', 'end', '+input'); - } - else { - cm.execCommand('insertSoftTab'); - } - } - }, - }); -}); - -CodeMirror.keyMap.default['Esc'] = 'focusOut'; -export default CodeMirror; diff --git a/web/pgadmin/static/css/style.css b/web/pgadmin/static/css/style.css index 674c17ffe..c190c1740 100644 --- a/web/pgadmin/static/css/style.css +++ b/web/pgadmin/static/css/style.css @@ -1,10 +1,6 @@ @import 'node_modules/@fortawesome/fontawesome-free/css/all.css'; @import 'node_modules/leaflet/dist/leaflet.css'; -@import 'node_modules/codemirror/lib/codemirror.css'; -@import 'node_modules/codemirror/addon/dialog/dialog.css'; -@import 'node_modules/codemirror/addon/scroll/simplescrollbars.css'; - @import 'node_modules/xterm/css/xterm.css'; @import 'node_modules/jsoneditor/dist/jsoneditor.min.css'; diff --git a/web/pgadmin/static/js/Theme/dark.js b/web/pgadmin/static/js/Theme/dark.js index bff32e95f..ce77688be 100644 --- a/web/pgadmin/static/js/Theme/dark.js +++ b/web/pgadmin/static/js/Theme/dark.js @@ -111,7 +111,27 @@ export default function(basicSettings) { diffColorFg: '#d4d4d4', diffSelectFG: '#d4d4d4', diffSelCheckbox: '#323E43' - } + }, + editor: { + fg: '#fff', + bg: '#212121', + selectionBg: '#536270', + keyword: '#db7c74', + number: '#7fcc5c', + string: '#e4e487', + variable: '#7dc9f1', + type: '#7dc9f1', + comment: '#7fcc5c', + punctuation: '#d6aaaa', + operator: '#d6aaaa', + //// + foldmarker: '#0000FF', + activeline: '#323e43', + activelineLight: '#323e43', + activelineBorderColor: 'none', + guttersBg: '#303030', + guttersFg: '#8A8A8A', + }, } }); } diff --git a/web/pgadmin/static/js/Theme/high_contrast.js b/web/pgadmin/static/js/Theme/high_contrast.js index 30578c0a1..1a5f79b14 100644 --- a/web/pgadmin/static/js/Theme/high_contrast.js +++ b/web/pgadmin/static/js/Theme/high_contrast.js @@ -109,7 +109,27 @@ export default function(basicSettings) { diffColorFg: '#FFFFFF', diffSelectFG: '#010B15', diffSelCheckbox: '#010b15', - } + }, + editor: { + fg: '#fff', + bg: '#010B15', + selectionBg: '#1F2932', + keyword: '#F8845F', + number: '#45D48A', + string: '#EAEA43', + variable: '#7DC9F1', + type: '#7DC9F1', + comment: '#FFAD65', + punctuation: '#d6aaaa', + operator: '#d6aaaa', + //// + foldmarker: '#FFFFFF', + activeline: '#063057', + activelineLight: '#063057', + activelineBorderColor: 'none', + guttersBg: '#2d3a48', + guttersFg: '#8b9cac', + }, } }); } diff --git a/web/pgadmin/static/js/Theme/index.jsx b/web/pgadmin/static/js/Theme/index.jsx index 9143e87b7..70bdfb397 100644 --- a/web/pgadmin/static/js/Theme/index.jsx +++ b/web/pgadmin/static/js/Theme/index.jsx @@ -23,6 +23,7 @@ import { CssBaseline } from '@material-ui/core'; import pickrOverride from './overrides/pickr.override'; import uplotOverride from './overrides/uplot.override'; import rcdockOverride from './overrides/rcdock.override'; +import cmOverride from './overrides/codemirror.override'; /* Common settings across all themes */ let basicSettings = createTheme(); @@ -333,6 +334,7 @@ function getFinalTheme(baseTheme) { ...pickrOverride(baseTheme), ...uplotOverride(baseTheme), ...rcdockOverride(baseTheme), + ...cmOverride(baseTheme) }, }, MuiOutlinedInput: { diff --git a/web/pgadmin/static/js/Theme/overrides/codemirror.override.js b/web/pgadmin/static/js/Theme/overrides/codemirror.override.js new file mode 100644 index 000000000..668140006 --- /dev/null +++ b/web/pgadmin/static/js/Theme/overrides/codemirror.override.js @@ -0,0 +1,129 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2023, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +export default function cmOverride(theme) { + const editor = theme.otherVars.editor; + return { + '.cm-editor': { + height: '100%', + color: editor.fg, + backgroundColor: editor.bg, + + '&.cm-focused': { + outline: 'none', + + '& .cm-scroller > .cm-selectionLayer .cm-selectionBackground': { + background: editor.selectionBg, + } + }, + + '& .cm-scroller': { + ...theme.mixins.fontSourceCode, + + '& .cm-content': { + '&[aria-readonly="true"] + .cm-cursorLayer': { + display: 'none', + }, + + '& .cm-activeLine': { + backgroundColor: editor.activeline, + }, + + '& .cm-activeLineManual': { + backgroundColor: editor.activeline, + }, + + '& .tok-keyword': { + color: editor.keyword, + fontWeight: 600 + }, + '& .tok-number': { + color: editor.number, + fontWeight: 600 + }, + '& .tok-string': {color: editor.string}, + '& .tok-variable': {color: editor.variable }, + '& .tok-comment': {color: editor.comment}, + '& .tok-operator': { color: editor.operator }, + '& .tok-punctuation': {color: editor.punctuation}, + '& .tok-typeName': {color: editor.type}, + }, + + '& .cm-selectionLayer': { + '& .cm-selectionBackground': { + background: editor.selectionBg, + } + }, + }, + + '& .cm-cursorLayer': { + '& .cm-cursor, & .cm-dropCursor': { + borderLeftColor: editor.fg, + } + }, + + '& .cm-gutters': { + backgroundColor: editor.guttersBg, + color: editor.guttersFg, + borderRight: 'none', + + '& .cm-foldGutter': { + padding: '0px', + color: editor.fg, + }, + + '& .cm-breakpoint-gutter': { + padding: '0px 2px', + cursor: 'pointer', + '& .cm-gutterElement': { + fontSize: '1.3em', + lineHeight: '1.1', + color: 'red' + } + } + + }, + + '& .cm-panels-bottom': { + border: '0 !important', + + '& .cm-search': { + display: 'none', + } + }, + '& .cm-error-highlight': { + borderBottom: '2px dotted red', + } + }, + '.cm-tooltip': { + backgroundColor: theme.palette.background.default + '!important', + color: theme.palette.text.primary + '!important', + border: `1px solid ${theme.otherVars.borderColor} !important`, + + '& li[aria-selected="true"]': { + backgroundColor: theme.otherVars.treeBgSelected + '!important', + color: theme.otherVars.treeFgSelected + '!important', + }, + + '& .pg-cm-autocomplete-icon': { + // marginRight: '2px', + marginLeft: '-2px', + padding: '0px 8px', + backgroundPosition: '50%', + width: '20px', + display: 'inline-block', + }, + + '&.pg-autocomp-loader': { + position: 'absolute', + paddingRight: '8px', + } + } + }; +} diff --git a/web/pgadmin/static/js/Theme/standard.js b/web/pgadmin/static/js/Theme/standard.js index 700f9c7eb..e541800f7 100644 --- a/web/pgadmin/static/js/Theme/standard.js +++ b/web/pgadmin/static/js/Theme/standard.js @@ -130,7 +130,29 @@ export default function(basicSettings) { diffColorFg: '#222', diffSelectFG: '#222', diffSelCheckbox: '#d6effc' - } + }, + editor: { + fg: '#222', + bg: '#fff', + selectionBg: '#d6effc', + keyword: '#908', + number: '#964', + string: '#a11', + variable: '#222', + type: '#05a', + comment: '#a50', + punctuation: '#737373', + operator: '#222', + //// + foldmarker: '#0000FF', + activeline: '#EDF9FF', + activelineLight: '#EDF9FF', + activelineBorderColor: '#BCDEF3', + guttersBg: '#f3f5f9', + guttersFg: '#848ea0', + }, + treeBgSelected: '#d6effc', + treeFgSelected: '#222', } }); } diff --git a/web/pgadmin/static/js/codemirror/addon/fold/pgadmin-sqlfoldcode.js b/web/pgadmin/static/js/codemirror/addon/fold/pgadmin-sqlfoldcode.js deleted file mode 100644 index 69fa04145..000000000 --- a/web/pgadmin/static/js/codemirror/addon/fold/pgadmin-sqlfoldcode.js +++ /dev/null @@ -1,131 +0,0 @@ -///////////////////////////////////////////////////////////// -// -// pgAdmin 4 - PostgreSQL Tools -// -// Copyright (C) 2013 - 2024, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -////////////////////////////////////////////////////////////// - -(function(mod) { - if (typeof exports == 'object' && typeof module == 'object') // CommonJS - mod(require('codemirror')); - else if (typeof define == 'function' && define.amd) // AMD - define(['codemirror'], mod); - else // Plain browser env - mod(window.CodeMirror); -})(function(CodeMirror) { - 'use strict'; - - let pgadminKeywordRangeFinder = function(cm, start, tokenSet) { - let line = start.line, - lineText = cm.getLine(line); - let at = lineText.length, - startChar, tokenType; - - let tokenSetNo = 0, - startToken, endToken; - let startTkn = tokenSet[tokenSetNo].start, - endTkn = tokenSet[tokenSetNo].end; - while (at > 0) { - let found = lineText.toUpperCase().lastIndexOf(startTkn, at); - found = checkStartTokenFoundOnEndToken(found, lineText.toUpperCase(), endTkn, startTkn); - startToken = startTkn; - endToken = endTkn; - - if (found < start.ch) { - /* If the start token is not found then search for the next set of token */ - tokenSetNo++; - if(tokenSetNo >= tokenSet.length) { - return undefined; - } - startTkn = tokenSet[tokenSetNo].start; - endTkn = tokenSet[tokenSetNo].end; - at = lineText.length; - continue; - } - - tokenType = cm.getTokenAt(CodeMirror.Pos(line, found + 1)).type; - if (!/^(comment|string)/.test(tokenType)) { - startChar = found; - break; - } - at = found - 1; - } - if (startChar == null || lineText.toUpperCase().lastIndexOf(startToken) > startChar) return; - let count = 1, - lastLine = cm.lineCount(), - end, endCh; - outer: for (let i = line + 1; i < lastLine; ++i) { - let text = cm.getLine(i).toUpperCase(), - pos = 0; - let whileloopvar = 0; - while (whileloopvar < 1) { - let nextOpen = text.indexOf(startToken, pos); - nextOpen = checkStartTokenFoundOnEndToken(nextOpen, text, endToken, startToken); - - let nextClose = text.indexOf(endToken, pos); - if (nextOpen < 0) nextOpen = text.length; - if (nextClose < 0) nextClose = text.length; - pos = Math.min(nextOpen, nextClose); - if (pos == text.length) { whileloopvar=1; break; } - if (cm.getTokenAt(CodeMirror.Pos(i, pos + 1)).type == tokenType) { - if (pos == nextOpen) ++count; - else if (!--count) { - end = i; - endCh = pos; - break outer; - } - } - ++pos; - } - } - if (end == null || end == line + 1) return; - return { - from: CodeMirror.Pos(line, startChar + startTkn.length), - to: CodeMirror.Pos(end, endCh), - }; - }; - - /** - * This function is responsible for finding whether the startToken is present - * in the endToken as well, to avoid mismatch of start and end points. - * e.g. In case of IF and END IF, IF is detected in both tokens, which creates - * confusion. The said function will resolve such issues. - * @function checkStartTokenFoundOnEndToken - * @returns {Number} - returns found - */ - function checkStartTokenFoundOnEndToken(found, text, endToken, startToken) { - if(found > 0) { - if(text.includes(endToken) - || !checkTokenMixedWithOtherAlphabets(text, startToken)) { - found = -1; - } - } - return found; - } - - /** - * This function is responsible for finding whether the startToken is mixed - * with other alphabets of the text. To avoid word like NOTIFY to be mistakenly treat as keyword. - * e.g. to avoid the IF detected as keyword in the word pgAdmin.Browser.notifier. - * Function also works with other tokens like LOOP, CASE, etc. - * @function checkTokenMixedWithOtherAlphabets - * @returns {Boolean} - returns true/false - */ - function checkTokenMixedWithOtherAlphabets(text, startToken) { - //this reg will check the token should be in format as - IF condition or IF(condition) - let reg = `\\b\\${startToken}\\s*\\(\\w*\\)(?!\\w)|\\b\\${startToken}\\(\\w*\\)(?!\\w)|\\b\\${startToken}\\s*(?!\\w)`; - let regex = RegExp(reg, 'g'); - return regex.exec(text) !== null; - } - - CodeMirror.registerHelper('fold', 'sql', function(cm, start) { - return pgadminKeywordRangeFinder(cm, start, [ - {start: 'BEGIN', end:'END;'}, - {start: 'IF', end:'END IF'}, - {start: 'LOOP', end:'END LOOP'}, - {start: 'CASE', end:'END CASE'}, - ]); - }); -}); diff --git a/web/pgadmin/static/js/codemirror/extension/centre_on_line.js b/web/pgadmin/static/js/codemirror/extension/centre_on_line.js deleted file mode 100644 index e1232813b..000000000 --- a/web/pgadmin/static/js/codemirror/extension/centre_on_line.js +++ /dev/null @@ -1,16 +0,0 @@ -///////////////////////////////////////////////////////////// -// -// pgAdmin 4 - PostgreSQL Tools -// -// Copyright (C) 2013 - 2024, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -////////////////////////////////////////////////////////////// - -import CodeMirror from 'codemirror/lib/codemirror'; - -CodeMirror.defineExtension('centerOnLine', function(line) { - let ht = this.getScrollInfo().clientHeight; - let coords = this.charCoords({line: line, ch: 0}, 'local'); - this.scrollTo(null, (coords.top + coords.bottom - ht) / 2); -}); diff --git a/web/pgadmin/static/js/components/CodeMirror.jsx b/web/pgadmin/static/js/components/CodeMirror.jsx deleted file mode 100644 index a1e73ae6d..000000000 --- a/web/pgadmin/static/js/components/CodeMirror.jsx +++ /dev/null @@ -1,587 +0,0 @@ -///////////////////////////////////////////////////////////// -// -// pgAdmin 4 - PostgreSQL Tools -// -// Copyright (C) 2013 - 2024, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -////////////////////////////////////////////////////////////// - -import React, { useEffect, useMemo, useRef, useState } from 'react'; -import OrigCodeMirror from 'bundled_codemirror'; -import {useOnScreen} from 'sources/custom_hooks'; -import PropTypes from 'prop-types'; -import CustomPropTypes from '../custom_prop_types'; -import gettext from 'sources/gettext'; -import { Box, InputAdornment, makeStyles } from '@material-ui/core'; -import clsx from 'clsx'; -import { InputText } from './FormComponents'; -import { DefaultButton, PgIconButton } from './Buttons'; -import CloseIcon from '@material-ui/icons/CloseRounded'; -import ArrowDownwardRoundedIcon from '@material-ui/icons/ArrowDownwardRounded'; -import ArrowUpwardRoundedIcon from '@material-ui/icons/ArrowUpwardRounded'; -import SwapHorizRoundedIcon from '@material-ui/icons/SwapHorizRounded'; -import SwapCallsRoundedIcon from '@material-ui/icons/SwapCallsRounded'; -import _ from 'lodash'; -import { RegexIcon, FormatCaseIcon } from './ExternalIcon'; -import { isMac } from '../keyboard_shortcuts'; -import { checkTrojanSource } from '../utils'; -import { copyToClipboard } from '../clipboard'; -import { useDelayedCaller } from '../../../static/js/custom_hooks'; -import usePreferences from '../../../preferences/static/js/store'; - -const useStyles = makeStyles((theme)=>({ - root: { - position: 'relative', - }, - hideCursor: { - '& .CodeMirror-cursors': { - display: 'none' - } - }, - findDialog: { - position: 'absolute', - zIndex: 99, - right: '4px', - ...theme.mixins.panelBorder.all, - borderTop: 'none', - padding: '2px 4px', - width: '250px', - backgroundColor: theme.palette.background.default, - }, - marginTop: { - marginTop: '0.25rem', - }, - copyButton: { - position: 'absolute', - zIndex: 99, - right: '4px', - width: '66px', - } -})); - -function parseString(string) { - return string.replace(/\\([nrt\\])/g, function(match, ch) { - if (ch == 'n') return '\n'; - if (ch == 'r') return '\r'; - if (ch == 't') return '\t'; - if (ch == '\\') return '\\'; - return match; - }); -} - -function parseQuery(query, useRegex=false, matchCase=false) { - try { - if (useRegex) { - query = new RegExp(query, matchCase ? 'g': 'gi'); - } else { - query = parseString(query); - if(!matchCase) { - query = query.toLowerCase(); - } - } - if (typeof query == 'string' ? query == '' : query.test('')) - query = /x^/; - return query; - } catch (error) { - return null; - } -} - -function getRegexFinder(query) { - return (stream) => { - query.lastIndex = stream.pos; - let match = query.exec(stream.string); - if (match && match.index == stream.pos) { - stream.pos += match[0].length || 1; - return 'searching'; - } else if (match) { - stream.pos = match.index; - } else { - stream.skipToEnd(); - } - }; -} - - -function getPlainStringFinder(query, matchCase) { - return (stream) => { - let matchIndex = (matchCase ? stream.string : stream.string.toLowerCase()).indexOf(query, stream.pos); - if(matchIndex == -1) { - stream.skipToEnd(); - } else if(matchIndex == stream.pos) { - stream.pos += query.length; - return 'searching'; - } else { - stream.pos = matchIndex; - } - }; -} - -function searchOverlay(query, matchCase) { - return { - token: typeof query == 'string' ? - getPlainStringFinder(query, matchCase) : getRegexFinder(query) - }; -} - -export const CodeMirrorInstancType = PropTypes.shape({ - getCursor: PropTypes.func, - getSearchCursor: PropTypes.func, - removeOverlay: PropTypes.func, - addOverlay: PropTypes.func, - setSelection: PropTypes.func, - scrollIntoView: PropTypes.func, - getSelection: PropTypes.func, -}); - -export function FindDialog({editor, show, replace, onClose, selFindVal}) { - const [findVal, setFindVal] = useState(selFindVal); - const [replaceVal, setReplaceVal] = useState(''); - const [useRegex, setUseRegex] = useState(false); - const [matchCase, setMatchCase] = useState(false); - const findInputRef = useRef(); - const highlightsearch = useRef(); - const searchCursor = useRef(); - const classes = useStyles(); - - const search = ()=>{ - if(editor) { - let query = parseQuery(findVal, useRegex, matchCase); - if(!query) return; - - searchCursor.current = editor.getSearchCursor(query, 0, !matchCase); - if(findVal != '') { - editor.removeOverlay(highlightsearch.current); - highlightsearch.current = searchOverlay(query, matchCase); - editor.addOverlay(highlightsearch.current); - onFindNext(); - } else { - editor.removeOverlay(highlightsearch.current); - } - } - }; - - useEffect(()=>{ - if(show) { - // Get selected text from editor and set it to find/replace input. - let selText = editor.getSelection(); - if(selText.length != 0) { - setFindVal(selText); - } - findInputRef.current?.select(); - search(); - } - }, [show]); - - useEffect(()=>{ - search(); - }, [findVal, useRegex, matchCase]); - - const clearAndClose = ()=>{ - editor.removeOverlay(highlightsearch.current); - onClose(); - }; - - const toggle = (name)=>{ - if(name == 'regex') { - setUseRegex((prev)=>!prev); - } else if(name == 'case') { - setMatchCase((prev)=>!prev); - } - }; - - const onFindEnter = (e) => { - if (e.key === 'Enter') { - e.preventDefault(); - if(e.shiftKey) { - onFindPrev(); - } else { - onFindNext(); - } - } - }; - - const onReplaceEnter = (e) => { - if (e.key === 'Enter') { - e.preventDefault(); - onReplace(); - } - }; - - const onEscape = (e)=>{ - if (e.key === 'Escape') { - e.preventDefault(); - clearAndClose(); - } - }; - - const onFindNext = ()=>{ - if(searchCursor.current?.find()) { - editor.setSelection(searchCursor.current.from(), searchCursor.current.to()); - editor.scrollIntoView({ - from: searchCursor.current.from(), - to: searchCursor.current.to() - }, 20); - } - }; - - const onFindPrev = ()=>{ - if(searchCursor.current?.find(true)) { - editor.setSelection(searchCursor.current.from(), searchCursor.current.to()); - editor.scrollIntoView({ - from: searchCursor.current.from(), - to: searchCursor.current.to() - }, 20); - } - }; - - const onReplace = ()=>{ - searchCursor.current.replace(replaceVal); - onFindNext(); - }; - - const onReplaceAll = ()=>{ - /* search from start */ - search(); - while(searchCursor.current.from()) { - onReplace(); - } - }; - - if(!editor) { - return <>; - } - - return ( - - {findInputRef.current = ele;}} - onChange={(value)=>setFindVal(value)} - onKeyPress={onFindEnter} - placeholder={gettext('Find text')} - controlProps={{ - title: gettext('Find text') - }} - endAdornment={ - - } size="xs" noBorder - onClick={()=>toggle('case')} color={matchCase ? 'primary' : 'default'} style={{marginRight: '2px'}}/> - } size="xs" noBorder - onClick={()=>toggle('regex')} color={useRegex ? 'primary' : 'default'}/> - - } - /> - {replace && - setReplaceVal(value)} - onKeyPress={onReplaceEnter} - placeholder={gettext('Replace value')} - controlProps={{ - title: gettext('Replace value') - }} - />} - - - } size="xs" noBorder onClick={onFindPrev} - style={{marginRight: '2px'}} /> - } size="xs" noBorder onClick={onFindNext} - style={{marginRight: '2px'}} /> - {replace && <> - } size="xs" noBorder onClick={onReplace} - style={{marginRight: '2px'}} /> - } size="xs" noBorder onClick={onReplaceAll}/> - } - - } size="xs" noBorder onClick={clearAndClose}/> - - - - ); -} - -FindDialog.propTypes = { - editor: CodeMirrorInstancType, - show: PropTypes.bool, - replace: PropTypes.bool, - onClose: PropTypes.func, - selFindVal: PropTypes.string, -}; - -export function CopyButton({show, copyText}) { - const classes = useStyles(); - const [copyBtnLabel, setCopyBtnLabel] = useState(gettext('Copy')); - const revertCopiedText = useDelayedCaller(()=>{ - setCopyBtnLabel(gettext('Copy')); - }); - - return ( - - { - copyToClipboard(copyText); - setCopyBtnLabel(gettext('Copied!')); - revertCopiedText(1500); - }}>{copyBtnLabel} - - ); -} - -CopyButton.propTypes = { - show: PropTypes.bool, - copyText: PropTypes.string -}; - -function handleDrop(editor, e) { - let dropDetails = null; - try { - dropDetails = JSON.parse(e.dataTransfer.getData('text')); - - /* Stop firefox from redirecting */ - - if(e.preventDefault) { - e.preventDefault(); - } - if (e.stopPropagation) { - e.stopPropagation(); - } - } catch(error) { - /* if parsing fails, it must be the drag internal of codemirror text */ - return; - } - - let cursor = editor.coordsChar({ - left: e.x, - top: e.y, - }); - editor.replaceRange(dropDetails.text, cursor); - editor.focus(); - editor.setSelection({ - ...cursor, - ch: cursor.ch + dropDetails.cur.from, - },{ - ...cursor, - ch: cursor.ch +dropDetails.cur.to, - }); -} - -function calcFontSize(fontSize) { - if(fontSize) { - fontSize = parseFloat((Math.round(parseFloat(fontSize + 'e+2')) + 'e-2')); - let rounded = Number(fontSize); - if(rounded > 0) { - return rounded + 'em'; - } - } - return '1em'; -} - -async function handlePaste(_editor, e) { - let copiedText = await e.clipboardData.getData('text'); - checkTrojanSource(copiedText, true); -} - -/* React wrapper for CodeMirror */ -export default function CodeMirror({currEditor, name, value, options, events, readonly, disabled, className, autocomplete=false, gutters=['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], showCopyBtn=false, cid, helpid}) { - const taRef = useRef(); - const editor = useRef(); - const cmWrapper = useRef(); - const isVisibleTrack = useRef(); - const classes = useStyles(); - const [[showFind, isReplace], setShowFind] = useState([false, false]); - const [showCopy, setShowCopy] = useState(false); - const preferencesStore = usePreferences(); - const defaultOptions = useMemo(()=>{ - let goLeftKey = 'Ctrl-Alt-Left', - goRightKey = 'Ctrl-Alt-Right', - commentKey = 'Ctrl-/'; - if(isMac()) { - goLeftKey = 'Cmd-Alt-Left'; - goRightKey = 'Cmd-Alt-Right'; - commentKey = 'Cmd-/'; - } - return { - tabindex: '0', - lineNumbers: true, - styleSelectedText: true, - mode: 'text/x-pgsql', - foldOptions: { - widget: '\u2026', - }, - foldGutter: true, - gutters: gutters, - extraKeys: { - // Autocomplete sql command - ...(autocomplete ? { - 'Ctrl-Space': 'autocomplete', - }: {}), - 'Alt-Up': 'goLineUp', - 'Alt-Down': 'goLineDown', - // Move word by word left/right - [goLeftKey]: 'goGroupLeft', - [goRightKey]: 'goGroupRight', - // Allow user to delete Tab(s) - 'Shift-Tab': 'indentLess', - //comment - [commentKey]: 'toggleComment', - }, - dragDrop: true, - screenReaderLabel: gettext('SQL editor'), - }; - }); - - useEffect(()=>{ - const finalOptions = {...defaultOptions, ...options}; - /* Create the object only once on mount */ - editor.current = new OrigCodeMirror.fromTextArea( - taRef.current, finalOptions); - - if(!_.isEmpty(value)) { - editor.current.setValue(value); - } else { - editor.current.setValue(''); - } - currEditor?.(editor.current); - if(editor.current) { - try { - cmWrapper.current = editor.current.getWrapperElement(); - } catch(e) { - cmWrapper.current = null; - } - - let findKey = 'Ctrl-F', replaceKey = 'Shift-Ctrl-F'; - if(isMac()) { - findKey = 'Cmd-F'; - replaceKey = 'Cmd-Alt-F'; - } - editor.current.addKeyMap({ - [findKey]: ()=>{ - setShowFind([false, false]); - setShowFind([true, false]); - }, - [replaceKey]: ()=>{ - if(!finalOptions.readOnly) { - setShowFind([false, false]); - setShowFind([true, true]); - } - }, - 'Cmd-G': false, - }); - } - - Object.keys(events||{}).forEach((eventName)=>{ - editor.current.on(eventName, events[eventName]); - }); - - editor.current.on('drop', handleDrop); - editor.current.on('paste', handlePaste); - return ()=>{ - editor.current?.toTextArea(); - }; - }, []); - - const autocompKeyup = (cm, event)=>{ - if (!cm.state.completionActive && (event.key == 'Backspace' || /^[ -~]{1}$/.test(event.key))) { - OrigCodeMirror.commands.autocomplete(cm, null, {completeSingle: false}); - } - }; - - useEffect(()=>{ - let pref = preferencesStore.getPreferencesForModule('sqleditor'); - let wrapEle = editor.current?.getWrapperElement(); - wrapEle && (wrapEle.style.fontSize = calcFontSize(pref.sql_font_size)); - - // Register keyup event if autocomplete is true - if (autocomplete && pref.autocomplete_on_key_press) { - editor.current.on('keyup', autocompKeyup); - } - - if(pref.plain_editor_mode) { - editor.current?.setOption('mode', 'text/plain'); - /* Although not required, setting explicitly as codemirror will remove code folding only on next edit */ - editor.current?.setOption('foldGutter', false); - } else { - editor.current?.setOption('mode', 'text/x-pgsql'); - editor.current?.setOption('foldGutter', pref.code_folding); - } - - editor.current?.setOption('indentWithTabs', !pref.use_spaces); - editor.current?.setOption('indentUnit', pref.tab_size); - editor.current?.setOption('tabSize', pref.tab_size); - editor.current?.setOption('lineWrapping', pref.wrap_code); - editor.current?.setOption('autoCloseBrackets', pref.insert_pair_brackets); - editor.current?.setOption('matchBrackets', pref.brace_matching); - editor.current?.refresh(); - }, [preferencesStore]); - - useEffect(()=>{ - if(editor.current) { - if(readonly || disabled) { - editor.current.setOption('readOnly', true); - editor.current.addKeyMap({'Tab': false}); - editor.current.addKeyMap({'Shift-Tab': false}); - cmWrapper.current.classList.add(classes.hideCursor); - } else { - editor.current.setOption('readOnly', false); - editor.current.removeKeyMap('Tab'); - editor.current.removeKeyMap('Shift-Tab'); - cmWrapper.current.classList.remove(classes.hideCursor); - } - } - }, [readonly, disabled]); - - useMemo(() => { - if(editor.current) { - if(value != editor.current.getValue()) { - if(!_.isEmpty(value)) { - editor.current.setValue(value); - } else { - editor.current.setValue(''); - } - } - } - }, [value]); - - const onScreenVisible = useOnScreen(cmWrapper); - if(!isVisibleTrack.current && onScreenVisible) { - isVisibleTrack.current = true; - editor.current?.refresh(); - } else if(!onScreenVisible) { - isVisibleTrack.current = false; - } - - const closeFind = ()=>{ - setShowFind([false, false]); - editor.current?.focus(); - }; - - return ( -
{ showCopyBtn && value.length > 0 && setShowCopy(true);}} - onMouseLeave={() => {showCopyBtn && setShowCopy(false);}} - > - 0 ? editor.current.getSelection() : ''}/> - -