plsqlwks 0.1.5__tar.gz → 0.1.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. plsqlwks-0.1.7/MANIFEST.in +91 -0
  2. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/PKG-INFO +306 -14
  3. plsqlwks-0.1.7/PLUGINS.md +320 -0
  4. plsqlwks-0.1.7/QUICKSTART.md +288 -0
  5. plsqlwks-0.1.7/README.md +616 -0
  6. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/__init__.py +1 -1
  7. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/config/__init__.py +4 -0
  8. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/config/loader.py +18 -0
  9. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/config/models.py +14 -1
  10. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/config/settings.py +52 -14
  11. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/db/editing.py +51 -24
  12. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/db/execution.py +407 -67
  13. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/db/models.py +26 -1
  14. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/db/session.py +17 -0
  15. plsqlwks-0.1.7/plsqlwks/exporting.py +209 -0
  16. plsqlwks-0.1.7/plsqlwks/html_exporting.py +142 -0
  17. plsqlwks-0.1.7/plsqlwks/plugins/__init__.py +28 -0
  18. plsqlwks-0.1.7/plsqlwks/plugins/_result_export.py +162 -0
  19. plsqlwks-0.1.7/plsqlwks/plugins/api.py +122 -0
  20. plsqlwks-0.1.7/plsqlwks/plugins/csv_export.py +128 -0
  21. plsqlwks-0.1.7/plsqlwks/plugins/html_export.py +128 -0
  22. plsqlwks-0.1.7/plsqlwks/plugins/loader.py +181 -0
  23. plsqlwks-0.1.7/plsqlwks/plugins/xlsx_export.py +151 -0
  24. plsqlwks-0.1.7/plsqlwks/sqlbinds.py +373 -0
  25. plsqlwks-0.1.7/plsqlwks/sqlsplit.py +836 -0
  26. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/ui/__init__.py +1 -1
  27. plsqlwks-0.1.7/plsqlwks/ui/app.py +356 -0
  28. plsqlwks-0.1.7/plsqlwks/ui/application_controller.py +88 -0
  29. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/ui/browser.py +4 -0
  30. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/ui/buffer.py +166 -28
  31. plsqlwks-0.1.5/plsqlwks/ui/app_tabs_browser.py → plsqlwks-0.1.7/plsqlwks/ui/catalog.py +173 -137
  32. plsqlwks-0.1.7/plsqlwks/ui/command_dispatcher.py +37 -0
  33. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/ui/commands.py +28 -1
  34. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/ui/constants.py +4 -3
  35. plsqlwks-0.1.7/plsqlwks/ui/db_operations.py +396 -0
  36. plsqlwks-0.1.7/plsqlwks/ui/db_session.py +406 -0
  37. plsqlwks-0.1.7/plsqlwks/ui/db_worker.py +397 -0
  38. plsqlwks-0.1.7/plsqlwks/ui/dialogs.py +445 -0
  39. plsqlwks-0.1.7/plsqlwks/ui/documents.py +497 -0
  40. plsqlwks-0.1.5/plsqlwks/ui/app_editor.py → plsqlwks-0.1.7/plsqlwks/ui/editor_controller.py +359 -273
  41. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/ui/errors.py +178 -7
  42. plsqlwks-0.1.7/plsqlwks/ui/input_controller.py +269 -0
  43. plsqlwks-0.1.7/plsqlwks/ui/key_reader.py +51 -0
  44. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/ui/keys.py +6 -3
  45. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/ui/menu.py +11 -4
  46. plsqlwks-0.1.7/plsqlwks/ui/plugin_host.py +183 -0
  47. plsqlwks-0.1.7/plsqlwks/ui/ports.py +71 -0
  48. plsqlwks-0.1.7/plsqlwks/ui/query_controller.py +596 -0
  49. plsqlwks-0.1.5/plsqlwks/ui/app_render.py → plsqlwks-0.1.7/plsqlwks/ui/renderer.py +122 -43
  50. plsqlwks-0.1.5/plsqlwks/ui/app_results.py → plsqlwks-0.1.7/plsqlwks/ui/result_controller.py +210 -98
  51. plsqlwks-0.1.7/plsqlwks/ui/result_presenter.py +766 -0
  52. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/ui/results.py +39 -4
  53. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/ui/state.py +21 -0
  54. plsqlwks-0.1.7/plsqlwks/ui/viewport.py +256 -0
  55. plsqlwks-0.1.7/plsqlwks/xlsx_exporting.py +435 -0
  56. plsqlwks-0.1.7/plsqlwks.egg-info/SOURCES.txt +81 -0
  57. plsqlwks-0.1.7/plugin-requirements/csv-export/requirements.txt +1 -0
  58. plsqlwks-0.1.7/plugin-requirements/html-export/requirements.txt +1 -0
  59. plsqlwks-0.1.7/plugin-requirements/xlsx-export/requirements.txt +2 -0
  60. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/pyproject.toml +16 -6
  61. plsqlwks-0.1.7/pytest.ini +9 -0
  62. plsqlwks-0.1.7/rchar.py +151 -0
  63. plsqlwks-0.1.7/requirements.txt +1 -0
  64. plsqlwks-0.1.7/tests/conftest.py +216 -0
  65. plsqlwks-0.1.7/tests/fixtures/config_exports.txt +56 -0
  66. plsqlwks-0.1.7/tests/fixtures/ui_exports.txt +459 -0
  67. plsqlwks-0.1.5/README.md +0 -328
  68. plsqlwks-0.1.5/plsqlwks/sqlbinds.py +0 -163
  69. plsqlwks-0.1.5/plsqlwks/sqlsplit.py +0 -275
  70. plsqlwks-0.1.5/plsqlwks/ui/app.py +0 -154
  71. plsqlwks-0.1.5/plsqlwks/ui/app_db.py +0 -932
  72. plsqlwks-0.1.5/plsqlwks/ui/app_files.py +0 -468
  73. plsqlwks-0.1.5/plsqlwks/ui/app_input.py +0 -606
  74. plsqlwks-0.1.5/plsqlwks/ui/db_worker.py +0 -228
  75. plsqlwks-0.1.5/plsqlwks.egg-info/PKG-INFO +0 -351
  76. plsqlwks-0.1.5/plsqlwks.egg-info/SOURCES.txt +0 -67
  77. plsqlwks-0.1.5/plsqlwks.egg-info/dependency_links.txt +0 -1
  78. plsqlwks-0.1.5/plsqlwks.egg-info/entry_points.txt +0 -2
  79. plsqlwks-0.1.5/plsqlwks.egg-info/requires.txt +0 -8
  80. plsqlwks-0.1.5/plsqlwks.egg-info/top_level.txt +0 -1
  81. plsqlwks-0.1.5/tests/test_config_workspace.py +0 -818
  82. plsqlwks-0.1.5/tests/test_db_editing.py +0 -947
  83. plsqlwks-0.1.5/tests/test_db_helpers.py +0 -1860
  84. plsqlwks-0.1.5/tests/test_db_worker.py +0 -464
  85. plsqlwks-0.1.5/tests/test_oracle_integration.py +0 -631
  86. plsqlwks-0.1.5/tests/test_packaging.py +0 -264
  87. plsqlwks-0.1.5/tests/test_pty_keys.py +0 -101
  88. plsqlwks-0.1.5/tests/test_rchar.py +0 -14
  89. plsqlwks-0.1.5/tests/test_sqlbinds.py +0 -133
  90. plsqlwks-0.1.5/tests/test_sqlsplit.py +0 -343
  91. plsqlwks-0.1.5/tests/test_ui_flows.py +0 -2863
  92. plsqlwks-0.1.5/tests/test_ui_keys.py +0 -4799
  93. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/license.txt +0 -0
  94. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/__main__.py +0 -0
  95. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/config/paths.py +0 -0
  96. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/config/session.py +0 -0
  97. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/db/__init__.py +0 -0
  98. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/db/explain.py +0 -0
  99. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/db/health.py +0 -0
  100. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/db/metadata.py +0 -0
  101. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/db/sql_analysis.py +0 -0
  102. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/db/transactions.py +0 -0
  103. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/ui/clipboard.py +0 -0
  104. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/ui/completion.py +0 -0
  105. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/ui/display.py +0 -0
  106. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/ui/help.py +0 -0
  107. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/ui/sql.py +0 -0
  108. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/ui/syntax.py +0 -0
  109. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/plsqlwks/workspace.py +0 -0
  110. {plsqlwks-0.1.5 → plsqlwks-0.1.7}/setup.cfg +0 -0
@@ -0,0 +1,91 @@
1
+ # Source distributions are intentionally allowlisted. Keep this list in sync
2
+ # with the exact artifact manifests asserted by tests/test_packaging.py.
3
+ global-exclude *
4
+
5
+ include MANIFEST.in
6
+ include PLUGINS.md
7
+ include QUICKSTART.md
8
+ include README.md
9
+ include license.txt
10
+ include pyproject.toml
11
+ include pytest.ini
12
+ include rchar.py
13
+ include requirements.txt
14
+
15
+ include plsqlwks/__init__.py
16
+ include plsqlwks/__main__.py
17
+ include plsqlwks/exporting.py
18
+ include plsqlwks/html_exporting.py
19
+ include plsqlwks/sqlbinds.py
20
+ include plsqlwks/sqlsplit.py
21
+ include plsqlwks/workspace.py
22
+ include plsqlwks/xlsx_exporting.py
23
+
24
+ include plsqlwks/config/__init__.py
25
+ include plsqlwks/config/loader.py
26
+ include plsqlwks/config/models.py
27
+ include plsqlwks/config/paths.py
28
+ include plsqlwks/config/session.py
29
+ include plsqlwks/config/settings.py
30
+
31
+ include plsqlwks/db/__init__.py
32
+ include plsqlwks/db/editing.py
33
+ include plsqlwks/db/execution.py
34
+ include plsqlwks/db/explain.py
35
+ include plsqlwks/db/health.py
36
+ include plsqlwks/db/metadata.py
37
+ include plsqlwks/db/models.py
38
+ include plsqlwks/db/session.py
39
+ include plsqlwks/db/sql_analysis.py
40
+ include plsqlwks/db/transactions.py
41
+
42
+ include plsqlwks/plugins/__init__.py
43
+ include plsqlwks/plugins/_result_export.py
44
+ include plsqlwks/plugins/api.py
45
+ include plsqlwks/plugins/csv_export.py
46
+ include plsqlwks/plugins/html_export.py
47
+ include plsqlwks/plugins/loader.py
48
+ include plsqlwks/plugins/xlsx_export.py
49
+
50
+ include plsqlwks/ui/__init__.py
51
+ include plsqlwks/ui/app.py
52
+ include plsqlwks/ui/application_controller.py
53
+ include plsqlwks/ui/browser.py
54
+ include plsqlwks/ui/buffer.py
55
+ include plsqlwks/ui/catalog.py
56
+ include plsqlwks/ui/clipboard.py
57
+ include plsqlwks/ui/command_dispatcher.py
58
+ include plsqlwks/ui/commands.py
59
+ include plsqlwks/ui/completion.py
60
+ include plsqlwks/ui/constants.py
61
+ include plsqlwks/ui/db_operations.py
62
+ include plsqlwks/ui/db_session.py
63
+ include plsqlwks/ui/db_worker.py
64
+ include plsqlwks/ui/dialogs.py
65
+ include plsqlwks/ui/display.py
66
+ include plsqlwks/ui/documents.py
67
+ include plsqlwks/ui/editor_controller.py
68
+ include plsqlwks/ui/errors.py
69
+ include plsqlwks/ui/help.py
70
+ include plsqlwks/ui/input_controller.py
71
+ include plsqlwks/ui/key_reader.py
72
+ include plsqlwks/ui/keys.py
73
+ include plsqlwks/ui/menu.py
74
+ include plsqlwks/ui/plugin_host.py
75
+ include plsqlwks/ui/ports.py
76
+ include plsqlwks/ui/query_controller.py
77
+ include plsqlwks/ui/renderer.py
78
+ include plsqlwks/ui/result_controller.py
79
+ include plsqlwks/ui/result_presenter.py
80
+ include plsqlwks/ui/results.py
81
+ include plsqlwks/ui/sql.py
82
+ include plsqlwks/ui/state.py
83
+ include plsqlwks/ui/syntax.py
84
+ include plsqlwks/ui/viewport.py
85
+
86
+ include plugin-requirements/csv-export/requirements.txt
87
+ include plugin-requirements/html-export/requirements.txt
88
+ include plugin-requirements/xlsx-export/requirements.txt
89
+ include tests/conftest.py
90
+ include tests/fixtures/config_exports.txt
91
+ include tests/fixtures/ui_exports.txt
@@ -1,11 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plsqlwks
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: An ncurses SQL and PL/SQL workspace for Oracle databases.
5
+ Author: unu2000
5
6
  License-Expression: LicenseRef-plsqlwks-Donationware
6
7
  Project-URL: Repository, https://gitlab.com/unununu/plsqlwks
7
8
  Project-URL: Issues, https://gitlab.com/unununu/plsqlwks/-/issues
8
9
  Project-URL: Changelog, https://gitlab.com/unununu/plsqlwks/-/blob/main/CHANGELOG.md
10
+ Project-URL: Ko-fi, https://ko-fi.com/unu2000
9
11
  Classifier: Programming Language :: Python :: 3
10
12
  Classifier: Programming Language :: Python :: 3 :: Only
11
13
  Classifier: Topic :: Database
@@ -13,6 +15,8 @@ Requires-Python: >=3.10
13
15
  Description-Content-Type: text/markdown
14
16
  License-File: license.txt
15
17
  Requires-Dist: oracledb>=2.0
18
+ Provides-Extra: xlsx
19
+ Requires-Dist: openpyxl>=3.1; extra == "xlsx"
16
20
  Provides-Extra: dev
17
21
  Requires-Dist: build>=1.2; extra == "dev"
18
22
  Requires-Dist: mypy>=1.10; extra == "dev"
@@ -25,8 +29,19 @@ Dynamic: license-file
25
29
 
26
30
  An ncurses SQL and PL/SQL workspace for Oracle databases.
27
31
 
32
+ Author and support: [unu2000 on Ko-fi](https://ko-fi.com/unu2000).
33
+
34
+ The supported and continuously tested database releases are Oracle Database
35
+ 19c and Oracle AI Database 26ai. See the
36
+ [Oracle compatibility matrix](https://gitlab.com/unununu/plsqlwks/-/blob/main/COMPATIBILITY.md) for the tested connection and
37
+ privilege profiles, safety gate, and integration-test setup.
38
+
28
39
  ![PLSQLWKS PREVIEW](https://gitlab.com/unununu/plsqlwks/-/raw/main/img/preview.png)
29
40
 
41
+ New here? Follow the [installation-to-first-query quick start](QUICKSTART.md)
42
+ for copy-ready connection setup, screenshots, practical SQL workflows,
43
+ troubleshooting, and a complete installed-plugin example.
44
+
30
45
  ## Connection
31
46
 
32
47
  The default connection is:
@@ -83,16 +98,52 @@ python3 -m pip install .
83
98
  Run the default test and lint checks from a development checkout with:
84
99
 
85
100
  ```bash
86
- python3 -m pytest -q
101
+ python3 -m pytest -q --maxfail=1
87
102
  python3 -m ruff check .
88
103
  ```
89
104
 
105
+ Plugin API and built-in plugin tests are optional and are not part of the
106
+ default core test run. Run them with either opt-in form:
107
+
108
+ ```bash
109
+ PLSQLWKS_TEST_PLUGINS=1 python3 -m pytest -q --maxfail=1
110
+ python3 -m pytest -q --maxfail=1 -m plugin
111
+ ```
112
+
113
+ The CSV and HTML export plugins have no additional dependencies. XLSX export
114
+ uses the optional `openpyxl` package, kept out of the base PLSQLWKS runtime.
115
+ Install a distribution with XLSX support through the standard extra:
116
+
117
+ ```bash
118
+ python3 -m pip install 'plsqlwks[xlsx]'
119
+ ```
120
+
121
+ For an editable development checkout, install the development and XLSX extras
122
+ together with `python3 -m pip install -e '.[dev,xlsx]'`. The repository also
123
+ retains `plugin-requirements/xlsx-export/requirements.txt` as a compatible
124
+ fallback for existing checkout automation.
125
+
90
126
  Oracle integration tests require the connection environment variables shown
91
- above and an accessible password file. Enable every test group with:
127
+ above and a nonempty regular password file. Setting `PLSQLWKS_TEST_ORACLE=1`
128
+ is an explicit opt-in, so invalid or missing credentials fail collection rather
129
+ than silently skipping the live suite. Set `PLSQLWKS_TEST_ORACLE_TARGET` to
130
+ `19c` or `26ai` when the test run must verify that it reached the intended
131
+ server release:
92
132
 
93
133
  ```bash
94
- PLSQLWKS_TEST_ORACLE=1 PLSQLWKS_TEST_PTY=1 PLSQLWKS_TEST_SLOW=1 \
95
- python3 -m pytest -q -rs
134
+ PLSQLWKS_TEST_ORACLE=1 PLSQLWKS_TEST_ORACLE_TARGET=19c \
135
+ python3 -m pytest -q -m oracle --maxfail=1
136
+ ```
137
+
138
+ Omit the target for an ad-hoc run against another reachable Oracle database.
139
+ The complete release-gating setup, including the developer, DML-only, and
140
+ read-only profiles, is documented in the
141
+ [Oracle compatibility matrix](https://gitlab.com/unununu/plsqlwks/-/blob/main/COMPATIBILITY.md).
142
+ Enable every optional test group with:
143
+
144
+ ```bash
145
+ PLSQLWKS_TEST_ORACLE=1 PLSQLWKS_TEST_PLUGINS=1 PLSQLWKS_TEST_PTY=1 PLSQLWKS_TEST_SLOW=1 \
146
+ python3 -m pytest -q -rs --maxfail=1
96
147
  ```
97
148
 
98
149
  ## Run
@@ -113,6 +164,11 @@ The CLI option takes precedence over `PLSQLWKS_WORKSPACE`. For compatibility, a
113
164
  source checkout that already contains a configured `workspace/` continues to
114
165
  use it and displays a migration notice; files are never moved automatically.
115
166
 
167
+ Fresh workspaces start in manual transaction mode and write
168
+ `[database] autocommit = no` to their generated `config.ini`. Existing explicit
169
+ `yes` or `no` settings are preserved; a missing or malformed setting uses the
170
+ safe manual fallback.
171
+
116
172
  Choose the initial transaction mode for one invocation with `--manual` or
117
173
  `--autocommit`. These options override `[database] autocommit` from the active
118
174
  `config.ini`:
@@ -153,9 +209,21 @@ explicitly, so Oracle rolls its uncommitted work back.
153
209
  If the old connection is already dead and reports an error while closing,
154
210
  plsqlwks still attempts the replacement connection and reports the close error
155
211
  as a warning after a successful reconnect.
212
+ An unexpected connection loss is shown in the header and status bar. Rows that
213
+ were already loaded remain available for viewing, but paging, editing, and
214
+ inserting are disabled until reconnect because their live cursor/session state
215
+ is no longer valid. If manual work may have been pending, the status explicitly
216
+ warns that the transaction outcome is unknown.
156
217
 
157
218
  After a successful quit, plsqlwks records the open file-backed tabs, active tab, and cursor positions in the managed `[session.tabs]` section of `config.ini`. Fresh platform-default workspaces use the user-config directory; explicitly selected and legacy workspaces keep `config.ini` inside the workspace for compatibility. On the next start the app tries each saved path independently, silently skips files that are missing or unreadable, and leaves the initial empty tab in place when none can be opened. A saved cursor position that no longer exists in its file starts at the beginning instead. Untitled, template, and generated schema tabs are not persisted because they do not have a file to reopen.
158
219
 
220
+ Worksheet and configuration saves replace their destination atomically while
221
+ preserving an existing file's POSIX permission bits. A
222
+ file-backed worksheet also detects when its file was changed or deleted outside
223
+ plsqlwks and asks whether to overwrite it, save under another name, or cancel.
224
+ Undo and redo update the unsaved-change marker by comparing the current content
225
+ with the last successful save.
226
+
159
227
  ## Keys
160
228
 
161
229
  ### Global
@@ -169,9 +237,9 @@ After a successful quit, plsqlwks records the open file-backed tabs, active tab,
169
237
  | `F8` | Toggle result grid / row-detail output |
170
238
  | `F9` | Show/focus/hide schema browser |
171
239
  | `F12` | Choose autocommit or manual transaction mode |
172
- | `Ctrl-Up` / `Ctrl-Down` | Scroll the focused pane or visible DBMS_OUTPUT |
240
+ | `Ctrl-Up` / `Ctrl-Down` | Scroll the focused pane or visible DBMS_OUTPUT by one line |
173
241
  | `Ctrl-W` | Close current file tab |
174
- | `Ctrl-PageUp` / `Ctrl-PageDown` | Switch file tabs |
242
+ | `Ctrl-PageUp` / `Ctrl-PageDown` | Scroll focused results by one page; otherwise switch file tabs |
175
243
  | `Alt-1`..`Alt-9` | Jump to visible file tab |
176
244
  | `Ctrl-Q` | Quit |
177
245
  | `Ctrl-C` while running | Interrupt the active database operation |
@@ -197,14 +265,14 @@ After a successful quit, plsqlwks records the open file-backed tabs, active tab,
197
265
  | `Delete` | Delete at the cursor |
198
266
  | `Ctrl-Backspace` / `Ctrl-Delete` | Delete the previous/next word |
199
267
  | `Enter` | Insert a new line |
200
- | `Tab` | Focus the result table |
268
+ | `Tab` | Focus the results or DBMS_OUTPUT pane |
201
269
  | `Shift-Tab` | Autocomplete keywords, schema objects, and columns |
202
270
  | `F2` / `Ctrl-S` | Save buffer |
203
271
  | `F3` / `Ctrl-O` | Open file |
204
272
  | `F4` | New template |
205
273
  | `F5` / `Ctrl-Enter` / `Alt-X` | Execute selected SQL or current statement |
206
274
  | `F11` | Execute selected SQL or whole buffer as a script |
207
- | `Alt-G` | Generate SELECT, INSERT, or UPDATE with table columns |
275
+ | `Alt-G` | Generate SELECT, INSERT, or UPDATE with table or view columns |
208
276
  | `Alt-+` | Refresh autocomplete metadata cache |
209
277
  | `Alt-R` | Rename current buffer |
210
278
  | `Ctrl-T` | New file tab |
@@ -223,7 +291,18 @@ After a successful quit, plsqlwks records the open file-backed tabs, active tab,
223
291
  | `Ctrl+=` | Reconnect |
224
292
 
225
293
  Use `/` on a line by itself after PL/SQL objects or anonymous blocks when running a script.
226
- Execution and explain errors report the mapped editor line and column, then move the cursor to that location when Oracle provides one.
294
+ Script execution reports statement progress as `n/total` and stops at the first
295
+ failed statement while preserving earlier results. Scripts are sent directly to
296
+ Oracle, not through SQL*Plus or SQLcl: client commands such as `PROMPT`, `SPOOL`,
297
+ and `SET SERVEROUTPUT`, and `&`/`&&` substitution variables, are rejected before
298
+ bind prompts or execution. Oracle SQL statements such as `SET TRANSACTION`
299
+ remain supported.
300
+ Execution and explain errors report mapped editor line and column diagnostics.
301
+ The cursor moves only when the buffer still matches the source that was sent to
302
+ Oracle; if it changed while the operation ran, the result warns about the stale
303
+ source instead. Use `Alt-O -> Editor -> Next execution diagnostic` or
304
+ `Previous execution diagnostic` to visit additional locations from the most
305
+ recent unchanged source.
227
306
  When executed or explained SQL contains bind variables such as `:id`, plsqlwks opens a text box for each value and sends the answers as Oracle bind parameters.
228
307
  Unquoted bind names are case-insensitive, so `:id` and `:ID` share one prompt and value. Quoted bind names remain case-sensitive.
229
308
  Set `[database] remember_bind_values = yes` in the active `config.ini` to prefill future bind prompts with values entered earlier in the same app session.
@@ -270,6 +349,7 @@ Read-only mode is a client-side guardrail that rejects statements which the SQL
270
349
  | `Home` / `End` | Move to the first/last result-grid column |
271
350
  | `Ctrl-Home` / `Ctrl-End` | Move to the first/last result-grid row |
272
351
  | `F8` | Toggle result grid / row-detail output |
352
+ | `Ctrl-C` | Copy the selected result cell to the clipboard |
273
353
  | `F10` | View the full selected result cell |
274
354
  | `Enter` | Edit the selected ROWID-backed result cell when available |
275
355
  | `INS` | Prepare a draft insert row for ROWID-backed results |
@@ -284,7 +364,11 @@ unquoted current-schema table that include `ROWID`, press `Enter` on a directly
284
364
  selected unquoted table column to update it by rowid. Press `INS` in the grid to
285
365
  prepare a draft row at the top of the grid, edit its cells with `Enter`, use
286
366
  `Ctrl-Alt-C` to insert it, or use `Esc` to cancel it.
287
- Database null values are displayed and entered as `<NULL>` in editable results. Plain `NULL` is stored as literal text.
367
+ While entering a cell, use Left/Right or Home/End to move within the existing
368
+ text and Backspace/Delete to edit around the cursor. DATE and TIMESTAMP cells
369
+ offer a choice between an ISO value and database-side `SYSDATE`.
370
+ Database null values are displayed and entered as `<NULL>` in editable results.
371
+ Plain `NULL` is stored as literal text.
288
372
  Grid edits compare the selected cell's originally loaded typed value as well as
289
373
  the `ROWID`. If another session changes that cell or deletes the row first, the
290
374
  edit is rejected and the query must be refreshed. Changes to other cells in the
@@ -297,8 +381,159 @@ precision above six, are read-only in the grid.
297
381
  Displayed CLOB and BLOB values are limited to the first 65,536 characters or
298
382
  bytes and include a truncation marker with the full size. Truncated LOB cells
299
383
  cannot be edited safely. Schema-browser DDL is always read in full.
384
+ DBMS_OUTPUT is collected after each statement and after every fetched result
385
+ page without replacing a real query grid. Output-read or cursor-cleanup failures
386
+ are displayed as warnings while preserving successfully fetched rows. PL/SQL
387
+ compiler warnings are also retained with successful statement results; compiler
388
+ errors remain execution failures with navigable source locations.
300
389
  `F7` cycles a grid fullscreen view that starts with the table header on the first terminal line and uses the last line for data, an editor-only fullscreen view, and a split layout with 2/3 editor and 1/3 data grid.
301
390
 
391
+ To export the active table, use one of these command paths:
392
+
393
+ - `Alt-O -> Results -> Export loaded rows to CSV`
394
+ - `Alt-O -> Results -> Export loaded rows to HTML`
395
+ - `Alt-O -> Results -> Export loaded rows to XLSX`
396
+
397
+ All three built-in Plugin API commands write the columns and exactly the rows
398
+ currently loaded in the grid; they never fetch additional result pages.
399
+ Relative names and the timestamped default name are resolved under the active
400
+ workspace's `results/` directory; absolute paths are also accepted, and an
401
+ existing file requires confirmation before replacement. Commit or cancel an
402
+ active insert draft before exporting so its temporary row cannot be included.
403
+ Each enabled export remains available in read-only mode because it does not
404
+ execute SQL or change a transaction.
405
+
406
+ The bundled exporters can be enabled independently in the active `config.ini`:
407
+
408
+ ```ini
409
+ [plugin.csv-export]
410
+ enabled = yes
411
+
412
+ [plugin.html-export]
413
+ enabled = yes
414
+
415
+ [plugin.xlsx-export]
416
+ enabled = yes
417
+ ```
418
+
419
+ Set an exporter's `enabled` value to `no` to omit its command from the
420
+ **Results** menu. A missing or malformed value defaults to enabled so existing
421
+ configurations keep all three commands. These switches affect only the bundled
422
+ exporters, not installed entry-point plugins, and take effect after restarting
423
+ PLSQLWKS.
424
+
425
+ The HTML command writes a standalone UTF-8 HTML5 document with column headings
426
+ and table rows, followed by the loaded-row count and any additional-row notice.
427
+ The result title is used only as browser-tab document metadata, not as a visible
428
+ heading. Document titles, column headers, and cell values are escaped as
429
+ untrusted text. The document contains a static embedded stylesheet, no
430
+ JavaScript, and no external resources. It reports when more rows are available
431
+ in PLSQLWKS but does not fetch them, and it does not open a browser after export.
432
+
433
+ The XLSX command writes one `Query result` worksheet with a header row and the
434
+ currently loaded display rows. The header row is frozen by default so it stays
435
+ visible while scrolling. Genuine source numbers become native Excel
436
+ numeric cells when they fit Excel's range and 15-significant-digit precision;
437
+ fixed-point scale such as `10.50` is preserved. Numeric-looking character data
438
+ and unsafe-precision numbers remain exact text and can still receive Excel's
439
+ number-as-text warning. Headers and other values, including text beginning with
440
+ `=`, `+`, `-`, or `@`, remain literal strings and are never interpreted as
441
+ spreadsheet formulas. The workbook contains no macros, external links, or
442
+ fetched continuation rows and is not opened after export. XLSX support is
443
+ optional and obtains `openpyxl>=3.1` from the standard `plsqlwks[xlsx]` extra;
444
+ the repository plugin requirements file remains a source-checkout fallback.
445
+ Each column uses the larger of its bold column name or widest data value,
446
+ estimated with Calibri 11-compatible proportional glyph widths, clamped from 3
447
+ through 60 units, and given a 17-pixel fit margin. Wrapping remains based on
448
+ logical visual length: values over 60 visual units or containing explicit line
449
+ breaks are wrapped.
450
+
451
+ The HTML plugin accepts three plugin-owned environment settings. They are
452
+ captured when PLSQLWKS loads the plugin:
453
+
454
+ ```bash
455
+ PLSQLWKS_HTML_EXPORT_NULL_VALUE="(null)"
456
+ PLSQLWKS_HTML_EXPORT_THEME="dark"
457
+ PLSQLWKS_HTML_EXPORT_DATE_FORMAT="%d.%m.%Y"
458
+ ```
459
+
460
+ `PLSQLWKS_HTML_EXPORT_NULL_VALUE` replaces the exact `<NULL>` grid display
461
+ token and defaults to empty; set it to `<NULL>` or another marker to keep NULL
462
+ values visible. `PLSQLWKS_HTML_EXPORT_THEME` is `bright` (the default) or
463
+ `dark`; both select only bundled static CSS, and printing uses a readable
464
+ bright palette. `PLSQLWKS_HTML_EXPORT_DATE_FORMAT` is empty by default and
465
+ otherwise uses Python `strftime` syntax with the same conservative ISO-display
466
+ matching described for CSV below. These settings affect only generated HTML
467
+ and do not expand Plugin API v1.
468
+
469
+ The XLSX plugin has equivalent plugin-owned environment settings, captured when
470
+ PLSQLWKS loads it:
471
+
472
+ ```bash
473
+ PLSQLWKS_XLSX_EXPORT_NULL_VALUE="(null)"
474
+ PLSQLWKS_XLSX_EXPORT_THEME="dark"
475
+ PLSQLWKS_XLSX_EXPORT_DATE_FORMAT="%d.%m.%Y"
476
+ PLSQLWKS_XLSX_EXPORT_AUTO_FILTER="no"
477
+ PLSQLWKS_XLSX_EXPORT_AUTO_WIDTH="no"
478
+ PLSQLWKS_XLSX_EXPORT_FREEZE_TOP_ROW="no"
479
+ ```
480
+
481
+ `PLSQLWKS_XLSX_EXPORT_NULL_VALUE` replaces the exact `<NULL>` display token and
482
+ defaults to empty; set it to `<NULL>` or another marker to keep NULL values
483
+ visible. `PLSQLWKS_XLSX_EXPORT_THEME` selects the bundled `bright` (default) or
484
+ `dark` cell styles. `PLSQLWKS_XLSX_EXPORT_DATE_FORMAT` is empty by default and
485
+ otherwise applies Python `strftime` directives to the same strict ISO-shaped
486
+ display strings as CSV and HTML. Formatted date and text values remain literal
487
+ spreadsheet strings rather than formulas or typed Excel dates. The
488
+ `PLSQLWKS_XLSX_EXPORT_AUTO_FILTER` setting enables Excel's column filter
489
+ controls by default. It accepts case-insensitive, whitespace-tolerant `1`,
490
+ `yes`, `true`, or `on` to enable them and `0`, `no`, `false`, or `off` to
491
+ disable them; an unset or malformed value falls back to enabled. When enabled,
492
+ the filter range spans exactly the header and currently loaded rows, without
493
+ applying filter criteria or initially hiding any rows.
494
+ `PLSQLWKS_XLSX_EXPORT_AUTO_WIDTH` controls proportional sizing from the widest
495
+ header or loaded data value and uses the same boolean syntax. When filtering is
496
+ enabled, the header candidate includes three extra character units for the filter
497
+ dropdown. Auto-width defaults to enabled; disabling it leaves Excel's default
498
+ column widths while preserving the existing multiline and over-60-unit cell
499
+ wrapping. `PLSQLWKS_XLSX_EXPORT_FREEZE_TOP_ROW` uses the same boolean syntax,
500
+ defaults to enabled, and keeps row 1 visible while scrolling; disabling it
501
+ leaves the worksheet unfrozen. Freezing is independent of filtering and
502
+ automatic widths. These settings belong to the built-in XLSX plugin and do not
503
+ expand Plugin API v1.
504
+
505
+ The built-in CSV export formatting can also be customized in the active
506
+ `config.ini`:
507
+
508
+ ```ini
509
+ [plugin.csv-export]
510
+ separator = ,
511
+ null_value =
512
+ date_format =
513
+ protect_formulas = no
514
+ ```
515
+
516
+ `separator` must be one character and defaults to a comma. `null_value`
517
+ defaults to empty, replacing the exact `<NULL>` display value with an empty CSV
518
+ field. Set it to `<NULL>` or another marker to retain a visible value.
519
+ `date_format` defaults to empty, which preserves displayed date values. When
520
+ set, it uses Python `strftime` syntax and formats only calendar-valid,
521
+ full-string ISO display values shaped as `YYYY-MM-DD` or
522
+ `YYYY-MM-DD HH:MM:SS[.digits][+/-HH:MM]`, with one to six fractional digits;
523
+ other text is exported unchanged. This deliberately strict heuristic cannot
524
+ identify non-ISO or otherwise preformatted database date text, while matching
525
+ text-column values are indistinguishable from dates and are formatted too.
526
+
527
+ `protect_formulas` defaults to `no`, preserving the exact field content and
528
+ existing CSV representation. Set it to `yes` for CSV files intended to be
529
+ opened by people in spreadsheet software. The protected mode quotes every
530
+ field and prefixes formula-triggering values with a tab, including risky ASCII
531
+ and full-width prefixes and leading control characters. This also makes
532
+ legitimate leading signed values such as `-42` text and leaves the tab in data
533
+ seen by programmatic CSV readers. OWASP notes that no CSV neutralization is
534
+ universal across all spreadsheet applications and save/re-open workflows, so
535
+ validate this opt-in mode with the applications in use.
536
+
302
537
  ### Schema Browser
303
538
 
304
539
  The `F9` schema browser groups tables, views, procedures, functions, packages, triggers, sequences, indexes, and private synonyms from the current schema. Type while the browser is focused to filter object names with a case-insensitive substring match; groups without matches are hidden and matching groups expand automatically without changing their saved expansion state.
@@ -344,8 +579,65 @@ currently be loaded from it.
344
579
  workspace/
345
580
  sql/ saved SQL files
346
581
  plsql/ saved PL/SQL files
347
- results/ reserved output directory
582
+ results/ CSV, HTML, XLSX, and other result output
583
+ ```
584
+
585
+ The first launch creates the workspace folders and starter SQL/PLSQL files.
586
+
587
+ ## Plugin API
588
+
589
+ Plugin API version 1 is a deliberately small, command-only extension point.
590
+ See [PLUGINS.md](PLUGINS.md) for the focused plugin author and testing guide.
591
+ PLSQLWKS uses that same API for its built-in loaded-row CSV, HTML, and XLSX
592
+ exporters; adding formats requires no direct App integration or public API
593
+ expansion.
594
+ Installed Python packages can add commands to the `Alt-O` menu through the
595
+ standard `plsqlwks.plugins` entry-point group:
596
+
597
+ ```toml
598
+ [project.entry-points."plsqlwks.plugins"]
599
+ example = "example_package.plugin:create_plugin"
600
+ ```
601
+
602
+ A minimal plugin factory is:
603
+
604
+ ```python
605
+ from plsqlwks.plugins import Plugin, PluginCommand, PluginContext
606
+
607
+
608
+ def show_loaded_count(context: PluginContext) -> None:
609
+ result = context.get_active_result()
610
+ count = len(result.rows) if result is not None else 0
611
+ context.set_status(f"{count} row(s) are currently loaded")
612
+
613
+
614
+ def create_plugin() -> Plugin:
615
+ return Plugin(
616
+ id="example",
617
+ name="Example commands",
618
+ commands=(
619
+ PluginCommand(
620
+ id="show-loaded-count",
621
+ section="Results",
622
+ title="Show loaded row count",
623
+ handler=show_loaded_count,
624
+ ),
625
+ ),
626
+ )
348
627
  ```
349
628
 
350
- The first launch creates the workspace folders and starter SQL/PLSQL files. The
351
- terminal UI does not currently provide a query-export command.
629
+ The entry point must resolve to a zero-argument callable returning `Plugin`.
630
+ Handlers receive only `PluginContext`: it provides an immutable snapshot of the
631
+ active tabular result, the results directory, insert-draft detection, text and
632
+ overwrite prompts, status updates, and UI error reporting. API v1 does not
633
+ provide database execution, mutable results, the application or UI state,
634
+ keyboard registration, drawing, events, lifecycle hooks, background jobs,
635
+ settings schemas, hot reload, or workspace-local executable plugins.
636
+
637
+ The `[plugin.csv-export]` section and the documented HTML/XLSX environment
638
+ variables configure only their corresponding PLSQLWKS-supplied plugins. Plugin
639
+ API v1 does not provide a generic settings schema or pass these values to
640
+ installed third-party plugins.
641
+
642
+ Installed plugins are trusted, in-process Python code. They are not sandboxed;
643
+ install plugins only from sources you trust.