neon 3.0.0 → 3.1.1
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.
- package/README.md +70 -5
- package/dist/_chunks/auth_selection-DGgq6ifc.js +83 -0
- package/dist/_chunks/cmd_pipeline-CUbBO9U_.js +2818 -0
- package/dist/_chunks/credentials-MYdHdKah.js +188 -0
- package/dist/_chunks/env-NbA61JR3.js +585 -0
- package/dist/_chunks/env_services-Tz9G4JeT.js +531 -0
- package/dist/_chunks/paths-DMq0Lt7a.js +151 -0
- package/dist/_chunks/profiles-Ir29rqns.js +217 -0
- package/dist/_chunks/psql-DWH-kc69.js +2169 -0
- package/dist/_chunks/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/_chunks/secure_file-BucZj4yQ.js +39 -0
- package/dist/analytics.js +163 -207
- package/dist/api.js +815 -758
- package/dist/auth.js +121 -141
- package/dist/auth_context.js +39 -53
- package/dist/cli.js +4 -7
- package/dist/commands/api.js +220 -250
- package/dist/commands/api_keys.js +251 -314
- package/dist/commands/auth.js +283 -328
- package/dist/commands/bootstrap.js +372 -437
- package/dist/commands/branches.js +304 -455
- package/dist/commands/bucket.js +374 -514
- package/dist/commands/checkout.js +213 -298
- package/dist/commands/config.js +573 -690
- package/dist/commands/connection_string.js +137 -165
- package/dist/commands/data_api.js +238 -260
- package/dist/commands/databases.js +67 -76
- package/dist/commands/deploy.js +31 -25
- package/dist/commands/dev.js +639 -719
- package/dist/commands/diff.js +156 -200
- package/dist/commands/env.js +255 -305
- package/dist/commands/functions.js +275 -355
- package/dist/commands/index.js +70 -65
- package/dist/commands/init.js +84 -119
- package/dist/commands/inspect.js +55 -55
- package/dist/commands/ip_allow.js +88 -120
- package/dist/commands/link.js +874 -1019
- package/dist/commands/logs.js +291 -0
- package/dist/commands/neon_auth.js +725 -933
- package/dist/commands/operations.js +34 -25
- package/dist/commands/orgs.js +28 -18
- package/dist/commands/profile.js +615 -846
- package/dist/commands/projects.js +313 -373
- package/dist/commands/psql.js +60 -58
- package/dist/commands/roles.js +55 -58
- package/dist/commands/schema_diff.js +87 -131
- package/dist/commands/set_context.js +34 -26
- package/dist/commands/snapshots.js +288 -413
- package/dist/commands/status.js +41 -37
- package/dist/commands/user.js +21 -10
- package/dist/commands/vpc_endpoints.js +85 -113
- package/dist/config.js +7 -6
- package/dist/config_format.js +50 -66
- package/dist/config_template.js +128 -157
- package/dist/context.js +183 -235
- package/dist/current_branch_fast_path.js +40 -49
- package/dist/dev/env.js +2 -446
- package/dist/dev/functions.js +54 -68
- package/dist/dev/inputs.js +46 -58
- package/dist/dev/runtime.js +135 -164
- package/dist/dev/websocket.js +766 -959
- package/dist/env.js +27 -33
- package/dist/env_file.js +118 -132
- package/dist/env_services.js +2 -51
- package/dist/errors.js +57 -68
- package/dist/functions_api.js +45 -43
- package/dist/help.js +189 -140
- package/dist/index.js +182 -257
- package/dist/init/agents.js +137 -118
- package/dist/init/auth.js +58 -68
- package/dist/init/bootstrap.js +325 -396
- package/dist/init/build_config.js +4 -2
- package/dist/init/detect_agent.js +56 -101
- package/dist/init/editors.js +35 -52
- package/dist/init/enrich_output.js +51 -66
- package/dist/init/extension.js +134 -171
- package/dist/init/inspect.js +179 -266
- package/dist/init/interactive.js +510 -622
- package/dist/init/neonctl.js +117 -168
- package/dist/init/orchestrate.js +157 -173
- package/dist/init/phases/auth.js +188 -202
- package/dist/init/phases/cleanup.js +23 -23
- package/dist/init/phases/db.js +251 -277
- package/dist/init/phases/getting_started.js +213 -223
- package/dist/init/phases/mcp.js +174 -224
- package/dist/init/phases/migrations.js +247 -248
- package/dist/init/phases/neon_auth.js +114 -133
- package/dist/init/phases/setup.js +546 -703
- package/dist/init/phases/skills.js +75 -86
- package/dist/init/phases/status.js +72 -67
- package/dist/init/resolve_context.js +102 -99
- package/dist/init/route_command.js +91 -98
- package/dist/init/skills.js +174 -218
- package/dist/init/vsix.js +77 -99
- package/dist/log.js +17 -16
- package/dist/neon_services.js +104 -129
- package/dist/parameters.gen.js +481 -471
- package/dist/pkg.js +17 -19
- package/dist/profile_keys.js +44 -47
- package/dist/psql/cli.js +44 -47
- package/dist/psql/command/cmd_cond.js +231 -406
- package/dist/psql/command/cmd_connect.js +557 -764
- package/dist/psql/command/cmd_copy.js +728 -984
- package/dist/psql/command/cmd_describe.js +1499 -1688
- package/dist/psql/command/cmd_format.js +733 -905
- package/dist/psql/command/cmd_io.js +2 -2193
- package/dist/psql/command/cmd_lo.js +297 -359
- package/dist/psql/command/cmd_meta.js +727 -878
- package/dist/psql/command/cmd_misc.js +138 -172
- package/dist/psql/command/cmd_pipeline.js +2 -1148
- package/dist/psql/command/cmd_restrict.js +119 -155
- package/dist/psql/command/cmd_show.js +529 -688
- package/dist/psql/command/dispatch.js +260 -325
- package/dist/psql/command/inputQueue.js +35 -33
- package/dist/psql/command/shared.js +49 -63
- package/dist/psql/complete/filenames.js +90 -133
- package/dist/psql/complete/index.js +59 -97
- package/dist/psql/complete/matcher.js +236 -300
- package/dist/psql/complete/psqlVars.js +218 -223
- package/dist/psql/complete/queries.js +159 -177
- package/dist/psql/complete/rules.js +1493 -2299
- package/dist/psql/core/common.js +2 -1253
- package/dist/psql/core/help.js +456 -546
- package/dist/psql/core/mainloop.js +692 -1303
- package/dist/psql/core/prompt.js +391 -408
- package/dist/psql/core/settings.js +429 -644
- package/dist/psql/core/sqlHelp.js +480 -554
- package/dist/psql/core/startup.js +2 -846
- package/dist/psql/core/syncVars.js +67 -110
- package/dist/psql/core/variables.js +156 -278
- package/dist/psql/describe/formatters.js +884 -1285
- package/dist/psql/describe/processNamePattern.js +173 -260
- package/dist/psql/describe/queries.js +1368 -2403
- package/dist/psql/describe/versionGate.js +32 -41
- package/dist/psql/index.js +2 -2030
- package/dist/psql/io/history.js +232 -271
- package/dist/psql/io/input.js +103 -108
- package/dist/psql/io/lineEditor/buffer.js +238 -319
- package/dist/psql/io/lineEditor/complete.js +135 -213
- package/dist/psql/io/lineEditor/filename.js +139 -148
- package/dist/psql/io/lineEditor/index.js +653 -870
- package/dist/psql/io/lineEditor/keymap.js +544 -702
- package/dist/psql/io/lineEditor/vt100.js +294 -341
- package/dist/psql/io/pgpass.js +158 -187
- package/dist/psql/io/pgservice.js +146 -183
- package/dist/psql/io/psqlrc.js +328 -403
- package/dist/psql/print/aligned.js +1020 -1683
- package/dist/psql/print/asciidoc.js +180 -214
- package/dist/psql/print/crosstab.js +281 -442
- package/dist/psql/print/csv.js +48 -70
- package/dist/psql/print/html.js +195 -226
- package/dist/psql/print/json.js +75 -88
- package/dist/psql/print/latex.js +291 -364
- package/dist/psql/print/pager.js +171 -242
- package/dist/psql/print/troff.js +194 -226
- package/dist/psql/print/unaligned.js +69 -95
- package/dist/psql/print/units.js +167 -169
- package/dist/psql/scanner/slash.js +428 -483
- package/dist/psql/scanner/sql.js +445 -889
- package/dist/psql/scanner/stringutils.js +309 -379
- package/dist/psql/types/index.js +8 -7
- package/dist/psql/types/scanner.js +25 -22
- package/dist/psql/wire/connection.js +2042 -2803
- package/dist/psql/wire/copy.js +84 -100
- package/dist/psql/wire/notify.js +39 -59
- package/dist/psql/wire/pipeline.js +305 -518
- package/dist/psql/wire/protocol.js +349 -417
- package/dist/psql/wire/sasl.js +180 -265
- package/dist/psql/wire/tls.js +400 -561
- package/dist/storage_api.js +115 -129
- package/dist/test_utils/fixtures.js +94 -113
- package/dist/test_utils/oauth_server.js +10 -7
- package/dist/test_utils/project_dir.js +33 -0
- package/dist/utils/ai_gateway_notice.js +131 -162
- package/dist/utils/api_enums.js +21 -28
- package/dist/utils/auth.js +10 -4
- package/dist/utils/branch_notice.js +20 -19
- package/dist/utils/branch_picker.js +83 -89
- package/dist/utils/cli_name.js +15 -12
- package/dist/utils/compute_units.js +20 -27
- package/dist/utils/config_diff.js +127 -158
- package/dist/utils/enrichers.js +95 -148
- package/dist/utils/esbuild.js +130 -189
- package/dist/utils/flags.js +35 -47
- package/dist/utils/formats.js +8 -15
- package/dist/utils/git_diff.js +69 -80
- package/dist/utils/inspect_db.js +101 -143
- package/dist/utils/inspect_queries.js +179 -142
- package/dist/utils/middlewares.js +39 -45
- package/dist/utils/openapi.js +87 -99
- package/dist/utils/package_manager.js +312 -110
- package/dist/utils/point_in_time.js +49 -53
- package/dist/utils/psql.js +89 -106
- package/dist/utils/service_picker.js +55 -58
- package/dist/utils/string.js +5 -5
- package/dist/utils/ui.js +38 -55
- package/dist/utils/write_sync.js +26 -35
- package/dist/utils/zip.js +4 -3
- package/dist/writer.js +67 -87
- package/package.json +11 -6
- package/dist/_shared/auth_selection.js +0 -86
- package/dist/_shared/credentials.js +0 -209
- package/dist/_shared/env-core/env.js +0 -558
- package/dist/_shared/env-core/reuse-secrets.js +0 -223
- package/dist/_shared/paths.js +0 -148
- package/dist/_shared/profiles.js +0 -276
- package/dist/_shared/secure_file.js +0 -43
|
@@ -1,1748 +1,1085 @@
|
|
|
1
1
|
import { boolDisplayOf, renderCellValue } from "./units.js";
|
|
2
|
+
//#region src/psql/print/aligned.ts
|
|
2
3
|
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
// Public helpers: display-width primitives reused by other formats.
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
// East-Asian wide / fullwidth ranges from Unicode Standard Annex #11.
|
|
17
|
-
// Trimmed to the ranges psql's `ucs_wcwidth` considers width 2.
|
|
4
|
+
* Aligned tabular printer (psql's default output mode).
|
|
5
|
+
*
|
|
6
|
+
* Mirrors print.c `print_aligned_text` / `print_aligned_vertical`.
|
|
7
|
+
*
|
|
8
|
+
* Supports horizontal (default), expanded/vertical (`\x on`), and
|
|
9
|
+
* wrapped (`\pset format wrapped`) layouts. Borders 0..3 (0 = none,
|
|
10
|
+
* 1 = light internal rules, 2 = full box, 3 = heavy with row rules).
|
|
11
|
+
* Unicode mode (`unicodeBorderLineStyle === 'unicode'`) swaps the
|
|
12
|
+
* ASCII rule glyphs for U+2500-range box-drawing characters.
|
|
13
|
+
*/
|
|
18
14
|
const WIDE_RANGES = [
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
15
|
+
[4352, 4447],
|
|
16
|
+
[9001, 9002],
|
|
17
|
+
[11904, 12350],
|
|
18
|
+
[12353, 13311],
|
|
19
|
+
[13312, 19903],
|
|
20
|
+
[19968, 40959],
|
|
21
|
+
[40960, 42191],
|
|
22
|
+
[44032, 55203],
|
|
23
|
+
[63744, 64255],
|
|
24
|
+
[65040, 65049],
|
|
25
|
+
[65072, 65135],
|
|
26
|
+
[65280, 65376],
|
|
27
|
+
[65504, 65510],
|
|
28
|
+
[127744, 128591],
|
|
29
|
+
[129280, 129535],
|
|
30
|
+
[131072, 196605],
|
|
31
|
+
[196608, 262141]
|
|
36
32
|
];
|
|
37
|
-
// Combining marks: width 0. From Unicode general categories Mn/Me/Cf
|
|
38
|
-
// and the zero-width controls upstream treats as width 0.
|
|
39
33
|
const ZERO_RANGES = [
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
34
|
+
[768, 879],
|
|
35
|
+
[1155, 1161],
|
|
36
|
+
[1425, 1469],
|
|
37
|
+
[1471, 1471],
|
|
38
|
+
[1473, 1474],
|
|
39
|
+
[1476, 1477],
|
|
40
|
+
[1479, 1479],
|
|
41
|
+
[1552, 1562],
|
|
42
|
+
[1611, 1631],
|
|
43
|
+
[1648, 1648],
|
|
44
|
+
[1750, 1756],
|
|
45
|
+
[1759, 1764],
|
|
46
|
+
[1767, 1768],
|
|
47
|
+
[1770, 1773],
|
|
48
|
+
[1809, 1809],
|
|
49
|
+
[1840, 1866],
|
|
50
|
+
[1958, 1968],
|
|
51
|
+
[2027, 2035],
|
|
52
|
+
[2070, 2073],
|
|
53
|
+
[2075, 2083],
|
|
54
|
+
[2085, 2087],
|
|
55
|
+
[2089, 2093],
|
|
56
|
+
[2137, 2139],
|
|
57
|
+
[2259, 2273],
|
|
58
|
+
[2275, 2306],
|
|
59
|
+
[2362, 2362],
|
|
60
|
+
[2364, 2364],
|
|
61
|
+
[2369, 2376],
|
|
62
|
+
[2381, 2381],
|
|
63
|
+
[2385, 2391],
|
|
64
|
+
[2402, 2403],
|
|
65
|
+
[2433, 2433],
|
|
66
|
+
[2492, 2492],
|
|
67
|
+
[2497, 2500],
|
|
68
|
+
[2509, 2509],
|
|
69
|
+
[2530, 2531],
|
|
70
|
+
[2558, 2558],
|
|
71
|
+
[2561, 2562],
|
|
72
|
+
[2620, 2620],
|
|
73
|
+
[2625, 2626],
|
|
74
|
+
[2631, 2632],
|
|
75
|
+
[2635, 2637],
|
|
76
|
+
[2641, 2641],
|
|
77
|
+
[2672, 2673],
|
|
78
|
+
[2677, 2677],
|
|
79
|
+
[2689, 2690],
|
|
80
|
+
[2748, 2748],
|
|
81
|
+
[2753, 2757],
|
|
82
|
+
[2759, 2760],
|
|
83
|
+
[2765, 2765],
|
|
84
|
+
[2786, 2787],
|
|
85
|
+
[2810, 2815],
|
|
86
|
+
[2817, 2817],
|
|
87
|
+
[2876, 2876],
|
|
88
|
+
[2879, 2879],
|
|
89
|
+
[2881, 2884],
|
|
90
|
+
[2893, 2893],
|
|
91
|
+
[2901, 2902],
|
|
92
|
+
[2914, 2915],
|
|
93
|
+
[2946, 2946],
|
|
94
|
+
[3008, 3008],
|
|
95
|
+
[3021, 3021],
|
|
96
|
+
[3072, 3072],
|
|
97
|
+
[3076, 3076],
|
|
98
|
+
[3134, 3136],
|
|
99
|
+
[3142, 3144],
|
|
100
|
+
[3146, 3149],
|
|
101
|
+
[3157, 3158],
|
|
102
|
+
[3170, 3171],
|
|
103
|
+
[3201, 3201],
|
|
104
|
+
[3260, 3260],
|
|
105
|
+
[3263, 3263],
|
|
106
|
+
[3270, 3270],
|
|
107
|
+
[3276, 3277],
|
|
108
|
+
[3298, 3299],
|
|
109
|
+
[3328, 3329],
|
|
110
|
+
[3387, 3388],
|
|
111
|
+
[3393, 3396],
|
|
112
|
+
[3405, 3405],
|
|
113
|
+
[3426, 3427],
|
|
114
|
+
[3530, 3530],
|
|
115
|
+
[3538, 3540],
|
|
116
|
+
[3542, 3542],
|
|
117
|
+
[3633, 3633],
|
|
118
|
+
[3636, 3642],
|
|
119
|
+
[3655, 3662],
|
|
120
|
+
[3761, 3761],
|
|
121
|
+
[3764, 3772],
|
|
122
|
+
[3784, 3789],
|
|
123
|
+
[3864, 3865],
|
|
124
|
+
[3893, 3893],
|
|
125
|
+
[3895, 3895],
|
|
126
|
+
[3897, 3897],
|
|
127
|
+
[3953, 3966],
|
|
128
|
+
[3968, 3972],
|
|
129
|
+
[3974, 3975],
|
|
130
|
+
[3981, 3991],
|
|
131
|
+
[3993, 4028],
|
|
132
|
+
[4038, 4038],
|
|
133
|
+
[4151, 4151],
|
|
134
|
+
[4153, 4154],
|
|
135
|
+
[4237, 4237],
|
|
136
|
+
[4957, 4959],
|
|
137
|
+
[5906, 5908],
|
|
138
|
+
[5938, 5940],
|
|
139
|
+
[5970, 5971],
|
|
140
|
+
[6002, 6003],
|
|
141
|
+
[6068, 6069],
|
|
142
|
+
[6071, 6077],
|
|
143
|
+
[6086, 6086],
|
|
144
|
+
[6089, 6099],
|
|
145
|
+
[6109, 6109],
|
|
146
|
+
[6155, 6157],
|
|
147
|
+
[6277, 6278],
|
|
148
|
+
[6313, 6313],
|
|
149
|
+
[6432, 6434],
|
|
150
|
+
[6439, 6440],
|
|
151
|
+
[6450, 6450],
|
|
152
|
+
[6457, 6459],
|
|
153
|
+
[6679, 6680],
|
|
154
|
+
[6683, 6683],
|
|
155
|
+
[6742, 6742],
|
|
156
|
+
[6744, 6750],
|
|
157
|
+
[6752, 6752],
|
|
158
|
+
[6754, 6754],
|
|
159
|
+
[6757, 6764],
|
|
160
|
+
[6771, 6780],
|
|
161
|
+
[6783, 6783],
|
|
162
|
+
[6832, 6845],
|
|
163
|
+
[6912, 6915],
|
|
164
|
+
[6964, 6964],
|
|
165
|
+
[6966, 6970],
|
|
166
|
+
[6972, 6972],
|
|
167
|
+
[6978, 6978],
|
|
168
|
+
[7019, 7027],
|
|
169
|
+
[7040, 7041],
|
|
170
|
+
[7074, 7077],
|
|
171
|
+
[7080, 7081],
|
|
172
|
+
[7083, 7085],
|
|
173
|
+
[7142, 7142],
|
|
174
|
+
[7144, 7145],
|
|
175
|
+
[7149, 7149],
|
|
176
|
+
[7151, 7153],
|
|
177
|
+
[7212, 7219],
|
|
178
|
+
[7222, 7223],
|
|
179
|
+
[7376, 7378],
|
|
180
|
+
[7380, 7392],
|
|
181
|
+
[7394, 7400],
|
|
182
|
+
[7405, 7405],
|
|
183
|
+
[7412, 7412],
|
|
184
|
+
[7416, 7417],
|
|
185
|
+
[7616, 7673],
|
|
186
|
+
[7675, 7679],
|
|
187
|
+
[8203, 8207],
|
|
188
|
+
[8234, 8238],
|
|
189
|
+
[8288, 8303],
|
|
190
|
+
[8400, 8432],
|
|
191
|
+
[11503, 11505],
|
|
192
|
+
[11647, 11647],
|
|
193
|
+
[11744, 11775],
|
|
194
|
+
[12330, 12333],
|
|
195
|
+
[12441, 12442],
|
|
196
|
+
[42607, 42610],
|
|
197
|
+
[42612, 42621],
|
|
198
|
+
[42654, 42655],
|
|
199
|
+
[42736, 42737],
|
|
200
|
+
[43010, 43010],
|
|
201
|
+
[43014, 43014],
|
|
202
|
+
[43019, 43019],
|
|
203
|
+
[43045, 43046],
|
|
204
|
+
[43204, 43205],
|
|
205
|
+
[43232, 43249],
|
|
206
|
+
[43302, 43309],
|
|
207
|
+
[43335, 43345],
|
|
208
|
+
[43392, 43394],
|
|
209
|
+
[43443, 43443],
|
|
210
|
+
[43446, 43449],
|
|
211
|
+
[43452, 43453],
|
|
212
|
+
[43493, 43493],
|
|
213
|
+
[43561, 43566],
|
|
214
|
+
[43569, 43570],
|
|
215
|
+
[43573, 43574],
|
|
216
|
+
[43587, 43587],
|
|
217
|
+
[43596, 43596],
|
|
218
|
+
[43644, 43644],
|
|
219
|
+
[43696, 43696],
|
|
220
|
+
[43698, 43700],
|
|
221
|
+
[43703, 43704],
|
|
222
|
+
[43710, 43711],
|
|
223
|
+
[43713, 43713],
|
|
224
|
+
[43756, 43757],
|
|
225
|
+
[43766, 43766],
|
|
226
|
+
[44005, 44005],
|
|
227
|
+
[44008, 44008],
|
|
228
|
+
[44013, 44013],
|
|
229
|
+
[64286, 64286],
|
|
230
|
+
[65024, 65039],
|
|
231
|
+
[65056, 65071],
|
|
232
|
+
[65279, 65279],
|
|
233
|
+
[65529, 65531],
|
|
234
|
+
[66045, 66045],
|
|
235
|
+
[66272, 66272],
|
|
236
|
+
[66422, 66426],
|
|
237
|
+
[68097, 68099],
|
|
238
|
+
[68101, 68102],
|
|
239
|
+
[68108, 68111],
|
|
240
|
+
[68152, 68154],
|
|
241
|
+
[68159, 68159],
|
|
242
|
+
[68325, 68326],
|
|
243
|
+
[917760, 917999]
|
|
250
244
|
];
|
|
251
245
|
const inRange = (cp, ranges) => {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
else
|
|
263
|
-
return true;
|
|
264
|
-
}
|
|
265
|
-
return false;
|
|
246
|
+
let lo = 0;
|
|
247
|
+
let hi = ranges.length - 1;
|
|
248
|
+
while (lo <= hi) {
|
|
249
|
+
const mid = lo + hi >> 1;
|
|
250
|
+
const entry = ranges[mid];
|
|
251
|
+
if (cp < entry[0]) hi = mid - 1;
|
|
252
|
+
else if (cp > entry[1]) lo = mid + 1;
|
|
253
|
+
else return true;
|
|
254
|
+
}
|
|
255
|
+
return false;
|
|
266
256
|
};
|
|
267
257
|
/**
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
258
|
+
* Visible terminal width of one Unicode code point.
|
|
259
|
+
*
|
|
260
|
+
* - C0/DEL control codes -> 0 (we don't try to render them; psql's
|
|
261
|
+
* `pg_wcwidth` returns -1 here, but treating them as 0 matches the
|
|
262
|
+
* way the value already passes through the rendering pipeline).
|
|
263
|
+
* - Zero-width combining marks / format chars -> 0.
|
|
264
|
+
* - East-Asian Wide / Fullwidth -> 2.
|
|
265
|
+
* - Everything else -> 1.
|
|
266
|
+
*/
|
|
277
267
|
const codePointWidth = (cp) => {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
return 0;
|
|
284
|
-
if (inRange(cp, WIDE_RANGES))
|
|
285
|
-
return 2;
|
|
286
|
-
return 1;
|
|
268
|
+
if (cp === 0) return 0;
|
|
269
|
+
if (cp < 32 || cp >= 127 && cp < 160) return 0;
|
|
270
|
+
if (inRange(cp, ZERO_RANGES)) return 0;
|
|
271
|
+
if (inRange(cp, WIDE_RANGES)) return 2;
|
|
272
|
+
return 1;
|
|
287
273
|
};
|
|
288
274
|
/**
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
return width;
|
|
275
|
+
* Compute the visible terminal width of a string. Iterates code points
|
|
276
|
+
* (not UTF-16 code units), so surrogate pairs count once. Newlines and
|
|
277
|
+
* tabs are passed through; callers should split by '\n' first when they
|
|
278
|
+
* need per-line width — matches `pg_wcssize`.
|
|
279
|
+
*/
|
|
280
|
+
const displayWidth = (text) => {
|
|
281
|
+
let width = 0;
|
|
282
|
+
for (const ch of text) width += codePointWidth(ch.codePointAt(0) ?? 0);
|
|
283
|
+
return width;
|
|
300
284
|
};
|
|
301
285
|
/**
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
const leftPad = pad >> 1;
|
|
320
|
-
const rightPad = pad - leftPad;
|
|
321
|
-
return " ".repeat(leftPad) + text + " ".repeat(rightPad);
|
|
286
|
+
* Pad `text` so its visible width is exactly `width`. Truncates the
|
|
287
|
+
* pre-existing string if it already exceeds `width` (caller is
|
|
288
|
+
* responsible for fitting; we do not truncate by default).
|
|
289
|
+
*
|
|
290
|
+
* For `center`, an odd remainder biases right (matches psql, which
|
|
291
|
+
* uses `nbspace / 2` for the left pad and `(nbspace + 1) / 2` for the
|
|
292
|
+
* right).
|
|
293
|
+
*/
|
|
294
|
+
const padToWidth = (text, width, alignment) => {
|
|
295
|
+
const w = displayWidth(text);
|
|
296
|
+
if (w >= width) return text;
|
|
297
|
+
const pad = width - w;
|
|
298
|
+
if (alignment === "left") return text + " ".repeat(pad);
|
|
299
|
+
if (alignment === "right") return " ".repeat(pad) + text;
|
|
300
|
+
const leftPad = pad >> 1;
|
|
301
|
+
const rightPad = pad - leftPad;
|
|
302
|
+
return " ".repeat(leftPad) + text + " ".repeat(rightPad);
|
|
322
303
|
};
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
//
|
|
335
|
-
// We don't have access to a live catalog at print time, so we maintain a
|
|
336
|
-
// curated set of well-known numeric/identifier OIDs that ship with stock PG.
|
|
337
|
-
// Custom domains over numeric types and contrib types not listed here will
|
|
338
|
-
// fall back to left-alignment — documented limitation.
|
|
339
|
-
// Exactly mirrors upstream psql's `column_type_alignment()` (common.c): the
|
|
340
|
-
// fixed set int2/int4/int8, float4/float8, numeric, oid, xid, cid, money.
|
|
341
|
-
// NOT interval / pg_lsn / xid8 — psql left-aligns those (review: minor
|
|
342
|
-
// divergences — we previously right-aligned interval & pg_lsn and omitted
|
|
343
|
-
// xid & cid).
|
|
344
|
-
const RIGHT_ALIGNED_OIDS = new Set([
|
|
345
|
-
20, // int8
|
|
346
|
-
21, // int2
|
|
347
|
-
23, // int4
|
|
348
|
-
26, // oid
|
|
349
|
-
28, // xid
|
|
350
|
-
29, // cid
|
|
351
|
-
700, // float4
|
|
352
|
-
701, // float8
|
|
353
|
-
790, // money (cash)
|
|
354
|
-
1700, // numeric
|
|
304
|
+
const RIGHT_ALIGNED_OIDS = /* @__PURE__ */ new Set([
|
|
305
|
+
20,
|
|
306
|
+
21,
|
|
307
|
+
23,
|
|
308
|
+
26,
|
|
309
|
+
28,
|
|
310
|
+
29,
|
|
311
|
+
700,
|
|
312
|
+
701,
|
|
313
|
+
790,
|
|
314
|
+
1700
|
|
355
315
|
]);
|
|
356
316
|
const isRightAlignedField = (oid) => RIGHT_ALIGNED_OIDS.has(oid);
|
|
357
317
|
const renderCell = (cell, nullPrint, numericLocale, dataTypeID, boolDisplay) => renderCellValue(cell, nullPrint, numericLocale, dataTypeID, boolDisplay);
|
|
358
318
|
const ASCII_GLYPHS = {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
319
|
+
hrule: "-",
|
|
320
|
+
vrule: "|",
|
|
321
|
+
topLeft: "+",
|
|
322
|
+
topMid: "+",
|
|
323
|
+
topRight: "+",
|
|
324
|
+
midLeft: "+",
|
|
325
|
+
midMid: "+",
|
|
326
|
+
midRight: "+",
|
|
327
|
+
botLeft: "+",
|
|
328
|
+
botMid: "+",
|
|
329
|
+
botRight: "+",
|
|
330
|
+
headerNlLeft: " ",
|
|
331
|
+
headerNlRight: "+",
|
|
332
|
+
wrapLeft: ".",
|
|
333
|
+
wrapRight: ".",
|
|
334
|
+
nlLeft: " ",
|
|
335
|
+
nlRight: "+",
|
|
336
|
+
wrapRightBorder: true,
|
|
337
|
+
midvruleNl: "|",
|
|
338
|
+
midvruleWrap: "|",
|
|
339
|
+
midvruleBlank: "|"
|
|
380
340
|
};
|
|
381
|
-
// `pg_asciiformat_old` — the legacy ASCII renderer kept around for
|
|
382
|
-
// `\pset linestyle old-ascii`. Two visible quirks vs the modern ASCII
|
|
383
|
-
// glyphs:
|
|
384
|
-
// - left-side `+` markers in headers (header_nl_left), no right-side
|
|
385
|
-
// marker on either headers or data, and `wrap_right_border=false`
|
|
386
|
-
// (no trailing marker at the table edge);
|
|
387
|
-
// - the column separator on continuation rows changes — `:` when the
|
|
388
|
-
// joining cell has more `\n`-split lines below, `;` when it has more
|
|
389
|
-
// wrap-split lines, ` ` when the cell is exhausted (`midvrule_blank`).
|
|
390
341
|
const OLD_ASCII_GLYPHS = {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
342
|
+
hrule: "-",
|
|
343
|
+
vrule: "|",
|
|
344
|
+
topLeft: "+",
|
|
345
|
+
topMid: "+",
|
|
346
|
+
topRight: "+",
|
|
347
|
+
midLeft: "+",
|
|
348
|
+
midMid: "+",
|
|
349
|
+
midRight: "+",
|
|
350
|
+
botLeft: "+",
|
|
351
|
+
botMid: "+",
|
|
352
|
+
botRight: "+",
|
|
353
|
+
headerNlLeft: "+",
|
|
354
|
+
headerNlRight: " ",
|
|
355
|
+
wrapLeft: " ",
|
|
356
|
+
wrapRight: " ",
|
|
357
|
+
nlLeft: " ",
|
|
358
|
+
nlRight: " ",
|
|
359
|
+
wrapRightBorder: false,
|
|
360
|
+
midvruleNl: ":",
|
|
361
|
+
midvruleWrap: ";",
|
|
362
|
+
midvruleBlank: " "
|
|
412
363
|
};
|
|
413
|
-
// Light box-drawing glyphs (the only Unicode variant we expose today —
|
|
414
|
-
// `unicode_border_linestyle=double` / `unicode_column_linestyle=double` /
|
|
415
|
-
// `unicode_header_linestyle=double` are parsed by `cmd_format.ts` but the
|
|
416
|
-
// shared `Unicode2LineStyle` slot in `PrintTableOpts` only carries
|
|
417
|
-
// `ascii | unicode`, so all three settings collapse onto these "single"
|
|
418
|
-
// glyphs. Adding the double variant requires extending the type to keep
|
|
419
|
-
// `single`/`double` distinct (or carrying a separate side-channel field) —
|
|
420
|
-
// out of scope for this pass.
|
|
421
|
-
//
|
|
422
|
-
// Codepoints (verified against upstream `unicode_style` in print.c):
|
|
423
|
-
// U+2500 ─ Box Drawings Light Horizontal (hrule)
|
|
424
|
-
// U+2502 │ Box Drawings Light Vertical (vrule)
|
|
425
|
-
// U+250C ┌ Box Drawings Light Down and Right (topLeft)
|
|
426
|
-
// U+252C ┬ Box Drawings Light Down and Horizontal (topMid)
|
|
427
|
-
// U+2510 ┐ Box Drawings Light Down and Left (topRight)
|
|
428
|
-
// U+251C ├ Box Drawings Light Vertical and Right (midLeft)
|
|
429
|
-
// U+253C ┼ Box Drawings Light Vertical and Horiz. (midMid)
|
|
430
|
-
// U+2524 ┤ Box Drawings Light Vertical and Left (midRight)
|
|
431
|
-
// U+2514 └ Box Drawings Light Up and Right (botLeft)
|
|
432
|
-
// U+2534 ┴ Box Drawings Light Up and Horizontal (botMid)
|
|
433
|
-
// U+2518 ┘ Box Drawings Light Up and Left (botRight)
|
|
434
364
|
const UNICODE_GLYPHS = {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
midvruleNl: "│",
|
|
457
|
-
midvruleWrap: "│",
|
|
458
|
-
midvruleBlank: "│",
|
|
365
|
+
hrule: "─",
|
|
366
|
+
vrule: "│",
|
|
367
|
+
topLeft: "┌",
|
|
368
|
+
topMid: "┬",
|
|
369
|
+
topRight: "┐",
|
|
370
|
+
midLeft: "├",
|
|
371
|
+
midMid: "┼",
|
|
372
|
+
midRight: "┤",
|
|
373
|
+
botLeft: "└",
|
|
374
|
+
botMid: "┴",
|
|
375
|
+
botRight: "┘",
|
|
376
|
+
headerNlLeft: " ",
|
|
377
|
+
headerNlRight: "↵",
|
|
378
|
+
wrapLeft: "…",
|
|
379
|
+
wrapRight: "…",
|
|
380
|
+
nlLeft: " ",
|
|
381
|
+
nlRight: "↵",
|
|
382
|
+
wrapRightBorder: true,
|
|
383
|
+
midvruleNl: "│",
|
|
384
|
+
midvruleWrap: "│",
|
|
385
|
+
midvruleBlank: "│"
|
|
459
386
|
};
|
|
460
387
|
const glyphsFor = (style) => {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
return OLD_ASCII_GLYPHS;
|
|
465
|
-
return ASCII_GLYPHS;
|
|
388
|
+
if (style === "unicode") return UNICODE_GLYPHS;
|
|
389
|
+
if (style === "old-ascii") return OLD_ASCII_GLYPHS;
|
|
390
|
+
return ASCII_GLYPHS;
|
|
466
391
|
};
|
|
467
|
-
// ---------------------------------------------------------------------------
|
|
468
|
-
// Column width computation.
|
|
469
|
-
// ---------------------------------------------------------------------------
|
|
470
392
|
/**
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
return widths;
|
|
393
|
+
* Compute the per-column max-width array used by horizontal layout.
|
|
394
|
+
* Considers header width and every line of every cell value. Returns
|
|
395
|
+
* widths in display-character units.
|
|
396
|
+
*
|
|
397
|
+
* Cells are pre-rendered with `nullPrint` and `numericLocale` already
|
|
398
|
+
* applied so width measurements match what the printer will emit.
|
|
399
|
+
*/
|
|
400
|
+
const computeColumnWidths = (rs, topt) => {
|
|
401
|
+
const nullPrint = topt.nullPrint;
|
|
402
|
+
const numericLocale = topt.numericLocale;
|
|
403
|
+
const boolDisplay = boolDisplayOf(topt);
|
|
404
|
+
const widths = rs.fields.map((f) => displayWidth(f.name));
|
|
405
|
+
for (const row of rs.rows) for (let i = 0; i < rs.fields.length; i++) {
|
|
406
|
+
const cellText = renderCell(row[i], nullPrint, numericLocale, rs.fields[i]?.dataTypeID, boolDisplay);
|
|
407
|
+
for (const line of cellText.split("\n")) {
|
|
408
|
+
const w = displayWidth(line);
|
|
409
|
+
if (w > widths[i]) widths[i] = w;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return widths;
|
|
494
413
|
};
|
|
495
414
|
const formatCell = (text) => {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
415
|
+
const lines = text.split("\n");
|
|
416
|
+
let width = 0;
|
|
417
|
+
for (const l of lines) {
|
|
418
|
+
const w = displayWidth(l);
|
|
419
|
+
if (w > width) width = w;
|
|
420
|
+
}
|
|
421
|
+
return {
|
|
422
|
+
lines,
|
|
423
|
+
width
|
|
424
|
+
};
|
|
504
425
|
};
|
|
505
426
|
/**
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
427
|
+
* Build a horizontal rule line (top, middle, bottom). Layout matches
|
|
428
|
+
* psql:
|
|
429
|
+
*
|
|
430
|
+
* border 0: `aa bb` -> `-- --` (no corners, single-space gaps)
|
|
431
|
+
* border 1: `-aa-+-bb-` (no outer corners, '+' between columns)
|
|
432
|
+
* border 2: `+-aa-+-bb-+` (full box)
|
|
433
|
+
* border 3: same as 2 here; row rules between data rows added elsewhere.
|
|
434
|
+
*
|
|
435
|
+
* The hrule covers `width + 2` characters per column when border >= 1
|
|
436
|
+
* (the content padding spaces become hrule chars on rule lines).
|
|
437
|
+
*/
|
|
517
438
|
const buildRule = (widths, border, glyphs, position) => {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
out += hrule.repeat(widths[i] + pad * 2);
|
|
546
|
-
if (i < widths.length - 1) {
|
|
547
|
-
if (border === 0) {
|
|
548
|
-
out += " "; // single space between columns in border 0
|
|
549
|
-
}
|
|
550
|
-
else {
|
|
551
|
-
out += mid;
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
if (border === 2 || border === 3) {
|
|
556
|
-
out += right;
|
|
557
|
-
}
|
|
558
|
-
return out;
|
|
439
|
+
const { hrule } = glyphs;
|
|
440
|
+
let left;
|
|
441
|
+
let mid;
|
|
442
|
+
let right;
|
|
443
|
+
if (position === "top") {
|
|
444
|
+
left = glyphs.topLeft;
|
|
445
|
+
mid = glyphs.topMid;
|
|
446
|
+
right = glyphs.topRight;
|
|
447
|
+
} else if (position === "middle") {
|
|
448
|
+
left = glyphs.midLeft;
|
|
449
|
+
mid = glyphs.midMid;
|
|
450
|
+
right = glyphs.midRight;
|
|
451
|
+
} else {
|
|
452
|
+
left = glyphs.botLeft;
|
|
453
|
+
mid = glyphs.botMid;
|
|
454
|
+
right = glyphs.botRight;
|
|
455
|
+
}
|
|
456
|
+
let out = "";
|
|
457
|
+
if (border === 2 || border === 3) out += left;
|
|
458
|
+
for (let i = 0; i < widths.length; i++) {
|
|
459
|
+
const pad = border === 0 ? 0 : 1;
|
|
460
|
+
out += hrule.repeat(widths[i] + pad * 2);
|
|
461
|
+
if (i < widths.length - 1) if (border === 0) out += " ";
|
|
462
|
+
else out += mid;
|
|
463
|
+
}
|
|
464
|
+
if (border === 2 || border === 3) out += right;
|
|
465
|
+
return out;
|
|
559
466
|
};
|
|
560
467
|
const renderHorizontal = (rs, opts, wrapped) => {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
// Top rule: emitted when border == 2 or 3.
|
|
689
|
-
if (!tuplesOnly && (border === 2 || border === 3)) {
|
|
690
|
-
out += buildRule(widths, border, glyphs, "top") + newline;
|
|
691
|
-
}
|
|
692
|
-
// ------- Header row(s) -------
|
|
693
|
-
if (!tuplesOnly) {
|
|
694
|
-
// For wrapped mode the header column may itself need wrapping if its
|
|
695
|
-
// own width exceeds the (possibly shrunk) column width. Upstream
|
|
696
|
-
// `print_aligned_text` only computes header line breaks based on `\n`
|
|
697
|
-
// splits — header text that's wider than the column wraps onto the
|
|
698
|
-
// next display line via the same wrap loop as the data path. We
|
|
699
|
-
// mirror that by passing the header text through `wrapLine` in
|
|
700
|
-
// wrapped mode (and through the simple `\n` split for plain mode).
|
|
701
|
-
const headerColLines = headerCells.map((c, i) => {
|
|
702
|
-
if (!wrapped)
|
|
703
|
-
return c.lines;
|
|
704
|
-
const out = [];
|
|
705
|
-
for (const line of c.lines) {
|
|
706
|
-
out.push(...wrapLine(line, widths[i]));
|
|
707
|
-
}
|
|
708
|
-
return out;
|
|
709
|
-
});
|
|
710
|
-
const headerLineCount = Math.max(1, ...headerColLines.map((l) => l.length));
|
|
711
|
-
for (let lineIdx = 0; lineIdx < headerLineCount; lineIdx++) {
|
|
712
|
-
// Per-column wrap state for this physical header line:
|
|
713
|
-
// `wrap` the cell has more content to emit on the next line
|
|
714
|
-
// via in-cell wrapping (wrap_left/wrap_right marker)
|
|
715
|
-
// `nl` the cell has more `\n`-split content below
|
|
716
|
-
// (header_nl_left/header_nl_right marker)
|
|
717
|
-
// `none` the cell is done (already emitted its last line)
|
|
718
|
-
const cellWrapPrev = headerColLines.map((l) => (lineIdx > 0 && lineIdx < l.length ? "nl" : "none"));
|
|
719
|
-
const cellWrapNext = headerColLines.map((l) => (lineIdx < l.length - 1 ? "nl" : "none"));
|
|
720
|
-
out += renderHeaderLine(headerColLines.map((l) => l[lineIdx] ?? ""), widths, border, glyphs, cellWrapPrev, cellWrapNext, lineIdx === 0);
|
|
721
|
-
out += newline;
|
|
722
|
-
}
|
|
723
|
-
// Header rule.
|
|
724
|
-
out += buildRule(widths, border, glyphs, "middle") + newline;
|
|
725
|
-
}
|
|
726
|
-
// ------- Data rows -------
|
|
727
|
-
for (let r = 0; r < cellGrid.length; r++) {
|
|
728
|
-
const row = cellGrid[r];
|
|
729
|
-
// For wrapped mode, each cell may need to be re-broken to fit
|
|
730
|
-
// the (possibly shrunk) column width.
|
|
731
|
-
const colLines = row.map((cell, i) => {
|
|
732
|
-
const lines = cell.lines;
|
|
733
|
-
if (!wrapped)
|
|
734
|
-
return lines;
|
|
735
|
-
// Each source line may itself need breaking down to fit.
|
|
736
|
-
const out = [];
|
|
737
|
-
for (const line of lines) {
|
|
738
|
-
out.push(...wrapLine(line, widths[i]));
|
|
739
|
-
}
|
|
740
|
-
return out;
|
|
741
|
-
});
|
|
742
|
-
// We also need to know, per column, which output lines originated as
|
|
743
|
-
// wrap continuations (vs. `\n` splits) so the markers can pick the
|
|
744
|
-
// correct glyph: `.` (wrap_left/right) when wrapped from an
|
|
745
|
-
// over-width source line, `+` (nl_left/right) when split by a literal
|
|
746
|
-
// newline in the cell content.
|
|
747
|
-
//
|
|
748
|
-
// We classify by walking the original cell.lines: each source line
|
|
749
|
-
// expands to N display lines (N >= 1) via `wrapLine`. The first
|
|
750
|
-
// expansion is "nl" (or the very first line of the cell, which has
|
|
751
|
-
// no marker), the rest are "wrap" continuations of the same source
|
|
752
|
-
// line.
|
|
753
|
-
const colLineKinds = row.map((cell, i) => {
|
|
754
|
-
const kinds = [];
|
|
755
|
-
const lines = cell.lines;
|
|
756
|
-
for (let si = 0; si < lines.length; si++) {
|
|
757
|
-
const chunks = wrapped
|
|
758
|
-
? wrapLine(lines[si], widths[i])
|
|
759
|
-
: [lines[si]];
|
|
760
|
-
for (let ci = 0; ci < chunks.length; ci++) {
|
|
761
|
-
if (si === 0 && ci === 0)
|
|
762
|
-
kinds.push("start");
|
|
763
|
-
else if (ci === 0)
|
|
764
|
-
kinds.push("nl");
|
|
765
|
-
else
|
|
766
|
-
kinds.push("wrap");
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
return kinds;
|
|
770
|
-
});
|
|
771
|
-
const lineCount = Math.max(1, ...colLines.map((l) => l.length));
|
|
772
|
-
for (let li = 0; li < lineCount; li++) {
|
|
773
|
-
// For each column, determine the wrap state for THIS output line.
|
|
774
|
-
// The LEFT marker is selected by `cellWrapPrev[j]`: what kind of
|
|
775
|
-
// continuation made this line exist (the `kind` of the current
|
|
776
|
-
// line — `wrap` or `nl` — only matters when li > 0).
|
|
777
|
-
// The RIGHT marker is selected by `cellWrapNext[j]`: the kind of
|
|
778
|
-
// the NEXT line (li + 1), which tells us whether this cell is
|
|
779
|
-
// still emitting more content.
|
|
780
|
-
const cellWrapPrev = colLineKinds.map((kinds, j) => {
|
|
781
|
-
if (li === 0 || li >= colLines[j].length)
|
|
782
|
-
return "none";
|
|
783
|
-
return kinds[li] === "wrap" ? "wrap" : "nl";
|
|
784
|
-
});
|
|
785
|
-
const cellWrapNext = colLineKinds.map((kinds, j) => {
|
|
786
|
-
const nextIdx = li + 1;
|
|
787
|
-
if (nextIdx >= colLines[j].length)
|
|
788
|
-
return "none";
|
|
789
|
-
return kinds[nextIdx] === "wrap" ? "wrap" : "nl";
|
|
790
|
-
});
|
|
791
|
-
out += renderDataLine(colLines.map((l) => l[li] ?? ""), widths, aligns, border, glyphs, cellWrapPrev, cellWrapNext, li === 0);
|
|
792
|
-
out += newline;
|
|
793
|
-
}
|
|
794
|
-
// Heavy borders (border == 3) insert a rule between rows.
|
|
795
|
-
if (border === 3 && r < cellGrid.length - 1) {
|
|
796
|
-
out += buildRule(widths, border, glyphs, "middle") + newline;
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
// Bottom rule when border 2/3.
|
|
800
|
-
if (!tuplesOnly && (border === 2 || border === 3)) {
|
|
801
|
-
out += buildRule(widths, border, glyphs, "bottom") + newline;
|
|
802
|
-
}
|
|
803
|
-
// Footer: (N rows) + trailing blank line. Upstream's
|
|
804
|
-
// `print_aligned_text` emits `printTableAddFooter()` and then
|
|
805
|
-
// `printTableCleanup()` adds a separator `\n`. Verified against
|
|
806
|
-
// vanilla psql 18: `SELECT 1; SELECT 2;` emits each result followed
|
|
807
|
-
// by `(1 row)\n\n` so consecutive queries are visually separated.
|
|
808
|
-
//
|
|
809
|
-
// Footer handling mirrors `footers_with_default` (print.c lines
|
|
810
|
-
// 397-413): user footers SUPPRESS the default `(N rows)` row counter.
|
|
811
|
-
// The default is only emitted when there are no user footers AND
|
|
812
|
-
// `defaultFooter` is true. This matters for `\d` output, which
|
|
813
|
-
// attaches user footers ("Access method: ...", "Owned by: ...") and
|
|
814
|
-
// doesn't want the row counter to interleave between the data rows
|
|
815
|
-
// and the footers.
|
|
816
|
-
const hasUserFooters = !!opts.footers && opts.footers.length > 0;
|
|
817
|
-
if (!tuplesOnly && topt.defaultFooter && !hasUserFooters) {
|
|
818
|
-
const n = rs.rows.length;
|
|
819
|
-
out += `(${String(n)} ${n === 1 ? "row" : "rows"})` + newline;
|
|
820
|
-
}
|
|
821
|
-
if (!tuplesOnly && opts.footers) {
|
|
822
|
-
for (const f of opts.footers)
|
|
823
|
-
out += f + newline;
|
|
824
|
-
}
|
|
825
|
-
// Trailing blank line between query results. Upstream `print.c`
|
|
826
|
-
// line 1196 emits `fputc('\n', fout)` ONLY when `stop_table` is set —
|
|
827
|
-
// chunked-cursor mid-error printing leaves it unset so the ERROR line
|
|
828
|
-
// lands flush against the last data row. Default is `true`, so happy-path
|
|
829
|
-
// queries still get the blank separator before the next command.
|
|
830
|
-
if (topt.stopTable) {
|
|
831
|
-
out += newline;
|
|
832
|
-
}
|
|
833
|
-
return out;
|
|
468
|
+
const topt = opts.topt;
|
|
469
|
+
const border = topt.border;
|
|
470
|
+
const tuplesOnly = topt.tuplesOnly;
|
|
471
|
+
const nullPrint = opts.nullPrint !== "" ? opts.nullPrint : topt.nullPrint;
|
|
472
|
+
const glyphs = glyphsFor(topt.unicodeBorderLineStyle);
|
|
473
|
+
const headers = rs.fields.map((f) => f.name);
|
|
474
|
+
const aligns = rs.fields.map((f) => isRightAlignedField(f.dataTypeID) ? "right" : "left");
|
|
475
|
+
const boolDisplay = boolDisplayOf(topt);
|
|
476
|
+
const cellGrid = rs.rows.map((row) => row.map((cell, i) => formatCell(renderCell(cell, nullPrint, topt.numericLocale, rs.fields[i]?.dataTypeID, boolDisplay))));
|
|
477
|
+
const headerCells = headers.map((h) => formatCell(h));
|
|
478
|
+
const colCount = rs.fields.length;
|
|
479
|
+
const widths = headerCells.map((c) => c.width);
|
|
480
|
+
for (const row of cellGrid) for (let i = 0; i < colCount; i++) if (row[i].width > widths[i]) widths[i] = row[i].width;
|
|
481
|
+
if (wrapped) {
|
|
482
|
+
const maxColumns = topt.columns > 0 ? topt.columns : topt.envColumns;
|
|
483
|
+
if (maxColumns > 0) {
|
|
484
|
+
const widthAverage = new Array(colCount).fill(0);
|
|
485
|
+
const maxWidth = headerCells.map((c) => c.width);
|
|
486
|
+
if (cellGrid.length > 0) {
|
|
487
|
+
for (const row of cellGrid) for (let i = 0; i < colCount; i++) {
|
|
488
|
+
widthAverage[i] += row[i].width;
|
|
489
|
+
if (row[i].width > maxWidth[i]) maxWidth[i] = row[i].width;
|
|
490
|
+
}
|
|
491
|
+
for (let i = 0; i < colCount; i++) widthAverage[i] /= cellGrid.length;
|
|
492
|
+
}
|
|
493
|
+
const overheadFor = (n) => {
|
|
494
|
+
if (border === 0) return n;
|
|
495
|
+
if (border === 1) return n * 3 - (n > 0 ? 1 : 0);
|
|
496
|
+
return n * 3 + 1;
|
|
497
|
+
};
|
|
498
|
+
const overhead = overheadFor(colCount);
|
|
499
|
+
const totalHeaderWidth = overhead + headerCells.reduce((a, c) => a + c.width, 0);
|
|
500
|
+
let total = overhead + widths.reduce((a, b) => a + b, 0);
|
|
501
|
+
if (maxColumns >= totalHeaderWidth) while (total > maxColumns) {
|
|
502
|
+
let worstCol = -1;
|
|
503
|
+
let maxRatio = 0;
|
|
504
|
+
for (let i = 0; i < colCount; i++) {
|
|
505
|
+
const headerW = headerCells[i].width;
|
|
506
|
+
if (widthAverage[i] > 0 && widths[i] > headerW) {
|
|
507
|
+
const ratio = widths[i] / widthAverage[i] + maxWidth[i] * .01;
|
|
508
|
+
if (ratio > maxRatio) {
|
|
509
|
+
maxRatio = ratio;
|
|
510
|
+
worstCol = i;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (worstCol === -1) break;
|
|
515
|
+
widths[worstCol]--;
|
|
516
|
+
total--;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
let out = "";
|
|
521
|
+
const newline = "\n";
|
|
522
|
+
let widthTotal = 0;
|
|
523
|
+
if (!tuplesOnly && topt.title) {
|
|
524
|
+
widthTotal = (border === 0 ? widths.length : border === 1 ? widths.length * 3 - (widths.length > 0 ? 1 : 0) : widths.length * 3 + 1) + widths.reduce((a, b) => a + b, 0);
|
|
525
|
+
const titleW = displayWidth(topt.title);
|
|
526
|
+
if (titleW >= widthTotal) out += topt.title + newline;
|
|
527
|
+
else {
|
|
528
|
+
const pad = widthTotal - titleW >> 1;
|
|
529
|
+
out += " ".repeat(pad) + topt.title + newline;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (!tuplesOnly && (border === 2 || border === 3)) out += buildRule(widths, border, glyphs, "top") + newline;
|
|
533
|
+
if (!tuplesOnly) {
|
|
534
|
+
const headerColLines = headerCells.map((c, i) => {
|
|
535
|
+
if (!wrapped) return c.lines;
|
|
536
|
+
const out = [];
|
|
537
|
+
for (const line of c.lines) out.push(...wrapLine(line, widths[i]));
|
|
538
|
+
return out;
|
|
539
|
+
});
|
|
540
|
+
const headerLineCount = Math.max(1, ...headerColLines.map((l) => l.length));
|
|
541
|
+
for (let lineIdx = 0; lineIdx < headerLineCount; lineIdx++) {
|
|
542
|
+
const cellWrapPrev = headerColLines.map((l) => lineIdx > 0 && lineIdx < l.length ? "nl" : "none");
|
|
543
|
+
const cellWrapNext = headerColLines.map((l) => lineIdx < l.length - 1 ? "nl" : "none");
|
|
544
|
+
out += renderHeaderLine(headerColLines.map((l) => l[lineIdx] ?? ""), widths, border, glyphs, cellWrapPrev, cellWrapNext, lineIdx === 0);
|
|
545
|
+
out += newline;
|
|
546
|
+
}
|
|
547
|
+
out += buildRule(widths, border, glyphs, "middle") + newline;
|
|
548
|
+
}
|
|
549
|
+
for (let r = 0; r < cellGrid.length; r++) {
|
|
550
|
+
const row = cellGrid[r];
|
|
551
|
+
const colLines = row.map((cell, i) => {
|
|
552
|
+
const lines = cell.lines;
|
|
553
|
+
if (!wrapped) return lines;
|
|
554
|
+
const out = [];
|
|
555
|
+
for (const line of lines) out.push(...wrapLine(line, widths[i]));
|
|
556
|
+
return out;
|
|
557
|
+
});
|
|
558
|
+
const colLineKinds = row.map((cell, i) => {
|
|
559
|
+
const kinds = [];
|
|
560
|
+
const lines = cell.lines;
|
|
561
|
+
for (let si = 0; si < lines.length; si++) {
|
|
562
|
+
const chunks = wrapped ? wrapLine(lines[si], widths[i]) : [lines[si]];
|
|
563
|
+
for (let ci = 0; ci < chunks.length; ci++) if (si === 0 && ci === 0) kinds.push("start");
|
|
564
|
+
else if (ci === 0) kinds.push("nl");
|
|
565
|
+
else kinds.push("wrap");
|
|
566
|
+
}
|
|
567
|
+
return kinds;
|
|
568
|
+
});
|
|
569
|
+
const lineCount = Math.max(1, ...colLines.map((l) => l.length));
|
|
570
|
+
for (let li = 0; li < lineCount; li++) {
|
|
571
|
+
const cellWrapPrev = colLineKinds.map((kinds, j) => {
|
|
572
|
+
if (li === 0 || li >= colLines[j].length) return "none";
|
|
573
|
+
return kinds[li] === "wrap" ? "wrap" : "nl";
|
|
574
|
+
});
|
|
575
|
+
const cellWrapNext = colLineKinds.map((kinds, j) => {
|
|
576
|
+
const nextIdx = li + 1;
|
|
577
|
+
if (nextIdx >= colLines[j].length) return "none";
|
|
578
|
+
return kinds[nextIdx] === "wrap" ? "wrap" : "nl";
|
|
579
|
+
});
|
|
580
|
+
out += renderDataLine(colLines.map((l) => l[li] ?? ""), widths, aligns, border, glyphs, cellWrapPrev, cellWrapNext, li === 0);
|
|
581
|
+
out += newline;
|
|
582
|
+
}
|
|
583
|
+
if (border === 3 && r < cellGrid.length - 1) out += buildRule(widths, border, glyphs, "middle") + newline;
|
|
584
|
+
}
|
|
585
|
+
if (!tuplesOnly && (border === 2 || border === 3)) out += buildRule(widths, border, glyphs, "bottom") + newline;
|
|
586
|
+
const hasUserFooters = !!opts.footers && opts.footers.length > 0;
|
|
587
|
+
if (!tuplesOnly && topt.defaultFooter && !hasUserFooters) {
|
|
588
|
+
const n = rs.rows.length;
|
|
589
|
+
out += `(${String(n)} ${n === 1 ? "row" : "rows"})
|
|
590
|
+
`;
|
|
591
|
+
}
|
|
592
|
+
if (!tuplesOnly && opts.footers) for (const f of opts.footers) out += f + newline;
|
|
593
|
+
if (topt.stopTable) out += newline;
|
|
594
|
+
return out;
|
|
834
595
|
};
|
|
835
596
|
/**
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
const pickMidvrule = (glyphs,
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
return glyphs.vrule;
|
|
858
|
-
if (right === "wrap")
|
|
859
|
-
return glyphs.midvruleWrap;
|
|
860
|
-
if (right === "nl")
|
|
861
|
-
return glyphs.midvruleNl;
|
|
862
|
-
return glyphs.midvruleBlank;
|
|
597
|
+
* Pick the column-separator glyph between two adjacent cells. ASCII and
|
|
598
|
+
* Unicode collapse `midvruleNl` / `midvruleWrap` / `midvruleBlank` onto the
|
|
599
|
+
* base `vrule`, so the alt-glyph branches only matter for `old-ascii`,
|
|
600
|
+
* which uses `:` for `\n` continuations, `;` for wrap continuations, and
|
|
601
|
+
* `" "` when the joining column is exhausted but the line still exists
|
|
602
|
+
* because of a wrap/continuation elsewhere in the row.
|
|
603
|
+
*
|
|
604
|
+
* Upstream picks the midvrule from the column *to the right of the
|
|
605
|
+
* separator* (`right`) — if that column is past-end, the separator
|
|
606
|
+
* collapses to `midvruleBlank` regardless of the left column's state.
|
|
607
|
+
*
|
|
608
|
+
* `firstLine = true` short-circuits to the base `vrule` — upstream
|
|
609
|
+
* only consults the midvrule table once at least one continuation has
|
|
610
|
+
* been emitted, so the first display line of every row always uses
|
|
611
|
+
* the regular vrule even if a column will wrap on the next line.
|
|
612
|
+
*/
|
|
613
|
+
const pickMidvrule = (glyphs, _left, right, firstLine) => {
|
|
614
|
+
if (firstLine) return glyphs.vrule;
|
|
615
|
+
if (right === "wrap") return glyphs.midvruleWrap;
|
|
616
|
+
if (right === "nl") return glyphs.midvruleNl;
|
|
617
|
+
return glyphs.midvruleBlank;
|
|
863
618
|
};
|
|
864
619
|
/**
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
620
|
+
* Right-marker glyph for a data row's cell. Mirrors the `wrap[j]`-driven
|
|
621
|
+
* fputs at upstream `print.c` lines 1151-1156:
|
|
622
|
+
* PRINT_LINE_WRAP_WRAP → format->wrap_right (`.` ascii, `…` unicode)
|
|
623
|
+
* PRINT_LINE_WRAP_NEWLINE → format->nl_right (`+` ascii, `↵` unicode)
|
|
624
|
+
* PRINT_LINE_WRAP_NONE → " " (only when not at the trailing edge
|
|
625
|
+
* without border)
|
|
626
|
+
*/
|
|
872
627
|
const dataRightMarker = (state, glyphs, isLast, border) => {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
// state === 'none': trailing edge — only emit a space when there's
|
|
878
|
-
// something AFTER us (another column to follow, or the right border
|
|
879
|
-
// of a border=2/3 box). For the LAST cell on a borderless row, nothing.
|
|
880
|
-
if (isLast && border !== 2 && border !== 3)
|
|
881
|
-
return "";
|
|
882
|
-
return " ";
|
|
628
|
+
if (state === "wrap") return glyphs.wrapRight;
|
|
629
|
+
if (state === "nl") return glyphs.nlRight;
|
|
630
|
+
if (isLast && border !== 2 && border !== 3) return "";
|
|
631
|
+
return " ";
|
|
883
632
|
};
|
|
884
633
|
/**
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
634
|
+
* Left-marker glyph for a data row's cell. Mirrors `wrap[j]` at upstream
|
|
635
|
+
* `print.c` lines 1066-1073:
|
|
636
|
+
* PRINT_LINE_WRAP_WRAP → format->wrap_left (`.` ascii, `…` unicode)
|
|
637
|
+
* PRINT_LINE_WRAP_NEWLINE → format->nl_left (" " for both ascii/unicode)
|
|
638
|
+
* PRINT_LINE_WRAP_NONE → " "
|
|
639
|
+
*
|
|
640
|
+
* Only emitted when border != 0 (the leading-gutter slot). For border=0
|
|
641
|
+
* upstream skips this entirely (no leading marker).
|
|
642
|
+
*/
|
|
894
643
|
const dataLeftMarker = (state, glyphs) => {
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
return glyphs.nlLeft;
|
|
899
|
-
return " ";
|
|
644
|
+
if (state === "wrap") return glyphs.wrapLeft;
|
|
645
|
+
if (state === "nl") return glyphs.nlLeft;
|
|
646
|
+
return " ";
|
|
900
647
|
};
|
|
901
648
|
/**
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
const renderHeaderLine = (cells, widths, border, glyphs,
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
cellWrapNext
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
firstLine) => {
|
|
929
|
-
const { vrule } = glyphs;
|
|
930
|
-
let out = "";
|
|
931
|
-
if (border === 2 || border === 3)
|
|
932
|
-
out += vrule;
|
|
933
|
-
for (let i = 0; i < cells.length; i++) {
|
|
934
|
-
const isLast = i === cells.length - 1;
|
|
935
|
-
// Leading gutter / wrap marker. Upstream `print.c` line 981-984:
|
|
936
|
-
// if (opt_border != 0 || (!format->wrap_right_border && i > 0))
|
|
937
|
-
// fputs(curr_nl_line ? format->header_nl_left : " ", fout);
|
|
938
|
-
// For ASCII (wrap_right_border=true): emitted iff border != 0.
|
|
939
|
-
// For old-ascii (wrap_right_border=false): emitted for non-first
|
|
940
|
-
// cells too at border=0 — the inter-column slot doubles as the
|
|
941
|
-
// leading-gutter slot of col[i].
|
|
942
|
-
if (border !== 0 || (!glyphs.wrapRightBorder && i > 0)) {
|
|
943
|
-
out += firstLine ? " " : glyphs.headerNlLeft;
|
|
944
|
-
}
|
|
945
|
-
// Header cells are always centered and padded to full width
|
|
946
|
-
// (upstream emits `%-*s%s%-*s` regardless of position — no
|
|
947
|
-
// skip-padding-on-last-cell quirk on header rows).
|
|
948
|
-
out += padToWidth(cells[i], widths[i], "center");
|
|
949
|
-
// Trailing marker. Upstream `print.c` line 1003-1005:
|
|
950
|
-
// if (opt_border != 0 || format->wrap_right_border)
|
|
951
|
-
// fputs(!header_done[i] ? format->header_nl_right : " ", fout);
|
|
952
|
-
// For ASCII this is always emitted (wrap_right_border=true), so
|
|
953
|
-
// multi-line headers get `+` between content and the column
|
|
954
|
-
// separator AND on the trailing edge of the last column. For
|
|
955
|
-
// OLD-ASCII (wrap_right_border=false) this slot is skipped at
|
|
956
|
-
// border=0 — the inter-column space comes from the NEXT cell's
|
|
957
|
-
// leading-gutter emit on the next loop iteration.
|
|
958
|
-
if (border !== 0 || glyphs.wrapRightBorder) {
|
|
959
|
-
out += cellWrapNext[i] !== "none" ? glyphs.headerNlRight : " ";
|
|
960
|
-
}
|
|
961
|
-
// Column divider (not on the last column). For border 0 ASCII the
|
|
962
|
-
// trailing-marker slot above already consumes the inter-column gap.
|
|
963
|
-
// For border 0 OLD-ASCII the next iteration emits the leading-gutter
|
|
964
|
-
// slot, so no extra space is needed here either.
|
|
965
|
-
if (!isLast && border !== 0) {
|
|
966
|
-
out += vrule;
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
if (border === 2 || border === 3)
|
|
970
|
-
out += vrule;
|
|
971
|
-
return out;
|
|
649
|
+
* Render one display-line of a header row. Centered cells, fixed
|
|
650
|
+
* `header_nl_*` markers on continuation lines.
|
|
651
|
+
*
|
|
652
|
+
* Layout (with ASCII glyphs):
|
|
653
|
+
* border 0: `<c1centered>[H]<c2centered>[H]` — `wrap_right_border=true`
|
|
654
|
+
* means the trailing slot still gets emitted even at table
|
|
655
|
+
* edge. `H` = `header_nl_right` ("+") when the next line in
|
|
656
|
+
* this column has more content, else " ".
|
|
657
|
+
* border 1: ` <c1centered>[H]| <c2centered>[H]`
|
|
658
|
+
* (leading gutter, header_nl_right marker between content and
|
|
659
|
+
* separator, optional trailing " " on the very last cell)
|
|
660
|
+
* border 2: `| <c1centered>[H]| <c2centered>[H]|` (full box)
|
|
661
|
+
*/
|
|
662
|
+
const renderHeaderLine = (cells, widths, border, glyphs, cellWrapPrev, cellWrapNext, firstLine) => {
|
|
663
|
+
const { vrule } = glyphs;
|
|
664
|
+
let out = "";
|
|
665
|
+
if (border === 2 || border === 3) out += vrule;
|
|
666
|
+
for (let i = 0; i < cells.length; i++) {
|
|
667
|
+
const isLast = i === cells.length - 1;
|
|
668
|
+
if (border !== 0 || !glyphs.wrapRightBorder && i > 0) out += firstLine ? " " : glyphs.headerNlLeft;
|
|
669
|
+
out += padToWidth(cells[i], widths[i], "center");
|
|
670
|
+
if (border !== 0 || glyphs.wrapRightBorder) out += cellWrapNext[i] !== "none" ? glyphs.headerNlRight : " ";
|
|
671
|
+
if (!isLast && border !== 0) out += vrule;
|
|
672
|
+
}
|
|
673
|
+
if (border === 2 || border === 3) out += vrule;
|
|
674
|
+
return out;
|
|
972
675
|
};
|
|
973
676
|
/**
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
const renderDataLine = (cells, widths, aligns, border, glyphs,
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
cellWrapNext
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
for (let i = 0; i < cells.length; i++) {
|
|
1008
|
-
const isLast = i === cells.length - 1;
|
|
1009
|
-
// Left marker. Border 0 has no leading-gutter slot for non-first
|
|
1010
|
-
// cells under ASCII (wrap_right_border=true); under OLD-ASCII the
|
|
1011
|
-
// leading-gutter slot doubles as the inter-column space for i > 0
|
|
1012
|
-
// (upstream `print.c` line 1066-1073).
|
|
1013
|
-
if (border !== 0) {
|
|
1014
|
-
out += dataLeftMarker(cellWrapPrev[i], glyphs);
|
|
1015
|
-
}
|
|
1016
|
-
else if (!glyphs.wrapRightBorder && i > 0) {
|
|
1017
|
-
out += dataLeftMarker(cellWrapPrev[i], glyphs);
|
|
1018
|
-
}
|
|
1019
|
-
// Decide whether to pad this cell. Upstream `print.c` lines 1141-1148:
|
|
1020
|
-
// left-aligned cells pad iff `finalspaces || wrap[j] != NONE`.
|
|
1021
|
-
// right-aligned cells always pad on the left side regardless.
|
|
1022
|
-
// `finalspaces` = (border == 2 || not last column).
|
|
1023
|
-
const finalspaces = border === 2 || border === 3 || !isLast;
|
|
1024
|
-
const needsTrailingPad = finalspaces || cellWrapNext[i] !== "none";
|
|
1025
|
-
if (aligns[i] === "right") {
|
|
1026
|
-
// Right-aligned cells always get full padding on the left so
|
|
1027
|
-
// content right-aligns. Trailing pad only when finalspaces (or
|
|
1028
|
-
// wrap, to keep marker aligned).
|
|
1029
|
-
out += padToWidth(cells[i], widths[i], "right");
|
|
1030
|
-
}
|
|
1031
|
-
else if (needsTrailingPad) {
|
|
1032
|
-
out += padToWidth(cells[i], widths[i], "left");
|
|
1033
|
-
}
|
|
1034
|
-
else {
|
|
1035
|
-
// Last cell, no wrap state — emit raw content (no trailing pad).
|
|
1036
|
-
out += cells[i];
|
|
1037
|
-
}
|
|
1038
|
-
// Right marker. Upstream `print.c` lines 1150-1156:
|
|
1039
|
-
// - WRAP → wrap_right
|
|
1040
|
-
// - NEWLINE→ nl_right
|
|
1041
|
-
// - NONE → space (only when not last col / border=2)
|
|
1042
|
-
// For OLD-ASCII at border 0 the inter-column trailing slot of
|
|
1043
|
-
// non-last cells is owned by the *next* iteration's leading-gutter
|
|
1044
|
-
// emit (single space either way). For the very last column, upstream
|
|
1045
|
-
// still emits a trailing char when the cell is on a continuation
|
|
1046
|
-
// (`cellWrapNext != none`) so wrapped cells keep their trailing
|
|
1047
|
-
// column position aligned with the first line.
|
|
1048
|
-
if (border !== 0 || glyphs.wrapRightBorder) {
|
|
1049
|
-
out += dataRightMarker(cellWrapNext[i], glyphs, isLast, border);
|
|
1050
|
-
}
|
|
1051
|
-
else if (isLast && cellWrapNext[i] !== "none") {
|
|
1052
|
-
out += dataRightMarker(cellWrapNext[i], glyphs, isLast, border);
|
|
1053
|
-
}
|
|
1054
|
-
// Column divider. Upstream lines 1158-1169: in old-ascii the
|
|
1055
|
-
// midvrule between col[i] and col[i+1] swaps to a continuation
|
|
1056
|
-
// glyph when *either* adjacent cell is on a wrap/nl continuation
|
|
1057
|
-
// (midvrule_nl=":", midvrule_wrap=";", midvrule_blank=" "). For
|
|
1058
|
-
// ASCII / Unicode all three midvrule_* equal the regular vrule, so
|
|
1059
|
-
// the branch collapses to `vrule`.
|
|
1060
|
-
if (!isLast && border !== 0) {
|
|
1061
|
-
out += pickMidvrule(glyphs, cellWrapPrev[i], cellWrapPrev[i + 1], firstLine);
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
if (border === 2 || border === 3)
|
|
1065
|
-
out += vrule;
|
|
1066
|
-
return out;
|
|
677
|
+
* Render one display-line of a data row.
|
|
678
|
+
*
|
|
679
|
+
* Mirrors upstream `print_aligned_text` (print.c lines 1047-1180): one
|
|
680
|
+
* loop body, per-column emit `[left-marker] <content> [right-marker]
|
|
681
|
+
* [column-divider]`, then `[right-border]`. Padding is applied when:
|
|
682
|
+
* - the cell is not the last column (finalspaces=true in upstream),
|
|
683
|
+
* - OR `wrap[j]` is set (cell continues, so marker needs an aligned
|
|
684
|
+
* position).
|
|
685
|
+
*
|
|
686
|
+
* Right-aligned columns always pad on the left (spaces first), but per
|
|
687
|
+
* upstream they get a trailing right-margin pad only at border=2 (the
|
|
688
|
+
* `finalspaces` branch). For border 0/1 right-aligned columns we follow
|
|
689
|
+
* the same rule: padding before content, no trailing pad on the last
|
|
690
|
+
* column when there's no continuation.
|
|
691
|
+
*/
|
|
692
|
+
const renderDataLine = (cells, widths, aligns, border, glyphs, cellWrapPrev, cellWrapNext, firstLine) => {
|
|
693
|
+
const { vrule } = glyphs;
|
|
694
|
+
let out = "";
|
|
695
|
+
if (border === 2 || border === 3) out += vrule;
|
|
696
|
+
for (let i = 0; i < cells.length; i++) {
|
|
697
|
+
const isLast = i === cells.length - 1;
|
|
698
|
+
if (border !== 0) out += dataLeftMarker(cellWrapPrev[i], glyphs);
|
|
699
|
+
else if (!glyphs.wrapRightBorder && i > 0) out += dataLeftMarker(cellWrapPrev[i], glyphs);
|
|
700
|
+
const needsTrailingPad = border === 2 || border === 3 || !isLast || cellWrapNext[i] !== "none";
|
|
701
|
+
if (aligns[i] === "right") out += padToWidth(cells[i], widths[i], "right");
|
|
702
|
+
else if (needsTrailingPad) out += padToWidth(cells[i], widths[i], "left");
|
|
703
|
+
else out += cells[i];
|
|
704
|
+
if (border !== 0 || glyphs.wrapRightBorder) out += dataRightMarker(cellWrapNext[i], glyphs, isLast, border);
|
|
705
|
+
else if (isLast && cellWrapNext[i] !== "none") out += dataRightMarker(cellWrapNext[i], glyphs, isLast, border);
|
|
706
|
+
if (!isLast && border !== 0) out += pickMidvrule(glyphs, cellWrapPrev[i], cellWrapPrev[i + 1], firstLine);
|
|
707
|
+
}
|
|
708
|
+
if (border === 2 || border === 3) out += vrule;
|
|
709
|
+
return out;
|
|
1067
710
|
};
|
|
1068
711
|
/**
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
712
|
+
* Break a single line of text into chunks of at most `width` visible
|
|
713
|
+
* columns. Greedy, code-point-aware. Used by wrapped mode and by
|
|
714
|
+
* vertical mode for value wrapping.
|
|
715
|
+
*/
|
|
1073
716
|
const wrapLine = (line, width) => {
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
if (buf.length > 0)
|
|
1092
|
-
chunks.push(buf);
|
|
1093
|
-
return chunks;
|
|
717
|
+
if (width <= 0) return [line];
|
|
718
|
+
if (displayWidth(line) <= width) return [line];
|
|
719
|
+
const chunks = [];
|
|
720
|
+
let buf = "";
|
|
721
|
+
let bufW = 0;
|
|
722
|
+
for (const ch of line) {
|
|
723
|
+
const cw = codePointWidth(ch.codePointAt(0) ?? 0);
|
|
724
|
+
if (bufW + cw > width && buf.length > 0) {
|
|
725
|
+
chunks.push(buf);
|
|
726
|
+
buf = "";
|
|
727
|
+
bufW = 0;
|
|
728
|
+
}
|
|
729
|
+
buf += ch;
|
|
730
|
+
bufW += cw;
|
|
731
|
+
}
|
|
732
|
+
if (buf.length > 0) chunks.push(buf);
|
|
733
|
+
return chunks;
|
|
1094
734
|
};
|
|
1095
|
-
// ---------------------------------------------------------------------------
|
|
1096
|
-
// Vertical / expanded rendering.
|
|
1097
|
-
// ---------------------------------------------------------------------------
|
|
1098
735
|
const renderVertical = (rs, opts) => {
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
if (outputColumns < minWidth)
|
|
1192
|
-
return minWidth - nameWidth - swidth;
|
|
1193
|
-
return outputColumns - nameWidth - swidth;
|
|
1194
|
-
};
|
|
1195
|
-
// First pass with the natural swidth.
|
|
1196
|
-
let newDwidth = compute(swidthInit);
|
|
1197
|
-
// If wrap is needed (newDwidth < natural) AND dmultiline wasn't
|
|
1198
|
-
// already true AND we're at border<2, upstream toggles dmultiline
|
|
1199
|
-
// on and re-runs with swidth+1.
|
|
1200
|
-
if (newDwidth < valueWidth && !dmultiline && dmAddOk) {
|
|
1201
|
-
newDwidth = compute(swidthAfterWrap);
|
|
1202
|
-
dmultiline = true;
|
|
1203
|
-
}
|
|
1204
|
-
// Clamp: never grow the data column beyond the natural value
|
|
1205
|
-
// width (matches `dwidth = newdwidth` in upstream).
|
|
1206
|
-
dwidth = newDwidth < valueWidth ? newDwidth : valueWidth;
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
let out = "";
|
|
1210
|
-
const newline = "\n";
|
|
1211
|
-
if (!tuplesOnly && topt.title) {
|
|
1212
|
-
out += topt.title + newline;
|
|
1213
|
-
}
|
|
1214
|
-
if (rs.rows.length === 0) {
|
|
1215
|
-
// Expanded mode on empty result: upstream emits the "(0 rows)"
|
|
1216
|
-
// footer (same wording as horizontal mode) followed by the trailing
|
|
1217
|
-
// blank-line separator, NOT a special "(No rows)" string. Verified
|
|
1218
|
-
// against vanilla psql 18: `\\pset expanded on` then `select 1
|
|
1219
|
-
// where false;` emits `(0 rows)\n\n`.
|
|
1220
|
-
if (!tuplesOnly)
|
|
1221
|
-
out += "(0 rows)" + newline;
|
|
1222
|
-
if (!tuplesOnly && opts.footers) {
|
|
1223
|
-
for (const f of opts.footers)
|
|
1224
|
-
out += f + newline;
|
|
1225
|
-
}
|
|
1226
|
-
if (!tuplesOnly)
|
|
1227
|
-
out += newline;
|
|
1228
|
-
return out;
|
|
1229
|
-
}
|
|
1230
|
-
// Decide whether multi-line markers should be emitted on header/data
|
|
1231
|
-
// continuations. Mirrors upstream `print_aligned_vertical` predicates
|
|
1232
|
-
// at print.c lines 1679-1689 (header right marker) and 1747-1763 (data
|
|
1233
|
-
// trailing marker):
|
|
1234
|
-
// non-old-ascii: header marker iff `border > 0 || hmultiline`
|
|
1235
|
-
// data marker iff `border > 1 || dmultiline`
|
|
1236
|
-
// old-ascii: header right marker emitted iff `border > 0`
|
|
1237
|
-
// data right marker emitted iff `border > 1`
|
|
1238
|
-
// (old-ascii relies on header_nl_LEFT for header
|
|
1239
|
-
// continuations and never emits the data right marker
|
|
1240
|
-
// at border<2 — it uses the alt midvrule glyph instead.)
|
|
1241
|
-
const emitHeaderMarker = oldAscii ? border > 0 : border > 0 || hmultiline;
|
|
1242
|
-
const emitDataMarker = oldAscii ? border > 1 : border > 1 || dmultiline;
|
|
1243
|
-
// Leading-slot for header column. Upstream `print_aligned_vertical`
|
|
1244
|
-
// line 1655-1657 emits the slot at `border==2 || (hmultiline &&
|
|
1245
|
-
// oldAscii)`. Old-ascii routes the continuation marker through the
|
|
1246
|
-
// leading gutter (`+` in `headerNlLeft`) instead of the trailing slot.
|
|
1247
|
-
const emitHeaderLeftSlot = border > 1 || (hmultiline && oldAscii);
|
|
1248
|
-
// Record-header label width. Upstream line 1610-1613 bumps `lhwidth`
|
|
1249
|
-
// by 1 at border<2 with hmultiline && oldAscii — the extra column is
|
|
1250
|
-
// the same `+` newline-indicator slot the data lines reserve.
|
|
1251
|
-
const lhwidth = border < 2 && hmultiline && oldAscii ? nameWidth + 1 : nameWidth;
|
|
1252
|
-
for (let r = 0; r < cellGrid.length; r++) {
|
|
1253
|
-
if (!tuplesOnly) {
|
|
1254
|
-
out += renderRecordHeader(r + 1, lhwidth, dwidth, border, glyphs, r === 0);
|
|
1255
|
-
out += newline;
|
|
1256
|
-
}
|
|
1257
|
-
else if (r > 0 || border === 2 || border === 3) {
|
|
1258
|
-
// tuples_only: upstream `print_aligned_vertical` line 1619-1621
|
|
1259
|
-
// still emits the inter-record separator (with the `* Record N`
|
|
1260
|
-
// label suppressed) for r>0, and the top rule at border=2/3 for
|
|
1261
|
-
// the very first record. Reuses the same record-header path with
|
|
1262
|
-
// `record=0` to suppress the label glyph.
|
|
1263
|
-
out += renderRecordHeader(0, lhwidth, dwidth, border, glyphs, r === 0);
|
|
1264
|
-
out += newline;
|
|
1265
|
-
}
|
|
1266
|
-
for (let c = 0; c < headers.length; c++) {
|
|
1267
|
-
const headerLines = headers[c].split("\n");
|
|
1268
|
-
const dataLines = cellGrid[r][c].split("\n");
|
|
1269
|
-
out += renderVerticalCell(headerLines, dataLines, nameWidth, dwidth, border, glyphs, hmultiline, emitHeaderMarker, emitDataMarker, emitHeaderLeftSlot, oldAscii);
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
if (!tuplesOnly && (border === 2 || border === 3)) {
|
|
1273
|
-
out +=
|
|
1274
|
-
glyphs.botLeft +
|
|
1275
|
-
glyphs.hrule.repeat(nameWidth + 2) +
|
|
1276
|
-
glyphs.botMid +
|
|
1277
|
-
glyphs.hrule.repeat(dwidth + 2) +
|
|
1278
|
-
glyphs.botRight +
|
|
1279
|
-
newline;
|
|
1280
|
-
}
|
|
1281
|
-
// Footer + trailing blank-line handling mirrors upstream
|
|
1282
|
-
// `print_aligned_vertical` (print.c lines 1804-1822):
|
|
1283
|
-
// - At border < 2, emit a blank line BEFORE the user footers
|
|
1284
|
-
// (separates the last data line from the footer block).
|
|
1285
|
-
// - At border >= 2, the bottom rule already provides the separator
|
|
1286
|
-
// so no extra blank is emitted before footers.
|
|
1287
|
-
// - Always emit a trailing blank line at the very end —
|
|
1288
|
-
// `fputc('\n', fout)` on line 1821 is UNCONDITIONAL (not gated by
|
|
1289
|
-
// `opt_tuples_only`), so tuples_only mode still gets a single
|
|
1290
|
-
// separator before the next command.
|
|
1291
|
-
// - Expanded mode never emits a `(N rows)` default footer; the
|
|
1292
|
-
// trailing blank is the only output between consecutive results.
|
|
1293
|
-
const hasUserFooters = !!opts.footers && opts.footers.length > 0;
|
|
1294
|
-
if (!tuplesOnly && hasUserFooters && border < 2) {
|
|
1295
|
-
out += newline;
|
|
1296
|
-
}
|
|
1297
|
-
if (!tuplesOnly && opts.footers) {
|
|
1298
|
-
for (const f of opts.footers)
|
|
1299
|
-
out += f + newline;
|
|
1300
|
-
}
|
|
1301
|
-
out += newline;
|
|
1302
|
-
return out;
|
|
736
|
+
const topt = opts.topt;
|
|
737
|
+
const border = topt.border;
|
|
738
|
+
const tuplesOnly = topt.tuplesOnly;
|
|
739
|
+
const nullPrint = opts.nullPrint !== "" ? opts.nullPrint : topt.nullPrint;
|
|
740
|
+
const glyphs = glyphsFor(topt.unicodeBorderLineStyle);
|
|
741
|
+
const oldAscii = topt.unicodeBorderLineStyle === "old-ascii";
|
|
742
|
+
const headers = rs.fields.map((f) => f.name);
|
|
743
|
+
let nameWidth = 0;
|
|
744
|
+
let hmultiline = false;
|
|
745
|
+
for (const h of headers) {
|
|
746
|
+
for (const line of h.split("\n")) {
|
|
747
|
+
const w = displayWidth(line);
|
|
748
|
+
if (w > nameWidth) nameWidth = w;
|
|
749
|
+
}
|
|
750
|
+
if (h.includes("\n")) hmultiline = true;
|
|
751
|
+
}
|
|
752
|
+
let valueWidth = 0;
|
|
753
|
+
let dmultiline = false;
|
|
754
|
+
const boolDisplay = boolDisplayOf(topt);
|
|
755
|
+
const cellGrid = rs.rows.map((row) => row.map((cell, i) => renderCell(cell, nullPrint, topt.numericLocale, rs.fields[i]?.dataTypeID, boolDisplay)));
|
|
756
|
+
for (const row of cellGrid) for (const v of row) {
|
|
757
|
+
if (v.includes("\n")) dmultiline = true;
|
|
758
|
+
for (const line of v.split("\n")) {
|
|
759
|
+
const w = displayWidth(line);
|
|
760
|
+
if (w > valueWidth) valueWidth = w;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
const wrappedMode = topt.format === "wrapped";
|
|
764
|
+
let dwidth = valueWidth;
|
|
765
|
+
if (wrappedMode) {
|
|
766
|
+
const outputColumns = topt.columns > 0 ? topt.columns : topt.envColumns;
|
|
767
|
+
if (outputColumns > 0) {
|
|
768
|
+
let baseSwidth;
|
|
769
|
+
if (border === 0) baseSwidth = 1 + (hmultiline ? 1 : 0);
|
|
770
|
+
else if (border === 1) baseSwidth = 3 + (hmultiline && oldAscii ? 1 : 0);
|
|
771
|
+
else baseSwidth = 7;
|
|
772
|
+
const dmAddOk = border < 2 && !oldAscii;
|
|
773
|
+
const swidthInit = baseSwidth + (dmAddOk && dmultiline ? 1 : 0);
|
|
774
|
+
const swidthAfterWrap = baseSwidth + (dmAddOk ? 1 : 0);
|
|
775
|
+
const labelOverhead = border === 0 ? 9 : border === 1 ? 12 : 15;
|
|
776
|
+
const nrows = cellGrid.length;
|
|
777
|
+
const rwidth = labelOverhead + (nrows > 0 ? 1 + Math.floor(Math.log10(Math.max(1, nrows))) : 0);
|
|
778
|
+
const compute = (swidth) => {
|
|
779
|
+
let width = nameWidth + swidth + valueWidth;
|
|
780
|
+
if (width < rwidth) width = rwidth;
|
|
781
|
+
let minWidth = nameWidth + swidth + 3;
|
|
782
|
+
if (minWidth < rwidth) minWidth = rwidth;
|
|
783
|
+
if (outputColumns >= width) return width - nameWidth - swidth;
|
|
784
|
+
if (outputColumns < minWidth) return minWidth - nameWidth - swidth;
|
|
785
|
+
return outputColumns - nameWidth - swidth;
|
|
786
|
+
};
|
|
787
|
+
let newDwidth = compute(swidthInit);
|
|
788
|
+
if (newDwidth < valueWidth && !dmultiline && dmAddOk) {
|
|
789
|
+
newDwidth = compute(swidthAfterWrap);
|
|
790
|
+
dmultiline = true;
|
|
791
|
+
}
|
|
792
|
+
dwidth = newDwidth < valueWidth ? newDwidth : valueWidth;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
let out = "";
|
|
796
|
+
const newline = "\n";
|
|
797
|
+
if (!tuplesOnly && topt.title) out += topt.title + newline;
|
|
798
|
+
if (rs.rows.length === 0) {
|
|
799
|
+
if (!tuplesOnly) out += "(0 rows)\n";
|
|
800
|
+
if (!tuplesOnly && opts.footers) for (const f of opts.footers) out += f + newline;
|
|
801
|
+
if (!tuplesOnly) out += newline;
|
|
802
|
+
return out;
|
|
803
|
+
}
|
|
804
|
+
const emitHeaderMarker = oldAscii ? border > 0 : border > 0 || hmultiline;
|
|
805
|
+
const emitDataMarker = oldAscii ? border > 1 : border > 1 || dmultiline;
|
|
806
|
+
const emitHeaderLeftSlot = border > 1 || hmultiline && oldAscii;
|
|
807
|
+
const lhwidth = border < 2 && hmultiline && oldAscii ? nameWidth + 1 : nameWidth;
|
|
808
|
+
for (let r = 0; r < cellGrid.length; r++) {
|
|
809
|
+
if (!tuplesOnly) {
|
|
810
|
+
out += renderRecordHeader(r + 1, lhwidth, dwidth, border, glyphs, r === 0);
|
|
811
|
+
out += newline;
|
|
812
|
+
} else if (r > 0 || border === 2 || border === 3) {
|
|
813
|
+
out += renderRecordHeader(0, lhwidth, dwidth, border, glyphs, r === 0);
|
|
814
|
+
out += newline;
|
|
815
|
+
}
|
|
816
|
+
for (let c = 0; c < headers.length; c++) {
|
|
817
|
+
const headerLines = headers[c].split("\n");
|
|
818
|
+
const dataLines = cellGrid[r][c].split("\n");
|
|
819
|
+
out += renderVerticalCell(headerLines, dataLines, nameWidth, dwidth, border, glyphs, hmultiline, emitHeaderMarker, emitDataMarker, emitHeaderLeftSlot, oldAscii);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
if (!tuplesOnly && (border === 2 || border === 3)) out += glyphs.botLeft + glyphs.hrule.repeat(nameWidth + 2) + glyphs.botMid + glyphs.hrule.repeat(dwidth + 2) + glyphs.botRight + newline;
|
|
823
|
+
const hasUserFooters = !!opts.footers && opts.footers.length > 0;
|
|
824
|
+
if (!tuplesOnly && hasUserFooters && border < 2) out += newline;
|
|
825
|
+
if (!tuplesOnly && opts.footers) for (const f of opts.footers) out += f + newline;
|
|
826
|
+
out += newline;
|
|
827
|
+
return out;
|
|
1303
828
|
};
|
|
1304
829
|
/**
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
830
|
+
* Render the `[ RECORD N ]` block header for vertical mode.
|
|
831
|
+
*
|
|
832
|
+
* border 0: `* Record N<spaces-to-fill>` (padded to `nameWidth + valueWidth`)
|
|
833
|
+
* border 1: `-[ RECORD N ]<dashes-to-fit>+<dashes-to-fit>`
|
|
834
|
+
* border 2: `+-[ RECORD N ]<dashes>+<dashes>+`
|
|
835
|
+
*
|
|
836
|
+
* Mirrors psql `print_aligned_vertical_line`.
|
|
837
|
+
*
|
|
838
|
+
* Layout invariants (verified empirically against vanilla psql 18):
|
|
839
|
+
*
|
|
840
|
+
* border 1 — data line is `<name padded to nameWidth> | <value>`:
|
|
841
|
+
* - The `|` column-separator sits at position `nameWidth + 2`.
|
|
842
|
+
* - Record header `+` mid junction aligns with that `|`, so the
|
|
843
|
+
* pre-junction (left) span is `nameWidth + 1` chars and the
|
|
844
|
+
* post-junction (right) span is `valueWidth + 1` chars.
|
|
845
|
+
* - When `1 + label.length` overflows the pre-junction span but the
|
|
846
|
+
* label still fits in the full data row width, upstream pads with
|
|
847
|
+
* hrule chars to the row width and OMITS the mid junction
|
|
848
|
+
* (`-[ RECORD 1 ]--` for `nameWidth=2, valueWidth=10`).
|
|
849
|
+
* - When `1 + label.length` overflows the row width too, upstream
|
|
850
|
+
* emits just the label-prefix with no padding (the label IS the
|
|
851
|
+
* whole divider line).
|
|
852
|
+
*
|
|
853
|
+
* border 2 — data line is `| <name> | <value> |`, row width is
|
|
854
|
+
* `1 + (nameWidth + 2) + 1 + (valueWidth + 2) + 1`:
|
|
855
|
+
* - Outer corners use the TOP glyph set on the first record
|
|
856
|
+
* (`topLeft`, `topMid`, `topRight`) and the MID glyph set on
|
|
857
|
+
* subsequent records (`midLeft`, `midMid`, `midRight`). The bottom
|
|
858
|
+
* rule (emitted by `renderVertical`) uses the BOT glyphs.
|
|
859
|
+
* - The `topMid` (or `midMid`) junction sits at position
|
|
860
|
+
* `leftSegLen + 2` to match the `|` column-separator in the data
|
|
861
|
+
* row. When `1 + label.length` overflows the left segment, the
|
|
862
|
+
* junction glyph is dropped and the row is padded entirely with
|
|
863
|
+
* hrule chars between the outer corners.
|
|
864
|
+
*
|
|
865
|
+
* border 0 — label is padded with spaces to `nameWidth + valueWidth`.
|
|
866
|
+
* Labels that exceed `nameWidth + valueWidth` pass through unchanged.
|
|
867
|
+
*
|
|
868
|
+
* `isFirst` only affects border 2 glyph selection; border 0 and 1 share
|
|
869
|
+
* a single set of single-line rule glyphs across all records.
|
|
870
|
+
*/
|
|
1346
871
|
const renderRecordHeader = (record, nameWidth, valueWidth, border, glyphs, isFirst) => {
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
// Data row width = nameWidth + 3 + valueWidth (`<name padded> | <value>`).
|
|
1386
|
-
// Pre-junction span = nameWidth + 1 (name col + gutter space). The
|
|
1387
|
-
// `+` mid junction lands at position `nameWidth + 2`, mirroring the
|
|
1388
|
-
// `|` in the data lines. Post-junction span = valueWidth + 1.
|
|
1389
|
-
const rowWidth = nameWidth + 3 + valueWidth;
|
|
1390
|
-
const leftSpan = nameWidth + 1;
|
|
1391
|
-
const rightSpan = valueWidth + 1;
|
|
1392
|
-
const left = hrule + label;
|
|
1393
|
-
if (left.length <= leftSpan) {
|
|
1394
|
-
// Label fits in the pre-junction span. Pad left to leftSpan, emit
|
|
1395
|
-
// the mid junction (`+`), then rightSpan hrules.
|
|
1396
|
-
const leftPadded = left + hrule.repeat(leftSpan - left.length);
|
|
1397
|
-
return leftPadded + glyphs.midMid + hrule.repeat(rightSpan);
|
|
1398
|
-
}
|
|
1399
|
-
if (left.length < rowWidth) {
|
|
1400
|
-
// Label overflows the pre-junction span but still fits within the
|
|
1401
|
-
// full row. Pad the label-prefix out to the row width with hrule
|
|
1402
|
-
// chars and DROP the mid junction (vanilla psql parity).
|
|
1403
|
-
return left + hrule.repeat(rowWidth - left.length);
|
|
1404
|
-
}
|
|
1405
|
-
// Label exceeds the full row width — emit just the label-prefix
|
|
1406
|
-
// (no padding, no junction).
|
|
1407
|
-
return left;
|
|
1408
|
-
}
|
|
1409
|
-
// border 2 / 3.
|
|
1410
|
-
//
|
|
1411
|
-
// Outer corners come from the TOP glyph set on the first record (so
|
|
1412
|
-
// the leading rule looks like a normal table top) and from the MID
|
|
1413
|
-
// glyph set on subsequent records (so it looks like an inter-row rule).
|
|
1414
|
-
// For ASCII these all collapse to `+`; the distinction matters for
|
|
1415
|
-
// Unicode (`┌`/`┐`/`┬` vs `├`/`┤`/`┼`).
|
|
1416
|
-
const outerLeft = isFirst ? glyphs.topLeft : glyphs.midLeft;
|
|
1417
|
-
const outerRight = isFirst ? glyphs.topRight : glyphs.midRight;
|
|
1418
|
-
const outerMid = isFirst ? glyphs.topMid : glyphs.midMid;
|
|
1419
|
-
// Data row width = 1 + (nameWidth + 2) + 1 + (valueWidth + 2) + 1.
|
|
1420
|
-
const leftSegLen = nameWidth + 2;
|
|
1421
|
-
const rightSegLen = valueWidth + 2;
|
|
1422
|
-
const leftCore = hrule + label;
|
|
1423
|
-
if (leftCore.length <= leftSegLen) {
|
|
1424
|
-
// Label fits in the left segment. Pad left to leftSegLen, emit the
|
|
1425
|
-
// top/mid junction, then rightSegLen hrules, then top/mid right
|
|
1426
|
-
// corner.
|
|
1427
|
-
const leftPadded = leftCore + hrule.repeat(leftSegLen - leftCore.length);
|
|
1428
|
-
return (outerLeft +
|
|
1429
|
-
leftPadded +
|
|
1430
|
-
outerMid +
|
|
1431
|
-
hrule.repeat(rightSegLen) +
|
|
1432
|
-
outerRight);
|
|
1433
|
-
}
|
|
1434
|
-
// Label overflows the left segment. Drop the mid junction and pad
|
|
1435
|
-
// hrules between the outer corners. When the natural row inner width
|
|
1436
|
-
// (= leftSegLen + 1 + rightSegLen) is itself smaller than the label
|
|
1437
|
-
// plus a single trailing hrule, the rule grows past the data-row
|
|
1438
|
-
// width so the label always has at least one `─`/`-` of breathing
|
|
1439
|
-
// room before the closing corner (verified against vanilla psql 18:
|
|
1440
|
-
// `\gx` on `SELECT 1 as one, 2 as two \pset border 2` emits
|
|
1441
|
-
// `┌─[ RECORD 1 ]─┐` — 14 inner chars vs the 9-char inner data row).
|
|
1442
|
-
const innerWidth = Math.max(leftSegLen + 1 + rightSegLen, leftCore.length + 1);
|
|
1443
|
-
return (outerLeft +
|
|
1444
|
-
leftCore +
|
|
1445
|
-
hrule.repeat(innerWidth - leftCore.length) +
|
|
1446
|
-
outerRight);
|
|
872
|
+
const { hrule } = glyphs;
|
|
873
|
+
if (record === 0) {
|
|
874
|
+
if (border === 0) return " ".repeat(nameWidth + valueWidth);
|
|
875
|
+
if (border === 1) {
|
|
876
|
+
const rowWidth = nameWidth + 3 + valueWidth;
|
|
877
|
+
const leftSpan = nameWidth + 1;
|
|
878
|
+
return hrule.repeat(leftSpan) + glyphs.midMid + hrule.repeat(rowWidth - leftSpan - 1);
|
|
879
|
+
}
|
|
880
|
+
const outerLeft = isFirst ? glyphs.topLeft : glyphs.midLeft;
|
|
881
|
+
const outerRight = isFirst ? glyphs.topRight : glyphs.midRight;
|
|
882
|
+
const outerMid = isFirst ? glyphs.topMid : glyphs.midMid;
|
|
883
|
+
return outerLeft + hrule.repeat(nameWidth + 2) + outerMid + hrule.repeat(valueWidth + 2) + outerRight;
|
|
884
|
+
}
|
|
885
|
+
if (border === 0) {
|
|
886
|
+
const label = `* Record ${String(record)}`;
|
|
887
|
+
const target = nameWidth + valueWidth;
|
|
888
|
+
if (label.length >= target) return label;
|
|
889
|
+
return label + " ".repeat(target - label.length);
|
|
890
|
+
}
|
|
891
|
+
const label = `[ RECORD ${String(record)} ]`;
|
|
892
|
+
if (border === 1) {
|
|
893
|
+
const rowWidth = nameWidth + 3 + valueWidth;
|
|
894
|
+
const leftSpan = nameWidth + 1;
|
|
895
|
+
const rightSpan = valueWidth + 1;
|
|
896
|
+
const left = hrule + label;
|
|
897
|
+
if (left.length <= leftSpan) return left + hrule.repeat(leftSpan - left.length) + glyphs.midMid + hrule.repeat(rightSpan);
|
|
898
|
+
if (left.length < rowWidth) return left + hrule.repeat(rowWidth - left.length);
|
|
899
|
+
return left;
|
|
900
|
+
}
|
|
901
|
+
const outerLeft = isFirst ? glyphs.topLeft : glyphs.midLeft;
|
|
902
|
+
const outerRight = isFirst ? glyphs.topRight : glyphs.midRight;
|
|
903
|
+
const outerMid = isFirst ? glyphs.topMid : glyphs.midMid;
|
|
904
|
+
const leftSegLen = nameWidth + 2;
|
|
905
|
+
const rightSegLen = valueWidth + 2;
|
|
906
|
+
const leftCore = hrule + label;
|
|
907
|
+
if (leftCore.length <= leftSegLen) return outerLeft + (leftCore + hrule.repeat(leftSegLen - leftCore.length)) + outerMid + hrule.repeat(rightSegLen) + outerRight;
|
|
908
|
+
const innerWidth = Math.max(leftSegLen + 1 + rightSegLen, leftCore.length + 1);
|
|
909
|
+
return outerLeft + leftCore + hrule.repeat(innerWidth - leftCore.length) + outerRight;
|
|
1447
910
|
};
|
|
1448
911
|
/**
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
912
|
+
* Render one cell (header + value) of an expanded-mode record.
|
|
913
|
+
*
|
|
914
|
+
* Mirrors the per-cell loop in upstream `print_aligned_vertical`
|
|
915
|
+
* (print.c lines 1636-1801). Each iteration of the outer loop emits ONE
|
|
916
|
+
* physical output line containing both the header part and the data
|
|
917
|
+
* part. Iteration continues until BOTH the header and data sides are
|
|
918
|
+
* exhausted (so a 3-line header against a 1-line value still emits 3
|
|
919
|
+
* lines, with the data side blank from line 2 onward).
|
|
920
|
+
*
|
|
921
|
+
* Layout per iteration (border=0):
|
|
922
|
+
* <name padded to nameWidth><header_nl_right or space>
|
|
923
|
+
* <" " or wrap_left><value chunk (padded to dwidth iff more follows)><wrap_right / nl_right>
|
|
924
|
+
*
|
|
925
|
+
* Layout per iteration (border=1):
|
|
926
|
+
* <name padded to nameWidth><header_nl_right or space>
|
|
927
|
+
* <vrule>
|
|
928
|
+
* <" " or wrap_left><value chunk><wrap_right / nl_right or trailing space>
|
|
929
|
+
*
|
|
930
|
+
* Layout per iteration (border=2): adds outer vrules, always pads the
|
|
931
|
+
* value column to dwidth, and always emits trailing markers.
|
|
932
|
+
*
|
|
933
|
+
* When header is done but data is not, the header side becomes pure
|
|
934
|
+
* whitespace of width `hwidth + opt_border (+ 1 for border-0 hmultiline)`.
|
|
935
|
+
* When data is done but header is not (because hheight > dheight), the
|
|
936
|
+
* data side is just `\n` at border<2, or `<dwidth spaces> |` at border>=2.
|
|
937
|
+
*/
|
|
1475
938
|
const renderVerticalCell = (headerLines, dataLines, nameWidth, dwidth, border, glyphs, hmultiline, emitHeaderMarker, emitDataMarker, emitHeaderLeftSlot, oldAscii) => {
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
if (border > 0) {
|
|
1562
|
-
if (offset !== 0)
|
|
1563
|
-
out += glyphs.midvruleWrap;
|
|
1564
|
-
else if (dLine === 0)
|
|
1565
|
-
out += vrule;
|
|
1566
|
-
else
|
|
1567
|
-
out += glyphs.midvruleNl;
|
|
1568
|
-
}
|
|
1569
|
-
// ---- Data part ----
|
|
1570
|
-
if (!dcomplete) {
|
|
1571
|
-
const chunk = dataChunks[dLine];
|
|
1572
|
-
// Leading slot: " " on the first chunk of a source line,
|
|
1573
|
-
// wrap_left on a wrap continuation. ALWAYS emitted (upstream
|
|
1574
|
-
// print.c line 1731 has no border guard).
|
|
1575
|
-
out += offset === 0 ? " " : glyphs.wrapLeft;
|
|
1576
|
-
out += chunk.text;
|
|
1577
|
-
// Mirror upstream's `offset += bytes_to_output`: bumped even on
|
|
1578
|
-
// the last chunk of a cell, so the next iteration (header still
|
|
1579
|
-
// continuing) sees offset > 0 and picks midvrule_wrap.
|
|
1580
|
-
offset += chunk.width;
|
|
1581
|
-
const isLastChunk = dLine === dataChunks.length - 1;
|
|
1582
|
-
const nextChunk = !isLastChunk ? dataChunks[dLine + 1] : null;
|
|
1583
|
-
let needsPad = false;
|
|
1584
|
-
let markerGlyph = "";
|
|
1585
|
-
if (nextChunk && !chunk.isWrapEnd) {
|
|
1586
|
-
// Wrap continuation: next chunk continues THIS source line.
|
|
1587
|
-
if (emitDataMarker) {
|
|
1588
|
-
needsPad = true;
|
|
1589
|
-
markerGlyph = glyphs.wrapRight;
|
|
1590
|
-
}
|
|
1591
|
-
dLine++;
|
|
1592
|
-
// Offset stays bumped — next chunk continues the same source line.
|
|
1593
|
-
}
|
|
1594
|
-
else if (nextChunk) {
|
|
1595
|
-
// Source-line boundary: next chunk starts a new data line.
|
|
1596
|
-
if (emitDataMarker) {
|
|
1597
|
-
needsPad = true;
|
|
1598
|
-
markerGlyph = glyphs.nlRight;
|
|
1599
|
-
}
|
|
1600
|
-
dLine++;
|
|
1601
|
-
offset = 0; // reset for new source line
|
|
1602
|
-
}
|
|
1603
|
-
else {
|
|
1604
|
-
// End of cell.
|
|
1605
|
-
if (border > 1) {
|
|
1606
|
-
// Border 2/3: pad to dwidth, trailing space, then vrule.
|
|
1607
|
-
needsPad = true;
|
|
1608
|
-
markerGlyph = " ";
|
|
1609
|
-
}
|
|
1610
|
-
dcomplete = true;
|
|
1611
|
-
// Offset stays bumped — header-tail iterations should see
|
|
1612
|
-
// midvrule_wrap unless the cell was empty.
|
|
1613
|
-
}
|
|
1614
|
-
if (needsPad) {
|
|
1615
|
-
const pad = dwidth - chunk.width;
|
|
1616
|
-
if (pad > 0)
|
|
1617
|
-
out += " ".repeat(pad);
|
|
1618
|
-
}
|
|
1619
|
-
out += markerGlyph;
|
|
1620
|
-
// ---- Right border ----
|
|
1621
|
-
if (border === 2 || border === 3)
|
|
1622
|
-
out += vrule;
|
|
1623
|
-
}
|
|
1624
|
-
else {
|
|
1625
|
-
// Data exhausted. Border<2 emits no further chars (a bare `\n`).
|
|
1626
|
-
// Border>=2 pads the value column then closes with right vrule
|
|
1627
|
-
// (upstream: `fprintf(fout, "%*s %s\n", dwidth, "", rightvrule)`
|
|
1628
|
-
// — note the TWO trailing spaces between the dwidth pad and rule).
|
|
1629
|
-
if (border === 2 || border === 3) {
|
|
1630
|
-
out += " ".repeat(dwidth) + " " + vrule;
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
out += "\n";
|
|
1634
|
-
}
|
|
1635
|
-
return out;
|
|
939
|
+
const { vrule } = glyphs;
|
|
940
|
+
let out = "";
|
|
941
|
+
const dataChunks = [];
|
|
942
|
+
for (const src of dataLines) if (dwidth > 0 && displayWidth(src) > dwidth) {
|
|
943
|
+
const pieces = wrapLine(src, dwidth);
|
|
944
|
+
const lastIdx = pieces.length - 1;
|
|
945
|
+
pieces.forEach((piece, pi) => {
|
|
946
|
+
dataChunks.push({
|
|
947
|
+
text: piece,
|
|
948
|
+
width: displayWidth(piece),
|
|
949
|
+
isWrapStart: pi === 0,
|
|
950
|
+
isWrapEnd: pi === lastIdx
|
|
951
|
+
});
|
|
952
|
+
});
|
|
953
|
+
} else dataChunks.push({
|
|
954
|
+
text: src,
|
|
955
|
+
width: displayWidth(src),
|
|
956
|
+
isWrapStart: true,
|
|
957
|
+
isWrapEnd: true
|
|
958
|
+
});
|
|
959
|
+
let hLine = 0;
|
|
960
|
+
let dLine = 0;
|
|
961
|
+
let offset = 0;
|
|
962
|
+
let hcomplete = headerLines.length === 0;
|
|
963
|
+
let dcomplete = dataChunks.length === 0;
|
|
964
|
+
while (!hcomplete || !dcomplete) {
|
|
965
|
+
if (border === 2 || border === 3) out += vrule;
|
|
966
|
+
if (!hcomplete) {
|
|
967
|
+
if (emitHeaderLeftSlot) out += hLine > 0 ? glyphs.headerNlLeft : " ";
|
|
968
|
+
const text = headerLines[hLine];
|
|
969
|
+
out += padToWidth(text, nameWidth, "left");
|
|
970
|
+
if (hLine + 1 < headerLines.length) {
|
|
971
|
+
if (emitHeaderMarker) out += glyphs.headerNlRight;
|
|
972
|
+
hLine++;
|
|
973
|
+
} else {
|
|
974
|
+
if (emitHeaderMarker) out += " ";
|
|
975
|
+
hcomplete = true;
|
|
976
|
+
}
|
|
977
|
+
} else {
|
|
978
|
+
let swidth = nameWidth + border;
|
|
979
|
+
if (border < 2 && hmultiline && oldAscii) swidth++;
|
|
980
|
+
if (border === 0 && hmultiline && !oldAscii) swidth++;
|
|
981
|
+
out += " ".repeat(swidth);
|
|
982
|
+
}
|
|
983
|
+
if (border > 0) if (offset !== 0) out += glyphs.midvruleWrap;
|
|
984
|
+
else if (dLine === 0) out += vrule;
|
|
985
|
+
else out += glyphs.midvruleNl;
|
|
986
|
+
if (!dcomplete) {
|
|
987
|
+
const chunk = dataChunks[dLine];
|
|
988
|
+
out += offset === 0 ? " " : glyphs.wrapLeft;
|
|
989
|
+
out += chunk.text;
|
|
990
|
+
offset += chunk.width;
|
|
991
|
+
const nextChunk = !(dLine === dataChunks.length - 1) ? dataChunks[dLine + 1] : null;
|
|
992
|
+
let needsPad = false;
|
|
993
|
+
let markerGlyph = "";
|
|
994
|
+
if (nextChunk && !chunk.isWrapEnd) {
|
|
995
|
+
if (emitDataMarker) {
|
|
996
|
+
needsPad = true;
|
|
997
|
+
markerGlyph = glyphs.wrapRight;
|
|
998
|
+
}
|
|
999
|
+
dLine++;
|
|
1000
|
+
} else if (nextChunk) {
|
|
1001
|
+
if (emitDataMarker) {
|
|
1002
|
+
needsPad = true;
|
|
1003
|
+
markerGlyph = glyphs.nlRight;
|
|
1004
|
+
}
|
|
1005
|
+
dLine++;
|
|
1006
|
+
offset = 0;
|
|
1007
|
+
} else {
|
|
1008
|
+
if (border > 1) {
|
|
1009
|
+
needsPad = true;
|
|
1010
|
+
markerGlyph = " ";
|
|
1011
|
+
}
|
|
1012
|
+
dcomplete = true;
|
|
1013
|
+
}
|
|
1014
|
+
if (needsPad) {
|
|
1015
|
+
const pad = dwidth - chunk.width;
|
|
1016
|
+
if (pad > 0) out += " ".repeat(pad);
|
|
1017
|
+
}
|
|
1018
|
+
out += markerGlyph;
|
|
1019
|
+
if (border === 2 || border === 3) out += vrule;
|
|
1020
|
+
} else if (border === 2 || border === 3) out += " ".repeat(dwidth) + " " + vrule;
|
|
1021
|
+
out += "\n";
|
|
1022
|
+
}
|
|
1023
|
+
return out;
|
|
1636
1024
|
};
|
|
1637
|
-
// ---------------------------------------------------------------------------
|
|
1638
|
-
// Mode selection.
|
|
1639
|
-
// ---------------------------------------------------------------------------
|
|
1640
1025
|
const horizontalTotalWidth = (rs, topt) => {
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
else if (border === 1)
|
|
1650
|
-
overhead = n * 3 - (n > 0 ? 1 : 0);
|
|
1651
|
-
else
|
|
1652
|
-
overhead = n * 3 + 1;
|
|
1653
|
-
return overhead + widths.reduce((a, b) => a + b, 0);
|
|
1026
|
+
const widths = computeColumnWidths(rs, topt);
|
|
1027
|
+
const border = topt.border;
|
|
1028
|
+
const n = widths.length;
|
|
1029
|
+
let overhead;
|
|
1030
|
+
if (border === 0) overhead = n;
|
|
1031
|
+
else if (border === 1) overhead = n * 3 - (n > 0 ? 1 : 0);
|
|
1032
|
+
else overhead = n * 3 + 1;
|
|
1033
|
+
return overhead + widths.reduce((a, b) => a + b, 0);
|
|
1654
1034
|
};
|
|
1655
1035
|
const chooseExpanded = (rs, topt) => {
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
}
|
|
1664
|
-
return false;
|
|
1036
|
+
if (topt.expanded === "on") return true;
|
|
1037
|
+
if (topt.expanded === "auto") {
|
|
1038
|
+
const maxColumns = topt.columns > 0 ? topt.columns : topt.envColumns;
|
|
1039
|
+
if (maxColumns <= 0) return false;
|
|
1040
|
+
return horizontalTotalWidth(rs, topt) > maxColumns;
|
|
1041
|
+
}
|
|
1042
|
+
return false;
|
|
1665
1043
|
};
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
else if (rs.rows.length === 0) {
|
|
1679
|
-
// Empty result: header + (0 rows) only, regardless of border.
|
|
1680
|
-
text = renderEmpty(rs, opts);
|
|
1681
|
-
}
|
|
1682
|
-
else {
|
|
1683
|
-
text = renderHorizontal(rs, opts, wrapped);
|
|
1684
|
-
}
|
|
1685
|
-
out.write(text);
|
|
1686
|
-
return Promise.resolve();
|
|
1687
|
-
},
|
|
1044
|
+
const alignedPrinter = {
|
|
1045
|
+
format: "aligned",
|
|
1046
|
+
printQuery(rs, opts, out) {
|
|
1047
|
+
const expanded = chooseExpanded(rs, opts.topt);
|
|
1048
|
+
const wrapped = opts.topt.format === "wrapped";
|
|
1049
|
+
let text;
|
|
1050
|
+
if (expanded) text = renderVertical(rs, opts);
|
|
1051
|
+
else if (rs.rows.length === 0) text = renderEmpty(rs, opts);
|
|
1052
|
+
else text = renderHorizontal(rs, opts, wrapped);
|
|
1053
|
+
out.write(text);
|
|
1054
|
+
return Promise.resolve();
|
|
1055
|
+
}
|
|
1688
1056
|
};
|
|
1689
1057
|
const renderEmpty = (rs, opts) => {
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
out += " ".repeat((widthTotal - titleW) >> 1) + topt.title + "\n";
|
|
1716
|
-
}
|
|
1717
|
-
}
|
|
1718
|
-
if (!tuplesOnly && (border === 2 || border === 3)) {
|
|
1719
|
-
out += buildRule(widths, border, glyphs, "top") + "\n";
|
|
1720
|
-
}
|
|
1721
|
-
if (!tuplesOnly) {
|
|
1722
|
-
const noneStates = headerCells.map(() => "none");
|
|
1723
|
-
out +=
|
|
1724
|
-
renderHeaderLine(headerCells.map((c) => c.lines[0] ?? ""), widths, border, glyphs, noneStates, noneStates, true) + "\n";
|
|
1725
|
-
out += buildRule(widths, border, glyphs, "middle") + "\n";
|
|
1726
|
-
}
|
|
1727
|
-
if (!tuplesOnly && (border === 2 || border === 3)) {
|
|
1728
|
-
out += buildRule(widths, border, glyphs, "bottom") + "\n";
|
|
1729
|
-
}
|
|
1730
|
-
// User footers suppress the default `(0 rows)` row counter — mirrors
|
|
1731
|
-
// upstream `footers_with_default` (print.c lines 397-413).
|
|
1732
|
-
const hasUserFooters = !!opts.footers && opts.footers.length > 0;
|
|
1733
|
-
if (!tuplesOnly && topt.defaultFooter && !hasUserFooters) {
|
|
1734
|
-
out += "(0 rows)\n";
|
|
1735
|
-
}
|
|
1736
|
-
if (!tuplesOnly && opts.footers) {
|
|
1737
|
-
for (const f of opts.footers)
|
|
1738
|
-
out += f + "\n";
|
|
1739
|
-
}
|
|
1740
|
-
// Trailing blank line between query results, mirroring the horizontal /
|
|
1741
|
-
// vertical code paths (which append `\n` via `printTableCleanup`).
|
|
1742
|
-
// Empty results were previously missing this separator, which made
|
|
1743
|
-
// multiple back-to-back `\d`-family queries run together. Emitted
|
|
1744
|
-
// unconditionally (matches `fputc('\n', fout)` on print.c line 1196 /
|
|
1745
|
-
// 1821 — both are outside the `!opt_tuples_only` guard).
|
|
1746
|
-
out += "\n";
|
|
1747
|
-
return out;
|
|
1058
|
+
const topt = opts.topt;
|
|
1059
|
+
const tuplesOnly = topt.tuplesOnly;
|
|
1060
|
+
const border = topt.border;
|
|
1061
|
+
const glyphs = glyphsFor(topt.unicodeBorderLineStyle);
|
|
1062
|
+
const headerCells = rs.fields.map((f) => formatCell(f.name));
|
|
1063
|
+
const widths = headerCells.map((c) => c.width);
|
|
1064
|
+
let out = "";
|
|
1065
|
+
if (!tuplesOnly && topt.title) {
|
|
1066
|
+
const widthTotal = (border === 0 ? widths.length : border === 1 ? widths.length * 3 - (widths.length > 0 ? 1 : 0) : widths.length * 3 + 1) + widths.reduce((a, b) => a + b, 0);
|
|
1067
|
+
const titleW = displayWidth(topt.title);
|
|
1068
|
+
if (titleW >= widthTotal) out += topt.title + "\n";
|
|
1069
|
+
else out += " ".repeat(widthTotal - titleW >> 1) + topt.title + "\n";
|
|
1070
|
+
}
|
|
1071
|
+
if (!tuplesOnly && (border === 2 || border === 3)) out += buildRule(widths, border, glyphs, "top") + "\n";
|
|
1072
|
+
if (!tuplesOnly) {
|
|
1073
|
+
const noneStates = headerCells.map(() => "none");
|
|
1074
|
+
out += renderHeaderLine(headerCells.map((c) => c.lines[0] ?? ""), widths, border, glyphs, noneStates, noneStates, true) + "\n";
|
|
1075
|
+
out += buildRule(widths, border, glyphs, "middle") + "\n";
|
|
1076
|
+
}
|
|
1077
|
+
if (!tuplesOnly && (border === 2 || border === 3)) out += buildRule(widths, border, glyphs, "bottom") + "\n";
|
|
1078
|
+
const hasUserFooters = !!opts.footers && opts.footers.length > 0;
|
|
1079
|
+
if (!tuplesOnly && topt.defaultFooter && !hasUserFooters) out += "(0 rows)\n";
|
|
1080
|
+
if (!tuplesOnly && opts.footers) for (const f of opts.footers) out += f + "\n";
|
|
1081
|
+
out += "\n";
|
|
1082
|
+
return out;
|
|
1748
1083
|
};
|
|
1084
|
+
//#endregion
|
|
1085
|
+
export { alignedPrinter, computeColumnWidths, displayWidth, padToWidth };
|