├── .gitignore ├── LICENSE ├── README.md ├── img └── Contract-example.png ├── package.json └── src ├── ContractViewer.js ├── Highlight.js └── highlight.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Daniel Novy 2 | All rights reserved. 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of highlight.js nor the names of its contributors 12 | may be used to endorse or promote products derived from this software 13 | without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY 16 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solidity Contract Viewer 2 | 3 | This module simplifies the process of showing a Solidity Contract in a webpage 4 | beautified with syntax highlight and code indentation. Dynamic changes on the 5 | underline contract properties are reflected imediatly in the contract code shown 6 | on the webpage. 7 | 8 | It uses a modified version of [highlight-js](https://github.com/isagalaev/highlight.js) 9 | adapted to handle solidity code. 10 | 11 | As in the highlight-js library, different styles can be applied by simple adding 12 | the desired style in the main html file. Styles can be downloaded from 13 | [here](https://github.com/isagalaev/highlight.js/tree/master/src/styles). 14 | 15 | # IMPORTANT WARNING 16 | 17 | Because of a limitation in React, the react version used in this library (0.12.2) should 18 | matches the version used in your project! Otherwise, you will get the message below 19 | when running your code: 20 | 21 | ``` 22 | EventPluginRegistry: Cannot inject event plugin ordering more than once. You are likely 23 | trying to load more than one copy of React. 24 | ``` 25 | 26 | As the message says, if you run two distinct version for your code and for this library, 27 | the React library will be loaded twice in your app and you will get the message above. 28 | 29 | # Install 30 | 31 | To install this module globally, just type `sudo npm install contract-viewer -g`. 32 | 33 | # Usage example 34 | 35 | This module works using contract templates. You basically have define a contract 36 | template as a js module, as in: 37 | 38 | ```javascript 39 | var mycontracttemplate = '\n\ 40 | contract ui_contract_name {\n\ 41 | bytes32 ui_string1_name = "ui_string1_value";\n\ 42 | function ui_function1_name(bytes32 value) {\n\ 43 | ui_string1_name = value;\n\ 44 | }\n\ 45 | }'; 46 | module.exports = mycontracttemplate; 47 | ``` 48 | 49 | After defining your contract template, you `require` it in the react component 50 | where you are using the contractviewer. You will notice that there are some placeholders 51 | in the contract template. In our case, all words starting with `ui_` is a placeholder. 52 | these are the dynamic parts of the contract. When you use the contractviewer, you 53 | link these placeholders using `state` variables of the react component so that whenever 54 | the state change, the contract is automatically updated in the webpage. See example 55 | below: 56 | 57 | ```javascript 58 | var MyTemplate = require('../data/MyContractTemplate.sol.js'); 59 | var ContractViewer = require('contract-viewer'); 60 | 61 | var myapp = react.createclass({ 62 | getinitialstate: function() { 63 | return { 64 | contractName : 'MyContract', 65 | string1Name : 'customerName', 66 | string1Value : 'Satoshi Nakamoto', 67 | function1Name: 'setCustomerName' 68 | }; 69 | }, 70 | onChangeCode: function(contractCode) { 71 | // Result contract code is stored on param 'contractCode' 72 | }, 73 | render: function() { 74 | return 75 | ; 83 | } 84 | }); 85 | ``` 86 | 87 | To show the resulting contract code, you need to add the desired style in the 88 | html file where the code is shown. For instance, if you want to use the `railscasts` 89 | style, you should download it from 90 | [this place](https://github.com/isagalaev/highlight.js/tree/master/src/styles) 91 | and add it to the header section of your page, as in: 92 | 93 | ```html 94 | 95 | 96 | ... 97 | 98 | ``` 99 | 100 | The code above using the railscasts style will look like this: 101 | 102 | ![](https://github.com/consensys/contract-viewer/blob/master/img/Contract-example.png) 103 | 104 | So, any changes in the state variables of the React component will be reflected imediatly in 105 | the contract being shown. 106 | 107 | -------------------------------------------------------------------------------- /img/Contract-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConsenSysMesh/contract-viewer/50f09833028a39a0f202d669f1ae5604316bf04f/img/Contract-example.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "contract-viewer", 3 | "description": "A solidity contract viewer with syntax highlight and beautify", 4 | "version": "0.1.11", 5 | "author": "Daniel Novy", 6 | "main": "./src/ContractViewer.js", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/consensys/contract-viewer.git" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/consensys/contract-viewer/issues" 13 | }, 14 | "browserify": { 15 | "transform": [ 16 | "reactify" 17 | ] 18 | }, 19 | "dependencies": { 20 | "browserify": "11.0.1", 21 | "js-beautify": "^1.5.10", 22 | "react": "^0.14.3", 23 | "reactify": "1.1.1" 24 | }, 25 | "keywords": [ 26 | "solidity", 27 | "contract", 28 | "react", 29 | "hightlight" 30 | ], 31 | "license": "BSD-3-Clause" 32 | } 33 | -------------------------------------------------------------------------------- /src/ContractViewer.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var React = require('react'); 4 | var Beautify = require('js-beautify').js_beautify; 5 | var Highlight = require('./Highlight'); 6 | 7 | var ContractViewer = React.createClass({ 8 | 9 | doReplace: function(contractCode) { 10 | var result = contractCode; 11 | for (var property in this.props) { 12 | if (this.props.hasOwnProperty(property)) { 13 | if (property != 'contract' && property != 'onChange') { 14 | var reg = new RegExp(property, 'g'); 15 | result = result.replace(reg, this.props[property]); 16 | } 17 | } 18 | } 19 | return result; 20 | }, 21 | 22 | render: function() { 23 | var originContract = this.props.contract; 24 | var replacedContract = this.doReplace(originContract); 25 | var beautified = Beautify(replacedContract); 26 | if (typeof this.props.onChange != 'undefined') { 27 | this.props.onChange(beautified); 28 | } 29 | return {beautified}; 30 | } 31 | 32 | }); 33 | 34 | module.exports = ContractViewer; 35 | -------------------------------------------------------------------------------- /src/Highlight.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var React = require('react'); 4 | var ReactDOM = require('react-dom'); 5 | var hljs = require('./highlight.js'); 6 | 7 | var Highlight = React.createClass({ 8 | 9 | getDefaultProps: function () { 10 | return { 11 | className: "" 12 | }; 13 | }, 14 | 15 | componentDidMount: function () { 16 | this.highlightCode(); 17 | }, 18 | 19 | componentDidUpdate: function () { 20 | this.highlightCode(); 21 | }, 22 | 23 | highlightCode: function () { 24 | var domNode = ReactDOM.findDOMNode(this); 25 | var nodes = domNode.querySelectorAll('pre code'); 26 | if (nodes.length > 0) { 27 | for (var i = 0; i < nodes.length; i=i+1) { 28 | hljs.highlightBlock(nodes[i]); 29 | } 30 | } 31 | }, 32 | 33 | render: function () { 34 | return
{this.props.children}
; 35 | } 36 | }); 37 | 38 | module.exports = Highlight; 39 | -------------------------------------------------------------------------------- /src/highlight.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | highlight-js library pack 4 | 5 | Copyright (c) 2006, Ivan Sagalaev 6 | All rights reserved. 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | * Neither the name of highlight.js nor the names of its contributors 16 | may be used to endorse or promote products derived from this software 17 | without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY 20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY 23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | */ 31 | 32 | (function(factory) { 33 | 34 | // Setup highlight.js for different environments. First is Node.js or 35 | // CommonJS. 36 | if(typeof exports !== 'undefined') { 37 | factory(exports); 38 | } else { 39 | // Export hljs globally even when using AMD for cases when this script 40 | // is loaded with others that may still expect a global hljs. 41 | window.hljs = factory({}); 42 | 43 | // Finally register the global hljs with AMD. 44 | if(typeof define === 'function' && define.amd) { 45 | define('hljs', [], function() { 46 | return window.hljs; 47 | }); 48 | } 49 | } 50 | 51 | }(function(hljs) { 52 | 53 | /* Utility functions */ 54 | 55 | function escape(value) { 56 | return value.replace(/&/gm, '&').replace(//gm, '>'); 57 | } 58 | 59 | function tag(node) { 60 | return node.nodeName.toLowerCase(); 61 | } 62 | 63 | function testRe(re, lexeme) { 64 | var match = re && re.exec(lexeme); 65 | return match && match.index == 0; 66 | } 67 | 68 | function isNotHighlighted(language) { 69 | return /^(no-?highlight|plain|text)$/.test(language); 70 | } 71 | 72 | function blockLanguage(block) { 73 | var i, match, length, 74 | classes = block.className + ' '; 75 | 76 | classes += block.parentNode ? block.parentNode.className : ''; 77 | 78 | // language-* takes precedence over non-prefixed class names and 79 | match = /\blang(?:uage)?-([\w-]+)\b/.exec(classes); 80 | if (match) { 81 | return getLanguage(match[1]) ? match[1] : 'no-highlight'; 82 | } 83 | 84 | classes = classes.split(/\s+/); 85 | for(i = 0, length = classes.length; i < length; i++) { 86 | if(getLanguage(classes[i]) || isNotHighlighted(classes[i])) { 87 | return classes[i]; 88 | } 89 | } 90 | 91 | } 92 | 93 | function inherit(parent, obj) { 94 | var result = {}, key; 95 | for (key in parent) 96 | result[key] = parent[key]; 97 | if (obj) 98 | for (key in obj) 99 | result[key] = obj[key]; 100 | return result; 101 | } 102 | 103 | /* Stream merging */ 104 | 105 | function nodeStream(node) { 106 | var result = []; 107 | (function _nodeStream(node, offset) { 108 | for (var child = node.firstChild; child; child = child.nextSibling) { 109 | if (child.nodeType == 3) 110 | offset += child.nodeValue.length; 111 | else if (child.nodeType == 1) { 112 | result.push({ 113 | event: 'start', 114 | offset: offset, 115 | node: child 116 | }); 117 | offset = _nodeStream(child, offset); 118 | // Prevent void elements from having an end tag that would actually 119 | // double them in the output. There are more void elements in HTML 120 | // but we list only those realistically expected in code display. 121 | if (!tag(child).match(/br|hr|img|input/)) { 122 | result.push({ 123 | event: 'stop', 124 | offset: offset, 125 | node: child 126 | }); 127 | } 128 | } 129 | } 130 | return offset; 131 | })(node, 0); 132 | return result; 133 | } 134 | 135 | function mergeStreams(original, highlighted, value) { 136 | var processed = 0; 137 | var result = ''; 138 | var nodeStack = []; 139 | 140 | function selectStream() { 141 | if (!original.length || !highlighted.length) { 142 | return original.length ? original : highlighted; 143 | } 144 | if (original[0].offset != highlighted[0].offset) { 145 | return (original[0].offset < highlighted[0].offset) ? original : highlighted; 146 | } 147 | 148 | /* 149 | To avoid starting the stream just before it should stop the order is 150 | ensured that original always starts first and closes last: 151 | 152 | if (event1 == 'start' && event2 == 'start') 153 | return original; 154 | if (event1 == 'start' && event2 == 'stop') 155 | return highlighted; 156 | if (event1 == 'stop' && event2 == 'start') 157 | return original; 158 | if (event1 == 'stop' && event2 == 'stop') 159 | return highlighted; 160 | 161 | ... which is collapsed to: 162 | */ 163 | return highlighted[0].event == 'start' ? original : highlighted; 164 | } 165 | 166 | function open(node) { 167 | function attr_str(a) {return ' ' + a.nodeName + '="' + escape(a.value) + '"';} 168 | result += '<' + tag(node) + Array.prototype.map.call(node.attributes, attr_str).join('') + '>'; 169 | } 170 | 171 | function close(node) { 172 | result += ''; 173 | } 174 | 175 | function render(event) { 176 | (event.event == 'start' ? open : close)(event.node); 177 | } 178 | 179 | while (original.length || highlighted.length) { 180 | var stream = selectStream(); 181 | result += escape(value.substr(processed, stream[0].offset - processed)); 182 | processed = stream[0].offset; 183 | if (stream == original) { 184 | /* 185 | On any opening or closing tag of the original markup we first close 186 | the entire highlighted node stack, then render the original tag along 187 | with all the following original tags at the same offset and then 188 | reopen all the tags on the highlighted stack. 189 | */ 190 | nodeStack.reverse().forEach(close); 191 | do { 192 | render(stream.splice(0, 1)[0]); 193 | stream = selectStream(); 194 | } while (stream == original && stream.length && stream[0].offset == processed); 195 | nodeStack.reverse().forEach(open); 196 | } else { 197 | if (stream[0].event == 'start') { 198 | nodeStack.push(stream[0].node); 199 | } else { 200 | nodeStack.pop(); 201 | } 202 | render(stream.splice(0, 1)[0]); 203 | } 204 | } 205 | return result + escape(value.substr(processed)); 206 | } 207 | 208 | /* Initialization */ 209 | 210 | function compileLanguage(language) { 211 | 212 | function reStr(re) { 213 | return (re && re.source) || re; 214 | } 215 | 216 | function langRe(value, global) { 217 | return new RegExp( 218 | reStr(value), 219 | 'm' + (language.case_insensitive ? 'i' : '') + (global ? 'g' : '') 220 | ); 221 | } 222 | 223 | function compileMode(mode, parent) { 224 | if (mode.compiled) 225 | return; 226 | mode.compiled = true; 227 | 228 | mode.keywords = mode.keywords || mode.beginKeywords; 229 | if (mode.keywords) { 230 | var compiled_keywords = {}; 231 | 232 | var flatten = function(className, str) { 233 | if (language.case_insensitive) { 234 | str = str.toLowerCase(); 235 | } 236 | str.split(' ').forEach(function(kw) { 237 | var pair = kw.split('|'); 238 | compiled_keywords[pair[0]] = [className, pair[1] ? Number(pair[1]) : 1]; 239 | }); 240 | }; 241 | 242 | if (typeof mode.keywords == 'string') { // string 243 | flatten('keyword', mode.keywords); 244 | } else { 245 | Object.keys(mode.keywords).forEach(function (className) { 246 | flatten(className, mode.keywords[className]); 247 | }); 248 | } 249 | mode.keywords = compiled_keywords; 250 | } 251 | mode.lexemesRe = langRe(mode.lexemes || /\b\w+\b/, true); 252 | 253 | if (parent) { 254 | if (mode.beginKeywords) { 255 | mode.begin = '\\b(' + mode.beginKeywords.split(' ').join('|') + ')\\b'; 256 | } 257 | if (!mode.begin) 258 | mode.begin = /\B|\b/; 259 | mode.beginRe = langRe(mode.begin); 260 | if (!mode.end && !mode.endsWithParent) 261 | mode.end = /\B|\b/; 262 | if (mode.end) 263 | mode.endRe = langRe(mode.end); 264 | mode.terminator_end = reStr(mode.end) || ''; 265 | if (mode.endsWithParent && parent.terminator_end) 266 | mode.terminator_end += (mode.end ? '|' : '') + parent.terminator_end; 267 | } 268 | if (mode.illegal) 269 | mode.illegalRe = langRe(mode.illegal); 270 | if (mode.relevance === undefined) 271 | mode.relevance = 1; 272 | if (!mode.contains) { 273 | mode.contains = []; 274 | } 275 | var expanded_contains = []; 276 | mode.contains.forEach(function(c) { 277 | if (c.variants) { 278 | c.variants.forEach(function(v) {expanded_contains.push(inherit(c, v));}); 279 | } else { 280 | expanded_contains.push(c == 'self' ? mode : c); 281 | } 282 | }); 283 | mode.contains = expanded_contains; 284 | mode.contains.forEach(function(c) {compileMode(c, mode);}); 285 | 286 | if (mode.starts) { 287 | compileMode(mode.starts, parent); 288 | } 289 | 290 | var terminators = 291 | mode.contains.map(function(c) { 292 | return c.beginKeywords ? '\\.?(' + c.begin + ')\\.?' : c.begin; 293 | }) 294 | .concat([mode.terminator_end, mode.illegal]) 295 | .map(reStr) 296 | .filter(Boolean); 297 | mode.terminators = terminators.length ? langRe(terminators.join('|'), true) : {exec: function(/*s*/) {return null;}}; 298 | } 299 | 300 | compileMode(language); 301 | } 302 | 303 | /* 304 | Core highlighting function. Accepts a language name, or an alias, and a 305 | string with the code to highlight. Returns an object with the following 306 | properties: 307 | 308 | - relevance (int) 309 | - value (an HTML string with highlighting markup) 310 | 311 | */ 312 | function highlight(name, value, ignore_illegals, continuation) { 313 | 314 | function subMode(lexeme, mode) { 315 | for (var i = 0; i < mode.contains.length; i++) { 316 | if (testRe(mode.contains[i].beginRe, lexeme)) { 317 | return mode.contains[i]; 318 | } 319 | } 320 | } 321 | 322 | function endOfMode(mode, lexeme) { 323 | if (testRe(mode.endRe, lexeme)) { 324 | while (mode.endsParent && mode.parent) { 325 | mode = mode.parent; 326 | } 327 | return mode; 328 | } 329 | if (mode.endsWithParent) { 330 | return endOfMode(mode.parent, lexeme); 331 | } 332 | } 333 | 334 | function isIllegal(lexeme, mode) { 335 | return !ignore_illegals && testRe(mode.illegalRe, lexeme); 336 | } 337 | 338 | function keywordMatch(mode, match) { 339 | var match_str = language.case_insensitive ? match[0].toLowerCase() : match[0]; 340 | return mode.keywords.hasOwnProperty(match_str) && mode.keywords[match_str]; 341 | } 342 | 343 | function buildSpan(classname, insideSpan, leaveOpen, noPrefix) { 344 | var classPrefix = noPrefix ? '' : options.classPrefix, 345 | openSpan = ''; 349 | 350 | return openSpan + insideSpan + closeSpan; 351 | } 352 | 353 | function processKeywords() { 354 | if (!top.keywords) 355 | return escape(mode_buffer); 356 | var result = ''; 357 | var last_index = 0; 358 | top.lexemesRe.lastIndex = 0; 359 | var match = top.lexemesRe.exec(mode_buffer); 360 | while (match) { 361 | result += escape(mode_buffer.substr(last_index, match.index - last_index)); 362 | var keyword_match = keywordMatch(top, match); 363 | if (keyword_match) { 364 | relevance += keyword_match[1]; 365 | result += buildSpan(keyword_match[0], escape(match[0])); 366 | } else { 367 | result += escape(match[0]); 368 | } 369 | last_index = top.lexemesRe.lastIndex; 370 | match = top.lexemesRe.exec(mode_buffer); 371 | } 372 | return result + escape(mode_buffer.substr(last_index)); 373 | } 374 | 375 | function processSubLanguage() { 376 | var explicit = typeof top.subLanguage == 'string'; 377 | if (explicit && !languages[top.subLanguage]) { 378 | return escape(mode_buffer); 379 | } 380 | 381 | var result = explicit ? 382 | highlight(top.subLanguage, mode_buffer, true, continuations[top.subLanguage]) : 383 | highlightAuto(mode_buffer, top.subLanguage.length ? top.subLanguage : undefined); 384 | 385 | // Counting embedded language score towards the host language may be disabled 386 | // with zeroing the containing mode relevance. Usecase in point is Markdown that 387 | // allows XML everywhere and makes every XML snippet to have a much larger Markdown 388 | // score. 389 | if (top.relevance > 0) { 390 | relevance += result.relevance; 391 | } 392 | if (explicit) { 393 | continuations[top.subLanguage] = result.top; 394 | } 395 | return buildSpan(result.language, result.value, false, true); 396 | } 397 | 398 | function processBuffer() { 399 | return top.subLanguage !== undefined ? processSubLanguage() : processKeywords(); 400 | } 401 | 402 | function startNewMode(mode, lexeme) { 403 | var markup = mode.className? buildSpan(mode.className, '', true): ''; 404 | if (mode.returnBegin) { 405 | result += markup; 406 | mode_buffer = ''; 407 | } else if (mode.excludeBegin) { 408 | result += escape(lexeme) + markup; 409 | mode_buffer = ''; 410 | } else { 411 | result += markup; 412 | mode_buffer = lexeme; 413 | } 414 | top = Object.create(mode, {parent: {value: top}}); 415 | } 416 | 417 | function processLexeme(buffer, lexeme) { 418 | 419 | mode_buffer += buffer; 420 | if (lexeme === undefined) { 421 | result += processBuffer(); 422 | return 0; 423 | } 424 | 425 | var new_mode = subMode(lexeme, top); 426 | if (new_mode) { 427 | result += processBuffer(); 428 | startNewMode(new_mode, lexeme); 429 | return new_mode.returnBegin ? 0 : lexeme.length; 430 | } 431 | 432 | var end_mode = endOfMode(top, lexeme); 433 | if (end_mode) { 434 | var origin = top; 435 | if (!(origin.returnEnd || origin.excludeEnd)) { 436 | mode_buffer += lexeme; 437 | } 438 | result += processBuffer(); 439 | do { 440 | if (top.className) { 441 | result += ''; 442 | } 443 | relevance += top.relevance; 444 | top = top.parent; 445 | } while (top != end_mode.parent); 446 | if (origin.excludeEnd) { 447 | result += escape(lexeme); 448 | } 449 | mode_buffer = ''; 450 | if (end_mode.starts) { 451 | startNewMode(end_mode.starts, ''); 452 | } 453 | return origin.returnEnd ? 0 : lexeme.length; 454 | } 455 | 456 | if (isIllegal(lexeme, top)) 457 | throw new Error('Illegal lexeme "' + lexeme + '" for mode "' + (top.className || '') + '"'); 458 | 459 | /* 460 | Parser should not reach this point as all types of lexemes should be caught 461 | earlier, but if it does due to some bug make sure it advances at least one 462 | character forward to prevent infinite looping. 463 | */ 464 | mode_buffer += lexeme; 465 | return lexeme.length || 1; 466 | } 467 | 468 | var language = getLanguage(name); 469 | if (!language) { 470 | throw new Error('Unknown language: "' + name + '"'); 471 | } 472 | 473 | compileLanguage(language); 474 | var top = continuation || language; 475 | var continuations = {}; // keep continuations for sub-languages 476 | var result = '', current; 477 | for(current = top; current != language; current = current.parent) { 478 | if (current.className) { 479 | result = buildSpan(current.className, '', true) + result; 480 | } 481 | } 482 | var mode_buffer = ''; 483 | var relevance = 0; 484 | try { 485 | var match, count, index = 0; 486 | while (true) { 487 | top.terminators.lastIndex = index; 488 | match = top.terminators.exec(value); 489 | if (!match) 490 | break; 491 | count = processLexeme(value.substr(index, match.index - index), match[0]); 492 | index = match.index + count; 493 | } 494 | processLexeme(value.substr(index)); 495 | for(current = top; current.parent; current = current.parent) { // close dangling modes 496 | if (current.className) { 497 | result += ''; 498 | } 499 | } 500 | return { 501 | relevance: relevance, 502 | value: result, 503 | language: name, 504 | top: top 505 | }; 506 | } catch (e) { 507 | if (e.message.indexOf('Illegal') != -1) { 508 | return { 509 | relevance: 0, 510 | value: escape(value) 511 | }; 512 | } else { 513 | throw e; 514 | } 515 | } 516 | } 517 | 518 | /* 519 | Highlighting with language detection. Accepts a string with the code to 520 | highlight. Returns an object with the following properties: 521 | 522 | - language (detected language) 523 | - relevance (int) 524 | - value (an HTML string with highlighting markup) 525 | - second_best (object with the same structure for second-best heuristically 526 | detected language, may be absent) 527 | 528 | */ 529 | function highlightAuto(text, languageSubset) { 530 | languageSubset = languageSubset || options.languages || Object.keys(languages); 531 | var result = { 532 | relevance: 0, 533 | value: escape(text) 534 | }; 535 | var second_best = result; 536 | languageSubset.forEach(function(name) { 537 | if (!getLanguage(name)) { 538 | return; 539 | } 540 | var current = highlight(name, text, false); 541 | current.language = name; 542 | if (current.relevance > second_best.relevance) { 543 | second_best = current; 544 | } 545 | if (current.relevance > result.relevance) { 546 | second_best = result; 547 | result = current; 548 | } 549 | }); 550 | if (second_best.language) { 551 | result.second_best = second_best; 552 | } 553 | return result; 554 | } 555 | 556 | /* 557 | Post-processing of the highlighted markup: 558 | 559 | - replace TABs with something more useful 560 | - replace real line-breaks with '
' for non-pre containers 561 | 562 | */ 563 | function fixMarkup(value) { 564 | if (options.tabReplace) { 565 | value = value.replace(/^((<[^>]+>|\t)+)/gm, function(match, p1 /*..., offset, s*/) { 566 | return p1.replace(/\t/g, options.tabReplace); 567 | }); 568 | } 569 | if (options.useBR) { 570 | value = value.replace(/\n/g, '
'); 571 | } 572 | return value; 573 | } 574 | 575 | function buildClassName(prevClassName, currentLang, resultLang) { 576 | var language = currentLang ? aliases[currentLang] : resultLang, 577 | result = [prevClassName.trim()]; 578 | 579 | if (!prevClassName.match(/\bhljs\b/)) { 580 | result.push('hljs'); 581 | } 582 | 583 | if (prevClassName.indexOf(language) === -1) { 584 | result.push(language); 585 | } 586 | 587 | return result.join(' ').trim(); 588 | } 589 | 590 | /* 591 | Applies highlighting to a DOM node containing code. Accepts a DOM node and 592 | two optional parameters for fixMarkup. 593 | */ 594 | function highlightBlock(block) { 595 | var language = blockLanguage(block); 596 | if (isNotHighlighted(language)) 597 | return; 598 | 599 | var node; 600 | if (options.useBR) { 601 | node = document.createElementNS('http://www.w3.org/1999/xhtml', 'div'); 602 | node.innerHTML = block.innerHTML.replace(/\n/g, '').replace(//g, '\n'); 603 | } else { 604 | node = block; 605 | } 606 | var text = node.textContent; 607 | var result = language ? highlight(language, text, true) : highlightAuto(text); 608 | 609 | var originalStream = nodeStream(node); 610 | if (originalStream.length) { 611 | var resultNode = document.createElementNS('http://www.w3.org/1999/xhtml', 'div'); 612 | resultNode.innerHTML = result.value; 613 | result.value = mergeStreams(originalStream, nodeStream(resultNode), text); 614 | } 615 | result.value = fixMarkup(result.value); 616 | 617 | block.innerHTML = result.value; 618 | block.className = buildClassName(block.className, language, result.language); 619 | block.result = { 620 | language: result.language, 621 | re: result.relevance 622 | }; 623 | if (result.second_best) { 624 | block.second_best = { 625 | language: result.second_best.language, 626 | re: result.second_best.relevance 627 | }; 628 | } 629 | } 630 | 631 | var options = { 632 | classPrefix: 'hljs-', 633 | tabReplace: null, 634 | useBR: false, 635 | languages: undefined 636 | }; 637 | 638 | /* 639 | Updates highlight.js global options with values passed in the form of an object 640 | */ 641 | function configure(user_options) { 642 | options = inherit(options, user_options); 643 | } 644 | 645 | /* 646 | Applies highlighting to all
..
blocks on a page. 647 | */ 648 | function initHighlighting() { 649 | if (initHighlighting.called) 650 | return; 651 | initHighlighting.called = true; 652 | 653 | var blocks = document.querySelectorAll('pre code'); 654 | Array.prototype.forEach.call(blocks, highlightBlock); 655 | } 656 | 657 | /* 658 | Attaches highlighting to the page load event. 659 | */ 660 | function initHighlightingOnLoad() { 661 | addEventListener('DOMContentLoaded', initHighlighting, false); 662 | addEventListener('load', initHighlighting, false); 663 | } 664 | 665 | var languages = {}; 666 | var aliases = {}; 667 | 668 | function registerLanguage(name, language) { 669 | var lang = languages[name] = language(hljs); 670 | if (lang.aliases) { 671 | lang.aliases.forEach(function(alias) {aliases[alias] = name;}); 672 | } 673 | } 674 | 675 | function listLanguages() { 676 | return Object.keys(languages); 677 | } 678 | 679 | function getLanguage(name) { 680 | return languages[name] || languages[aliases[name]]; 681 | } 682 | 683 | /* Interface definition */ 684 | 685 | hljs.highlight = highlight; 686 | hljs.highlightAuto = highlightAuto; 687 | hljs.fixMarkup = fixMarkup; 688 | hljs.highlightBlock = highlightBlock; 689 | hljs.configure = configure; 690 | hljs.initHighlighting = initHighlighting; 691 | hljs.initHighlightingOnLoad = initHighlightingOnLoad; 692 | hljs.registerLanguage = registerLanguage; 693 | hljs.listLanguages = listLanguages; 694 | hljs.getLanguage = getLanguage; 695 | hljs.inherit = inherit; 696 | 697 | // Common regexps 698 | hljs.IDENT_RE = '[a-zA-Z]\\w*'; 699 | hljs.UNDERSCORE_IDENT_RE = '[a-zA-Z_]\\w*'; 700 | hljs.NUMBER_RE = '\\b\\d+(\\.\\d+)?'; 701 | hljs.C_NUMBER_RE = '(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)'; // 0x..., 0..., decimal, float 702 | hljs.BINARY_NUMBER_RE = '\\b(0b[01]+)'; // 0b... 703 | hljs.RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~'; 704 | 705 | // Common modes 706 | hljs.BACKSLASH_ESCAPE = { 707 | begin: '\\\\[\\s\\S]', relevance: 0 708 | }; 709 | hljs.APOS_STRING_MODE = { 710 | className: 'string', 711 | begin: '\'', end: '\'', 712 | illegal: '\\n', 713 | contains: [hljs.BACKSLASH_ESCAPE] 714 | }; 715 | hljs.QUOTE_STRING_MODE = { 716 | className: 'string', 717 | begin: '"', end: '"', 718 | illegal: '\\n', 719 | contains: [hljs.BACKSLASH_ESCAPE] 720 | }; 721 | hljs.PHRASAL_WORDS_MODE = { 722 | begin: /\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such)\b/ 723 | }; 724 | hljs.COMMENT = function (begin, end, inherits) { 725 | var mode = hljs.inherit( 726 | { 727 | className: 'comment', 728 | begin: begin, end: end, 729 | contains: [] 730 | }, 731 | inherits || {} 732 | ); 733 | mode.contains.push(hljs.PHRASAL_WORDS_MODE); 734 | mode.contains.push({ 735 | className: 'doctag', 736 | begin: "(?:TODO|FIXME|NOTE|BUG|XXX):", 737 | relevance: 0 738 | }); 739 | return mode; 740 | }; 741 | hljs.C_LINE_COMMENT_MODE = hljs.COMMENT('//', '$'); 742 | hljs.C_BLOCK_COMMENT_MODE = hljs.COMMENT('/\\*', '\\*/'); 743 | hljs.HASH_COMMENT_MODE = hljs.COMMENT('#', '$'); 744 | hljs.NUMBER_MODE = { 745 | className: 'number', 746 | begin: hljs.NUMBER_RE, 747 | relevance: 0 748 | }; 749 | hljs.C_NUMBER_MODE = { 750 | className: 'number', 751 | begin: hljs.C_NUMBER_RE, 752 | relevance: 0 753 | }; 754 | hljs.BINARY_NUMBER_MODE = { 755 | className: 'number', 756 | begin: hljs.BINARY_NUMBER_RE, 757 | relevance: 0 758 | }; 759 | hljs.CSS_NUMBER_MODE = { 760 | className: 'number', 761 | begin: hljs.NUMBER_RE + '(' + 762 | '%|em|ex|ch|rem' + 763 | '|vw|vh|vmin|vmax' + 764 | '|cm|mm|in|pt|pc|px' + 765 | '|deg|grad|rad|turn' + 766 | '|s|ms' + 767 | '|Hz|kHz' + 768 | '|dpi|dpcm|dppx' + 769 | ')?', 770 | relevance: 0 771 | }; 772 | hljs.REGEXP_MODE = { 773 | className: 'regexp', 774 | begin: /\//, end: /\/[gimuy]*/, 775 | illegal: /\n/, 776 | contains: [ 777 | hljs.BACKSLASH_ESCAPE, 778 | { 779 | begin: /\[/, end: /\]/, 780 | relevance: 0, 781 | contains: [hljs.BACKSLASH_ESCAPE] 782 | } 783 | ] 784 | }; 785 | hljs.TITLE_MODE = { 786 | className: 'title', 787 | begin: hljs.IDENT_RE, 788 | relevance: 0 789 | }; 790 | hljs.UNDERSCORE_TITLE_MODE = { 791 | className: 'title', 792 | begin: hljs.UNDERSCORE_IDENT_RE, 793 | relevance: 0 794 | }; 795 | 796 | hljs.registerLanguage('solidity', function(hljs) { 797 | return { 798 | aliases: ['sol'], 799 | keywords: { 800 | keyword: 801 | 'in of if for while finally var new function do return void else break catch ' + 802 | 'instanceof with throw case default try this switch continue typeof delete ' + 803 | 'let yield const export super debugger as async await ' + 804 | 'mapping address struct internal public returns private ' + 805 | 'string uint uint8 uint16 uint32 uint64 uint128 ' + 806 | 'bytes4 bytes8 bytes16 bytes32 bytes64 bytes128 ' + 807 | 'now block timestamp coinbase difficulty gaslimit number blockhash gas msg.sender ' + 808 | 'gasprice origin contract this is modifier', 809 | literal: 810 | 'true false null undefined NaN Infinity', 811 | built_in: 812 | 'eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent ' + 813 | 'encodeURI encodeURIComponent escape unescape Object Function Boolean Error ' + 814 | 'EvalError InternalError RangeError ReferenceError StopIteration SyntaxError ' + 815 | 'TypeError URIError Number Math Date String RegExp Array Float32Array ' + 816 | 'Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array ' + 817 | 'Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require ' + 818 | 'module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect ' + 819 | 'Promise ' + 820 | 'sha3 sha256 ripemd160 ecrecover suicide call callcode ' 821 | }, 822 | contains: [ 823 | { 824 | className: 'pi', 825 | relevance: 10, 826 | begin: /^\s*['"]use (strict|asm)['"]/ 827 | }, 828 | hljs.APOS_STRING_MODE, 829 | hljs.QUOTE_STRING_MODE, 830 | { // template string 831 | className: 'string', 832 | begin: '`', end: '`', 833 | contains: [ 834 | hljs.BACKSLASH_ESCAPE, 835 | { 836 | className: 'subst', 837 | begin: '\\$\\{', end: '\\}' 838 | } 839 | ] 840 | }, 841 | hljs.C_LINE_COMMENT_MODE, 842 | hljs.C_BLOCK_COMMENT_MODE, 843 | { 844 | className: 'number', 845 | variants: [ 846 | { begin: '\\b(0[bB][01]+)' }, 847 | { begin: '\\b(0[oO][0-7]+)' }, 848 | { begin: hljs.C_NUMBER_RE } 849 | ], 850 | relevance: 0 851 | }, 852 | { // "value" container 853 | begin: '(' + hljs.RE_STARTERS_RE + '|\\b(case|return|throw)\\b)\\s*', 854 | keywords: 'return throw case', 855 | contains: [ 856 | hljs.C_LINE_COMMENT_MODE, 857 | hljs.C_BLOCK_COMMENT_MODE, 858 | hljs.REGEXP_MODE, 859 | { // E4X / JSX 860 | begin: /\s*[);\]]/, 861 | relevance: 0, 862 | subLanguage: 'xml' 863 | } 864 | ], 865 | relevance: 0 866 | }, 867 | { 868 | className: 'function', 869 | beginKeywords: 'function contract struct modifier', end: /\{/, excludeEnd: true, 870 | contains: [ 871 | hljs.inherit(hljs.TITLE_MODE, {begin: /[A-Za-z$_][0-9A-Za-z$_]*/}), 872 | { 873 | className: 'params', 874 | begin: /\(/, end: /\)/, 875 | excludeBegin: true, 876 | excludeEnd: true, 877 | contains: [ 878 | hljs.C_LINE_COMMENT_MODE, 879 | hljs.C_BLOCK_COMMENT_MODE 880 | ] 881 | } 882 | ], 883 | illegal: /\[|%/ 884 | }, 885 | { 886 | begin: /\$[(.]/ // relevance booster for a pattern common to JS libs: `$(something)` and `$.something` 887 | }, 888 | { 889 | begin: '\\.' + hljs.IDENT_RE, relevance: 0 // hack: prevents detection of keywords after dots 890 | }, 891 | // ECMAScript 6 modules import 892 | { 893 | beginKeywords: 'import', end: '[;$]', 894 | keywords: 'import from as', 895 | contains: [ 896 | hljs.APOS_STRING_MODE, 897 | hljs.QUOTE_STRING_MODE 898 | ] 899 | }, 900 | { // ES6 class 901 | className: 'class', 902 | beginKeywords: 'class', end: /[{;=]/, excludeEnd: true, 903 | illegal: /[:"\[\]]/, 904 | contains: [ 905 | {beginKeywords: 'extends'}, 906 | hljs.UNDERSCORE_TITLE_MODE 907 | ] 908 | } 909 | ], 910 | illegal: /#/ 911 | }; 912 | }); 913 | 914 | return hljs; 915 | })); 916 | --------------------------------------------------------------------------------