From 4a4d4569ae4f99274bdd65b59e964fb59944a6b3 Mon Sep 17 00:00:00 2001 From: Aditya Toshniwal Date: Mon, 2 Jun 2025 15:58:07 +0530 Subject: [PATCH] Improved PL/PGSQL code folding and support nested blocks. #6118 --- .github/workflows/run-python-tests-epas.yml | 5 ++ .github/workflows/run-python-tests-pg.yml | 5 ++ docs/en_US/release_notes_9_5.rst | 4 +- web/package.json | 2 +- .../ReactCodeMirror/components/Editor.jsx | 9 ++-- .../ReactCodeMirror/extensions/dialect.js | 10 ++++ .../extensions/plpgsqlFoldService.js | 53 +++++++++++++++++++ web/yarn.lock | 10 ++-- 8 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 web/pgadmin/static/js/components/ReactCodeMirror/extensions/plpgsqlFoldService.js diff --git a/.github/workflows/run-python-tests-epas.yml b/.github/workflows/run-python-tests-epas.yml index 3e7fb9d21..025d99265 100644 --- a/.github/workflows/run-python-tests-epas.yml +++ b/.github/workflows/run-python-tests-epas.yml @@ -39,6 +39,11 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Update python version + uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: Setup the EDB APT repo on Linux if: ${{ matrix.os == 'ubuntu-22.04' }} run: | diff --git a/.github/workflows/run-python-tests-pg.yml b/.github/workflows/run-python-tests-pg.yml index e58592aee..9ef39cfb3 100644 --- a/.github/workflows/run-python-tests-pg.yml +++ b/.github/workflows/run-python-tests-pg.yml @@ -28,6 +28,11 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Update python version + uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: Setup the PGDG APT repo on Linux if: ${{ matrix.os == 'ubuntu-22.04' }} run: | diff --git a/docs/en_US/release_notes_9_5.rst b/docs/en_US/release_notes_9_5.rst index 1aa40d52f..3ce9d32c5 100644 --- a/docs/en_US/release_notes_9_5.rst +++ b/docs/en_US/release_notes_9_5.rst @@ -28,4 +28,6 @@ Housekeeping Bug fixes ********* - | `Issue #8032 `_ - Fixed an issue where the Schema Diff Tool incorrectly reported differences due to variations in the order of the privileges. \ No newline at end of file + | `Issue #6118 `_ - Improved PL/pgSQL code folding and support nested blocks. + | `Issue #8032 `_ - Fixed an issue where the Schema Diff Tool incorrectly reported differences due to variations in the order of the privileges. + | `Issue #8691 `_ - Fixed an issue in the query tool where using multiple cursors to copy text resulted in only the first line being copied. diff --git a/web/package.json b/web/package.json index 31e1302f0..5078def77 100644 --- a/web/package.json +++ b/web/package.json @@ -73,7 +73,7 @@ "@babel/plugin-proposal-class-properties": "^7.10.4", "@babel/preset-react": "^7.12.13", "@codemirror/lang-json": "^6.0.1", - "@codemirror/lang-sql": "^6.8.0", + "@codemirror/lang-sql": "^6.9.0", "@date-io/core": "^3.0.0", "@date-io/date-fns": "3.x", "@emotion/sheet": "^1.0.1", diff --git a/web/pgadmin/static/js/components/ReactCodeMirror/components/Editor.jsx b/web/pgadmin/static/js/components/ReactCodeMirror/components/Editor.jsx index 9676aa1c9..13c682a89 100644 --- a/web/pgadmin/static/js/components/ReactCodeMirror/components/Editor.jsx +++ b/web/pgadmin/static/js/components/ReactCodeMirror/components/Editor.jsx @@ -54,9 +54,10 @@ import currentQueryHighlighterExtn from '../extensions/currentQueryHighlighter'; import { autoCompleteCompartment, eolCompartment, indentNewLine, eol } from '../extensions/extraStates'; import { OS_EOL } from '../../../../../tools/sqleditor/static/js/components/QueryToolConstants'; import { useTheme } from '@mui/material'; +import plpgsqlFoldService from '../extensions/plpgsqlFoldService'; -const arrowRightHtml = ReactDOMServer.renderToString(); -const arrowDownHtml = ReactDOMServer.renderToString(); +const arrowRightHtml = ReactDOMServer.renderToString(); +const arrowDownHtml = ReactDOMServer.renderToString(); function handleDrop(e, editor) { let dropDetails = null; @@ -158,7 +159,9 @@ const defaultExtensions = [ autoCompleteCompartment.of([]), EditorView.clipboardOutputFilter.of((text, state)=>{ return CustomEditorView.getSelectionFromState(state); - }) + }), + // Custom folding service for PL/pgSQL + plpgsqlFoldService, ]; export default function Editor({ diff --git a/web/pgadmin/static/js/components/ReactCodeMirror/extensions/dialect.js b/web/pgadmin/static/js/components/ReactCodeMirror/extensions/dialect.js index f2b227ddb..af5af70ba 100644 --- a/web/pgadmin/static/js/components/ReactCodeMirror/extensions/dialect.js +++ b/web/pgadmin/static/js/components/ReactCodeMirror/extensions/dialect.js @@ -1,4 +1,5 @@ import { SQLDialect, PostgreSQL } from '@codemirror/lang-sql'; +import { foldNodeProp } from '@codemirror/language'; const extraKeywords = 'unsafe'; const keywords = PostgreSQL.spec.keywords.replace(/\b\w\b/, '') + ' ' + extraKeywords; @@ -10,5 +11,14 @@ const PgSQL = SQLDialect.define({ specialVar: '', keywords: keywords, types: PostgreSQL.spec.types, +}).configureLanguage({ + // Disable default folding behavior as it conflicts with custom folding + props: [ + foldNodeProp.add({ + Statement() { return null; }, + BlockComment() { return null; } + }), + ] }); + export default PgSQL; diff --git a/web/pgadmin/static/js/components/ReactCodeMirror/extensions/plpgsqlFoldService.js b/web/pgadmin/static/js/components/ReactCodeMirror/extensions/plpgsqlFoldService.js new file mode 100644 index 000000000..2da80fc64 --- /dev/null +++ b/web/pgadmin/static/js/components/ReactCodeMirror/extensions/plpgsqlFoldService.js @@ -0,0 +1,53 @@ +import { foldService } from '@codemirror/language'; + +function findRange(pair, state, startLine) { + let depth = 1; + const from = startLine.to; + + for (let i = startLine.number + 1; i <= state.doc.lines; i++) { + const line = state.doc.line(i); + const text = line.text.trim(); + + if (pair.start.test(text)) { + depth++; + } else if (pair.end.test(text)) { + depth--; + if (depth === 0) { + // Only fold if there is at least one line between the start and end. + if (i <= startLine.number + 1) { + return null; + } + + // leaving the closing keyword line visible after folding. + const to = state.doc.line(i - 1).to; + return { from, to }; + } + } + } + // No valid closing block found + return null; +} + + +const plpgsqlFoldService = foldService.of((state, startPos) => { + const startLine = state.doc.lineAt(startPos); + const startText = startLine.text.trim(); + + const foldPairs = [ + // Added 'i' flag for case-insensitivity + { start: /^BEGIN\b/i, end: /^END\b\s*;$/i }, + { start: /^IF\b/i, end: /^END IF\b\s*;$/i }, + { start: /^FOR\b/i, end: /^END LOOP\b\s*;$/i }, + { start: /^CASE\b/i, end: /^END CASE\b\s*;$/i } + ]; + + for (let pair of foldPairs) { + if (pair.start.test(startText)) { + return findRange(pair, state, startLine); + } + } + + return null; +}); + +export default plpgsqlFoldService; diff --git a/web/yarn.lock b/web/yarn.lock index b295919b3..3354bddb2 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -1497,9 +1497,9 @@ __metadata: languageName: node linkType: hard -"@codemirror/lang-sql@npm:^6.8.0": - version: 6.8.0 - resolution: "@codemirror/lang-sql@npm:6.8.0" +"@codemirror/lang-sql@npm:^6.9.0": + version: 6.9.0 + resolution: "@codemirror/lang-sql@npm:6.9.0" dependencies: "@codemirror/autocomplete": ^6.0.0 "@codemirror/language": ^6.0.0 @@ -1507,7 +1507,7 @@ __metadata: "@lezer/common": ^1.2.0 "@lezer/highlight": ^1.0.0 "@lezer/lr": ^1.0.0 - checksum: 1b5a3c8129b09f24039d8c0906fc4cb8d0f706a424a1d56721057bd1e647797c2b1240bb53eed9bf2bac5806a4e0363e555a3963f04c478efa05829890c537f7 + checksum: aca08c11b519f962e9a59e14dc6f54102deaa7a15a390c2dc50401234d33a1fd0c5d2436e6f1810a0363b6f2649480d28eb16a10a7e3f4221ffa3f130ef0672e languageName: node linkType: hard @@ -13637,7 +13637,7 @@ __metadata: "@babel/preset-react": ^7.12.13 "@babel/preset-typescript": ^7.24.7 "@codemirror/lang-json": ^6.0.1 - "@codemirror/lang-sql": ^6.8.0 + "@codemirror/lang-sql": ^6.9.0 "@date-io/core": ^3.0.0 "@date-io/date-fns": 3.x "@emotion/memoize": ^0.9.0