From 69b2ead2b62a7ea381c80e190cab1004df786529 Mon Sep 17 00:00:00 2001 From: Rasmus Wejlgaard Date: Sat, 8 Nov 2025 23:21:30 +0000 Subject: [PATCH] move latex junk to another file --- internal/ui/latex.go | 380 +++++++++++++++++++++++++++++++++++++++++++ internal/ui/views.go | 377 ------------------------------------------ 2 files changed, 380 insertions(+), 377 deletions(-) create mode 100644 internal/ui/latex.go diff --git a/internal/ui/latex.go b/internal/ui/latex.go new file mode 100644 index 0000000..914b4c5 --- /dev/null +++ b/internal/ui/latex.go @@ -0,0 +1,380 @@ +package ui + +import "strings" + +// renderLatexMath converts LaTeX math expressions to Unicode for terminal display +func renderLatexMath(latex string) string { + result := latex + + // Remove LaTeX math delimiters + result = strings.ReplaceAll(result, `\(`, "") + result = strings.ReplaceAll(result, `\)`, "") + result = strings.ReplaceAll(result, `\[`, "") + result = strings.ReplaceAll(result, `\]`, "") + result = strings.ReplaceAll(result, `$$`, "") + + // Remove single $ delimiters (being careful not to remove actual dollar signs in text) + // Simple approach: if line starts/ends with $, remove it + result = strings.TrimSpace(result) + if strings.HasPrefix(result, "$") && strings.HasSuffix(result, "$") && len(result) > 2 { + result = result[1 : len(result)-1] + result = strings.TrimSpace(result) + } + + // Map of common LaTeX commands to Unicode equivalents + replacements := map[string]string{ + // Greek letters (lowercase) + `\alpha`: "α", + `\beta`: "β", + `\gamma`: "γ", + `\delta`: "δ", + `\epsilon`: "ε", + `\zeta`: "ζ", + `\eta`: "η", + `\theta`: "θ", + `\iota`: "ι", + `\kappa`: "κ", + `\lambda`: "λ", + `\mu`: "μ", + `\nu`: "ν", + `\xi`: "ξ", + `\pi`: "π", + `\rho`: "ρ", + `\sigma`: "σ", + `\tau`: "τ", + `\upsilon`: "υ", + `\phi`: "φ", + `\chi`: "χ", + `\psi`: "ψ", + `\omega`: "ω", + + // Greek letters (uppercase) + `\Gamma`: "Γ", + `\Delta`: "Δ", + `\Theta`: "Θ", + `\Lambda`: "Λ", + `\Xi`: "Ξ", + `\Pi`: "Π", + `\Sigma`: "Σ", + `\Upsilon`: "Υ", + `\Phi`: "Φ", + `\Psi`: "Ψ", + `\Omega`: "Ω", + + // Math operators + `\times`: "×", + `\div`: "÷", + `\pm`: "±", + `\mp`: "∓", + `\cdot`: "·", + `\star`: "⋆", + `\ast`: "∗", + `\circ`: "∘", + `\bullet`: "•", + + // Relations + `\le`: "≤", + `\ge`: "≥", + `\leq`: "≤", + `\geq`: "≥", + `\ne`: "≠", + `\neq`: "≠", + `\approx`: "≈", + `\equiv`: "≡", + `\sim`: "∼", + `\simeq`: "≃", + `\propto`: "∝", + + // Arrows + `\to`: "→", + `\rightarrow`: "→", + `\leftarrow`: "←", + `\Rightarrow`: "⇒", + `\Leftarrow`: "⇐", + `\mapsto`: "↦", + + // Set theory + `\in`: "∈", + `\notin`: "∉", + `\subset`: "⊂", + `\supset`: "⊃", + `\subseteq`: "⊆", + `\supseteq`: "⊇", + `\cup`: "∪", + `\cap`: "∩", + `\emptyset`: "∅", + `\forall`: "∀", + `\exists`: "∃", + + // Calculus + `\partial`: "∂", + `\nabla`: "∇", + `\int`: "∫", + `\sum`: "∑", + `\prod`: "∏", + `\infty`: "∞", + + // Logic + `\land`: "∧", + `\lor`: "∨", + `\lnot`: "¬", + `\neg`: "¬", + `\wedge`: "∧", + `\vee`: "∨", + + // Special symbols + `\hbar`: "ℏ", + `\ell`: "ℓ", + `\Re`: "ℜ", + `\Im`: "ℑ", + `\angle`: "∠", + `\triangle`: "△", + `\square`: "□", + `\degree`: "°", + + // Superscripts (common ones) + `^0`: "⁰", + `^1`: "¹", + `^2`: "²", + `^3`: "³", + `^4`: "⁴", + `^5`: "⁵", + `^6`: "⁶", + `^7`: "⁷", + `^8`: "⁸", + `^9`: "⁹", + `^+`: "⁺", + `^-`: "⁻", + `^=`: "⁼", + `^(`: "⁽", + `^)`: "⁾", + + // Subscripts (common ones) + `_0`: "₀", + `_1`: "₁", + `_2`: "₂", + `_3`: "₃", + `_4`: "₄", + `_5`: "₅", + `_6`: "₆", + `_7`: "₇", + `_8`: "₈", + `_9`: "₉", + `_+`: "₊", + `_-`: "₋", + `_=`: "₌", + `_(`: "₍", + `_)`: "₎", + } + + // Apply all replacements + for latexCmd, unicode := range replacements { + result = strings.ReplaceAll(result, latexCmd, unicode) + } + + // Handle simple fractions \frac{a}{b} -> a/b + result = handleFractions(result) + + // Handle square roots \sqrt{x} -> √(x) + result = handleSquareRoots(result) + + // Handle superscripts ^{...} and subscripts _{...} + result = handleSuperscripts(result) + result = handleSubscripts(result) + + return result +} + +// handleFractions converts \frac{numerator}{denominator} to numerator/denominator +func handleFractions(text string) string { + // Simple regex-free approach for basic fractions + result := text + for { + start := strings.Index(result, `\frac{`) + if start == -1 { + break + } + + // Find the numerator + numStart := start + 6 + numEnd, numerator := findBracedContent(result, numStart) + if numEnd == -1 { + break + } + + // Find the denominator + if numEnd >= len(result) || result[numEnd] != '{' { + break + } + denomEnd, denominator := findBracedContent(result, numEnd+1) + if denomEnd == -1 { + break + } + + // Replace \frac{num}{denom} with (num)/(denom) + replacement := "(" + numerator + ")/(" + denominator + ")" + result = result[:start] + replacement + result[denomEnd:] + } + return result +} + +// handleSquareRoots converts \sqrt{x} to √(x) +func handleSquareRoots(text string) string { + result := text + for { + start := strings.Index(result, `\sqrt{`) + if start == -1 { + break + } + + // Find the content + contentStart := start + 6 + contentEnd, content := findBracedContent(result, contentStart) + if contentEnd == -1 { + break + } + + // Replace \sqrt{content} with √(content) + replacement := "√(" + content + ")" + result = result[:start] + replacement + result[contentEnd:] + } + return result +} + +// findBracedContent finds content within braces starting at position i +// Returns the position after the closing brace and the content +func findBracedContent(text string, start int) (int, string) { + if start >= len(text) { + return -1, "" + } + + depth := 1 + i := start + + for i < len(text) && depth > 0 { + if text[i] == '{' { + depth++ + } else if text[i] == '}' { + depth-- + if depth == 0 { + return i + 1, text[start:i] + } + } + i++ + } + + return -1, "" +} + +// handleSuperscripts converts ^{...} to Unicode superscripts where possible +func handleSuperscripts(text string) string { + superscriptMap := map[rune]string{ + '0': "⁰", '1': "¹", '2': "²", '3': "³", '4': "⁴", + '5': "⁵", '6': "⁶", '7': "⁷", '8': "⁸", '9': "⁹", + 'a': "ᵃ", 'b': "ᵇ", 'c': "ᶜ", 'd': "ᵈ", 'e': "ᵉ", + 'f': "ᶠ", 'g': "ᵍ", 'h': "ʰ", 'i': "ⁱ", 'j': "ʲ", + 'k': "ᵏ", 'l': "ˡ", 'm': "ᵐ", 'n': "ⁿ", 'o': "ᵒ", + 'p': "ᵖ", 'r': "ʳ", 's': "ˢ", 't': "ᵗ", 'u': "ᵘ", + 'v': "ᵛ", 'w': "ʷ", 'x': "ˣ", 'y': "ʸ", 'z': "ᶻ", + 'A': "ᴬ", 'B': "ᴮ", 'D': "ᴰ", 'E': "ᴱ", 'G': "ᴳ", + 'H': "ᴴ", 'I': "ᴵ", 'J': "ᴶ", 'K': "ᴷ", 'L': "ᴸ", + 'M': "ᴹ", 'N': "ᴺ", 'O': "ᴼ", 'P': "ᴾ", 'R': "ᴿ", + 'T': "ᵀ", 'U': "ᵁ", 'V': "ⱽ", 'W': "ᵂ", + '+': "⁺", '-': "⁻", '=': "⁼", '(': "⁽", ')': "⁾", + } + + result := text + + // Handle ^{...} format + for { + start := strings.Index(result, "^{") + if start == -1 { + break + } + + contentStart := start + 2 + contentEnd, content := findBracedContent(result, contentStart) + if contentEnd == -1 { + break + } + + // Convert content to superscript + var superscript strings.Builder + for _, ch := range content { + if sup, ok := superscriptMap[ch]; ok { + superscript.WriteString(sup) + } else { + // If no superscript version exists, wrap in parentheses + superscript.WriteRune(ch) + } + } + + result = result[:start] + superscript.String() + result[contentEnd:] + } + + // Handle simple ^x format (single character without braces) + for i := 0; i < len(result)-1; i++ { + if result[i] == '^' && result[i+1] != '{' { + ch := rune(result[i+1]) + if sup, ok := superscriptMap[ch]; ok { + result = result[:i] + sup + result[i+2:] + } + } + } + + return result +} + +// handleSubscripts converts _{...} to Unicode subscripts where possible +func handleSubscripts(text string) string { + subscriptMap := map[rune]string{ + '0': "₀", '1': "₁", '2': "₂", '3': "₃", '4': "₄", + '5': "₅", '6': "₆", '7': "₇", '8': "₈", '9': "₉", + 'a': "ₐ", 'e': "ₑ", 'h': "ₕ", 'i': "ᵢ", 'j': "ⱼ", + 'k': "ₖ", 'l': "ₗ", 'm': "ₘ", 'n': "ₙ", 'o': "ₒ", + 'p': "ₚ", 'r': "ᵣ", 's': "ₛ", 't': "ₜ", 'u': "ᵤ", + 'v': "ᵥ", 'x': "ₓ", + '+': "₊", '-': "₋", '=': "₌", '(': "₍", ')': "₎", + } + + result := text + + // Handle _{...} format + for { + start := strings.Index(result, "_{") + if start == -1 { + break + } + + contentStart := start + 2 + contentEnd, content := findBracedContent(result, contentStart) + if contentEnd == -1 { + break + } + + // Convert content to subscript + var subscript strings.Builder + for _, ch := range content { + if sub, ok := subscriptMap[ch]; ok { + subscript.WriteString(sub) + } else { + // If no subscript version exists, wrap in parentheses + subscript.WriteRune(ch) + } + } + + result = result[:start] + subscript.String() + result[contentEnd:] + } + + // Handle simple _x format (single character without braces) + for i := 0; i < len(result)-1; i++ { + if result[i] == '_' && result[i+1] != '{' { + ch := rune(result[i+1]) + if sub, ok := subscriptMap[ch]; ok { + result = result[:i] + sub + result[i+2:] + } + } + } + + return result +} diff --git a/internal/ui/views.go b/internal/ui/views.go index 310b55b..c56834b 100644 --- a/internal/ui/views.go +++ b/internal/ui/views.go @@ -751,383 +751,6 @@ func highlightCode(code, language string) string { return strings.TrimRight(buf.String(), "\n") } -// renderLatexMath converts LaTeX math expressions to Unicode for terminal display -func renderLatexMath(latex string) string { - result := latex - - // Remove LaTeX math delimiters - result = strings.ReplaceAll(result, `\(`, "") - result = strings.ReplaceAll(result, `\)`, "") - result = strings.ReplaceAll(result, `\[`, "") - result = strings.ReplaceAll(result, `\]`, "") - result = strings.ReplaceAll(result, `$$`, "") - - // Remove single $ delimiters (being careful not to remove actual dollar signs in text) - // Simple approach: if line starts/ends with $, remove it - result = strings.TrimSpace(result) - if strings.HasPrefix(result, "$") && strings.HasSuffix(result, "$") && len(result) > 2 { - result = result[1 : len(result)-1] - result = strings.TrimSpace(result) - } - - // Map of common LaTeX commands to Unicode equivalents - replacements := map[string]string{ - // Greek letters (lowercase) - `\alpha`: "α", - `\beta`: "β", - `\gamma`: "γ", - `\delta`: "δ", - `\epsilon`: "ε", - `\zeta`: "ζ", - `\eta`: "η", - `\theta`: "θ", - `\iota`: "ι", - `\kappa`: "κ", - `\lambda`: "λ", - `\mu`: "μ", - `\nu`: "ν", - `\xi`: "ξ", - `\pi`: "π", - `\rho`: "ρ", - `\sigma`: "σ", - `\tau`: "τ", - `\upsilon`: "υ", - `\phi`: "φ", - `\chi`: "χ", - `\psi`: "ψ", - `\omega`: "ω", - - // Greek letters (uppercase) - `\Gamma`: "Γ", - `\Delta`: "Δ", - `\Theta`: "Θ", - `\Lambda`: "Λ", - `\Xi`: "Ξ", - `\Pi`: "Π", - `\Sigma`: "Σ", - `\Upsilon`: "Υ", - `\Phi`: "Φ", - `\Psi`: "Ψ", - `\Omega`: "Ω", - - // Math operators - `\times`: "×", - `\div`: "÷", - `\pm`: "±", - `\mp`: "∓", - `\cdot`: "·", - `\star`: "⋆", - `\ast`: "∗", - `\circ`: "∘", - `\bullet`: "•", - - // Relations - `\le`: "≤", - `\ge`: "≥", - `\leq`: "≤", - `\geq`: "≥", - `\ne`: "≠", - `\neq`: "≠", - `\approx`: "≈", - `\equiv`: "≡", - `\sim`: "∼", - `\simeq`: "≃", - `\propto`: "∝", - - // Arrows - `\to`: "→", - `\rightarrow`: "→", - `\leftarrow`: "←", - `\Rightarrow`: "⇒", - `\Leftarrow`: "⇐", - `\mapsto`: "↦", - - // Set theory - `\in`: "∈", - `\notin`: "∉", - `\subset`: "⊂", - `\supset`: "⊃", - `\subseteq`: "⊆", - `\supseteq`: "⊇", - `\cup`: "∪", - `\cap`: "∩", - `\emptyset`: "∅", - `\forall`: "∀", - `\exists`: "∃", - - // Calculus - `\partial`: "∂", - `\nabla`: "∇", - `\int`: "∫", - `\sum`: "∑", - `\prod`: "∏", - `\infty`: "∞", - - // Logic - `\land`: "∧", - `\lor`: "∨", - `\lnot`: "¬", - `\neg`: "¬", - `\wedge`: "∧", - `\vee`: "∨", - - // Special symbols - `\hbar`: "ℏ", - `\ell`: "ℓ", - `\Re`: "ℜ", - `\Im`: "ℑ", - `\angle`: "∠", - `\triangle`: "△", - `\square`: "□", - `\degree`: "°", - - // Superscripts (common ones) - `^0`: "⁰", - `^1`: "¹", - `^2`: "²", - `^3`: "³", - `^4`: "⁴", - `^5`: "⁵", - `^6`: "⁶", - `^7`: "⁷", - `^8`: "⁸", - `^9`: "⁹", - `^+`: "⁺", - `^-`: "⁻", - `^=`: "⁼", - `^(`: "⁽", - `^)`: "⁾", - - // Subscripts (common ones) - `_0`: "₀", - `_1`: "₁", - `_2`: "₂", - `_3`: "₃", - `_4`: "₄", - `_5`: "₅", - `_6`: "₆", - `_7`: "₇", - `_8`: "₈", - `_9`: "₉", - `_+`: "₊", - `_-`: "₋", - `_=`: "₌", - `_(`: "₍", - `_)`: "₎", - } - - // Apply all replacements - for latexCmd, unicode := range replacements { - result = strings.ReplaceAll(result, latexCmd, unicode) - } - - // Handle simple fractions \frac{a}{b} -> a/b - result = handleFractions(result) - - // Handle square roots \sqrt{x} -> √(x) - result = handleSquareRoots(result) - - // Handle superscripts ^{...} and subscripts _{...} - result = handleSuperscripts(result) - result = handleSubscripts(result) - - return result -} - -// handleFractions converts \frac{numerator}{denominator} to numerator/denominator -func handleFractions(text string) string { - // Simple regex-free approach for basic fractions - result := text - for { - start := strings.Index(result, `\frac{`) - if start == -1 { - break - } - - // Find the numerator - numStart := start + 6 - numEnd, numerator := findBracedContent(result, numStart) - if numEnd == -1 { - break - } - - // Find the denominator - if numEnd >= len(result) || result[numEnd] != '{' { - break - } - denomEnd, denominator := findBracedContent(result, numEnd+1) - if denomEnd == -1 { - break - } - - // Replace \frac{num}{denom} with (num)/(denom) - replacement := "(" + numerator + ")/(" + denominator + ")" - result = result[:start] + replacement + result[denomEnd:] - } - return result -} - -// handleSquareRoots converts \sqrt{x} to √(x) -func handleSquareRoots(text string) string { - result := text - for { - start := strings.Index(result, `\sqrt{`) - if start == -1 { - break - } - - // Find the content - contentStart := start + 6 - contentEnd, content := findBracedContent(result, contentStart) - if contentEnd == -1 { - break - } - - // Replace \sqrt{content} with √(content) - replacement := "√(" + content + ")" - result = result[:start] + replacement + result[contentEnd:] - } - return result -} - -// findBracedContent finds content within braces starting at position i -// Returns the position after the closing brace and the content -func findBracedContent(text string, start int) (int, string) { - if start >= len(text) { - return -1, "" - } - - depth := 1 - i := start - - for i < len(text) && depth > 0 { - if text[i] == '{' { - depth++ - } else if text[i] == '}' { - depth-- - if depth == 0 { - return i + 1, text[start:i] - } - } - i++ - } - - return -1, "" -} - -// handleSuperscripts converts ^{...} to Unicode superscripts where possible -func handleSuperscripts(text string) string { - superscriptMap := map[rune]string{ - '0': "⁰", '1': "¹", '2': "²", '3': "³", '4': "⁴", - '5': "⁵", '6': "⁶", '7': "⁷", '8': "⁸", '9': "⁹", - 'a': "ᵃ", 'b': "ᵇ", 'c': "ᶜ", 'd': "ᵈ", 'e': "ᵉ", - 'f': "ᶠ", 'g': "ᵍ", 'h': "ʰ", 'i': "ⁱ", 'j': "ʲ", - 'k': "ᵏ", 'l': "ˡ", 'm': "ᵐ", 'n': "ⁿ", 'o': "ᵒ", - 'p': "ᵖ", 'r': "ʳ", 's': "ˢ", 't': "ᵗ", 'u': "ᵘ", - 'v': "ᵛ", 'w': "ʷ", 'x': "ˣ", 'y': "ʸ", 'z': "ᶻ", - 'A': "ᴬ", 'B': "ᴮ", 'D': "ᴰ", 'E': "ᴱ", 'G': "ᴳ", - 'H': "ᴴ", 'I': "ᴵ", 'J': "ᴶ", 'K': "ᴷ", 'L': "ᴸ", - 'M': "ᴹ", 'N': "ᴺ", 'O': "ᴼ", 'P': "ᴾ", 'R': "ᴿ", - 'T': "ᵀ", 'U': "ᵁ", 'V': "ⱽ", 'W': "ᵂ", - '+': "⁺", '-': "⁻", '=': "⁼", '(': "⁽", ')': "⁾", - } - - result := text - - // Handle ^{...} format - for { - start := strings.Index(result, "^{") - if start == -1 { - break - } - - contentStart := start + 2 - contentEnd, content := findBracedContent(result, contentStart) - if contentEnd == -1 { - break - } - - // Convert content to superscript - var superscript strings.Builder - for _, ch := range content { - if sup, ok := superscriptMap[ch]; ok { - superscript.WriteString(sup) - } else { - // If no superscript version exists, wrap in parentheses - superscript.WriteRune(ch) - } - } - - result = result[:start] + superscript.String() + result[contentEnd:] - } - - // Handle simple ^x format (single character without braces) - for i := 0; i < len(result)-1; i++ { - if result[i] == '^' && result[i+1] != '{' { - ch := rune(result[i+1]) - if sup, ok := superscriptMap[ch]; ok { - result = result[:i] + sup + result[i+2:] - } - } - } - - return result -} - -// handleSubscripts converts _{...} to Unicode subscripts where possible -func handleSubscripts(text string) string { - subscriptMap := map[rune]string{ - '0': "₀", '1': "₁", '2': "₂", '3': "₃", '4': "₄", - '5': "₅", '6': "₆", '7': "₇", '8': "₈", '9': "₉", - 'a': "ₐ", 'e': "ₑ", 'h': "ₕ", 'i': "ᵢ", 'j': "ⱼ", - 'k': "ₖ", 'l': "ₗ", 'm': "ₘ", 'n': "ₙ", 'o': "ₒ", - 'p': "ₚ", 'r': "ᵣ", 's': "ₛ", 't': "ₜ", 'u': "ᵤ", - 'v': "ᵥ", 'x': "ₓ", - '+': "₊", '-': "₋", '=': "₌", '(': "₍", ')': "₎", - } - - result := text - - // Handle _{...} format - for { - start := strings.Index(result, "_{") - if start == -1 { - break - } - - contentStart := start + 2 - contentEnd, content := findBracedContent(result, contentStart) - if contentEnd == -1 { - break - } - - // Convert content to subscript - var subscript strings.Builder - for _, ch := range content { - if sub, ok := subscriptMap[ch]; ok { - subscript.WriteString(sub) - } else { - // If no subscript version exists, wrap in parentheses - subscript.WriteRune(ch) - } - } - - result = result[:start] + subscript.String() + result[contentEnd:] - } - - // Handle simple _x format (single character without braces) - for i := 0; i < len(result)-1; i++ { - if result[i] == '_' && result[i+1] != '{' { - ch := rune(result[i+1]) - if sub, ok := subscriptMap[ch]; ok { - result = result[:i] + sub + result[i+2:] - } - } - } - - return result -} - func (m uiModel) renderItem(item *model.Item, isCursor bool) string { var b strings.Builder