mirror of
https://github.com/RWejlgaard/org.git
synced 2026-05-06 04:34:45 +00:00
380 lines
8.7 KiB
Go
380 lines
8.7 KiB
Go
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
|
||
}
|