Skip to content

Commit

Permalink
Pretty-print values in the REPL
Browse files Browse the repository at this point in the history
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
  ];
}
```
  • Loading branch information
9999years committed Feb 4, 2024
1 parent 49cf090 commit f5a7bb6
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 23 deletions.
3 changes: 2 additions & 1 deletion src/libcmd/repl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ struct NixRepl
.ansiColors = true,
.force = true,
.derivationPaths = true,
.maxDepth = maxDepth
.maxDepth = maxDepth,
.prettyIndent = 2
});
}
};
Expand Down
22 changes: 22 additions & 0 deletions src/libexpr/print-options.hh
Original file line number Diff line number Diff line change
Expand Up @@ -17,42 +17,64 @@ struct PrintOptions
* If true, output ANSI color sequences.
*/
bool ansiColors = false;

/**
* If true, force values.
*/
bool force = false;

/**
* If true and `force` is set, print derivations as
* `«derivation /nix/store/...»` instead of as attribute sets.
*/
bool derivationPaths = false;

/**
* If true, track which values have been printed and skip them on
* subsequent encounters. Useful for self-referential values.
*/
bool trackRepeated = true;

/**
* Maximum depth to evaluate to.
*/
size_t maxDepth = std::numeric_limits<size_t>::max();

/**
* Maximum number of attributes in attribute sets to print.
*
* Note that this is a limit for the entire print invocation, not for each
* attribute set encountered.
*/
size_t maxAttrs = std::numeric_limits<size_t>::max();

/**
* Maximum number of list items to print.
*
* Note that this is a limit for the entire print invocation, not for each
* list encountered.
*/
size_t maxListItems = std::numeric_limits<size_t>::max();

/**
* Maximum string length to print.
*/
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;
}
};

/**
Expand Down
114 changes: 103 additions & 11 deletions src/libexpr/print.cc
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ struct ImportantFirstAttrNameCmp
};

typedef std::set<Value *> ValuesSeen;
typedef std::vector<std::pair<std::string, Value *>> AttrVec;

class Printer
{
Expand All @@ -163,6 +164,21 @@ class Printer
std::optional<ValuesSeen> seen;
size_t attrsPrinted = 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()
{
Expand Down Expand Up @@ -260,6 +276,28 @@ class Printer
}
}

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)
{
if (seen && !seen->insert(&v).second) {
Expand All @@ -270,9 +308,10 @@ class Printer
if (options.force && options.derivationPaths && state.isDerivation(v)) {
printDerivation(v);
} else if (depth < options.maxDepth) {
output << "{ ";
increaseIndent();
output << "{";

std::vector<std::pair<std::string, Value *>> sorted;
AttrVec sorted;
for (auto & i : *v.attrs)
sorted.emplace_back(std::pair(state.symbols[i.name], i.value));

Expand All @@ -281,7 +320,15 @@ class Printer
else
std::sort(sorted.begin(), sorted.end(), ImportantFirstAttrNameCmp());

auto prettyPrint = shouldPrettyPrintAttrs(sorted);

for (auto & i : sorted) {
if (prettyPrint) {
output << "\n" << indent;
} else {
output << " ";
}

if (attrsPrinted >= options.maxAttrs) {
printElided(sorted.size() - attrsPrinted, "attribute", "attributes");
break;
Expand All @@ -290,13 +337,42 @@ class Printer
printAttributeName(output, i.first);
output << " = ";
print(*i.second, depth + 1);
output << "; ";
output << ";";
attrsPrinted++;
}

decreaseIndent();
if (prettyPrint) {
output << "\n" << indent;
} else {
output << " ";
}
output << "}";
} else
} else {
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)
Expand All @@ -306,11 +382,20 @@ class Printer
return;
}

output << "[ ";
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) {
printElided(v.listSize() - listItemsPrinted, "item", "items");
printElided(listItems.size() - listItemsPrinted, "item", "items");
break;
}

Expand All @@ -319,13 +404,19 @@ class Printer
} else {
printNullptr();
}
output << " ";
listItemsPrinted++;
}

decreaseIndent();
if (prettyPrint) {
output << "\n" << indent;
} else {
output << " ";
}
output << "]";
} else {
output << "[ ... ]";
}
else
output << "... ";
output << "]";
}

void printFunction(Value & v)
Expand Down Expand Up @@ -488,6 +579,7 @@ class Printer
{
attrsPrinted = 0;
listItemsPrinted = 0;
indent.clear();

if (options.trackRepeated) {
seen.emplace();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ error:
| ^
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» }
72 changes: 66 additions & 6 deletions tests/functional/repl.sh
Original file line number Diff line number Diff line change
Expand Up @@ -146,29 +146,89 @@ echo "$replResult" | grepQuiet -s afterChange
# Normal output should print attributes in lexicographical order non-recursively
testReplResponseNoRegex '
{ 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
testReplResponseNoRegex '
[ 42 1 "thingy" ({ a = 1; }) ([ 1 2 3 ]) ]
' '[ 42 1 "thingy" { ... } [ ... ] ]'
' \
'[
42
1
"thingy"
{ ... }
[ ... ]
]
'

# Same for let expressions
testReplResponseNoRegex '
let x = { y = { a = 1; }; inherit x; }; in x
' '{ x = { ... }; y = { ... }; }'
' \
'{
x = { ... };
y = { ... };
}
'

# The :p command should recursively print sets, but prevent infinite recursion
testReplResponseNoRegex '
: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
testReplResponseNoRegex '
: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
testReplResponseNoRegex '
:p let x = { y = { a = 1; }; inherit x; }; in x
' '{ x = { x = «repeated»; y = { a = 1; }; }; y = «repeated»; }'
' \
'{
x = {
x = «repeated»;
y = { a = 1; };
};
y = «repeated»;
}
'
Loading

0 comments on commit f5a7bb6

Please sign in to comment.