Textarea Plus

An userscript to improve plain textarea for code editing.

目前為 2014-10-07 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Textarea Plus
  3. // @description An userscript to improve plain textarea for code editing.
  4. // @namespace eight04.blogspot.com
  5. // @include http*
  6. // @version 1.1
  7. // @grant GM_addStyle
  8. // ==/UserScript==
  9.  
  10. "use strict";
  11.  
  12. var ignoreClassList = ["CodeMirror", "ace_editor"];
  13.  
  14. var textareaPlus = function(){
  15.  
  16. var editor = {
  17. indent: indent,
  18. unindent: unindent,
  19. home: home,
  20. selectHome: selectHome,
  21. enter: enter
  22. };
  23. function selectionRange(data){
  24. return data.pos[1] - data.pos[0];
  25. }
  26. function takeRange(data) {
  27. if (data.pos[0] > data.pos[1]) {
  28. return [data.pos[1], data.pos[0]];
  29. }
  30. return data.pos;
  31. }
  32. function insert(data, text){
  33. var pos = takeRange(data);
  34. data.text = data.text.substr(0, pos[0]) + text + data.text.substr(pos[1]);
  35. data.pos[0] = pos[0] + text.length;
  36. data.pos[1] = data.pos[0];
  37. }
  38. function del(data){
  39. if (!selectionRange(data)) {
  40. data.pos[1]++;
  41. }
  42. insert(data, "");
  43. }
  44. function getLineStart(data, pos){
  45. if (pos == undefined) {
  46. pos = data.pos[1];
  47. }
  48. if (data.text[pos] == "\n") {
  49. pos--;
  50. }
  51. var s = data.text.lastIndexOf("\n", pos);
  52. if (s < 0) {
  53. return 0;
  54. }
  55. return s + 1;
  56. }
  57. function getLineEnd(data, pos){
  58. if (pos == undefined) {
  59. pos = data.pos[1];
  60. }
  61. var s = data.text.indexOf("\n", pos);
  62. if (s < 0) {
  63. return data.text.length;
  64. }
  65. return s;
  66. }
  67. function multiIndent(data){
  68. var pos = takeRange(data);
  69. var lineStart = getLineStart(data, pos[0]), lineEnd = getLineEnd(data, pos[1]);
  70. var lines = data.text.substr(lineStart, lineEnd - lineStart);
  71. lines = lines.split("\n");
  72. for (var i = 0; i < lines.length; i++) {
  73. lines[i] = "\t" + lines[i];
  74. }
  75. lines = lines.join("\n");
  76. data.text = data.text.substr(0, lineStart) + lines + data.text.substr(lineEnd);
  77. if (data.pos[0] > data.pos[1]) {
  78. data.pos[1] = lineStart;
  79. data.pos[0] = lineEnd + i;
  80. } else {
  81. data.pos[0] = lineStart;
  82. data.pos[1] = lineEnd + i;
  83. }
  84. }
  85. function inMultiLine(data) {
  86. var pos = takeRange(data);
  87. var s = data.text.indexOf("\n", pos[0]);
  88. if (s < 0) {
  89. return false;
  90. }
  91. return s < pos[1];
  92. }
  93. function indent(data){
  94. if (!selectionRange(data)) {
  95. insert(data, "\t");
  96. if (data.pos[0] < getTextStart(data)) {
  97. home(data);
  98. }
  99. } else {
  100. if (inMultiLine(data)) {
  101. multiIndent(data);
  102. } else {
  103. insert(data, "\t");
  104. }
  105. }
  106. }
  107. function multiUnindent(data){
  108. var pos = takeRange(data);
  109. var lineStart = getLineStart(data, pos[0]), lineEnd = getLineEnd(data, pos[1]);
  110. var lines = data.text.substr(lineStart, lineEnd - lineStart);
  111. lines = lines.split("\n");
  112. var len = 0;
  113. for (var i = 0; i < lines.length; i++) {
  114. var m = lines[i].match(/^( {4}| {0,3}\t?)(.*)$/);
  115. // console.log(m);
  116. len += m[1].length;
  117. lines[i] = m[2];
  118. }
  119. lines = lines.join("\n");
  120. data.text = data.text.substr(0, lineStart) + lines + data.text.substr(lineEnd);
  121. if (data.pos[0] > data.pos[1]) {
  122. data.pos[1] = lineStart;
  123. data.pos[0] = lineEnd - len;
  124. } else {
  125. data.pos[0] = lineStart;
  126. data.pos[1] = lineEnd - len;
  127. }
  128. }
  129. function backspace(data) {
  130. if (selectionRange(data)) {
  131. del(data);
  132. } else if (data.pos[0] > 0) {
  133. data.pos[0]--;
  134. del(data);
  135. }
  136. }
  137. function unindent(data) {
  138. if (inMultiLine(data)) {
  139. multiUnindent(data);
  140. } else if (!selectionRange(data) && data.text[data.pos[0] - 1] == "\t") {
  141. backspace(data);
  142. } else {
  143. multiUnindent(data);
  144. home(data);
  145. }
  146. }
  147. function searchFrom(text, re, pos) {
  148. pos = pos || 0;
  149. var t = text.substr(pos);
  150. var s = t.search(re);
  151. if (s < 0) {
  152. return -1;
  153. }
  154. return s + pos;
  155. }
  156. function getTextStart(data, pos) {
  157. var lineStart = getLineStart(data, pos);
  158. pos = searchFrom(data.text, /[\S\n]/, lineStart);
  159. if (pos < 0 || data.text[pos] == "\n") {
  160. return getLineEnd(data);
  161. }
  162. return pos;
  163. }
  164. function isTextStart(data) {
  165. return getTextStart(data) == data.pos[1];
  166. }
  167. function home(data) {
  168. if (isTextStart(data)) {
  169. data.pos[0] = getLineStart(data);
  170. } else {
  171. data.pos[0] = getTextStart(data);
  172. }
  173. data.pos[1] = data.pos[0];
  174. }
  175. function selectHome(data) {
  176. var pos = data.pos[0];
  177. home(data);
  178. data.pos[0] = pos;
  179. }
  180. function getIndents(data) {
  181. var pos = takeRange(data);
  182. var lineStart = getLineStart(data, pos[0]);
  183. var len;
  184. var textStart = getTextStart(data, pos[0]);
  185. // console.log(textStart);
  186. if (textStart >= pos[0]) {
  187. len = pos[0] - lineStart;
  188. } else {
  189. len = textStart - lineStart;
  190. }
  191. return data.text.substr(lineStart, len);
  192. }
  193. function enter(data) {
  194. // console.log(data);
  195. var indents = getIndents(data);
  196. insert(data, "\n" + indents);
  197. }
  198. function init(node, command) {
  199. var data = {
  200. text: node.value,
  201. pos: [node.selectionStart, node.selectionEnd]
  202. }, t;
  203. if (node.selectionDirection == "backward") {
  204. t = data.pos[0];
  205. data.pos[0] = data.pos[1];
  206. data.pos[1] = t;
  207. }
  208. editor[command](data);
  209. node.value = data.text;
  210. if (data.pos[0] > data.pos[1]) {
  211. node.setSelectionRange(data.pos[1], data.pos[0], "backward");
  212. } else {
  213. node.setSelectionRange(data.pos[0], data.pos[1], "forward");
  214. }
  215. }
  216. return init;
  217. }();
  218.  
  219. function validArea(area) {
  220. if (area.nodeName != "TEXTAREA") {
  221. return false;
  222. }
  223. if (area.dataset.textareaPlus === "false") {
  224. return false;
  225. }
  226. if (area.dataset.textareaPlus === "true") {
  227. return true;
  228. }
  229. // if (area.onkeydown) {
  230. // area.dataset.textareaPlus = "false";
  231. // return false;
  232. // }
  233. var node = area, i;
  234. while ((node = node.parentNode) != document.body) {
  235. for (i = 0; i < ignoreClassList.length; i++) {
  236. if (node.classList.contains(ignoreClassList[i])) {
  237. area.dataset.textareaPlus = "false";
  238. return false;
  239. }
  240. }
  241. }
  242. area.dataset.textareaPlus = "true";
  243. return true;
  244. }
  245.  
  246. window.addEventListener("keydown", function(e){
  247. if (!validArea(e.target)) {
  248. return;
  249. }
  250. var command;
  251. if (e.keyCode == 9 && !e.ctrlKey && !e.altKey) {
  252. // tab
  253. if (e.shiftKey) {
  254. command = "unindent";
  255. } else {
  256. command = "indent";
  257. }
  258. } else if (e.keyCode == 13 && !e.ctrlKey && !e.altKey && !e.shiftKey) {
  259. // enter
  260. command = "enter";
  261. } else if (e.keyCode == 36 && !e.ctrlKey && !e.altKey) {
  262. // home
  263. if (!e.shiftKey) {
  264. command = "home";
  265. } else {
  266. command = "selectHome";
  267. }
  268. } else {
  269. return;
  270. }
  271. e.preventDefault();
  272. e.stopPropagation();
  273.  
  274. textareaPlus(e.target, command);
  275. }, true);
  276.  
  277. GM_addStyle("textarea {tab-size: 4; -moz-tab-size: 4; -o-tab-size: 4;}");