diff --git a/docs/en_US/images/query_status_bar.png b/docs/en_US/images/query_status_bar.png index e885920ba97..44b99a0235e 100644 Binary files a/docs/en_US/images/query_status_bar.png and b/docs/en_US/images/query_status_bar.png differ diff --git a/docs/en_US/query_tool_toolbar.rst b/docs/en_US/query_tool_toolbar.rst index 42db9874a74..fa050ffce4f 100644 --- a/docs/en_US/query_tool_toolbar.rst +++ b/docs/en_US/query_tool_toolbar.rst @@ -1,231 +1,234 @@ -.. _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. -* **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])} + + ); +}