15
$\begingroup$

I have a huge set of packages with, arguably, far too many functions. Paring down the number of functions, while a good thing to do eventually, is something I just don't want to spend the time on right now. Rather I just want a way to turn off the symbols from showing up in the front-end by default.

I know this is possible as DeclareKnownSymbols doesn't show up or autocorrect until I have prepend System` to the name. And things like DataPaclets`GetDataPacletResource don't show up black despite the fact that they have values.

How can I set this up myself?

$\endgroup$
1
  • $\begingroup$ Related: 115649 $\endgroup$
    – QuantumDot
    May 28, 2017 at 18:59

1 Answer 1

13
+100
$\begingroup$

Core Function

Here's a single function that does all this coloring stuff:

FESetSymbolColoring[
   {syms__},
   cont : _String | Automatic : Automatic,
   contPath : {__String} | Automatic : Automatic,
   which :
    "Undefined" | "Removed" | "Defined" | "Cleared" |
     1 | 2 | 3 | 
     4 | {(1 | 2 | 3 | 4 | "Undefined" | "Removed" | "Defined" | 
         "Cleared") ..}
   ] :=
  With[{
    stuff =
     Map[
       Function[Null,
        If[StringQ@Unevaluated[#],
         Replace[
          StringSplit[#, "`", 2],
          {{c_, s_} :> {c <> "`", s}, {s_} :> {$Context, s}}
          ],
         {Context@#, SymbolName@Unevaluated@#}
         ],
        HoldAllComplete
        ],
       HoldComplete[syms]
       ] // Apply[List],
    whi =
     Replace[Flatten@{which},
      {
       "Undefined" -> 1,
       "Removed" -> 2,
       "Defined" -> 3,
       "Cleared" -> 4
       },
      1
      ]
    },
   FrontEndExecute@
    FrontEnd`UpdateKernelSymbolContexts[
     Replace[cont, Automatic :> $Context],
     Replace[contPath, Automatic :> $ContextPath],
     KeyValueMap[
      With[{symlist = #2},
        Prepend[Replace[cont, Automatic :> #]]@
         Fold[
          ReplacePart[#, #2 -> symlist] &,
          ConstantArray[{}, 4],
          whi
          ]
        ] &,
      GroupBy[stuff, First -> Last]
      ]
     ]
   ];
FESetSymbolColoring[s_, a___] :=
  FESetSymbolColoring[{s}, a];
FESetSymbolColoring~SetAttributes~HoldAllComplete;

Use it like:

FESetSymbolColoring[{a,b,c},"Defined"]

or

FESetSymbolColoring[{a,b,c},"Removed"]

The place to start, here, is actually FrontEndResourceString["GetFEKernelInit"] (which just pulls from a GetFEKernelInit.tr file).

And in it we'll find the definition of System`DeclareKnownSymbols but also this curious function: Internal`SymbolList.

And we'll see that it's often used as Internal`SymbolList[True]. The trick, though, is to set use Internal`SymbolList[False] which just turns off the last part of the output to Internal`SymbolList[], which is used by FrontEnd`Private`GetCurrentSymbolsContexts (I assume) to set the coloring. So that's really the trick. Just make a block like:

FEHiddenBlock[expr_] :=
  (
   Internal`SymbolList[False];
   (Internal`SymbolList[True]; #) &@expr
   );
FEHiddenBlock~SetAttributes~HoldAllComplete;

and any symbols you define within it won't be recognized by the FE until you call System`DeclareKnownSymbols on their symbol names (if they're in $Context) or call this on the symbols:

FEUnhideSymbols[syms__] :=
  With[{stuff =
     Map[
       Function[Null, {Context@#, SymbolName@Unevaluated@#}, 
        HoldAllComplete],
       HoldComplete[syms]
       ] // Apply[List]
    },
   KeyValueMap[
     FrontEndExecute@
       FrontEnd`UpdateKernelSymbolContexts[
        #,
        $ContextPath,
        {{#, {}, {}, #2, {}}}
        ] &,
     GroupBy[stuff, First -> Last]
     ];
   ];
FEUnhideSymbols~SetAttributes~HoldAllComplete;

Note that that will cause the FE to recognize value-less symbols.

We can also hide symbols that are recognize via a slight variant to that method:

FERehideSymbols[syms__] :=
  With[{stuff =
     Map[
       Function[Null, {Context@#, SymbolName@Unevaluated@#}, 
        HoldAllComplete],
       HoldComplete[syms]
       ] // Apply[List]
    },
   KeyValueMap[
     FrontEndExecute@
       FrontEnd`UpdateKernelSymbolContexts[
        #,
        $ContextPath,
        {{#, {}, #2, {}, {}}}
        ] &,
     GroupBy[stuff, First -> Last]
     ];
   ];
FERehideSymbols~SetAttributes~HoldAllComplete;

Examples:

Declare hidden

hidden

Unhide

unhide

Rehide

rehide


Warning:

All symbol coloring is inherently a little bit fragile (unless you initially hid your symbols with Internal`SymbolList[False]). Consider the case of:

FESetSymbolColoring[Global`a, "Defined"]
Global`a // Clear

Nothing seems to have happened, but that's just because Clear sticks Global`a onto the fourth list in the SymbolList and FrontEnd`Private`GetUpdatedSymbolContexts picks this up and removes the coloring.

So you need to set the coloring after all definitions, etc. are locked in.


Update:

QuantumDot asked about the second and fourth arguments of that last list. I'll get into a fuller description, but here's some evocative code to guide the discussion:

With[{
  a = Unique[a],
  b = Unique[b],
  c = Unique[c],
  e = Unique[PacletManager`testSymbol
     (*Chosen because on $ContextPath and not System` or Global`*)
   \
 ]
  },
 a = 1;
 a // Clear;
 b = 1;
 c = 1;
 c // Remove;
 e = 1;
 Last@Internal`SymbolList[]
 ]

{{"Global`", {}, {"c$2909"}, {"b$2908"}, {"a$2907"}}, \
{"PacletManager`", {"testSymbol"}, {}, {"testSymbol$2910"}, {}}, \
{"System`", {}, {}, {}, {"ToExpression"}}}

Note that the cleared symbol is passed to the 4th list, the symbol given a value is passed to the 3rd, the removed symbol is passed to the 2nd, and the non-Global symbol given no value (but introduced via Shift-Enter) is passed to the first.

Now here's the code I've been using to play with symbol coloring:

feSomethingWithSymbols[
   {syms__},
   cont : _String | Automatic : Automatic,
   contPath : {__String} | Automatic : Automatic,
   which : 1 | 2 | 3 | 4 | {(1 | 2 | 3 | 4) ..},
   echo : True | False : True
   ] :=
  With[{stuff =
     Map[
       Function[Null,
        If[StringQ@Unevaluated[#],
         Replace[StringSplit[#, "`", 2], {
           {c_, s_} :>
            {c <> "`", s},
           {s_} :>
            {$Context, s}
           }],
         {Context@#, SymbolName@Unevaluated@#}
         ],
        HoldAllComplete
        ],
       HoldComplete[syms]
       ] // Apply[List]
    },
   FrontEndExecute@
    FrontEnd`UpdateKernelSymbolContexts[
     Replace[cont, Automatic :> $Context],
     Replace[contPath, Automatic :> $ContextPath],
     If[echo, Echo, Identity]@KeyValueMap[
       With[{symlist = #2},
         Prepend[Replace[cont, Automatic :> #]]@
          Fold[
           ReplacePart[#, #2 -> symlist] &,
           ConstantArray[{}, 4],
           Flatten@{which}
           ]
         ] &,
       GroupBy[stuff, First -> Last]
       ]
     ]
   ];
feSomethingWithSymbols~SetAttributes~HoldAllComplete;

Basically it's an easily customizable call to UpdateKernelSymbolContexts.

And we'll add a convenient stateQ function to specify whether a symbol is blue, red, or black:

stateQ[sym_] :=
  With[{s = Mean /@ Transpose@Flatten[ImageData@#, 1]},
     Which[
      Last@s > .9,
      "Undefined",
      First@s > .9,
      "Shadowed",
      True,
      "Defined"
      ]
     ] &@Rasterize[Cell[BoxData@ToString@Unevaluated@sym, "Input"], 
    "Image"];
stateQ~SetAttributes~HoldAllComplete;

And here's a convenient testing function (which will show some interesting stuff):

testSyms[{syms__}, pre_: Null, testList_] :=
  (
   Quiet[Remove[syms]];
   pre;
   feSomethingWithSymbols[{syms}, testList];
   stateQ[syms]
   );
testSyms~SetAttributes~HoldFirst

{1} -- Used by: defining symbols without values

testList = {1};
"test1" -> testSyms[{test1}, testList]
"test1" -> stateQ[test1] 
(*This differest from testSyms as the FE has time to recolor before \
this is evaluated*) 
"test2" -> testSyms[{test2}, test2 = 1;, testList]
"test2" -> stateQ[test2]
"Print" -> testSyms[{Print}, testList]
"Print" -> stateQ["Print"]
"Global`ToExpression" ->
 testSyms[{"Global`ToExpression"}, testList]
"System`ToExpression" ->
 stateQ[ToExpression]

"test1" -> "Defined"

"test1" -> "Undefined"

"test2" -> "Defined"

"test2" -> "Undefined"

"Print" -> "Defined"

"Print" -> "Defined"

"Global`ToExpression" -> "Undefined"

"System`ToExpression" -> "Shadowed"

We can see that almost nothing happened. All that really happened was that "System`ToExpression" noted a symbol conflict.

One other interesting thing to note is that when symbols are passed into the evaluator they clearly get marked as defined, even if they have no values to them.

{2} -- Used by: Remove

These are symbols that are noted as unknown by the front-end no matter what they are:

testList = {2};
"test1" -> testSyms[{test1}, testList]
"test1" -> stateQ[test1] 
(*This differest from testSyms as the FE has time to recolor before \
this is evaluated*) 
"test2" -> testSyms[{test2}, test2 = 1;, testList]
"test2" -> stateQ[test2]
"Print" -> testSyms[{Print}, testList]
"Print" -> stateQ["Print"]
"Global`ToExpression" ->
 testSyms[{"Global`ToExpression"}, testList]
"System`ToExpression" ->
 stateQ[ToExpression]

"test1" -> "Defined"

"test1" -> "Undefined"

"test2" -> "Defined"

"test2" -> "Undefined"

"Print" -> "Undefined"

"Print" -> "Undefined"

"Global`ToExpression" -> "Undefined"

"System`ToExpression" -> "Defined"

{3} -- Used by: Giving values

These are noted as defined, no matter what they are:

testList = {3};
"test1" -> testSyms[{test1}, testList]
"test1" -> stateQ[test1] 
(*This differest from testSyms as the FE has time to recolor before \
this is evaluated*) 
"test2" -> testSyms[{test2}, test2 = 1;, testList]
"test2" -> stateQ[test2]
"Print" -> testSyms[{Print}, testList]
"Print" -> stateQ["Print"]
"Global`ToExpression" ->
 testSyms[{"Global`ToExpression"}, testList]
"System`ToExpression" ->
 stateQ[ToExpression]

"test1" -> "Defined"

"test1" -> "Defined"

"test2" -> "Defined"

"test2" -> "Defined"

"Print" -> "Defined"

"Print" -> "Defined"

"Global`ToExpression" -> "Defined"

"System`ToExpression" -> "Shadowed"

{4} -- Used by: Clear

This seems to check whether the symbol in the "Global`" context and if it is, marks it as unknown.

testList = {4};
"test1" -> testSyms[{test1}, testList]
"test1" -> stateQ[test1] 
(*This differest from testSyms as the FE has time to recolor before \
this is evaluated*) 
"test2" -> testSyms[{test2}, test2 = 1;, testList]
Options[test2] = {"a" -> "b"};
feSomethingWithSymbols[{test2}, {4}];
"test2" -> stateQ[test2]
"Print" -> testSyms[{Print}, testList]
"Print" -> stateQ["Print"]
"Global`ToExpression" ->
 testSyms[{"Global`ToExpression"}, testList]
"System`ToExpression" ->
 stateQ[ToExpression]

"test1" -> "Defined"

"test1" -> "Undefined"

"test2" -> "Defined"

"test2" -> "Undefined"

"Print" -> "Defined"

"Print" -> "Defined"

"Global`ToExpression" -> "Undefined"

"System`ToExpression" -> "Shadowed"
$\endgroup$
13
  • $\begingroup$ Do you know how to make the symbol appear black, but not appear in the autocompletion list? That would be very nice. $\endgroup$
    – QuantumDot
    Jul 27, 2017 at 0:40
  • $\begingroup$ @QuantumDot don't know if that's possible via the FrontEnd directly but it is however settable at the system level and maybe soon at the paclet level, if Itai Seggev's "[forward] ... to the relevant developer" bears fruit. $\endgroup$
    – b3m2a1
    Jul 27, 2017 at 0:43
  • $\begingroup$ Ok, thanks for the info! $\endgroup$
    – QuantumDot
    Jul 27, 2017 at 0:57
  • $\begingroup$ @QuantumDot I'll do a bit more digging before throwing up my hands in defeat, but if you look at Names["FrontEnd*`*Symbol*"] there really isn't much left to cover. $\endgroup$
    – b3m2a1
    Jul 27, 2017 at 1:00
  • $\begingroup$ Its kind of entertaining to play around with this... have you found out what happens if you put the list of symbols in the second or fourth positions of the last argument of FrontEnd`UpdateKernelSymbolContexts? $\endgroup$
    – QuantumDot
    Jul 29, 2017 at 16:55

Your Answer

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

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