Greasy Fork 还支持 简体中文。

GitHub Code Show Whitespace

A userscript that shows whitespace (space, tabs and carriage returns) in code blocks

目前為 2017-04-14 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name GitHub Code Show Whitespace
  3. // @version 0.1.3
  4. // @description A userscript that shows whitespace (space, tabs and carriage returns) in code blocks
  5. // @license https://opensource.org/licenses/MIT
  6. // @author Rob Garrison
  7. // @namespace https://github.com/Mottie
  8. // @include https://github.com/*
  9. // @include https://gist.github.com/*
  10. // @run-at document-idle
  11. // @grant GM_addStyle
  12. // @require https://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=188072
  13. // @icon https://github.com/fluidicon.png
  14. // ==/UserScript==
  15. (() => {
  16. "use strict";
  17.  
  18. // include em-space & en-space?
  19. const whitespace = {
  20. "%20" : "<span class='pl-space ghcw-whitespace'> </span>",
  21. "%A0" : "<span class='pl-nbsp ghcw-whitespace'>&nbsp;</span>",
  22. "%09" : "<span class='pl-tab ghcw-whitespace'>\x09</span>",
  23. // non-matching key; applied manually
  24. "CRLF" : "<span class='pl-crlf ghcw-whitespace'></span>\n"
  25. },
  26. // ignore +/- in diff code blocks
  27. regexWS = /^(?:[+-]*)(\x20|&nbsp;|\x09)+/g,
  28. regexCR = /\r*\n$/,
  29. regexTabSize = /\btab-size-\d\b/g,
  30.  
  31. toggleButton = document.createElement("div");
  32. toggleButton.className = "ghcw-toggle btn btn-sm tooltipped tooltipped-n";
  33. toggleButton.setAttribute("aria-label", "Toggle Whitespace");
  34. toggleButton.innerHTML = "<span class='pl-tab'></span>";
  35.  
  36. GM_addStyle(`
  37. .highlight .blob-code-inner { tab-size: 2; }
  38. /* GitHub-Dark overrides the above setting */
  39. .highlight.ghcw-active.tab-size-2 .pl-tab { width: 1.1em; }
  40. .highlight.ghcw-active.tab-size-4 .pl-tab { width: 2.2em; }
  41. .highlight.ghcw-active.tab-size-6 .pl-tab { width: 3.3em; }
  42. .highlight.ghcw-active.tab-size-8 .pl-tab { width: 4.4em; }
  43.  
  44. .ghcw-active .ghcw-whitespace,
  45. .gist-content-wrapper .file-actions .btn-group {
  46. position: relative;
  47. display: inline-block;
  48. }
  49. .ghcw-active .ghcw-whitespace:before {
  50. position: absolute;
  51. overflow: hidden;
  52. opacity: .4;
  53. user-select: none;
  54. }
  55. .ghcw-toggle .pl-tab {
  56. pointer-events: none;
  57. }
  58. .ghcw-active .pl-space:before {
  59. content: "\\b7";
  60. }
  61. .ghcw-active .pl-nbsp:before {
  62. content: "\\b7";
  63. }
  64. .ghcw-active .pl-tab:before,
  65. .ghcw-toggle .pl-tab:before {
  66. content: "\\bb";
  67. }
  68. .ghcw-active .pl-crlf:before {
  69. content: "\\231d";
  70. top: -.75em;
  71. }
  72. `);
  73.  
  74. function addToggle() {
  75. $$(".file-actions").forEach(el => {
  76. if (!$(".ghcw-toggle", el)) {
  77. el.insertBefore(toggleButton.cloneNode(true), el.childNodes[0]);
  78. }
  79. });
  80. }
  81.  
  82. function addWhitespace(block) {
  83. let lines, indx, len;
  84. if (block && !block.classList.contains("ghcw-processed")) {
  85. block.classList.add("ghcw-processed");
  86. updateTabSize(block);
  87. indx = 0;
  88.  
  89. // class name of each code row
  90. lines = $$(".blob-code-inner:not(.blob-code-hunk)", block);
  91. len = lines.length;
  92.  
  93. // loop with delay to allow user interaction
  94. const loop = () => {
  95. let line,
  96. // max number of DOM insertions per loop
  97. max = 0;
  98. while (max < 50 && indx < len) {
  99. if (indx >= len) {
  100. return;
  101. }
  102. line = lines[indx];
  103. line.innerHTML = line.innerHTML
  104. .replace(regexWS, s => {
  105. let idx = 0,
  106. ln = s.length,
  107. result = "";
  108. for (idx = 0; idx < ln; idx++) {
  109. result += whitespace[encodeURI(s[idx])] || s[idx] || "";
  110. }
  111. return result;
  112. })
  113. // remove end CRLF if it exists
  114. .replace(regexCR, "") +
  115. whitespace.CRLF;
  116. max++;
  117. indx++;
  118. }
  119. if (indx < len) {
  120. setTimeout(() => {
  121. loop();
  122. }, 200);
  123. }
  124. };
  125. loop();
  126. }
  127. }
  128.  
  129. function updateTabSize(block) {
  130. // remove previous tab-size setting
  131. block.className = block.className.replace(regexTabSize, " ");
  132. // calculate tab-size; GitHub-Dark allows user modification
  133. const len = window.getComputedStyle($(".blob-code-inner", block)).tabSize;
  134. block.classList.add(`tab-size-${len}`);
  135. }
  136.  
  137. function $(selector, el) {
  138. return (el || document).querySelector(selector);
  139. }
  140.  
  141. function $$(selector, el) {
  142. return Array.from((el || document).querySelectorAll(selector));
  143. }
  144.  
  145. function closest(selector, el) {
  146. while (el && el.nodeType === 1) {
  147. if (el.matches(selector)) {
  148. return el;
  149. }
  150. el = el.parentNode;
  151. }
  152. return null;
  153. }
  154.  
  155. // bind whitespace toggle button
  156. document.addEventListener("click", event => {
  157. const target = event.target;
  158. if (
  159. target.nodeName === "DIV" &&
  160. target.classList.contains("ghcw-toggle")
  161. ) {
  162. let block = $(".highlight", closest(".file", target));
  163. if (block) {
  164. target.classList.toggle("selected");
  165. block.classList.toggle("ghcw-active");
  166. updateTabSize(block);
  167. addWhitespace(block);
  168. }
  169. }
  170. });
  171.  
  172. document.addEventListener("ghmo:container", addToggle);
  173. document.addEventListener("ghmo:diff", addToggle);
  174. // toggle added to diff & file view
  175. addToggle();
  176.  
  177. })();