mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Upgrade CodeMirror from version 5 to 6. #7097
This commit is contained in:
parent
721290b1e9
commit
d3ede3151a
@ -76,6 +76,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/plugin-proposal-class-properties": "^7.10.4",
|
"@babel/plugin-proposal-class-properties": "^7.10.4",
|
||||||
"@babel/preset-react": "^7.12.13",
|
"@babel/preset-react": "^7.12.13",
|
||||||
|
"@codemirror/lang-sql": "^6.5.5",
|
||||||
"@date-io/core": "^1.3.6",
|
"@date-io/core": "^1.3.6",
|
||||||
"@date-io/date-fns": "1.x",
|
"@date-io/date-fns": "1.x",
|
||||||
"@emotion/sheet": "^1.0.1",
|
"@emotion/sheet": "^1.0.1",
|
||||||
@ -102,7 +103,7 @@
|
|||||||
"chartjs-plugin-zoom": "^2.0.1",
|
"chartjs-plugin-zoom": "^2.0.1",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"closest": "^0.0.1",
|
"closest": "^0.0.1",
|
||||||
"codemirror": "^5.59.2",
|
"codemirror": "^6.0.1",
|
||||||
"convert-units": "^2.3.4",
|
"convert-units": "^2.3.4",
|
||||||
"cssnano": "^5.0.2",
|
"cssnano": "^5.0.2",
|
||||||
"dagre": "^0.8.4",
|
"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",
|
"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-once": "yarn run linter && yarn run jest --maxWorkers=50%",
|
||||||
"test:js": "yarn run test:js-once --watch",
|
"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:js-coverage": "yarn run test:js-once --collect-coverage",
|
||||||
"test:feature": "yarn run bundle && python regression/runtests.py --pkg feature_tests",
|
"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",
|
"test": "yarn run test:js-once && yarn run bundle && python regression/runtests.py",
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
div[role=tabpanel] > .pgadmin-control-group.form-group.c.jscexceptions {
|
div[role=tabpanel] > .pgadmin-control-group.form-group.c.jscexceptions {
|
||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
}
|
}
|
||||||
div[role=tabpanel] >.pgadmin-control-group.jstcode .CodeMirror-sizer{
|
|
||||||
height: 400px;
|
|
||||||
}
|
|
||||||
|
@ -17,17 +17,14 @@ import usePreferences, { setupPreferenceBroadcast } from '../../../preferences/s
|
|||||||
import checkNodeVisibility from '../../../static/js/check_node_visibility';
|
import checkNodeVisibility from '../../../static/js/check_node_visibility';
|
||||||
|
|
||||||
define('pgadmin.browser', [
|
define('pgadmin.browser', [
|
||||||
'sources/gettext', 'sources/url_for', 'sources/pgadmin', 'bundled_codemirror',
|
'sources/gettext', 'sources/url_for', 'sources/pgadmin',
|
||||||
'sources/csrf', 'pgadmin.authenticate.kerberos',
|
'sources/csrf', 'pgadmin.authenticate.kerberos',
|
||||||
'pgadmin.browser.utils', 'pgadmin.browser.messages',
|
'pgadmin.browser.utils', 'pgadmin.browser.messages',
|
||||||
'pgadmin.browser.node', 'pgadmin.browser.collection', 'pgadmin.browser.activity',
|
'pgadmin.browser.node', 'pgadmin.browser.collection', 'pgadmin.browser.activity',
|
||||||
'sources/codemirror/addon/fold/pgadmin-sqlfoldcode',
|
|
||||||
'pgadmin.browser.keyboard', 'sources/tree/pgadmin_tree_save_state',
|
'pgadmin.browser.keyboard', 'sources/tree/pgadmin_tree_save_state',
|
||||||
], function(
|
], 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 pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
|
||||||
let select_object_msg = gettext('Please select an object in the tree view.');
|
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
|
// Use spaces instead of tab
|
||||||
if (pgBrowser.utils.useSpaces == 'True') {
|
if (pgBrowser.utils.useSpaces == 'True') {
|
||||||
pgAdmin.Browser.editor_shortcut_keys.Tab = 'insertSoftTab';
|
pgAdmin.Browser.editor_shortcut_keys.Tab = 'insertSoftTab';
|
||||||
|
@ -7,8 +7,7 @@
|
|||||||
code,
|
code,
|
||||||
kbd,
|
kbd,
|
||||||
pre,
|
pre,
|
||||||
samp,
|
samp {
|
||||||
.CodeMirror pre {
|
|
||||||
font-family: $font-family-editor !important;
|
font-family: $font-family-editor !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{% if file_name is not defined %}
|
{% if file_name is not defined %}
|
||||||
{% set file_name=node_type %}
|
{% set file_name=node_type %}
|
||||||
{% endif %}
|
{% 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-image: url('{{ url_for('NODE-%s.static' % node_type, filename='img/%s.svg' % file_name )}}') !important;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: 20px !important;
|
background-size: 20px !important;
|
||||||
|
@ -16,7 +16,7 @@ window.hookConsole = function(callback) {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
require(
|
require(
|
||||||
['sources/generated/app.bundle', 'sources/generated/codemirror', 'sources/generated/browser_nodes'],
|
['sources/generated/app.bundle', 'sources/generated/browser_nodes'],
|
||||||
function() {
|
function() {
|
||||||
},
|
},
|
||||||
function() {
|
function() {
|
||||||
|
@ -35,14 +35,6 @@
|
|||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
#server_activity .CodeMirror,
|
|
||||||
#database_activity .CodeMirror,
|
|
||||||
#server_activity .CodeMirror-scroll,
|
|
||||||
#database_activity .CodeMirror-scroll {
|
|
||||||
height: auto;
|
|
||||||
max-height:100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-hidden {
|
.dashboard-hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ import gettext from 'sources/gettext';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import getApiInstance from 'sources/api_instance';
|
import getApiInstance from 'sources/api_instance';
|
||||||
import { makeStyles } from '@material-ui/core/styles';
|
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 Loader from 'sources/components/Loader';
|
||||||
import withStandardTabInfo from '../../../../static/js/helpers/withStandardTabInfo';
|
import withStandardTabInfo from '../../../../static/js/helpers/withStandardTabInfo';
|
||||||
import { BROWSER_PANELS } from '../../../../browser/static/js/constants';
|
import { BROWSER_PANELS } from '../../../../browser/static/js/constants';
|
||||||
@ -24,7 +24,6 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
height: '100% !important',
|
height: '100% !important',
|
||||||
width: '100% !important',
|
width: '100% !important',
|
||||||
background: theme.palette.grey[400],
|
background: theme.palette.grey[400],
|
||||||
overflow: 'auto !important',
|
|
||||||
minHeight: '100%',
|
minHeight: '100%',
|
||||||
minWidth: '100%',
|
minWidth: '100%',
|
||||||
},
|
},
|
||||||
@ -99,10 +98,7 @@ function SQL({nodeData, node, treeNodeInfo, isActive, isStale, setIsStale}) {
|
|||||||
className={classes.textArea}
|
className={classes.textArea}
|
||||||
value={nodeSQL}
|
value={nodeSQL}
|
||||||
readonly={true}
|
readonly={true}
|
||||||
options={{
|
showCopyBtn
|
||||||
lineNumbers: true,
|
|
||||||
mode: 'text/x-pgsql',
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -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;
|
|
@ -1,10 +1,6 @@
|
|||||||
@import 'node_modules/@fortawesome/fontawesome-free/css/all.css';
|
@import 'node_modules/@fortawesome/fontawesome-free/css/all.css';
|
||||||
@import 'node_modules/leaflet/dist/leaflet.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/xterm/css/xterm.css';
|
||||||
|
|
||||||
@import 'node_modules/jsoneditor/dist/jsoneditor.min.css';
|
@import 'node_modules/jsoneditor/dist/jsoneditor.min.css';
|
||||||
|
@ -111,7 +111,27 @@ export default function(basicSettings) {
|
|||||||
diffColorFg: '#d4d4d4',
|
diffColorFg: '#d4d4d4',
|
||||||
diffSelectFG: '#d4d4d4',
|
diffSelectFG: '#d4d4d4',
|
||||||
diffSelCheckbox: '#323E43'
|
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',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,27 @@ export default function(basicSettings) {
|
|||||||
diffColorFg: '#FFFFFF',
|
diffColorFg: '#FFFFFF',
|
||||||
diffSelectFG: '#010B15',
|
diffSelectFG: '#010B15',
|
||||||
diffSelCheckbox: '#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',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import { CssBaseline } from '@material-ui/core';
|
|||||||
import pickrOverride from './overrides/pickr.override';
|
import pickrOverride from './overrides/pickr.override';
|
||||||
import uplotOverride from './overrides/uplot.override';
|
import uplotOverride from './overrides/uplot.override';
|
||||||
import rcdockOverride from './overrides/rcdock.override';
|
import rcdockOverride from './overrides/rcdock.override';
|
||||||
|
import cmOverride from './overrides/codemirror.override';
|
||||||
|
|
||||||
/* Common settings across all themes */
|
/* Common settings across all themes */
|
||||||
let basicSettings = createTheme();
|
let basicSettings = createTheme();
|
||||||
@ -333,6 +334,7 @@ function getFinalTheme(baseTheme) {
|
|||||||
...pickrOverride(baseTheme),
|
...pickrOverride(baseTheme),
|
||||||
...uplotOverride(baseTheme),
|
...uplotOverride(baseTheme),
|
||||||
...rcdockOverride(baseTheme),
|
...rcdockOverride(baseTheme),
|
||||||
|
...cmOverride(baseTheme)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MuiOutlinedInput: {
|
MuiOutlinedInput: {
|
||||||
|
129
web/pgadmin/static/js/Theme/overrides/codemirror.override.js
Normal file
129
web/pgadmin/static/js/Theme/overrides/codemirror.override.js
Normal file
@ -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',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -130,7 +130,29 @@ export default function(basicSettings) {
|
|||||||
diffColorFg: '#222',
|
diffColorFg: '#222',
|
||||||
diffSelectFG: '#222',
|
diffSelectFG: '#222',
|
||||||
diffSelCheckbox: '#d6effc'
|
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',
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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'},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
@ -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);
|
|
||||||
});
|
|
@ -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 (
|
|
||||||
<Box className={classes.findDialog} visibility={show ? 'visible' : 'hidden'} tabIndex="0" onKeyDown={onEscape}>
|
|
||||||
<InputText value={findVal}
|
|
||||||
inputRef={(ele)=>{findInputRef.current = ele;}}
|
|
||||||
onChange={(value)=>setFindVal(value)}
|
|
||||||
onKeyPress={onFindEnter}
|
|
||||||
placeholder={gettext('Find text')}
|
|
||||||
controlProps={{
|
|
||||||
title: gettext('Find text')
|
|
||||||
}}
|
|
||||||
endAdornment={
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<PgIconButton data-test="case" title="Match case" icon={<FormatCaseIcon />} size="xs" noBorder
|
|
||||||
onClick={()=>toggle('case')} color={matchCase ? 'primary' : 'default'} style={{marginRight: '2px'}}/>
|
|
||||||
<PgIconButton data-test="regex" title="Use regex" icon={<RegexIcon />} size="xs" noBorder
|
|
||||||
onClick={()=>toggle('regex')} color={useRegex ? 'primary' : 'default'}/>
|
|
||||||
</InputAdornment>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{replace &&
|
|
||||||
<InputText value={replaceVal}
|
|
||||||
className={classes.marginTop}
|
|
||||||
onChange={(value)=>setReplaceVal(value)}
|
|
||||||
onKeyPress={onReplaceEnter}
|
|
||||||
placeholder={gettext('Replace value')}
|
|
||||||
controlProps={{
|
|
||||||
title: gettext('Replace value')
|
|
||||||
}}
|
|
||||||
/>}
|
|
||||||
|
|
||||||
<Box display="flex" className={classes.marginTop}>
|
|
||||||
<PgIconButton title={gettext('Previous')} icon={<ArrowUpwardRoundedIcon />} size="xs" noBorder onClick={onFindPrev}
|
|
||||||
style={{marginRight: '2px'}} />
|
|
||||||
<PgIconButton title={gettext('Next')} icon={<ArrowDownwardRoundedIcon />} size="xs" noBorder onClick={onFindNext}
|
|
||||||
style={{marginRight: '2px'}} />
|
|
||||||
{replace && <>
|
|
||||||
<PgIconButton title={gettext('Replace')} icon={<SwapHorizRoundedIcon style={{height: 'unset'}}/>} size="xs" noBorder onClick={onReplace}
|
|
||||||
style={{marginRight: '2px'}} />
|
|
||||||
<PgIconButton title={gettext('Replace All')} icon={<SwapCallsRoundedIcon />} size="xs" noBorder onClick={onReplaceAll}/>
|
|
||||||
</>}
|
|
||||||
<Box marginLeft="auto">
|
|
||||||
<PgIconButton title={gettext('Close')} icon={<CloseIcon />} size="xs" noBorder onClick={clearAndClose}/>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<Box className={classes.copyButton} visibility={show ? 'visible' : 'hidden'}>
|
|
||||||
<DefaultButton onClick={() => {
|
|
||||||
copyToClipboard(copyText);
|
|
||||||
setCopyBtnLabel(gettext('Copied!'));
|
|
||||||
revertCopiedText(1500);
|
|
||||||
}}>{copyBtnLabel}</DefaultButton>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<div className={clsx(className, classes.root)}
|
|
||||||
onMouseEnter={() => { showCopyBtn && value.length > 0 && setShowCopy(true);}}
|
|
||||||
onMouseLeave={() => {showCopyBtn && setShowCopy(false);}}
|
|
||||||
>
|
|
||||||
<FindDialog editor={editor.current} show={showFind} replace={isReplace} onClose={closeFind} selFindVal={editor.current?.getSelection() && editor.current.getSelection().length > 0 ? editor.current.getSelection() : ''}/>
|
|
||||||
<CopyButton editor={editor.current} show={showCopy} copyText={value}></CopyButton>
|
|
||||||
<textarea ref={taRef} name={name} title={gettext('SQL editor')}
|
|
||||||
id={cid} aria-describedby={helpid} value={value??''} onChange={()=>{/* dummy */}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
CodeMirror.propTypes = {
|
|
||||||
currEditor: PropTypes.func,
|
|
||||||
name: PropTypes.string,
|
|
||||||
value: PropTypes.string,
|
|
||||||
options: PropTypes.object,
|
|
||||||
change: PropTypes.func,
|
|
||||||
events: PropTypes.object,
|
|
||||||
readonly: PropTypes.bool,
|
|
||||||
disabled: PropTypes.bool,
|
|
||||||
className: CustomPropTypes.className,
|
|
||||||
autocomplete: PropTypes.bool,
|
|
||||||
gutters: PropTypes.array,
|
|
||||||
showCopyBtn: PropTypes.bool,
|
|
||||||
cid: PropTypes.string,
|
|
||||||
helpid: PropTypes.string,
|
|
||||||
};
|
|
@ -32,7 +32,7 @@ import { KeyboardDateTimePicker, KeyboardDatePicker, KeyboardTimePicker, MuiPick
|
|||||||
import DateFnsUtils from '@date-io/date-fns';
|
import DateFnsUtils from '@date-io/date-fns';
|
||||||
import * as DateFns from 'date-fns';
|
import * as DateFns from 'date-fns';
|
||||||
|
|
||||||
import CodeMirror from './CodeMirror';
|
import CodeMirror from './ReactCodeMirror';
|
||||||
import gettext from 'sources/gettext';
|
import gettext from 'sources/gettext';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { DefaultButton, PrimaryButton, PgIconButton } from './Buttons';
|
import { DefaultButton, PrimaryButton, PgIconButton } from './Buttons';
|
||||||
@ -127,7 +127,7 @@ FormIcon.propTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* Wrapper on any form component to add label, error indicator and help message */
|
/* Wrapper on any form component to add label, error indicator and help message */
|
||||||
export function FormInput({ children, error, className, label, helpMessage, required, testcid, withContainer=true, labelGridBasis=3, controlGridBasis=9 }) {
|
export function FormInput({ children, error, className, label, helpMessage, required, testcid, lid, withContainer=true, labelGridBasis=3, controlGridBasis=9 }) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const cid = testcid || _.uniqueId('c');
|
const cid = testcid || _.uniqueId('c');
|
||||||
const helpid = `h${cid}`;
|
const helpid = `h${cid}`;
|
||||||
@ -135,7 +135,7 @@ export function FormInput({ children, error, className, label, helpMessage, requ
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Grid item lg={labelGridBasis} md={labelGridBasis} sm={12} xs={12}>
|
<Grid item lg={labelGridBasis} md={labelGridBasis} sm={12} xs={12}>
|
||||||
<InputLabel htmlFor={cid} className={clsx(classes.formLabel, error ? classes.formLabelError : null)} required={required}>
|
<InputLabel id={lid} htmlFor={lid ? undefined : cid} className={clsx(classes.formLabel, error ? classes.formLabelError : null)} required={required}>
|
||||||
{label}
|
{label}
|
||||||
<FormIcon type={MESSAGE_TYPE.ERROR} style={{ marginLeft: 'auto', visibility: error ? 'unset' : 'hidden' }} />
|
<FormIcon type={MESSAGE_TYPE.ERROR} style={{ marginLeft: 'auto', visibility: error ? 'unset' : 'hidden' }} />
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
@ -152,7 +152,7 @@ export function FormInput({ children, error, className, label, helpMessage, requ
|
|||||||
return (
|
return (
|
||||||
<Grid container spacing={0} className={className} data-testid="form-input">
|
<Grid container spacing={0} className={className} data-testid="form-input">
|
||||||
<Grid item lg={labelGridBasis} md={labelGridBasis} sm={12} xs={12}>
|
<Grid item lg={labelGridBasis} md={labelGridBasis} sm={12} xs={12}>
|
||||||
<InputLabel htmlFor={cid} className={clsx(classes.formLabel, error ? classes.formLabelError : null)} required={required}>
|
<InputLabel id={lid} htmlFor={lid ? undefined : cid} className={clsx(classes.formLabel, error ? classes.formLabelError : null)} required={required}>
|
||||||
{label}
|
{label}
|
||||||
<FormIcon type={MESSAGE_TYPE.ERROR} style={{ marginLeft: 'auto', visibility: error ? 'unset' : 'hidden' }} />
|
<FormIcon type={MESSAGE_TYPE.ERROR} style={{ marginLeft: 'auto', visibility: error ? 'unset' : 'hidden' }} />
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
@ -174,6 +174,7 @@ FormInput.propTypes = {
|
|||||||
helpMessage: PropTypes.string,
|
helpMessage: PropTypes.string,
|
||||||
required: PropTypes.bool,
|
required: PropTypes.bool,
|
||||||
testcid: PropTypes.any,
|
testcid: PropTypes.any,
|
||||||
|
lid: PropTypes.any,
|
||||||
withContainer: PropTypes.bool,
|
withContainer: PropTypes.bool,
|
||||||
labelGridBasis: PropTypes.number,
|
labelGridBasis: PropTypes.number,
|
||||||
controlGridBasis: PropTypes.number,
|
controlGridBasis: PropTypes.number,
|
||||||
@ -191,16 +192,10 @@ export function InputSQL({ value, options, onChange, className, controlProps, in
|
|||||||
}}
|
}}
|
||||||
value={value || ''}
|
value={value || ''}
|
||||||
options={{
|
options={{
|
||||||
lineNumbers: true,
|
|
||||||
mode: 'text/x-pgsql',
|
|
||||||
...options,
|
...options,
|
||||||
}}
|
}}
|
||||||
className={clsx(classes.sql, className)}
|
className={clsx(classes.sql, className)}
|
||||||
events={{
|
onChange={onChange}
|
||||||
change: (cm) => {
|
|
||||||
onChange?.(cm.getValue());
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
{...controlProps}
|
{...controlProps}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@ -220,9 +215,10 @@ export function FormInputSQL({ hasError, required, label, className, helpMessage
|
|||||||
if (noLabel) {
|
if (noLabel) {
|
||||||
return <InputSQL value={value} options={controlProps} {...props} />;
|
return <InputSQL value={value} options={controlProps} {...props} />;
|
||||||
} else {
|
} else {
|
||||||
|
const lid = _.uniqueId('l');
|
||||||
return (
|
return (
|
||||||
<FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid} >
|
<FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid} lid={lid}>
|
||||||
<InputSQL value={value} options={controlProps} {...props} />
|
<InputSQL value={value} options={controlProps} labelledBy={lid} {...props} />
|
||||||
</FormInput>
|
</FormInput>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,150 @@
|
|||||||
|
import {
|
||||||
|
EditorView
|
||||||
|
} from '@codemirror/view';
|
||||||
|
import { StateEffect, EditorState } from '@codemirror/state';
|
||||||
|
import { autocompletion } from '@codemirror/autocomplete';
|
||||||
|
import {undo} from '@codemirror/commands';
|
||||||
|
import { errorMarkerEffect } from './extensions/errorMarker';
|
||||||
|
import { activeLineEffect, activeLineField } from './extensions/activeLineMarker';
|
||||||
|
import { clearBreakpoints, hasBreakpoint, toggleBreakpoint } from './extensions/breakpointGutter';
|
||||||
|
|
||||||
|
|
||||||
|
function getAutocompLoading({ bottom, left }, dom) {
|
||||||
|
const cmRect = dom.getBoundingClientRect();
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.classList.add('cm-tooltip', 'pg-autocomp-loader');
|
||||||
|
div.innerText = 'Loading...';
|
||||||
|
div.style.position = 'absolute';
|
||||||
|
div.style.top = (bottom - cmRect.top) + 'px';
|
||||||
|
div.style.left = (left - cmRect.left) + 'px';
|
||||||
|
dom?.appendChild(div);
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class CustomEditorView extends EditorView {
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
this._cleanDoc = this.state.doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue() {
|
||||||
|
return this.state.doc.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(newValue, markClean=false) {
|
||||||
|
newValue = newValue || '';
|
||||||
|
if(markClean) {
|
||||||
|
// create a new doc with new value to make it clean
|
||||||
|
this._cleanDoc = EditorState.create({
|
||||||
|
doc: newValue
|
||||||
|
}).doc;
|
||||||
|
}
|
||||||
|
this.dispatch({
|
||||||
|
changes: { from: 0, to: this.getValue().length, insert: newValue }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelection() {
|
||||||
|
return this.state.sliceDoc(this.state.selection.main.from, this.state.selection.main.to) ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceSelection(newValue) {
|
||||||
|
this.dispatch(this.state.replaceSelection(newValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
getCursor() {
|
||||||
|
let offset = this.state.selection.main.head;
|
||||||
|
let line = this.state.doc.lineAt(offset);
|
||||||
|
return {line: line.number, ch: offset - line.from};
|
||||||
|
}
|
||||||
|
|
||||||
|
setCursor(lineNo, ch) {
|
||||||
|
const n = this.state.doc.line(lineNo).from + ch;
|
||||||
|
this.dispatch({ selection: { anchor: n, head: n } });
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentLineNo() {
|
||||||
|
return this.state.doc.lineAt(this.state.selection.main.head).number;
|
||||||
|
}
|
||||||
|
|
||||||
|
lineCount() {
|
||||||
|
return this.state.doc.lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLine(lineNo) {
|
||||||
|
// line is 1-based;
|
||||||
|
return this.state.doc.line(lineNo).text;
|
||||||
|
}
|
||||||
|
|
||||||
|
getActiveLine() {
|
||||||
|
const activeLineChunk = this.state.field(activeLineField).chunkPos;
|
||||||
|
if(activeLineChunk.length > 0) {
|
||||||
|
return this.state.doc.lineAt(activeLineChunk[0]).number;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasBreakpoint(lineNo) {
|
||||||
|
const line = this.state.doc.line(lineNo);
|
||||||
|
return hasBreakpoint(this, line.from);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleBreakpoint(lineNo, silent, val) {
|
||||||
|
const line = this.state.doc.line(lineNo);
|
||||||
|
toggleBreakpoint(this, line.from, silent, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearBreakpoints() {
|
||||||
|
clearBreakpoints(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
markClean() {
|
||||||
|
this._cleanDoc = this.state.doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
isDirty() {
|
||||||
|
return !this._cleanDoc.eq(this.state.doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
undo() {
|
||||||
|
return undo(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
fireDOMEvent(event) {
|
||||||
|
this.contentDOM.dispatchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerAutocomplete(completionFunc) {
|
||||||
|
this.dispatch({
|
||||||
|
effects: StateEffect.appendConfig.of(
|
||||||
|
autocompletion({
|
||||||
|
override: [(context) => {
|
||||||
|
this.loadingDiv?.remove();
|
||||||
|
this.loadingDiv = getAutocompLoading(this.coordsAtPos(context.pos), this.dom);
|
||||||
|
context.addEventListener('abort', () => {
|
||||||
|
this.loadingDiv?.remove();
|
||||||
|
});
|
||||||
|
return Promise.resolve(completionFunc(context, () => {
|
||||||
|
this.loadingDiv?.remove();
|
||||||
|
}));
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setErrorMark(fromCursor, toCursor) {
|
||||||
|
const from = this.state.doc.line(fromCursor.line).from + fromCursor.pos;
|
||||||
|
const to = this.state.doc.line(toCursor.line).from + toCursor.pos;
|
||||||
|
this.dispatch({ effects: errorMarkerEffect.of({ from, to }) });
|
||||||
|
}
|
||||||
|
|
||||||
|
removeErrorMark() {
|
||||||
|
this.dispatch({ effects: errorMarkerEffect.of({ clear: true }) });
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveLine(line) {
|
||||||
|
this.dispatch({ effects: activeLineEffect.of({ from: line, to: line }) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
432
web/pgadmin/static/js/components/ReactCodeMirror/Editor.jsx
Normal file
432
web/pgadmin/static/js/components/ReactCodeMirror/Editor.jsx
Normal file
@ -0,0 +1,432 @@
|
|||||||
|
/////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// pgAdmin 4 - PostgreSQL Tools
|
||||||
|
//
|
||||||
|
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
|
||||||
|
// This software is released under the PostgreSQL Licence
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import ReactDOMServer from 'react-dom/server';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import gettext from 'sources/gettext';
|
||||||
|
import { makeStyles } from '@material-ui/core';
|
||||||
|
import { PgIconButton } from '../Buttons';
|
||||||
|
import { checkTrojanSource } from '../../utils';
|
||||||
|
import { copyToClipboard } from '../../clipboard';
|
||||||
|
import { useDelayedCaller } from '../../custom_hooks';
|
||||||
|
import usePreferences from '../../../../preferences/static/js/store';
|
||||||
|
import FileCopyRoundedIcon from '@material-ui/icons/FileCopyRounded';
|
||||||
|
import CheckRoundedIcon from '@material-ui/icons/CheckRounded';
|
||||||
|
import KeyboardArrowRightRoundedIcon from '@material-ui/icons/KeyboardArrowRightRounded';
|
||||||
|
import ExpandMoreRoundedIcon from '@material-ui/icons/ExpandMoreRounded';
|
||||||
|
|
||||||
|
// Codemirror packages
|
||||||
|
import {
|
||||||
|
lineNumbers,
|
||||||
|
highlightSpecialChars,
|
||||||
|
drawSelection,
|
||||||
|
dropCursor,
|
||||||
|
highlightActiveLine,
|
||||||
|
EditorView,
|
||||||
|
keymap,
|
||||||
|
} from '@codemirror/view';
|
||||||
|
import { EditorState, Compartment } from '@codemirror/state';
|
||||||
|
import { history, defaultKeymap, historyKeymap, indentLess, insertTab } from '@codemirror/commands';
|
||||||
|
import { highlightSelectionMatches } from '@codemirror/search';
|
||||||
|
import { closeBrackets, autocompletion, closeBracketsKeymap, completionKeymap } from '@codemirror/autocomplete';
|
||||||
|
import {
|
||||||
|
foldGutter,
|
||||||
|
indentOnInput,
|
||||||
|
bracketMatching,
|
||||||
|
indentUnit,
|
||||||
|
foldKeymap,
|
||||||
|
} from '@codemirror/language';
|
||||||
|
|
||||||
|
import FindDialog from './FindDialog';
|
||||||
|
import syntaxHighlighting from './extensions/highlighting';
|
||||||
|
import PgSQL from './extensions/dialect';
|
||||||
|
import { sql } from '@codemirror/lang-sql';
|
||||||
|
import errorMarkerExtn from './extensions/errorMarker';
|
||||||
|
import CustomEditorView from './CustomEditorView';
|
||||||
|
import breakpointGutter, { breakpointEffect } from './extensions/breakpointGutter';
|
||||||
|
import activeLineExtn from './extensions/activeLineMarker';
|
||||||
|
|
||||||
|
const arrowRightHtml = ReactDOMServer.renderToString(<KeyboardArrowRightRoundedIcon style={{fontSize: '1.2em'}} />);
|
||||||
|
const arrowDownHtml = ReactDOMServer.renderToString(<ExpandMoreRoundedIcon style={{fontSize: '1.2em'}} />);
|
||||||
|
|
||||||
|
const useStyles = makeStyles(() => ({
|
||||||
|
copyButton: {
|
||||||
|
position: 'absolute',
|
||||||
|
zIndex: 99,
|
||||||
|
right: '4px',
|
||||||
|
top: '4px',
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
export function CopyButton({ editor }) {
|
||||||
|
const classes = useStyles();
|
||||||
|
const [isCopied, setIsCopied] = useState(false);
|
||||||
|
const revertCopiedText = useDelayedCaller(() => {
|
||||||
|
setIsCopied(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PgIconButton size="small" className={classes.copyButton} icon={isCopied ? <CheckRoundedIcon /> : <FileCopyRoundedIcon />}
|
||||||
|
title={isCopied ? gettext('Copied!') : gettext('Copy')}
|
||||||
|
onClick={() => {
|
||||||
|
copyToClipboard(editor?.getValue());
|
||||||
|
setIsCopied(true);
|
||||||
|
revertCopiedText(1500);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
CopyButton.propTypes = {
|
||||||
|
editor: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleDrop(e, editor) {
|
||||||
|
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 */
|
||||||
|
// editor.inputState.handlers.drop(e, editor);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dropPos = editor.posAtCoords({ x: e.x, y: e.y });
|
||||||
|
editor.dispatch({
|
||||||
|
changes: { from: dropPos, to: dropPos, insert: dropDetails.text || '' },
|
||||||
|
selection: { anchor: dropPos + dropDetails.cur.from, head: dropPos + dropDetails.cur.to }
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePaste(e) {
|
||||||
|
let copiedText = e.clipboardData.getData('text');
|
||||||
|
checkTrojanSource(copiedText, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* React wrapper for CodeMirror */
|
||||||
|
const defaultExtensions = [
|
||||||
|
highlightSpecialChars(),
|
||||||
|
drawSelection(),
|
||||||
|
dropCursor(),
|
||||||
|
EditorState.allowMultipleSelections.of(true),
|
||||||
|
indentOnInput(),
|
||||||
|
syntaxHighlighting,
|
||||||
|
highlightSelectionMatches(),
|
||||||
|
keymap.of([defaultKeymap, closeBracketsKeymap, historyKeymap, foldKeymap, completionKeymap].flat()),
|
||||||
|
keymap.of([{
|
||||||
|
key: 'Tab',
|
||||||
|
preventDefault: true,
|
||||||
|
run: insertTab,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'Shift-Tab',
|
||||||
|
preventDefault: true,
|
||||||
|
run: indentLess,
|
||||||
|
}]),
|
||||||
|
sql({
|
||||||
|
dialect: PgSQL,
|
||||||
|
}),
|
||||||
|
PgSQL.language.data.of({
|
||||||
|
autocomplete: false,
|
||||||
|
}),
|
||||||
|
EditorView.domEventHandlers({
|
||||||
|
drop: handleDrop,
|
||||||
|
paste: handlePaste,
|
||||||
|
}),
|
||||||
|
errorMarkerExtn()
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Editor({
|
||||||
|
currEditor, name, value, options, onCursorActivity, onChange, readonly, disabled, autocomplete = false,
|
||||||
|
breakpoint = false, onBreakPointChange, showActiveLine=false, showCopyBtn = false,
|
||||||
|
keepHistory = true, cid, helpid, labelledBy}) {
|
||||||
|
const [[showFind, isReplace], setShowFind] = useState([false, false]);
|
||||||
|
const [showCopy, setShowCopy] = useState(false);
|
||||||
|
|
||||||
|
const editorContainerRef = useRef();
|
||||||
|
const editor = useRef();
|
||||||
|
const defaultOptions = {
|
||||||
|
lineNumbers: true,
|
||||||
|
foldGutter: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const preferencesStore = usePreferences();
|
||||||
|
const editable = !disabled;
|
||||||
|
const configurables = useRef(new Compartment());
|
||||||
|
const editableConfig = useRef(new Compartment());
|
||||||
|
|
||||||
|
const findDialogKeyMap = [{
|
||||||
|
key: 'Mod-f', run: (view, e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setShowFind([false, false]);
|
||||||
|
setShowFind([true, false]);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'Mod-Alt-f', run: (view, e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setShowFind([false, false]);
|
||||||
|
setShowFind([true, true]);
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const finalOptions = { ...defaultOptions, ...options };
|
||||||
|
const finalExtns = [
|
||||||
|
...defaultExtensions,
|
||||||
|
];
|
||||||
|
if (finalOptions.lineNumbers) {
|
||||||
|
finalExtns.push(lineNumbers());
|
||||||
|
}
|
||||||
|
if (finalOptions.foldGutter) {
|
||||||
|
finalExtns.push(foldGutter({
|
||||||
|
markerDOM: (open)=>{
|
||||||
|
let icon = document.createElement('span');
|
||||||
|
if(open) {
|
||||||
|
icon.innerHTML = arrowDownHtml;
|
||||||
|
} else {
|
||||||
|
icon.innerHTML = arrowRightHtml;
|
||||||
|
}
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (editorContainerRef.current) {
|
||||||
|
const state = EditorState.create({
|
||||||
|
extensions: [
|
||||||
|
...finalExtns,
|
||||||
|
configurables.current.of([]),
|
||||||
|
keymap.of(findDialogKeyMap),
|
||||||
|
editableConfig.current.of([
|
||||||
|
EditorView.editable.of(!disabled),
|
||||||
|
EditorState.readOnly.of(readonly),
|
||||||
|
].concat(keepHistory ? [history()] : [])),
|
||||||
|
[EditorView.updateListener.of(function(update) {
|
||||||
|
if(update.selectionSet) {
|
||||||
|
onCursorActivity?.(update.view.getCursor(), update.view);
|
||||||
|
}
|
||||||
|
if(update.docChanged) {
|
||||||
|
onChange?.(update.view.getValue(), update.view);
|
||||||
|
}
|
||||||
|
if(breakpoint) {
|
||||||
|
for(const transaction of update.transactions) {
|
||||||
|
for(const effect of transaction.effects) {
|
||||||
|
if(effect.is(breakpointEffect)) {
|
||||||
|
if(effect.value.silent) {
|
||||||
|
/* do nothing */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const lineNo = editor.current.state.doc.lineAt(effect.value.pos).number;
|
||||||
|
onBreakPointChange?.(lineNo, effect.value.on);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})],
|
||||||
|
EditorView.contentAttributes.of({
|
||||||
|
id: cid,
|
||||||
|
'aria-describedby': helpid,
|
||||||
|
'aria-labelledby': labelledBy,
|
||||||
|
}),
|
||||||
|
breakpoint ? breakpointGutter : [],
|
||||||
|
showActiveLine ? highlightActiveLine() : activeLineExtn(),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.current = new CustomEditorView({
|
||||||
|
state,
|
||||||
|
parent: editorContainerRef.current
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!_.isEmpty(value)) {
|
||||||
|
editor.current.setValue(value);
|
||||||
|
} else {
|
||||||
|
editor.current.setValue('');
|
||||||
|
}
|
||||||
|
|
||||||
|
currEditor?.(editor.current);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
editor.current?.destroy();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useMemo(() => {
|
||||||
|
if(editor.current) {
|
||||||
|
if(value != editor.current.getValue()) {
|
||||||
|
if(!_.isEmpty(value)) {
|
||||||
|
editor.current.setValue(value);
|
||||||
|
} else {
|
||||||
|
editor.current.setValue('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let pref = preferencesStore.getPreferencesForModule('sqleditor');
|
||||||
|
let newConfigExtn = [];
|
||||||
|
|
||||||
|
const fontSize = calcFontSize(pref.sql_font_size);
|
||||||
|
newConfigExtn.push(EditorView.theme({
|
||||||
|
'.cm-content': {
|
||||||
|
fontSize: fontSize,
|
||||||
|
},
|
||||||
|
'.cm-gutters': {
|
||||||
|
fontSize: fontSize,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const autoCompOptions = {
|
||||||
|
icons: false,
|
||||||
|
addToOptions: [{
|
||||||
|
render: (completion) => {
|
||||||
|
const element = document.createElement('div');
|
||||||
|
if (completion.type == 'keyword') {
|
||||||
|
element.className = 'cm-completionIcon cm-completionIcon-keyword';
|
||||||
|
} else if (completion.type == 'property') {
|
||||||
|
// CM adds columns as property, although we have changed this.
|
||||||
|
element.className = 'pg-cm-autocomplete-icon icon-column';
|
||||||
|
} else if (completion.type == 'type') {
|
||||||
|
// CM adds table as type
|
||||||
|
element.className = 'pg-cm-autocomplete-icon icon-table';
|
||||||
|
} else {
|
||||||
|
element.className = 'pg-cm-autocomplete-icon icon-' + completion.type;
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
},
|
||||||
|
position: 20,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
if (autocomplete) {
|
||||||
|
if (pref.autocomplete_on_key_press) {
|
||||||
|
newConfigExtn.push(autocompletion({
|
||||||
|
...autoCompOptions,
|
||||||
|
activateOnTyping: true,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
newConfigExtn.push(autocompletion({
|
||||||
|
...autoCompOptions,
|
||||||
|
activateOnTyping: false,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newConfigExtn.push(
|
||||||
|
EditorState.tabSize.of(pref.tab_size),
|
||||||
|
);
|
||||||
|
if (pref.use_spaces) {
|
||||||
|
newConfigExtn.push(
|
||||||
|
indentUnit.of(new Array(pref.tab_size).fill(' ').join('')),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
newConfigExtn.push(
|
||||||
|
indentUnit.of('\t'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pref.wrap_code) {
|
||||||
|
newConfigExtn.push(
|
||||||
|
EditorView.lineWrapping
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pref.insert_pair_brackets) {
|
||||||
|
newConfigExtn.push(closeBrackets());
|
||||||
|
}
|
||||||
|
if (pref.brace_matching) {
|
||||||
|
newConfigExtn.push(bracketMatching());
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.current.dispatch({
|
||||||
|
effects: configurables.current.reconfigure(newConfigExtn)
|
||||||
|
});
|
||||||
|
}, [preferencesStore]);
|
||||||
|
|
||||||
|
useMemo(() => {
|
||||||
|
if (editor.current) {
|
||||||
|
if (value != editor.current.getValue()) {
|
||||||
|
editor.current.dispatch({
|
||||||
|
changes: { from: 0, to: editor.current.state.doc.length, insert: value || '' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
editor.current?.dispatch({
|
||||||
|
effects: editableConfig.current.reconfigure([
|
||||||
|
EditorView.editable.of(editable),
|
||||||
|
EditorState.readOnly.of(readonly),
|
||||||
|
].concat(keepHistory ? [history()] : []))
|
||||||
|
});
|
||||||
|
}, [readonly, disabled, keepHistory]);
|
||||||
|
|
||||||
|
const closeFind = () => {
|
||||||
|
setShowFind([false, false]);
|
||||||
|
editor.current?.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseEnter = useCallback(()=>{showCopyBtn && setShowCopy(true);});
|
||||||
|
const onMouseLeave = useCallback(()=>{showCopyBtn && setShowCopy(false);});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} style={{height: '100%'}}>
|
||||||
|
<div style={{ height: '100%' }} ref={editorContainerRef} name={name}></div>
|
||||||
|
{showCopy && <CopyButton editor={editor.current} />}
|
||||||
|
{showFind &&
|
||||||
|
<FindDialog editor={editor.current} show={showFind} replace={isReplace} onClose={closeFind} />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Editor.propTypes = {
|
||||||
|
currEditor: PropTypes.func,
|
||||||
|
name: PropTypes.string,
|
||||||
|
value: PropTypes.string,
|
||||||
|
options: PropTypes.object,
|
||||||
|
onCursorActivity: PropTypes.func,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
readonly: PropTypes.bool,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
autocomplete: PropTypes.bool,
|
||||||
|
breakpoint: PropTypes.bool,
|
||||||
|
onBreakPointChange: PropTypes.func,
|
||||||
|
showActiveLine: PropTypes.bool,
|
||||||
|
showCopyBtn: PropTypes.bool,
|
||||||
|
keepHistory: PropTypes.bool,
|
||||||
|
cid: PropTypes.string,
|
||||||
|
helpid: PropTypes.string,
|
||||||
|
labelledBy: PropTypes.string,
|
||||||
|
};
|
203
web/pgadmin/static/js/components/ReactCodeMirror/FindDialog.jsx
Normal file
203
web/pgadmin/static/js/components/ReactCodeMirror/FindDialog.jsx
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
/////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// pgAdmin 4 - PostgreSQL Tools
|
||||||
|
//
|
||||||
|
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
|
||||||
|
// This software is released under the PostgreSQL Licence
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import gettext from 'sources/gettext';
|
||||||
|
import { Box, InputAdornment, makeStyles } from '@material-ui/core';
|
||||||
|
import { InputText } from '../FormComponents';
|
||||||
|
import { 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 { RegexIcon, FormatCaseIcon } from '../ExternalIcon';
|
||||||
|
|
||||||
|
import {
|
||||||
|
openSearchPanel,
|
||||||
|
closeSearchPanel,
|
||||||
|
setSearchQuery,
|
||||||
|
SearchQuery,
|
||||||
|
findNext,
|
||||||
|
findPrevious,
|
||||||
|
replaceNext,
|
||||||
|
replaceAll,
|
||||||
|
} from '@codemirror/search';
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme)=>({
|
||||||
|
root: {
|
||||||
|
position: 'absolute',
|
||||||
|
zIndex: 99,
|
||||||
|
right: '4px',
|
||||||
|
top: '0px',
|
||||||
|
...theme.mixins.panelBorder.all,
|
||||||
|
borderTop: 'none',
|
||||||
|
padding: '2px 4px',
|
||||||
|
width: '250px',
|
||||||
|
backgroundColor: theme.palette.background.default,
|
||||||
|
},
|
||||||
|
marginTop: {
|
||||||
|
marginTop: '0.25rem',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default function FindDialog({editor, show, replace, onClose}) {
|
||||||
|
const [findVal, setFindVal] = useState(editor?.getSelection());
|
||||||
|
const [replaceVal, setReplaceVal] = useState('');
|
||||||
|
const [useRegex, setUseRegex] = useState(false);
|
||||||
|
const [matchCase, setMatchCase] = useState(false);
|
||||||
|
const findInputRef = useRef();
|
||||||
|
const searchQuery = useRef();
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
const search = ()=>{
|
||||||
|
if(editor) {
|
||||||
|
let query = new SearchQuery({
|
||||||
|
search: findVal,
|
||||||
|
caseSensitive: matchCase,
|
||||||
|
regexp: useRegex,
|
||||||
|
wholeWord: false,
|
||||||
|
replace: replaceVal,
|
||||||
|
});
|
||||||
|
if ((searchQuery.current && !query.eq(searchQuery.current))
|
||||||
|
|| !searchQuery.current) {
|
||||||
|
searchQuery.current = query;
|
||||||
|
editor.dispatch({effects: setSearchQuery.of(query)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
if(show) {
|
||||||
|
openSearchPanel(editor);
|
||||||
|
// Get selected text from editor and set it to find/replace input.
|
||||||
|
let selText = editor.getSelection();
|
||||||
|
setFindVal(selText);
|
||||||
|
findInputRef.current && findInputRef.current.select();
|
||||||
|
}
|
||||||
|
}, [show]);
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
search();
|
||||||
|
}, [findVal, replaceVal, useRegex, matchCase]);
|
||||||
|
|
||||||
|
const clearAndClose = ()=>{
|
||||||
|
onClose();
|
||||||
|
closeSearchPanel(editor);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = ()=>{
|
||||||
|
findNext(editor);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFindPrev = ()=>{
|
||||||
|
findPrevious(editor);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onReplace = ()=>{
|
||||||
|
replaceNext(editor);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onReplaceAll = ()=>{
|
||||||
|
replaceAll(editor);
|
||||||
|
};
|
||||||
|
|
||||||
|
if(!editor) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box className={classes.root} visibility={show ? 'visible' : 'hidden'} tabIndex="0" onKeyDown={onEscape}>
|
||||||
|
<InputText value={findVal}
|
||||||
|
inputRef={(ele)=>{findInputRef.current = ele;}}
|
||||||
|
onChange={(value)=>setFindVal(value)}
|
||||||
|
onKeyPress={onFindEnter}
|
||||||
|
endAdornment={
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<PgIconButton data-test="case" title="Match case" icon={<FormatCaseIcon />} size="xs" noBorder
|
||||||
|
onClick={()=>toggle('case')} color={matchCase ? 'primary' : 'default'} style={{marginRight: '2px'}}/>
|
||||||
|
<PgIconButton data-test="regex" title="Use regex" icon={<RegexIcon />} size="xs" noBorder
|
||||||
|
onClick={()=>toggle('regex')} color={useRegex ? 'primary' : 'default'}/>
|
||||||
|
</InputAdornment>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{replace &&
|
||||||
|
<InputText value={replaceVal}
|
||||||
|
className={classes.marginTop}
|
||||||
|
onChange={(value)=>setReplaceVal(value)}
|
||||||
|
onKeyPress={onReplaceEnter}
|
||||||
|
/>}
|
||||||
|
|
||||||
|
<Box display="flex" className={classes.marginTop}>
|
||||||
|
<PgIconButton title={gettext('Previous')} icon={<ArrowUpwardRoundedIcon />} size="xs" noBorder onClick={onFindPrev}
|
||||||
|
style={{marginRight: '2px'}} />
|
||||||
|
<PgIconButton title={gettext('Next')} icon={<ArrowDownwardRoundedIcon />} size="xs" noBorder onClick={onFindNext}
|
||||||
|
style={{marginRight: '2px'}} />
|
||||||
|
{replace && <>
|
||||||
|
<PgIconButton title={gettext('Replace')} icon={<SwapHorizRoundedIcon style={{height: 'unset'}}/>} size="xs" noBorder onClick={onReplace}
|
||||||
|
style={{marginRight: '2px'}} />
|
||||||
|
<PgIconButton title={gettext('Replace All')} icon={<SwapCallsRoundedIcon />} size="xs" noBorder onClick={onReplaceAll}/>
|
||||||
|
</>}
|
||||||
|
<Box marginLeft="auto">
|
||||||
|
<PgIconButton title={gettext('Close')} icon={<CloseIcon />} size="xs" noBorder onClick={clearAndClose}/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const CodeMirrorInstanceType = PropTypes.shape({
|
||||||
|
getValue: PropTypes.func,
|
||||||
|
setValue: PropTypes.func,
|
||||||
|
getSelection: PropTypes.func,
|
||||||
|
dispatch: PropTypes.func,
|
||||||
|
});
|
||||||
|
|
||||||
|
FindDialog.propTypes = {
|
||||||
|
editor: CodeMirrorInstanceType,
|
||||||
|
show: PropTypes.bool,
|
||||||
|
replace: PropTypes.bool,
|
||||||
|
onClose: PropTypes.func,
|
||||||
|
selFindVal: PropTypes.string,
|
||||||
|
};
|
@ -0,0 +1,33 @@
|
|||||||
|
import {
|
||||||
|
EditorView,
|
||||||
|
Decoration,
|
||||||
|
} from '@codemirror/view';
|
||||||
|
import { StateEffect, StateField } from '@codemirror/state';
|
||||||
|
|
||||||
|
export const activeLineEffect = StateEffect.define({
|
||||||
|
map: ({ from, to }, change) => ({ from: change.mapPos(from), to: change.mapPos(to) })
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeLineDeco = Decoration.line({ class: 'cm-activeLine' });
|
||||||
|
|
||||||
|
export const activeLineField = StateField.define({
|
||||||
|
create() {
|
||||||
|
return Decoration.none;
|
||||||
|
},
|
||||||
|
update(value, tr) {
|
||||||
|
for (let e of tr.effects) if (e.is(activeLineEffect)) {
|
||||||
|
if(e.value.clear || e.value.from == -1) {
|
||||||
|
return Decoration.none;
|
||||||
|
}
|
||||||
|
const line = tr.state.doc.line(e.value.from);
|
||||||
|
return Decoration.set([activeLineDeco.range(line.from)]);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
provide: f => EditorView.decorations.from(f)
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export default function activeLineExtn() {
|
||||||
|
return [activeLineField];
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
import {GutterMarker, gutter} from '@codemirror/view';
|
||||||
|
import {StateField, StateEffect, RangeSet} from '@codemirror/state';
|
||||||
|
|
||||||
|
export const breakpointEffect = StateEffect.define({
|
||||||
|
map: (val, mapping) => {
|
||||||
|
return {pos: mapping.mapPos(val.pos), on: val.on, clear: val.clear, silent: val.silent};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const breakpointField = StateField.define({
|
||||||
|
create() { return RangeSet.empty; },
|
||||||
|
update(set, transaction) {
|
||||||
|
set = set.map(transaction.changes);
|
||||||
|
for (let e of transaction.effects) {
|
||||||
|
if (e.is(breakpointEffect)) {
|
||||||
|
if(e.value.clear) {
|
||||||
|
return RangeSet.empty;
|
||||||
|
}
|
||||||
|
if (e.value.on)
|
||||||
|
set = set.update({add: [breakpointMarker.range(e.value.pos)]});
|
||||||
|
else
|
||||||
|
set = set.update({filter: from => from != e.value.pos});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export function hasBreakpoint(view, pos) {
|
||||||
|
let breakpoints = view.state.field(breakpointField);
|
||||||
|
let has = false;
|
||||||
|
breakpoints.between(pos, pos, () => {has = true;});
|
||||||
|
return has;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toggleBreakpoint(view, pos, silent, val) {
|
||||||
|
view.dispatch({
|
||||||
|
effects: breakpointEffect.of({pos, on: typeof(val) == 'undefined' ? !hasBreakpoint(view, pos) : val, silent})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearBreakpoints(view) {
|
||||||
|
view.dispatch({
|
||||||
|
effects: breakpointEffect.of({clear: true, silent: true})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const breakpointMarker = new class extends GutterMarker {
|
||||||
|
toDOM() { return document.createTextNode('●'); }
|
||||||
|
};
|
||||||
|
|
||||||
|
const breakpointGutter = [
|
||||||
|
breakpointField,
|
||||||
|
gutter({
|
||||||
|
class: 'cm-breakpoint-gutter',
|
||||||
|
markers: v => v.state.field(breakpointField),
|
||||||
|
initialSpacer: () => breakpointMarker,
|
||||||
|
domEventHandlers: {
|
||||||
|
mousedown(view, line) {
|
||||||
|
toggleBreakpoint(view, line.from);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
export default breakpointGutter;
|
@ -0,0 +1,14 @@
|
|||||||
|
import { SQLDialect, PostgreSQL } from '@codemirror/lang-sql';
|
||||||
|
|
||||||
|
const extraKeywords = 'unsafe';
|
||||||
|
const keywords = PostgreSQL.spec.keywords.replace(/\b\w\b/, '') + ' ' + extraKeywords;
|
||||||
|
|
||||||
|
const PgSQL = SQLDialect.define({
|
||||||
|
charSetCasts: true,
|
||||||
|
doubleDollarQuotedStrings: true,
|
||||||
|
operatorChars: '+-*/<>=~!@#%^&|`?',
|
||||||
|
specialVar: '',
|
||||||
|
keywords: keywords,
|
||||||
|
types: PostgreSQL.spec.types,
|
||||||
|
});
|
||||||
|
export default PgSQL;
|
@ -0,0 +1,35 @@
|
|||||||
|
import {
|
||||||
|
EditorView,
|
||||||
|
Decoration,
|
||||||
|
} from '@codemirror/view';
|
||||||
|
import { StateEffect, StateField } from '@codemirror/state';
|
||||||
|
|
||||||
|
export const errorMarkerEffect = StateEffect.define({
|
||||||
|
map: ({ from, to }, change) => ({ from: change.mapPos(from), to: change.mapPos(to) })
|
||||||
|
});
|
||||||
|
|
||||||
|
const errorMakerDeco = Decoration.mark({ class: 'cm-error-highlight' });
|
||||||
|
|
||||||
|
export const errorMakerField = StateField.define({
|
||||||
|
create() {
|
||||||
|
return Decoration.none;
|
||||||
|
},
|
||||||
|
update(underlines, tr) {
|
||||||
|
underlines = underlines.map(tr.changes);
|
||||||
|
for (let e of tr.effects) if (e.is(errorMarkerEffect)) {
|
||||||
|
if (e.value.clear) {
|
||||||
|
return Decoration.none;
|
||||||
|
}
|
||||||
|
underlines = underlines.update({
|
||||||
|
add: [errorMakerDeco.range(e.value.from, e.value.to)]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return underlines;
|
||||||
|
},
|
||||||
|
provide: f => EditorView.decorations.from(f)
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export default function errorMarkerExtn() {
|
||||||
|
return [errorMakerField];
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
import {
|
||||||
|
syntaxHighlighting,
|
||||||
|
} from '@codemirror/language';
|
||||||
|
|
||||||
|
import {tagHighlighter, tags, classHighlighter} from '@lezer/highlight';
|
||||||
|
|
||||||
|
export const extendedClassHighlighter = tagHighlighter([
|
||||||
|
{tag: tags.link, class: 'tok-link'},
|
||||||
|
{tag: tags.heading, class: 'tok-heading'},
|
||||||
|
{tag: tags.emphasis, class: 'tok-emphasis'},
|
||||||
|
{tag: tags.strong, class: 'tok-strong'},
|
||||||
|
{tag: tags.keyword, class: 'tok-keyword'},
|
||||||
|
{tag: tags.atom, class: 'tok-atom'},
|
||||||
|
{tag: tags.bool, class: 'tok-bool'},
|
||||||
|
{tag: tags.url, class: 'tok-url'},
|
||||||
|
{tag: tags.labelName, class: 'tok-labelName'},
|
||||||
|
{tag: tags.inserted, class: 'tok-inserted'},
|
||||||
|
{tag: tags.deleted, class: 'tok-deleted'},
|
||||||
|
{tag: tags.literal, class: 'tok-literal'},
|
||||||
|
{tag: tags.string, class: 'tok-string'},
|
||||||
|
{tag: tags.number, class: 'tok-number'},
|
||||||
|
{tag: [tags.regexp, tags.escape, tags.special(tags.string)], class: 'tok-string2'},
|
||||||
|
{tag: tags.variableName, class: 'tok-variableName'},
|
||||||
|
{tag: tags.local(tags.variableName), class: 'tok-variableName tok-local'},
|
||||||
|
{tag: tags.definition(tags.variableName), class: 'tok-variableName tok-definition'},
|
||||||
|
{tag: tags.special(tags.variableName), class: 'tok-variableName2'},
|
||||||
|
{tag: tags.definition(tags.propertyName), class: 'tok-propertyName tok-definition'},
|
||||||
|
{tag: tags.typeName, class: 'tok-typeName'},
|
||||||
|
{tag: tags.namespace, class: 'tok-namespace'},
|
||||||
|
{tag: tags.className, class: 'tok-className'},
|
||||||
|
{tag: tags.macroName, class: 'tok-macroName'},
|
||||||
|
{tag: tags.propertyName, class: 'tok-propertyName'},
|
||||||
|
{tag: tags.operator, class: 'tok-operator'},
|
||||||
|
{tag: tags.comment, class: 'tok-comment'},
|
||||||
|
{tag: tags.meta, class: 'tok-meta'},
|
||||||
|
{tag: tags.invalid, class: 'tok-invalid'},
|
||||||
|
{tag: tags.punctuation, class: 'tok-punctuation'},
|
||||||
|
{tag: [tags.name, tags.deleted, tags.character, tags.propertyName, tags.macroName], class: 'tok-name'},
|
||||||
|
]);
|
||||||
|
|
||||||
|
export default syntaxHighlighting(classHighlighter);
|
33
web/pgadmin/static/js/components/ReactCodeMirror/index.jsx
Normal file
33
web/pgadmin/static/js/components/ReactCodeMirror/index.jsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// pgAdmin 4 - PostgreSQL Tools
|
||||||
|
//
|
||||||
|
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
|
||||||
|
// This software is released under the PostgreSQL Licence
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { makeStyles } from '@material-ui/core';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import Editor from './Editor';
|
||||||
|
import CustomPropTypes from '../../custom_prop_types';
|
||||||
|
|
||||||
|
const useStyles = makeStyles(() => ({
|
||||||
|
root: {
|
||||||
|
position: 'relative',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default function CodeMirror({className, ...props}) {
|
||||||
|
const classes = useStyles();
|
||||||
|
return (
|
||||||
|
<div className={clsx(className, classes.root)}>
|
||||||
|
<Editor {...props} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.propTypes = {
|
||||||
|
className: CustomPropTypes.className,
|
||||||
|
};
|
@ -1,208 +0,0 @@
|
|||||||
/* To override inbuilt Green color for matchingbracket */
|
|
||||||
.cm-s-default .CodeMirror-matchingbracket {
|
|
||||||
color: $sql-bracket-match-fg !important;
|
|
||||||
background-color: $sql-bracket-match-bg !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror {
|
|
||||||
font-size: 1em;
|
|
||||||
font-family: monospace, monospace;
|
|
||||||
background-color: $color-editor-bg !important;
|
|
||||||
color: $color-editor-fg;
|
|
||||||
border-radius: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure the codemirror editor displays full height gutters when resized */
|
|
||||||
.CodeMirror, .CodeMirror-gutter {
|
|
||||||
height: 100% !important;
|
|
||||||
min-height: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* class to disable Codemirror editor */
|
|
||||||
.cm_disabled {
|
|
||||||
background: $input-disabled-bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* make syntax-highlighting bold */
|
|
||||||
.cm-s-default {
|
|
||||||
& .cm-quote {color: #090;}
|
|
||||||
& .cm-keyword {color: $color-editor-keyword; font-weight: 600;}
|
|
||||||
& .cm-atom {color: $color-editor-fg;}
|
|
||||||
& .cm-number {color: $color-editor-number; font-weight: 600;}
|
|
||||||
& .cm-def {color: $color-editor-fg;}
|
|
||||||
& .cm-punctuation,
|
|
||||||
& .cm-property,
|
|
||||||
& .cm-operator { color: $color-editor-operator; }
|
|
||||||
& .cm-variable {color: $color-editor-variable; }
|
|
||||||
& .cm-variable-2,
|
|
||||||
& .cm-variable-3,
|
|
||||||
& .cm-type {color: $color-editor-variable-2;}
|
|
||||||
& .cm-comment {color: $color-editor-comment;}
|
|
||||||
& .cm-string {color: $color-editor-string;}
|
|
||||||
& .cm-string-2 {color: $color-editor-string;}
|
|
||||||
& .cm-meta {color: $color-editor-fg;}
|
|
||||||
& .cm-qualifier {color: $color-editor-fg;}
|
|
||||||
& .cm-builtin {color: $color-editor-builtin;}
|
|
||||||
& .cm-bracket {color: $color-editor-bracket;}
|
|
||||||
& .cm-tag {color: $color-editor-fg;}
|
|
||||||
& .cm-attribute {color: $color-editor-fg;}
|
|
||||||
& .cm-hr {color: $color-editor-fg;}
|
|
||||||
& .cm-link {color: $color-editor-fg;}
|
|
||||||
|
|
||||||
& :not(.cm-fat-cursor) .CodeMirror-cursor {
|
|
||||||
border-left: thin solid $color-editor-fg;
|
|
||||||
border-right: none;
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Codemirror buttons */
|
|
||||||
.CodeMirror-dialog button {
|
|
||||||
font-family: $font-family-primary;
|
|
||||||
color: $color-primary-fg;
|
|
||||||
font-size: 70%;
|
|
||||||
background-image: -webkit-linear-gradient(top, $color-primary-light 0%, $color-primary 100%);
|
|
||||||
background-image: -o-linear-gradient(top, $color-primary-light 0%, $color-primary 100%);
|
|
||||||
background-image: -webkit-gradient(linear, left top, left bottom, from($color-primary-light), to($color-primary));
|
|
||||||
background-image: linear-gradient(to bottom, $color-primary-light 0%, $color-primary 100%);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{$color-primary-light}', endColorstr='#{$color-primary}', GradientType=0);
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
border-color: $color-primary;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-gutters {
|
|
||||||
z-index: 2;
|
|
||||||
background-color: $sql-gutters-bg;
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* workaround for codemirrors 'readOnly' option which is set to true instead of 'noCursor' */
|
|
||||||
.hide-cursor-workaround .CodeMirror-cursors {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-linenumber {
|
|
||||||
color: $color-fg;
|
|
||||||
font-size: 0.85em;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.debugger-container .breakpoints {
|
|
||||||
width: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror, .CodeMirror-gutters {
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-foldgutter {
|
|
||||||
width: .9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.breakpoints {
|
|
||||||
width: .9em;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-foldgutter-open,
|
|
||||||
.CodeMirror-foldgutter-folded {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-foldgutter-open:after {
|
|
||||||
content: "\25BC";
|
|
||||||
font-weight: 900;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-foldgutter-folded:after {
|
|
||||||
content: "\25B6";
|
|
||||||
font-weight: 900;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.CodeMirror-foldmarker {
|
|
||||||
color: $color-editor-foldmarker;
|
|
||||||
text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px;
|
|
||||||
font-family: $font-family-primary;
|
|
||||||
line-height: .3;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-hints {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 10;
|
|
||||||
overflow: hidden;
|
|
||||||
list-style: none;
|
|
||||||
|
|
||||||
margin: 0;
|
|
||||||
padding: 0px;
|
|
||||||
|
|
||||||
-webkit-box-shadow: $dropdown-box-shadow;
|
|
||||||
-moz-box-shadow: $dropdown-box-shadow;
|
|
||||||
box-shadow: $dropdown-box-shadow;
|
|
||||||
border-radius: $border-radius;
|
|
||||||
border: $panel-border;
|
|
||||||
|
|
||||||
background: $sql-hint-bg;
|
|
||||||
font-size: 90%;
|
|
||||||
font-family: $font-family-editor !important;
|
|
||||||
|
|
||||||
max-height: 20em;
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
& li {
|
|
||||||
padding: 0.125rem;
|
|
||||||
border-radius: 0rem;
|
|
||||||
&.CodeMirror-hint-active {
|
|
||||||
background: $sql-hint-active-bg;
|
|
||||||
color: $sql-hint-active-fg;
|
|
||||||
}
|
|
||||||
& .sqleditor-hint {
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror .CodeMirror-selected {
|
|
||||||
background: $sql-editor-selection-bg !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-activeline-background {
|
|
||||||
background: $color-editor-activeline !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-simplescroll-horizontal {
|
|
||||||
height: $scrollbar-width;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-simplescroll-vertical {
|
|
||||||
width: $scrollbar-width;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-scrollbar-filler, .CodeMirror-simplescroll-horizontal, .CodeMirror-simplescroll-vertical {
|
|
||||||
background-color: lighten($scrollbar-base-color, 15%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-simplescroll-horizontal, .CodeMirror-simplescroll-vertical {
|
|
||||||
|
|
||||||
& div {
|
|
||||||
border: 0.25rem solid transparent;
|
|
||||||
border-radius: $border-radius*2;
|
|
||||||
background-clip: content-box;
|
|
||||||
background-color: rgba($scrollbar-base-color, 0.7);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: $scrollbar-base-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-gray-lighter {
|
|
||||||
background-color: $sql-editor-disable-bg !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sql-editor-mark {
|
|
||||||
border-bottom: 2px dotted red;
|
|
||||||
}
|
|
@ -838,10 +838,6 @@ table.table-empty-rows{
|
|||||||
|
|
||||||
.filter-textarea {
|
.filter-textarea {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
& .CodeMirror-scroll {
|
|
||||||
min-height: 120px;
|
|
||||||
max-height: 120px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dataview_filter_dialog {
|
.dataview_filter_dialog {
|
||||||
|
@ -17,7 +17,6 @@ $theme-colors: (
|
|||||||
--psql-selection: #{$psql-selection};
|
--psql-selection: #{$psql-selection};
|
||||||
}
|
}
|
||||||
|
|
||||||
@import 'codemirror.overrides';
|
|
||||||
@import 'pgadmin.style';
|
@import 'pgadmin.style';
|
||||||
@import 'jsoneditor.overrides';
|
@import 'jsoneditor.overrides';
|
||||||
@import 'rc-dock/dist/rc-dock.css';
|
@import 'rc-dock/dist/rc-dock.css';
|
||||||
|
@ -114,13 +114,13 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
|||||||
url: baseUrl,
|
url: baseUrl,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: {
|
data: {
|
||||||
'breakpoint_list': breakpoint_list.lenght > 0 ? breakpoint_list.join() : null,
|
'breakpoint_list': breakpoint_list.length > 0 ? breakpoint_list.join() : null,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(function (res) {
|
.then(function (res) {
|
||||||
if (res.data.data.status) {
|
if (res.data.data.status) {
|
||||||
executeQuery(transId);
|
executeQuery(transId);
|
||||||
setUnsetBreakpoint(res, breakpoint_list);
|
editor.current.clearBreakpoints();
|
||||||
}
|
}
|
||||||
enableToolbarButtons();
|
enableToolbarButtons();
|
||||||
})
|
})
|
||||||
@ -158,7 +158,8 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const raisePollingError = () => {
|
const raisePollingError = (error) => {
|
||||||
|
console.error(error);
|
||||||
pgAdmin.Browser.notifier.alert(
|
pgAdmin.Browser.notifier.alert(
|
||||||
gettext('Debugger Error'),
|
gettext('Debugger Error'),
|
||||||
gettext('Error while polling result.')
|
gettext('Error while polling result.')
|
||||||
@ -263,7 +264,7 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
|||||||
) {
|
) {
|
||||||
editor.current.setValue(res.data.data.result[0].src);
|
editor.current.setValue(res.data.data.result[0].src);
|
||||||
|
|
||||||
setActiveLine(res.data.data.result[0].linenumber - 2);
|
editor.current.setActiveLine(res.data.data.result[0].linenumber - 1);
|
||||||
}
|
}
|
||||||
// Call function to create and update Stack information ....
|
// Call function to create and update Stack information ....
|
||||||
getStackInformation(transId);
|
getStackInformation(transId);
|
||||||
@ -277,7 +278,8 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(function () {
|
.catch(function (error) {
|
||||||
|
console.error(error);
|
||||||
pgAdmin.Browser.notifier.alert(
|
pgAdmin.Browser.notifier.alert(
|
||||||
gettext('Debugger Error'),
|
gettext('Debugger Error'),
|
||||||
gettext('Error while executing requested debugging information.')
|
gettext('Error while executing requested debugging information.')
|
||||||
@ -285,31 +287,6 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const setActiveLine = (lineNo) => {
|
|
||||||
/* If lineNo sent, remove active line */
|
|
||||||
if (lineNo && editor.current.activeLineNo) {
|
|
||||||
editor.current.removeLineClass(
|
|
||||||
editor.current.activeLineNo, 'wrap', 'CodeMirror-activeline-background'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If lineNo not sent, set it to active line */
|
|
||||||
if (!lineNo && editor.current.activeLineNo) {
|
|
||||||
lineNo = editor.current.activeLineNo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set new active line only if positive */
|
|
||||||
if (lineNo > 0) {
|
|
||||||
editor.current.activeLineNo = lineNo;
|
|
||||||
editor.current.addLineClass(
|
|
||||||
editor.current.activeLineNo, 'wrap', 'CodeMirror-activeline-background'
|
|
||||||
);
|
|
||||||
|
|
||||||
/* centerOnLine is codemirror extension in bundle/codemirror.js */
|
|
||||||
editor.current.centerOnLine(editor.current.activeLineNo);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectFrame = (frameId) => {
|
const selectFrame = (frameId) => {
|
||||||
// Make ajax call to listen the database message
|
// Make ajax call to listen the database message
|
||||||
let baseUrl = url_for('debugger.select_frame', {
|
let baseUrl = url_for('debugger.select_frame', {
|
||||||
@ -324,7 +301,7 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
|||||||
if (res.data.data.status) {
|
if (res.data.data.status) {
|
||||||
editor.current.setValue(res.data.data.result[0].src);
|
editor.current.setValue(res.data.data.result[0].src);
|
||||||
updateBreakpoint(params.transId, true);
|
updateBreakpoint(params.transId, true);
|
||||||
setActiveLine(res.data.data.result[0].linenumber - 2);
|
editor.current.setActiveLine(res.data.data.result[0].linenumber - 1);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(function () {
|
.catch(function () {
|
||||||
@ -386,20 +363,6 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const setUnsetBreakpoint = (res, breakpoint_list) => {
|
|
||||||
if (res.data.data.status) {
|
|
||||||
for (let brk_val of breakpoint_list) {
|
|
||||||
let info = editor.current.lineInfo((brk_val - 1));
|
|
||||||
|
|
||||||
if (info) {
|
|
||||||
if (info.gutterMarkers != undefined) {
|
|
||||||
editor.current.setGutterMarker((brk_val - 1), 'breakpoints', null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const triggerClearBreakpoint = () => {
|
const triggerClearBreakpoint = () => {
|
||||||
let clearBreakpoint = (br_list) => {
|
let clearBreakpoint = (br_list) => {
|
||||||
// If there is no break point to clear then we should return from here.
|
// If there is no break point to clear then we should return from here.
|
||||||
@ -421,8 +384,8 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
|||||||
'breakpoint_list': breakpoint_list.join(),
|
'breakpoint_list': breakpoint_list.join(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(function (res) {
|
.then(function () {
|
||||||
setUnsetBreakpoint(res, breakpoint_list);
|
editor.current.clearBreakpoints();
|
||||||
enableToolbarButtons();
|
enableToolbarButtons();
|
||||||
})
|
})
|
||||||
.catch(raiseClearBrekpointError);
|
.catch(raiseClearBrekpointError);
|
||||||
@ -448,63 +411,11 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const debuggerMark = () => {
|
|
||||||
let marker = document.createElement('div');
|
|
||||||
marker.style.color = '#822';
|
|
||||||
marker.innerHTML = '●';
|
|
||||||
return marker;
|
|
||||||
};
|
|
||||||
|
|
||||||
const triggerToggleBreakpoint = () => {
|
const triggerToggleBreakpoint = () => {
|
||||||
disableToolbarButtons();
|
disableToolbarButtons();
|
||||||
let info = editor.current.lineInfo(editor.current.activeLineNo);
|
const lineNo = editor.current.getActiveLine();
|
||||||
let baseUrl = '';
|
editor.current.toggleBreakpoint(lineNo);
|
||||||
|
enableToolbarButtons();
|
||||||
// If gutterMarker is undefined that means there is no marker defined previously
|
|
||||||
// So we need to set the breakpoint command here...
|
|
||||||
if (info.gutterMarkers == undefined) {
|
|
||||||
baseUrl = url_for('debugger.set_breakpoint', {
|
|
||||||
'trans_id': params.transId,
|
|
||||||
'line_no': editor.current.activeLineNo + 1,
|
|
||||||
'set_type': '1',
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
baseUrl = url_for('debugger.set_breakpoint', {
|
|
||||||
'trans_id': params.transId,
|
|
||||||
'line_no': editor.current.activeLineNo + 1,
|
|
||||||
'set_type': '0',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
api({
|
|
||||||
url: baseUrl,
|
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
.then(function (res) {
|
|
||||||
if (res.data.data.status) {
|
|
||||||
// Call function to create and update local variables ....
|
|
||||||
let info_local = editor.current.lineInfo(editor.current.activeLineNo);
|
|
||||||
|
|
||||||
if (info_local.gutterMarkers != undefined) {
|
|
||||||
editor.current.setGutterMarker(editor.current.activeLineNo, 'breakpoints', null);
|
|
||||||
} else {
|
|
||||||
editor.current.setGutterMarker(editor.current.activeLineNo, 'breakpoints', debuggerMark());
|
|
||||||
}
|
|
||||||
|
|
||||||
enableToolbarButtons();
|
|
||||||
} else if (res.data.status === 'NotConnected') {
|
|
||||||
pgAdmin.Browser.notifier.alert(
|
|
||||||
gettext('Debugger Error'),
|
|
||||||
gettext('Error while toggling breakpoint.')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function () {
|
|
||||||
pgAdmin.Browser.notifier.alert(
|
|
||||||
gettext('Debugger Error'),
|
|
||||||
gettext('Error while toggling breakpoint.')
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const stopDebugging = () => {
|
const stopDebugging = () => {
|
||||||
@ -522,7 +433,7 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
|||||||
.then(function (res) {
|
.then(function (res) {
|
||||||
if (res.data.data.status) {
|
if (res.data.data.status) {
|
||||||
// Remove active time in the editor
|
// Remove active time in the editor
|
||||||
setActiveLine(-1);
|
editor.current.setActiveLine(-1);
|
||||||
// Clear timeout on stop debugger.
|
// Clear timeout on stop debugger.
|
||||||
clearTimeout(timeOut);
|
clearTimeout(timeOut);
|
||||||
|
|
||||||
@ -628,7 +539,7 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
|||||||
|
|
||||||
const pollEndExecuteError = (res) => {
|
const pollEndExecuteError = (res) => {
|
||||||
params.directDebugger.direct_execution_completed = true;
|
params.directDebugger.direct_execution_completed = true;
|
||||||
setActiveLine(-1);
|
editor.current.setActiveLine(-1);
|
||||||
|
|
||||||
//Set the notification message to inform the user that execution is
|
//Set the notification message to inform the user that execution is
|
||||||
// completed with error.
|
// completed with error.
|
||||||
@ -654,7 +565,7 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
|||||||
|
|
||||||
const updateResultAndMessages = (res) => {
|
const updateResultAndMessages = (res) => {
|
||||||
if (res.data.data.result != null) {
|
if (res.data.data.result != null) {
|
||||||
setActiveLine(-1);
|
editor.current.setActiveLine(-1);
|
||||||
// Call function to update results information and set result panel focus
|
// Call function to update results information and set result panel focus
|
||||||
eventBus.current.fireEvent(DEBUGGER_EVENTS.SET_RESULTS, res.data.data.col_info, res.data.data.result);
|
eventBus.current.fireEvent(DEBUGGER_EVENTS.SET_RESULTS, res.data.data.col_info, res.data.data.result);
|
||||||
eventBus.current.fireEvent(DEBUGGER_EVENTS.FOCUS_PANEL, PANELS.RESULTS);
|
eventBus.current.fireEvent(DEBUGGER_EVENTS.FOCUS_PANEL, PANELS.RESULTS);
|
||||||
@ -725,7 +636,7 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
|||||||
As Once the EDB procedure execution is completed then we are
|
As Once the EDB procedure execution is completed then we are
|
||||||
not getting any result so we need to ignore the result.
|
not getting any result so we need to ignore the result.
|
||||||
*/
|
*/
|
||||||
setActiveLine(-1);
|
editor.current.setActiveLine(-1);
|
||||||
params.directDebugger.direct_execution_completed = true;
|
params.directDebugger.direct_execution_completed = true;
|
||||||
params.directDebugger.polling_timeout_idle = true;
|
params.directDebugger.polling_timeout_idle = true;
|
||||||
|
|
||||||
@ -852,26 +763,9 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
|||||||
return breakpoint_list;
|
return breakpoint_list;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to get the latest breakpoint information and update the
|
// Function to get the latest breakpoint information
|
||||||
// gutters of codemirror
|
|
||||||
const updateBreakpoint = (transId, updateLocalVar = false) => {
|
const updateBreakpoint = (transId, updateLocalVar = false) => {
|
||||||
let callBackFunc = (br_list) => {
|
let callBackFunc = () => {
|
||||||
// If there is no break point to clear then we should return from here.
|
|
||||||
if ((br_list.length == 1) && (br_list[0].linenumber == -1))
|
|
||||||
return;
|
|
||||||
|
|
||||||
let breakpoint_list = getBreakpointList(br_list);
|
|
||||||
|
|
||||||
|
|
||||||
for (let brk_val of breakpoint_list) {
|
|
||||||
let info = editor.current.lineInfo((brk_val - 1));
|
|
||||||
|
|
||||||
if (info.gutterMarkers != undefined) {
|
|
||||||
editor.current.setGutterMarker((brk_val - 1), 'breakpoints', null);
|
|
||||||
} else {
|
|
||||||
editor.current.setGutterMarker((brk_val - 1), 'breakpoints', debuggerMark());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (updateLocalVar) {
|
if (updateLocalVar) {
|
||||||
// Call function to create and update local variables ....
|
// Call function to create and update local variables ....
|
||||||
getLocalVariables(params.transId);
|
getLocalVariables(params.transId);
|
||||||
@ -976,7 +870,7 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
|||||||
const updateInfo = (res, transId) => {
|
const updateInfo = (res, transId) => {
|
||||||
if (!params.directDebugger.debug_type && !params.directDebugger.first_time_indirect_debug) {
|
if (!params.directDebugger.debug_type && !params.directDebugger.first_time_indirect_debug) {
|
||||||
setLoaderText('');
|
setLoaderText('');
|
||||||
setActiveLine(-1);
|
editor.current.setActiveLine(-1);
|
||||||
clearAllBreakpoint(transId);
|
clearAllBreakpoint(transId);
|
||||||
|
|
||||||
params.directDebugger.first_time_indirect_debug = true;
|
params.directDebugger.first_time_indirect_debug = true;
|
||||||
@ -987,7 +881,7 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
|||||||
// If the source is really changed then only update the breakpoint information
|
// If the source is really changed then only update the breakpoint information
|
||||||
updateBreakpointInfo(res, transId);
|
updateBreakpointInfo(res, transId);
|
||||||
|
|
||||||
setActiveLine(res.data.data.result[0].linenumber - 2);
|
editor.current.setActiveLine(res.data.data.result[0].linenumber - 1);
|
||||||
// Update the stack, local variables and parameters information
|
// Update the stack, local variables and parameters information
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
getStackInformation(transId);
|
getStackInformation(transId);
|
||||||
|
@ -16,7 +16,7 @@ import gettext from 'sources/gettext';
|
|||||||
import url_for from 'sources/url_for';
|
import url_for from 'sources/url_for';
|
||||||
|
|
||||||
import getApiInstance from '../../../../../static/js/api_instance';
|
import getApiInstance from '../../../../../static/js/api_instance';
|
||||||
import CodeMirror from '../../../../../static/js/components/CodeMirror';
|
import CodeMirror from '../../../../../static/js/components/ReactCodeMirror';
|
||||||
import { DEBUGGER_EVENTS } from '../DebuggerConstants';
|
import { DEBUGGER_EVENTS } from '../DebuggerConstants';
|
||||||
import { DebuggerEventsContext } from './DebuggerComponent';
|
import { DebuggerEventsContext } from './DebuggerComponent';
|
||||||
import { usePgAdmin } from '../../../../../static/js/BrowserComponent';
|
import { usePgAdmin } from '../../../../../static/js/BrowserComponent';
|
||||||
@ -36,13 +36,6 @@ export default function DebuggerEditor({ getEditor, params }) {
|
|||||||
|
|
||||||
const api = getApiInstance();
|
const api = getApiInstance();
|
||||||
|
|
||||||
function makeMarker() {
|
|
||||||
let marker = document.createElement('div');
|
|
||||||
marker.style.color = '#822';
|
|
||||||
marker.innerHTML = '●';
|
|
||||||
return marker;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setBreakpoint(lineNo, setType) {
|
function setBreakpoint(lineNo, setType) {
|
||||||
// Make ajax call to set/clear the break point by user
|
// Make ajax call to set/clear the break point by user
|
||||||
let baseUrl = url_for('debugger.set_breakpoint', {
|
let baseUrl = url_for('debugger.set_breakpoint', {
|
||||||
@ -67,31 +60,6 @@ export default function DebuggerEditor({ getEditor, params }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onBreakPoint(cm, n, gutter) {
|
|
||||||
// If breakpoint gutter is clicked and execution is not completed then only set the breakpoint
|
|
||||||
if (gutter == 'breakpoints' && !params.debuggerDirect.polling_timeout_idle) {
|
|
||||||
let info = cm.lineInfo(n);
|
|
||||||
// If gutterMarker is undefined that means there is no marker defined previously
|
|
||||||
// So we need to set the breakpoint command here...
|
|
||||||
if (info.gutterMarkers == undefined) {
|
|
||||||
setBreakpoint(n + 1, 1); //set the breakpoint
|
|
||||||
} else {
|
|
||||||
if (info.gutterMarkers.breakpoints == undefined) {
|
|
||||||
setBreakpoint(n + 1, 1); //set the breakpoint
|
|
||||||
} else {
|
|
||||||
setBreakpoint(n + 1, 0); //clear the breakpoint
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If line folding is defined then gutterMarker will be defined so
|
|
||||||
// we need to find out 'breakpoints' information
|
|
||||||
let markers = info.gutterMarkers;
|
|
||||||
if (markers != undefined && info.gutterMarkers.breakpoints == undefined)
|
|
||||||
markers = info.gutterMarkers.breakpoints;
|
|
||||||
cm.setGutterMarker(n, 'breakpoints', markers ? null : makeMarker());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eventBus.registerListener(DEBUGGER_EVENTS.EDITOR_SET_SQL, (value, focus = true) => {
|
eventBus.registerListener(DEBUGGER_EVENTS.EDITOR_SET_SQL, (value, focus = true) => {
|
||||||
focus && editor.current?.focus();
|
focus && editor.current?.focus();
|
||||||
editor.current?.setValue(value);
|
editor.current?.setValue(value);
|
||||||
@ -99,19 +67,21 @@ export default function DebuggerEditor({ getEditor, params }) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
self = this;
|
self = this;
|
||||||
// Register the callback when user set/clear the breakpoint on gutter area.
|
|
||||||
editor.current.on('gutterClick', onBreakPoint);
|
|
||||||
getEditor(editor.current);
|
getEditor(editor.current);
|
||||||
}, [editor.current]);
|
}, [editor.current]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CodeMirror
|
<CodeMirror
|
||||||
currEditor={(obj) => {
|
currEditor={(obj) => {
|
||||||
editor.current = obj;
|
editor.current = obj;
|
||||||
}}
|
}}
|
||||||
gutters={['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'breakpoints']}
|
|
||||||
value={''}
|
value={''}
|
||||||
|
onBreakPointChange={(line, on)=>{
|
||||||
|
setBreakpoint(line, on ? 1 : 0);
|
||||||
|
}}
|
||||||
className={classes.sql}
|
className={classes.sql}
|
||||||
disabled={true}
|
readonly={true}
|
||||||
|
breakpoint
|
||||||
/>);
|
/>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
require(
|
require(
|
||||||
['sources/generated/debugger', 'sources/pgadmin', 'sources/generated/codemirror'],
|
['sources/generated/debugger', 'sources/pgadmin'],
|
||||||
function(pgDirectDebug, pgAdmin) {
|
function(pgDirectDebug, pgAdmin) {
|
||||||
var pgDebug = window.pgAdmin.Tools.Debugger;
|
var pgDebug = window.pgAdmin.Tools.Debugger;
|
||||||
pgDebug.load(document.getElementById('debugger-main-container'), {{ uniqueId }}, {{ debug_type }}, '{{ function_name_with_arguments }}', '{{layout|safe}}');
|
pgDebug.load(document.getElementById('debugger-main-container'), {{ uniqueId }}, {{ debug_type }}, '{{ function_name_with_arguments }}', '{{layout|safe}}');
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
{% block init_script %}
|
{% block init_script %}
|
||||||
try {
|
try {
|
||||||
require(
|
require(
|
||||||
['sources/generated/browser_nodes', 'sources/generated/codemirror'],
|
['sources/generated/browser_nodes'],
|
||||||
function() {
|
function() {
|
||||||
require(['sources/generated/erd_tool'], function(module) {
|
require(['sources/generated/erd_tool'], function(module) {
|
||||||
window.pgAdmin.Tools.ERD.loadComponent(
|
window.pgAdmin.Tools.ERD.loadComponent(
|
||||||
|
@ -108,17 +108,6 @@
|
|||||||
height: calc(100% - #{$footer-height-calc});
|
height: calc(100% - #{$footer-height-calc});
|
||||||
}
|
}
|
||||||
|
|
||||||
.wizard-right-panel_content .CodeMirror {
|
|
||||||
border: 1px solid $color-gray-light;
|
|
||||||
height: 100% !important;
|
|
||||||
min-height: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wizard-right-panel_content .CodeMirror-linenumber {
|
|
||||||
background: $color-gray-light;
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grant_wizard_container {
|
.grant_wizard_container {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% block init_script %}
|
{% block init_script %}
|
||||||
try {
|
try {
|
||||||
require(
|
require(
|
||||||
['sources/generated/codemirror', 'sources/generated/browser_nodes', 'sources/generated/schema_diff'],
|
['sources/generated/browser_nodes', 'sources/generated/schema_diff'],
|
||||||
function() {
|
function() {
|
||||||
var pgSchemaDiff = window.pgAdmin.Tools.SchemaDiff;
|
var pgSchemaDiff = window.pgAdmin.Tools.SchemaDiff;
|
||||||
pgSchemaDiff.load(document.getElementById('schema-diff-main-container'),{{trans_id}});
|
pgSchemaDiff.load(document.getElementById('schema-diff-main-container'),{{trans_id}});
|
||||||
|
@ -10,13 +10,12 @@ import { makeStyles } from '@material-ui/styles';
|
|||||||
import React, {useContext, useCallback, useEffect } from 'react';
|
import React, {useContext, useCallback, useEffect } from 'react';
|
||||||
import { format } from 'sql-formatter';
|
import { format } from 'sql-formatter';
|
||||||
import { QueryToolContext, QueryToolEventsContext } from '../QueryToolComponent';
|
import { QueryToolContext, QueryToolEventsContext } from '../QueryToolComponent';
|
||||||
import CodeMirror from '../../../../../../static/js/components/CodeMirror';
|
import CodeMirror from '../../../../../../static/js/components/ReactCodeMirror';
|
||||||
import {PANELS, QUERY_TOOL_EVENTS} from '../QueryToolConstants';
|
import {PANELS, QUERY_TOOL_EVENTS} from '../QueryToolConstants';
|
||||||
import url_for from 'sources/url_for';
|
import url_for from 'sources/url_for';
|
||||||
import { LayoutDockerContext, LAYOUT_EVENTS } from '../../../../../../static/js/helpers/Layout';
|
import { LayoutDockerContext, LAYOUT_EVENTS } from '../../../../../../static/js/helpers/Layout';
|
||||||
import ConfirmSaveContent from '../../../../../../static/js/Dialogs/ConfirmSaveContent';
|
import ConfirmSaveContent from '../../../../../../static/js/Dialogs/ConfirmSaveContent';
|
||||||
import gettext from 'sources/gettext';
|
import gettext from 'sources/gettext';
|
||||||
import OrigCodeMirror from 'bundled_codemirror';
|
|
||||||
import { isMac } from '../../../../../../static/js/keyboard_shortcuts';
|
import { isMac } from '../../../../../../static/js/keyboard_shortcuts';
|
||||||
import { checkTrojanSource } from '../../../../../../static/js/utils';
|
import { checkTrojanSource } from '../../../../../../static/js/utils';
|
||||||
import { parseApiError } from '../../../../../../static/js/api_instance';
|
import { parseApiError } from '../../../../../../static/js/api_instance';
|
||||||
@ -32,211 +31,32 @@ const useStyles = makeStyles(()=>({
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function registerAutocomplete(api, transId, sqlEditorPref, onFailure) {
|
async function registerAutocomplete(editor, api, transId) {
|
||||||
let timeoutId;
|
editor.registerAutocomplete((context, onAvailable)=>{
|
||||||
let loadingEle;
|
return new Promise((resolve, reject)=>{
|
||||||
let prevSearch = null;
|
const url = url_for('sqleditor.autocomplete', {
|
||||||
OrigCodeMirror.registerHelper('hint', 'sql', function (editor) {
|
'trans_id': transId,
|
||||||
let data = [],
|
});
|
||||||
doc = editor.getDoc(),
|
const word = context.matchBefore(/\w*/);
|
||||||
cur = doc.getCursor(),
|
const fullSql = context.state.doc.toString();
|
||||||
// function context
|
api.post(url, JSON.stringify([fullSql, fullSql]))
|
||||||
ctx = {
|
.then((res) => {
|
||||||
editor: editor,
|
onAvailable();
|
||||||
// URL for auto-complete
|
resolve({
|
||||||
url: url_for('sqleditor.autocomplete', {
|
from: word.from,
|
||||||
'trans_id': transId,
|
options: Object.keys(res.data.data.result).map((key)=>({
|
||||||
}),
|
label: key, type: res.data.data.result[key].object_type
|
||||||
data: data,
|
})),
|
||||||
// Get the line number in the cursor position
|
validFor: (text, from)=>{
|
||||||
current_line: cur.line,
|
return text.startsWith(fullSql.slice(from));
|
||||||
/*
|
}
|
||||||
* Render function for hint to add our own class
|
|
||||||
* and icon as per the object type.
|
|
||||||
*/
|
|
||||||
hint_render: function (elt, data_arg, cur_arg) {
|
|
||||||
let el = document.createElement('span');
|
|
||||||
switch (cur_arg.type) {
|
|
||||||
case 'database':
|
|
||||||
el.className = 'sqleditor-hint pg-icon-' + cur_arg.type;
|
|
||||||
break;
|
|
||||||
case 'datatype':
|
|
||||||
el.className = 'sqleditor-hint icon-type';
|
|
||||||
break;
|
|
||||||
case 'keyword':
|
|
||||||
el.className = 'sqleditor-hint icon-key';
|
|
||||||
break;
|
|
||||||
case 'table alias':
|
|
||||||
el.className = 'sqleditor-hint icon-at';
|
|
||||||
break;
|
|
||||||
case 'join':
|
|
||||||
case 'fk join':
|
|
||||||
el.className = 'sqleditor-hint icon-join';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
el.className = 'sqleditor-hint icon-' + cur_arg.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
el.appendChild(document.createTextNode(cur_arg.text));
|
|
||||||
elt.appendChild(el);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
data.push(doc.getValue());
|
|
||||||
|
|
||||||
if (!editor.state.autoCompleteList)
|
|
||||||
editor.state.autoCompleteList = [];
|
|
||||||
|
|
||||||
// This function is used to show the loading element until response comes.
|
|
||||||
const showLoading = (editor)=>{
|
|
||||||
if (editor.getInputField().getAttribute('aria-activedescendant') != null) {
|
|
||||||
hideLoading();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!loadingEle) {
|
|
||||||
let ownerDocument = editor.getInputField().ownerDocument;
|
|
||||||
loadingEle = ownerDocument.createElement('div');
|
|
||||||
loadingEle.className = 'CodeMirror-hints';
|
|
||||||
let iconEle = ownerDocument.createElement('div');
|
|
||||||
iconEle.className = 'icon-spinner';
|
|
||||||
iconEle.style.marginTop = '4px';
|
|
||||||
iconEle.style.marginLeft = '2px';
|
|
||||||
|
|
||||||
let spanEle = ownerDocument.createElement('span');
|
|
||||||
spanEle.innerText = gettext('Loading...');
|
|
||||||
spanEle.style.marginLeft = '17px';
|
|
||||||
|
|
||||||
iconEle.appendChild(spanEle);
|
|
||||||
loadingEle.appendChild(iconEle);
|
|
||||||
ownerDocument.body.appendChild(loadingEle);
|
|
||||||
}
|
|
||||||
let pos = editor.cursorCoords(true);
|
|
||||||
loadingEle.style.left = pos.left + 'px';
|
|
||||||
loadingEle.style.top = pos.bottom + 'px';
|
|
||||||
loadingEle.style.height = '25px';
|
|
||||||
};
|
|
||||||
|
|
||||||
// This function is used to hide the loading element.
|
|
||||||
const hideLoading = ()=>{
|
|
||||||
loadingEle?.parentNode?.removeChild(loadingEle);
|
|
||||||
loadingEle = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
then: function (cb) {
|
|
||||||
let self_local = this;
|
|
||||||
|
|
||||||
// This function is used to filter the data and call the callback
|
|
||||||
// function with that filtered data.
|
|
||||||
function setAutoCompleteData() {
|
|
||||||
const searchRe = new RegExp('^"{0,1}' + search, 'i');
|
|
||||||
let filterData = self_local.editor.state.autoCompleteList.filter((item)=>{
|
|
||||||
return searchRe.test(item.text);
|
|
||||||
});
|
});
|
||||||
|
})
|
||||||
cb({
|
.catch((err) => {
|
||||||
list: filterData,
|
onAvailable();
|
||||||
from: {
|
reject(err);
|
||||||
line: self_local.current_line,
|
});
|
||||||
ch: start,
|
});
|
||||||
},
|
|
||||||
to: {
|
|
||||||
line: self_local.current_line,
|
|
||||||
ch: end,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Below logic find the start and end point
|
|
||||||
* to replace the selected auto complete suggestion.
|
|
||||||
*/
|
|
||||||
let token = self_local.editor.getTokenAt(cur),
|
|
||||||
start, end, search;
|
|
||||||
if (token.end > cur.ch) {
|
|
||||||
token.end = cur.ch;
|
|
||||||
token.string = token.string.slice(0, cur.ch - token.start);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token.string.match(/^[."`\w@]\w*$/)) {
|
|
||||||
search = token.string;
|
|
||||||
start = token.start;
|
|
||||||
end = token.end;
|
|
||||||
} else {
|
|
||||||
start = end = cur.ch;
|
|
||||||
search = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Added 1 in the start position if search string
|
|
||||||
* started with "." or "`" else auto complete of code mirror
|
|
||||||
* will remove the "." when user select any suggestion.
|
|
||||||
*/
|
|
||||||
if (search.charAt(0) == '.' || search.charAt(0) == '``') {
|
|
||||||
start += 1;
|
|
||||||
search = search.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handled special case when autocomplete on keypress is off,
|
|
||||||
// the query is cleared, and retype some other words and press CTRL/CMD + Space.
|
|
||||||
if (!sqlEditorPref.autocomplete_on_key_press && start == 0)
|
|
||||||
self_local.editor.state.autoCompleteList = [];
|
|
||||||
|
|
||||||
// Clear the auto complete list if previous token/search is blank or dot.
|
|
||||||
if (prevSearch == '' || prevSearch == '.' || prevSearch == '"')
|
|
||||||
self_local.editor.state.autoCompleteList = [];
|
|
||||||
|
|
||||||
prevSearch = search;
|
|
||||||
// Get the text from start to the current cursor position.
|
|
||||||
self_local.data.push(
|
|
||||||
doc.getRange({
|
|
||||||
line: 0,
|
|
||||||
ch: 0,
|
|
||||||
}, {
|
|
||||||
line: self_local.current_line,
|
|
||||||
ch: token.start + 1,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// If search token is not empty and auto complete list have some data
|
|
||||||
// then no need to send the request to the backend to fetch the data.
|
|
||||||
// auto complete the data using already fetched list.
|
|
||||||
if (search != '' && self_local.editor.state.autoCompleteList.length != 0) {
|
|
||||||
setAutoCompleteData();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Show loading indicator
|
|
||||||
showLoading(self_local.editor);
|
|
||||||
|
|
||||||
timeoutId && clearTimeout(timeoutId);
|
|
||||||
timeoutId = setTimeout(()=> {
|
|
||||||
timeoutId = null;
|
|
||||||
// Make ajax call to find the autocomplete data
|
|
||||||
api.post(self_local.url, JSON.stringify(self_local.data))
|
|
||||||
.then((res) => {
|
|
||||||
hideLoading();
|
|
||||||
let result = [];
|
|
||||||
|
|
||||||
_.each(res.data.data.result, function (obj, key) {
|
|
||||||
result.push({
|
|
||||||
text: key,
|
|
||||||
type: obj.object_type,
|
|
||||||
render: self_local.hint_render,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
self_local.editor.state.autoCompleteList = result;
|
|
||||||
setAutoCompleteData();
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
hideLoading();
|
|
||||||
onFailure?.(err);
|
|
||||||
});
|
|
||||||
}, 300);
|
|
||||||
}.bind(ctx),
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,31 +67,22 @@ export default function Query() {
|
|||||||
const queryToolCtx = useContext(QueryToolContext);
|
const queryToolCtx = useContext(QueryToolContext);
|
||||||
const layoutDocker = useContext(LayoutDockerContext);
|
const layoutDocker = useContext(LayoutDockerContext);
|
||||||
const lastCursorPos = React.useRef();
|
const lastCursorPos = React.useRef();
|
||||||
const lastSavedText = React.useRef('');
|
|
||||||
const markedLine = React.useRef(0);
|
|
||||||
const marker = React.useRef();
|
|
||||||
const pgAdmin = usePgAdmin();
|
const pgAdmin = usePgAdmin();
|
||||||
const preferencesStore = usePreferences();
|
const preferencesStore = usePreferences();
|
||||||
|
|
||||||
const removeHighlightError = (cmObj)=>{
|
|
||||||
// Remove already existing marker
|
|
||||||
marker.current?.clear();
|
|
||||||
cmObj.removeLineClass(markedLine.current, 'wrap', 'CodeMirror-activeline-background');
|
|
||||||
markedLine.current = 0;
|
|
||||||
};
|
|
||||||
const highlightError = (cmObj, {errormsg: result, data})=>{
|
const highlightError = (cmObj, {errormsg: result, data})=>{
|
||||||
let errorLineNo = 0,
|
let errorLineNo = 0,
|
||||||
startMarker = 0,
|
startMarker = 0,
|
||||||
endMarker = 0,
|
endMarker = 0,
|
||||||
selectedLineNo = 0,
|
selectedLineNo = 1,
|
||||||
origQueryLen = cmObj.getValue().length;
|
origQueryLen = cmObj.getValue().length;
|
||||||
|
|
||||||
removeHighlightError(cmObj);
|
cmObj.removeErrorMark();
|
||||||
|
|
||||||
// In case of selection we need to find the actual line no
|
// In case of selection we need to find the actual line no
|
||||||
if (cmObj.getSelection().length > 0) {
|
if (cmObj.getSelection().length > 0) {
|
||||||
selectedLineNo = cmObj.getCursor(true).line;
|
selectedLineNo = cmObj.getCurrentLineNo();
|
||||||
origQueryLen = cmObj.getLine(selectedLineNo).length;
|
origQueryLen = cmObj.line(selectedLineNo).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch the LINE string using regex from the result
|
// Fetch the LINE string using regex from the result
|
||||||
@ -281,8 +92,8 @@ export default function Query() {
|
|||||||
|
|
||||||
// If line and character is null then no need to mark
|
// If line and character is null then no need to mark
|
||||||
if (line != null && char != null) {
|
if (line != null && char != null) {
|
||||||
errorLineNo = (parseInt(line[1]) - 1) + selectedLineNo;
|
errorLineNo = parseInt(line[1]) + selectedLineNo - 1;
|
||||||
let errorCharNo = (parseInt(char[1]) - 1);
|
let errorCharNo = parseInt(char[1]) - 1;
|
||||||
|
|
||||||
/* If explain query has been run we need to
|
/* If explain query has been run we need to
|
||||||
calculate the character number.
|
calculate the character number.
|
||||||
@ -298,8 +109,8 @@ export default function Query() {
|
|||||||
* have also added 1 per line for the "\n" character.
|
* have also added 1 per line for the "\n" character.
|
||||||
*/
|
*/
|
||||||
let prevLineChars = 0;
|
let prevLineChars = 0;
|
||||||
for (let i = selectedLineNo > 0 ? selectedLineNo : 0; i < errorLineNo; i++)
|
for (let i = selectedLineNo; i < errorLineNo; i++)
|
||||||
prevLineChars += cmObj.getLine(i).length + 1;
|
prevLineChars += cmObj.getLine(i).length;
|
||||||
|
|
||||||
/* Marker starting point for the individual line is
|
/* Marker starting point for the individual line is
|
||||||
* equal to error character index minus total no of
|
* equal to error character index minus total no of
|
||||||
@ -316,18 +127,14 @@ export default function Query() {
|
|||||||
endMarker = errorLine.length;
|
endMarker = errorLine.length;
|
||||||
|
|
||||||
// Mark the error text
|
// Mark the error text
|
||||||
marker.current = cmObj.markText({
|
cmObj.setErrorMark({
|
||||||
line: errorLineNo,
|
line: errorLineNo,
|
||||||
ch: startMarker,
|
pos: startMarker,
|
||||||
}, {
|
}, {
|
||||||
line: errorLineNo,
|
line: errorLineNo,
|
||||||
ch: endMarker,
|
pos: endMarker,
|
||||||
}, {
|
|
||||||
className: 'sql-editor-mark',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
markedLine.current = errorLineNo;
|
|
||||||
cmObj.addLineClass(errorLineNo, 'wrap', 'CodeMirror-activeline-background');
|
|
||||||
cmObj.focus();
|
cmObj.focus();
|
||||||
cmObj.setCursor(errorLineNo, endMarker);
|
cmObj.setCursor(errorLineNo, endMarker);
|
||||||
}
|
}
|
||||||
@ -364,7 +171,7 @@ export default function Query() {
|
|||||||
if(result) {
|
if(result) {
|
||||||
highlightError(editor.current, result);
|
highlightError(editor.current, result);
|
||||||
} else {
|
} else {
|
||||||
removeHighlightError(editor.current);
|
editor.current.removeErrorMark();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -381,7 +188,7 @@ export default function Query() {
|
|||||||
editor.current.setValue(res.data);
|
editor.current.setValue(res.data);
|
||||||
//Check the file content for Trojan Source
|
//Check the file content for Trojan Source
|
||||||
checkTrojanSource(res.data);
|
checkTrojanSource(res.data);
|
||||||
lastSavedText.current = res.data;
|
editor.current.markClean();
|
||||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.LOAD_FILE_DONE, fileName, true);
|
eventBus.fireEvent(QUERY_TOOL_EVENTS.LOAD_FILE_DONE, fileName, true);
|
||||||
}).catch((err)=>{
|
}).catch((err)=>{
|
||||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.LOAD_FILE_DONE, null, false);
|
eventBus.fireEvent(QUERY_TOOL_EVENTS.LOAD_FILE_DONE, null, false);
|
||||||
@ -390,12 +197,11 @@ export default function Query() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
eventBus.registerListener(QUERY_TOOL_EVENTS.SAVE_FILE, (fileName)=>{
|
eventBus.registerListener(QUERY_TOOL_EVENTS.SAVE_FILE, (fileName)=>{
|
||||||
let editorValue = editor.current.getValue();
|
|
||||||
queryToolCtx.api.post(url_for('sqleditor.save_file'), {
|
queryToolCtx.api.post(url_for('sqleditor.save_file'), {
|
||||||
'file_name': decodeURI(fileName),
|
'file_name': decodeURI(fileName),
|
||||||
'file_content': editor.current.getValue(),
|
'file_content': editor.current.getValue(),
|
||||||
}).then(()=>{
|
}).then(()=>{
|
||||||
lastSavedText.current = editorValue;
|
editor.current.markClean();
|
||||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.SAVE_FILE_DONE, fileName, true);
|
eventBus.fireEvent(QUERY_TOOL_EVENTS.SAVE_FILE_DONE, fileName, true);
|
||||||
pgAdmin.Browser.notifier.success(gettext('File saved successfully.'));
|
pgAdmin.Browser.notifier.success(gettext('File saved successfully.'));
|
||||||
}).catch((err)=>{
|
}).catch((err)=>{
|
||||||
@ -426,16 +232,11 @@ export default function Query() {
|
|||||||
key.shiftKey = false;
|
key.shiftKey = false;
|
||||||
key.altKey = replace;
|
key.altKey = replace;
|
||||||
}
|
}
|
||||||
editor.current?.triggerOnKeyDown(
|
editor.current?.fireDOMEvent(new KeyboardEvent('keydown', key));
|
||||||
new KeyboardEvent('keydown', key)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
eventBus.registerListener(QUERY_TOOL_EVENTS.EDITOR_SET_SQL, (value, focus=true)=>{
|
eventBus.registerListener(QUERY_TOOL_EVENTS.EDITOR_SET_SQL, (value, focus=true)=>{
|
||||||
focus && editor.current?.focus();
|
focus && editor.current?.focus();
|
||||||
if(!queryToolCtx.params.is_query_tool){
|
editor.current?.setValue(value, !queryToolCtx.params.is_query_tool);
|
||||||
lastSavedText.current = value;
|
|
||||||
}
|
|
||||||
editor.current?.setValue(value);
|
|
||||||
if (value == '' && editor.current) {
|
if (value == '' && editor.current) {
|
||||||
editor.current.state.autoCompleteList = [];
|
editor.current.state.autoCompleteList = [];
|
||||||
}
|
}
|
||||||
@ -500,7 +301,7 @@ export default function Query() {
|
|||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
const warnSaveTextClose = ()=>{
|
const warnSaveTextClose = ()=>{
|
||||||
if(!isDirty() || !queryToolCtx.preferences?.sqleditor.prompt_save_query_changes) {
|
if(!editor.current.isDirty() || !queryToolCtx.preferences?.sqleditor.prompt_save_query_changes) {
|
||||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.WARN_TXN_CLOSE);
|
eventBus.fireEvent(QUERY_TOOL_EVENTS.WARN_TXN_CLOSE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -526,23 +327,20 @@ export default function Query() {
|
|||||||
}, [queryToolCtx.preferences]);
|
}, [queryToolCtx.preferences]);
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
registerAutocomplete(queryToolCtx.api, queryToolCtx.params.trans_id, queryToolCtx.preferences.sqleditor,
|
registerAutocomplete(editor.current, queryToolCtx.api, queryToolCtx.params.trans_id, queryToolCtx.preferences.sqleditor,
|
||||||
(err)=>{eventBus.fireEvent(QUERY_TOOL_EVENTS.HANDLE_API_ERROR, err);}
|
(err)=>{eventBus.fireEvent(QUERY_TOOL_EVENTS.HANDLE_API_ERROR, err);}
|
||||||
);
|
);
|
||||||
}, [queryToolCtx.params.trans_id]);
|
}, [queryToolCtx.params.trans_id]);
|
||||||
|
|
||||||
const isDirty = ()=>(lastSavedText.current !== editor.current.getValue());
|
const cursorActivity = useCallback(_.debounce((cursor)=>{
|
||||||
|
lastCursorPos.current = cursor;
|
||||||
const cursorActivity = useCallback(_.debounce((cmObj)=>{
|
eventBus.fireEvent(QUERY_TOOL_EVENTS.CURSOR_ACTIVITY, [lastCursorPos.current.line, lastCursorPos.current.ch+1]);
|
||||||
const c = cmObj.getCursor();
|
|
||||||
lastCursorPos.current = c;
|
|
||||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.CURSOR_ACTIVITY, [c.line+1, c.ch+1]);
|
|
||||||
}, 100), []);
|
}, 100), []);
|
||||||
|
|
||||||
const change = useCallback(()=>{
|
const change = useCallback(()=>{
|
||||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.QUERY_CHANGED, isDirty());
|
eventBus.fireEvent(QUERY_TOOL_EVENTS.QUERY_CHANGED, editor.current.isDirty());
|
||||||
|
|
||||||
if(!queryToolCtx.params.is_query_tool && isDirty()){
|
if(!queryToolCtx.params.is_query_tool && editor.current.isDirty()){
|
||||||
if(queryToolCtx.preferences.sqleditor.view_edit_promotion_warning){
|
if(queryToolCtx.preferences.sqleditor.view_edit_promotion_warning){
|
||||||
checkViewEditDataPromotion();
|
checkViewEditDataPromotion();
|
||||||
} else {
|
} else {
|
||||||
@ -552,7 +350,7 @@ export default function Query() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const closePromotionWarning = (closeModal)=>{
|
const closePromotionWarning = (closeModal)=>{
|
||||||
if(isDirty()) {
|
if(editor.current.isDirty()) {
|
||||||
editor.current.undo();
|
editor.current.undo();
|
||||||
closeModal?.();
|
closeModal?.();
|
||||||
}
|
}
|
||||||
@ -567,7 +365,7 @@ export default function Query() {
|
|||||||
promoteToQueryTool();
|
promoteToQueryTool();
|
||||||
let cursor = editor.current.getCursor();
|
let cursor = editor.current.getCursor();
|
||||||
editor.current.setValue(editor.current.getValue());
|
editor.current.setValue(editor.current.getValue());
|
||||||
editor.current.setCursor(cursor);
|
editor.current.setCursor(cursor.line, cursor.ch);
|
||||||
editor.current.focus();
|
editor.current.focus();
|
||||||
let title = getTitle(pgAdmin, queryToolCtx.preferences.browser, null,null,queryToolCtx.params.server_name, queryToolCtx.params.dbname, queryToolCtx.params.user);
|
let title = getTitle(pgAdmin, queryToolCtx.preferences.browser, null,null,queryToolCtx.params.server_name, queryToolCtx.params.dbname, queryToolCtx.params.user);
|
||||||
queryToolCtx.updateTitle(title);
|
queryToolCtx.updateTitle(title);
|
||||||
@ -599,11 +397,9 @@ export default function Query() {
|
|||||||
}}
|
}}
|
||||||
value={''}
|
value={''}
|
||||||
className={classes.sql}
|
className={classes.sql}
|
||||||
events={{
|
onCursorActivity={cursorActivity}
|
||||||
'focus': cursorActivity,
|
onChange={change}
|
||||||
'cursorActivity': cursorActivity,
|
|
||||||
'change': change,
|
|
||||||
}}
|
|
||||||
autocomplete={true}
|
autocomplete={true}
|
||||||
|
keepHistory={queryToolCtx.params.is_query_tool}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ import AssessmentRoundedIcon from '@material-ui/icons/AssessmentRounded';
|
|||||||
import ExplicitRoundedIcon from '@material-ui/icons/ExplicitRounded';
|
import ExplicitRoundedIcon from '@material-ui/icons/ExplicitRounded';
|
||||||
import { SaveDataIcon, CommitIcon, RollbackIcon, ViewDataIcon } from '../../../../../../static/js/components/ExternalIcon';
|
import { SaveDataIcon, CommitIcon, RollbackIcon, ViewDataIcon } from '../../../../../../static/js/components/ExternalIcon';
|
||||||
import { InputSwitch } from '../../../../../../static/js/components/FormComponents';
|
import { InputSwitch } from '../../../../../../static/js/components/FormComponents';
|
||||||
import CodeMirror from '../../../../../../static/js/components/CodeMirror';
|
import CodeMirror from '../../../../../../static/js/components/ReactCodeMirror';
|
||||||
import { DefaultButton } from '../../../../../../static/js/components/Buttons';
|
import { DefaultButton } from '../../../../../../static/js/components/Buttons';
|
||||||
import { useDelayedCaller } from '../../../../../../static/js/custom_hooks';
|
import { useDelayedCaller } from '../../../../../../static/js/custom_hooks';
|
||||||
import Loader from 'sources/components/Loader';
|
import Loader from 'sources/components/Loader';
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
{% block init_script %}
|
{% block init_script %}
|
||||||
try {
|
try {
|
||||||
require(
|
require(
|
||||||
['sources/generated/browser_nodes', 'sources/generated/codemirror'],
|
['sources/generated/browser_nodes'],
|
||||||
function() {
|
function() {
|
||||||
require(['sources/generated/sqleditor'], function(module) {
|
require(['sources/generated/sqleditor'], function(module) {
|
||||||
window.pgAdmin.Tools.SQLEditor.loadComponent(
|
window.pgAdmin.Tools.SQLEditor.loadComponent(
|
||||||
|
@ -172,8 +172,8 @@ class QueryToolAutoCompleteFeatureTest(BaseFeatureTest):
|
|||||||
ActionChains(self.page.driver).key_down(
|
ActionChains(self.page.driver).key_down(
|
||||||
Keys.CONTROL).send_keys(Keys.SPACE).key_up(
|
Keys.CONTROL).send_keys(Keys.SPACE).key_up(
|
||||||
Keys.CONTROL).perform()
|
Keys.CONTROL).perform()
|
||||||
if self.page.check_if_element_exist_by_xpath(
|
if self.page.check_if_element_exist_by_css_selector(
|
||||||
QueryToolLocators.code_mirror_hint_box_xpath, 15):
|
QueryToolLocators.code_mirror_hint_box, 15):
|
||||||
hint_displayed = True
|
hint_displayed = True
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
@ -186,12 +186,11 @@ class QueryToolAutoCompleteFeatureTest(BaseFeatureTest):
|
|||||||
else:
|
else:
|
||||||
# if no IntelliSense is present it means there is only one option
|
# if no IntelliSense is present it means there is only one option
|
||||||
# so check if required string is present in codeMirror
|
# so check if required string is present in codeMirror
|
||||||
code_mirror = self.driver.find_elements(
|
code_mirror_text = self.driver.find_element(
|
||||||
By.XPATH, QueryToolLocators.code_mirror_data_xpath)
|
By.CSS_SELECTOR, QueryToolLocators.code_mirror_content
|
||||||
for data in code_mirror:
|
.format('#id-query')).text
|
||||||
code_mirror_text = data.text
|
|
||||||
print("Single entry..........")
|
if expected_string not in code_mirror_text:
|
||||||
if expected_string not in code_mirror_text:
|
print("single entry exception.........")
|
||||||
print("single entry exception.........")
|
raise RuntimeError("Required String %s is not "
|
||||||
raise RuntimeError("Required String %s is not "
|
"present" % expected_string)
|
||||||
"present" % expected_string)
|
|
||||||
|
@ -46,7 +46,7 @@ class TableDdlFeatureTest(BaseFeatureTest):
|
|||||||
|
|
||||||
# Wait till data is displayed in SQL Tab
|
# Wait till data is displayed in SQL Tab
|
||||||
self.assertTrue(self.page.check_if_element_exist_by_xpath(
|
self.assertTrue(self.page.check_if_element_exist_by_xpath(
|
||||||
"//*[contains(@class,'CodeMirror-lines') and "
|
"//*[contains(@class,'cm-line') and "
|
||||||
"contains(.,'CREATE TABLE IF NOT EXISTS public.%s')]"
|
"contains(.,'CREATE TABLE IF NOT EXISTS public.%s')]"
|
||||||
% self.test_table_name, 10), "No data displayed in SQL tab")
|
% self.test_table_name, 10), "No data displayed in SQL tab")
|
||||||
|
|
||||||
|
@ -51,18 +51,14 @@ class CopySQLFeatureTest(BaseFeatureTest):
|
|||||||
self.page.click_tab("SQL")
|
self.page.click_tab("SQL")
|
||||||
# Wait till data is displayed in SQL Tab
|
# Wait till data is displayed in SQL Tab
|
||||||
self.assertTrue(self.page.check_if_element_exist_by_xpath(
|
self.assertTrue(self.page.check_if_element_exist_by_xpath(
|
||||||
"//*[contains(@class,'CodeMirror-lines') and "
|
"//*[contains(@class,'cm-line') and "
|
||||||
"contains(.,'CREATE TABLE IF NOT EXISTS public.%s')]"
|
"contains(.,'CREATE TABLE IF NOT EXISTS public.%s')]"
|
||||||
% self.test_table_name, 10), "No data displayed in SQL tab")
|
% self.test_table_name, 10), "No data displayed in SQL tab")
|
||||||
|
|
||||||
# Fetch the inner html & check for escaped characters
|
# Fetch the inner html & check for escaped characters
|
||||||
source_code = self.driver.find_elements(
|
sql_query = self.driver.find_element(
|
||||||
By.XPATH, QueryToolLocators.code_mirror_data_xpath)
|
By.CSS_SELECTOR, QueryToolLocators.code_mirror_content
|
||||||
|
.format('#id-sql')).text
|
||||||
sql_query = ''
|
|
||||||
for data in source_code:
|
|
||||||
sql_query += data.text
|
|
||||||
sql_query += '\n'
|
|
||||||
|
|
||||||
return sql_query
|
return sql_query
|
||||||
|
|
||||||
@ -76,12 +72,9 @@ class CopySQLFeatureTest(BaseFeatureTest):
|
|||||||
self.driver.switch_to.frame(
|
self.driver.switch_to.frame(
|
||||||
self.driver.find_element(By.TAG_NAME, "iframe"))
|
self.driver.find_element(By.TAG_NAME, "iframe"))
|
||||||
|
|
||||||
code_mirror = self.driver.find_elements(
|
query_tool_result = self.driver.find_element(
|
||||||
By.XPATH, QueryToolLocators.code_mirror_data_xpath)
|
By.CSS_SELECTOR, QueryToolLocators.code_mirror_content
|
||||||
query_tool_result = ''
|
.format('#id-query')).text
|
||||||
for data in code_mirror:
|
|
||||||
query_tool_result += data.text
|
|
||||||
query_tool_result += '\n'
|
|
||||||
|
|
||||||
return query_tool_result
|
return query_tool_result
|
||||||
|
|
||||||
|
@ -128,12 +128,12 @@ class CheckForXssFeatureTest(BaseFeatureTest):
|
|||||||
|
|
||||||
# Wait till data is displayed in SQL Tab
|
# Wait till data is displayed in SQL Tab
|
||||||
self.assertTrue(self.page.check_if_element_exist_by_xpath(
|
self.assertTrue(self.page.check_if_element_exist_by_xpath(
|
||||||
"//*[contains(@class,'CodeMirror-lines') and "
|
"//*[contains(@class,'cm-line') and "
|
||||||
"contains(.,'CREATE TABLE')]", 10), "No data displayed in SQL tab")
|
"contains(.,'CREATE TABLE')]", 10), "No data displayed in SQL tab")
|
||||||
|
|
||||||
# Fetch the inner html & check for escaped characters
|
# Fetch the inner html & check for escaped characters
|
||||||
source_code = self.page.find_by_xpath(
|
source_code = self.page.find_by_xpath(
|
||||||
"//*[contains(@class,'CodeMirror-lines') and "
|
"//*[contains(@class,'cm-line') and "
|
||||||
"contains(.,'CREATE TABLE')]"
|
"contains(.,'CREATE TABLE')]"
|
||||||
).get_attribute('innerHTML')
|
).get_attribute('innerHTML')
|
||||||
|
|
||||||
|
@ -235,12 +235,13 @@ class QueryToolLocators:
|
|||||||
|
|
||||||
sql_editor_message = "//div[@id='id-messages'][contains(string(), '{}')]"
|
sql_editor_message = "//div[@id='id-messages'][contains(string(), '{}')]"
|
||||||
|
|
||||||
code_mirror_hint_box_xpath = "//ul[@class='CodeMirror-hints default']"
|
code_mirror_hint_box = ".cm-editor .cm-tooltip-autocomplete"
|
||||||
|
|
||||||
code_mirror_hint_item_xpath = \
|
code_mirror_hint_item_xpath = \
|
||||||
"//ul[contains(@class, 'CodeMirror-hints') and contains(., '{}')]"
|
("//div[contains(@class, 'cm-tooltip-autocomplete') "
|
||||||
|
"and contains(., '{}')]")
|
||||||
|
|
||||||
code_mirror_data_xpath = "//pre[@class=' CodeMirror-line ']/span"
|
code_mirror_content = "{0} .cm-content"
|
||||||
|
|
||||||
btn_commit = "button[data-label='Commit']"
|
btn_commit = "button[data-label='Commit']"
|
||||||
|
|
||||||
|
@ -892,7 +892,8 @@ class PgadminPage:
|
|||||||
driver.switch_to.frame(
|
driver.switch_to.frame(
|
||||||
driver.find_element(By.TAG_NAME, "iframe"))
|
driver.find_element(By.TAG_NAME, "iframe"))
|
||||||
element = driver.find_element(
|
element = driver.find_element(
|
||||||
By.CSS_SELECTOR, "#sqleditor-container .CodeMirror")
|
By.CSS_SELECTOR,
|
||||||
|
"#sqleditor-container #id-query .cm-content")
|
||||||
if element.is_displayed() and element.is_enabled():
|
if element.is_displayed() and element.is_enabled():
|
||||||
return element
|
return element
|
||||||
except (NoSuchElementException, WebDriverException):
|
except (NoSuchElementException, WebDriverException):
|
||||||
@ -933,9 +934,9 @@ class PgadminPage:
|
|||||||
action.perform()
|
action.perform()
|
||||||
else:
|
else:
|
||||||
self.driver.execute_script(
|
self.driver.execute_script(
|
||||||
"arguments[0].CodeMirror.setValue(arguments[1]);"
|
"arguments[0].cmView.view.setValue(arguments[1]);"
|
||||||
"arguments[0].CodeMirror.setCursor("
|
"arguments[0].cmView.view.setCursor("
|
||||||
"arguments[0].CodeMirror.lineCount(),0);",
|
"arguments[0].cmView.view.lineCount(),0);",
|
||||||
codemirror_ele, field_content)
|
codemirror_ele, field_content)
|
||||||
|
|
||||||
def click_tab(self, tab_name):
|
def click_tab(self, tab_name):
|
||||||
|
@ -168,20 +168,20 @@ describe('SchemaView', ()=>{
|
|||||||
|
|
||||||
it('no changes', async ()=>{
|
it('no changes', async ()=>{
|
||||||
await user.click(ctrl.container.querySelector('button[data-test="SQL"]'));
|
await user.click(ctrl.container.querySelector('button[data-test="SQL"]'));
|
||||||
expect(ctrl.container.querySelector('[data-testid="SQL"] textarea')).toHaveValue('-- No updates.');
|
expect(ctrl.container.querySelector('[data-testid="SQL"] .cm-content')).toHaveTextContent('-- No updates.');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('data invalid', async ()=>{
|
it('data invalid', async ()=>{
|
||||||
await user.clear(ctrl.container.querySelector('[name="field2"]'));
|
await user.clear(ctrl.container.querySelector('[name="field2"]'));
|
||||||
await user.type(ctrl.container.querySelector('[name="field2"]'), '2');
|
await user.type(ctrl.container.querySelector('[name="field2"]'), '2');
|
||||||
await user.click(ctrl.container.querySelector('button[data-test="SQL"]'));
|
await user.click(ctrl.container.querySelector('button[data-test="SQL"]'));
|
||||||
expect(ctrl.container.querySelector('[data-testid="SQL"] textarea')).toHaveValue('-- Definition incomplete.');
|
expect(ctrl.container.querySelector('[data-testid="SQL"] .cm-content')).toHaveTextContent('-- Definition incomplete.');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('valid data', async ()=>{
|
it('valid data', async ()=>{
|
||||||
await simulateValidData();
|
await simulateValidData();
|
||||||
await user.click(ctrl.container.querySelector('button[data-test="SQL"]'));
|
await user.click(ctrl.container.querySelector('button[data-test="SQL"]'));
|
||||||
expect(ctrl.container.querySelector('[data-testid="SQL"] textarea')).toHaveValue('select 1;');
|
expect(ctrl.container.querySelector('[data-testid="SQL"] .cm-content')).toHaveTextContent('select 1;');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
const getSearchCursorRet = {
|
|
||||||
_from: 3,
|
|
||||||
_to: 14,
|
|
||||||
find: function(_rev) {
|
|
||||||
if(_rev){
|
|
||||||
this._from = 1;
|
|
||||||
this._to = 10;
|
|
||||||
} else {
|
|
||||||
this._from = 3;
|
|
||||||
this._to = 14;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
from: function() {return this._from;},
|
|
||||||
to: function() {return this._to;},
|
|
||||||
replace: jest.fn(),
|
|
||||||
};
|
|
||||||
const fromTextAreaRet = {
|
|
||||||
'getValue':()=>'',
|
|
||||||
'setValue': jest.fn(),
|
|
||||||
'refresh': jest.fn(),
|
|
||||||
'setOption': jest.fn(),
|
|
||||||
'removeKeyMap': jest.fn(),
|
|
||||||
'addKeyMap': jest.fn(),
|
|
||||||
'getSelection': () => '',
|
|
||||||
'getSearchCursor': jest.fn(()=>getSearchCursorRet),
|
|
||||||
'getCursor': jest.fn(),
|
|
||||||
'removeOverlay': jest.fn(),
|
|
||||||
'addOverlay': jest.fn(),
|
|
||||||
'setSelection': jest.fn(),
|
|
||||||
'scrollIntoView': jest.fn(),
|
|
||||||
'getWrapperElement': ()=>document.createElement('div'),
|
|
||||||
'on': jest.fn(),
|
|
||||||
'off': jest.fn(),
|
|
||||||
'toTextArea': jest.fn(),
|
|
||||||
};
|
|
||||||
module.exports = {
|
|
||||||
fromTextArea: jest.fn(()=>fromTextAreaRet)
|
|
||||||
};
|
|
@ -9,41 +9,54 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import {default as OrigCodeMirror} from 'bundled_codemirror';
|
|
||||||
import { withTheme } from '../fake_theme';
|
import { withTheme } from '../fake_theme';
|
||||||
|
|
||||||
import pgWindow from 'sources/window';
|
import pgWindow from 'sources/window';
|
||||||
import CodeMirror from 'sources/components/CodeMirror';
|
import CodeMirror from 'sources/components/ReactCodeMirror';
|
||||||
import { FindDialog } from '../../../pgadmin/static/js/components/CodeMirror';
|
import FindDialog from 'sources/components/ReactCodeMirror/FindDialog';
|
||||||
|
import CustomEditorView from 'sources/components/ReactCodeMirror/CustomEditorView';
|
||||||
import fakePgAdmin from '../fake_pgadmin';
|
import fakePgAdmin from '../fake_pgadmin';
|
||||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
import { render, screen, fireEvent } from '@testing-library/react';
|
||||||
|
import * as CMSearch from '@codemirror/search';
|
||||||
|
|
||||||
|
jest.mock('sources/components/ReactCodeMirror/CustomEditorView');
|
||||||
|
jest.mock('@codemirror/search', () => ({
|
||||||
|
...(jest.requireActual('@codemirror/search')),
|
||||||
|
SearchQuery: jest.fn().mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
eq: jest.fn(),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
openSearchPanel: jest.fn(),
|
||||||
|
closeSearchPanel: jest.fn(),
|
||||||
|
replaceNext: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
describe('CodeMirror', ()=>{
|
describe('CodeMirror', ()=>{
|
||||||
const ThemedCM = withTheme(CodeMirror);
|
const ThemedCM = withTheme(CodeMirror);
|
||||||
let cmInstance, options={
|
let cmInstance, editor;
|
||||||
lineNumbers: true,
|
|
||||||
mode: 'text/x-pgsql',
|
|
||||||
},
|
|
||||||
cmObj = OrigCodeMirror.fromTextArea();
|
|
||||||
|
|
||||||
const cmRerender = (props)=>{
|
const cmRerender = (props)=>{
|
||||||
cmInstance.rerender(
|
cmInstance.rerender(
|
||||||
<ThemedCM
|
<ThemedCM
|
||||||
value={'Init text'}
|
value={'Init text'}
|
||||||
options={options}
|
|
||||||
className="testClass"
|
className="testClass"
|
||||||
|
currEditor={(obj) => {
|
||||||
|
editor = obj;
|
||||||
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
beforeEach(()=>{
|
beforeEach(()=>{
|
||||||
pgWindow.pgAdmin = fakePgAdmin;
|
pgWindow.pgAdmin = fakePgAdmin;
|
||||||
// jest.spyOn(OrigCodeMirror, 'fromTextArea').mockReturnValue(cmObj);
|
|
||||||
cmInstance = render(
|
cmInstance = render(
|
||||||
<ThemedCM
|
<ThemedCM
|
||||||
value={'Init text'}
|
value={'Init text'}
|
||||||
options={options}
|
|
||||||
className="testClass"
|
className="testClass"
|
||||||
|
currEditor={(obj) => {
|
||||||
|
editor = obj;
|
||||||
|
}}
|
||||||
/>);
|
/>);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -52,17 +65,23 @@ describe('CodeMirror', ()=>{
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('init', async ()=>{
|
it('init', async ()=>{
|
||||||
/* textarea ref passed to fromTextArea */
|
expect(CustomEditorView).toHaveBeenCalledTimes(1);
|
||||||
expect(OrigCodeMirror.fromTextArea).toHaveBeenCalledWith(cmInstance.container.querySelector('textarea'), expect.objectContaining(options));
|
expect(editor.setValue).toHaveBeenCalledWith('Init text');
|
||||||
await waitFor(() => expect(cmObj.setValue).toHaveBeenCalledWith('Init text'));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('change value', ()=>{
|
it('change value', ()=>{
|
||||||
|
editor.state = {
|
||||||
|
doc: [],
|
||||||
|
};
|
||||||
|
editor.setValue.mockClear();
|
||||||
|
jest.spyOn(editor, 'getValue').mockReturnValue('Init text');
|
||||||
cmRerender({value: 'the new text'});
|
cmRerender({value: 'the new text'});
|
||||||
expect(cmObj.setValue).toHaveBeenCalledWith('the new text');
|
expect(editor.setValue).toHaveBeenCalledWith('the new text');
|
||||||
|
|
||||||
|
editor.setValue.mockClear();
|
||||||
|
jest.spyOn(editor, 'getValue').mockReturnValue('the new text');
|
||||||
cmRerender({value: null});
|
cmRerender({value: null});
|
||||||
expect(cmObj.setValue).toHaveBeenCalledWith('');
|
expect(editor.setValue).toHaveBeenCalledWith('');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -73,7 +92,7 @@ describe('CodeMirror', ()=>{
|
|||||||
const ctrlMount = (props)=>{
|
const ctrlMount = (props)=>{
|
||||||
ctrl = render(
|
ctrl = render(
|
||||||
<ThemedFindDialog
|
<ThemedFindDialog
|
||||||
editor={cmObj}
|
editor={editor}
|
||||||
show={true}
|
show={true}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
{...props}
|
{...props}
|
||||||
@ -84,29 +103,27 @@ describe('CodeMirror', ()=>{
|
|||||||
it('init', ()=>{
|
it('init', ()=>{
|
||||||
ctrlMount({});
|
ctrlMount({});
|
||||||
|
|
||||||
cmObj.removeOverlay.mockClear();
|
CMSearch.SearchQuery.mockClear();
|
||||||
cmObj.addOverlay.mockClear();
|
|
||||||
const input = ctrl.container.querySelector('input');
|
const input = ctrl.container.querySelector('input');
|
||||||
|
|
||||||
fireEvent.change(input, {
|
fireEvent.change(input, {
|
||||||
target: {value: '\n\r\tA'},
|
target: {value: '\n\r\tA'},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(cmObj.removeOverlay).toHaveBeenCalled();
|
expect(CMSearch.SearchQuery).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
expect(cmObj.addOverlay).toHaveBeenCalled();
|
search: expect.stringContaining('A')
|
||||||
expect(cmObj.setSelection).toHaveBeenCalledWith(3, 14);
|
}));
|
||||||
expect(cmObj.scrollIntoView).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('escape', ()=>{
|
it('escape', ()=>{
|
||||||
ctrlMount({});
|
ctrlMount({});
|
||||||
cmObj.removeOverlay.mockClear();
|
CMSearch.closeSearchPanel.mockClear();
|
||||||
|
|
||||||
fireEvent.keyDown(ctrl.container.querySelector('input'), {
|
fireEvent.keyDown(ctrl.container.querySelector('input'), {
|
||||||
key: 'Escape',
|
key: 'Escape',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(cmObj.removeOverlay).toHaveBeenCalled();
|
expect(CMSearch.closeSearchPanel).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('toggle match case', ()=>{
|
it('toggle match case', ()=>{
|
||||||
@ -132,7 +149,8 @@ describe('CodeMirror', ()=>{
|
|||||||
|
|
||||||
it('replace', async ()=>{
|
it('replace', async ()=>{
|
||||||
ctrlMount({replace: true});
|
ctrlMount({replace: true});
|
||||||
cmObj.getSearchCursor().replace.mockClear();
|
CMSearch.SearchQuery.mockClear();
|
||||||
|
|
||||||
fireEvent.change(ctrl.container.querySelectorAll('input')[0], {
|
fireEvent.change(ctrl.container.querySelectorAll('input')[0], {
|
||||||
target: {value: 'A'},
|
target: {value: 'A'},
|
||||||
});
|
});
|
||||||
@ -142,9 +160,13 @@ describe('CodeMirror', ()=>{
|
|||||||
fireEvent.keyPress(ctrl.container.querySelectorAll('input')[1], {
|
fireEvent.keyPress(ctrl.container.querySelectorAll('input')[1], {
|
||||||
key: 'Enter', shiftKey: true, code: 13, charCode: 13
|
key: 'Enter', shiftKey: true, code: 13, charCode: 13
|
||||||
});
|
});
|
||||||
await waitFor(()=>{
|
|
||||||
expect(cmObj.getSearchCursor().replace).toHaveBeenCalled();
|
expect(CMSearch.SearchQuery).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
});
|
search: 'A',
|
||||||
|
replace: 'B'
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(CMSearch.replaceNext).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -50,6 +50,20 @@ global.afterEach(() => {
|
|||||||
|
|
||||||
window.HTMLElement.prototype.scrollIntoView = function() {};
|
window.HTMLElement.prototype.scrollIntoView = function() {};
|
||||||
|
|
||||||
|
// required for Codemirror 6 to run in jsdom
|
||||||
|
document.createRange = () => {
|
||||||
|
const range = new Range();
|
||||||
|
|
||||||
|
range.getBoundingClientRect = jest.fn();
|
||||||
|
|
||||||
|
range.getClientRects = jest.fn(() => ({
|
||||||
|
item: () => null,
|
||||||
|
length: 0,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return range;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
jest.setTimeout(15000); // 1 second
|
jest.setTimeout(15000); // 1 second
|
||||||
|
|
||||||
|
@ -357,7 +357,6 @@ module.exports = [{
|
|||||||
// Specify entry points of application
|
// Specify entry points of application
|
||||||
entry: {
|
entry: {
|
||||||
'app.bundle': sourceDir + '/bundle/app.js',
|
'app.bundle': sourceDir + '/bundle/app.js',
|
||||||
codemirror: sourceDir + '/bundle/codemirror.js',
|
|
||||||
'security.pages': 'security.pages',
|
'security.pages': 'security.pages',
|
||||||
sqleditor: './pgadmin/tools/sqleditor/static/js/index.js',
|
sqleditor: './pgadmin/tools/sqleditor/static/js/index.js',
|
||||||
schema_diff: './pgadmin/tools/schema_diff/static/js/index.js',
|
schema_diff: './pgadmin/tools/schema_diff/static/js/index.js',
|
||||||
|
@ -22,7 +22,6 @@ let webpackShimConfig = {
|
|||||||
// used by webpack while creating bundle
|
// used by webpack while creating bundle
|
||||||
resolveAlias: {
|
resolveAlias: {
|
||||||
'top': path.join(__dirname, './pgadmin/'),
|
'top': path.join(__dirname, './pgadmin/'),
|
||||||
'bundled_codemirror': path.join(__dirname, './pgadmin/static/bundle/codemirror'),
|
|
||||||
'bundled_browser': path.join(__dirname, './pgadmin/static/bundle/browser'),
|
'bundled_browser': path.join(__dirname, './pgadmin/static/bundle/browser'),
|
||||||
'sources': path.join(__dirname, './pgadmin/static/js/'),
|
'sources': path.join(__dirname, './pgadmin/static/js/'),
|
||||||
'translations': path.join(__dirname, './pgadmin/tools/templates/js/translations'),
|
'translations': path.join(__dirname, './pgadmin/tools/templates/js/translations'),
|
||||||
|
162
web/yarn.lock
162
web/yarn.lock
@ -2354,6 +2354,103 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@codemirror/autocomplete@npm:^6.0.0":
|
||||||
|
version: 6.12.0
|
||||||
|
resolution: "@codemirror/autocomplete@npm:6.12.0"
|
||||||
|
dependencies:
|
||||||
|
"@codemirror/language": ^6.0.0
|
||||||
|
"@codemirror/state": ^6.0.0
|
||||||
|
"@codemirror/view": ^6.17.0
|
||||||
|
"@lezer/common": ^1.0.0
|
||||||
|
peerDependencies:
|
||||||
|
"@codemirror/language": ^6.0.0
|
||||||
|
"@codemirror/state": ^6.0.0
|
||||||
|
"@codemirror/view": ^6.0.0
|
||||||
|
"@lezer/common": ^1.0.0
|
||||||
|
checksum: 1d4da6ccc12f5a67053a76b361f2683b5af031dd405a0bd2a261a265eb8cb7dfb115343a3291260d1ba31ce7ccb5427208ebe50f50f6747fcf27a50b62c87f7e
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@codemirror/commands@npm:^6.0.0":
|
||||||
|
version: 6.3.3
|
||||||
|
resolution: "@codemirror/commands@npm:6.3.3"
|
||||||
|
dependencies:
|
||||||
|
"@codemirror/language": ^6.0.0
|
||||||
|
"@codemirror/state": ^6.4.0
|
||||||
|
"@codemirror/view": ^6.0.0
|
||||||
|
"@lezer/common": ^1.1.0
|
||||||
|
checksum: 7d23aecc973823969434b839aefa9a98bb47212d2ce0e6869ae903adbb5233aad22a760788fb7bb6eb45b00b01a4932fb93ad43bacdcbc0215e7500cf54b17bb
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@codemirror/lang-sql@npm:^6.5.5":
|
||||||
|
version: 6.5.5
|
||||||
|
resolution: "@codemirror/lang-sql@npm:6.5.5"
|
||||||
|
dependencies:
|
||||||
|
"@codemirror/autocomplete": ^6.0.0
|
||||||
|
"@codemirror/language": ^6.0.0
|
||||||
|
"@codemirror/state": ^6.0.0
|
||||||
|
"@lezer/common": ^1.2.0
|
||||||
|
"@lezer/highlight": ^1.0.0
|
||||||
|
"@lezer/lr": ^1.0.0
|
||||||
|
checksum: 404003ae73b986bd7a00f6924db78b7ffb28fdc38d689fdc11416aaafe2d5c6dc37cc18972530f82e940acb61e18ac74a1cf7712beef448c145344ff93970dc3
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@codemirror/language@npm:^6.0.0":
|
||||||
|
version: 6.10.0
|
||||||
|
resolution: "@codemirror/language@npm:6.10.0"
|
||||||
|
dependencies:
|
||||||
|
"@codemirror/state": ^6.0.0
|
||||||
|
"@codemirror/view": ^6.23.0
|
||||||
|
"@lezer/common": ^1.1.0
|
||||||
|
"@lezer/highlight": ^1.0.0
|
||||||
|
"@lezer/lr": ^1.0.0
|
||||||
|
style-mod: ^4.0.0
|
||||||
|
checksum: 3bfd9968f5a34ce22434489a5b226db5f3bc454a1ae7c4381587ff4270ac6af61b10f93df560cb73e9a77cc13d4843722a7a7b94dbed02a3ab1971dd329b9e81
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@codemirror/lint@npm:^6.0.0":
|
||||||
|
version: 6.4.2
|
||||||
|
resolution: "@codemirror/lint@npm:6.4.2"
|
||||||
|
dependencies:
|
||||||
|
"@codemirror/state": ^6.0.0
|
||||||
|
"@codemirror/view": ^6.0.0
|
||||||
|
crelt: ^1.0.5
|
||||||
|
checksum: 5e699960c1b28dbaa584fe091a3201978907bf4b9e52810fb15d3ceaf310e38053435e0b594da0985266ae812039a5cd6c36023284a6f8568664bdca04db137f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@codemirror/search@npm:^6.0.0":
|
||||||
|
version: 6.5.5
|
||||||
|
resolution: "@codemirror/search@npm:6.5.5"
|
||||||
|
dependencies:
|
||||||
|
"@codemirror/state": ^6.0.0
|
||||||
|
"@codemirror/view": ^6.0.0
|
||||||
|
crelt: ^1.0.5
|
||||||
|
checksum: 825196ef63273494ba9a6153b01eda385edb65e77a1e49980dd3a28d4a692af1e9575e03e4b6c84f6fa2afe72217113ff4c50f58b20d13fe0d277cda5dd7dc81
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.4.0":
|
||||||
|
version: 6.4.0
|
||||||
|
resolution: "@codemirror/state@npm:6.4.0"
|
||||||
|
checksum: c5236fe5786f1b85d17273a5c17fa8aeb063658c1404ab18caeb6e7591663ec96b65d453ab8162f75839c3801b04cd55ba4bc48f44cb61ebfeeee383f89553c7
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0":
|
||||||
|
version: 6.23.1
|
||||||
|
resolution: "@codemirror/view@npm:6.23.1"
|
||||||
|
dependencies:
|
||||||
|
"@codemirror/state": ^6.4.0
|
||||||
|
style-mod: ^4.1.0
|
||||||
|
w3c-keyname: ^2.2.4
|
||||||
|
checksum: 5ea3ba5761c574e1f6e1f1058cb452189c890982a77991606d0ae40da3c6fff77f7c7fc3c43fa78d62677ccdfa65dbc56175706b793e34ad4ec7a63b21e8c18e
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@date-io/core@npm:1.x, @date-io/core@npm:^1.3.13, @date-io/core@npm:^1.3.6":
|
"@date-io/core@npm:1.x, @date-io/core@npm:^1.3.13, @date-io/core@npm:^1.3.6":
|
||||||
version: 1.3.13
|
version: 1.3.13
|
||||||
resolution: "@date-io/core@npm:1.3.13"
|
resolution: "@date-io/core@npm:1.3.13"
|
||||||
@ -3086,6 +3183,31 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@lezer/common@npm:^1.0.0, @lezer/common@npm:^1.1.0, @lezer/common@npm:^1.2.0":
|
||||||
|
version: 1.2.1
|
||||||
|
resolution: "@lezer/common@npm:1.2.1"
|
||||||
|
checksum: 0bd092e293a509ce334f4aaf9a4d4a25528f743cd9d7e7948c697e34ac703b805b288b62ad01563488fb206fc34ff05084f7fc5d864be775924b3d0d53ea5dd2
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@lezer/highlight@npm:^1.0.0":
|
||||||
|
version: 1.2.0
|
||||||
|
resolution: "@lezer/highlight@npm:1.2.0"
|
||||||
|
dependencies:
|
||||||
|
"@lezer/common": ^1.0.0
|
||||||
|
checksum: 5b9dfe741f95db13f6124cb9556a43011cb8041ecf490be98d44a86b04d926a66e912bcd3a766f6a3d79e064410f1a2f60ab240b50b645a12c56987bf4870086
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@lezer/lr@npm:^1.0.0":
|
||||||
|
version: 1.4.0
|
||||||
|
resolution: "@lezer/lr@npm:1.4.0"
|
||||||
|
dependencies:
|
||||||
|
"@lezer/common": ^1.0.0
|
||||||
|
checksum: 4c8517017e9803415c6c5cb8230d8764107eafd7d0b847676cd1023abb863a4b268d0d01c7ce3cf1702c4749527c68f0a26b07c329cb7b68c36ed88362d7b193
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@material-ui/core@npm:4.12.4":
|
"@material-ui/core@npm:4.12.4":
|
||||||
version: 4.12.4
|
version: 4.12.4
|
||||||
resolution: "@material-ui/core@npm:4.12.4"
|
resolution: "@material-ui/core@npm:4.12.4"
|
||||||
@ -6147,10 +6269,18 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"codemirror@npm:^5.59.2":
|
"codemirror@npm:^6.0.1":
|
||||||
version: 5.65.13
|
version: 6.0.1
|
||||||
resolution: "codemirror@npm:5.65.13"
|
resolution: "codemirror@npm:6.0.1"
|
||||||
checksum: 47060461edaebecd03b3fba4e73a30cdccc0c51ce3a3a05bafae3c9cafd682101383e94d77d54081eaf1ae18da5b74343e98343c637c52cea409956469039098
|
dependencies:
|
||||||
|
"@codemirror/autocomplete": ^6.0.0
|
||||||
|
"@codemirror/commands": ^6.0.0
|
||||||
|
"@codemirror/language": ^6.0.0
|
||||||
|
"@codemirror/lint": ^6.0.0
|
||||||
|
"@codemirror/search": ^6.0.0
|
||||||
|
"@codemirror/state": ^6.0.0
|
||||||
|
"@codemirror/view": ^6.0.0
|
||||||
|
checksum: 1a78f7077ac5801bdbff162aa0c61bf2b974603c7e9a477198c3ce50c789af674a061d7c293c58b73807eda345c2b5228c38ad2aabb9319d552d5486f785cbef
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@ -6504,6 +6634,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"crelt@npm:^1.0.5":
|
||||||
|
version: 1.0.6
|
||||||
|
resolution: "crelt@npm:1.0.6"
|
||||||
|
checksum: dad842093371ad702afbc0531bfca2b0a8dd920b23a42f26e66dabbed9aad9acd5b9030496359545ef3937c3aced0fd4ac39f7a2d280a23ddf9eb7fdcb94a69f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"cross-env@npm:^7.0.3":
|
"cross-env@npm:^7.0.3":
|
||||||
version: 7.0.3
|
version: 7.0.3
|
||||||
resolution: "cross-env@npm:7.0.3"
|
resolution: "cross-env@npm:7.0.3"
|
||||||
@ -14809,6 +14946,7 @@ __metadata:
|
|||||||
"@babel/preset-env": ^7.10.2
|
"@babel/preset-env": ^7.10.2
|
||||||
"@babel/preset-react": ^7.12.13
|
"@babel/preset-react": ^7.12.13
|
||||||
"@babel/preset-typescript": ^7.22.5
|
"@babel/preset-typescript": ^7.22.5
|
||||||
|
"@codemirror/lang-sql": ^6.5.5
|
||||||
"@date-io/core": ^1.3.6
|
"@date-io/core": ^1.3.6
|
||||||
"@date-io/date-fns": 1.x
|
"@date-io/date-fns": 1.x
|
||||||
"@emotion/core": ^10.0.14
|
"@emotion/core": ^10.0.14
|
||||||
@ -14852,7 +14990,7 @@ __metadata:
|
|||||||
chartjs-plugin-zoom: ^2.0.1
|
chartjs-plugin-zoom: ^2.0.1
|
||||||
classnames: ^2.2.6
|
classnames: ^2.2.6
|
||||||
closest: ^0.0.1
|
closest: ^0.0.1
|
||||||
codemirror: ^5.59.2
|
codemirror: ^6.0.1
|
||||||
convert-units: ^2.3.4
|
convert-units: ^2.3.4
|
||||||
copy-webpack-plugin: ^11.0.0
|
copy-webpack-plugin: ^11.0.0
|
||||||
cross-env: ^7.0.3
|
cross-env: ^7.0.3
|
||||||
@ -15888,6 +16026,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"style-mod@npm:^4.0.0, style-mod@npm:^4.1.0":
|
||||||
|
version: 4.1.0
|
||||||
|
resolution: "style-mod@npm:4.1.0"
|
||||||
|
checksum: 8402b14ca11113a3640d46b3cf7ba49f05452df7846bc5185a3535d9b6a64a3019e7fb636b59ccbb7816aeb0725b24723e77a85b05612a9360e419958e13b4e6
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"style-to-js@npm:1.1.9":
|
"style-to-js@npm:1.1.9":
|
||||||
version: 1.1.9
|
version: 1.1.9
|
||||||
resolution: "style-to-js@npm:1.1.9"
|
resolution: "style-to-js@npm:1.1.9"
|
||||||
@ -16916,6 +17061,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"w3c-keyname@npm:^2.2.4":
|
||||||
|
version: 2.2.8
|
||||||
|
resolution: "w3c-keyname@npm:2.2.8"
|
||||||
|
checksum: 95bafa4c04fa2f685a86ca1000069c1ec43ace1f8776c10f226a73296caeddd83f893db885c2c220ebeb6c52d424e3b54d7c0c1e963bbf204038ff1a944fbb07
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"w3c-xmlserializer@npm:^4.0.0":
|
"w3c-xmlserializer@npm:^4.0.0":
|
||||||
version: 4.0.0
|
version: 4.0.0
|
||||||
resolution: "w3c-xmlserializer@npm:4.0.0"
|
resolution: "w3c-xmlserializer@npm:4.0.0"
|
||||||
|
Loading…
Reference in New Issue
Block a user