Pretty-print values in the REPL
Pretty-print values in the REPL by printing each item in a list or
attrset on a separate line. When possible, single-item lists and
attrsets are printed on one line, as long as they don't contain a nested
list, attrset, or thunk.
Before:
```
{ attrs = { a = { b = { c = { }; }; }; }; list = [ 1 ]; list' = [ 1 2 3 ]; }
```
After:
```
{
attrs = {
a = {
b = {
c = { };
};
};
};
list = [ 1 ];
list' = [
1
2
3
];
}
```
(cherry picked from commit c0a15fb7d03dfb8f53bc6726c414bc88aa362592)
Change-Id: Ia2b41849165a5ddb63f7a8c272a2476b3e4292df
This commit is contained in:
parent
b221a14f0a
commit
1958152d14
|
@ -98,7 +98,8 @@ struct NixRepl
|
||||||
.ansiColors = true,
|
.ansiColors = true,
|
||||||
.force = true,
|
.force = true,
|
||||||
.derivationPaths = true,
|
.derivationPaths = true,
|
||||||
.maxDepth = maxDepth
|
.maxDepth = maxDepth,
|
||||||
|
.prettyIndent = 2
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,24 +17,29 @@ struct PrintOptions
|
||||||
* If true, output ANSI color sequences.
|
* If true, output ANSI color sequences.
|
||||||
*/
|
*/
|
||||||
bool ansiColors = false;
|
bool ansiColors = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, force values.
|
* If true, force values.
|
||||||
*/
|
*/
|
||||||
bool force = false;
|
bool force = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true and `force` is set, print derivations as
|
* If true and `force` is set, print derivations as
|
||||||
* `«derivation /nix/store/...»` instead of as attribute sets.
|
* `«derivation /nix/store/...»` instead of as attribute sets.
|
||||||
*/
|
*/
|
||||||
bool derivationPaths = false;
|
bool derivationPaths = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, track which values have been printed and skip them on
|
* If true, track which values have been printed and skip them on
|
||||||
* subsequent encounters. Useful for self-referential values.
|
* subsequent encounters. Useful for self-referential values.
|
||||||
*/
|
*/
|
||||||
bool trackRepeated = true;
|
bool trackRepeated = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum depth to evaluate to.
|
* Maximum depth to evaluate to.
|
||||||
*/
|
*/
|
||||||
size_t maxDepth = std::numeric_limits<size_t>::max();
|
size_t maxDepth = std::numeric_limits<size_t>::max();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum number of attributes in attribute sets to print.
|
* Maximum number of attributes in attribute sets to print.
|
||||||
*
|
*
|
||||||
|
@ -42,6 +47,7 @@ struct PrintOptions
|
||||||
* attribute set encountered.
|
* attribute set encountered.
|
||||||
*/
|
*/
|
||||||
size_t maxAttrs = std::numeric_limits<size_t>::max();
|
size_t maxAttrs = std::numeric_limits<size_t>::max();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum number of list items to print.
|
* Maximum number of list items to print.
|
||||||
*
|
*
|
||||||
|
@ -49,10 +55,26 @@ struct PrintOptions
|
||||||
* list encountered.
|
* list encountered.
|
||||||
*/
|
*/
|
||||||
size_t maxListItems = std::numeric_limits<size_t>::max();
|
size_t maxListItems = std::numeric_limits<size_t>::max();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum string length to print.
|
* Maximum string length to print.
|
||||||
*/
|
*/
|
||||||
size_t maxStringLength = std::numeric_limits<size_t>::max();
|
size_t maxStringLength = std::numeric_limits<size_t>::max();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indentation width for pretty-printing.
|
||||||
|
*
|
||||||
|
* If set to 0 (the default), values are not pretty-printed.
|
||||||
|
*/
|
||||||
|
size_t prettyIndent = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if pretty-printing is enabled.
|
||||||
|
*/
|
||||||
|
inline bool prettyPrint()
|
||||||
|
{
|
||||||
|
return prettyIndent > 0;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -151,6 +151,7 @@ struct ImportantFirstAttrNameCmp
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::set<const void *> ValuesSeen;
|
typedef std::set<const void *> ValuesSeen;
|
||||||
|
typedef std::vector<std::pair<std::string, Value *>> AttrVec;
|
||||||
|
|
||||||
class Printer
|
class Printer
|
||||||
{
|
{
|
||||||
|
@ -161,6 +162,21 @@ private:
|
||||||
std::optional<ValuesSeen> seen;
|
std::optional<ValuesSeen> seen;
|
||||||
size_t attrsPrinted = 0;
|
size_t attrsPrinted = 0;
|
||||||
size_t listItemsPrinted = 0;
|
size_t listItemsPrinted = 0;
|
||||||
|
std::string indent;
|
||||||
|
|
||||||
|
void increaseIndent()
|
||||||
|
{
|
||||||
|
if (options.prettyPrint()) {
|
||||||
|
indent.append(options.prettyIndent, ' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void decreaseIndent()
|
||||||
|
{
|
||||||
|
if (options.prettyPrint()) {
|
||||||
|
indent.resize(indent.size() - options.prettyIndent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void printRepeated()
|
void printRepeated()
|
||||||
{
|
{
|
||||||
|
@ -258,6 +274,28 @@ private:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool shouldPrettyPrintAttrs(AttrVec & v)
|
||||||
|
{
|
||||||
|
if (!options.prettyPrint() || v.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pretty-print attrsets with more than one item.
|
||||||
|
if (v.size() > 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto item = v[0].second;
|
||||||
|
if (!item) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pretty-print single-item attrsets only if they contain nested
|
||||||
|
// structures.
|
||||||
|
auto itemType = item->type();
|
||||||
|
return itemType == nList || itemType == nAttrs || itemType == nThunk;
|
||||||
|
}
|
||||||
|
|
||||||
void printAttrs(Value & v, size_t depth)
|
void printAttrs(Value & v, size_t depth)
|
||||||
{
|
{
|
||||||
if (seen && !seen->insert(v.attrs).second) {
|
if (seen && !seen->insert(v.attrs).second) {
|
||||||
|
@ -268,9 +306,10 @@ private:
|
||||||
if (options.force && options.derivationPaths && state.isDerivation(v)) {
|
if (options.force && options.derivationPaths && state.isDerivation(v)) {
|
||||||
printDerivation(v);
|
printDerivation(v);
|
||||||
} else if (depth < options.maxDepth) {
|
} else if (depth < options.maxDepth) {
|
||||||
output << "{ ";
|
increaseIndent();
|
||||||
|
output << "{";
|
||||||
|
|
||||||
std::vector<std::pair<std::string, Value *>> sorted;
|
AttrVec sorted;
|
||||||
for (auto & i : *v.attrs)
|
for (auto & i : *v.attrs)
|
||||||
sorted.emplace_back(std::pair(state.symbols[i.name], i.value));
|
sorted.emplace_back(std::pair(state.symbols[i.name], i.value));
|
||||||
|
|
||||||
|
@ -279,7 +318,15 @@ private:
|
||||||
else
|
else
|
||||||
std::sort(sorted.begin(), sorted.end(), ImportantFirstAttrNameCmp());
|
std::sort(sorted.begin(), sorted.end(), ImportantFirstAttrNameCmp());
|
||||||
|
|
||||||
|
auto prettyPrint = shouldPrettyPrintAttrs(sorted);
|
||||||
|
|
||||||
for (auto & i : sorted) {
|
for (auto & i : sorted) {
|
||||||
|
if (prettyPrint) {
|
||||||
|
output << "\n" << indent;
|
||||||
|
} else {
|
||||||
|
output << " ";
|
||||||
|
}
|
||||||
|
|
||||||
if (attrsPrinted >= options.maxAttrs) {
|
if (attrsPrinted >= options.maxAttrs) {
|
||||||
printElided(sorted.size() - attrsPrinted, "attribute", "attributes");
|
printElided(sorted.size() - attrsPrinted, "attribute", "attributes");
|
||||||
break;
|
break;
|
||||||
|
@ -288,14 +335,43 @@ private:
|
||||||
printAttributeName(output, i.first);
|
printAttributeName(output, i.first);
|
||||||
output << " = ";
|
output << " = ";
|
||||||
print(*i.second, depth + 1);
|
print(*i.second, depth + 1);
|
||||||
output << "; ";
|
output << ";";
|
||||||
attrsPrinted++;
|
attrsPrinted++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decreaseIndent();
|
||||||
|
if (prettyPrint) {
|
||||||
|
output << "\n" << indent;
|
||||||
|
} else {
|
||||||
|
output << " ";
|
||||||
|
}
|
||||||
output << "}";
|
output << "}";
|
||||||
} else
|
} else {
|
||||||
output << "{ ... }";
|
output << "{ ... }";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shouldPrettyPrintList(std::span<Value * const> list)
|
||||||
|
{
|
||||||
|
if (!options.prettyPrint() || list.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pretty-print lists with more than one item.
|
||||||
|
if (list.size() > 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto item = list[0];
|
||||||
|
if (!item) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pretty-print single-item lists only if they contain nested
|
||||||
|
// structures.
|
||||||
|
auto itemType = item->type();
|
||||||
|
return itemType == nList || itemType == nAttrs || itemType == nThunk;
|
||||||
|
}
|
||||||
|
|
||||||
void printList(Value & v, size_t depth)
|
void printList(Value & v, size_t depth)
|
||||||
{
|
{
|
||||||
|
@ -304,9 +380,18 @@ private:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
output << "[ ";
|
|
||||||
if (depth < options.maxDepth) {
|
if (depth < options.maxDepth) {
|
||||||
for (auto elem : v.listItems()) {
|
increaseIndent();
|
||||||
|
output << "[";
|
||||||
|
auto listItems = v.listItems();
|
||||||
|
auto prettyPrint = shouldPrettyPrintList(listItems);
|
||||||
|
for (auto elem : listItems) {
|
||||||
|
if (prettyPrint) {
|
||||||
|
output << "\n" << indent;
|
||||||
|
} else {
|
||||||
|
output << " ";
|
||||||
|
}
|
||||||
|
|
||||||
if (listItemsPrinted >= options.maxListItems) {
|
if (listItemsPrinted >= options.maxListItems) {
|
||||||
printElided(v.listSize() - listItemsPrinted, "item", "items");
|
printElided(v.listSize() - listItemsPrinted, "item", "items");
|
||||||
break;
|
break;
|
||||||
|
@ -317,13 +402,19 @@ private:
|
||||||
} else {
|
} else {
|
||||||
printNullptr();
|
printNullptr();
|
||||||
}
|
}
|
||||||
output << " ";
|
|
||||||
listItemsPrinted++;
|
listItemsPrinted++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decreaseIndent();
|
||||||
|
if (prettyPrint) {
|
||||||
|
output << "\n" << indent;
|
||||||
|
} else {
|
||||||
|
output << " ";
|
||||||
}
|
}
|
||||||
else
|
|
||||||
output << "... ";
|
|
||||||
output << "]";
|
output << "]";
|
||||||
|
} else {
|
||||||
|
output << "[ ... ]";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void printFunction(Value & v)
|
void printFunction(Value & v)
|
||||||
|
@ -486,6 +577,7 @@ public:
|
||||||
{
|
{
|
||||||
attrsPrinted = 0;
|
attrsPrinted = 0;
|
||||||
listItemsPrinted = 0;
|
listItemsPrinted = 0;
|
||||||
|
indent.clear();
|
||||||
|
|
||||||
if (options.trackRepeated) {
|
if (options.trackRepeated) {
|
||||||
seen.emplace();
|
seen.emplace();
|
||||||
|
|
|
@ -6,4 +6,4 @@ error:
|
||||||
| ^
|
| ^
|
||||||
10|
|
10|
|
||||||
|
|
||||||
error: cannot coerce a set to a string: { a = { a = { a = { a = "ha"; b = "ha"; c = "ha"; d = "ha"; e = "ha"; f = "ha"; g = "ha"; h = "ha"; j = "ha"; }; «4294967295 attributes elided»}; «4294967294 attributes elided»}; «4294967293 attributes elided»}
|
error: cannot coerce a set to a string: { a = { a = { a = { a = "ha"; b = "ha"; c = "ha"; d = "ha"; e = "ha"; f = "ha"; g = "ha"; h = "ha"; j = "ha"; }; «4294967295 attributes elided» }; «4294967294 attributes elided» }; «4294967293 attributes elided» }
|
||||||
|
|
|
@ -151,29 +151,86 @@ echo "$replResult" | grepQuiet -s afterChange
|
||||||
# Normal output should print attributes in lexicographical order non-recursively
|
# Normal output should print attributes in lexicographical order non-recursively
|
||||||
testReplResponseNoRegex '
|
testReplResponseNoRegex '
|
||||||
{ a = { b = 2; }; l = [ 1 2 3 ]; s = "string"; n = 1234; x = rec { y = { z = { inherit y; }; }; }; }
|
{ a = { b = 2; }; l = [ 1 2 3 ]; s = "string"; n = 1234; x = rec { y = { z = { inherit y; }; }; }; }
|
||||||
' '{ a = { ... }; l = [ ... ]; n = 1234; s = "string"; x = { ... }; }'
|
' \
|
||||||
|
'{
|
||||||
|
a = { ... };
|
||||||
|
l = [ ... ];
|
||||||
|
n = 1234;
|
||||||
|
s = "string";
|
||||||
|
x = { ... };
|
||||||
|
}
|
||||||
|
'
|
||||||
|
|
||||||
# Same for lists, but order is preserved
|
# Same for lists, but order is preserved
|
||||||
testReplResponseNoRegex '
|
testReplResponseNoRegex '
|
||||||
[ 42 1 "thingy" ({ a = 1; }) ([ 1 2 3 ]) ]
|
[ 42 1 "thingy" ({ a = 1; }) ([ 1 2 3 ]) ]
|
||||||
' '[ 42 1 "thingy" { ... } [ ... ] ]'
|
' \
|
||||||
|
'[
|
||||||
|
42
|
||||||
|
1
|
||||||
|
"thingy"
|
||||||
|
{ ... }
|
||||||
|
[ ... ]
|
||||||
|
]
|
||||||
|
'
|
||||||
|
|
||||||
# Same for let expressions
|
# Same for let expressions
|
||||||
testReplResponseNoRegex '
|
testReplResponseNoRegex '
|
||||||
let x = { y = { a = 1; }; inherit x; }; in x
|
let x = { y = { a = 1; }; inherit x; }; in x
|
||||||
' '{ x = «repeated»; y = { ... }; }'
|
' \
|
||||||
|
'{
|
||||||
|
x = { ... };
|
||||||
|
y = { ... };
|
||||||
|
}
|
||||||
|
'
|
||||||
|
|
||||||
# The :p command should recursively print sets, but prevent infinite recursion
|
# The :p command should recursively print sets, but prevent infinite recursion
|
||||||
testReplResponseNoRegex '
|
testReplResponseNoRegex '
|
||||||
:p { a = { b = 2; }; s = "string"; n = 1234; x = rec { y = { z = { inherit y; }; }; }; }
|
:p { a = { b = 2; }; s = "string"; n = 1234; x = rec { y = { z = { inherit y; }; }; }; }
|
||||||
' '{ a = { b = 2; }; n = 1234; s = "string"; x = { y = { z = { y = «repeated»; }; }; }; }'
|
' \
|
||||||
|
'{
|
||||||
|
a = { b = 2; };
|
||||||
|
n = 1234;
|
||||||
|
s = "string";
|
||||||
|
x = {
|
||||||
|
y = {
|
||||||
|
z = {
|
||||||
|
y = «repeated»;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
'
|
||||||
|
|
||||||
# Same for lists
|
# Same for lists
|
||||||
testReplResponseNoRegex '
|
testReplResponseNoRegex '
|
||||||
:p [ 42 1 "thingy" (rec { a = 1; b = { inherit a; inherit b; }; }) ([ 1 2 3 ]) ]
|
:p [ 42 1 "thingy" (rec { a = 1; b = { inherit a; inherit b; }; }) ([ 1 2 3 ]) ]
|
||||||
' '[ 42 1 "thingy" { a = 1; b = { a = 1; b = «repeated»; }; } [ 1 2 3 ] ]'
|
' \
|
||||||
|
'[
|
||||||
|
42
|
||||||
|
1
|
||||||
|
"thingy"
|
||||||
|
{
|
||||||
|
a = 1;
|
||||||
|
b = {
|
||||||
|
a = 1;
|
||||||
|
b = «repeated»;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
[
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
]
|
||||||
|
]
|
||||||
|
'
|
||||||
|
|
||||||
# Same for let expressions
|
# Same for let expressions
|
||||||
testReplResponseNoRegex '
|
testReplResponseNoRegex '
|
||||||
:p let x = { y = { a = 1; }; inherit x; }; in x
|
:p let x = { y = { a = 1; }; inherit x; }; in x
|
||||||
' '{ x = «repeated»; y = { a = 1; }; }'
|
' \
|
||||||
|
'{
|
||||||
|
x = «repeated»;
|
||||||
|
y = { a = 1 };
|
||||||
|
}
|
||||||
|
'
|
||||||
|
|
|
@ -720,7 +720,7 @@ TEST_F(ValuePrintingTests, ansiColorsAttrsElided)
|
||||||
vAttrs.mkAttrs(builder.finish());
|
vAttrs.mkAttrs(builder.finish());
|
||||||
|
|
||||||
test(vAttrs,
|
test(vAttrs,
|
||||||
"{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT "«1 attribute elided»" ANSI_NORMAL "}",
|
"{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT "«1 attribute elided»" ANSI_NORMAL " }",
|
||||||
PrintOptions {
|
PrintOptions {
|
||||||
.ansiColors = true,
|
.ansiColors = true,
|
||||||
.maxAttrs = 1
|
.maxAttrs = 1
|
||||||
|
@ -733,7 +733,7 @@ TEST_F(ValuePrintingTests, ansiColorsAttrsElided)
|
||||||
vAttrs.mkAttrs(builder.finish());
|
vAttrs.mkAttrs(builder.finish());
|
||||||
|
|
||||||
test(vAttrs,
|
test(vAttrs,
|
||||||
"{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT "«2 attributes elided»" ANSI_NORMAL "}",
|
"{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT "«2 attributes elided»" ANSI_NORMAL " }",
|
||||||
PrintOptions {
|
PrintOptions {
|
||||||
.ansiColors = true,
|
.ansiColors = true,
|
||||||
.maxAttrs = 1
|
.maxAttrs = 1
|
||||||
|
@ -757,7 +757,7 @@ TEST_F(ValuePrintingTests, ansiColorsListElided)
|
||||||
vList.bigList.size = 2;
|
vList.bigList.size = 2;
|
||||||
|
|
||||||
test(vList,
|
test(vList,
|
||||||
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«1 item elided»" ANSI_NORMAL "]",
|
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«1 item elided»" ANSI_NORMAL " ]",
|
||||||
PrintOptions {
|
PrintOptions {
|
||||||
.ansiColors = true,
|
.ansiColors = true,
|
||||||
.maxListItems = 1
|
.maxListItems = 1
|
||||||
|
@ -770,7 +770,7 @@ TEST_F(ValuePrintingTests, ansiColorsListElided)
|
||||||
vList.bigList.size = 3;
|
vList.bigList.size = 3;
|
||||||
|
|
||||||
test(vList,
|
test(vList,
|
||||||
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«2 items elided»" ANSI_NORMAL "]",
|
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«2 items elided»" ANSI_NORMAL " ]",
|
||||||
PrintOptions {
|
PrintOptions {
|
||||||
.ansiColors = true,
|
.ansiColors = true,
|
||||||
.maxListItems = 1
|
.maxListItems = 1
|
||||||
|
|
Loading…
Reference in a new issue