summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorsyuilo <syuilotan@yahoo.co.jp>2017-02-12 02:38:47 +0900
committersyuilo <syuilotan@yahoo.co.jp>2017-02-12 02:38:47 +0900
commite19899962edfa9005e1f8fdbc56b1c8e7982864c (patch)
tree2d034dbf6cfbce48e01760a09230735dbf5c9841 /src
parent[Client] Use JSON for API requests (diff)
downloadsharkey-e19899962edfa9005e1f8fdbc56b1c8e7982864c.tar.gz
sharkey-e19899962edfa9005e1f8fdbc56b1c8e7982864c.tar.bz2
sharkey-e19899962edfa9005e1f8fdbc56b1c8e7982864c.zip
インラインのコードでもシンタックスハイライトを有効化
Diffstat (limited to 'src')
-rw-r--r--src/common/text/core/syntax-highlighter.js331
-rw-r--r--src/common/text/elements/code.js338
-rw-r--r--src/common/text/elements/inline-code.js5
-rw-r--r--src/web/app/base.styl72
-rw-r--r--src/web/app/common/scripts/text-compiler.js4
5 files changed, 377 insertions, 373 deletions
diff --git a/src/common/text/core/syntax-highlighter.js b/src/common/text/core/syntax-highlighter.js
new file mode 100644
index 0000000000..61237daf62
--- /dev/null
+++ b/src/common/text/core/syntax-highlighter.js
@@ -0,0 +1,331 @@
+function escape(text) {
+ return text
+ .replace(/>/g, '&gt;')
+ .replace(/</g, '&lt;');
+}
+
+// 文字数が多い順にソートします
+// そうしないと、「function」という文字列が与えられたときに「func」が先にマッチしてしまう可能性があるためです
+const _keywords = [
+ 'true',
+ 'false',
+ 'null',
+ 'nil',
+ 'undefined',
+ 'void',
+ 'var',
+ 'const',
+ 'let',
+ 'mut',
+ 'dim',
+ 'if',
+ 'then',
+ 'else',
+ 'switch',
+ 'match',
+ 'case',
+ 'default',
+ 'for',
+ 'each',
+ 'in',
+ 'while',
+ 'loop',
+ 'continue',
+ 'break',
+ 'do',
+ 'goto',
+ 'next',
+ 'end',
+ 'sub',
+ 'throw',
+ 'try',
+ 'catch',
+ 'finally',
+ 'enum',
+ 'delegate',
+ 'function',
+ 'func',
+ 'fun',
+ 'fn',
+ 'return',
+ 'yield',
+ 'async',
+ 'await',
+ 'require',
+ 'include',
+ 'import',
+ 'imports',
+ 'export',
+ 'exports',
+ 'from',
+ 'as',
+ 'using',
+ 'use',
+ 'internal',
+ 'module',
+ 'namespace',
+ 'where',
+ 'select',
+ 'struct',
+ 'union',
+ 'new',
+ 'delete',
+ 'this',
+ 'super',
+ 'base',
+ 'class',
+ 'interface',
+ 'abstract',
+ 'static',
+ 'public',
+ 'private',
+ 'protected',
+ 'virtual',
+ 'partial',
+ 'override',
+ 'extends',
+ 'implements',
+ 'constructor'
+];
+
+const keywords = _keywords
+ .concat(_keywords.map(k => k[0].toUpperCase() + k.substr(1)))
+ .concat(_keywords.map(k => k.toUpperCase()))
+ .sort((a, b) => b.length - a.length);
+
+const symbols = [
+ '=',
+ '+',
+ '-',
+ '*',
+ '/',
+ '%',
+ '~',
+ '^',
+ '&',
+ '|',
+ '>',
+ '<',
+ '!',
+ '?'
+];
+
+const elements = [
+ // comment
+ code => {
+ if (code.substr(0, 2) != '//') return null;
+ const match = code.match(/^\/\/(.+?)\n/);
+ if (!match) return null;
+ const comment = match[0];
+ return {
+ html: `<span class="comment">${escape(comment)}</span>`,
+ next: comment.length
+ };
+ },
+
+ // block comment
+ code => {
+ const match = code.match(/^\/\*([\s\S]+?)\*\//);
+ if (!match) return null;
+ return {
+ html: `<span class="comment">${escape(match[0])}</span>`,
+ next: match[0].length
+ };
+ },
+
+ // string
+ code => {
+ if (!/^['"`]/.test(code)) return null;
+ const begin = code[0];
+ let str = begin;
+ let thisIsNotAString = false;
+ for (let i = 1; i < code.length; i++) {
+ const char = code[i];
+ if (char == '\\') {
+ str += char;
+ str += code[i + 1] || '';
+ i++;
+ continue;
+ } else if (char == begin) {
+ str += char;
+ break;
+ } else if (char == '\n' || i == (code.length - 1)) {
+ thisIsNotAString = true;
+ break;
+ } else {
+ str += char;
+ }
+ }
+ if (thisIsNotAString) {
+ return null;
+ } else {
+ return {
+ html: `<span class="string">${escape(str)}</span>`,
+ next: str.length
+ };
+ }
+ },
+
+ // regexp
+ code => {
+ if (code[0] != '/') return null;
+ let regexp = '';
+ let thisIsNotARegexp = false;
+ for (let i = 1; i < code.length; i++) {
+ const char = code[i];
+ if (char == '\\') {
+ regexp += char;
+ regexp += code[i + 1] || '';
+ i++;
+ continue;
+ } else if (char == '/') {
+ break;
+ } else if (char == '\n' || i == (code.length - 1)) {
+ thisIsNotARegexp = true;
+ break;
+ } else {
+ regexp += char;
+ }
+ }
+
+ if (thisIsNotARegexp) return null;
+ if (regexp[0] == ' ' && regexp[regexp.length - 1] == ' ') return null;
+
+ return {
+ html: `<span class="regexp">/${escape(regexp)}/</span>`,
+ next: regexp.length + 2
+ };
+ },
+
+ // label
+ code => {
+ if (code[0] != '@') return null;
+ const match = code.match(/^@([a-zA-Z_-]+?)\n/);
+ if (!match) return null;
+ const label = match[0];
+ return {
+ html: `<span class="label">${label}</span>`,
+ next: label.length
+ };
+ },
+
+ // number
+ (code, i, source) => {
+ const prev = source[i - 1];
+ if (prev && /[a-zA-Z]/.test(prev)) return null;
+ if (!/^[\-\+]?[0-9\.]+/.test(code)) return null;
+ const match = code.match(/^[\-\+]?[0-9\.]+/)[0];
+ if (match) {
+ return {
+ html: `<span class="number">${match}</span>`,
+ next: match.length
+ };
+ } else {
+ return null;
+ }
+ },
+
+ // nan
+ (code, i, source) => {
+ const prev = source[i - 1];
+ if (prev && /[a-zA-Z]/.test(prev)) return null;
+ if (code.substr(0, 3) == 'NaN') {
+ return {
+ html: `<span class="nan">NaN</span>`,
+ next: 3
+ };
+ } else {
+ return null;
+ }
+ },
+
+ // method
+ code => {
+ const match = code.match(/^([a-zA-Z_-]+?)\(/);
+ if (!match) return null;
+
+ if (match[1] == '-') return null;
+
+ return {
+ html: `<span class="method">${match[1]}</span>`,
+ next: match[1].length
+ };
+ },
+
+ // property
+ (code, i, source) => {
+ const prev = source[i - 1];
+ if (prev != '.') return null;
+
+ const match = code.match(/^[a-zA-Z0-9_-]+/);
+ if (!match) return null;
+
+ return {
+ html: `<span class="property">${match[0]}</span>`,
+ next: match[0].length
+ };
+ },
+
+ // keyword
+ (code, i, source) => {
+ const prev = source[i - 1];
+ if (prev && /[a-zA-Z]/.test(prev)) return null;
+
+ const match = keywords.filter(k => code.substr(0, k.length) == k)[0];
+ if (match) {
+ if (/^[a-zA-Z]/.test(code.substr(match.length))) return null;
+ return {
+ html: `<span class="keyword ${match}">${match}</span>`,
+ next: match.length
+ };
+ } else {
+ return null;
+ }
+ },
+
+ // symbol
+ code => {
+ const match = symbols.filter(s => code[0] == s)[0];
+ if (match) {
+ return {
+ html: `<span class="symbol">${match}</span>`,
+ next: 1
+ };
+ } else {
+ return null;
+ }
+ }
+];
+
+// specify lang is todo
+module.exports = (source, lang) => {
+ let code = source;
+ let html = '';
+
+ let i = 0;
+
+ function push(token) {
+ html += token.html;
+ code = code.substr(token.next);
+ i += token.next;
+ }
+
+ while (code != '') {
+ const parsed = elements.some(el => {
+ const e = el(code, i, source);
+ if (e) {
+ push(e);
+ return true;
+ }
+ });
+
+ if (!parsed) {
+ push({
+ html: escape(code[0]),
+ next: 1
+ });
+ }
+ }
+
+ return html;
+};
diff --git a/src/common/text/elements/code.js b/src/common/text/elements/code.js
index 902504c9ec..99fe6a183c 100644
--- a/src/common/text/elements/code.js
+++ b/src/common/text/elements/code.js
@@ -1,7 +1,9 @@
/**
- * Code
+ * Code (block)
*/
+const genHtml = require('../core/syntax-highlighter');
+
module.exports = text => {
const match = text.match(/^```([\s\S]+?)```/);
if (!match) return null;
@@ -10,338 +12,6 @@ module.exports = text => {
type: 'code',
content: code,
code: code.substr(3, code.length - 6).trim(),
- codeHtml: genHtml(code.substr(3, code.length - 6).trim())
+ html: genHtml(code.substr(3, code.length - 6).trim())
};
};
-
-function escape(text) {
- return text
- .replace(/>/g, '&gt;')
- .replace(/</g, '&lt;');
-}
-
-// 文字数が多い順にソートします
-// そうしないと、「function」という文字列が与えられたときに「func」が先にマッチしてしまう可能性があるためです
-const _keywords = [
- 'true',
- 'false',
- 'null',
- 'nil',
- 'undefined',
- 'void',
- 'var',
- 'const',
- 'let',
- 'mut',
- 'dim',
- 'if',
- 'then',
- 'else',
- 'switch',
- 'match',
- 'case',
- 'default',
- 'for',
- 'each',
- 'in',
- 'while',
- 'loop',
- 'continue',
- 'break',
- 'do',
- 'goto',
- 'next',
- 'end',
- 'sub',
- 'throw',
- 'try',
- 'catch',
- 'finally',
- 'enum',
- 'delegate',
- 'function',
- 'func',
- 'fun',
- 'fn',
- 'return',
- 'yield',
- 'async',
- 'await',
- 'require',
- 'include',
- 'import',
- 'imports',
- 'export',
- 'exports',
- 'from',
- 'as',
- 'using',
- 'use',
- 'internal',
- 'module',
- 'namespace',
- 'where',
- 'select',
- 'struct',
- 'union',
- 'new',
- 'delete',
- 'this',
- 'super',
- 'base',
- 'class',
- 'interface',
- 'abstract',
- 'static',
- 'public',
- 'private',
- 'protected',
- 'virtual',
- 'partial',
- 'override',
- 'extends',
- 'implements',
- 'constructor'
-];
-
-const keywords = _keywords
- .concat(_keywords.map(k => k[0].toUpperCase() + k.substr(1)))
- .concat(_keywords.map(k => k.toUpperCase()))
- .sort((a, b) => b.length - a.length);
-
-const symbols = [
- '=',
- '+',
- '-',
- '*',
- '/',
- '%',
- '~',
- '^',
- '&',
- '|',
- '>',
- '<',
- '!',
- '?'
-];
-
-const elements = [
- // comment
- code => {
- if (code.substr(0, 2) != '//') return null;
- const match = code.match(/^\/\/(.+?)\n/);
- if (!match) return null;
- const comment = match[0];
- return {
- html: `<span class="comment">${escape(comment)}</span>`,
- next: comment.length
- };
- },
-
- // block comment
- code => {
- const match = code.match(/^\/\*([\s\S]+?)\*\//);
- if (!match) return null;
- return {
- html: `<span class="comment">${escape(match[0])}</span>`,
- next: match[0].length
- };
- },
-
- // string
- code => {
- if (!/^['"`]/.test(code)) return null;
- const begin = code[0];
- let str = begin;
- let thisIsNotAString = false;
- for (let i = 1; i < code.length; i++) {
- const char = code[i];
- if (char == '\\') {
- str += char;
- str += code[i + 1] || '';
- i++;
- continue;
- } else if (char == begin) {
- str += char;
- break;
- } else if (char == '\n' || i == (code.length - 1)) {
- thisIsNotAString = true;
- break;
- } else {
- str += char;
- }
- }
- if (thisIsNotAString) {
- return null;
- } else {
- return {
- html: `<span class="string">${escape(str)}</span>`,
- next: str.length
- };
- }
- },
-
- // regexp
- code => {
- if (code[0] != '/') return null;
- let regexp = '';
- let thisIsNotARegexp = false;
- for (let i = 1; i < code.length; i++) {
- const char = code[i];
- if (char == '\\') {
- regexp += char;
- regexp += code[i + 1] || '';
- i++;
- continue;
- } else if (char == '/') {
- break;
- } else if (char == '\n' || i == (code.length - 1)) {
- thisIsNotARegexp = true;
- break;
- } else {
- regexp += char;
- }
- }
-
- if (thisIsNotARegexp) return null;
- if (regexp[0] == ' ' && regexp[regexp.length - 1] == ' ') return null;
-
- return {
- html: `<span class="regexp">/${escape(regexp)}/</span>`,
- next: regexp.length + 2
- };
- },
-
- // label
- code => {
- if (code[0] != '@') return null;
- const match = code.match(/^@([a-zA-Z_-]+?)\n/);
- if (!match) return null;
- const label = match[0];
- return {
- html: `<span class="label">${label}</span>`,
- next: label.length
- };
- },
-
- // number
- (code, i, source) => {
- const prev = source[i - 1];
- if (prev && /[a-zA-Z]/.test(prev)) return null;
- if (!/^[\-\+]?[0-9\.]+/.test(code)) return null;
- const match = code.match(/^[\-\+]?[0-9\.]+/)[0];
- if (match) {
- return {
- html: `<span class="number">${match}</span>`,
- next: match.length
- };
- } else {
- return null;
- }
- },
-
- // nan
- (code, i, source) => {
- const prev = source[i - 1];
- if (prev && /[a-zA-Z]/.test(prev)) return null;
- if (code.substr(0, 3) == 'NaN') {
- return {
- html: `<span class="nan">NaN</span>`,
- next: 3
- };
- } else {
- return null;
- }
- },
-
- // method
- code => {
- const match = code.match(/^([a-zA-Z_-]+?)\(/);
- if (!match) return null;
-
- if (match[1] == '-') return null;
-
- return {
- html: `<span class="method">${match[1]}</span>`,
- next: match[1].length
- };
- },
-
- // property
- (code, i, source) => {
- const prev = source[i - 1];
- if (prev != '.') return null;
-
- const match = code.match(/^[a-zA-Z0-9_-]+/);
- if (!match) return null;
-
- return {
- html: `<span class="property">${match[0]}</span>`,
- next: match[0].length
- };
- },
-
- // keyword
- (code, i, source) => {
- const prev = source[i - 1];
- if (prev && /[a-zA-Z]/.test(prev)) return null;
-
- const match = keywords.filter(k => code.substr(0, k.length) == k)[0];
- if (match) {
- if (/^[a-zA-Z]/.test(code.substr(match.length))) return null;
- return {
- html: `<span class="keyword ${match}">${match}</span>`,
- next: match.length
- };
- } else {
- return null;
- }
- },
-
- // symbol
- code => {
- const match = symbols.filter(s => code[0] == s)[0];
- if (match) {
- return {
- html: `<span class="symbol">${match}</span>`,
- next: 1
- };
- } else {
- return null;
- }
- }
-];
-
-// specify lang is todo
-function genHtml(source, lang) {
- let code = source;
- let html = '';
-
- let i = 0;
-
- function push(token) {
- html += token.html;
- code = code.substr(token.next);
- i += token.next;
- }
-
- while (code != '') {
- const parsed = elements.some(el => {
- const e = el(code, i, source);
- if (e) {
- push(e);
- return true;
- }
- });
-
- if (!parsed) {
- push({
- html: escape(code[0]),
- next: 1
- });
- }
- }
-
- return html;
-}
diff --git a/src/common/text/elements/inline-code.js b/src/common/text/elements/inline-code.js
index d117c53a15..37e9b1a0ff 100644
--- a/src/common/text/elements/inline-code.js
+++ b/src/common/text/elements/inline-code.js
@@ -2,6 +2,8 @@
* Code (inline)
*/
+const genHtml = require('../core/syntax-highlighter');
+
module.exports = text => {
const match = text.match(/^`(.+?)`/);
if (!match) return null;
@@ -9,6 +11,7 @@ module.exports = text => {
return {
type: 'inline-code',
content: code,
- code: code.substr(1, code.length - 2).trim()
+ code: code.substr(1, code.length - 2).trim(),
+ html: genHtml(code.substr(1, code.length - 2).trim())
};
};
diff --git a/src/web/app/base.styl b/src/web/app/base.styl
index 334ef91f76..c35f66c9a5 100644
--- a/src/web/app/base.styl
+++ b/src/web/app/base.styl
@@ -110,54 +110,54 @@ a
code
font-family Consolas, 'Courier New', Courier, Monaco, monospace
-pre
- display block
+ .comment
+ opacity 0.5
- > code
- display block
- overflow auto
- tab-size 2
+ .string
+ color #e96900
- .comment
- opacity 0.5
+ .regexp
+ color #e9003f
- .string
- color #e96900
+ .keyword
+ color #2973b7
- .regexp
- color #e9003f
+ &.true
+ &.false
+ &.null
+ &.nil
+ &.undefined
+ color #ae81ff
- .keyword
- color #2973b7
+ .symbol
+ color #42b983
- &.true
- &.false
- &.null
- &.nil
- &.undefined
- color #ae81ff
+ .number
+ .nan
+ color #ae81ff
- .symbol
- color #42b983
+ .var:not(.keyword)
+ font-weight bold
+ font-style italic
+ //text-decoration underline
- .number
- .nan
- color #ae81ff
+ .method
+ font-style italic
+ color #8964c1
- .var:not(.keyword)
- font-weight bold
- font-style italic
- //text-decoration underline
+ .property
+ color #a71d5d
- .method
- font-style italic
- color #8964c1
+ .label
+ color #e9003f
- .property
- color #a71d5d
+pre
+ display block
- .label
- color #e9003f
+ > code
+ display block
+ overflow auto
+ tab-size 2
mk-locker
display block
diff --git a/src/web/app/common/scripts/text-compiler.js b/src/web/app/common/scripts/text-compiler.js
index f47ce0f3c3..62e70463ad 100644
--- a/src/web/app/common/scripts/text-compiler.js
+++ b/src/web/app/common/scripts/text-compiler.js
@@ -31,9 +31,9 @@ module.exports = function(tokens, shouldBreak, shouldEscape) {
case 'hashtag': // TODO
return '<a>' + escape(token.content) + '</a>';
case 'code':
- return '<pre><code>' + token.codeHtml + '</code></pre>';
+ return '<pre><code>' + token.html + '</code></pre>';
case 'inline-code':
- return '<code>' + escape(token.code) + '</code>';
+ return '<code>' + token.html + '</code>';
}
}).join('');