-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Lines plugin #2413
base: master
Are you sure you want to change the base?
Lines plugin #2413
Conversation
I am curious why you made a decision of not respecting a new line in a comment? I see both pros and cons of that decision, but I think it would be valuable to have it documented somewhere why such a decision was made. My personal preference would be to respect newlines even in comments if we are announcing that the plugin is splitting lines, though. |
Comments do not contribute to the I only put comments in there to show that it can handle any kind of valid markup. I seriously doubt that it will encounter comments in the wild but better safe than sorry. |
if (!listeners.length) { | ||
// no plugin needs line wrappers, so we don't add any | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible for CSS-only solutions to exist such that there could be no JS listeners but still worth wrapping every line?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this is so the plugin does nothing by default. Without this, you just can't turn it off which is a problem because it is incompatible with Keep markup if kept nodes span multiple lines.
Prism.hooks.run('lines-register', /** @type {RegisterHookEnv} */({ | ||
pre: pre, | ||
env: env, | ||
listeners: listeners |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why implement a separate listener system inside the lines plugin vs using Prism.hooks
?
I should really read the description more closely before commenting lol.
I'm inclined to think taking advantage of Prism.hooks is better. I don't think we need to reinvent the wheel for this. |
Sorry for the delay.
Could you please explain how?
This is the reason why added the listeners in the first place and I don't see how to do this cleanly with hooks. Let's look at an example: line numbers. The lines plugin doesn't track the number/index of the current line (and arguable it shouldn't looking at counting line numbers in unified diffs), this is the responsibility of the listeners. The listener has to keep track of the current line number via state. This is pretty easy to do using the current listener approach: Prism.hooks.add('lines-register', env => {
let counter = 0;
listeners.push({
onNewLine({ line }) {
counter++;
line.setAttribute('data-line-number', counter);
}
});
}); |
Listeners could track it in the plugin scope: let counter;
Prism.hooks.add('lines-init', env => {
counter = 0;
});
Prism.hooks.add('lines-on-new-line', env => {
counter++;
env.line.setAttribute('data-line-number', counter);
}); Unless you're thinking of something more complicated? If we're going to do this, we might want to standardize some sort of namespacing for plugins, e.g |
This type of init-event that resets the current state is what I wanted to avoid. I can only see plugin-wide shared mutable state go wrong. Other plugins, such as Command line, use the Prism.hooks.add('lines-on-new-line', env => {
let state = env.vars['line-number'] || { counter: 0 };
state.counter++;
env.line.setAttribute('data-line-number', state.counter);
}); The main advantage of this is that plugin state is automatically reset. Or maybe we could expose just a single Prism.hooks.add('lines-on-new-line', env => {
let state = env.getState('line-number', () => ({ counter: 0 }));
state.counter++;
env.line.setAttribute('data-line-number', state.counter);
}); |
Why parse the DOM and then modify it? Why not just splitting the tokens: Prism.hooks.add('after-tokenize', function (env) {
if (!env.tokens) {
return;
}
var oldTokens = env.tokens;
var array = [];
oldTokens.forEach(function (tokens) {
if (tokens instanceof Prism.Token) {
if (tokens.content.indexOf("\n") !== -1) {
var codeLines = tokens.content.split(NEW_LINE_EXP);
codeLines.forEach(function (line, index) {
if (line.trim().length === 0) {
array.push(line);
} else {
array.push(new Prism.Token(tokens.type, line, tokens.alias));
}
if (!tokens.content.endsWith(line)) { // HACK, maybe we need some better splitting function that only preserves the needed \n
array.push("\n");
}
});
return;
}
}
array.push(tokens);
});
env.tokens = array;
}); As the first part, and then handling all Prism.hooks.add('before-insert', function (env) {
if (!env.code) {
return;
}
var codeLines = env.highlightedCode.split(NEW_LINE_EXP);
env.highlightedCode = "<table class=source style=\"margin-left:0;\">";
codeLines.forEach(function (line) {
env.highlightedCode += "<tr><td class=lnum></td><td>" + line +"</td></tr>";
});
env.highlightedCode += "</table>"
}); Here, easily, new hooks for |
Because we have other plugins that use the A good example is Markdown. Markdown uses the By splitting the produced HTML and not the tokens into lines, it is a lot easier to implement language-specific plugins. |
Then possible another hook might be needed. I still think it's way more elegant to work on the tokens. |
The
I agree but it's not possible for us without breaking existing code. |
This is the continuation of #2389.
The only function of this plugin is to wrap every line with an element, so we can more easily do line-based functionality (like line number and line highlights). This is done by parsing Prism's generated HTML code and then modifying the parsed DOM. After all lines are wrapped, the parsed DOM is converted into HTML code again (to be parsed and inserted into the actual document). The plugin only uses the
before-insert
hook.The idea is to re-implement Line Numbers and Line Highlight with this plugin. The main advantage is that the browser will then align line numbers for us, so we no longer have to measure the height of each line (and update that on window resize). This will even resolve some issues.
The basic line-splitting algorithm is the same as in #2389. I modified it to be more efficient but the operation is still quite costly because of the parsing and subsequent creation of an entire document. To mitigate this a little, the Lines plugin is opt-in meaning that it will only wrap lines if it is requested to do so. This is done via the:
Register hook
The Lines plugin has one custom hook:
lines-register
.This hook is used to register listeners that can modify the line wrapper elements among other things. The Lines plugin will only be active for a given code block if at least one listener is registered for the given code block. (If no other plugin needs line wrappers, they won't be added.)
Listeners
Listeners are a lot like hooks but more object-oriented. Instead of being a single function, a listener is an object with functions for specific events. This is necessary because events might have to share state which is very hard to do with Prism hooks.
The best example is the
onNewLine
event that is fired multiple times per code block. If you want to count line numbers, you have to update the state of the counter for eachonNewLine
event (per code block per highlight). This is very hard to do with the normally stateless Prism hooks.Listeners can also share state with other listeners by modifying the environment object of an event.
TODO / discussion
index.html
yet in case we change the API.Some feedback would be appreciated because I'm not sure about the listener API.