Idea: I want to customize those classes generated by Prism for each token (for example, var in JS will be generated to <span class="token keyword">var</span>. I want to change the string "token keyword".
Reason: Some situation require me to use locally scoped classes, like in a CSS Modules project or one that follow BEM Style.
Solution: I wrote a small plugin, based on Highlight Keyword, to customize those classes using a style-map object. The object looks like this:
var customStyleMap = {
tokenType: className
}
with tokenType is type of token I want to customize (eg: 'keyword', 'string', 'operator'...) and className is the class string I want to use for that token (eg: 'my-special-class-for-token' or a hashed one like '_3ka93h'
Example Usage:
var customStyleMap = {
keyword: 'special-keyword',
string: 'special-string'
};
customizePrismClasses(customStyleMap);
Or we can use the object returned by CSS Modules:
var customStyleMap = require('my-custom-style-map.css');
customizePrismClasses(customStyleMap);
Then, the string var will become <span class="special-keyword">var</span>.
The source code is very simple, but I think it is useful, especially for project which we want to scope the class name locally, like CSS Modules projects.
Please tell me what do you guys think about this. I'll make a pull request if you think it is useful enough.
The functionality is useful. I would suggest you also allow nested objects, in case someone wants to customize these tokens differently per language.
Also, do not use a global function, instead hang it on Prism.plugins.yourpluginname
Thank you, I'll make the pull request
@LeaVerou Hi LeaVerou, can you suggest which hooks should I use? I'm currently use 'wrap' but env.language is undefined
{
"type": "punctuation",
"content": "<",
"tag": "span",
"classes": [
"token",
"punctuation"
],
"attributes": {},
"parent": [
//...
]
}
Damn. No, wrap is the perfect hook for this. @zeitgeist87 do you think it would be easy to add language to env there?
Ok.. as far as I see, the step when use highlightAll and highlightElement is 1) we find the language name by element's class name, then 2) we find the grammar by that language name, then 3) we call highlight with grammar and language
The problem is when we use highlight API, we don't mention the language name but we pass the grammar directly, like highlight(someString, Prism.languages.jsx), so highlight function doesn't know about language name now
@LeaVerou I got an idea, how about in highlight function, if language is undefined then we find it by grammar? Something like this:
highlight: function (text, grammar, language) {
// find language name by grammar if undefined
if (typeof language === 'undefined') {
language = _.keyBy(Prism.languages, grammar);
}
var tokens = _.tokenize(text, grammar);
return Token.stringify(_.util.encode(tokens), language);
},
The lodash function is just for example. I think we can find it using the __id property of grammar
Ok, real code here, how about this:
highlight: function (text, grammar, language) {
// find language name by grammar if undefined
if (typeof language === 'undefined') {
for (var key in Prism.languages) {
if (grammar.__id === Prism.languages[key].__id) {
language = key;
}
}
}
var tokens = _.tokenize(text, grammar);
return Token.stringify(_.util.encode(tokens), language);
},
It is possible, that __id doesn't exist. It is added on the fly by the _.util.objId() function. In the rare event where insertBefore is never called you would have no __id fields:
This should work in all cases, but I havn't tested it:
if (typeof language === 'undefined') {
for (var key in Prism.languages) {
if (_.util.objId(grammar) === _.util.objId(Prism.languages[key])) {
language = key;
break;
}
}
}
The question is, if we should make the language parameter for the highlight function optional. Inside Prism the parameter is always supplied. You could use the above code just as easily in your plugin or before you call the highlight function.
On the other hand we have examples on http://prismjs.com/ without the language parameter:
var Prism = require('prismjs');
// The code snippet you want to highlight, as a string
var code = "var data = 1;";
// Returns a highlighted HTML string
var html = Prism.highlight(code, Prism.languages.javascript);
But this example was added only a few days ago with a PR. @LeaVerou What do you think?
I think @dvkndn should submit a PR with a basic version of this plugin, and he can add this functionality in the future, if people request it. I suggested it in case it was trivial. I don't think it's worth spending any more time on this, unless people actually ask about it.
@zeitgeist87 sorry I created a pull request before I see your comment. My mistake.
Personally, I don't think we should have both the grammar and language parameter in highlight function, or at least in the API. One can be found be the other. If both of them can be set, someone can call Prism.highlight(code, Prism.languages.javascript, 'css');
Also, I think it is ok to implement that inside the plugin only.
Hi @LeaVerou @zeitgeist87 if you don't mind, can I ask one more thing: where should I let user pass the style map object?
My idea is that user should call a function like Prism.plugins.customizeClasses(classMap), with classMap is defined by them. Inside that function I add the code into 'wrap' hook. Then when they call any of those functions in API, everything will be fine. The same applied when they use <script src="prism.js" data-manual></script>
However, for the use case when Prism automatically highlight after loaded, I'm afraid it would not work because the plugin function will be called after Prism auto highlight elements.
So I'm thinking about 2 approach:
data-manual and call the API themselves, after calling the plugin function.What do you think?
I think I will follow 2nd approach
Prism autohighlight runs only after all other JS has finished. This should be fine:
<script src="prism.js"></script>
<script src="plugins/your-plugin/your-plugin.js"></script>
<script>
Prism.plugins.YourPlugin.customizeClasses({ .... });
</script>
Don't add it to the wrap hook inside of customizeClasses. I would do it like this:
(function() {
var classMap;
Prism.plugins.YourPlugin = {
customizeClasses: function (map) {
// maybe merge classMap and map?
classMap = map;
}
}
Prism.hooks.add('wrap', function (env) {
if (!classMap) {
return;
}
/* use classMap */
});
}());
You don't need __id to get the language. You can compare objects directly. If a reference points to the exact same object, a comparison returns true.
obj1 = {};
obj2 = obj1;
obj3 = {};
console.log(obj1 == obj2, obj1 == obj3); // prints true false
So this should work (I haven't tested it though):
for (var key in Prism.languages) {
if (grammar == Prism.languages[key]) {
language = key;
break;
}
}
Fixed with PR #950