From e19899962edfa9005e1f8fdbc56b1c8e7982864c Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sun, 12 Feb 2017 02:38:47 +0900
Subject: [PATCH] =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=83=A9=E3=82=A4=E3=83=B3?=
 =?UTF-8?q?=E3=81=AE=E3=82=B3=E3=83=BC=E3=83=89=E3=81=A7=E3=82=82=E3=82=B7?=
 =?UTF-8?q?=E3=83=B3=E3=82=BF=E3=83=83=E3=82=AF=E3=82=B9=E3=83=8F=E3=82=A4?=
 =?UTF-8?q?=E3=83=A9=E3=82=A4=E3=83=88=E3=82=92=E6=9C=89=E5=8A=B9=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/common/text/core/syntax-highlighter.js  | 331 +++++++++++++++++++
 src/common/text/elements/code.js            | 338 +-------------------
 src/common/text/elements/inline-code.js     |   5 +-
 src/web/app/base.styl                       |  82 ++---
 src/web/app/common/scripts/text-compiler.js |   4 +-
 5 files changed, 382 insertions(+), 378 deletions(-)
 create mode 100644 src/common/text/core/syntax-highlighter.js

diff --git a/src/common/text/core/syntax-highlighter.js b/src/common/text/core/syntax-highlighter.js
new file mode 100644
index 000000000..61237daf6
--- /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 902504c9e..99fe6a183 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 d117c53a1..37e9b1a0f 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 334ef91f7..c35f66c9a 100644
--- a/src/web/app/base.styl
+++ b/src/web/app/base.styl
@@ -110,6 +110,47 @@ a
 code
 	font-family Consolas, 'Courier New', Courier, Monaco, monospace
 
+	.comment
+		opacity 0.5
+
+	.string
+		color #e96900
+
+	.regexp
+		color #e9003f
+
+	.keyword
+		color #2973b7
+
+		&.true
+		&.false
+		&.null
+		&.nil
+		&.undefined
+			color #ae81ff
+
+	.symbol
+		color #42b983
+
+	.number
+	.nan
+		color #ae81ff
+
+	.var:not(.keyword)
+		font-weight bold
+		font-style italic
+		//text-decoration underline
+
+	.method
+		font-style italic
+		color #8964c1
+
+	.property
+		color #a71d5d
+
+	.label
+		color #e9003f
+
 pre
 	display block
 
@@ -118,47 +159,6 @@ pre
 		overflow auto
 		tab-size 2
 
-		.comment
-			opacity 0.5
-
-		.string
-			color #e96900
-
-		.regexp
-			color #e9003f
-
-		.keyword
-			color #2973b7
-
-			&.true
-			&.false
-			&.null
-			&.nil
-			&.undefined
-				color #ae81ff
-
-		.symbol
-			color #42b983
-
-		.number
-		.nan
-			color #ae81ff
-
-		.var:not(.keyword)
-			font-weight bold
-			font-style italic
-			//text-decoration underline
-
-		.method
-			font-style italic
-			color #8964c1
-
-		.property
-			color #a71d5d
-
-		.label
-			color #e9003f
-
 mk-locker
 	display block
 	position fixed
diff --git a/src/web/app/common/scripts/text-compiler.js b/src/web/app/common/scripts/text-compiler.js
index f47ce0f3c..62e70463a 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('');