From 9fa0e3b8cd5c2843f581f3554c0d1a43547e773e Mon Sep 17 00:00:00 2001 From: Rohit Bhati Date: Mon, 2 Sep 2024 09:30:39 +0530 Subject: [PATCH] Added feature to auto detect the OS end-of-line (EOL) character. --- docs/en_US/images/query_status_bar.png | Bin 21040 -> 43228 bytes docs/en_US/query_tool_toolbar.rst | 465 +++++----- .../ReactCodeMirror/CustomEditorView.js | 18 +- .../ReactCodeMirror/components/Editor.jsx | 821 +++++++++--------- .../ReactCodeMirror/extensions/extraStates.js | 6 + .../js/components/QueryToolConstants.js | 5 +- .../static/js/components/sections/Query.jsx | 10 +- .../js/components/sections/StatusBar.jsx | 286 +++--- 8 files changed, 851 insertions(+), 760 deletions(-) diff --git a/docs/en_US/images/query_status_bar.png b/docs/en_US/images/query_status_bar.png index e885920ba9727f67f2807a4c64317f41bcc64779..44b99a0235e2fce5d2c26edd0acc816731c040fe 100644 GIT binary patch literal 43228 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 literal 21040 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 and - :ref:`View/Edit Data ` tools are actually different operating - modes of the same tool. Some controls will be disabled in either mode. - -.. image:: images/query_toolbar.png - :alt: Query tool toolbar - :align: center - -Hover over an icon in pgAdmin to display a tooltip that describes the icon's -functionality. - -File Options -************ - -.. table:: - :class: longtable - :widths: 1 4 1 - - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - | Icon | Behavior | Shortcut | - +======================+===================================================================================================+================+ - | *Open File* | Click the *Open File* icon to display a previously saved query in the SQL Editor. | Cmd/Ctrl + O | - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - | *Save File* | Click the *Save* icon to perform a quick-save of a previously saved query, or to access the | Cmd/Ctrl + S | - | | *Save* menu: | | - | | | | - | | * Select *Save* to save the selected content of the SQL Editor panel in a file. | | - | | | | - | | * Select *Save As* to open a new browser dialog and specify a new location to which to save the | | - | | selected content of the SQL Editor panel. | | - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - -Filter/Limit Options -******************** - -.. table:: - :class: longtable - :widths: 1 4 1 - - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - | Icon | Behavior | Shortcut | - +======================+===================================================================================================+================+ - | *Filter* | Click the *Filter* icon to set filtering and sorting criteria for the data when in *View/Edit data| Option/Alt + F | - | | mode*. Click the down arrow to access other filtering and sorting options: | | - | | | | - | | * In the *SQL Filter*, you can enter a SQL query as filtering criteria. | | - | | In *Data Sorting*, you can select the column and specify the order for sorting. | | - | | | | - | | * Click *Filter by Selection* to show only the rows containing the values in the selected cells. | | - | | | | - | | * Click *Exclude by Selection* to show only the rows that do not contain the values in the | | - | | selected cells. | | - | | | | - | | * Click *Remove Sort/Filter* to remove any previously selected sort or filtering options. | | - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - | Limit Selector | Select a value in the *Limit Selector* to limit the size of the dataset to a number of rows. | Option/Alt + R | - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - -Query Editing Options -********************* - -.. image:: images/query_editing.png - :alt: Query tool editing options - :align: center - -.. table:: - :class: longtable - :widths: 1 4 1 - - +----------------------+---------------------------------------------------------------------------------------------------+-----------------------+ - | Icon | Behavior | Shortcut | - +======================+===================================================================================================+=======================+ - | *Edit* | Use the *Edit* menu to search, replace, or navigate the code displayed in the SQL Editor: | Option/Alt + Shift + N| - | +---------------------------------------------------------------------------------------------------+-----------------------+ - | | Select *Find* to provide a search target, and search the SQL Editor contents. | Cmd/Ctrl + F | - | +---------------------------------------------------------------------------------------------------+-----------------------+ - | | Select *Replace* to locate and replace (with prompting) individual occurrences of the target. | Option + Cmd + F (MAC)| - | | | Ctrl + Shift + F | - | | | (Others) | - | +---------------------------------------------------------------------------------------------------+-----------------------+ - | | Select *Go to Line/Column* to go to specified line number and column position | Cmd/Ctrl + L | - | +---------------------------------------------------------------------------------------------------+-----------------------+ - | | Select *Indent Selection* to indent the currently selected text. | Tab | - | +---------------------------------------------------------------------------------------------------+-----------------------+ - | | Select *Unindent Selection* to remove indentation from the currently selected text. | Shift + Tab | - | +---------------------------------------------------------------------------------------------------+-----------------------+ - | | Select *Toggle Comment* to comment/uncomment any lines that contain the selection in SQL style. | Cmd/Ctrl + / | - | +---------------------------------------------------------------------------------------------------+-----------------------+ - | | Select *Clear Query* to clear the query editor window. | Option/Alt + Ctrl + L | - | +---------------------------------------------------------------------------------------------------+-----------------------+ - | | Select *Format SQL* to format the selected SQL or all the SQL if none is selected | Cmd/Ctrl + K | - +----------------------+---------------------------------------------------------------------------------------------------+-----------------------+ - -Query Execution -*************** - -.. image:: images/query_execution.png - :alt: Query tool execute options - :align: center - -.. table:: - :class: longtable - :widths: 1 4 1 - - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - | Icon | Behavior | Shortcut | - +======================+===================================================================================================+================+ - | *Stop* | Click the *Stop* icon to cancel the execution of the currently running query. |Option + Shift +| - | | |Q | - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - | *Execute script* | Click the *Execute script* icon to either execute or refresh the query highlighted in the SQL | F5 | - | | editor panel. Click the down arrow to access other execution options: | | - | | | | - | | * Add a check next to *Auto rollback on error?* to instruct the server to automatically roll back| | - | | a transaction if an error occurs during the transaction. | | - | | | | - | | * Add a check next to *Auto commit?* to instruct the server to automatically commit each | | - | | transaction. Any changes made by the transaction will be visible to others, and | | - | | durable in the event of a crash. | | - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - | *Execute query* | Click the *Execute query* icon to either execute the query where the cursor is present or | Option+F5 (MAC)| - | | refresh the query highlighted in the SQL editor panel. | Alt+F5 (Others)| - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - | *Explain* | Click the *Explain* icon to view an explanation plan for the current query. The result of the | F7 | - | | EXPLAIN is displayed graphically on the *Explain* tab of the output panel, and in text | | - | | form on the *Data Output* tab. | | - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - | *Explain analyze* | Click the *Explain analyze* icon to invoke an EXPLAIN ANALYZE command on the current query. | Shift + F7 | - | | | | - | | Navigate through the *Explain Options* menu to select options for the EXPLAIN command: | | - | | | | - | | * Select *Verbose* to display additional information regarding the query plan. | | - | | | | - | | * Select *Costs* to include information on the estimated startup and total cost of each | | - | | plan node, as well as the estimated number of rows and the estimated width of each | | - | | row. | | - | | | | - | | * Select *Buffers* to include information on buffer usage. | | - | | | | - | | * Select *Timing* to include information about the startup time and the amount of time | | - | | spent in each node of the query. | | - | | | | - | | * Select *Summary* to include the summary information about the query plan. | | - | | | | - | | * Select *Settings* to include the information on the configuration parameters. | | - | | | | - | | * Select *Wal* to include the information on WAL record generation. | | - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - | *Commit* | Click the *Commit* icon to commit the transaction. |Shift + Ctrl + M| - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - | *Rollback* | Click the *Rollback* icon to rollback the transaction. |Shift + Ctrl + R| - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - | *Macros* | Click the *Macros* icon to manage the macros. You can create, edit or clear the macros through | | - | | the *Manage Macros* option. | | - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - -Data Editing Options -******************** - -.. image:: images/query_data_editing.png - :alt: Query tool data editing options - :align: center - -.. table:: - :class: longtable - :widths: 1 4 1 - - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - | Icon | Behavior | Shortcut | - +======================+===================================================================================================+================+ - | *Add row* | Click the *Add row* icon to add a new row | | - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - | *Copy* | Click the *Copy* icon to copy the content with or without header: | Cmd/Ctrl + C | - | | | | - | | * Click the *Copy* icon to copy the content that is currently highlighted in the Data Output | | - | | panel. | | - | | | | - | | * Click *Copy with headers* to copy the highlighted content along with the header. | | - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - | *Paste* | Click the *Paste* icon to paste a previously copied row with or without serial/identity values: | Option/Alt + | - | | | Shift + P | - | | | | - | | * Click the *Paste* icon to paste a previously copied row into a new row. | | - | | | | - | | * Click the *Paste with SERIAL/IDENTITY values?* if you want to paste the copied column values | | - | | in the serial/identity columns. | | - | | | | - | | Note that copied row having *Bytea* datatype cell will be pasted as *Null*. | | - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - | *Delete* | Click the *Delete* icon to mark the selected rows for deletion. These marked rows get deleted | Option/Alt + | - | | | Shift + D | - | | when you click the *Save Data Changes* icon. | | - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - | *Save Data Changes* | Click the *Save Data Changes* icon to save data changes (insert, update, or delete) in the Data | F6 | - | | Output Panel to the server. | | - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - | *Save results to* | Click the Save results to file icon to save the result set of the current query as a delimited | F8 | - | *file* | text file (CSV, if the field separator is set to a comma). This button will only be enabled when | | - | | a query has been executed and there are results in the data grid. You can specify the CSV/TXT | | - | | settings in the Preference Dialogue under SQL Editor -> CSV/TXT output. | | - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - | Graph Visualiser | Use the Graph Visualiser button to generate graphs of the query results. | | - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - | SQL | Use the SQL button to check the current query that gave the data. | | - +----------------------+---------------------------------------------------------------------------------------------------+----------------+ - -Status Bar -********** - -.. image:: images/query_status_bar.png - :alt: Query tool status bar - :align: center - -The status bar shows the following information: - -* **Total rows**: The total number of rows returned by the query. -* **Query complete**: The time is taken by the query to complete. -* **Rows selected**: The number of rows selected in the data output panel. -* **Changes staged**: This information showed the number of rows added, deleted, and updated. -* **Ln**: In the Query tab, it is the line number at which the cursor is positioned. -* **Col**: In the Query tab, it is the column number at which the cursor is positioned +.. _query_tool_toolbar: + +*************************** +`Query Tool Toolbar`:index: +*************************** + +The *Query Tool* toolbar uses context-sensitive icons that provide shortcuts to +frequently performed tasks. If an icon is highlighted, the option is enabled; +if the icon is grayed-out, the task is disabled. + +.. note:: The :ref:`Query Tool ` and + :ref:`View/Edit Data ` tools are actually different operating + modes of the same tool. Some controls will be disabled in either mode. + +.. image:: images/query_toolbar.png + :alt: Query tool toolbar + :align: center + +Hover over an icon in pgAdmin to display a tooltip that describes the icon's +functionality. + +File Options +************ + +.. table:: + :class: longtable + :widths: 1 4 1 + + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + | Icon | Behavior | Shortcut | + +======================+===================================================================================================+================+ + | *Open File* | Click the *Open File* icon to display a previously saved query in the SQL Editor. | Cmd/Ctrl + O | + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + | *Save File* | Click the *Save* icon to perform a quick-save of a previously saved query, or to access the | Cmd/Ctrl + S | + | | *Save* menu: | | + | | | | + | | * Select *Save* to save the selected content of the SQL Editor panel in a file. | | + | | | | + | | * Select *Save As* to open a new browser dialog and specify a new location to which to save the | | + | | selected content of the SQL Editor panel. | | + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + +Filter/Limit Options +******************** + +.. table:: + :class: longtable + :widths: 1 4 1 + + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + | Icon | Behavior | Shortcut | + +======================+===================================================================================================+================+ + | *Filter* | Click the *Filter* icon to set filtering and sorting criteria for the data when in *View/Edit data| Option/Alt + F | + | | mode*. Click the down arrow to access other filtering and sorting options: | | + | | | | + | | * In the *SQL Filter*, you can enter a SQL query as filtering criteria. | | + | | In *Data Sorting*, you can select the column and specify the order for sorting. | | + | | | | + | | * Click *Filter by Selection* to show only the rows containing the values in the selected cells. | | + | | | | + | | * Click *Exclude by Selection* to show only the rows that do not contain the values in the | | + | | selected cells. | | + | | | | + | | * Click *Remove Sort/Filter* to remove any previously selected sort or filtering options. | | + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + | Limit Selector | Select a value in the *Limit Selector* to limit the size of the dataset to a number of rows. | Option/Alt + R | + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + +Query Editing Options +********************* + +.. image:: images/query_editing.png + :alt: Query tool editing options + :align: center + +.. table:: + :class: longtable + :widths: 1 4 1 + + +----------------------+---------------------------------------------------------------------------------------------------+-----------------------+ + | Icon | Behavior | Shortcut | + +======================+===================================================================================================+=======================+ + | *Edit* | Use the *Edit* menu to search, replace, or navigate the code displayed in the SQL Editor: | Option/Alt + Shift + N| + | +---------------------------------------------------------------------------------------------------+-----------------------+ + | | Select *Find* to provide a search target, and search the SQL Editor contents. | Cmd/Ctrl + F | + | +---------------------------------------------------------------------------------------------------+-----------------------+ + | | Select *Replace* to locate and replace (with prompting) individual occurrences of the target. | Option + Cmd + F (MAC)| + | | | Ctrl + Shift + F | + | | | (Others) | + | +---------------------------------------------------------------------------------------------------+-----------------------+ + | | Select *Go to Line/Column* to go to specified line number and column position | Cmd/Ctrl + L | + | +---------------------------------------------------------------------------------------------------+-----------------------+ + | | Select *Indent Selection* to indent the currently selected text. | Tab | + | +---------------------------------------------------------------------------------------------------+-----------------------+ + | | Select *Unindent Selection* to remove indentation from the currently selected text. | Shift + Tab | + | +---------------------------------------------------------------------------------------------------+-----------------------+ + | | Select *Toggle Comment* to comment/uncomment any lines that contain the selection in SQL style. | Cmd/Ctrl + / | + | +---------------------------------------------------------------------------------------------------+-----------------------+ + | | Select *Clear Query* to clear the query editor window. | Option/Alt + Ctrl + L | + | +---------------------------------------------------------------------------------------------------+-----------------------+ + | | Select *Format SQL* to format the selected SQL or all the SQL if none is selected | Cmd/Ctrl + K | + +----------------------+---------------------------------------------------------------------------------------------------+-----------------------+ + +Query Execution +*************** + +.. image:: images/query_execution.png + :alt: Query tool execute options + :align: center + +.. table:: + :class: longtable + :widths: 1 4 1 + + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + | Icon | Behavior | Shortcut | + +======================+===================================================================================================+================+ + | *Stop* | Click the *Stop* icon to cancel the execution of the currently running query. |Option + Shift +| + | | |Q | + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + | *Execute script* | Click the *Execute script* icon to either execute or refresh the query highlighted in the SQL | F5 | + | | editor panel. Click the down arrow to access other execution options: | | + | | | | + | | * Add a check next to *Auto rollback on error?* to instruct the server to automatically roll back| | + | | a transaction if an error occurs during the transaction. | | + | | | | + | | * Add a check next to *Auto commit?* to instruct the server to automatically commit each | | + | | transaction. Any changes made by the transaction will be visible to others, and | | + | | durable in the event of a crash. | | + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + | *Execute query* | Click the *Execute query* icon to either execute the query where the cursor is present or | Option+F5 (MAC)| + | | refresh the query highlighted in the SQL editor panel. | Alt+F5 (Others)| + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + | *Explain* | Click the *Explain* icon to view an explanation plan for the current query. The result of the | F7 | + | | EXPLAIN is displayed graphically on the *Explain* tab of the output panel, and in text | | + | | form on the *Data Output* tab. | | + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + | *Explain analyze* | Click the *Explain analyze* icon to invoke an EXPLAIN ANALYZE command on the current query. | Shift + F7 | + | | | | + | | Navigate through the *Explain Options* menu to select options for the EXPLAIN command: | | + | | | | + | | * Select *Verbose* to display additional information regarding the query plan. | | + | | | | + | | * Select *Costs* to include information on the estimated startup and total cost of each | | + | | plan node, as well as the estimated number of rows and the estimated width of each | | + | | row. | | + | | | | + | | * Select *Buffers* to include information on buffer usage. | | + | | | | + | | * Select *Timing* to include information about the startup time and the amount of time | | + | | spent in each node of the query. | | + | | | | + | | * Select *Summary* to include the summary information about the query plan. | | + | | | | + | | * Select *Settings* to include the information on the configuration parameters. | | + | | | | + | | * Select *Wal* to include the information on WAL record generation. | | + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + | *Commit* | Click the *Commit* icon to commit the transaction. |Shift + Ctrl + M| + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + | *Rollback* | Click the *Rollback* icon to rollback the transaction. |Shift + Ctrl + R| + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + | *Macros* | Click the *Macros* icon to manage the macros. You can create, edit or clear the macros through | | + | | the *Manage Macros* option. | | + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + +Data Editing Options +******************** + +.. image:: images/query_data_editing.png + :alt: Query tool data editing options + :align: center + +.. table:: + :class: longtable + :widths: 1 4 1 + + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + | Icon | Behavior | Shortcut | + +======================+===================================================================================================+================+ + | *Add row* | Click the *Add row* icon to add a new row | | + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + | *Copy* | Click the *Copy* icon to copy the content with or without header: | Cmd/Ctrl + C | + | | | | + | | * Click the *Copy* icon to copy the content that is currently highlighted in the Data Output | | + | | panel. | | + | | | | + | | * Click *Copy with headers* to copy the highlighted content along with the header. | | + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + | *Paste* | Click the *Paste* icon to paste a previously copied row with or without serial/identity values: | Option/Alt + | + | | | Shift + P | + | | | | + | | * Click the *Paste* icon to paste a previously copied row into a new row. | | + | | | | + | | * Click the *Paste with SERIAL/IDENTITY values?* if you want to paste the copied column values | | + | | in the serial/identity columns. | | + | | | | + | | Note that copied row having *Bytea* datatype cell will be pasted as *Null*. | | + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + | *Delete* | Click the *Delete* icon to mark the selected rows for deletion. These marked rows get deleted | Option/Alt + | + | | | Shift + D | + | | when you click the *Save Data Changes* icon. | | + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + | *Save Data Changes* | Click the *Save Data Changes* icon to save data changes (insert, update, or delete) in the Data | F6 | + | | Output Panel to the server. | | + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + | *Save results to* | Click the Save results to file icon to save the result set of the current query as a delimited | F8 | + | *file* | text file (CSV, if the field separator is set to a comma). This button will only be enabled when | | + | | a query has been executed and there are results in the data grid. You can specify the CSV/TXT | | + | | settings in the Preference Dialogue under SQL Editor -> CSV/TXT output. | | + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + | Graph Visualiser | Use the Graph Visualiser button to generate graphs of the query results. | | + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + | SQL | Use the SQL button to check the current query that gave the data. | | + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + +Status Bar +********** + +.. image:: images/query_status_bar.png + :alt: Query tool status bar + :align: center + +The status bar shows the following information: + +* **Total rows**: The total number of rows returned by the query. +* **Query complete**: The time is taken by the query to complete. +* **Rows selected**: The number of rows selected in the data output panel. +* **Changes staged**: This information showed the number of rows added, deleted, and updated. +* **LF/CRLF**: This information showed the end of line sequence of text based on operating system. + * Click on the LF/CRLF indicator to choose the appropriate end-of-line (EOL) format for your needs, + either LF or CRLF, to ensure proper file formatting. +* **Ln**: In the Query tab, it is the line number at which the cursor is positioned. +* **Col**: In the Query tab, it is the column number at which the cursor is positioned diff --git a/web/pgadmin/static/js/components/ReactCodeMirror/CustomEditorView.js b/web/pgadmin/static/js/components/ReactCodeMirror/CustomEditorView.js index 6801b44a77c..80dc598de02 100644 --- a/web/pgadmin/static/js/components/ReactCodeMirror/CustomEditorView.js +++ b/web/pgadmin/static/js/components/ReactCodeMirror/CustomEditorView.js @@ -9,7 +9,7 @@ import { errorMarkerEffect } from './extensions/errorMarker'; import { currentQueryHighlighterEffect } from './extensions/currentQueryHighlighter'; import { activeLineEffect, activeLineField } from './extensions/activeLineMarker'; import { clearBreakpoints, hasBreakpoint, toggleBreakpoint } from './extensions/breakpointGutter'; -import { autoCompleteCompartment } from './extensions/extraStates'; +import { autoCompleteCompartment, eol, eolCompartment } from './extensions/extraStates'; function getAutocompLoading({ bottom, left }, dom) { @@ -30,11 +30,13 @@ export default class CustomEditorView extends EditorView { this._cleanDoc = this.state.doc; } - getValue(tillCursor=false) { + getValue(tillCursor=false, lineSep=false) { if(tillCursor) { return this.state.sliceDoc(0, this.state.selection.main.head); + } else if (lineSep) { + return this.state.doc.sliceString(0, this.state.doc.length, lineSep); } - return this.state.doc.toString(); + return this.state.sliceDoc(); } /* Function to extract query based on position passed */ @@ -328,4 +330,14 @@ export default class CustomEditorView extends EditorView { setQueryHighlightMark(from,to) { this.dispatch({ effects: currentQueryHighlighterEffect.of({ from, to }) }); } + + getEOL(){ + return this.state.facet(eol); + } + + setEOL(val){ + this.dispatch({ + effects: eolCompartment.reconfigure(eol.of(val)) + }); + } } diff --git a/web/pgadmin/static/js/components/ReactCodeMirror/components/Editor.jsx b/web/pgadmin/static/js/components/ReactCodeMirror/components/Editor.jsx index f7c22f74484..b3bb081b8d8 100644 --- a/web/pgadmin/static/js/components/ReactCodeMirror/components/Editor.jsx +++ b/web/pgadmin/static/js/components/ReactCodeMirror/components/Editor.jsx @@ -1,407 +1,414 @@ -///////////////////////////////////////////////////////////// -// -// pgAdmin 4 - PostgreSQL Tools -// -// Copyright (C) 2013 - 2023, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -////////////////////////////////////////////////////////////// - -import React, { useEffect, useMemo, useRef } from 'react'; -import ReactDOMServer from 'react-dom/server'; -import PropTypes from 'prop-types'; -import { checkTrojanSource } from '../../../utils'; -import usePreferences from '../../../../../preferences/static/js/store'; -import KeyboardArrowRightRoundedIcon from '@mui/icons-material/KeyboardArrowRightRounded'; -import ExpandMoreRoundedIcon from '@mui/icons-material/ExpandMoreRounded'; - -// Codemirror packages -import { - lineNumbers, - highlightSpecialChars, - drawSelection, - dropCursor, - rectangularSelection, - crosshairCursor, - highlightActiveLine, - EditorView, - keymap, -} from '@codemirror/view'; -import { EditorState, Compartment } from '@codemirror/state'; -import { history, defaultKeymap, historyKeymap, indentLess, indentMore, deleteCharBackwardStrict } from '@codemirror/commands'; -import { closeBrackets, autocompletion, closeBracketsKeymap, completionKeymap, acceptCompletion } from '@codemirror/autocomplete'; -import { - foldGutter, - indentOnInput, - bracketMatching, - indentUnit, - foldKeymap, - indentService -} from '@codemirror/language'; -import { highlightSelectionMatches } from '@codemirror/search'; -import syntaxHighlighting from '../extensions/highlighting'; -import PgSQL from '../extensions/dialect'; -import { sql } from '@codemirror/lang-sql'; -import { json } from '@codemirror/lang-json'; -import errorMarkerExtn from '../extensions/errorMarker'; -import CustomEditorView from '../CustomEditorView'; -import breakpointGutter, { breakpointEffect } from '../extensions/breakpointGutter'; -import activeLineExtn from '../extensions/activeLineMarker'; -import currentQueryHighlighterExtn from '../extensions/currentQueryHighlighter'; -import { autoCompleteCompartment, indentNewLine } from '../extensions/extraStates'; - -const arrowRightHtml = ReactDOMServer.renderToString(); -const arrowDownHtml = ReactDOMServer.renderToString(); - -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 { - /* if parsing fails, it must be the drag internal of codemirror text */ - 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); -} - - -function insertTabWithUnit({ state, dispatch }) { - if (state.selection.ranges.some(r => !r.empty)) - return indentMore({ state, dispatch }); - dispatch(state.update(state.replaceSelection(state.facet(indentUnit)), { scrollIntoView: true, userEvent: 'input' })); - return true; -} - -/* React wrapper for CodeMirror */ -const defaultExtensions = [ - highlightSpecialChars(), - drawSelection(), - rectangularSelection(), - dropCursor(), - crosshairCursor(), - EditorState.allowMultipleSelections.of(true), - indentOnInput(), - syntaxHighlighting, - keymap.of([{ - key: 'Tab', - preventDefault: true, - run: insertTabWithUnit, - shift: indentLess, - },{ - key: 'Tab', - run: acceptCompletion, - },{ - key: 'Backspace', - preventDefault: true, - run: deleteCharBackwardStrict, - }]), - PgSQL.language.data.of({ - autocomplete: false, - }), - EditorView.domEventHandlers({ - drop: handleDrop, - paste: handlePaste, - }), - errorMarkerExtn(), - indentService.of((context, pos) => { - if(context.state.facet(indentNewLine)) { - const previousLine = context.lineAt(pos, -1); - let prevText = previousLine.text.replaceAll('\t', ' '.repeat(context.state.tabSize)); - return prevText.match(/^\s*/)?.[0].length; - } - return 0; - }), - autoCompleteCompartment.of([]), -]; - -export default function Editor({ - currEditor, name, value, options, onCursorActivity, onChange, readonly, disabled, autocomplete = false, - breakpoint = false, onBreakPointChange, showActiveLine=false, - keepHistory = true, cid, helpid, labelledBy, customKeyMap, language='pgsql'}) { - - const editorContainerRef = useRef(); - const editor = useRef(); - const defaultOptions = { - lineNumbers: true, - foldGutter: true, - }; - - const preferencesStore = usePreferences(); - const editable = !disabled; - - const shortcuts = useRef(new Compartment()); - const configurables = useRef(new Compartment()); - const editableConfig = useRef(new Compartment()); - - useEffect(() => { - const finalOptions = { ...defaultOptions, ...options }; - const finalExtns = [ - (language == 'json') ? json() : sql({dialect: PgSQL}), - ...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, - shortcuts.current.of([]), - configurables.current.of([]), - 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(()=>{ - const keys = keymap.of([customKeyMap??[], defaultKeymap, closeBracketsKeymap, historyKeymap, foldKeymap, completionKeymap].flat()); - editor.current?.dispatch({ - effects: shortcuts.current.reconfigure(keys) - }); - }, [customKeyMap]); - - 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(' '.repeat(pref.tab_size)), - ); - } else { - newConfigExtn.push( - indentUnit.of('\t'), - ); - } - - if(pref.indent_new_line) { - newConfigExtn.push(indentNewLine.of(true)); - } else { - newConfigExtn.push(indentNewLine.of(false)); - } - - if (pref.wrap_code) { - newConfigExtn.push( - EditorView.lineWrapping - ); - } - - if (pref.insert_pair_brackets) { - newConfigExtn.push(closeBrackets()); - } - - if (pref.highlight_selection_matches){ - newConfigExtn.push(highlightSelectionMatches()); - } - - if (pref.brace_matching) { - newConfigExtn.push(bracketMatching()); - } - if (pref.underline_query_cursor){ - newConfigExtn.push(currentQueryHighlighterExtn()); - } - - 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]); - - return useMemo(()=>( -
- ), []); -} - -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, - customKeyMap: PropTypes.array, - language: PropTypes.string, -}; +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2023, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import React, { useEffect, useMemo, useRef } from 'react'; +import ReactDOMServer from 'react-dom/server'; +import PropTypes from 'prop-types'; +import { checkTrojanSource } from '../../../utils'; +import usePreferences from '../../../../../preferences/static/js/store'; +import KeyboardArrowRightRoundedIcon from '@mui/icons-material/KeyboardArrowRightRounded'; +import ExpandMoreRoundedIcon from '@mui/icons-material/ExpandMoreRounded'; + +// Codemirror packages +import { + lineNumbers, + highlightSpecialChars, + drawSelection, + dropCursor, + rectangularSelection, + crosshairCursor, + highlightActiveLine, + EditorView, + keymap, +} from '@codemirror/view'; +import { EditorState, Compartment } from '@codemirror/state'; +import { history, defaultKeymap, historyKeymap, indentLess, indentMore, deleteCharBackwardStrict } from '@codemirror/commands'; +import { closeBrackets, autocompletion, closeBracketsKeymap, completionKeymap, acceptCompletion } from '@codemirror/autocomplete'; +import { + foldGutter, + indentOnInput, + bracketMatching, + indentUnit, + foldKeymap, + indentService +} from '@codemirror/language'; +import { highlightSelectionMatches } from '@codemirror/search'; +import syntaxHighlighting from '../extensions/highlighting'; +import PgSQL from '../extensions/dialect'; +import { sql } from '@codemirror/lang-sql'; +import { json } from '@codemirror/lang-json'; +import errorMarkerExtn from '../extensions/errorMarker'; +import CustomEditorView from '../CustomEditorView'; +import breakpointGutter, { breakpointEffect } from '../extensions/breakpointGutter'; +import activeLineExtn from '../extensions/activeLineMarker'; +import currentQueryHighlighterExtn from '../extensions/currentQueryHighlighter'; +import { autoCompleteCompartment, eolCompartment, indentNewLine, eol } from '../extensions/extraStates'; +import { OS_EOL } from '../../../../../tools/sqleditor/static/js/components/QueryToolConstants'; + +const arrowRightHtml = ReactDOMServer.renderToString(); +const arrowDownHtml = ReactDOMServer.renderToString(); + +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 { + /* if parsing fails, it must be the drag internal of codemirror text */ + 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); +} + + +function insertTabWithUnit({ state, dispatch }) { + if (state.selection.ranges.some(r => !r.empty)) + return indentMore({ state, dispatch }); + dispatch(state.update(state.replaceSelection(state.facet(indentUnit)), { scrollIntoView: true, userEvent: 'input' })); + return true; +} + +/* React wrapper for CodeMirror */ +const defaultExtensions = [ + highlightSpecialChars(), + drawSelection(), + rectangularSelection(), + dropCursor(), + crosshairCursor(), + EditorState.allowMultipleSelections.of(true), + indentOnInput(), + syntaxHighlighting, + keymap.of([{ + key: 'Tab', + preventDefault: true, + run: insertTabWithUnit, + shift: indentLess, + },{ + key: 'Tab', + run: acceptCompletion, + },{ + key: 'Backspace', + preventDefault: true, + run: deleteCharBackwardStrict, + }]), + PgSQL.language.data.of({ + autocomplete: false, + }), + EditorView.domEventHandlers({ + drop: handleDrop, + paste: handlePaste, + }), + errorMarkerExtn(), + indentService.of((context, pos) => { + if(context.state.facet(indentNewLine)) { + const previousLine = context.lineAt(pos, -1); + let prevText = previousLine.text.replaceAll('\t', ' '.repeat(context.state.tabSize)); + return prevText.match(/^\s*/)?.[0].length; + } + return 0; + }), + autoCompleteCompartment.of([]), + EditorView.clipboardOutputFilter.of((text, state)=>{ + const lineSep = state.facet(eol); + return state.doc.sliceString(0, text.length, lineSep); + }) +]; + +export default function Editor({ + currEditor, name, value, options, onCursorActivity, onChange, readonly, disabled, autocomplete = false, + breakpoint = false, onBreakPointChange, showActiveLine=false, + keepHistory = true, cid, helpid, labelledBy, customKeyMap, language='pgsql'}) { + + const editorContainerRef = useRef(); + const editor = useRef(); + const defaultOptions = { + lineNumbers: true, + foldGutter: true, + }; + + const preferencesStore = usePreferences(); + const editable = !disabled; + + const shortcuts = useRef(new Compartment()); + const configurables = useRef(new Compartment()); + const editableConfig = useRef(new Compartment()); + + useEffect(() => { + const finalOptions = { ...defaultOptions, ...options }; + const osEOL = OS_EOL === 'crlf' ? '\r\n' : '\n'; + const finalExtns = [ + (language == 'json') ? json() : sql({dialect: PgSQL}), + ...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, + eolCompartment.of([eol.of(osEOL)]), + shortcuts.current.of([]), + configurables.current.of([]), + 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(()=>{ + const keys = keymap.of([customKeyMap??[], defaultKeymap, closeBracketsKeymap, historyKeymap, foldKeymap, completionKeymap].flat()); + editor.current?.dispatch({ + effects: shortcuts.current.reconfigure(keys) + }); + }, [customKeyMap]); + + 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(' '.repeat(pref.tab_size)), + ); + } else { + newConfigExtn.push( + indentUnit.of('\t'), + ); + } + + if(pref.indent_new_line) { + newConfigExtn.push(indentNewLine.of(true)); + } else { + newConfigExtn.push(indentNewLine.of(false)); + } + + if (pref.wrap_code) { + newConfigExtn.push( + EditorView.lineWrapping + ); + } + + if (pref.insert_pair_brackets) { + newConfigExtn.push(closeBrackets()); + } + + if (pref.highlight_selection_matches){ + newConfigExtn.push(highlightSelectionMatches()); + } + + if (pref.brace_matching) { + newConfigExtn.push(bracketMatching()); + } + if (pref.underline_query_cursor){ + newConfigExtn.push(currentQueryHighlighterExtn()); + } + + 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]); + + return useMemo(()=>( +
+ ), []); +} + +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, + customKeyMap: PropTypes.array, + language: PropTypes.string, +}; diff --git a/web/pgadmin/static/js/components/ReactCodeMirror/extensions/extraStates.js b/web/pgadmin/static/js/components/ReactCodeMirror/extensions/extraStates.js index 5e4c7b433ea..0970c4cddd4 100644 --- a/web/pgadmin/static/js/components/ReactCodeMirror/extensions/extraStates.js +++ b/web/pgadmin/static/js/components/ReactCodeMirror/extensions/extraStates.js @@ -13,4 +13,10 @@ export const indentNewLine = Facet.define({ combine: values => values.length ? values[0] : true, }); +export const eol = Facet.define({ + combine: values => values.length ? values[0] : '\n', +}); + export const autoCompleteCompartment = new Compartment(); +export const eolCompartment = new Compartment(); + diff --git a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolConstants.js b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolConstants.js index a1c3825679e..ddbeb91ec96 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolConstants.js +++ b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolConstants.js @@ -75,6 +75,7 @@ export const QUERY_TOOL_EVENTS = { RESET_GRAPH_VISUALISER: 'RESET_GRAPH_VISUALISER', GOTO_LAST_SCROLL: 'GOTO_LAST_SCROLL', + CHANGE_EOL: 'CHANGE_EOL' }; export const CONNECTION_STATUS = { @@ -105,4 +106,6 @@ export const PANELS = { GRAPH_VISUALISER: 'id-graph-visualiser', }; -export const MAX_QUERY_LENGTH = 1000000; \ No newline at end of file +export const MAX_QUERY_LENGTH = 1000000; + +export const OS_EOL = navigator.platform === 'win32' ? 'crlf' : 'lf'; \ No newline at end of file diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx index 178c011ad99..ee926ab26ca 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx @@ -65,7 +65,6 @@ export default function Query({onTextSelect}) { const pgAdmin = usePgAdmin(); const preferencesStore = usePreferences(); const queryToolPref = queryToolCtx.preferences.sqleditor; - const highlightError = (cmObj, {errormsg: result, data}, executeCursor)=>{ let errorLineNo = 0, startMarker = 0, @@ -175,7 +174,6 @@ export default function Query({onTextSelect}) { } }); - eventBus.registerListener(QUERY_TOOL_EVENTS.LOAD_FILE, (fileName, storage)=>{ queryToolCtx.api.post(url_for('sqleditor.load_file'), { 'file_name': decodeURI(fileName), @@ -200,7 +198,7 @@ export default function Query({onTextSelect}) { eventBus.registerListener(QUERY_TOOL_EVENTS.SAVE_FILE, (fileName)=>{ queryToolCtx.api.post(url_for('sqleditor.save_file'), { 'file_name': decodeURI(fileName), - 'file_content': editor.current.getValue(), + 'file_content': editor.current.getValue(false, editor.current?.getEOL()), }).then(()=>{ editor.current.markClean(); eventBus.fireEvent(QUERY_TOOL_EVENTS.SAVE_FILE_DONE, fileName, true); @@ -288,6 +286,12 @@ export default function Query({onTextSelect}) { editor.current.setValue(formattedSql); } }); + + eventBus.registerListener(QUERY_TOOL_EVENTS.CHANGE_EOL, (lineSep)=>{ + editor.current?.setEOL(lineSep); + eventBus.fireEvent(QUERY_TOOL_EVENTS.QUERY_CHANGED, true); + }); + eventBus.registerListener(QUERY_TOOL_EVENTS.EDITOR_TOGGLE_CASE, ()=>{ let selectedText = editor.current?.getSelection(); if (!selectedText) return; diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/StatusBar.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/StatusBar.jsx index c4dbf06cc7b..0a6bd2425e2 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/StatusBar.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/StatusBar.jsx @@ -1,115 +1,171 @@ - -///////////////////////////////////////////////////////////// -// -// pgAdmin 4 - PostgreSQL Tools -// -// Copyright (C) 2013 - 2024, The pgAdmin Development Team -// This software is released under the PostgreSQL Licence -// -////////////////////////////////////////////////////////////// -import React, { useEffect, useState, useContext } from 'react'; -import { styled } from '@mui/material/styles'; -import { Box } from '@mui/material'; -import _ from 'lodash'; -import { QUERY_TOOL_EVENTS } from '../QueryToolConstants'; -import { useStopwatch } from '../../../../../../static/js/custom_hooks'; -import { QueryToolEventsContext } from '../QueryToolComponent'; -import gettext from 'sources/gettext'; - - -const StyledBox = styled(Box)(({theme}) => ({ - display: 'flex', - alignItems: 'center', - ...theme.mixins.panelBorder.top, - flexWrap: 'wrap', - backgroundColor: theme.otherVars.editorToolbarBg, - userSelect: 'text', - '& .StatusBar-padding': { - padding: '2px 12px', - '& .StatusBar-mlAuto': { - marginLeft: 'auto', - }, - '& .StatusBar-divider': { - ...theme.mixins.panelBorder.right, - }, - }, -})); - -export function StatusBar() { - - const eventBus = useContext(QueryToolEventsContext); - const [position, setPosition] = useState([1, 1]); - const [lastTaskText, setLastTaskText] = useState(null); - const [rowsCount, setRowsCount] = useState([0, 0]); - const [selectedRowsCount, setSelectedRowsCount] = useState(0); - const [dataRowChangeCounts, setDataRowChangeCounts] = useState({ - isDirty: false, - added: 0, - updated: 0, - deleted: 0, - }); - const {seconds, minutes, hours, msec, start:startTimer, pause:pauseTimer, reset:resetTimer} = useStopwatch({}); - - useEffect(()=>{ - eventBus.registerListener(QUERY_TOOL_EVENTS.CURSOR_ACTIVITY, (newPos)=>{ - setPosition(newPos||[1, 1]); - }); - eventBus.registerListener(QUERY_TOOL_EVENTS.EXECUTION_END, ()=>{ - pauseTimer(); - setLastTaskText(gettext('Query complete')); - }); - eventBus.registerListener(QUERY_TOOL_EVENTS.TASK_START, (taskText, startTime)=>{ - resetTimer(); - startTimer(startTime); - setLastTaskText(taskText); - }); - eventBus.registerListener(QUERY_TOOL_EVENTS.TASK_END, (taskText, endTime)=>{ - pauseTimer(endTime); - setLastTaskText(taskText); - }); - eventBus.registerListener(QUERY_TOOL_EVENTS.ROWS_FETCHED, (fetched, total)=>{ - setRowsCount([fetched||0, total||0]); - }); - eventBus.registerListener(QUERY_TOOL_EVENTS.SELECTED_ROWS_COLS_CELL_CHANGED, (rows)=>{ - setSelectedRowsCount(rows); - }); - eventBus.registerListener(QUERY_TOOL_EVENTS.DATAGRID_CHANGED, (_isDirty, dataChangeStore)=>{ - setDataRowChangeCounts({ - added: Object.keys(dataChangeStore.added||{}).length, - updated: Object.keys(dataChangeStore.updated||{}).length, - deleted: Object.keys(dataChangeStore.deleted||{}).length, - }); - }); - }, []); - - let stagedText = ''; - if(dataRowChangeCounts.added > 0) { - stagedText += ' ' + gettext('Added: %s', dataRowChangeCounts.added); - } - if(dataRowChangeCounts.updated > 0) { - stagedText += ' ' + gettext('Updated: %s', dataRowChangeCounts.updated); - } - if(dataRowChangeCounts.deleted > 0) { - stagedText += ' ' + gettext('Deleted: %s', dataRowChangeCounts.deleted); - } - - return ( - - {gettext('Total rows: %s of %s', rowsCount[0], rowsCount[1])} - {lastTaskText && - {lastTaskText} {hours.toString().padStart(2, '0')}:{minutes.toString().padStart(2, '0')}:{seconds.toString().padStart(2, '0')}.{msec.toString().padStart(3, '0')} - } - {!lastTaskText && !_.isNull(lastTaskText) && - {lastTaskText} {hours.toString().padStart(2, '0')}:{minutes.toString().padStart(2, '0')}:{seconds.toString().padStart(2, '0')}.{msec.toString().padStart(3, '0')} - } - {Boolean(selectedRowsCount) && - {gettext('Rows selected: %s',selectedRowsCount)}} - {stagedText && - - {gettext('Changes staged: %s', stagedText)} - - } - {gettext('Ln %s, Col %s', position[0], position[1])} - - ); -} + +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2024, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// +import React, { useEffect, useState, useContext, useCallback } from 'react'; +import { styled } from '@mui/material/styles'; +import { Box, Tooltip } from '@mui/material'; +import _ from 'lodash'; +import { OS_EOL, QUERY_TOOL_EVENTS } from '../QueryToolConstants'; +import { useStopwatch } from '../../../../../../static/js/custom_hooks'; +import { QueryToolEventsContext } from '../QueryToolComponent'; +import gettext from 'sources/gettext'; +import { PgMenu, PgMenuItem } from '../../../../../../static/js/components/Menu'; + + +const StyledBox = styled(Box)(({theme}) => ({ + display: 'flex', + alignItems: 'center', + ...theme.mixins.panelBorder.top, + flexWrap: 'wrap', + backgroundColor: theme.otherVars.editorToolbarBg, + userSelect: 'text', + '& .StatusBar-padding': { + padding: '2px 12px', + '& .StatusBar-mlAuto': { + marginLeft: 'auto', + }, + '& .StatusBar-divider': { + ...theme.mixins.panelBorder.right, + }, + }, +})); + + +export function StatusBar() { + const eventBus = useContext(QueryToolEventsContext); + const [position, setPosition] = useState([1, 1]); + const [lastTaskText, setLastTaskText] = useState(null); + const [rowsCount, setRowsCount] = useState([0, 0]); + const [selectedRowsCount, setSelectedRowsCount] = useState(0); + const [dataRowChangeCounts, setDataRowChangeCounts] = useState({ + isDirty: false, + added: 0, + updated: 0, + deleted: 0, + }); + const {seconds, minutes, hours, msec, start:startTimer, pause:pauseTimer, reset:resetTimer} = useStopwatch({}); + + const [selectedEol, setSelectedEol] = useState(OS_EOL); + const [openMenuName, setOpenMenuName] = useState(null); + const prevMenuOpenIdRef = React.useRef(null); + const eolMenuRef = React.useRef(null); + + const toggleMenu = React.useCallback((e) => { + const name = e.currentTarget?.getAttribute('name'); + setOpenMenuName(() => { + return prevMenuOpenIdRef.current === name ? null : name; + }); + prevMenuOpenIdRef.current = null; + }, []); + + const onMenuClose = React.useCallback(() => { + prevMenuOpenIdRef.current = openMenuName; + setTimeout(() => { + prevMenuOpenIdRef.current = null; + }, 300); + setOpenMenuName(null); + }, [openMenuName]); + + const handleEndOfLineChange = useCallback((e)=>{ + const val = e.value; + const lineSep = val === 'crlf' ? '\r\n' : '\n'; + setSelectedEol(val); + eventBus.fireEvent(QUERY_TOOL_EVENTS.CHANGE_EOL, lineSep); + onMenuClose(); + }, []); + + useEffect(()=>{ + eventBus.registerListener(QUERY_TOOL_EVENTS.CURSOR_ACTIVITY, (newPos)=>{ + setPosition(newPos||[1, 1]); + }); + eventBus.registerListener(QUERY_TOOL_EVENTS.EXECUTION_END, ()=>{ + pauseTimer(); + setLastTaskText(gettext('Query complete')); + }); + eventBus.registerListener(QUERY_TOOL_EVENTS.TASK_START, (taskText, startTime)=>{ + resetTimer(); + startTimer(startTime); + setLastTaskText(taskText); + }); + eventBus.registerListener(QUERY_TOOL_EVENTS.TASK_END, (taskText, endTime)=>{ + pauseTimer(endTime); + setLastTaskText(taskText); + }); + eventBus.registerListener(QUERY_TOOL_EVENTS.ROWS_FETCHED, (fetched, total)=>{ + setRowsCount([fetched||0, total||0]); + }); + eventBus.registerListener(QUERY_TOOL_EVENTS.SELECTED_ROWS_COLS_CELL_CHANGED, (rows)=>{ + setSelectedRowsCount(rows); + }); + eventBus.registerListener(QUERY_TOOL_EVENTS.DATAGRID_CHANGED, (_isDirty, dataChangeStore)=>{ + setDataRowChangeCounts({ + added: Object.keys(dataChangeStore.added||{}).length, + updated: Object.keys(dataChangeStore.updated||{}).length, + deleted: Object.keys(dataChangeStore.deleted||{}).length, + }); + }); + }, []); + + let stagedText = ''; + if(dataRowChangeCounts.added > 0) { + stagedText += ' ' + gettext('Added: %s', dataRowChangeCounts.added); + } + if(dataRowChangeCounts.updated > 0) { + stagedText += ' ' + gettext('Updated: %s', dataRowChangeCounts.updated); + } + if(dataRowChangeCounts.deleted > 0) { + stagedText += ' ' + gettext('Deleted: %s', dataRowChangeCounts.deleted); + } + + return ( + + {gettext('Total rows: %s of %s', rowsCount[0], rowsCount[1])} + {lastTaskText && + {lastTaskText} {hours.toString().padStart(2, '0')}:{minutes.toString().padStart(2, '0')}:{seconds.toString().padStart(2, '0')}.{msec.toString().padStart(3, '0')} + } + {!lastTaskText && !_.isNull(lastTaskText) && + {lastTaskText} {hours.toString().padStart(2, '0')}:{minutes.toString().padStart(2, '0')}:{seconds.toString().padStart(2, '0')}.{msec.toString().padStart(3, '0')} + } + {Boolean(selectedRowsCount) && + {gettext('Rows selected: %s',selectedRowsCount)}} + {stagedText && + + {gettext('Changes staged: %s', stagedText)} + + } + + + + {selectedEol.toUpperCase()} + + + + {gettext('LF')} + {gettext('CRLF')} + + + {gettext('Ln %s, Col %s', position[0], position[1])} + + ); +}