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
Unhide
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"