39
$\begingroup$

It would be convenient if there were a style sheet that automatically indented input cells, and automatically commented output cells, so that one could generate a stack exchange response in WL, and just copy the output. Is there such a style sheet?

$\endgroup$
  • $\begingroup$ Here is the description of a project with a similar goal. A download link is available in comments further down (but it will stop working when Dropbox disables public links in a couple of months.) $\endgroup$ – C. E. Dec 31 '16 at 20:38
  • $\begingroup$ Look for the SENotebook package in the comments here: meta.mathematica.stackexchange.com/questions/1286/… $\endgroup$ – Michael E2 Jan 1 '17 at 1:02
  • 3
    $\begingroup$ @MichaelE2 and C.E: Oh wow. I myself forgot about my package :D Also, it's the 4 year anniversary of that one — "0.9 beta released 31 Dec 2012". In any case, Carl (and others): That notebook is a Mathematica -> SE prototype whereas yours is the other way round, but the stylesheet should probably still be useful. It's available publicly on Dropbox and Bitbucket, but I'll tidy that up and host it on Github, in case anyone wants to contribute and make it better :) $\endgroup$ – rm -rf Jan 1 '17 at 3:05
  • $\begingroup$ Since R. M.'s package got mentioned, I'll also link to Kuba's implementation; somebody might get inspired and come up with a total solution that eases back-and-forth between SE and Mathematica... $\endgroup$ – J. M. will be back soon Jan 6 '17 at 6:52
  • $\begingroup$ @J.M. following up on that I think I could build out a system that uses a ServiceObject to upload to SE on top of what I've alread built and go the other way too, even without much trouble. But I think such things largely exist. Halirutan has his SEUploader which, unless I'm wrong, pushes a notebook to SE as an answer. And Carl has a largely functional downloader. If Halirutan included Carl's downloader in his uploader palette I think that would cover all the cases except chat. $\endgroup$ – b3m2a1 Aug 1 '17 at 5:33
29
$\begingroup$

I've come up with a stylesheet to do this, given below. Some of the issues that I needed to address were:

  1. Prevent cell labels from showing up.
  2. Indent both input and output cells so that they are code.
  3. Automatically comment the output.
  4. Produce output that works correctly with Copy. That is, no need to use Copy As | Plain Text.
  5. Allow one to copy multiple Code/Output/Text cells at once.
  6. Support InputForm and FullForm.

Since the input typically uses white space to control appearance, I modified the Code style instead of the Input style.

  1. To prevent cell labels from showing up, I needed to set ShowCellLabel->False at the notebook level. However, I still want to see the labels in a notebook. I did this by setting ShowCellLabel->True for Code, Input and Output cell styes.

  2. To indent the input cells, I added a CellEpilog to the Code style, that looks for tabs, and inserts tabs if none are found. One could modify my code to look for spaces instead. To indent the output cells, I added a CellEvaluationFunction to the Code style. The CellEvaluationFunction also turned the output into a comment. In both cases (CellEpilog and CellEvaluationFunction), one needs to use FullForm in some cases, e.g., Pattern[a, Blank[]] instead of a_:

  3. Using RawBoxes @ ToString[.., InputForm] produces an Output cell that yields "Copy As | Plain Text" output when using the Copy command. This means one can highlight multiple cells, and then use the Copy (Ctrl-C) command, instead of using the right-click context menu to do Copy As.

  4. Using InputForm or FullForm wrappers gave me some problems with PageWidth, so using the wrappers with long lines will need some post-processing help.

  5. Use Alt-8 (Cmd-8 on a mac) to begin a Code cell. I gave Code cells a light blue background to indicate that they are cells intended to be copied to Stack Exchange.

Improvement suggestions are welcome.

The stylesheet is given below:

Notebook[
  {
  Cell[StyleData[StyleDefinitions->"Default.nb"]],
  Cell[StyleData["Notebook"],ShowCellLabel->False,MenuSortingValue->10000],
  Cell[StyleData["Code"],
    CellEpilog :> With[{cellObject=EvaluationCell[]},
      NotebookWrite[cellObject,
        With[{cell=NotebookRead[cellObject]},
          Replace[cell,
            {
            Cell[BoxData[RowBox[{"\t",BlankSequence[]}]],BlankSequence[]]->cell,
            Cell[BoxData[Pattern[a,Blank[]]],Pattern[b,BlankSequence[]]]:>Cell[BoxData[RowBox[{"\t",ReplaceAll[a,"\n"->Sequence["\n","\t"]]}]],b]
            }
          ]
        ]
      ]
    ],
    CellEvaluationFunction ->
      (
      Replace[ToExpression[#1],
        {
        Alternatives[Null,$Failed]->Null,
        Alternatives[Pattern[e,Blank[InputForm]],Pattern[e,Blank[FullForm]]]:>RawBoxes[StringJoin["\t(* ",ToString[e]," *)"]],
        Pattern[e,Blank[]]:>RawBoxes[StringReplace[StringJoin["\t(* ",ToString[e,InputForm,PageWidth->60]," *)"],"\\\n"->"\\\n\t"]]
        }
      ]&
      ),
    InitializationCell->False,
    ShowCellLabel->True,
    MenuSortingValue->10000,
    Background->RGBColor[0.477119,0.957519,1]
  ],
  Cell[StyleData["Input"],ShowCellLabel->True,MenuSortingValue->10000],
  Cell[StyleData["Output"],ShowCellLabel->True,MenuSortingValue->10000]
  },
  Saveable->False,WindowSize->{808,689},WindowMargins->{{Automatic,143},{40,Automatic}},
  ShowCellLabel->False,
  FrontEndVersion->"11.0 for Mac OS X x86 (32-bit, 64-bit Kernel) (July 28, 2016)",
  StyleDefinitions->"PrivateStylesheetFormatting.nb"
]

Here's another version that uses Blockquote to display output:

Notebook[
  {
  Cell[StyleData[StyleDefinitions->"Default.nb"]],
  Cell[StyleData["Notebook"],ShowCellLabel->False,MenuSortingValue->10000],
  Cell[StyleData["Code"],
    CellEpilog:>With[{cellObject=EvaluationCell[]},
      NotebookWrite[cellObject,
        With[{cell=NotebookRead[cellObject]},
          Replace[cell,
            {
            Cell[BoxData[RowBox[{"\t",BlankSequence[]}]],BlankSequence[]]->cell,
            Cell[BoxData[Pattern[a,Blank[]]],Pattern[b,BlankSequence[]]]:>Cell[BoxData[RowBox[{"\t",ReplaceAll[a,Rule["\n",Sequence["\n","\t"]]]}]],b]
            }
          ]
        ]
      ]
    ],
    CellEvaluationFunction->
      (
      Replace[ToExpression[#1],
        {
        Alternatives[Null,$Failed]->Null,
        Alternatives[Pattern[e,Blank[InputForm]],Pattern[e,Blank[FullForm]]]:>RawBoxes[StringJoin[">",ToString[e]]],
        Pattern[e,Blank[]]:>RawBoxes[StringJoin[">",ToString[e,InputForm]]]
        }
      ]&
      ),
    InitializationCell->False,
    ShowCellLabel->True,
    MenuSortingValue->10000,
    Background->RGBColor[0.477119,0.957519,1]
  ],
  Cell[StyleData["Input"],ShowCellLabel->True,MenuSortingValue->10000],
  Cell[StyleData["Output"],ShowCellLabel->True,MenuSortingValue->10000]
  },
  Saveable->False,WindowSize->{808,689},WindowMargins->{{Automatic,143},{40,Automatic}},
  ShowCellLabel->False,
  FrontEndVersion->"11.0 for Mac OS X x86 (32-bit, 64-bit Kernel) (July 28, 2016)",
  StyleDefinitions->"PrivateStylesheetFormatting.nb"
]
$\endgroup$
  • 9
    $\begingroup$ Very cool! Let me join others in saying it is a pleasure to have you on this site. $\endgroup$ – Mr.Wizard Dec 31 '16 at 19:24
  • 1
    $\begingroup$ Umm...@Mr.Wizard...Who are these "others"? You seem to be the only person to have welcomed Carl to the site. (Sorry Carl, I guess I need to add a New Year's resolution to not pick on you... Neat idea, by the way) $\endgroup$ – Daniel Lichtblau Dec 31 '16 at 20:12
  • 1
    $\begingroup$ @Daniel The Greek Chorus I carry in my head? :o) Leonid at least; I guess I just assumed there would be others. $\endgroup$ – Mr.Wizard Jan 1 '17 at 1:00
  • 1
    $\begingroup$ I entered an expression such as Sqrt[\[Alpha]], then copied it and pasted it into Stackexchange .. which repeated it as Sqrt[\[Alpha]] ... It would be cool if the stylesheet could automate the conversion of same to something like Sqrt[ $\alpha$] $\endgroup$ – wolfies Jan 1 '17 at 13:59
  • 4
    $\begingroup$ @Mr.Wizard I always thought Leonid and Carl were sworn enemies from the DC Comics universe. $\endgroup$ – Daniel Lichtblau Jan 1 '17 at 21:40
5
$\begingroup$

My initial versions mangled the downvalues of Out, used by %. Also, my initial versions defaulted to InputForm.

Edited to add support for compound/multiple expressions in an input, and to mark up messages.

Edited to fix bug when code cell is last cell of notebook

Edited to quiet messages during reformatting of cells

I've made some core changes to the style sheet.

Previously, I used calls like Cells[CellLabel->"Out[55]//FullForm="] to find all of the output cells associated with an input. This required that I determined the line numbers of a cell when it contained multiple lines. I also used NextCell[EvaluatedCell[]] in the CellProlog to find the next cell before evaluation so that I could figure out what messages occurred for a given input.

Now I use a more robust method. In the CellProlog, I give the evaluation cell the option CellTags->"StackExchange" and CellLabelAutoDelete->False. This combination means that all the cells generated from the input have the option CellTags->"StackExchange" and include the original CellLabel. Hence, it is much easier to find the associated cells using the CellTags option, and it is easy to restore the original CellLabel because the label hasn't been deleted. I also made a number of improvements to the formatting of output cells, to support forms like TeXForm, and to produce the normal plain text for output instead of OutputForm.

Finally, I mentioned earlier that style sheets needed to use FullForm in some places. However, if you paste the Notebook[]; NotebookPut@@%; expression I give for the style sheet, it turns out that the conversion from StandardForm to FullForm needed for style sheets happens automatically. This means that I can rewrite my notebook expression so that it is a little bit easier to read, and I can include some comments.

An example of the use of the style sheets. Before evaluation, we have:

Before evaluation

After evaluation (using the blockquote style sheet), we have:

enter image description here

And, the version pasted using "Copy" (where all 3 cells are selected) is seen below:

    Row[{N[Pi,100], x^2, "c"}] //InputForm

Row[{3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844376249654`100., x^2, "c"}]

Row[{N[Pi,100], x^2, "c"}] //FullForm

Row[List[3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844376249654`100.,Power[x,2],"c"]]

Row[{N[Pi,100], x^2, "c"}]

3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068x^2c

Using the new multi-input/message markdown support

Now, the following will work (here I use the code style sheet):

multiple inputs

which looks like the following when pasted:

1/0
Row[{N[Pi,100],b^2,"c"}]//FullForm
Sin[x,y];
3+6

Power::infy: Infinite expression 1/0 encountered. >>

(* ComplexInfinity *)

(* Row[List[3.141592653589793238462643383279502884197169399375\
1058209749445923078164062862089986280348253421170679\
82148086513282306647093844376249654`100., 
Power[b, 2], "c"]] *)

Sin::argx: Sin called with 2 arguments; 1 argument is expected. >>

(* 9 *)

The style sheets are given below, first the code style sheet, followed by the blockquote style sheet. Install by copying and evaluating the code below, then selecting Install Stylesheet:

code style sheet

Notebook[
    {
    Cell[StyleData[StyleDefinitions->"Default.nb"]],
    (* the Notebook style needs to have ShowCellLabel\[Rule]False so that copying the cells doesn't copy the label *)
    Cell[StyleData["Notebook"], ShowCellLabel->False, MenuSortingValue->10000],
    Cell[StyleData["Code"],
        (* CellTags and CellLabelAutoDelete options mean the generated cells include information needed for
         * postprocessing. The $Output PageWidth change is useful for creating strings with linebreaks *)
			CellProlog:>(
				SetOptions[EvaluationCell[], CellTags->"StackExchange", CellLabelAutoDelete->False];
				width=Options[$Output, PageWidth];
            SetOptions[$Output, PageWidth->60]
			),
			CellEpilog:>Quiet @ With[{
				cellObject = EvaluationCell[],
				out = Cells[CellTags->"StackExchange", CellStyle->"Output"],
				message = Cells[CellTags->"StackExchange", CellStyle->"Message"]
				},
				SetOptions[$Output, width];

            (* reformat input/code cell if needed, and remove CellTags/CellLabelAutoDelete added in CellProlog *)
            Replace[NotebookRead[cellObject],
                Cell[BoxData[a_], "Code", b__Rule] :> 
                NotebookWrite[cellObject,
                    Cell[BoxData @ If[MatchQ[a, RowBox[{"\t", __}]],
                            a,
                            RowBox[{"\t", If[ListQ[a], RowBox, Identity] @ ReplaceAll[a,  "\n"->Sequence["\n","\t"]]}]
                        ],
                        "Code", TextClipboardType -> "PlainText", FilterRules[{b}, CellLabel]
                    ]
                ]
            ];

            (* add CellLabelAutoDelete\[Rule]False to the output cells so that the original CellLabel can be reused *)
            Map[SetOptions[#, CellLabelAutoDelete->False]&, out];

            (* add > to the beginning of Message cells *)
            Cases[message,
                obj_ :> Replace[NotebookRead[obj], 
                    Cell[BoxData[a_], s__String, b__] :>
                    NotebookWrite[obj, Cell[BoxData[RowBox[{">", a}]], s, DeleteCases[{b}, CellTags->_]]]
                ]
            ];

            (* reformat output cells so that they appear as commented code *)
            Cases[out,
                obj_ :> Replace[NotebookRead[obj],
                    {
                    (* setting PageWidth\[Rule]60 in the Prolog means indenting adding indents is all that's needed *)
                    Cell[a_String, s__String, b__] :>
                    NotebookWrite[obj,
                        Cell[
                            StringJoin["\t(* ", StringReplace[a, "\n"->"\n\t"], " *)"],
                            s, TextClipboardType -> "PlainText", DeleteCases[{b}, CellTags|CellLabelAutoDelete->_]
                        ]
                    ],

                    (* ExportPacket produces a plaintext string, and ExportString adds linebreaks
                     * Some postprocessing of the ExportString output is needed
                     *)
                    c:Cell[_BoxData, s__String, b__] :>
                    NotebookWrite[obj,
                        Cell[
                            StringReplace[
                                ExportString[First@FrontEndExecute@ExportPacket[c, "PlainText"], "Package", PageWidth->60],
                                Shortest[__]~~"\n\""~~o___~~"\"\n"~~EndOfString :>
                                "\t(* "<>StringReplace[o, {"\\\\"->"\\", "\\\n"->"\\\n\t", "\\"~~x_ :> x}] <>" *)"
                            ],
                            s, TextClipboardType -> "PlainText", DeleteCases[{b}, CellTags|CellLabelAutoDelete->_]
                        ]
                    ]
                    }
                ],
                {1}
            ]
        ],
        InitializationCell->False,
        ShowCellLabel->True,Background->RGBColor[0.477119`,0.957519`,1]
    ],
    Cell[StyleData["Input"],ShowCellLabel->True],
    Cell[StyleData["Output"],ShowCellLabel->True]
    },
    Saveable->False,WindowSize->{808,689},WindowMargins->{{Automatic,143},{40,Automatic}},
    FrontEndVersion->"10.3 for Mac OS X x86 (32-bit, 64-bit Kernel) (December 10, 2015)",
    StyleDefinitions->"PrivateStylesheetFormatting.nb"
];
NotebookPut @ %;

block quote style sheet

Notebook[
    {
    Cell[StyleData[StyleDefinitions->"Default.nb"]],
    (* the Notebook style needs to have ShowCellLabel\[Rule]False so that copying the cells doesn't copy the label *)
    Cell[StyleData["Notebook"], ShowCellLabel->False, MenuSortingValue->10000],
    Cell[StyleData["Code"],
        (* CellTags and CellLabelAutoDelete options mean the generated cells include information needed for
         * postprocessing. The $Output PageWidth change is useful for creating strings with linebreaks *)
			CellProlog:>(
				SetOptions[EvaluationCell[], CellTags->"StackExchange", CellLabelAutoDelete->False];
				width=Options[$Output, PageWidth];
            SetOptions[$Output, PageWidth->60]
			),
			CellEpilog:>Quiet @ With[{
				cellObject = EvaluationCell[],
				out = Cells[CellTags->"StackExchange", CellStyle->"Output"],
				message = Cells[CellTags->"StackExchange", CellStyle->"Message"]
				},
				SetOptions[$Output, width];

            (* reformat input/code cell if needed, and remove CellTags/CellLabelAutoDelete added in CellProlog *)
            Replace[NotebookRead[cellObject],
                Cell[BoxData[a_], "Code", b__Rule] :> 
                NotebookWrite[cellObject,
                    Cell[BoxData @ If[MatchQ[a, RowBox[{"\t", __}]],
                            a,
                            RowBox[{"\t", If[ListQ[a], RowBox, Identity] @ ReplaceAll[a,  "\n"->Sequence["\n","\t"]]}]
                        ],
                        "Code", TextClipboardType -> "PlainText", FilterRules[{b}, CellLabel]
                    ]
                ]
            ];

            (* add CellLabelAutoDelete\[Rule]False to the output cells so that the original CellLabel can be reused *)
            Map[SetOptions[#, CellLabelAutoDelete->False]&, out];

            (* add >- to the beginning of Message cells *)
            Cases[message,
                obj_ :> Replace[NotebookRead[obj], 
                    Cell[BoxData[a_], s__String, b__] :>
                    NotebookWrite[obj, Cell[BoxData[RowBox[{">-", a}]], s, DeleteCases[{b}, CellTags->_]]]
                ]
            ];

            (* add > to output cells to produce blockquote formatting *)
            Replace[out,
                obj_ :> Replace[NotebookRead[obj],
                    {
                    Cell[a_String, s__String, b__] :>
                    NotebookWrite[obj,
                        Cell[StringJoin[">", a],
                            "Output", TextClipboardType -> "PlainText", DeleteCases[{b}, CellTags|CellLabelAutoDelete -> _]
                        ]
                    ],
                    Cell[BoxData[a_], s__String, b__] :> 
                    NotebookWrite[obj,
                        Cell[BoxData[RowBox[{">",a}]], 
                            "Output", TextClipboardType -> "PlainText", DeleteCases[{b}, CellTags|CellLabelAutoDelete -> _]
                        ]
                    ]
                    }
                ],
                {1}
            ]
        ],
        InitializationCell->False,
        ShowCellLabel->True,Background->RGBColor[0.477119`,0.957519`,1]
    ],
    Cell[StyleData["Input"],ShowCellLabel->True],
    Cell[StyleData["Output"],ShowCellLabel->True]
    },
    Saveable->False,WindowSize->{808,689},WindowMargins->{{Automatic,143},{40,Automatic}},
    FrontEndVersion->"10.3 for Mac OS X x86 (32-bit, 64-bit Kernel) (December 10, 2015)",
    StyleDefinitions->"PrivateStylesheetFormatting.nb"
];
NotebookPut @ %;
$\endgroup$
  • $\begingroup$ My last edit was motivated by the case where a user sets On["Packing"]. In that case, we get messages due to unpacking while reformatting the cells for import into stack exchange. There shouldn't be any messages while reformatting the cells, unless there is a bug in the reformatting code, so the Quiet prevents any such message from being generated. If there is a bug in the reformatting code, one can remove the Quiet for debugging purposes. $\endgroup$ – Carl Woll Jan 7 '17 at 21:38

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.