GitHub Code Guides

A userscript that allows you to add one or more vertical guidelines to the code

  1. // ==UserScript==
  2. // @name GitHub Code Guides
  3. // @version 1.1.14
  4. // @description A userscript that allows you to add one or more vertical guidelines to the code
  5. // @license MIT
  6. // @author Rob Garrison
  7. // @namespace https://github.com/Mottie
  8. // @match https://github.com/*
  9. // @run-at document-idle
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @grant GM_registerMenuCommand
  13. // @icon https://github.githubassets.com/pinned-octocat.svg
  14. // @supportURL https://github.com/Mottie/GitHub-userscripts/issues
  15. // ==/UserScript==
  16.  
  17. /* copy into textarea to check the guides
  18. 1 2 3 4 5 6 7 8
  19. 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345
  20. */
  21. (() => {
  22. "use strict";
  23. const style = document.createElement("style");
  24. // eslint-disable-next-line one-var
  25. let guides = GM_getValue("ghcg-guides", [{
  26. chars: 80,
  27. color: "rgba(0, 0, 0, .3)",
  28. width: 0.2
  29. }]),
  30. font = GM_getValue("ghcg-font", "Menlo"),
  31. tabSize = GM_getValue("ghcg-tabs", 2);
  32.  
  33. function adjust(val) {
  34. return `${val.toFixed(1)}ch`;
  35. }
  36.  
  37. function addDefinition(start, end, color) {
  38. return `
  39. transparent ${start},
  40. ${color} ${start},
  41. ${color} ${end},
  42. transparent ${end},
  43. `;
  44. }
  45.  
  46. function addGuides(vals) {
  47. let css = "";
  48. // to align the guides *after* the setting, we need to subtract 0.5, then
  49. // add another 0.1 to give the guide a tiny bit of white space to the left
  50. vals.forEach(guide => {
  51. let start = parseFloat(guide.chars) - 0.5,
  52. size = parseFloat(guide.width) || 0.2;
  53. const color = guide.color || "rgba(0, 0, 0, .3)";
  54. // each line needs to be at least 0.2ch in width to be visible
  55. size = size > 0.2 ? size : 0.2;
  56. css += addDefinition(adjust(start), adjust(start + size), color);
  57. });
  58. style.textContent = `
  59. table.tab-size[data-tab-size] {
  60. tab-size: ${tabSize};
  61. -moz-tab-size: ${tabSize};
  62. }
  63. span.blob-code-inner:before,
  64. td.blob-code-inner:not(.blob-code-hunk):before,
  65. .blob-code-context .blob-code-inner:before,
  66. .blob-code-addition .blob-code-inner:before,
  67. .blob-code-deletion .blob-code-inner:before {
  68. display: block;
  69. position: absolute;
  70. top: 0;
  71. left: 1em;
  72. width: 100%;
  73. height: 100%;
  74. text-indent: -1em;
  75. }
  76. .blob-code span.blob-code-inner {
  77. display: block !important;
  78. }
  79. span.blob-code-inner,
  80. td.blob-code-inner:not(.blob-code-hunk),
  81. .blob-code-inner:before {
  82. font-family: "${font}", Consolas, "Liberation Mono", Menlo, Courier, monospace !important;
  83. }
  84. span.blob-code-inner:before,
  85. td.blob-code-inner:not(.blob-code-hunk):before {
  86. background: linear-gradient(to right, transparent 0%, ${css} transparent 100%) !important;
  87. pointer-events: none;
  88. content: '';
  89. }
  90. `;
  91. }
  92.  
  93. function validateGuides(vals) {
  94. let last = 0;
  95. const valid = [];
  96. if (!Array.isArray(vals)) {
  97. console.log("Code-Guides Userscript: Invalid guidelines", vals);
  98. return;
  99. }
  100. // Object.keys() creates an array of string values
  101. const lines = vals.sort((a, b) => parseFloat(a.chars) - parseFloat(b.chars));
  102. lines.forEach(line => {
  103. const num = parseFloat(line.chars);
  104. // 0.2 is the width of the "ch" in CSS to make it visible
  105. if (num >= last + line.width) {
  106. valid.push(line);
  107. last = num;
  108. }
  109. });
  110. if (valid.length) {
  111. guides = valid;
  112. GM_setValue("ghcg-guides", valid);
  113. GM_setValue("ghcg-font", font);
  114. GM_setValue("ghcg-tabs", tabSize);
  115. addGuides(valid);
  116. }
  117. }
  118.  
  119. document.querySelector("head").appendChild(style);
  120. validateGuides(guides);
  121.  
  122. // Add GM options
  123. GM_registerMenuCommand("Set code guideline position & color", () => {
  124. let val = prompt(
  125. `Enter valid JSON [{ "chars":80, "color":"#f00", "width":0.2 }, ...}]`,
  126. JSON.stringify(guides)
  127. );
  128. if (val !== null) {
  129. try {
  130. val = JSON.parse(val);
  131. validateGuides(val);
  132. } catch (err) {
  133. console.log(err);
  134. }
  135. }
  136. });
  137.  
  138. GM_registerMenuCommand("Set code guideline default font", () => {
  139. const val = prompt("Enter code font (monospaced)", font);
  140. if (val !== null) {
  141. font = val;
  142. validateGuides(guides);
  143. }
  144. });
  145.  
  146. GM_registerMenuCommand("Set code guideline tab size", () => {
  147. const val = prompt("Enter code guideline tab size", tabSize);
  148. if (val !== null) {
  149. tabSize = val;
  150. validateGuides(guides);
  151. }
  152. });
  153.  
  154. })();