(The full code is at the end of this post, put together into a shortcut ctrl-|
that copies the cell containing the selection, wraps the selection in brackets, and places the cursor before the brackets so as to allow the user to type some function to apply to that subexpression.)
My solution is by no means pretty nor bulletproof. Most checks are done by replacing the selection by a blank pattern and comparing the new cell contents to the old one, both in terms of the boxes and as expressions. It also checks things like whether the selection is a (standalone) expression.
The first test is to check that the selection does not span more than a cell (and is non-empty). Any selection within a cell is accepted. For error recovery it will be useful to save the original contents of the selectedcells
(which are a list of CellObjects
) as a (list of) Cell
expression(s) cells
.
nb = EvaluationNotebook[];
selectedcells = SelectedCells[nb];
cells = NotebookRead[selectedcells];
If[Length[selectedcells] == 1,
The next step is to replace the selection by a recognizable marker, obtained using Unique[]
. But first save the current selection
(as Box
es) as we will alter it. We also check whether the selection starts with -
or +
, either at the "bottom level" of a RowBox
or in a nested one (improvements welcome): this extrasign
is used to add +
when e.g., -b+c
is selected in a-b+c
.
selection = NotebookRead[nb];
extrasign =
MatchQ[selection,
RowBox[{"-" | "+" | RowBox[{("-" | "+"), ___}], ___}]];
unique = Unique[];
NotebookApply[nb,
If[extrasign,
RowBox[{"+", placeholderBox["\[SelectionPlaceholder]"]}],
placeholderBox["\[SelectionPlaceholder]"]]];
The placeholderBox
auxiliary builds essentially $123[<selection>]
(as boxes) where $123
is the unique
symbol produced above. Thus, NotebookApply
wraps the selection as an argument of $123
, with an additional +
if needed.
Also check now that the selection
is a valid expression (this auxiliary tests whether the result of MakeExpression
is an ErrorBox
). Presumably I could do that test earlier.
If[validExpression[selection],
Set up the various box data: boxdata
corresponds to the original cell; newboxdata
corresponds to the cell after the action of NotebookApply
; boxdatapattern
is boxdata
with the selection replaced by a BlankNullSequence
. We will check that boxdata
matches boxdatapattern
to catch cases such as "b+b" being selected in "b+b^2".
boxdata = cells[[1, 1]];
newboxdata = NotebookRead[selectedcells][[1, 1]];
boxdatapattern = If[extrasign,
ReplaceAll[newboxdata,
f_Symbol[a___, "+", placeholderBox[___], b___] :>
f[a, ___, b]],
ReplaceAll[newboxdata, placeholderBox[___] :> ___]];
The same for expressions: expr
and newexpr
are the expressions corresponding to the initial cell contents and the altered ones.
expr = MakeExpression[boxdata, StandardForm];
newexpr = MakeExpression[newboxdata, StandardForm];
Check that the initial boxdata
matches the pattern obtained by replacing the selection by ___
, that we started with an actual expression (otherwise the project is doomed) and that we got an expression out, which contains unique
: this catches cases where $123
got glued with a symbol before it, giving a symbol a$123
.
If[MatchQ[boxdata, boxdatapattern] &&
(Head[expr] =!= ErrorBox) &&
(Head[newexpr] =!= ErrorBox) &&
Not[FreeQ[newexpr, unique]] &&
Finally, the topic of another question I asked, about MatchQ and Orderless functions. This last test essentially checks that the original expression matches the pattern obtained by replacing the selection with ___
. The pattern matcher does not respect Hold
for patterns containing Orderless
functions (such as Plus
, Times
), so I hide these Orderless
functions inside Verbatim
.
MatchQ[expr,
ReplaceAll[newexpr,
{unique[___] :> ___,
(h_Symbol /; MemberQ[Attributes[h], Orderless]) :>
Verbatim[h]}]]
,
This is the end of checking that the selection is a nice subexpression. The rest of the code manipulates cells to write a copy of the original cell back in the notebook, and to replace unique
by a function func
(by default FullSimplify
) so that the user can just do shift-Enter
to make this common transformation of the input, or can edit FullSimplify
to something else, in place. There is also some error recovery to avoid losing data.
Please do not consider the version number as any guarantee of any kind.
(*Version 2*)
(*The "FrontEndExecute" business is from
"https://mathematica.stackexchange.com/questions/6224"/*)
(*The "CreateDialog" and "FrontEnd`BoxReferenceFind" business is
based on
http://community.wolfram.com/groups/-/m/t/489487 by Kuba Podkalicki*)
Module[
{unique, nb, selectedcells, cells, selection, extrasign,
boxdata, newboxdata, boxdatapattern, placeholder,
pos, func, expr, newexpr,
doEW, placeholderBox, validExpression,
errorMultiCell, errorNotExpr, errorNotSubExpr, errorUnique},
func = FullSimplify;
System`FrontEndExecute[
FrontEnd`ResetMenusPacket[{Automatic}]];
System`FrontEndExecute[
FrontEnd`AddMenuCommands[
"SubsessionEvaluateCells",
{System`MenuItem["Evaluate &With", FrontEnd`KernelExecute[doEW],
System`MenuKey["|", System`Modifiers -> {"Control"}],
System`MenuEvaluator -> Automatic]}]];
placeholderBox[sel_] := RowBox[{ToString[unique], "[", sel, "]"}];
doEW := (
unique = Unique[];
nb = EvaluationNotebook[];(*todo:
several parts of this code need that selection stays static*)
selectedcells = SelectedCells[nb];
cells = NotebookRead[selectedcells];
If[Length[selectedcells] == 1,
selection = NotebookRead[nb];
extrasign =
MatchQ[selection,
RowBox[{"-" | "+" | RowBox[{("-" | "+"), ___}], ___}]];
NotebookApply[nb,
If[extrasign,
RowBox[{"+", placeholderBox["\[SelectionPlaceholder]"]}],
placeholderBox["\[SelectionPlaceholder]"]]];
If[validExpression[selection],
boxdata = cells[[1, 1]];
newboxdata = NotebookRead[selectedcells][[1, 1]];
boxdatapattern = If[extrasign,
ReplaceAll[newboxdata,
f_Symbol[a___, "+", placeholderBox[___], b___] :>
f[a, ___, b]],
ReplaceAll[newboxdata, placeholderBox[___] :> ___]];
expr = MakeExpression[boxdata, StandardForm];(*todo*)
newexpr = MakeExpression[newboxdata, StandardForm];
If[MatchQ[boxdata, boxdatapattern] &&
(Head[expr] =!= ErrorBox) &&
(Head[newexpr] =!= ErrorBox) &&
Not[FreeQ[newexpr, unique]] &&
MatchQ[expr, ReplaceAll[newexpr, {unique[___] :> ___,
(h_Symbol /; MemberQ[Attributes[h], Orderless]) :>
Verbatim[h]}]],
pos = Most[First[Position[newexpr, unique]]];
SelectionMove[selectedcells[[1]], Before, Cell,
AutoScroll -> False];
NotebookWrite[nb, cells, After, AutoScroll -> False];
SelectionMove[selectedcells[[1]], After, CellContents,
AutoScroll -> False];
NotebookWrite[nb,
"//EvaluateAt[" <> StringTake[ToString[Rest[pos]], {2, -2}] <>
"]", AutoScroll -> False];
NotebookFind[nb, ToString[unique], Previous,
AutoScroll -> False];
NotebookWrite[nb, func, All]
,
NotebookWrite[selectedcells[[1]], cells[[1]]];
errorNotSubExpr],
NotebookWrite[selectedcells[[1]], cells[[1]]];
errorNotExpr],
errorMultiCell];
);
errorMultiCell :=
MessageDialog[
"The selection is bigger than one cell. Please choose a smaller \
selection."];
errorNotExpr :=
MessageDialog[
StringForm[
"The selection `1` is not an expression, please choose a \
different selection.", DisplayForm[selection]]];
errorNotSubExpr :=
MessageDialog[
StringForm[
"The selection `1` is not a subexpression of the full expression, \
please choose a different selection.", DisplayForm[selection]]];
errorUnique :=
MessageDialog[
StringForm[
"The expression contains the placeholder `1`, which would \
interfere with the code of EvaluateWith", DisplayForm[unique]]];
validExpression[
boxes_] := (Head[MakeExpression[boxes, StandardForm]] =!= ErrorBox);
EvaluateAt[pos__] := Function[arg,
expr = HoldComplete[arg];
func = expr[[1, pos, 0]];
ReleaseHold@ReplacePart[expr, List[pos] -> expr[[1, pos]]],
{HoldAllComplete}
];
]
MakeExpression's
ref page says thatErrorBox
is generated in case of "invalid or incomplete syntax". Do you need something more than that? $\endgroup$Module[{x}, x = (a + b)*c; x + 1 ]
. Moreover,SelectionMove[nb,All,Expression]
only expands selection to the nearest wrapping element so when you do that for my case where onlya+b
are selected, the result will be(a+b)
selected, still noc
there. $\endgroup$FullSimplify
to it and plugging the result back should give an equivalent result. Perhaps a more formal definition would be that theFullForm
of the selection should be a subtree of theFullForm
of the cell? $\endgroup$