I'd like to write a function that outputs multiple cells, as in Multiple output cells, but not when the function is called with a semicolon at the end. Any idea how?
4 Answers
New Implemantation:
f[x_] := Dynamic[
NotebookWrite[EvaluationCell[], Cell[BoxData[ToBoxes[#]], "Echo"] & /@ Range[x]];,
None, TrackedSymbols :> {}, Evaluator -> None]
Original Answer:
As Dynamic
objects are only evaluated when they are visible, one can use them to print cells only when there is no ;
at the end of the input. The following one removes the Out
cell containing a DynamicWrapper
as the Dynamic
object itself, after the cells are printed.
ClearAll@f
f[x_] := Module[{evalCellObject},
DynamicWrapper["",
evalCellObject = EvaluationCell[];
NotebookWrite[EvaluationNotebook[], Cell[BoxData[ToBoxes[#]], "Echo"] & /@ Range[x]];
NotebookDelete[evalCellObject]]]
-
$\begingroup$ Awesome, thanks! Word to the wise: don't forget the final
NotebookDelete[evalCellObject]
statement! $\endgroup$– Chris KMay 31, 2016 at 13:20 -
$\begingroup$ One quirk (not a big deal for me): if you select a bunch of cells that make up a notebook section, then run them with shift-return, this doesn't produce output. $\endgroup$– Chris KMay 31, 2016 at 14:45
-
$\begingroup$ Actually I'm having bigger issues now, with input cells disappearing when opening closed sections that use this technique. Probably not a practical solution after all. $\endgroup$– Chris KJun 1, 2016 at 2:36
-
$\begingroup$ @ChrisK The first two are very valid points. The issue stated in your third comment I could only reproduce by removing the
NotebookDelete
part (, which messes up a lot of different things). I implemented a new version of the same approach that is more straight forward and should also be more robust. Please give it a try and report back if there are any remaining issues. $\endgroup$– Karsten7Jun 1, 2016 at 11:42 -
$\begingroup$ I'll add some explanatory text for the new implementation if there are no issues left. $\endgroup$– Karsten7Jun 1, 2016 at 11:47
The code below is currently broken but I'll leave it for reference. I'll attempt a full rewrite somewhat later. Thanks to gwr for testing my code and pointing out problems.
The first possibility that comes to mind is the use of $PreRead
and $PrePrint
to set a global variable, then use the value of this variable within your function to control Cell generation. A basic example building on andre's code:
$print = True;
$PrePrint = ($print = True; #) &;
$PreRead = Replace[#, row : RowBox[{__, ";"}] :> ($print = False; row)] &;
f[x_] :=
(If[$print, CellPrint @ ExpressionCell[#, "Output"] &] /@ {x + 1, x + 2};)
Now:
f[0] (* "prints" two cells *)
1 2
f[0]; (* prints nothings *)
-
1$\begingroup$ Seems like you need to drop the
;
after$PrePrint = ...
for it to work. Also while it prints two cells they are notOut[n]
andOut[n+1]
? $\endgroup$– gwrMay 27, 2016 at 14:45 -
$\begingroup$ @gwr The lack of
Out[]
tags is not part of this question; I am merely extending existing behavior as the OP requested. However thank you for catching that bug; it's actually because I forgot to consider the order of the definitions. $\endgroup$ May 27, 2016 at 14:50 -
1$\begingroup$ Still nitpicking:
f[0]
will work, but then afterf[0];
anotherf[0]
will not work. It only works once? $\endgroup$– gwrMay 27, 2016 at 14:55 -
$\begingroup$ ... thus you need a "turn on" statement also:
$PreRead = Replace[ #, { row:RowBox[{__,";"}]:> ($print = False;row), row : RowBox[{__}] :> ($print = True; row)}]&;
. $\endgroup$– gwrMay 27, 2016 at 15:02 -
1$\begingroup$ @gwr Not nit-picking at all! Thank you for debugging my disgracefully sloppy code. I thought I could get a quick but valid answer in here and I overestimated my ability to write code without testing it. $\endgroup$ May 27, 2016 at 23:10
This one checks if the input ended with ;
and prints Echo
cells if it didn't.
f[x_] := If[
Cases[NotebookRead[EvaluationCell[]],
BoxData[RowBox[{___, last_}]] :> last] == {";"},
Null,
CellPrint[ExpressionCell[#, "Echo"]] & /@ Range[x];
]
For example:
f[5]
f[5];
This approach can be extended to also cover CompoundExpression
s containing f
by checking if the function is followed by ;
, instead of checking if the input ended with ;
.
f[x_] := If[Cases[NotebookRead[EvaluationCell[]],
BoxData[RowBox[{___, RowBox[{"f", ___}], end_, ___}]] :> end] == {";"},
Null,
CellPrint[ExpressionCell[#, "Echo"]] & /@ Range[x];]
-
1
-
$\begingroup$ @jkuczm That input doesn't end with a
;
and therefore my first implementation is printing the cells. The OP isn't unambiguous about the expected behavior in this case. Thanks for pointing it out. I added an implementation of the same approach that doesn't print cells if the function is followed by a;
, even as a part of aCompoundExpression
. $\endgroup$– Karsten7May 28, 2016 at 16:57
You can use an intermediate expression that can be returned by your function, not printed as side effect. Then you can use $Post
to post-process this returned expression, so that it'll result in multiple printed cells.
ClearAll[multipleCellsOutput, printMultipleCellsOutput]
printMultipleCellsOutput =
# /. multipleCellsOutput[cells_List] :> Scan[CellPrint, cells] &;
$Post = printMultipleCellsOutput;
Example of function "evaluating to multiple cells":
ClearAll[f]
f[x_] := multipleCellsOutput[ExpressionCell[x + #, "Output"] & /@ {1, 2}]
its usage:
f[x]
1 + x 2 + x
f[x]; (* Prints nothing *)
f[y]; z
z
x; f[y]
1 + y 2 + y
x; f[y]; f[z]
1 + z 2 + z
f[x] + 1; (* Prints nothing *)
f[y] + 1
1 + y 2 + y 1 + Null