您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Have a better textarea! A userscript which can improve plain textarea for code editing.
当前为
- // ==UserScript==
- // @name Textarea Plus
- // @version 3.0.0
- // @description Have a better textarea! A userscript which can improve plain textarea for code editing.
- // @homepageURL https://github.com/eight04/textarea-plus
- // @supportURL https://github.com/eight04/textarea-plus/issues
- // @license MIT
- // @author eight04 <eight04@gmail.com>
- // @namespace eight04.blogspot.com
- // @include *
- // @grant GM_getValue
- // @grant GM_setValue
- // @grant GM_registerMenuCommand
- // @grant GM_addStyle
- // @compatible firefox Tampermonkey latest
- // @compatible chrome Tampermonkey latest
- // @require https://greasyfork.org/scripts/7212-gm-config-eight-s-version/code/GM_config%20(eight's%20version).js?version=156587
- // ==/UserScript==
- var textareaPlus = (function () {
- 'use strict';
- /* eslint-env node */
- function isSameLine(editor) {
- return !editor.getSelection().includes("\n");
- }
- function getIndentInfo(text, {indentSize}) {
- var i, count = 0;
- for (i = 0; i < text.length; i++) {
- var c = text[i];
- if (c == " ") {
- count++;
- } else if (c == "\t") {
- count += indentSize;
- } else {
- break;
- }
- }
- return {
- count: Math.floor(count / indentSize),
- extraSpaces: count % indentSize,
- length: i
- };
- }
- function getIndentChar({indentStyle, indentSize}) {
- if (indentStyle === "TAB") {
- return "\t";
- }
- return " ".repeat(indentSize);
- }
- function runIndent({editor, options}) {
- if (!isSameLine(editor)) {
- runMultiIndent(editor, options);
- return;
- }
- var range = editor.getSelectionLineRange(),
- line = editor.getSelectionLine(),
- indent = getIndentInfo(line, options),
- pos = editor.getSelectionRange().start;
- if (pos > range.start + indent.length) {
- editor.setRangeText(
- getIndentChar(options),
- pos,
- pos,
- "end"
- );
- } else {
- editor.setRangeText(
- getIndentChar(options).repeat(indent.count + 1),
- range.start,
- range.start + indent.length,
- "end"
- );
- }
- }
- function runUnindent({editor, options}) {
- if (!isSameLine(editor)) {
- runMultiIndent(editor, options, -1);
- return;
- }
- var range = editor.getSelectionLineRange(),
- line = editor.getSelectionLine(),
- indent = getIndentInfo(line, options),
- pos = editor.getCaretPos(true),
- indentChar = getIndentChar(options);
- const indentCount = indent.count + (indent.extraSpaces ? 1 : 0);
- if (pos <= range.start + indent.length && indentCount) {
- editor.setRangeText(
- indentChar.repeat(indentCount - 1),
- range.start,
- range.start + indent.length,
- "end"
- );
- } else if (line.slice(0, pos - range.start).endsWith(indentChar)) {
- editor.setRangeText(
- "", pos - indentChar.length, pos, "end"
- );
- }
- }
- function runMultiIndent(editor, options, diff = 1) {
- var range = editor.getSelectionRange(),
- lines = editor.getSelectionLine(),
- lineRange = editor.getSelectionLineRange();
- if (lines[range.end - lineRange.start - 1] == "\n") {
- lineRange.end = range.end - 1;
- lines = lines.slice(0, range.end - lineRange.start - 1);
- }
- lines = lines.split("\n").map(line => {
- if (!line) return line;
- var indent = getIndentInfo(line, options),
- count = indent.count + diff;
- if (count < 0) {
- count = 0;
- // remove extra space when there is no indent
- indent.extraSpaces = 0;
- }
- return getIndentChar(options).repeat(count) +
- " ".repeat(indent.extraSpaces) +
- line.slice(indent.length);
- }).join("\n");
- editor.setRangeText(lines, lineRange.start, lineRange.end);
- editor.setSelectionRange(lineRange.start, lineRange.start + lines.length + 1);
- }
- function runSmartHome({editor, options, event}) {
- const collapse = !event.shiftKey;
- var line = editor.getCurrentLine(),
- range = editor.getCurrentLineRange(),
- pos = editor.getCaretPos(collapse) - range.start,
- indent = getIndentInfo(line, options);
- if (pos == indent.length) {
- editor.setCaretPos(range.start, collapse);
- } else {
- editor.setCaretPos(range.start + indent.length, collapse);
- }
- }
- const BRACES = {
- __proto__: null,
- "[": "]",
- "{": "}",
- "(": ")",
- };
- function runNewLine({editor, options}) {
- var content = editor.getContent(),
- range = editor.getSelectionRange(),
- line = editor.getSelectionLine(),
- lineRange = editor.getLineRange(range.start, range.start),
- indent = getIndentInfo(line, options),
- out = "\n", pos,
- left = content[range.start - 1],
- right = content[range.end];
- if (/[[{(]/.test(left)) {
- out += getIndentChar(options).repeat(indent.count + 1);
- } else {
- out += line.slice(0, Math.min(indent.length, range.start - lineRange.start));
- }
- pos = range.start + out.length;
- if (BRACES[left] && right == BRACES[left]) {
- out += "\n" + line.slice(0, indent.length);
- }
- editor.setRangeText(out);
- editor.setSelectionRange(pos, pos);
- }
- function runCompleteBraces({editor, options, event}) {
- const left = event.key;
- const right = options.completeBraces[left];
- var text = editor.getSelection(),
- range = editor.getSelectionRange();
- editor.setRangeText(left + text + right, range.start, range.end);
- editor.setSelectionRange(range.start + 1, range.start + 1 + text.length);
- }
- const COMMANDS = [
- {
- // indent
- test: e => e.key === "Tab" && !e.shiftKey,
- run: runIndent
- },
- {
- // unindent
- test: e => e.key === "Tab" && e.shiftKey,
- run: runUnindent
- },
- {
- // smart home
- test: e => e.key === "Home",
- run: runSmartHome
- },
- {
- // new line
- test: e => e.key === "Enter",
- run: runNewLine
- },
- {
- // complete braces
- test: (e, {completeBraces}) => completeBraces[e.key],
- run: runCompleteBraces
- }
- ];
- const DEFAULT_OPTIONS = {
- indentSize: 4,
- indentStyle: "TAB",
- completeBraces: {
- __proto__: null,
- "[": "]",
- "{": "}",
- "(": ")",
- "\"": "\"",
- "'": "'"
- }
- };
- function createCommandExecutor(options = {}) {
- options = Object.assign({}, DEFAULT_OPTIONS, options);
- function run(event, editorFactory) {
- for (const command of COMMANDS) {
- if (command.test(event, options)) {
- event.preventDefault();
- command.run({editor: editorFactory(), options, event});
- break;
- }
- }
- }
- return {run};
- }
- var textareaPlus = {createCommandExecutor};
- return textareaPlus;
- }());
- /* eslint-env browser, greasemonkey */
- /* global textareaPlus GM_config */
- 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;
- j = content.indexOf("\n", end);
- 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 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;
- }
- let commandExcutor, styleEl;
- GM_config.setup({
- indentSize: {
- label: "Indent size",
- type: "number",
- default: 4
- },
- indentStyle: {
- label: "Indent style",
- type: "radio",
- default: "TAB",
- options: {
- TAB: "Tab",
- SPACE: "Space"
- }
- },
- completeBraces: {
- label: "Complete braces. One pair per line",
- type: "textarea",
- default: "[]\n{}\n()"
- }
- }, () => {
- const options = GM_config.get();
- options.completeBraces = createMap(options.completeBraces);
- commandExcutor = textareaPlus.createCommandExecutor(options);
- if (styleEl) styleEl.remove();
- styleEl = GM_addStyle(`
- textarea {
- tab-size: ${options.indentSize};
- -moz-tab-size: ${options.indentSize};
- -o-tab-size: ${options.indentSize};
- }`
- );
- function createMap(text) {
- const map = {__proto__: null};
- for (const pair of text.split(/\s+/g)) {
- if (pair.length == 2) {
- map[pair[0]] = pair[1];
- } else if (pair.length != 0) {
- alert(`Invalid pair: ${pair}`);
- }
- }
- return map;
- }
- });
- window.addEventListener("keydown", function(e){
- if (!validArea(e.target) || e.ctrlKey || e.altKey) {
- return;
- }
- if (e.defaultPrevented) {
- return;
- }
- commandExcutor.run(e, () => new Editor(e.target));
- });