您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
An userscript to improve plain textarea for code editing.
当前为
// ==UserScript== // @name Textarea Plus // @description An userscript to improve plain textarea for code editing. // @namespace eight04.blogspot.com // @include * // @version 2.0.0 // @grant GM_addStyle // ==/UserScript== class Editor { constructor(textarea) { this.el = textarea; } getSelectionRange() { return { start: this.el.selectionStart, end: this.el.selectionEnd }; } setSelectionRange(start, end) { this.el.setSelectionRange(start, end); } getCaretPos(collapse = false) { if (this.el.selectionDirection == "backward" || collapse) { return this.el.selectionStart; } return this.el.selectionEnd; } setCaretPos(pos, collapse = false) { if (collapse) { this.setSelectionRange(pos, pos); } else { var start = this.el.selectionStart, end = this.el.selectionEnd, dir = this.el.selectionDirection; if (dir == "backward") { [start, end] = [end, start]; dir = "forward"; } end = pos; if (end < start) { [start, end] = [end, start]; dir = "backward"; } this.el.selectionEnd = end; this.el.selectionStart = start; this.el.selectionDirection = dir; } } getLineRange(start, end) { var content = this.getContent(), i, j; i = content.lastIndexOf("\n", start - 1) + 1; if (content[end - 1] == "\n") { j = end; } else { j = content.indexOf("\n", end) + 1; if (j == 0) { j = content.length; } } return { start: i, end: j }; } getSelectionLineRange() { var range = this.getSelectionRange(); return this.getLineRange(range.start, range.end); } getSelectionLine() { var content = this.getContent(), range = this.getSelectionLineRange(); return content.slice(range.start, range.end); } getCurrentLineRange() { var pos = this.getCaretPos(); return this.getLineRange(pos, pos); } getCurrentLine() { var range = this.getCurrentLineRange(), content = this.getContent(); return content.slice(range.start, range.end); } getContent() { return this.el.value; } getSelection() { var content = this.getContent(), range = this.getSelectionRange(); return content.slice(range.start, range.end); } setRangeText(...args) { this.el.setRangeText(...args); } } var braceMatch = { "[": "]", "{": "}", "(": ")" }; class Commands { constructor(editor) { this.editor = editor; this.indentSize = 4; this.indentChar = "\t"; } sameLine() { return !this.editor.getSelection().includes("\n"); } betweenText() { var line = this.editor.getSelectionLine(), pos = this.editor.getCaretPos(), lineRange = this.editor.getSelectionLineRange(), match = line.match(/\S/); if (!match) { return false; } return pos >= lineRange.start + match.index; } getIndentInfo(text) { var i, count = 0; for (i = 0; i < text.length; i++) { var c = text[i]; if (c == " ") { count++; } else if (c == "\t") { count += 4; } else { break; } } return { count: Math.floor(count / this.indentSize), length: i }; } indent() { if (this.sameLine()) { var range = this.editor.getSelectionLineRange(), line = this.editor.getSelectionLine(), indent = this.getIndentInfo(line), pos = this.editor.getSelectionRange().start; if (pos >= range.start + indent.length) { this.editor.setRangeText( this.indentChar, pos, pos, "end" ); } else { this.editor.setRangeText( this.indentChar.repeat(indent.count + 1), range.start, range.start + indent.length, "end" ); } } else { this.multiIndent(); } return true; } multiIndent(diff = 1) { var lines = this.editor.getSelectionLine().split("\n"), range = this.editor.getSelectionLineRange(); lines = lines.map(line => { if (!line) return line; var indent = this.getIndentInfo(line), count = indent.count + diff; if (count < 0) count = 0; return this.indentChar.repeat(count) + line.slice(indent.length); }).join("\n"); this.editor.setRangeText(lines, range.start, range.end, "select"); } unindent() { if (this.sameLine()) { var range = this.editor.getSelectionLineRange(), line = this.editor.getSelectionLine(), indent = this.getIndentInfo(line), pos = this.editor.getCaretPos(true); if (pos <= range.start + indent.length && indent.count) { this.editor.setRangeText( this.indentChar.repeat(indent.count - 1), range.start, range.start + indent.length, "end" ); } else if (line.slice(0, pos - range.start).endsWith(this.indentChar)) { this.editor.setRangeText( "", pos - this.indentChar.length, pos, "end" ); } } else { this.multiIndent(-1); } return true; } smartHome(collapse = false) { var line = this.editor.getCurrentLine(), range = this.editor.getCurrentLineRange(), pos = this.editor.getCaretPos(collapse) - range.start, indent = this.getIndentInfo(line); if (pos == indent.length) { this.editor.setCaretPos(range.start, collapse); } else { this.editor.setCaretPos(range.start + indent.length, collapse); } return true; } newLineIndent() { var content = this.editor.getContent(), range = this.editor.getSelectionRange(), line = this.editor.getSelectionLine(), indent = this.getIndentInfo(line), out = "\n", pos, left = content[range.start - 1], right = content[range.end]; if (/[\[{(]/.test(left)) { out += this.indentChar.repeat(indent.count + 1); } else { out += line.slice(0, indent.length); } pos = range.start + out.length; if (right == braceMatch[left]) { out += "\n" + line.slice(0, indent.length); } this.editor.setRangeText(out); this.editor.setSelectionRange(pos, pos); return true; } completeBraces(left) { var right = braceMatch[left]; if (!right) return false; var text = this.editor.getSelection(), range = this.editor.getSelectionRange(); this.editor.setRangeText(left + text + right, range.start, range.end); this.editor.setSelectionRange(range.start + 1, range.start + 1 + text.length); return true; } } var ignoreClassList = ["CodeMirror", "ace_editor"]; function validArea(area) { if (area.nodeName != "TEXTAREA") { return false; } if (area.dataset.textareaPlus === "false") { return false; } if (area.dataset.textareaPlus === "true") { return true; } var node = area, i; while ((node = node.parentNode) != document.body) { for (i = 0; i < ignoreClassList.length; i++) { if (node.classList.contains(ignoreClassList[i])) { area.dataset.textareaPlus = "false"; return false; } } } area.dataset.textareaPlus = "true"; return true; } window.addEventListener("keydown", function(e){ if (!validArea(e.target) || e.ctrlKey || e.altKey) { return; } if (e.defaultPrevented) { return; } function commands() { return new Commands(new Editor(e.target)); } var result = false; if (e.keyCode == 9) { // tab if (e.shiftKey) { result = commands().unindent(); } else { result = commands().indent(); } } else if (e.keyCode == 13) { // enter result = commands().newLineIndent(); } else if (e.keyCode == 36) { // home result = commands().smartHome(!e.shiftKey); } else if (braceMatch[e.key]) { // braces result = commands().completeBraces(e.key); } if (result) { e.preventDefault(); } }); GM_addStyle("textarea {tab-size: 4; -moz-tab-size: 4; -o-tab-size: 4;}");