org/internal/ui/latex.go

380 lines
8.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}