15
$\begingroup$

Since many versions the frontend does a pretty good job as an editor for package files (.m or .wl). In versions up to 11.1 code as the following could be used to additionally tweak the appearance and behavior of the package files in the frontend:

NotebookSave[Notebook[{
  Cell[StyleData[StyleDefinitions -> "Package.nb"]],
  Cell[StyleData["Code"], CellFrame -> 3, 
    CellMargins -> {{55, 3}, {4, 4}}, 
    CellFrameColor -> RGBColor[0.1, 0.6, 0.1]]
}], FileNameJoin[{$HomeDirectory, "Desktop", "mypckgstyle.nb"}]];

Export[FileNameJoin[{$HomeDirectory, "Desktop", "dummy.m"}],
  "f[x_]:=x^2;",
  "Text"
];

pckgstyle=CurrentValue[$FrontEndSession, "DefaultPackageStyleDefinitions"];
 CurrentValue[$FrontEndSession, "DefaultPackageStyleDefinitions"] = 
   FileNameJoin[{$HomeDirectory, "Desktop", "mypckgstyle.nb"}];

NotebookOpen[FileNameJoin[{$HomeDirectory, "Desktop", "dummy.m"}]];

CurrentValue[$FrontEndSession, "DefaultPackageStyleDefinitions"] = pckgstyle;

what that code does is to define a new stylesheet which inherits from the default "Package.nb" stylesheets and changes some details about "Code" cells. It then generates a dummy package file and opens it, using the new stylesheet. After that it restores the original settings for the frontend setting "DefaultPackageStyleDefinitions". If executing this with version < 11.1 you should find the code cells have a thick green frame.

With version 11.1 that seems to not work anymore, some of the settings in such a stylesheet will not be respected but overridden by the settings from "Package.nb", among those the CellFrame and CellFrameColor options. Interestingly other settings will work as expected and as in older versions. Has anyone an explanation for the behavior? What I am doing is not really well documented so I am hesitating to call it a bug, on the other hand I don't see how the new behavior could be justified to be better than what the old was...

Any explanation or workarounds would be appreciated and accepted as answers.

$\endgroup$
7
  • $\begingroup$ Are you aware of github.com/kubaPod/MoreStyles ? Works for me in 11.1. Of course this does not exactly answer your question, but may be a starting point. $\endgroup$ Mar 31, 2017 at 10:20
  • $\begingroup$ @RolfMertig: no, I was not aware of that. Will have a look at it, probably this weekend. Thanks for the pointer. $\endgroup$ Mar 31, 2017 at 12:24
  • $\begingroup$ @AlbertRetey Your issue is that the style you need to change is "InitializationCell", not "Code". That's where "CellFrame*" is defined in "Package.nb". $\endgroup$
    – b3m2a1
    Mar 31, 2017 at 16:35
  • $\begingroup$ @MB1965: I think your comment will solve my problem, if you make it an answer I will accept it. If you have any background information I will appreciate that. It looks like any cell that has InitializationCell->True set will inherit from styledata from "InitializationCell", no matter what its actual style is. I think either the behavior or one of the stylesheets has changed in version 11.1, but maybe it only does now behave as was originally intended... $\endgroup$ Mar 31, 2017 at 20:05
  • 1
    $\begingroup$ slightly related: 126517 $\endgroup$
    – Kuba
    Apr 2, 2017 at 19:29

1 Answer 1

8
+500
$\begingroup$

Simple Answer

So I mentioned in a comment that your method will work. The only issue is that there is a style specification with higher precedence that gets applied after your styles. If you look at "Package.nb" they use "InitializationCell" to specify the CellFrameColor and whatnot. If you override that style instead you're golden.

Since 11.0 (I believe) the "InitializationCell" style gets applied to anything with InitializationCell -> True. "InitializationCell" is much like StandardForm in that it's applied after standard Cell styles (and so has higher precedence).

Overkill Answer

On the other hand, you can actually edit the "Package.nb" styles in place without overwriting the file. This will be a code-intensive answer, so I'll put it all in one block at the end.

First off we need a way to find the style cells. This is relatively easy. We'll do a very simple match. Matching more syntax and vectorizing would be better (I've done that for myself) but requires many more lines of code.

cellMatchQ[Cell[StyleData[name : Except[_Rule], ___], ___], style_] :=

    Switch[{name, style},
   {_String, _String | _StringExpression | _Alternatives},
   StringMatchQ[name, style],
   _,
   MatchQ[name, style]
   ];
StylesheetCells[nb_NotebookObject, style_] :=
  Select[Cells[nb],
   cellMatchQ[NotebookRead@#, style] &
   ];

Then we need a system for editing the cells:

StylesheetApplyEdits[cells : {__CellObject}] :=

  With[{e = EvaluationCell[]},
   Do[
    SelectionMove[c, All, Cell,
     AutoScroll -> False];
    FrontEndTokenExecute[ParentNotebook@c, "ToggleShowExpression"]; 
    FrontEndTokenExecute[ParentNotebook@c, "ToggleShowExpression"];,
    {c, cells}
    ];
   SelectionMove[e, After, Cell]
   ];

Options[StylesheetEdit] =
  Options@Cell;
StylesheetEdit[cells : {__CellObject}, ops : OptionsPattern[]] :=
  (
   Do[SetOptions[c, ops], {c, cells}];
   StylesheetApplyEdits@cells;
   );
StylesheetEdit[nb_NotebookObject, style_, ops : OptionsPattern[]] :=

   Replace[StylesheetCells[nb, style], {
    {} -> Null,
    c : {__} :> StylesheetEdit[c, ops]
    }];

Note that the ApplyEdits call is necessary so the cascade updates properly.

Now we'll use a method for selecting stylesheet notebooks to thread this all together:

$PackageStyleNotebook :=

 SelectFirst[FrontEndExecute@FrontEnd`ObjectChildren[$FrontEnd], 
  Quiet@NotebookFileName@# === 
    FileNameJoin@{$InstallationDirectory, "SystemFiles", "FrontEnd", 
      "StyleSheets", "Package.nb"} &]

If there are any packages open, this will return the NotebookObject that defines their styles.

Now we can use this as such:

StylesheetEdit[$PackageStyleNotebook, "InitializationCell",
 CellFrameColor -> Pink
 ]

Note that this doesn't save any style changes. So every time you close the last of your package notebooks all your styles will get erased. But this can be applied to any style notebook. Which is what makes it fun and powerful. For myself I've also added a StylesheetNew function that will write appropriately configured cells to a notebook, and that can be used with this functionality to easily add new styles.

This is overkill, but fun and flexible overkill.

Now, as promised, here's everything in a block:

cellMatchQ[Cell[StyleData[name : Except[_Rule], ___], ___], style_] :=

    Switch[{name, style},
   {_String, _String | _StringExpression | _Alternatives},
   StringMatchQ[name, style],
   _,
   MatchQ[name, style]
   ];
StylesheetCells[nb_NotebookObject, style_] :=
  Select[Cells[nb],
   cellMatchQ[NotebookRead@#, style] &
   ];

StylesheetApplyEdits[cells : {__CellObject}] :=

  With[{e = EvaluationCell[]},
   Do[
    SelectionMove[c, All, Cell,
     AutoScroll -> False];
    FrontEndTokenExecute[ParentNotebook@c, "ToggleShowExpression"]; 
    FrontEndTokenExecute[ParentNotebook@c, "ToggleShowExpression"];,
    {c, cells}
    ];
   SelectionMove[e, After, Cell]
   ];

Options[StylesheetEdit] =
  Options@Cell;
StylesheetEdit[cells : {__CellObject}, ops : OptionsPattern[]] :=
  (
   Do[SetOptions[c, ops], {c, cells}];
   StylesheetApplyEdits@cells;
   );
StylesheetEdit[nb_NotebookObject, style_, ops : OptionsPattern[]] :=

   Replace[StylesheetCells[nb, style], {
    {} -> Null,
    c : {__} :> StylesheetEdit[c, ops]
    }];

$PackageStyleNotebook :=

 SelectFirst[FrontEndExecute@FrontEnd`ObjectChildren[$FrontEnd], 
  Quiet@NotebookFileName@# === 
    FileNameJoin@{$InstallationDirectory, "SystemFiles", "FrontEnd", 
      "StyleSheets", "Package.nb"} &]
$\endgroup$
6
  • $\begingroup$ thanks, interesting answer which is appreciated. One thing I would like to have more explanation is why it is wrong to change the "Code" style for "Code" cells as that is overwritten by the "InitializationCell" style. It seems to me to be a peculiarity that would be worth mentioning somewhere in the documentation (which it might be, but where?). It isn't something that is self-explanatory, is it? $\endgroup$ Mar 31, 2017 at 20:21
  • $\begingroup$ @AlbertRetey Sorry, maybe wrong is the wrong word ;) It's just that it has higher application precedence, just like StandardForm. I edited the answer to include that. I'll also edit out the wrong. $\endgroup$
    – b3m2a1
    Mar 31, 2017 at 20:23
  • $\begingroup$ @AlbertRetey I think we can understand the higher application precedence of "InitializationCell" as coming from the fact that it's turned on and off at the Option level of a cell, just like StandardForm wraps around boxes and whatnot--i.e. more deeply nested than cell. Interestingly, "InitializationCell" actually takes precedence over StandardForm, I found. $\endgroup$
    – b3m2a1
    Mar 31, 2017 at 20:28
  • $\begingroup$ well, after all it was the wrong way to achieve my goal :-). How did you find about those precedences, just by experimenting? Now that I know they exist they make sense to some extent, but it would of course be nice if one would have a chance to look up which precedence rules do exist... $\endgroup$ Mar 31, 2017 at 20:28
  • 2
    $\begingroup$ @AlbertRetey I found about them the hard way--code I expected to work didn't. Try setting LineSpacing -> {0, 1} on the "Code" style. It doesn't do anything. And that's because the BoxData enforces StandardForm, which has a different spec. On the other hand if you set that on "InitializationCell" it actually does do something. That shows that "InitializationCell" applied after StandardForm does. $\endgroup$
    – b3m2a1
    Mar 31, 2017 at 20:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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