Chat GPT Code Blocks Line Numbers

Adds line numbers to Chat GPT code blocks either live (as the code is generated) or lazy (once the code has finished generating). Use lazy mode for performance mode. The default mode is the live mode. Use the toggle in the Tampermonkey menu to switch between the modes.

  1. // ==UserScript==
  2. // @name Chat GPT Code Blocks Line Numbers
  3. // @author NWP
  4. // @description Adds line numbers to Chat GPT code blocks either live (as the code is generated) or lazy (once the code has finished generating). Use lazy mode for performance mode. The default mode is the live mode. Use the toggle in the Tampermonkey menu to switch between the modes.
  5. // @namespace https://gf.qytechs.cn/users/877912
  6. // @version 0.1
  7. // @license MIT
  8. // @match https://chatgpt.com/*
  9. // @grant GM_registerMenuCommand
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. function refreshPage() {
  18. location.reload();
  19. }
  20.  
  21. function toggleLiveLineNumbers() {
  22. const isLive = GM_getValue('liveLineNumbers', true); // Default to true
  23. GM_setValue('liveLineNumbers', !isLive);
  24. refreshPage();
  25. }
  26.  
  27. function registerMenu() {
  28. GM_registerMenuCommand(
  29. `Live line numbers: ${GM_getValue('liveLineNumbers', true) ? 'ON' : 'OFF'}`, // Default to true
  30. toggleLiveLineNumbers
  31. );
  32. }
  33.  
  34. registerMenu();
  35.  
  36. const liveLineNumbers = GM_getValue('liveLineNumbers', true); // Default to true
  37.  
  38. function createLineNumbersElement() {
  39. const lineNumbersElement = document.createElement('div');
  40. lineNumbersElement.classList.add('line-numbers-inline');
  41. lineNumbersElement.style.display = 'flex';
  42. lineNumbersElement.style.flexDirection = 'column';
  43. lineNumbersElement.style.paddingTop = '0.07rem';
  44. lineNumbersElement.style.textAlign = 'right';
  45. lineNumbersElement.style.userSelect = 'none';
  46. lineNumbersElement.style.pointerEvents = 'none';
  47. lineNumbersElement.style.color = '#aaa';
  48. lineNumbersElement.style.background = '#1e1e1e';
  49. lineNumbersElement.style.lineHeight = '1.5';
  50. lineNumbersElement.style.zIndex = '2';
  51. lineNumbersElement.style.position = 'sticky';
  52. lineNumbersElement.style.left = '0';
  53. lineNumbersElement.style.minWidth = 'auto';
  54. lineNumbersElement.style.transform = 'none';
  55. return lineNumbersElement;
  56. }
  57.  
  58. function updateLineNumbers(codeBlock, lineNumbersElement) {
  59. const lines = codeBlock.textContent.split('\n');
  60. lineNumbersElement.innerHTML = '';
  61. const maxLineNumber = lines.length;
  62. const digits = maxLineNumber.toString().length;
  63. const minWidth = `${digits}ch`;
  64. lineNumbersElement.style.minWidth = minWidth;
  65. codeBlock.style.paddingLeft = "1ch";
  66. lines.forEach((_, index) => {
  67. const lineNumber = document.createElement('div');
  68. lineNumber.style.lineHeight = '1.5';
  69. lineNumber.style.margin = '0';
  70. lineNumber.style.padding = '0';
  71. lineNumber.textContent = (index + 1).toString();
  72. lineNumbersElement.appendChild(lineNumber);
  73. });
  74. }
  75.  
  76. function addLineNumbersInline(container, useLiveUpdates) {
  77. const codeBlock = container.querySelector('code');
  78. if (!codeBlock || container.querySelector('.line-numbers-inline')) return;
  79. container.style.position = 'relative';
  80. container.style.display = 'flex';
  81. container.style.alignItems = 'flex-start';
  82. container.style.padding = '0';
  83. container.style.margin = '0';
  84. container.style.overflowX = 'auto';
  85. container.style.overflowY = 'hidden';
  86.  
  87. const lineNumbersElement = createLineNumbersElement();
  88. container.insertBefore(lineNumbersElement, codeBlock);
  89.  
  90. if (useLiveUpdates) {
  91. updateLineNumbers(codeBlock, lineNumbersElement);
  92.  
  93. let updateScheduled = false;
  94. const observer = new MutationObserver(() => {
  95. if (!updateScheduled) {
  96. updateScheduled = true;
  97. requestAnimationFrame(() => {
  98. updateLineNumbers(codeBlock, lineNumbersElement);
  99. updateScheduled = false;
  100. });
  101. }
  102. });
  103.  
  104. observer.observe(codeBlock, {
  105. childList: true,
  106. subtree: true,
  107. characterData: true,
  108. });
  109. } else {
  110. let updateTimeout;
  111. function debouncedUpdate() {
  112. clearTimeout(updateTimeout);
  113. updateTimeout = setTimeout(() => {
  114. updateLineNumbers(codeBlock, lineNumbersElement);
  115. }, 500);
  116. }
  117.  
  118. const observer = new MutationObserver(mutations => {
  119. let shouldUpdate = false;
  120. mutations.forEach(mutation => {
  121. if (mutation.type === 'childList' || mutation.type === 'characterData') {
  122. shouldUpdate = true;
  123. }
  124. });
  125. if (shouldUpdate) {
  126. debouncedUpdate();
  127. }
  128. });
  129.  
  130. observer.observe(codeBlock, {
  131. childList: true,
  132. subtree: true,
  133. characterData: true,
  134. });
  135.  
  136. debouncedUpdate();
  137. }
  138. }
  139.  
  140. function observeCodeBlocks(useLiveUpdates) {
  141. const observer = new MutationObserver(mutations => {
  142. mutations.forEach(mutation => {
  143. if (mutation.addedNodes.length > 0) {
  144. mutation.addedNodes.forEach(node => {
  145. if (node.nodeType === 1) {
  146. const codeContainers = node.querySelectorAll('div.overflow-y-auto');
  147. codeContainers.forEach(container => addLineNumbersInline(container, useLiveUpdates));
  148. }
  149. });
  150. }
  151. });
  152. });
  153.  
  154. observer.observe(document.body, {
  155. childList: true,
  156. subtree: true,
  157. });
  158.  
  159. document.querySelectorAll('div.overflow-y-auto').forEach(container => {
  160. addLineNumbersInline(container, useLiveUpdates);
  161. });
  162. }
  163.  
  164. observeCodeBlocks(liveLineNumbers);
  165.  
  166. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址