mirror of
https://github.com/RWejlgaard/org.git
synced 2026-05-06 04:34:45 +00:00
fix: latex math support
This commit is contained in:
parent
015feb3637
commit
c8114a5f72
1 changed files with 415 additions and 11 deletions
|
|
@ -660,11 +660,20 @@ func renderNotesWithHighlighting(notes []string) []string {
|
|||
} else if codeBlockDelimiter == "markdown" {
|
||||
// Ending a markdown code block
|
||||
inCodeBlock = false
|
||||
// Highlight and add the code
|
||||
// Highlight and add the code (or render LaTeX)
|
||||
if len(codeLines) > 0 {
|
||||
highlighted := highlightCode(strings.Join(codeLines, "\n"), codeLanguage)
|
||||
highlightedLines := strings.Split(highlighted, "\n")
|
||||
result = append(result, highlightedLines...)
|
||||
var processedLines []string
|
||||
if codeLanguage == "latex" {
|
||||
// Apply LaTeX-to-Unicode conversion
|
||||
for _, line := range codeLines {
|
||||
processedLines = append(processedLines, renderLatexMath(line))
|
||||
}
|
||||
} else {
|
||||
// Apply syntax highlighting
|
||||
highlighted := highlightCode(strings.Join(codeLines, "\n"), codeLanguage)
|
||||
processedLines = strings.Split(highlighted, "\n")
|
||||
}
|
||||
result = append(result, processedLines...)
|
||||
}
|
||||
result = append(result, note) // Keep the delimiter visible
|
||||
codeLines = []string{}
|
||||
|
|
@ -677,11 +686,20 @@ func renderNotesWithHighlighting(notes []string) []string {
|
|||
// Check for org-mode style code block end
|
||||
if strings.HasPrefix(trimmed, "#+END_SRC") {
|
||||
inCodeBlock = false
|
||||
// Highlight and add the code
|
||||
// Highlight and add the code (or render LaTeX)
|
||||
if len(codeLines) > 0 {
|
||||
highlighted := highlightCode(strings.Join(codeLines, "\n"), codeLanguage)
|
||||
highlightedLines := strings.Split(highlighted, "\n")
|
||||
result = append(result, highlightedLines...)
|
||||
var processedLines []string
|
||||
if codeLanguage == "latex" {
|
||||
// Apply LaTeX-to-Unicode conversion
|
||||
for _, line := range codeLines {
|
||||
processedLines = append(processedLines, renderLatexMath(line))
|
||||
}
|
||||
} else {
|
||||
// Apply syntax highlighting
|
||||
highlighted := highlightCode(strings.Join(codeLines, "\n"), codeLanguage)
|
||||
processedLines = strings.Split(highlighted, "\n")
|
||||
}
|
||||
result = append(result, processedLines...)
|
||||
}
|
||||
result = append(result, note) // Keep the delimiter visible
|
||||
codeLines = []string{}
|
||||
|
|
@ -700,9 +718,18 @@ func renderNotesWithHighlighting(notes []string) []string {
|
|||
|
||||
// Handle case where code block wasn't closed
|
||||
if inCodeBlock && len(codeLines) > 0 {
|
||||
highlighted := highlightCode(strings.Join(codeLines, "\n"), codeLanguage)
|
||||
highlightedLines := strings.Split(highlighted, "\n")
|
||||
result = append(result, highlightedLines...)
|
||||
var processedLines []string
|
||||
if codeLanguage == "latex" {
|
||||
// Apply LaTeX-to-Unicode conversion
|
||||
for _, line := range codeLines {
|
||||
processedLines = append(processedLines, renderLatexMath(line))
|
||||
}
|
||||
} else {
|
||||
// Apply syntax highlighting
|
||||
highlighted := highlightCode(strings.Join(codeLines, "\n"), codeLanguage)
|
||||
processedLines = strings.Split(highlighted, "\n")
|
||||
}
|
||||
result = append(result, processedLines...)
|
||||
}
|
||||
|
||||
return result
|
||||
|
|
@ -724,6 +751,383 @@ 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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue