mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2024-11-22 00:37:36 -06:00
Upgrade CodeMirror from version 5 to 6. #7097
This commit is contained in:
parent
721290b1e9
commit
d3ede3151a
@ -76,6 +76,7 @@
|
||||
"dependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "^7.10.4",
|
||||
"@babel/preset-react": "^7.12.13",
|
||||
"@codemirror/lang-sql": "^6.5.5",
|
||||
"@date-io/core": "^1.3.6",
|
||||
"@date-io/date-fns": "1.x",
|
||||
"@emotion/sheet": "^1.0.1",
|
||||
@ -102,7 +103,7 @@
|
||||
"chartjs-plugin-zoom": "^2.0.1",
|
||||
"classnames": "^2.2.6",
|
||||
"closest": "^0.0.1",
|
||||
"codemirror": "^5.59.2",
|
||||
"codemirror": "^6.0.1",
|
||||
"convert-units": "^2.3.4",
|
||||
"cssnano": "^5.0.2",
|
||||
"dagre": "^0.8.4",
|
||||
@ -174,6 +175,7 @@
|
||||
"bundle": "cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=2048 yarn run bundle:dev",
|
||||
"test:js-once": "yarn run linter && yarn run jest --maxWorkers=50%",
|
||||
"test:js": "yarn run test:js-once --watch",
|
||||
"test:js-file": "yarn run test:js-once -t",
|
||||
"test:js-coverage": "yarn run test:js-once --collect-coverage",
|
||||
"test:feature": "yarn run bundle && python regression/runtests.py --pkg feature_tests",
|
||||
"test": "yarn run test:js-once && yarn run bundle && python regression/runtests.py",
|
||||
|
@ -1,6 +1,3 @@
|
||||
div[role=tabpanel] > .pgadmin-control-group.form-group.c.jscexceptions {
|
||||
min-height: 400px;
|
||||
}
|
||||
div[role=tabpanel] >.pgadmin-control-group.jstcode .CodeMirror-sizer{
|
||||
height: 400px;
|
||||
}
|
||||
|
@ -17,17 +17,14 @@ import usePreferences, { setupPreferenceBroadcast } from '../../../preferences/s
|
||||
import checkNodeVisibility from '../../../static/js/check_node_visibility';
|
||||
|
||||
define('pgadmin.browser', [
|
||||
'sources/gettext', 'sources/url_for', 'sources/pgadmin', 'bundled_codemirror',
|
||||
'sources/gettext', 'sources/url_for', 'sources/pgadmin',
|
||||
'sources/csrf', 'pgadmin.authenticate.kerberos',
|
||||
'pgadmin.browser.utils', 'pgadmin.browser.messages',
|
||||
'pgadmin.browser.node', 'pgadmin.browser.collection', 'pgadmin.browser.activity',
|
||||
'sources/codemirror/addon/fold/pgadmin-sqlfoldcode',
|
||||
'pgadmin.browser.keyboard', 'sources/tree/pgadmin_tree_save_state',
|
||||
], function(
|
||||
gettext, url_for, pgAdmin, codemirror, csrfToken, Kerberos,
|
||||
gettext, url_for, pgAdmin, csrfToken, Kerberos,
|
||||
) {
|
||||
let CodeMirror = codemirror.default;
|
||||
|
||||
let pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
let select_object_msg = gettext('Please select an object in the tree view.');
|
||||
|
||||
@ -1766,13 +1763,6 @@ define('pgadmin.browser', [
|
||||
},
|
||||
});
|
||||
|
||||
/* Remove paste event mapping from CodeMirror's emacsy KeyMap binding
|
||||
* specific to Mac LineNumber:5797 - lib/Codemirror.js
|
||||
* It is preventing default paste event(Cmd-V) from triggering
|
||||
* in runtime.
|
||||
*/
|
||||
delete CodeMirror.keyMap.emacsy['Ctrl-V'];
|
||||
|
||||
// Use spaces instead of tab
|
||||
if (pgBrowser.utils.useSpaces == 'True') {
|
||||
pgAdmin.Browser.editor_shortcut_keys.Tab = 'insertSoftTab';
|
||||
|
@ -7,8 +7,7 @@
|
||||
code,
|
||||
kbd,
|
||||
pre,
|
||||
samp,
|
||||
.CodeMirror pre {
|
||||
samp {
|
||||
font-family: $font-family-editor !important;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
{% if file_name is not defined %}
|
||||
{% set file_name=node_type %}
|
||||
{% endif %}
|
||||
.icon-{{file_name}} {
|
||||
.icon-{{file_name}}, .cm-autocomplete-option-{{file_name}} .cm-completionIcon {
|
||||
background-image: url('{{ url_for('NODE-%s.static' % node_type, filename='img/%s.svg' % file_name )}}') !important;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 20px !important;
|
||||
|
@ -16,7 +16,7 @@ window.hookConsole = function(callback) {
|
||||
}
|
||||
try {
|
||||
require(
|
||||
['sources/generated/app.bundle', 'sources/generated/codemirror', 'sources/generated/browser_nodes'],
|
||||
['sources/generated/app.bundle', 'sources/generated/browser_nodes'],
|
||||
function() {
|
||||
},
|
||||
function() {
|
||||
|
@ -35,14 +35,6 @@
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
#server_activity .CodeMirror,
|
||||
#database_activity .CodeMirror,
|
||||
#server_activity .CodeMirror-scroll,
|
||||
#database_activity .CodeMirror-scroll {
|
||||
height: auto;
|
||||
max-height:100px;
|
||||
}
|
||||
|
||||
.dashboard-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import gettext from 'sources/gettext';
|
||||
import PropTypes from 'prop-types';
|
||||
import getApiInstance from 'sources/api_instance';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import CodeMirror from '../../../../static/js/components/CodeMirror';
|
||||
import CodeMirror from '../../../../static/js/components/ReactCodeMirror';
|
||||
import Loader from 'sources/components/Loader';
|
||||
import withStandardTabInfo from '../../../../static/js/helpers/withStandardTabInfo';
|
||||
import { BROWSER_PANELS } from '../../../../browser/static/js/constants';
|
||||
@ -24,7 +24,6 @@ const useStyles = makeStyles((theme) => ({
|
||||
height: '100% !important',
|
||||
width: '100% !important',
|
||||
background: theme.palette.grey[400],
|
||||
overflow: 'auto !important',
|
||||
minHeight: '100%',
|
||||
minWidth: '100%',
|
||||
},
|
||||
@ -99,10 +98,7 @@ function SQL({nodeData, node, treeNodeInfo, isActive, isStale, setIsStale}) {
|
||||
className={classes.textArea}
|
||||
value={nodeSQL}
|
||||
readonly={true}
|
||||
options={{
|
||||
lineNumbers: true,
|
||||
mode: 'text/x-pgsql',
|
||||
}}
|
||||
showCopyBtn
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -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/leaflet/dist/leaflet.css';
|
||||
|
||||
@import 'node_modules/codemirror/lib/codemirror.css';
|
||||
@import 'node_modules/codemirror/addon/dialog/dialog.css';
|
||||
@import 'node_modules/codemirror/addon/scroll/simplescrollbars.css';
|
||||
|
||||
@import 'node_modules/xterm/css/xterm.css';
|
||||
|
||||
@import 'node_modules/jsoneditor/dist/jsoneditor.min.css';
|
||||
|
@ -111,7 +111,27 @@ export default function(basicSettings) {
|
||||
diffColorFg: '#d4d4d4',
|
||||
diffSelectFG: '#d4d4d4',
|
||||
diffSelCheckbox: '#323E43'
|
||||
}
|
||||
},
|
||||
editor: {
|
||||
fg: '#fff',
|
||||
bg: '#212121',
|
||||
selectionBg: '#536270',
|
||||
keyword: '#db7c74',
|
||||
number: '#7fcc5c',
|
||||
string: '#e4e487',
|
||||
variable: '#7dc9f1',
|
||||
type: '#7dc9f1',
|
||||
comment: '#7fcc5c',
|
||||
punctuation: '#d6aaaa',
|
||||
operator: '#d6aaaa',
|
||||
////
|
||||
foldmarker: '#0000FF',
|
||||
activeline: '#323e43',
|
||||
activelineLight: '#323e43',
|
||||
activelineBorderColor: 'none',
|
||||
guttersBg: '#303030',
|
||||
guttersFg: '#8A8A8A',
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -109,7 +109,27 @@ export default function(basicSettings) {
|
||||
diffColorFg: '#FFFFFF',
|
||||
diffSelectFG: '#010B15',
|
||||
diffSelCheckbox: '#010b15',
|
||||
}
|
||||
},
|
||||
editor: {
|
||||
fg: '#fff',
|
||||
bg: '#010B15',
|
||||
selectionBg: '#1F2932',
|
||||
keyword: '#F8845F',
|
||||
number: '#45D48A',
|
||||
string: '#EAEA43',
|
||||
variable: '#7DC9F1',
|
||||
type: '#7DC9F1',
|
||||
comment: '#FFAD65',
|
||||
punctuation: '#d6aaaa',
|
||||
operator: '#d6aaaa',
|
||||
////
|
||||
foldmarker: '#FFFFFF',
|
||||
activeline: '#063057',
|
||||
activelineLight: '#063057',
|
||||
activelineBorderColor: 'none',
|
||||
guttersBg: '#2d3a48',
|
||||
guttersFg: '#8b9cac',
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import { CssBaseline } from '@material-ui/core';
|
||||
import pickrOverride from './overrides/pickr.override';
|
||||
import uplotOverride from './overrides/uplot.override';
|
||||
import rcdockOverride from './overrides/rcdock.override';
|
||||
import cmOverride from './overrides/codemirror.override';
|
||||
|
||||
/* Common settings across all themes */
|
||||
let basicSettings = createTheme();
|
||||
@ -333,6 +334,7 @@ function getFinalTheme(baseTheme) {
|
||||
...pickrOverride(baseTheme),
|
||||
...uplotOverride(baseTheme),
|
||||
...rcdockOverride(baseTheme),
|
||||
...cmOverride(baseTheme)
|
||||
},
|
||||
},
|
||||
MuiOutlinedInput: {
|
||||
|
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',
|
||||
diffSelectFG: '#222',
|
||||
diffSelCheckbox: '#d6effc'
|
||||
}
|
||||
},
|
||||
editor: {
|
||||
fg: '#222',
|
||||
bg: '#fff',
|
||||
selectionBg: '#d6effc',
|
||||
keyword: '#908',
|
||||
number: '#964',
|
||||
string: '#a11',
|
||||
variable: '#222',
|
||||
type: '#05a',
|
||||
comment: '#a50',
|
||||
punctuation: '#737373',
|
||||
operator: '#222',
|
||||
////
|
||||
foldmarker: '#0000FF',
|
||||
activeline: '#EDF9FF',
|
||||
activelineLight: '#EDF9FF',
|
||||
activelineBorderColor: '#BCDEF3',
|
||||
guttersBg: '#f3f5f9',
|
||||
guttersFg: '#848ea0',
|
||||
},
|
||||
treeBgSelected: '#d6effc',
|
||||
treeFgSelected: '#222',
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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 * as DateFns from 'date-fns';
|
||||
|
||||
import CodeMirror from './CodeMirror';
|
||||
import CodeMirror from './ReactCodeMirror';
|
||||
import gettext from 'sources/gettext';
|
||||
import _ from 'lodash';
|
||||
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 */
|
||||
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 cid = testcid || _.uniqueId('c');
|
||||
const helpid = `h${cid}`;
|
||||
@ -135,7 +135,7 @@ export function FormInput({ children, error, className, label, helpMessage, requ
|
||||
return (
|
||||
<>
|
||||
<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}
|
||||
<FormIcon type={MESSAGE_TYPE.ERROR} style={{ marginLeft: 'auto', visibility: error ? 'unset' : 'hidden' }} />
|
||||
</InputLabel>
|
||||
@ -152,7 +152,7 @@ export function FormInput({ children, error, className, label, helpMessage, requ
|
||||
return (
|
||||
<Grid container spacing={0} className={className} data-testid="form-input">
|
||||
<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}
|
||||
<FormIcon type={MESSAGE_TYPE.ERROR} style={{ marginLeft: 'auto', visibility: error ? 'unset' : 'hidden' }} />
|
||||
</InputLabel>
|
||||
@ -174,6 +174,7 @@ FormInput.propTypes = {
|
||||
helpMessage: PropTypes.string,
|
||||
required: PropTypes.bool,
|
||||
testcid: PropTypes.any,
|
||||
lid: PropTypes.any,
|
||||
withContainer: PropTypes.bool,
|
||||
labelGridBasis: PropTypes.number,
|
||||
controlGridBasis: PropTypes.number,
|
||||
@ -191,16 +192,10 @@ export function InputSQL({ value, options, onChange, className, controlProps, in
|
||||
}}
|
||||
value={value || ''}
|
||||
options={{
|
||||
lineNumbers: true,
|
||||
mode: 'text/x-pgsql',
|
||||
...options,
|
||||
}}
|
||||
className={clsx(classes.sql, className)}
|
||||
events={{
|
||||
change: (cm) => {
|
||||
onChange?.(cm.getValue());
|
||||
},
|
||||
}}
|
||||
onChange={onChange}
|
||||
{...controlProps}
|
||||
{...props}
|
||||
/>
|
||||
@ -220,9 +215,10 @@ export function FormInputSQL({ hasError, required, label, className, helpMessage
|
||||
if (noLabel) {
|
||||
return <InputSQL value={value} options={controlProps} {...props} />;
|
||||
} else {
|
||||
const lid = _.uniqueId('l');
|
||||
return (
|
||||
<FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid} >
|
||||
<InputSQL value={value} options={controlProps} {...props} />
|
||||
<FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid} lid={lid}>
|
||||
<InputSQL value={value} options={controlProps} labelledBy={lid} {...props} />
|
||||
</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 {
|
||||
height: 100%;
|
||||
& .CodeMirror-scroll {
|
||||
min-height: 120px;
|
||||
max-height: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.dataview_filter_dialog {
|
||||
|
@ -17,7 +17,6 @@ $theme-colors: (
|
||||
--psql-selection: #{$psql-selection};
|
||||
}
|
||||
|
||||
@import 'codemirror.overrides';
|
||||
@import 'pgadmin.style';
|
||||
@import 'jsoneditor.overrides';
|
||||
@import 'rc-dock/dist/rc-dock.css';
|
||||
|
@ -114,13 +114,13 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
||||
url: baseUrl,
|
||||
method: 'POST',
|
||||
data: {
|
||||
'breakpoint_list': breakpoint_list.lenght > 0 ? breakpoint_list.join() : null,
|
||||
'breakpoint_list': breakpoint_list.length > 0 ? breakpoint_list.join() : null,
|
||||
},
|
||||
})
|
||||
.then(function (res) {
|
||||
if (res.data.data.status) {
|
||||
executeQuery(transId);
|
||||
setUnsetBreakpoint(res, breakpoint_list);
|
||||
editor.current.clearBreakpoints();
|
||||
}
|
||||
enableToolbarButtons();
|
||||
})
|
||||
@ -158,7 +158,8 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
||||
}
|
||||
};
|
||||
|
||||
const raisePollingError = () => {
|
||||
const raisePollingError = (error) => {
|
||||
console.error(error);
|
||||
pgAdmin.Browser.notifier.alert(
|
||||
gettext('Debugger Error'),
|
||||
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);
|
||||
|
||||
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 ....
|
||||
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(
|
||||
gettext('Debugger Error'),
|
||||
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) => {
|
||||
// Make ajax call to listen the database message
|
||||
let baseUrl = url_for('debugger.select_frame', {
|
||||
@ -324,7 +301,7 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
||||
if (res.data.data.status) {
|
||||
editor.current.setValue(res.data.data.result[0].src);
|
||||
updateBreakpoint(params.transId, true);
|
||||
setActiveLine(res.data.data.result[0].linenumber - 2);
|
||||
editor.current.setActiveLine(res.data.data.result[0].linenumber - 1);
|
||||
}
|
||||
})
|
||||
.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 = () => {
|
||||
let clearBreakpoint = (br_list) => {
|
||||
// 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(),
|
||||
},
|
||||
})
|
||||
.then(function (res) {
|
||||
setUnsetBreakpoint(res, breakpoint_list);
|
||||
.then(function () {
|
||||
editor.current.clearBreakpoints();
|
||||
enableToolbarButtons();
|
||||
})
|
||||
.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 = () => {
|
||||
disableToolbarButtons();
|
||||
let info = editor.current.lineInfo(editor.current.activeLineNo);
|
||||
let baseUrl = '';
|
||||
|
||||
// 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 lineNo = editor.current.getActiveLine();
|
||||
editor.current.toggleBreakpoint(lineNo);
|
||||
enableToolbarButtons();
|
||||
};
|
||||
|
||||
const stopDebugging = () => {
|
||||
@ -522,7 +433,7 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
||||
.then(function (res) {
|
||||
if (res.data.data.status) {
|
||||
// Remove active time in the editor
|
||||
setActiveLine(-1);
|
||||
editor.current.setActiveLine(-1);
|
||||
// Clear timeout on stop debugger.
|
||||
clearTimeout(timeOut);
|
||||
|
||||
@ -628,7 +539,7 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
||||
|
||||
const pollEndExecuteError = (res) => {
|
||||
params.directDebugger.direct_execution_completed = true;
|
||||
setActiveLine(-1);
|
||||
editor.current.setActiveLine(-1);
|
||||
|
||||
//Set the notification message to inform the user that execution is
|
||||
// completed with error.
|
||||
@ -654,7 +565,7 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
||||
|
||||
const updateResultAndMessages = (res) => {
|
||||
if (res.data.data.result != null) {
|
||||
setActiveLine(-1);
|
||||
editor.current.setActiveLine(-1);
|
||||
// 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.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
|
||||
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.polling_timeout_idle = true;
|
||||
|
||||
@ -852,26 +763,9 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
||||
return breakpoint_list;
|
||||
};
|
||||
|
||||
// Function to get the latest breakpoint information and update the
|
||||
// gutters of codemirror
|
||||
// Function to get the latest breakpoint information
|
||||
const updateBreakpoint = (transId, updateLocalVar = false) => {
|
||||
let callBackFunc = (br_list) => {
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
let callBackFunc = () => {
|
||||
if (updateLocalVar) {
|
||||
// Call function to create and update local variables ....
|
||||
getLocalVariables(params.transId);
|
||||
@ -976,7 +870,7 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
|
||||
const updateInfo = (res, transId) => {
|
||||
if (!params.directDebugger.debug_type && !params.directDebugger.first_time_indirect_debug) {
|
||||
setLoaderText('');
|
||||
setActiveLine(-1);
|
||||
editor.current.setActiveLine(-1);
|
||||
clearAllBreakpoint(transId);
|
||||
|
||||
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
|
||||
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
|
||||
setTimeout(function () {
|
||||
getStackInformation(transId);
|
||||
|
@ -16,7 +16,7 @@ import gettext from 'sources/gettext';
|
||||
import url_for from 'sources/url_for';
|
||||
|
||||
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 { DebuggerEventsContext } from './DebuggerComponent';
|
||||
import { usePgAdmin } from '../../../../../static/js/BrowserComponent';
|
||||
@ -36,13 +36,6 @@ export default function DebuggerEditor({ getEditor, params }) {
|
||||
|
||||
const api = getApiInstance();
|
||||
|
||||
function makeMarker() {
|
||||
let marker = document.createElement('div');
|
||||
marker.style.color = '#822';
|
||||
marker.innerHTML = '●';
|
||||
return marker;
|
||||
}
|
||||
|
||||
function setBreakpoint(lineNo, setType) {
|
||||
// Make ajax call to set/clear the break point by user
|
||||
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) => {
|
||||
focus && editor.current?.focus();
|
||||
editor.current?.setValue(value);
|
||||
@ -99,19 +67,21 @@ export default function DebuggerEditor({ getEditor, params }) {
|
||||
|
||||
useEffect(() => {
|
||||
self = this;
|
||||
// Register the callback when user set/clear the breakpoint on gutter area.
|
||||
editor.current.on('gutterClick', onBreakPoint);
|
||||
getEditor(editor.current);
|
||||
}, [editor.current]);
|
||||
|
||||
return (
|
||||
<CodeMirror
|
||||
currEditor={(obj) => {
|
||||
editor.current = obj;
|
||||
}}
|
||||
gutters={['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'breakpoints']}
|
||||
value={''}
|
||||
onBreakPointChange={(line, on)=>{
|
||||
setBreakpoint(line, on ? 1 : 0);
|
||||
}}
|
||||
className={classes.sql}
|
||||
disabled={true}
|
||||
readonly={true}
|
||||
breakpoint
|
||||
/>);
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
try {
|
||||
require(
|
||||
['sources/generated/debugger', 'sources/pgadmin', 'sources/generated/codemirror'],
|
||||
['sources/generated/debugger', 'sources/pgadmin'],
|
||||
function(pgDirectDebug, pgAdmin) {
|
||||
var pgDebug = window.pgAdmin.Tools.Debugger;
|
||||
pgDebug.load(document.getElementById('debugger-main-container'), {{ uniqueId }}, {{ debug_type }}, '{{ function_name_with_arguments }}', '{{layout|safe}}');
|
||||
|
@ -29,7 +29,7 @@
|
||||
{% block init_script %}
|
||||
try {
|
||||
require(
|
||||
['sources/generated/browser_nodes', 'sources/generated/codemirror'],
|
||||
['sources/generated/browser_nodes'],
|
||||
function() {
|
||||
require(['sources/generated/erd_tool'], function(module) {
|
||||
window.pgAdmin.Tools.ERD.loadComponent(
|
||||
|
@ -108,17 +108,6 @@
|
||||
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 {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% block init_script %}
|
||||
try {
|
||||
require(
|
||||
['sources/generated/codemirror', 'sources/generated/browser_nodes', 'sources/generated/schema_diff'],
|
||||
['sources/generated/browser_nodes', 'sources/generated/schema_diff'],
|
||||
function() {
|
||||
var pgSchemaDiff = window.pgAdmin.Tools.SchemaDiff;
|
||||
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 { format } from 'sql-formatter';
|
||||
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 url_for from 'sources/url_for';
|
||||
import { LayoutDockerContext, LAYOUT_EVENTS } from '../../../../../../static/js/helpers/Layout';
|
||||
import ConfirmSaveContent from '../../../../../../static/js/Dialogs/ConfirmSaveContent';
|
||||
import gettext from 'sources/gettext';
|
||||
import OrigCodeMirror from 'bundled_codemirror';
|
||||
import { isMac } from '../../../../../../static/js/keyboard_shortcuts';
|
||||
import { checkTrojanSource } from '../../../../../../static/js/utils';
|
||||
import { parseApiError } from '../../../../../../static/js/api_instance';
|
||||
@ -32,211 +31,32 @@ const useStyles = makeStyles(()=>({
|
||||
}
|
||||
}));
|
||||
|
||||
function registerAutocomplete(api, transId, sqlEditorPref, onFailure) {
|
||||
let timeoutId;
|
||||
let loadingEle;
|
||||
let prevSearch = null;
|
||||
OrigCodeMirror.registerHelper('hint', 'sql', function (editor) {
|
||||
let data = [],
|
||||
doc = editor.getDoc(),
|
||||
cur = doc.getCursor(),
|
||||
// function context
|
||||
ctx = {
|
||||
editor: editor,
|
||||
// URL for auto-complete
|
||||
url: url_for('sqleditor.autocomplete', {
|
||||
'trans_id': transId,
|
||||
}),
|
||||
data: data,
|
||||
// Get the line number in the cursor position
|
||||
current_line: cur.line,
|
||||
/*
|
||||
* 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);
|
||||
async function registerAutocomplete(editor, api, transId) {
|
||||
editor.registerAutocomplete((context, onAvailable)=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
const url = url_for('sqleditor.autocomplete', {
|
||||
'trans_id': transId,
|
||||
});
|
||||
const word = context.matchBefore(/\w*/);
|
||||
const fullSql = context.state.doc.toString();
|
||||
api.post(url, JSON.stringify([fullSql, fullSql]))
|
||||
.then((res) => {
|
||||
onAvailable();
|
||||
resolve({
|
||||
from: word.from,
|
||||
options: Object.keys(res.data.data.result).map((key)=>({
|
||||
label: key, type: res.data.data.result[key].object_type
|
||||
})),
|
||||
validFor: (text, from)=>{
|
||||
return text.startsWith(fullSql.slice(from));
|
||||
}
|
||||
});
|
||||
|
||||
cb({
|
||||
list: filterData,
|
||||
from: {
|
||||
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),
|
||||
};
|
||||
})
|
||||
.catch((err) => {
|
||||
onAvailable();
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -247,31 +67,22 @@ export default function Query() {
|
||||
const queryToolCtx = useContext(QueryToolContext);
|
||||
const layoutDocker = useContext(LayoutDockerContext);
|
||||
const lastCursorPos = React.useRef();
|
||||
const lastSavedText = React.useRef('');
|
||||
const markedLine = React.useRef(0);
|
||||
const marker = React.useRef();
|
||||
const pgAdmin = usePgAdmin();
|
||||
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})=>{
|
||||
let errorLineNo = 0,
|
||||
startMarker = 0,
|
||||
endMarker = 0,
|
||||
selectedLineNo = 0,
|
||||
selectedLineNo = 1,
|
||||
origQueryLen = cmObj.getValue().length;
|
||||
|
||||
removeHighlightError(cmObj);
|
||||
cmObj.removeErrorMark();
|
||||
|
||||
// In case of selection we need to find the actual line no
|
||||
if (cmObj.getSelection().length > 0) {
|
||||
selectedLineNo = cmObj.getCursor(true).line;
|
||||
origQueryLen = cmObj.getLine(selectedLineNo).length;
|
||||
selectedLineNo = cmObj.getCurrentLineNo();
|
||||
origQueryLen = cmObj.line(selectedLineNo).length;
|
||||
}
|
||||
|
||||
// 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 != null && char != null) {
|
||||
errorLineNo = (parseInt(line[1]) - 1) + selectedLineNo;
|
||||
let errorCharNo = (parseInt(char[1]) - 1);
|
||||
errorLineNo = parseInt(line[1]) + selectedLineNo - 1;
|
||||
let errorCharNo = parseInt(char[1]) - 1;
|
||||
|
||||
/* If explain query has been run we need to
|
||||
calculate the character number.
|
||||
@ -298,8 +109,8 @@ export default function Query() {
|
||||
* have also added 1 per line for the "\n" character.
|
||||
*/
|
||||
let prevLineChars = 0;
|
||||
for (let i = selectedLineNo > 0 ? selectedLineNo : 0; i < errorLineNo; i++)
|
||||
prevLineChars += cmObj.getLine(i).length + 1;
|
||||
for (let i = selectedLineNo; i < errorLineNo; i++)
|
||||
prevLineChars += cmObj.getLine(i).length;
|
||||
|
||||
/* Marker starting point for the individual line is
|
||||
* equal to error character index minus total no of
|
||||
@ -316,18 +127,14 @@ export default function Query() {
|
||||
endMarker = errorLine.length;
|
||||
|
||||
// Mark the error text
|
||||
marker.current = cmObj.markText({
|
||||
cmObj.setErrorMark({
|
||||
line: errorLineNo,
|
||||
ch: startMarker,
|
||||
pos: startMarker,
|
||||
}, {
|
||||
line: errorLineNo,
|
||||
ch: endMarker,
|
||||
}, {
|
||||
className: 'sql-editor-mark',
|
||||
pos: endMarker,
|
||||
});
|
||||
|
||||
markedLine.current = errorLineNo;
|
||||
cmObj.addLineClass(errorLineNo, 'wrap', 'CodeMirror-activeline-background');
|
||||
cmObj.focus();
|
||||
cmObj.setCursor(errorLineNo, endMarker);
|
||||
}
|
||||
@ -364,7 +171,7 @@ export default function Query() {
|
||||
if(result) {
|
||||
highlightError(editor.current, result);
|
||||
} else {
|
||||
removeHighlightError(editor.current);
|
||||
editor.current.removeErrorMark();
|
||||
}
|
||||
});
|
||||
|
||||
@ -381,7 +188,7 @@ export default function Query() {
|
||||
editor.current.setValue(res.data);
|
||||
//Check the file content for Trojan Source
|
||||
checkTrojanSource(res.data);
|
||||
lastSavedText.current = res.data;
|
||||
editor.current.markClean();
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.LOAD_FILE_DONE, fileName, true);
|
||||
}).catch((err)=>{
|
||||
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)=>{
|
||||
let editorValue = editor.current.getValue();
|
||||
queryToolCtx.api.post(url_for('sqleditor.save_file'), {
|
||||
'file_name': decodeURI(fileName),
|
||||
'file_content': editor.current.getValue(),
|
||||
}).then(()=>{
|
||||
lastSavedText.current = editorValue;
|
||||
editor.current.markClean();
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.SAVE_FILE_DONE, fileName, true);
|
||||
pgAdmin.Browser.notifier.success(gettext('File saved successfully.'));
|
||||
}).catch((err)=>{
|
||||
@ -426,16 +232,11 @@ export default function Query() {
|
||||
key.shiftKey = false;
|
||||
key.altKey = replace;
|
||||
}
|
||||
editor.current?.triggerOnKeyDown(
|
||||
new KeyboardEvent('keydown', key)
|
||||
);
|
||||
editor.current?.fireDOMEvent(new KeyboardEvent('keydown', key));
|
||||
});
|
||||
eventBus.registerListener(QUERY_TOOL_EVENTS.EDITOR_SET_SQL, (value, focus=true)=>{
|
||||
focus && editor.current?.focus();
|
||||
if(!queryToolCtx.params.is_query_tool){
|
||||
lastSavedText.current = value;
|
||||
}
|
||||
editor.current?.setValue(value);
|
||||
editor.current?.setValue(value, !queryToolCtx.params.is_query_tool);
|
||||
if (value == '' && editor.current) {
|
||||
editor.current.state.autoCompleteList = [];
|
||||
}
|
||||
@ -500,7 +301,7 @@ export default function Query() {
|
||||
|
||||
useEffect(()=>{
|
||||
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);
|
||||
return;
|
||||
}
|
||||
@ -526,23 +327,20 @@ export default function Query() {
|
||||
}, [queryToolCtx.preferences]);
|
||||
|
||||
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);}
|
||||
);
|
||||
}, [queryToolCtx.params.trans_id]);
|
||||
|
||||
const isDirty = ()=>(lastSavedText.current !== editor.current.getValue());
|
||||
|
||||
const cursorActivity = useCallback(_.debounce((cmObj)=>{
|
||||
const c = cmObj.getCursor();
|
||||
lastCursorPos.current = c;
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.CURSOR_ACTIVITY, [c.line+1, c.ch+1]);
|
||||
const cursorActivity = useCallback(_.debounce((cursor)=>{
|
||||
lastCursorPos.current = cursor;
|
||||
eventBus.fireEvent(QUERY_TOOL_EVENTS.CURSOR_ACTIVITY, [lastCursorPos.current.line, lastCursorPos.current.ch+1]);
|
||||
}, 100), []);
|
||||
|
||||
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){
|
||||
checkViewEditDataPromotion();
|
||||
} else {
|
||||
@ -552,7 +350,7 @@ export default function Query() {
|
||||
}, []);
|
||||
|
||||
const closePromotionWarning = (closeModal)=>{
|
||||
if(isDirty()) {
|
||||
if(editor.current.isDirty()) {
|
||||
editor.current.undo();
|
||||
closeModal?.();
|
||||
}
|
||||
@ -567,7 +365,7 @@ export default function Query() {
|
||||
promoteToQueryTool();
|
||||
let cursor = editor.current.getCursor();
|
||||
editor.current.setValue(editor.current.getValue());
|
||||
editor.current.setCursor(cursor);
|
||||
editor.current.setCursor(cursor.line, cursor.ch);
|
||||
editor.current.focus();
|
||||
let title = getTitle(pgAdmin, queryToolCtx.preferences.browser, null,null,queryToolCtx.params.server_name, queryToolCtx.params.dbname, queryToolCtx.params.user);
|
||||
queryToolCtx.updateTitle(title);
|
||||
@ -599,11 +397,9 @@ export default function Query() {
|
||||
}}
|
||||
value={''}
|
||||
className={classes.sql}
|
||||
events={{
|
||||
'focus': cursorActivity,
|
||||
'cursorActivity': cursorActivity,
|
||||
'change': change,
|
||||
}}
|
||||
onCursorActivity={cursorActivity}
|
||||
onChange={change}
|
||||
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 { SaveDataIcon, CommitIcon, RollbackIcon, ViewDataIcon } from '../../../../../../static/js/components/ExternalIcon';
|
||||
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 { useDelayedCaller } from '../../../../../../static/js/custom_hooks';
|
||||
import Loader from 'sources/components/Loader';
|
||||
|
@ -29,7 +29,7 @@
|
||||
{% block init_script %}
|
||||
try {
|
||||
require(
|
||||
['sources/generated/browser_nodes', 'sources/generated/codemirror'],
|
||||
['sources/generated/browser_nodes'],
|
||||
function() {
|
||||
require(['sources/generated/sqleditor'], function(module) {
|
||||
window.pgAdmin.Tools.SQLEditor.loadComponent(
|
||||
|
@ -172,8 +172,8 @@ class QueryToolAutoCompleteFeatureTest(BaseFeatureTest):
|
||||
ActionChains(self.page.driver).key_down(
|
||||
Keys.CONTROL).send_keys(Keys.SPACE).key_up(
|
||||
Keys.CONTROL).perform()
|
||||
if self.page.check_if_element_exist_by_xpath(
|
||||
QueryToolLocators.code_mirror_hint_box_xpath, 15):
|
||||
if self.page.check_if_element_exist_by_css_selector(
|
||||
QueryToolLocators.code_mirror_hint_box, 15):
|
||||
hint_displayed = True
|
||||
break
|
||||
else:
|
||||
@ -186,12 +186,11 @@ class QueryToolAutoCompleteFeatureTest(BaseFeatureTest):
|
||||
else:
|
||||
# if no IntelliSense is present it means there is only one option
|
||||
# so check if required string is present in codeMirror
|
||||
code_mirror = self.driver.find_elements(
|
||||
By.XPATH, QueryToolLocators.code_mirror_data_xpath)
|
||||
for data in code_mirror:
|
||||
code_mirror_text = data.text
|
||||
print("Single entry..........")
|
||||
if expected_string not in code_mirror_text:
|
||||
print("single entry exception.........")
|
||||
raise RuntimeError("Required String %s is not "
|
||||
"present" % expected_string)
|
||||
code_mirror_text = self.driver.find_element(
|
||||
By.CSS_SELECTOR, QueryToolLocators.code_mirror_content
|
||||
.format('#id-query')).text
|
||||
|
||||
if expected_string not in code_mirror_text:
|
||||
print("single entry exception.........")
|
||||
raise RuntimeError("Required String %s is not "
|
||||
"present" % expected_string)
|
||||
|
@ -46,7 +46,7 @@ class TableDdlFeatureTest(BaseFeatureTest):
|
||||
|
||||
# Wait till data is displayed in SQL Tab
|
||||
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')]"
|
||||
% self.test_table_name, 10), "No data displayed in SQL tab")
|
||||
|
||||
|
@ -51,18 +51,14 @@ class CopySQLFeatureTest(BaseFeatureTest):
|
||||
self.page.click_tab("SQL")
|
||||
# Wait till data is displayed in SQL Tab
|
||||
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')]"
|
||||
% self.test_table_name, 10), "No data displayed in SQL tab")
|
||||
|
||||
# Fetch the inner html & check for escaped characters
|
||||
source_code = self.driver.find_elements(
|
||||
By.XPATH, QueryToolLocators.code_mirror_data_xpath)
|
||||
|
||||
sql_query = ''
|
||||
for data in source_code:
|
||||
sql_query += data.text
|
||||
sql_query += '\n'
|
||||
sql_query = self.driver.find_element(
|
||||
By.CSS_SELECTOR, QueryToolLocators.code_mirror_content
|
||||
.format('#id-sql')).text
|
||||
|
||||
return sql_query
|
||||
|
||||
@ -76,12 +72,9 @@ class CopySQLFeatureTest(BaseFeatureTest):
|
||||
self.driver.switch_to.frame(
|
||||
self.driver.find_element(By.TAG_NAME, "iframe"))
|
||||
|
||||
code_mirror = self.driver.find_elements(
|
||||
By.XPATH, QueryToolLocators.code_mirror_data_xpath)
|
||||
query_tool_result = ''
|
||||
for data in code_mirror:
|
||||
query_tool_result += data.text
|
||||
query_tool_result += '\n'
|
||||
query_tool_result = self.driver.find_element(
|
||||
By.CSS_SELECTOR, QueryToolLocators.code_mirror_content
|
||||
.format('#id-query')).text
|
||||
|
||||
return query_tool_result
|
||||
|
||||
|
@ -128,12 +128,12 @@ class CheckForXssFeatureTest(BaseFeatureTest):
|
||||
|
||||
# Wait till data is displayed in SQL Tab
|
||||
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")
|
||||
|
||||
# Fetch the inner html & check for escaped characters
|
||||
source_code = self.page.find_by_xpath(
|
||||
"//*[contains(@class,'CodeMirror-lines') and "
|
||||
"//*[contains(@class,'cm-line') and "
|
||||
"contains(.,'CREATE TABLE')]"
|
||||
).get_attribute('innerHTML')
|
||||
|
||||
|
@ -235,12 +235,13 @@ class QueryToolLocators:
|
||||
|
||||
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 = \
|
||||
"//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']"
|
||||
|
||||
|
@ -892,7 +892,8 @@ class PgadminPage:
|
||||
driver.switch_to.frame(
|
||||
driver.find_element(By.TAG_NAME, "iframe"))
|
||||
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():
|
||||
return element
|
||||
except (NoSuchElementException, WebDriverException):
|
||||
@ -933,9 +934,9 @@ class PgadminPage:
|
||||
action.perform()
|
||||
else:
|
||||
self.driver.execute_script(
|
||||
"arguments[0].CodeMirror.setValue(arguments[1]);"
|
||||
"arguments[0].CodeMirror.setCursor("
|
||||
"arguments[0].CodeMirror.lineCount(),0);",
|
||||
"arguments[0].cmView.view.setValue(arguments[1]);"
|
||||
"arguments[0].cmView.view.setCursor("
|
||||
"arguments[0].cmView.view.lineCount(),0);",
|
||||
codemirror_ele, field_content)
|
||||
|
||||
def click_tab(self, tab_name):
|
||||
|
@ -168,20 +168,20 @@ describe('SchemaView', ()=>{
|
||||
|
||||
it('no changes', async ()=>{
|
||||
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 ()=>{
|
||||
await user.clear(ctrl.container.querySelector('[name="field2"]'));
|
||||
await user.type(ctrl.container.querySelector('[name="field2"]'), '2');
|
||||
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 ()=>{
|
||||
await simulateValidData();
|
||||
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 {default as OrigCodeMirror} from 'bundled_codemirror';
|
||||
import { withTheme } from '../fake_theme';
|
||||
|
||||
import pgWindow from 'sources/window';
|
||||
import CodeMirror from 'sources/components/CodeMirror';
|
||||
import { FindDialog } from '../../../pgadmin/static/js/components/CodeMirror';
|
||||
import CodeMirror from 'sources/components/ReactCodeMirror';
|
||||
import FindDialog from 'sources/components/ReactCodeMirror/FindDialog';
|
||||
import CustomEditorView from 'sources/components/ReactCodeMirror/CustomEditorView';
|
||||
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', ()=>{
|
||||
const ThemedCM = withTheme(CodeMirror);
|
||||
let cmInstance, options={
|
||||
lineNumbers: true,
|
||||
mode: 'text/x-pgsql',
|
||||
},
|
||||
cmObj = OrigCodeMirror.fromTextArea();
|
||||
let cmInstance, editor;
|
||||
|
||||
const cmRerender = (props)=>{
|
||||
cmInstance.rerender(
|
||||
<ThemedCM
|
||||
value={'Init text'}
|
||||
options={options}
|
||||
className="testClass"
|
||||
currEditor={(obj) => {
|
||||
editor = obj;
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
beforeEach(()=>{
|
||||
pgWindow.pgAdmin = fakePgAdmin;
|
||||
// jest.spyOn(OrigCodeMirror, 'fromTextArea').mockReturnValue(cmObj);
|
||||
cmInstance = render(
|
||||
<ThemedCM
|
||||
value={'Init text'}
|
||||
options={options}
|
||||
className="testClass"
|
||||
currEditor={(obj) => {
|
||||
editor = obj;
|
||||
}}
|
||||
/>);
|
||||
});
|
||||
|
||||
@ -52,17 +65,23 @@ describe('CodeMirror', ()=>{
|
||||
});
|
||||
|
||||
it('init', async ()=>{
|
||||
/* textarea ref passed to fromTextArea */
|
||||
expect(OrigCodeMirror.fromTextArea).toHaveBeenCalledWith(cmInstance.container.querySelector('textarea'), expect.objectContaining(options));
|
||||
await waitFor(() => expect(cmObj.setValue).toHaveBeenCalledWith('Init text'));
|
||||
expect(CustomEditorView).toHaveBeenCalledTimes(1);
|
||||
expect(editor.setValue).toHaveBeenCalledWith('Init text');
|
||||
});
|
||||
|
||||
it('change value', ()=>{
|
||||
editor.state = {
|
||||
doc: [],
|
||||
};
|
||||
editor.setValue.mockClear();
|
||||
jest.spyOn(editor, 'getValue').mockReturnValue('Init 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});
|
||||
expect(cmObj.setValue).toHaveBeenCalledWith('');
|
||||
expect(editor.setValue).toHaveBeenCalledWith('');
|
||||
});
|
||||
|
||||
|
||||
@ -73,7 +92,7 @@ describe('CodeMirror', ()=>{
|
||||
const ctrlMount = (props)=>{
|
||||
ctrl = render(
|
||||
<ThemedFindDialog
|
||||
editor={cmObj}
|
||||
editor={editor}
|
||||
show={true}
|
||||
onClose={onClose}
|
||||
{...props}
|
||||
@ -84,29 +103,27 @@ describe('CodeMirror', ()=>{
|
||||
it('init', ()=>{
|
||||
ctrlMount({});
|
||||
|
||||
cmObj.removeOverlay.mockClear();
|
||||
cmObj.addOverlay.mockClear();
|
||||
CMSearch.SearchQuery.mockClear();
|
||||
const input = ctrl.container.querySelector('input');
|
||||
|
||||
fireEvent.change(input, {
|
||||
target: {value: '\n\r\tA'},
|
||||
});
|
||||
|
||||
expect(cmObj.removeOverlay).toHaveBeenCalled();
|
||||
expect(cmObj.addOverlay).toHaveBeenCalled();
|
||||
expect(cmObj.setSelection).toHaveBeenCalledWith(3, 14);
|
||||
expect(cmObj.scrollIntoView).toHaveBeenCalled();
|
||||
expect(CMSearch.SearchQuery).toHaveBeenCalledWith(expect.objectContaining({
|
||||
search: expect.stringContaining('A')
|
||||
}));
|
||||
});
|
||||
|
||||
it('escape', ()=>{
|
||||
ctrlMount({});
|
||||
cmObj.removeOverlay.mockClear();
|
||||
CMSearch.closeSearchPanel.mockClear();
|
||||
|
||||
fireEvent.keyDown(ctrl.container.querySelector('input'), {
|
||||
key: 'Escape',
|
||||
});
|
||||
|
||||
expect(cmObj.removeOverlay).toHaveBeenCalled();
|
||||
expect(CMSearch.closeSearchPanel).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('toggle match case', ()=>{
|
||||
@ -132,7 +149,8 @@ describe('CodeMirror', ()=>{
|
||||
|
||||
it('replace', async ()=>{
|
||||
ctrlMount({replace: true});
|
||||
cmObj.getSearchCursor().replace.mockClear();
|
||||
CMSearch.SearchQuery.mockClear();
|
||||
|
||||
fireEvent.change(ctrl.container.querySelectorAll('input')[0], {
|
||||
target: {value: 'A'},
|
||||
});
|
||||
@ -142,9 +160,13 @@ describe('CodeMirror', ()=>{
|
||||
fireEvent.keyPress(ctrl.container.querySelectorAll('input')[1], {
|
||||
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() {};
|
||||
|
||||
// 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
|
||||
|
||||
|
@ -357,7 +357,6 @@ module.exports = [{
|
||||
// Specify entry points of application
|
||||
entry: {
|
||||
'app.bundle': sourceDir + '/bundle/app.js',
|
||||
codemirror: sourceDir + '/bundle/codemirror.js',
|
||||
'security.pages': 'security.pages',
|
||||
sqleditor: './pgadmin/tools/sqleditor/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
|
||||
resolveAlias: {
|
||||
'top': path.join(__dirname, './pgadmin/'),
|
||||
'bundled_codemirror': path.join(__dirname, './pgadmin/static/bundle/codemirror'),
|
||||
'bundled_browser': path.join(__dirname, './pgadmin/static/bundle/browser'),
|
||||
'sources': path.join(__dirname, './pgadmin/static/js/'),
|
||||
'translations': path.join(__dirname, './pgadmin/tools/templates/js/translations'),
|
||||
|
162
web/yarn.lock
162
web/yarn.lock
@ -2354,6 +2354,103 @@ __metadata:
|
||||
languageName: node
|
||||
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":
|
||||
version: 1.3.13
|
||||
resolution: "@date-io/core@npm:1.3.13"
|
||||
@ -3086,6 +3183,31 @@ __metadata:
|
||||
languageName: node
|
||||
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":
|
||||
version: 4.12.4
|
||||
resolution: "@material-ui/core@npm:4.12.4"
|
||||
@ -6147,10 +6269,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"codemirror@npm:^5.59.2":
|
||||
version: 5.65.13
|
||||
resolution: "codemirror@npm:5.65.13"
|
||||
checksum: 47060461edaebecd03b3fba4e73a30cdccc0c51ce3a3a05bafae3c9cafd682101383e94d77d54081eaf1ae18da5b74343e98343c637c52cea409956469039098
|
||||
"codemirror@npm:^6.0.1":
|
||||
version: 6.0.1
|
||||
resolution: "codemirror@npm:6.0.1"
|
||||
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
|
||||
linkType: hard
|
||||
|
||||
@ -6504,6 +6634,13 @@ __metadata:
|
||||
languageName: node
|
||||
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":
|
||||
version: 7.0.3
|
||||
resolution: "cross-env@npm:7.0.3"
|
||||
@ -14809,6 +14946,7 @@ __metadata:
|
||||
"@babel/preset-env": ^7.10.2
|
||||
"@babel/preset-react": ^7.12.13
|
||||
"@babel/preset-typescript": ^7.22.5
|
||||
"@codemirror/lang-sql": ^6.5.5
|
||||
"@date-io/core": ^1.3.6
|
||||
"@date-io/date-fns": 1.x
|
||||
"@emotion/core": ^10.0.14
|
||||
@ -14852,7 +14990,7 @@ __metadata:
|
||||
chartjs-plugin-zoom: ^2.0.1
|
||||
classnames: ^2.2.6
|
||||
closest: ^0.0.1
|
||||
codemirror: ^5.59.2
|
||||
codemirror: ^6.0.1
|
||||
convert-units: ^2.3.4
|
||||
copy-webpack-plugin: ^11.0.0
|
||||
cross-env: ^7.0.3
|
||||
@ -15888,6 +16026,13 @@ __metadata:
|
||||
languageName: node
|
||||
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":
|
||||
version: 1.1.9
|
||||
resolution: "style-to-js@npm:1.1.9"
|
||||
@ -16916,6 +17061,13 @@ __metadata:
|
||||
languageName: node
|
||||
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":
|
||||
version: 4.0.0
|
||||
resolution: "w3c-xmlserializer@npm:4.0.0"
|
||||
|
Loading…
Reference in New Issue
Block a user