This is a question that's been bugging me for quite some time, now.
When we call the FE in a batched mode we can only get one result out. For instance, we could try to get two stylesheet files. First unbatched:
FrontEndExecute /@
{
FrontEnd`FindFileOnPath["Default.nb", "StyleSheetPath"],
FrontEnd`FindFileOnPath["ReverseColor.nb", "StyleSheetPath"]
}
{
"/Applications/Mathematica.app/Contents/SystemFiles/FrontEnd/StyleSheets/Default.nb",
"/Applications/Mathematica.app/Contents/SystemFiles/FrontEnd/StyleSheets/ReverseColor.nb"
}
Then we can try batching this:
FrontEndExecute@
{
FrontEnd`FindFileOnPath["Default.nb", "StyleSheetPath"],
FrontEnd`FindFileOnPath["ReverseColor.nb", "StyleSheetPath"]
}
$Failed
And... it fails. So how can I get both results out in a batched call? Can I?
What have I tried
FrontEndExecute
is a just an alias for MathLink`CallFrontEnd
. But it passes NoResult
as the second argument.
I tried to figure out if there were other possible options for that:
hasMCall =
Quiet@
ToExpression[
DeleteDuplicates@Join[Names["*`*"], Names["*`*`*"]],
StandardForm,
Function[Null,
If[
MemberQ[DownValues[#],
MathLink`CallFrontEnd | MathLink`CallFrontEndHeld,
∞,
Heads -> True
],
#,
Nothing
],
HoldFirst
]
];
Cases[DownValues /@ hasMCall,
c : (MathLink`CallFrontEnd | MathLink`CallFrontEndHeld)[__, b_] :> b,
∞
] // DeleteDuplicates
{FE`NoResult, System`FEDump`NoResult}
But I couldn't find any in the DownValues
. Beyond this, I've tried a bunch of different second arguments having to do with "Result"
or whatever. They've all failed.
Batched FrontEndExecute
If you're not used to batching calls to the FE, we do it because often it's more efficient than passing commands one by one. Basically we just push all the calls in at once. As a first example, let's consider making a bunch of useless cells. We could generally write this like so:
makeBunchOfCellsUnbatched[] :=
(
FrontEndExecute@
FrontEnd`NotebookSuspendScreenUpdates[EvaluationNotebook[]];
SelectionMove[EvaluationNotebook[], After, Notebook,
AutoScroll -> False
];
Do[
NotebookWrite[EvaluationNotebook[],
Cell["", "MyStyle"],
AutoScroll -> False
],
200
];
SelectionMove[EvaluationCell[], After, Cell];
FrontEndExecute@
FrontEnd`NotebookResumeScreenUpdates[EvaluationNotebook[]]
)
But we could write this with a batched call like so:
makeBunchOfCells[] :=
FrontEndExecute@
Flatten@{
FrontEnd`NotebookSuspendScreenUpdates[
FrontEnd`EvaluationNotebook[]],
FrontEnd`SelectionMove[FrontEnd`EvaluationNotebook[], After,
Notebook,
AutoScroll -> False
],
Table[
FrontEnd`NotebookWrite[FrontEnd`EvaluationNotebook[],
Cell["", "MyStyle"],
AutoScroll -> False
],
200
],
FrontEnd`SelectionMove[FrontEnd`EvaluationCell[], After, Cell],
FrontEnd`NotebookResumeScreenUpdates[FrontEnd`EvaluationNotebook[]]
}
And when we compare the runtimes:
makeBunchOfCellsUnbatched[] // AbsoluteTiming // First
11.5724
makeBunchOfCells[] // AbsoluteTiming // First
4.05879
We see this is much more efficient.
We can also consider a batched SetOptions
:
setCellOptions[
moveAfter : True | False : True,
ops : OptionsPattern[]
] :=
Internal`WithLocalSettings[
Null,
FrontEndExecute@
Flatten@{
FrontEnd`NotebookSuspendScreenUpdates[
FrontEnd`EvaluationNotebook[]],
Map[
FrontEnd`SetOptions[#,
FilterRules[{ops},
Except[Alternatives @@ Keys@Options@Cells]]
] &,
Cells[FrontEnd`EvaluationNotebook[],
Sequence @@ FilterRules[{ops}, Options@Cells]
]
],
If[moveAfter,
FrontEnd`SelectionMove[FrontEnd`EvaluationCell[], After,
Cell],
Nothing
]
},
FrontEndExecute@
FrontEnd`NotebookResumeScreenUpdates[FrontEnd`EvaluationNotebook[]]
];
setCellOptionsUnbatched[
moveAfter : True | False : True,
ops : OptionsPattern[]
] :=
Internal`WithLocalSettings[
Null,
FrontEndExecute@
FrontEnd`NotebookSuspendScreenUpdates[
FrontEnd`EvaluationNotebook[]];
FrontEndExecute /@
Flatten@{
Map[
FrontEnd`SetOptions[#,
FilterRules[{ops},
Except[Alternatives @@ Keys@Options@Cells]]] &,
Cells[FrontEnd`EvaluationNotebook[],
Sequence @@ FilterRules[{ops}, Options@Cells]
]
],
If[moveAfter,
FrontEnd`SelectionMove[FrontEnd`EvaluationCell[], After,
Cell],
Nothing
]
},
FrontEndExecute@
FrontEnd`NotebookResumeScreenUpdates[FrontEnd`EvaluationNotebook[]]
];
After making a bunch of cells we get results like this:
setCellOptionsUnbatched[
Background -> RandomColor[],
CellStyle -> "MyStyle"
] // RepeatedTiming // First
1.834
setCellOptions[
Background -> RandomColor[],
CellStyle -> "MyStyle"
] // RepeatedTiming // First
0.3
And so if you're making a lot of use of the FE it's generally best to Sow
your calls or something and then push them all in at once. It is for this reason that I want to be able to get a batched result, too.
SetAttributes[FrontEndExecute, Listable]
thenFrontEndExecute@ { ..., ... }
works. $\endgroup$Map
it. $\endgroup$