Skip to content
This repository was archived by the owner on Jul 6, 2023. It is now read-only.

Commit d963538

Browse files
LinneaAnderssoninmost-light
authored andcommitted
Add support for multiline details column
1 parent 09ce816 commit d963538

File tree

2 files changed

+174
-73
lines changed

2 files changed

+174
-73
lines changed

cypher-shell/src/main/java/org/neo4j/shell/prettyprint/TablePlanFormatter.java

Lines changed: 106 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,16 @@ private int width(@Nonnull String header, @Nonnull Map<String, Integer> columns)
5656
return 2 + Math.max(header.length(), columns.get(header));
5757
}
5858

59-
private void pad(int width, char chr, @Nonnull StringBuilder result) {
59+
private static void pad(int width, char chr, @Nonnull StringBuilder result) {
6060
result.append(OutputFormatter.repeat(chr, width));
6161
}
6262

6363

64-
private void divider(@Nonnull List<String> headers, @Nullable Line line /*= null*/, @Nonnull StringBuilder result, @Nonnull Map<String, Integer> columns) {
64+
private void divider(@Nonnull List<String> headers, @Nullable TableRow tableRow /*= null*/, @Nonnull StringBuilder result, @Nonnull Map<String, Integer> columns) {
6565
for (String header : headers) {
66-
if (line != null && header.equals(OPERATOR) && line.connection.isPresent()) {
66+
if (tableRow != null && header.equals(OPERATOR) && tableRow.connection.isPresent()) {
6767
result.append("|");
68-
String connection = line.connection.get();
68+
String connection = tableRow.connection.get();
6969
result.append(" ").append(connection);
7070
pad(width(header, columns) - connection.length() - 1, ' ', result);
7171
} else {
@@ -79,33 +79,34 @@ private void divider(@Nonnull List<String> headers, @Nullable Line line /*= null
7979
@Nonnull
8080
String formatPlan(@Nonnull Plan plan) {
8181
Map<String, Integer> columns = new HashMap<>();
82-
List<Line> lines = accumulate(plan, new Root(), columns);
82+
List<TableRow> tableRows = accumulate(plan, new Root(), columns);
8383

8484
// Remove Identifiers column if we have a Details column
8585
List<String> headers = HEADERS.stream().filter(header -> columns.containsKey(header) && !(header.equals(IDENTIFIERS) && columns.containsKey(DETAILS))).collect(Collectors.toList());
8686

87-
StringBuilder result = new StringBuilder((2 + NEWLINE.length() + headers.stream().mapToInt(h -> width(h, columns)).sum()) * (lines.size() * 2 + 3));
88-
89-
List<Line> allLines = new ArrayList<>();
90-
Map<String, Justified> headerMap = headers.stream().map(header -> Pair.of(header, new Left(header))).collect(toMap(p -> p._1, p -> p._2));
91-
allLines.add(new Line(OPERATOR, headerMap, Optional.empty()));
92-
allLines.addAll(lines);
93-
for (Line line : allLines) {
94-
divider(headers, line, result, columns);
95-
for (String header : headers) {
96-
Justified detail = line.get(header);
97-
result.append("| ");
98-
if (detail instanceof Left) {
99-
result.append(detail.text);
100-
pad(width(header, columns) - detail.length - 2, ' ', result);
87+
StringBuilder result = new StringBuilder((2 + NEWLINE.length() + headers.stream().mapToInt(h -> width(h, columns)).sum()) * (tableRows.size() * 2 + 3));
88+
89+
List<TableRow> allTableRows = new ArrayList<>();
90+
Map<String,Cell> headerMap = headers.stream().map(header -> Pair.of(header, new LeftJustifiedCell(header))).collect(toMap(p -> p._1, p -> p._2));
91+
allTableRows.add(new TableRow(OPERATOR, headerMap, Optional.empty()));
92+
allTableRows.addAll( tableRows );
93+
for (int rowIndex = 0; rowIndex < allTableRows.size(); rowIndex++) {
94+
TableRow tableRow = allTableRows.get( rowIndex);
95+
divider(headers, tableRow, result, columns);
96+
for (int rowLineIndex = 0; rowLineIndex < tableRow.height; rowLineIndex++) {
97+
for (String header : headers) {
98+
Cell cell = tableRow.get(header);
99+
String defaultText = "";
100+
if (header.equals(OPERATOR) && rowIndex + 1 < allTableRows.size()) {
101+
defaultText = allTableRows.get(rowIndex + 1).connection.orElse("").replace('\\', ' ');
102+
}
103+
result.append( "| " );
104+
int columnWidth = width(header, columns);
105+
cell.writePaddedLine(rowLineIndex, defaultText, columnWidth, result);
106+
result.append( " " );
101107
}
102-
if (detail instanceof Right) {
103-
pad(width(header, columns) - detail.length - 2, ' ', result);
104-
result.append(detail.text);
105-
}
106-
result.append(" ");
108+
result.append("|").append(NEWLINE);
107109
}
108-
result.append("|").append(NEWLINE);
109110
}
110111
divider(headers, null, result, columns);
111112

@@ -171,7 +172,7 @@ private String serialize(@Nonnull String key, @Nonnull Value v) {
171172
}
172173
}
173174

174-
@Nonnull private Stream<List<Line>> children(@Nonnull Plan plan, Level level,@Nonnull Map<String, Integer> columns) {
175+
@Nonnull private Stream<List<TableRow>> children(@Nonnull Plan plan, Level level, @Nonnull Map<String, Integer> columns) {
175176
List<? extends Plan> c = plan.children();
176177
switch (c.size()) {
177178
case 0:
@@ -184,54 +185,55 @@ private String serialize(@Nonnull String key, @Nonnull Value v) {
184185
throw new IllegalStateException("Plan has more than 2 children " + c);
185186
}
186187

187-
@Nonnull private List<Line> accumulate(@Nonnull Plan plan, @Nonnull Level level, @Nonnull Map<String, Integer> columns) {
188+
@Nonnull private List<TableRow> accumulate(@Nonnull Plan plan, @Nonnull Level level, @Nonnull Map<String, Integer> columns) {
188189
String line = level.line() + plan.operatorType(); // wa plan.name
189-
mapping(OPERATOR, new Left(line), columns);
190+
mapping(OPERATOR, new LeftJustifiedCell(line), columns);
190191

191192
return Stream.concat(
192-
Stream.of(new Line(line, details(plan, columns), level.connector())),
193+
Stream.of(new TableRow(line, details(plan, columns), level.connector())),
193194
children(plan, level, columns).flatMap(Collection::stream))
194195
.collect(Collectors.toList());
195196
}
196197

197198
@Nonnull
198-
private Map<String, Justified> details(@Nonnull Plan plan, @Nonnull Map<String, Integer> columns) {
199+
private Map<String,Cell> details(@Nonnull Plan plan, @Nonnull Map<String, Integer> columns) {
199200
Map<String, Value> args = plan.arguments();
200201

201-
Stream<Optional<Pair<String, Justified>>> formattedPlan = args.entrySet().stream().map((e) -> {
202+
Stream<Optional<Pair<String,Cell>>> formattedPlan = args.entrySet().stream().map(e -> {
202203
Value value = e.getValue();
203204
switch (e.getKey()) {
204205
case "EstimatedRows":
205-
return mapping(ESTIMATED_ROWS, new Right(format(value.asDouble())), columns);
206+
return mapping(ESTIMATED_ROWS, new RightJustifiedCell(format(value.asDouble())), columns);
206207
case "Rows":
207-
return mapping(ROWS, new Right(value.asNumber().toString()), columns);
208+
return mapping(ROWS, new RightJustifiedCell(value.asNumber().toString()), columns);
208209
case "DbHits":
209-
return mapping(HITS, new Right(value.asNumber().toString()), columns);
210+
return mapping(HITS, new RightJustifiedCell(value.asNumber().toString()), columns);
210211
case "PageCacheHits":
211-
return mapping(PAGE_CACHE, new Right(String.format("%s/%s",value.asNumber(),args.getOrDefault("PageCacheMisses", ZERO_VALUE).asNumber())), columns);
212+
return mapping(PAGE_CACHE, new RightJustifiedCell(String.format("%s/%s", value.asNumber(), args.getOrDefault("PageCacheMisses", ZERO_VALUE).asNumber())), columns);
212213
case "Time":
213-
return mapping(TIME, new Right(String.format("%.3f", value.asLong() / 1000000.0d)), columns);
214+
return mapping(TIME, new RightJustifiedCell(String.format("%.3f", value.asLong() / 1000000.0d)), columns);
214215
case "Order":
215-
return mapping( ORDER, new Left( String.format( "%s", value.asString() ) ), columns );
216+
return mapping(ORDER, new LeftJustifiedCell(String.format("%s", value.asString())), columns);
216217
case "Details":
217-
return mapping( DETAILS, new Left( truncate(value.asString()) ), columns );
218+
return mapping(DETAILS, new LeftJustifiedCell(splitDetails(value.asString())), columns);
218219
case "Memory":
219-
return mapping( MEMORY, new Right( String.format( "%s", value.asNumber().toString() ) ), columns );
220+
return mapping(MEMORY, new RightJustifiedCell(String.format("%s", value.asNumber().toString())), columns);
220221
default:
221222
return Optional.empty();
222223
}
223224
});
225+
224226
return Stream.concat(
225227
formattedPlan,
226228
Stream.of(
227-
Optional.of(Pair.of(IDENTIFIERS, new Left(identifiers(plan, columns)))),
228-
Optional.of(Pair.of(OTHER, new Left(other(plan, columns))))))
229-
.filter(Optional::isPresent)
230-
.collect(toMap(o -> o.get()._1, o -> o.get()._2));
229+
Optional.of(Pair.of(IDENTIFIERS, new LeftJustifiedCell(identifiers(plan, columns)))),
230+
Optional.of(Pair.of(OTHER, new LeftJustifiedCell(other(plan, columns))))))
231+
.filter(Optional::isPresent)
232+
.collect(toMap(o -> o.get()._1, o -> o.get()._2));
231233
}
232234

233235
@Nonnull
234-
private Optional<Pair<String, Justified>> mapping(@Nonnull String key, @Nonnull Justified value, @Nonnull Map<String, Integer> columns) {
236+
private Optional<Pair<String,Cell>> mapping(@Nonnull String key, @Nonnull Cell value, @Nonnull Map<String, Integer> columns) {
235237
update(columns, key, value.length);
236238
return Optional.of(Pair.of(key, value));
237239
}
@@ -285,46 +287,75 @@ private String format(@Nonnull Double v) {
285287
return String.valueOf(Math.round(v));
286288
}
287289

288-
289-
static class Line {
290-
290+
static class TableRow {
291291
private final String tree;
292-
private final Map<String, Justified> details;
292+
private final Map<String,Cell> cells;
293293
private final Optional<String> connection;
294+
private final int height;
294295

295-
Line(String tree, Map<String, Justified> details, Optional<String> connection) {
296+
TableRow(String tree, Map<String,Cell> cells, Optional<String> connection) {
296297
this.tree = tree;
297-
this.details = details;
298+
this.cells = cells;
298299
this.connection = connection == null ? Optional.empty() : connection;
300+
this.height = cells.values().stream().mapToInt(v -> v.lines.length).max().orElse(0);
299301
}
300302

301-
Justified get(String key) {
302-
if (key.equals(TablePlanFormatter.OPERATOR)) {
303-
return new Left(tree);
304-
} else
305-
return details.getOrDefault(key, new Left(""));
303+
Cell get(String key) {
304+
if (key.equals(TablePlanFormatter.OPERATOR))
305+
return new LeftJustifiedCell(tree);
306+
else
307+
return cells.getOrDefault(key, new LeftJustifiedCell(""));
306308
}
307309
}
308310

309-
static abstract class Justified {
311+
static abstract class Cell {
310312
final int length;
311-
final String text;
313+
final String[] lines;
314+
315+
Cell(String[] lines) {
316+
this.length = Stream.of(lines).mapToInt(String::length).max().orElse(0);
317+
this.lines = lines;
318+
}
319+
320+
abstract void writePaddedLine(int lineIndex, String orElseValue, int columnWidth, StringBuilder result);
321+
322+
protected int paddingWidth(int columnWidth, String line) {
323+
return columnWidth - line.length() - 2;
324+
}
312325

313-
Justified(String text) {
314-
this.length = text.length();
315-
this.text = text;
326+
protected String getLineOrElse(int lineIndex, String orElseValue) {
327+
if (lineIndex < lines.length)
328+
return lines[lineIndex];
329+
else
330+
return orElseValue;
316331
}
317332
}
318333

319-
static class Left extends Justified {
320-
Left(String text) {
321-
super(text);
334+
static class LeftJustifiedCell extends Cell
335+
{
336+
LeftJustifiedCell(String... lines) {
337+
super(lines);
338+
}
339+
340+
@Override
341+
void writePaddedLine(int lineIndex, String orElseValue, int columnWidth, StringBuilder result) {
342+
String line = getLineOrElse(lineIndex, orElseValue);
343+
result.append(line);
344+
pad(paddingWidth(columnWidth, line), ' ', result);
322345
}
323346
}
324347

325-
static class Right extends Justified {
326-
Right(String text) {
327-
super(text);
348+
static class RightJustifiedCell extends Cell {
349+
RightJustifiedCell(String... lines) {
350+
super(lines);
351+
}
352+
353+
@Override
354+
void writePaddedLine(int lineIndex, String orElseValue, int columnWidth, StringBuilder result)
355+
{
356+
String line = getLineOrElse(lineIndex, orElseValue);
357+
pad(paddingWidth(columnWidth, line), ' ', result);
358+
result.append(line);
328359
}
329360
}
330361

@@ -447,11 +478,16 @@ public static <T1, T2> Pair<T1, T2> of(T1 _1, T2 _2) {
447478
}
448479
}
449480

450-
private String truncate( String original ) {
451-
if(original.length() <= MAX_DETAILS_COLUMN_WIDTH){
452-
return original;
481+
private String[] splitDetails(String original) {
482+
List<String> detailsList = new ArrayList<>();
483+
484+
int currentPos = 0;
485+
while(currentPos < original.length()){
486+
int newPos = Math.min(original.length(), currentPos + MAX_DETAILS_COLUMN_WIDTH);
487+
detailsList.add(original.substring( currentPos, newPos));
488+
currentPos = newPos;
453489
}
454490

455-
return original.substring( 0, MAX_DETAILS_COLUMN_WIDTH - 3 ) + "...";
491+
return detailsList.toArray(new String[0]);
456492
}
457493
}

0 commit comments

Comments
 (0)