-
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
The Lines plugin: control your lines via CSS #2389
base: master
Are you sure you want to change the base?
Conversation
If anybody is curious, the idea for this plugin came out of necessity to implement visible line wrapping indicators. I did some research and came up with quite a robust and efficient solution to the problem. You can see the full step by step explanation at my blog post about the wrap indicators. While I was working on that, I realised that I needed PrismJS'es support too since it is my primary syntax highlighter. It turned out that pure CSS styling was almost incompatible with mostly JavaScript driven Line Numbers and Line Highlight plugins. Moreover, that blog post with quite a few I wanted line numbering and wished wrap indicators, but I was willing to sacrifice the numbering if it is so heavy. Once I implemented Lines, I thought that I will give it a go and will try to mimic Line Numbers plugin's look and feel using pure CSS. Then I looked at Line Highlight and realised that its "active line numbers" is dependent on Line Numbers, so I looked at what the minimal required support is needed to make it possible to use some trivial line highlighting. I am quite pleased with the result since it is fast, only uses scripting at the setup stage, and allows authors to skin their lines using CSS. |
Thank you for this PR @galaxy4public! For the future: Please also tell us what parts of your contribution don't work (yet). This is very important because reviewers are also just human, so issues might get through until an unlucky user finds them. The main flaw of the current implementation is that it breaks multi-line tokens. ExampleI say "breaks" but really what's happening is that the plugin is currently generating invalid markup and the browser is trying its best to save it. The idea of using Correct line wrapping(Line ends are explicitly denoted with there\n
<span class="x">are\n
<span class="y">so\n
many\n</span>
lines</span>\n
here Has to be: <div>there\n</div>
<div><span class="x">are\n</span></div>
<div><span class="x"><span class="y">so\n</span></span></div>
<div><span class="x"><span class="y">many\n</span></span></div>
<div><span class="x">lines</span>\n</div>
<div>here</div> Keep in mind: The generated markup may be any valid markup, so empty tags and self-closing tags around line ends are likely going to be a problem. Example: <span></span>\n
<br/>\n I think I should also point out that tags may also contain line ends. Example: <!-- no explicit \n this time -->
<span
attr="some
value"></span> And now for the worst part: HTML doesn't require a closing tag for all non-self-closing tags (e.g. Implementing this correctly is really tricky but if we can do it, then we have something really powerful on our hands. I really like this, so I hope that we can pull this off. |
Also regarding the added anchor: Once we have the line wrapping, adding some tags at the start/end of a line is trivial. |
@RunDevelopment , done. Now the plugin fully parses the P.S. This was not the way I envisioned I would spend half of my Sunday :) |
The only file I changed is the |
Just found and fixed a bug that was coming out of me not paying attention. I was assuming that the shadow Upon closer examination, there is no need for that check since we are supposed to always commit the last line buffer to the block (even if nothing was in fact added). I tested this thoroughly and as of now, I am not aware of any weaknesses in this plugin. So, please merge it when possible :) |
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.
Thank you for your time @galaxy4public!
I looked at it and there are things that need to be improved.
However, the main problem I see is that the main algorithm isn't easy to understand and has a lot of edge cases (we already talked about a few), so we really need some tests. We already use JSDOM, so you can just create a file tests/plugins/lines/test.js
, insert the following code, and start writing tests.
The test file
const { assert } = require('chai');
const jsdom = require('jsdom');
const { JSDOM } = jsdom;
const dom = new JSDOM();
const document = dom.window.document;
global.self = global;
global.document = document;
global.DOMParser = dom.window.DOMParser;
global.Node = dom.window.Node;
require('../../../prism');
require('../../../plugins/lines/prism-lines');
describe('Prism Lines Plugin', function () {
function highlightedMarkup(text, language) {
const pre = document.createElement('pre');
pre.className = `language-${language}`;
const code = document.createElement('code');
code.textContent = text;
pre.appendChild(code);
Prism.highlightElement(code);
return code.innerHTML;
}
it('- should <do something>', function () {
const result = highlightedMarkup(`/* a \n b */`, 'javascript');
assert.equal(result, `<div class="line"><a></a><span class="token comment">/* a \n</span></div><div class="line"><a></a><span class="token comment"> b */</span></div>`);
});;
});
Note: This doesn't support the use of DocumentFragment
, but we should get rid of that anyway (see comment).
var p1 = document.createElement(node.nodeName); | ||
var p2 = document.createElement(node.nodeName); | ||
if (node.hasAttributes()) { | ||
for (i = 0; i < node.attributes.length ; i++) { |
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.
Missing var
. Without the var
, i
will be defined in the global namespace.
for (i = 0; i < node.attributes.length ; i++) { | |
for (var i = 0; i < node.attributes.length ; i++) { |
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.
Today I learned something I now don't like about JavaScript, thanks! It's counter-intuitive, but you are right about the scope.
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.
That's just how it works. You basically said that i
is a global variable and JS just did as it was told.
Well, this is a problem that (almost) every dynamically typed language has since variables can be (re-)defined at runtime.
In JavaScript, everything outside of var
s inside functions (different behavior for let
/const
) is a global variable. Array
, Object
, RegExp
are all global variables. You can even delete or replace them if you wanted to. (Please don't.)
This might seem horrible at first (and in many ways, it is) but this also really powerful because, in JS, you can bend everything to your will. (This is also what enables polyfills.)
Well, modern language features such as let
and const
are a lot stricter tho.
} | ||
wrapped.appendChild(line); | ||
|
||
if (wrapped.children) { |
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.
According to TS's lib.dom.d.ts
, children
is an HTMLCollection object and can therefore never be falsy.
Did you mean wrapped.children.length
?
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 guess it comes out of habit not to reference a subattribute without checking parent's existence. Even if some doc says it should be always there, I tend to verify.
} | ||
|
||
if (kid && /\n/.test(kid.textContent)) { | ||
// found a special one :) |
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.
Please also say what the special case is.
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.
Well, the kid who has a line break within their grasp! The whole point of the function is to split the tree at the first new line break, but to do so, we need to go deep first and find the text node containing the line break
So, we do it recursively and building two trees (one for each side of the split) as we descend.
@@ -1185,6 +1185,11 @@ | |||
"description": "Line number at the beginning of code lines.", | |||
"owner": "kuba-kubula" | |||
}, | |||
"lines": { | |||
"title": "Lines", | |||
"description": "Introduces HTML markup around physical lines to allow styling of the particular 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.
This seems more complicated than it has to be. How about: "Adds wrappers around lines to allow line-bases styling"?
|
||
<meta charset="utf-8" /> | ||
<link rel="icon" href="favicon.png" /> | ||
<title>Line Numbers ▲ Prism plugins</title> |
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.
Wrong title.
We should also discuss what we want this to be: As I mentioned before, I see this as a library that can be used to implement a variety of line-based plugins (Line Numbers, Line Highlight, Line Wrap Indicator(?), etc.). I also think it should be opt-in because parsing and processing potentially megabytes of generated markup can take a while and should be avoided wherever possible. Right now, this plugin is three plugins in one (Lines API + Line Numbers + Line Wrap Indicator) and I think we should separate those three. You said yourself: "The primary job of this plugin is to wrap each physical line in a What do you think @galaxy4public? There's also the issue that this plugin is currently incompatible with the Funky theme. |
Can you please elaborate what's wrong with the Funky theme support? |
@RunDevelopment, regarding your bigger strategic question on what's the future - I am not a developer, so I am not in a position to suggest one way or another. I needed that functionality for my personal blog, but trying a good Netizen I wanted to contribute back, this is the reason I raised the PR. I already spent quite a bit of time on this, so investing more into the strategic decisions and the follow-up implementation won't cut it for me, unfortunately. This is what I can do:
Once the above is done, I would prefer somebody else to slice/dice or transform the idea any further. How does this sound? |
That's understandable. It's quite a big feature and I want other parts of Prism to rely on this as well, so it's a lot of work. Thank you for your work!
Then can I branch your work and continue to work on it? |
Sure, that was the whole purpose of the exercise to improve Prism. I would appreciate some mention in a changelog, if possible, but even if not, I think it is more important to have the feature. So, please go ahead and branch it. I will try to contribute when time permits. |
Thank you @galaxy4public! I already made a branch and will publish a new PR in the next few days. I will close this once I open the new PR. |
Did this get implemented? I'm trying to wrap lines without loosing indentation. Thanks. |
@miltone92 See #2413. It's not finished yet (v2.0) but it does what you need. |
This is a very lightweight plugin which was created with high performance in mind.
The primary job of this plugin is to wrap each physical line in a
<div>
so CSS styling could be applied to any desired line. In the documentation there some showcases how one can re-implement the visual look and feel of the Line Numbers Plugin using the semantic markup provided by this plugin and pure CSS; also there is an example how one can implement basic lines highlighting (a small subset of the Line Highlight plugin).This plugin is not a replacement for any of the above, since the scope is different -- the examples are just to show how much freedom one can get if they can address a particular line.