6
$\begingroup$

Suppose we have several lines of code in a cell:

a
b;
Module[{}, c
 d]
e

How can we wrap them in a CompoundExpression i.e. make the cell into something like

a;
b;
Module[{}, c
 d];
e

programmatically?

$\endgroup$
5
$\begingroup$

There are basically two ways to approach this. One would be to read the underlying expression in held form, apply the CompoundExpression to it and write it back. The problem with this approach is that it wouldn't preserve the individual formatting concerning e.g. linebreaks. For a decent "look" of the generated code you would need code which automatically does some (standardized) "pretty printing", like e.g. Leonids CodeFormatter.

The other approach would be to only work with the box representation of your input cells. That would mean to programmatically append a ";" to all subexpressions (lines) which would not already have one. The following will do this for a single cell expression (it should work for your example but is probably not fool proof, but should be easily extendable for cases which don't work):

With[{newlinepatt = ("\n" | FromCharacterCode[{62371}])},
 applyCompoundExpressionToCell[cell_Cell] :=
  cell /. BoxData[cmds : {___}] :> BoxData[
    Replace[cmds, {
       n : (newlinepatt) :> n,
       c_String :> (RowBox[{c, ";"}]),
       RowBox[{h___, n : newlinepatt}] :> RowBox[{h, ";", n}],
       r : RowBox[{h___, last : Except[";"]}] :> RowBox[{r, ";"}]
      }, {1}
    ]
 ]
]

A side note about FromCharacterCode[{62371}]: that will generate an \[IndentingNewLine] which is otherwise auto-converted in the frontend and kind of hard to be recognized...

To apply this function to an existing cell, you would need to replace the original cell expression with the one that applyComoundExpressionToCell returns. I would strongly recommend not to edit the original notebook in place but rather create a copy with the new code. You can then check that the new version looks and works alright and if happy save it and eventually replace the original one. The following snippet will read a notebook expression from a source notebook object, replace all input cells (which probably are the only ones where the desired operation makes sense) and write the result to a new notebook:

NotebookPut[
  NotebookGet[srcnb] /. 
   cell : Cell[_BoxData, "Input", ___] :> 
   applyCompoundExpressionToCell[cell]
]

Here is a palette with a button which will convert the selected cell and write the converted version below it and another one which will create a converted copy of the selected notebook. You could adapt the code of the former so it will replace the existing cell, but again, I would not recommend to do that unless you are very confident that nothing will break...

CreatePalette[{
  Button[
   "Convert Cell",
   SelectionMove[InputNotebook[], All, Cell];
   Function[
    SelectionMove[InputNotebook[], After, Cell];
    NotebookWrite[InputNotebook[], #]
   ][
   applyCompoundExpressionToCell[NotebookRead[InputNotebook[]]]
   ]
  ],
  Button[
   "Convert Notebook",
   NotebookPut[
    NotebookGet[InputNotebook[]] /. 
     cell : Cell[_BoxData, "Input", ___] :> 
    applyCompoundExpressionToCell[cell]
   ]
  ]
 },
 WindowTitle -> "Semicolons"
]
$\endgroup$
  • $\begingroup$ If you have additional empty line after e.g. a it will fail. $\endgroup$ – Kuba Nov 21 '15 at 10:40
  • $\begingroup$ I was guessing that there are many cases where it will fail, this one was -- I think -- easy to repair. As I won't have time to improve the code to handle all corner cases I made the answer a comminity wiki, so everyone finds cases where the above does not work and has time to fix the code, please feel free to change the code... $\endgroup$ – Albert Retey Nov 21 '15 at 11:04
  • $\begingroup$ Oh, I don't think you should've made it CW, it is already educational for readers. But yep, there are many edge cases. $\endgroup$ – Kuba Nov 21 '15 at 11:06
  • 1
    $\begingroup$ The edge cases are the drawback of the Boxes vs. held FullForm approach I guess. Do you think that making it CW has other drawbacks than me not earning reps? Actively preventing the latter helps me to avoid to spend to much time on the site :-) $\endgroup$ – Albert Retey Nov 21 '15 at 11:12
  • $\begingroup$ Then don't switch it back! Good idea :) $\endgroup$ – Kuba Nov 21 '15 at 11:14
2
$\begingroup$

Slightly different approach, based on "fixing" boxes too:

DynamicModule[{},
  Button["Close lines", fix @ InputNotebook[]]
  ,
  Initialization :> (
    fix[nb_NotebookObject] := fix /@ SelectedCells[nb];

    fix[cell_CellObject] := NotebookWrite[cell, fix@NotebookRead@cell];

    fix[cell_Cell] := MapAt[fix, cell, {1, 1}];

    fix[l_List] := fix /@ l;

    fix[x_String] := If[
      StringMatchQ[x, ";" | "\n" | "\[IndentingNewLine]"], 
      x, 
      RowBox[{x, ";"}]
    ];

    fix[box_] := With[{elem = Level[box, {-1}]},
      Which[
        Last@elem === ";", 
        box
        ,
        MatchQ[Last@elem, "\n" | "\[IndentingNewLine]"], 
        ReplacePart[box, {1, -2} -> RowBox[{box[[1, -2]], ";"}]]
        ,
        True, 
        RowBox[{box, ";"}]
     ]
   ];
  )
] // CreatePalette[#, CellContext -> Notebook] &

Before:

enter image description here

After:

enter image description here

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.