Skip to content
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

Add line numbers library #46

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -5,3 +5,7 @@
- ENHANCEMENT: you can now disable the automatic inclusion of our default theme and provide one of the other highlight.js themes. See "Theming Support" in the README.

- ENHANCEMENT: detect nested code snippets (PR #42 by @defreeman)

# 2.1.0

- ENHANCEMENT: Add code line numbers with code-highlight-linenums
17 changes: 17 additions & 0 deletions README.md
Expand Up @@ -151,6 +151,23 @@ app.import('vendor/highlight.pack.js', {
});
```

# Line Numbering Support

Line numbering support is provided by a highlight addon [code-highlight-linenums](https://github.com/OverZealous/code-highlight-linenums). To enable call with `lineNumbers=true`.

```hbs
{{code-snippet name="my-nice-example.js" lineNumbers=true}}
```

For not including the code-highlight-linenums library, specify on the config like this:

```js
// in ember-cli-build.js
var app = new EmberApp(defaults, {
includeCodeHighlightLinenums: false
});
```

# Theming Support

We include a basic syntax-highlighting theme by default, but highlight.js has 79 different themes to choose from and it's possible to make your own just by writing a stylesheet.
Expand Down
16 changes: 14 additions & 2 deletions app/components/code-snippet.js
@@ -1,13 +1,16 @@
import Ember from "ember";
import Snippets from "../snippets";
import codeHighlightLinenums from "code-highlight-linenums";

/* global require */
var Highlight = self.require('highlight.js');

export default Ember.Component.extend({
tagName: 'pre',
classNameBindings: ['language'],
classNames: ['code-snippet'],
unindent: true,
lineNumbers: false,

_unindent: function(src) {
if (!this.get('unindent')) {
Expand All @@ -27,15 +30,24 @@ export default Ember.Component.extend({
},

source: Ember.computed('name', function(){
return this._unindent(
const source = this._unindent(
(Snippets[this.get('name')] || "")
.replace(/^(\s*\n)*/, '')
.replace(/\s*$/, '')
);
if (this.get('lineNumbers')) {
const lang = this.get('language');
return codeHighlightLinenums(source, {hljs:Highlight, lang, start:1 })
}

return source;

}),

didInsertElement: function(){
Highlight.highlightBlock(this.get('element'));
if(!this.get('lineNumbers')) {
Highlight.highlightBlock(this.get('element'));
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency, let's eliminate didInsertElement and always render {{{source}}}, regardless of whether lineNumbers is true or false.

Otherwise the fastboot behavior is different based on whether you set lineNumbers, which would be surprising.

},

language: Ember.computed('name', function(){
Expand Down
6 changes: 5 additions & 1 deletion app/templates/components/code-snippet.hbs
@@ -1 +1,5 @@
{{source}}
<code class="hljs">
{{#if lineNumbers}}
{{{source}}}
{{else}}{{source}}{{/if}}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To go with the previous comment about didInsertElement: let's remove this conditional and just always do the things that it does when lineNumbers=true.

</code>
13 changes: 13 additions & 0 deletions index.js
Expand Up @@ -37,6 +37,15 @@ module.exports = {
}
},

includeCodeHighlightLinenums: function() {
var app = findHost(this);
if (typeof app.options.includeCodeHighlightLinenums === 'boolean') {
return app.options.includeCodeHighlightLinenums;
} else {
return true;
}
},

includeHighlightStyle: function() {
var app = findHost(this);
if (typeof app.options.includeHighlightStyle === 'boolean') {
Expand Down Expand Up @@ -72,5 +81,9 @@ module.exports = {
if (this.includeHighlightStyle()) {
app.import('vendor/highlight-style.css');
}
if (this.includeCodeHighlightLinenums()) {
app.import('vendor/code-highlight-linenums.js');
}
app.import('vendor/shims/code-highlight-linenums.js');
}
};
96 changes: 96 additions & 0 deletions vendor/code-highlight-linenums.js
@@ -0,0 +1,96 @@
(function(root) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment like

Copied from https://github.com/OverZealous/code-highlight-linenums v0.2.1
MIT Licensed

ensuring of course that that's really the right URL and version number.

"use strict";

function codeHighlightLinenums(code, opts) {
opts = opts || {};
var hljs = opts.hljs,
lang = opts.lang,
start = opts.start || 0;
// prevent errors by hljs
code = code || '';
if(lang && /:/.test(lang)) {
start = +lang.split(/:/)[1];
lang = lang.split(/:/)[0];
} else {
start = +start;
}

if(hljs) {
if(lang) {
code = hljs.highlight(lang, code).value;
} else {
code = hljs.highlightAuto(code).value;
}
}

if(start) {
// move all closing spans to the previous line
code = code.replace(/([\r\n]\s*)(<\/span>)/ig, '$2$1');

// replace spans with line-wraps inside them
code = cleanLineBreaks(code);

code = code.split(/\r\n|\r|\n/);
var max = (start + code.length).toString().length;

code = code
.map(function(line, i) {
return '<span class="line width-' + max + '" start="' + (start + i) + '">' + line + '</span>';
})
.join('\n');
}

return code;
}

// Simplified parser that looks for opening & closing spans, and walks the tree.
// If there are any unclosed spans when a newline is encountered, we close them on the previous line,
// and copy them forward to the next line.
function cleanLineBreaks(code) {
var openSpans = [],
matcher = /<\/?span[^>]*>|\r\n|\r|\n/ig,
newline = /\r\n|\r|\n/,
closingTag = /^<\//;

return code.replace(matcher, function(match) {
if(newline.test(match)) {
if(openSpans.length) {
return openSpans.map(function() { return '</span>' }).join('') + match + openSpans.join('');
} else {
return match;
}
} else if(closingTag.test(match)) {
openSpans.pop();
return match;
} else {
openSpans.push(match);
return match;
}
});
}

(function(factory) {
if(typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(factory)
} else if(typeof exports === 'object') {
/**
* Node. Does not work with strict CommonJS, but
* only CommonJS-like environments that support module.exports,
* like Node.
*/
module.exports = factory();
} else {
// Browser globals (root is window)
root.codeHighlightLinenums = factory();
}
}(function() {
/**
* Just return a value to define the module export.
* This example returns an object, but the module
* can return a function as the exported value.
*/
return codeHighlightLinenums;
}))

})(this);
80 changes: 79 additions & 1 deletion vendor/highlight-style.css
Expand Up @@ -118,4 +118,82 @@

.hljs-chunk {
color: #aaa;
}
}

/* this uses the CSS suggested by
https://github.com/OverZealous/code-highlight-linenums#example-lesscss
*/

pre.code-snippet > code .line {
display: inline-block;
position: relative;
padding-left: calc(2ch + 18px);
}

pre.code-snippet > code .line:before {
box-sizing: content-box;
display: inline-block;
position: absolute;
top: 0;
bottom: 0;
text-align: right;
width: 2ch;
content: attr(start);
padding-right: 9px;
padding-left: 9px;
margin-left: calc(-2ch - 27px);
margin-right: 9px;
}

pre.code-snippet > code .line:after {
content: ' ';
}


pre.code-snippet > code .line:first-child:before {
padding-top: 9px;
margin-top: -9px;
border-bottom-left-radius: 1em;
}

pre.code-snippet > code .line:last-child:before {
padding-bottom: 9px;
margin-bottom: -9px;
border-bottom-left-radius: 1em;
}

pre.code-snippet > code .line.width-3 {
padding-left: calc(3ch + 18px);
}

pre.code-snippet > code .line.width-3:before {
width: 3ch;
margin-left: calc(-3ch - 27px);
}

pre.code-snippet > code .line.width-4 {
padding-left: calc(4ch + 18px);
}

pre.code-snippet > code .line.width-4:before {
width: 4ch;
margin-left: calc(-4ch - 27px);
}

pre.code-snippet > code .line.width-5 {
padding-left: calc(5ch + 18px);
}

pre.code-snippet > code .line.width-5:before {
width: 5ch;
margin-left: calc(-5ch - 27px);
}

pre.code-snippet > code .line.width-6 {
padding-left: calc(6ch + 18px);
}

pre.code-snippet > code .line.width-6:before {
width: 6ch;
margin-left: calc(-6ch - 27px);
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole file won't be included if the user chooses to install their own theme. Maybe that's OK? But if they install stock highlight.js themes they will lack styling for the line numbers.

An alternative would be to make this a separate CSS file that gets included whenever the code numbers JS file also gets included. If this CSS works well with many different highlight.js themes, that would probably be best.

12 changes: 12 additions & 0 deletions vendor/shims/code-highlight-linenums.js
@@ -0,0 +1,12 @@
(function() {
function vendorModule() {
'use strict';

return {
'default': self['codeHighlightLinenums'],
__esModule: true,
};
}

define('code-highlight-linenums', [], vendorModule);
})();
4 changes: 4 additions & 0 deletions yarn.lock
Expand Up @@ -76,6 +76,10 @@ can-symlink@^1.0.0:
dependencies:
tmp "0.0.28"

code-highlight-linenums@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/code-highlight-linenums/-/code-highlight-linenums-0.2.1.tgz#9519151404fdaa34df0b8fb809c0e2ee2913ce0e"

concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
Expand Down