From c596bdd99a6041683f8b1b6a97e35fc009b6e592 Mon Sep 17 00:00:00 2001 From: Nicolas Varrot Date: Fri, 13 Feb 2026 19:53:04 +0000 Subject: [PATCH] feat: line numbers in code blocks with toggle button - Show line numbers for code blocks with more than 3 lines - Toggle button (# icon + line count) in the language header bar - Preference persisted in localStorage - Line numbers are non-selectable (won't copy with code) - Hidden for short snippets to reduce visual noise --- src/components/CodeBlock.tsx | 56 ++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/src/components/CodeBlock.tsx b/src/components/CodeBlock.tsx index 18bc291..1d785ce 100644 --- a/src/components/CodeBlock.tsx +++ b/src/components/CodeBlock.tsx @@ -1,5 +1,5 @@ import { useState, useCallback, type HTMLAttributes, type ReactElement } from 'react'; -import { Check, Copy } from 'lucide-react'; +import { Check, Copy, Hash } from 'lucide-react'; /** Extract the language from the nested element's className (e.g. "language-ts"). */ function extractLanguage(children: React.ReactNode): string | null { @@ -42,29 +42,73 @@ function formatLanguage(lang: string): string { * Custom
 renderer for ReactMarkdown.
  * Wraps code blocks with a language label and a floating copy button.
  */
+const LINE_NUMBER_KEY = 'pinchchat-line-numbers';
+const LINE_THRESHOLD = 3; // Only show line numbers for blocks with more than this many lines
+
 export function CodeBlock(props: HTMLAttributes) {
   const [copied, setCopied] = useState(false);
+  const [showLineNumbers, setShowLineNumbers] = useState(() => {
+    const stored = localStorage.getItem(LINE_NUMBER_KEY);
+    return stored === null ? true : stored === 'true';
+  });
   const language = extractLanguage(props.children);
 
+  const code = (props.children as ReactElement<{ children?: string }> | undefined)?.props?.children;
+  const lines = typeof code === 'string' ? code.replace(/\n$/, '').split('\n') : [];
+  const hasEnoughLines = lines.length > LINE_THRESHOLD;
+
   const handleCopy = useCallback(() => {
-    // Extract text from the nested  element
-    const code = (props.children as ReactElement<{ children?: string }> | undefined)?.props?.children;
     if (typeof code === 'string') {
       navigator.clipboard.writeText(code).then(() => {
         setCopied(true);
         setTimeout(() => setCopied(false), 2000);
       });
     }
-  }, [props.children]);
+  }, [code]);
+
+  const toggleLineNumbers = useCallback(() => {
+    setShowLineNumbers(prev => {
+      const next = !prev;
+      localStorage.setItem(LINE_NUMBER_KEY, String(next));
+      return next;
+    });
+  }, []);
+
+  const shouldShowNumbers = showLineNumbers && hasEnoughLines;
 
   return (
     
{language && (
- {formatLanguage(language)} + {formatLanguage(language)} + {hasEnoughLines && ( + + )}
)} -
+      {shouldShowNumbers ? (
+        
+ +
+        
+ ) : ( +
+      )}