Textarea Plus

An userscript to improve plain textarea for code editing.

当前为 2014-09-09 提交的版本,查看 最新版本

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