Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
|
@@ -10,6 +10,7 @@ from PIL import Image
|
|
| 10 |
import fitz # PyMuPDF
|
| 11 |
import time
|
| 12 |
import re
|
|
|
|
| 13 |
|
| 14 |
# Set page configuration
|
| 15 |
st.set_page_config(
|
|
@@ -191,6 +192,24 @@ def find_environments(latex_code):
|
|
| 191 |
|
| 192 |
return environments
|
| 193 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
# Default LaTeX template
|
| 195 |
default_template = r"""\documentclass{article}
|
| 196 |
\usepackage[utf8]{inputenc}
|
|
@@ -286,7 +305,7 @@ Your conclusion here.
|
|
| 286 |
\end{document}
|
| 287 |
"""
|
| 288 |
|
| 289 |
-
# Enhanced VSCode-like styling
|
| 290 |
st.markdown("""
|
| 291 |
<style>
|
| 292 |
/* Base theming - VS Code inspired */
|
|
@@ -309,48 +328,6 @@ st.markdown("""
|
|
| 309 |
background-color: #1177bb;
|
| 310 |
}
|
| 311 |
|
| 312 |
-
/* Custom editor with built-in syntax highlighting */
|
| 313 |
-
#latex-editor-container {
|
| 314 |
-
position: relative;
|
| 315 |
-
width: 100%;
|
| 316 |
-
height: 600px;
|
| 317 |
-
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
| 318 |
-
font-size: 14px;
|
| 319 |
-
line-height: 1.5;
|
| 320 |
-
background-color: #1e1e1e;
|
| 321 |
-
color: #d4d4d4;
|
| 322 |
-
border-radius: 4px;
|
| 323 |
-
border: 1px solid #252526;
|
| 324 |
-
padding: 10px;
|
| 325 |
-
}
|
| 326 |
-
|
| 327 |
-
/* Default coloring for textarea */
|
| 328 |
-
.stTextArea textarea {
|
| 329 |
-
font-family: 'Consolas', 'Monaco', 'Courier New', monospace !important;
|
| 330 |
-
font-size: 14px !important;
|
| 331 |
-
line-height: 1.5 !important;
|
| 332 |
-
background-color: #1e1e1e !important;
|
| 333 |
-
color: #d4d4d4 !important;
|
| 334 |
-
padding: 10px !important;
|
| 335 |
-
border-radius: 4px !important;
|
| 336 |
-
border: 1px solid #252526 !important;
|
| 337 |
-
}
|
| 338 |
-
|
| 339 |
-
/* Highlight LaTeX specific elements */
|
| 340 |
-
.stTextArea textarea::selection {
|
| 341 |
-
background-color: #264f78;
|
| 342 |
-
}
|
| 343 |
-
|
| 344 |
-
/* VS Code-like editor container */
|
| 345 |
-
.editor-container {
|
| 346 |
-
background-color: #1e1e1e;
|
| 347 |
-
border-radius: 4px;
|
| 348 |
-
border: 1px solid #2d2d2d;
|
| 349 |
-
margin-bottom: 1rem;
|
| 350 |
-
display: flex;
|
| 351 |
-
flex-direction: column;
|
| 352 |
-
}
|
| 353 |
-
|
| 354 |
/* Editor tab bar */
|
| 355 |
.tab-bar {
|
| 356 |
display: flex;
|
|
@@ -693,171 +670,15 @@ st.markdown("""
|
|
| 693 |
margin-right: 10px;
|
| 694 |
}
|
| 695 |
|
| 696 |
-
/*
|
| 697 |
-
|
| 698 |
-
|
|
|
|
|
|
|
| 699 |
}
|
| 700 |
</style>
|
| 701 |
-
|
| 702 |
-
<div id="syntax-styles">
|
| 703 |
-
<style>
|
| 704 |
-
.latex-command { color: #569cd6; }
|
| 705 |
-
.latex-environment { color: #4ec9b0; }
|
| 706 |
-
.latex-bracket { color: #d4d4d4; }
|
| 707 |
-
.latex-math { color: #c586c0; }
|
| 708 |
-
.latex-comment { color: #6a9955; }
|
| 709 |
-
.latex-package { color: #dcdcaa; }
|
| 710 |
-
</style>
|
| 711 |
-
</div>
|
| 712 |
-
|
| 713 |
-
<script>
|
| 714 |
-
// Add inline syntax highlighting support for the editor
|
| 715 |
-
document.addEventListener('DOMContentLoaded', function() {
|
| 716 |
-
// Function to apply syntax highlighting to the textarea
|
| 717 |
-
function applySyntaxHighlighting() {
|
| 718 |
-
const textarea = document.querySelector('textarea');
|
| 719 |
-
if (!textarea) return;
|
| 720 |
-
|
| 721 |
-
// Find text matches and colorize them
|
| 722 |
-
const text = textarea.value;
|
| 723 |
-
|
| 724 |
-
// Color LaTeX commands
|
| 725 |
-
textarea.style.color = '#d4d4d4'; // Default text color
|
| 726 |
-
|
| 727 |
-
// Apply specific colors for different LaTeX constructs
|
| 728 |
-
// We can't do this directly in the textarea, but we indicate the presence of these elements
|
| 729 |
-
if (text.match(/\\usepackage/g)) {
|
| 730 |
-
const packageButtons = document.querySelectorAll('.toolbar-button[title="Add Package"]');
|
| 731 |
-
packageButtons.forEach(button => button.style.color = '#dcdcaa');
|
| 732 |
-
}
|
| 733 |
-
|
| 734 |
-
if (text.match(/\\begin\{align\}/g)) {
|
| 735 |
-
const alignButtons = document.querySelectorAll('.toolbar-button[title="Align"]');
|
| 736 |
-
alignButtons.forEach(button => button.style.color = '#4ec9b0');
|
| 737 |
-
}
|
| 738 |
-
|
| 739 |
-
if (text.match(/\\\w+/g)) {
|
| 740 |
-
// Commands are present
|
| 741 |
-
}
|
| 742 |
-
|
| 743 |
-
if (text.match(/\$/g)) {
|
| 744 |
-
const mathButtons = document.querySelectorAll('.toolbar-button[title="Inline Math"]');
|
| 745 |
-
mathButtons.forEach(button => button.style.color = '#c586c0');
|
| 746 |
-
}
|
| 747 |
-
}
|
| 748 |
-
|
| 749 |
-
// Set up an interval to periodically check and update highlighting
|
| 750 |
-
setInterval(applySyntaxHighlighting, 1000);
|
| 751 |
-
});
|
| 752 |
-
</script>
|
| 753 |
""", unsafe_allow_html=True)
|
| 754 |
|
| 755 |
-
# Function to create a functional VS Code-like toolbar
|
| 756 |
-
def render_functional_toolbar():
|
| 757 |
-
toolbar_html = """
|
| 758 |
-
<div class="toolbar">
|
| 759 |
-
<button class="toolbar-button" title="Bold" onclick="insertAtCursor('\\\\textbf{', '}')">
|
| 760 |
-
<strong>B</strong>
|
| 761 |
-
</button>
|
| 762 |
-
<button class="toolbar-button" title="Italic" onclick="insertAtCursor('\\\\textit{', '}')">
|
| 763 |
-
<em>I</em>
|
| 764 |
-
</button>
|
| 765 |
-
<button class="toolbar-button" title="Inline Math" onclick="insertAtCursor('$', '$')">
|
| 766 |
-
∑
|
| 767 |
-
</button>
|
| 768 |
-
<div class="toolbar-separator"></div>
|
| 769 |
-
<button class="toolbar-button" title="Section" onclick="insertAtCursor('\\\\section{', '}')">
|
| 770 |
-
§
|
| 771 |
-
</button>
|
| 772 |
-
<button class="toolbar-button" title="Subsection" onclick="insertAtCursor('\\\\subsection{', '}')">
|
| 773 |
-
§§
|
| 774 |
-
</button>
|
| 775 |
-
<div class="toolbar-separator"></div>
|
| 776 |
-
<button class="toolbar-button" title="Itemize" onclick="insertAtCursor('\\\\begin{itemize}\\n \\\\item ', '\\n\\\\end{itemize}')">
|
| 777 |
-
•
|
| 778 |
-
</button>
|
| 779 |
-
<button class="toolbar-button" title="Enumerate" onclick="insertAtCursor('\\\\begin{enumerate}\\n \\\\item ', '\\n\\\\end{enumerate}')">
|
| 780 |
-
1.
|
| 781 |
-
</button>
|
| 782 |
-
<div class="toolbar-separator"></div>
|
| 783 |
-
<button class="toolbar-button" title="Equation" onclick="insertAtCursor('\\\\begin{equation}\\n ', '\\n\\\\end{equation}')">
|
| 784 |
-
=
|
| 785 |
-
</button>
|
| 786 |
-
<button class="toolbar-button" title="Align" onclick="insertAtCursor('\\\\begin{align}\\n ', '\\n\\\\end{align}')">
|
| 787 |
-
≡
|
| 788 |
-
</button>
|
| 789 |
-
<button class="toolbar-button" title="Figure" onclick="insertAtCursor('\\\\begin{figure}[h]\\n \\\\centering\\n \\\\includegraphics[width=0.8\\\\textwidth]{', '}\\n \\\\caption{Caption}\\n \\\\label{fig:label}\\n\\\\end{figure}')">
|
| 790 |
-
🖼
|
| 791 |
-
</button>
|
| 792 |
-
<button class="toolbar-button" title="Table" onclick="insertAtCursor('\\\\begin{table}[h]\\n \\\\centering\\n \\\\begin{tabular}{ccc}\\n ', ' & B & C \\\\\\\\\\n 1 & 2 & 3 \\\\\\\\\\n \\\\end{tabular}\\n \\\\caption{Caption}\\n \\\\label{tab:label}\\n\\\\end{table}')">
|
| 793 |
-
⊞
|
| 794 |
-
</button>
|
| 795 |
-
<div class="toolbar-separator"></div>
|
| 796 |
-
<button class="toolbar-button" title="Fraction" onclick="insertAtCursor('\\\\frac{', '}{denominator}')">
|
| 797 |
-
⅟
|
| 798 |
-
</button>
|
| 799 |
-
<button class="toolbar-button" title="Square Root" onclick="insertAtCursor('\\\\sqrt{', '}')">
|
| 800 |
-
√
|
| 801 |
-
</button>
|
| 802 |
-
<button class="toolbar-button" title="Add Package" onclick="insertAtCursor('\\\\usepackage{', '}')">
|
| 803 |
-
📦
|
| 804 |
-
</button>
|
| 805 |
-
</div>
|
| 806 |
-
|
| 807 |
-
<script>
|
| 808 |
-
function insertAtCursor(before, after) {
|
| 809 |
-
// Find the textarea element
|
| 810 |
-
var textarea = document.querySelector('textarea');
|
| 811 |
-
if (!textarea) return;
|
| 812 |
-
|
| 813 |
-
// Get current cursor position
|
| 814 |
-
var startPos = textarea.selectionStart;
|
| 815 |
-
var endPos = textarea.selectionEnd;
|
| 816 |
-
|
| 817 |
-
// Get text before and after selection
|
| 818 |
-
var textBefore = textarea.value.substring(0, startPos);
|
| 819 |
-
var textSelected = textarea.value.substring(startPos, endPos);
|
| 820 |
-
var textAfter = textarea.value.substring(endPos);
|
| 821 |
-
|
| 822 |
-
// Insert the text
|
| 823 |
-
var newText;
|
| 824 |
-
if (textSelected) {
|
| 825 |
-
// If text is selected, wrap it with before and after
|
| 826 |
-
newText = textBefore + before + textSelected + after + textAfter;
|
| 827 |
-
} else {
|
| 828 |
-
// If no text is selected, just insert before and after
|
| 829 |
-
newText = textBefore + before + after + textAfter;
|
| 830 |
-
// Put cursor between before and after
|
| 831 |
-
var newCursorPos = startPos + before.length;
|
| 832 |
-
}
|
| 833 |
-
|
| 834 |
-
// Update textarea value
|
| 835 |
-
textarea.value = newText;
|
| 836 |
-
|
| 837 |
-
// Set selection range to position cursor appropriately
|
| 838 |
-
if (textSelected) {
|
| 839 |
-
// If text was selected, select the wrapped text
|
| 840 |
-
textarea.setSelectionRange(startPos, endPos + before.length + after.length);
|
| 841 |
-
} else {
|
| 842 |
-
// Otherwise position cursor between before and after
|
| 843 |
-
textarea.setSelectionRange(newCursorPos, newCursorPos);
|
| 844 |
-
}
|
| 845 |
-
|
| 846 |
-
// Focus on textarea
|
| 847 |
-
textarea.focus();
|
| 848 |
-
|
| 849 |
-
// Trigger an input event to update Streamlit
|
| 850 |
-
var event = new Event('input', { bubbles: true });
|
| 851 |
-
textarea.dispatchEvent(event);
|
| 852 |
-
|
| 853 |
-
// Trigger change event to update Streamlit session state
|
| 854 |
-
var changeEvent = new Event('change', { bubbles: true });
|
| 855 |
-
textarea.dispatchEvent(changeEvent);
|
| 856 |
-
}
|
| 857 |
-
</script>
|
| 858 |
-
"""
|
| 859 |
-
st.markdown(toolbar_html, unsafe_allow_html=True)
|
| 860 |
-
|
| 861 |
# VS Code-style editor tabs
|
| 862 |
def render_editor_tabs(active_tab="document.tex"):
|
| 863 |
tab_html = f"""
|
|
@@ -880,6 +701,26 @@ def render_status_bar():
|
|
| 880 |
"""
|
| 881 |
st.markdown(status_html, unsafe_allow_html=True)
|
| 882 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 883 |
# Render a document outline based on section hierarchy
|
| 884 |
def render_document_outline(structure):
|
| 885 |
if not structure:
|
|
@@ -1052,7 +893,7 @@ def render_environments_panel(environments):
|
|
| 1052 |
|
| 1053 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 1054 |
|
| 1055 |
-
# Display a simple F5
|
| 1056 |
def render_f5_shortcut():
|
| 1057 |
st.markdown(
|
| 1058 |
'<div class="shortcut-item">'
|
|
@@ -1062,6 +903,120 @@ def render_f5_shortcut():
|
|
| 1062 |
unsafe_allow_html=True
|
| 1063 |
)
|
| 1064 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1065 |
# Main application
|
| 1066 |
def main():
|
| 1067 |
# Initialize session state
|
|
@@ -1094,20 +1049,74 @@ def main():
|
|
| 1094 |
# Tab bar
|
| 1095 |
render_editor_tabs()
|
| 1096 |
|
| 1097 |
-
# Toolbar
|
| 1098 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1099 |
|
| 1100 |
-
|
| 1101 |
-
|
| 1102 |
-
|
| 1103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1104 |
value=st.session_state.latex_code,
|
| 1105 |
height=500,
|
| 1106 |
-
|
| 1107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1108 |
)
|
|
|
|
| 1109 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 1110 |
-
|
|
|
|
|
|
|
| 1111 |
|
| 1112 |
# Status bar
|
| 1113 |
render_status_bar()
|
|
|
|
| 10 |
import fitz # PyMuPDF
|
| 11 |
import time
|
| 12 |
import re
|
| 13 |
+
from streamlit_monaco import st_monaco
|
| 14 |
|
| 15 |
# Set page configuration
|
| 16 |
st.set_page_config(
|
|
|
|
| 192 |
|
| 193 |
return environments
|
| 194 |
|
| 195 |
+
# Define LaTeX code snippets for toolbar actions
|
| 196 |
+
latex_snippets = {
|
| 197 |
+
"bold": "\\textbf{$0}",
|
| 198 |
+
"italic": "\\textit{$0}",
|
| 199 |
+
"math_inline": "$\n$0\n$",
|
| 200 |
+
"section": "\\section{$0}",
|
| 201 |
+
"subsection": "\\subsection{$0}",
|
| 202 |
+
"itemize": "\\begin{itemize}\n\t\\item $0\n\\end{itemize}",
|
| 203 |
+
"enumerate": "\\begin{enumerate}\n\t\\item $0\n\\end{enumerate}",
|
| 204 |
+
"equation": "\\begin{equation}\n\t$0\n\\end{equation}",
|
| 205 |
+
"align": "\\begin{align}\n\t$0\n\\end{align}",
|
| 206 |
+
"figure": "\\begin{figure}[h]\n\t\\centering\n\t\\includegraphics[width=0.8\\textwidth]{$0}\n\t\\caption{Caption}\n\t\\label{fig:label}\n\\end{figure}",
|
| 207 |
+
"table": "\\begin{table}[h]\n\t\\centering\n\t\\begin{tabular}{ccc}\n\t\t$0 & B & C \\\\\n\t\t1 & 2 & 3 \\\\\n\t\\end{tabular}\n\t\\caption{Caption}\n\t\\label{tab:label}\n\\end{table}",
|
| 208 |
+
"fraction": "\\frac{$0}{denominator}",
|
| 209 |
+
"sqrt": "\\sqrt{$0}",
|
| 210 |
+
"package": "\\usepackage{$0}"
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
# Default LaTeX template
|
| 214 |
default_template = r"""\documentclass{article}
|
| 215 |
\usepackage[utf8]{inputenc}
|
|
|
|
| 305 |
\end{document}
|
| 306 |
"""
|
| 307 |
|
| 308 |
+
# Enhanced VSCode-like styling
|
| 309 |
st.markdown("""
|
| 310 |
<style>
|
| 311 |
/* Base theming - VS Code inspired */
|
|
|
|
| 328 |
background-color: #1177bb;
|
| 329 |
}
|
| 330 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 331 |
/* Editor tab bar */
|
| 332 |
.tab-bar {
|
| 333 |
display: flex;
|
|
|
|
| 670 |
margin-right: 10px;
|
| 671 |
}
|
| 672 |
|
| 673 |
+
/* Monaco editor container */
|
| 674 |
+
.monaco-editor-container {
|
| 675 |
+
border: 1px solid #2d2d2d;
|
| 676 |
+
border-radius: 4px;
|
| 677 |
+
overflow: hidden;
|
| 678 |
}
|
| 679 |
</style>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 680 |
""", unsafe_allow_html=True)
|
| 681 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 682 |
# VS Code-style editor tabs
|
| 683 |
def render_editor_tabs(active_tab="document.tex"):
|
| 684 |
tab_html = f"""
|
|
|
|
| 701 |
"""
|
| 702 |
st.markdown(status_html, unsafe_allow_html=True)
|
| 703 |
|
| 704 |
+
# VS Code-style toolbar buttons
|
| 705 |
+
def render_toolbar_buttons():
|
| 706 |
+
col1, col2, col3, col4, col5 = st.columns([1, 1, 1, 1, 5])
|
| 707 |
+
|
| 708 |
+
with col1:
|
| 709 |
+
st.button("Bold", key="btn_bold", help="Insert bold text")
|
| 710 |
+
st.button("Italic", key="btn_italic", help="Insert italic text")
|
| 711 |
+
|
| 712 |
+
with col2:
|
| 713 |
+
st.button("Section", key="btn_section", help="Insert section heading")
|
| 714 |
+
st.button("Subsection", key="btn_subsection", help="Insert subsection heading")
|
| 715 |
+
|
| 716 |
+
with col3:
|
| 717 |
+
st.button("Itemize", key="btn_itemize", help="Insert bulleted list")
|
| 718 |
+
st.button("Enumerate", key="btn_enumerate", help="Insert numbered list")
|
| 719 |
+
|
| 720 |
+
with col4:
|
| 721 |
+
st.button("Equation", key="btn_equation", help="Insert equation environment")
|
| 722 |
+
st.button("Table", key="btn_table", help="Insert table environment")
|
| 723 |
+
|
| 724 |
# Render a document outline based on section hierarchy
|
| 725 |
def render_document_outline(structure):
|
| 726 |
if not structure:
|
|
|
|
| 893 |
|
| 894 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 895 |
|
| 896 |
+
# Display a simple F5 shortcut reminder
|
| 897 |
def render_f5_shortcut():
|
| 898 |
st.markdown(
|
| 899 |
'<div class="shortcut-item">'
|
|
|
|
| 903 |
unsafe_allow_html=True
|
| 904 |
)
|
| 905 |
|
| 906 |
+
# Define LaTeX language for Monaco editor
|
| 907 |
+
def get_latex_language_definition():
|
| 908 |
+
return {
|
| 909 |
+
"defaultToken": "",
|
| 910 |
+
"tokenPostfix": ".tex",
|
| 911 |
+
"ignoreCase": True,
|
| 912 |
+
|
| 913 |
+
"brackets": [
|
| 914 |
+
{ "token": "delimiter.curly", "open": "{", "close": "}" },
|
| 915 |
+
{ "token": "delimiter.square", "open": "[", "close": "]" },
|
| 916 |
+
{ "token": "delimiter.parenthesis", "open": "(", "close": ")" }
|
| 917 |
+
],
|
| 918 |
+
|
| 919 |
+
"keywords": [
|
| 920 |
+
"documentclass", "usepackage", "begin", "end", "section", "subsection",
|
| 921 |
+
"subsubsection", "chapter", "paragraph", "subparagraph", "part", "appendix",
|
| 922 |
+
"maketitle", "tableofcontents", "figure", "table", "equation", "align",
|
| 923 |
+
"itemize", "enumerate", "description", "center", "verbatim", "minipage",
|
| 924 |
+
"textbf", "textit", "texttt", "textrm", "textsc", "textsf", "emph", "cite",
|
| 925 |
+
"ref", "label", "caption", "includegraphics", "input", "include", "hline",
|
| 926 |
+
"vspace", "hspace", "newpage", "clearpage", "pagebreak", "newcommand",
|
| 927 |
+
"renewcommand", "providecommand", "newenvironment", "renewenvironment",
|
| 928 |
+
"provideenvironment", "newtheorem", "footnote", "item", "multicolumn"
|
| 929 |
+
],
|
| 930 |
+
|
| 931 |
+
"operators": [
|
| 932 |
+
"&", "_", "^", "~", "'", "\"", "`"
|
| 933 |
+
],
|
| 934 |
+
|
| 935 |
+
"tokenizer": {
|
| 936 |
+
"root": [
|
| 937 |
+
[/\\([a-zA-Z]+)/, {
|
| 938 |
+
cases: {
|
| 939 |
+
"@keywords": { token: "keyword.$1" },
|
| 940 |
+
"@default": "keyword"
|
| 941 |
+
}
|
| 942 |
+
}],
|
| 943 |
+
[/\{/, "delimiter.curly"],
|
| 944 |
+
[/\}/, "delimiter.curly"],
|
| 945 |
+
[/\[/, "delimiter.square"],
|
| 946 |
+
[/\]/, "delimiter.square"],
|
| 947 |
+
[/\(/, "delimiter.parenthesis"],
|
| 948 |
+
[/\)/, "delimiter.parenthesis"],
|
| 949 |
+
[/\$\$/, { token: "delimiter.dollar", next: "@math_block" }],
|
| 950 |
+
[/\$/, { token: "delimiter.dollar", next: "@math_inline" }],
|
| 951 |
+
[/%.*$/, "comment"],
|
| 952 |
+
[/[^\\{}\[\]$%&_\^~'"` ]+/, ""]
|
| 953 |
+
],
|
| 954 |
+
|
| 955 |
+
"math_block": [
|
| 956 |
+
[/\$\$/, { token: "delimiter.dollar", next: "@pop" }],
|
| 957 |
+
[/[^\\$]+/, "predefined.mathematical"],
|
| 958 |
+
[/\\[a-zA-Z]+/, "predefined.mathematical"],
|
| 959 |
+
[/[\\$]/, "predefined.mathematical"]
|
| 960 |
+
],
|
| 961 |
+
|
| 962 |
+
"math_inline": [
|
| 963 |
+
[/\$/, { token: "delimiter.dollar", next: "@pop" }],
|
| 964 |
+
[/[^\\$]+/, "predefined.mathematical"],
|
| 965 |
+
[/\\[a-zA-Z]+/, "predefined.mathematical"],
|
| 966 |
+
[/[\\$]/, "predefined.mathematical"]
|
| 967 |
+
]
|
| 968 |
+
}
|
| 969 |
+
}
|
| 970 |
+
|
| 971 |
+
# Define LaTeX autocompletion suggestions for Monaco editor
|
| 972 |
+
def get_latex_completions():
|
| 973 |
+
return [
|
| 974 |
+
# Document structure
|
| 975 |
+
{"label": "\\documentclass", "kind": 14, "insertText": "\\documentclass{${1:article}}", "documentation": "Defines the document class"},
|
| 976 |
+
{"label": "\\usepackage", "kind": 14, "insertText": "\\usepackage{${1:package}}", "documentation": "Imports a package"},
|
| 977 |
+
{"label": "\\begin", "kind": 14, "insertText": "\\begin{${1:environment}}\n\t$0\n\\end{$1}", "documentation": "Creates an environment"},
|
| 978 |
+
{"label": "\\end", "kind": 14, "insertText": "\\end{${1:environment}}", "documentation": "Ends an environment"},
|
| 979 |
+
|
| 980 |
+
# Sections
|
| 981 |
+
{"label": "\\section", "kind": 14, "insertText": "\\section{${1:title}}", "documentation": "Creates a section"},
|
| 982 |
+
{"label": "\\subsection", "kind": 14, "insertText": "\\subsection{${1:title}}", "documentation": "Creates a subsection"},
|
| 983 |
+
{"label": "\\subsubsection", "kind": 14, "insertText": "\\subsubsection{${1:title}}", "documentation": "Creates a subsubsection"},
|
| 984 |
+
|
| 985 |
+
# Formatting
|
| 986 |
+
{"label": "\\textbf", "kind": 14, "insertText": "\\textbf{${1:text}}", "documentation": "Makes text bold"},
|
| 987 |
+
{"label": "\\textit", "kind": 14, "insertText": "\\textit{${1:text}}", "documentation": "Makes text italic"},
|
| 988 |
+
{"label": "\\underline", "kind": 14, "insertText": "\\underline{${1:text}}", "documentation": "Underlines text"},
|
| 989 |
+
{"label": "\\emph", "kind": 14, "insertText": "\\emph{${1:text}}", "documentation": "Emphasizes text"},
|
| 990 |
+
|
| 991 |
+
# Math
|
| 992 |
+
{"label": "\\begin{equation}", "kind": 14, "insertText": "\\begin{equation}\n\t${1}\n\\end{equation}", "documentation": "Creates an equation environment"},
|
| 993 |
+
{"label": "\\begin{align}", "kind": 14, "insertText": "\\begin{align}\n\t${1}\n\\end{align}", "documentation": "Creates an align environment"},
|
| 994 |
+
{"label": "\\frac", "kind": 14, "insertText": "\\frac{${1:numerator}}{${2:denominator}}", "documentation": "Creates a fraction"},
|
| 995 |
+
{"label": "\\sqrt", "kind": 14, "insertText": "\\sqrt{${1:expression}}", "documentation": "Creates a square root"},
|
| 996 |
+
{"label": "\\sum", "kind": 14, "insertText": "\\sum_{${1:lower}}^{${2:upper}}", "documentation": "Creates a summation"},
|
| 997 |
+
{"label": "\\int", "kind": 14, "insertText": "\\int_{${1:lower}}^{${2:upper}}", "documentation": "Creates an integral"},
|
| 998 |
+
|
| 999 |
+
# Lists
|
| 1000 |
+
{"label": "\\begin{itemize}", "kind": 14, "insertText": "\\begin{itemize}\n\t\\item ${1}\n\\end{itemize}", "documentation": "Creates a bulleted list"},
|
| 1001 |
+
{"label": "\\begin{enumerate}", "kind": 14, "insertText": "\\begin{enumerate}\n\t\\item ${1}\n\\end{enumerate}", "documentation": "Creates a numbered list"},
|
| 1002 |
+
{"label": "\\item", "kind": 14, "insertText": "\\item ${1}", "documentation": "Creates a list item"},
|
| 1003 |
+
|
| 1004 |
+
# Tables
|
| 1005 |
+
{"label": "\\begin{table}", "kind": 14, "insertText": "\\begin{table}[${1:h}]\n\t\\centering\n\t\\begin{tabular}{${2:ccc}}\n\t\t${3}\n\t\\end{tabular}\n\t\\caption{${4:caption}}\n\t\\label{tab:${5:label}}\n\\end{table}", "documentation": "Creates a table environment"},
|
| 1006 |
+
|
| 1007 |
+
# Figures
|
| 1008 |
+
{"label": "\\begin{figure}", "kind": 14, "insertText": "\\begin{figure}[${1:h}]\n\t\\centering\n\t\\includegraphics[width=${2:0.8}\\textwidth]{${3:filename}}\n\t\\caption{${4:caption}}\n\t\\label{fig:${5:label}}\n\\end{figure}", "documentation": "Creates a figure environment"},
|
| 1009 |
+
|
| 1010 |
+
# Common environments
|
| 1011 |
+
{"label": "\\begin{center}", "kind": 14, "insertText": "\\begin{center}\n\t${1}\n\\end{center}", "documentation": "Creates centered text"},
|
| 1012 |
+
{"label": "\\begin{verbatim}", "kind": 14, "insertText": "\\begin{verbatim}\n${1}\n\\end{verbatim}", "documentation": "Creates verbatim text"},
|
| 1013 |
+
|
| 1014 |
+
# References
|
| 1015 |
+
{"label": "\\label", "kind": 14, "insertText": "\\label{${1:label}}", "documentation": "Creates a label for cross-referencing"},
|
| 1016 |
+
{"label": "\\ref", "kind": 14, "insertText": "\\ref{${1:label}}", "documentation": "References a label"},
|
| 1017 |
+
{"label": "\\cite", "kind": 14, "insertText": "\\cite{${1:key}}", "documentation": "Cites a reference"}
|
| 1018 |
+
]
|
| 1019 |
+
|
| 1020 |
# Main application
|
| 1021 |
def main():
|
| 1022 |
# Initialize session state
|
|
|
|
| 1049 |
# Tab bar
|
| 1050 |
render_editor_tabs()
|
| 1051 |
|
| 1052 |
+
# Toolbar buttons
|
| 1053 |
+
render_toolbar_buttons()
|
| 1054 |
+
|
| 1055 |
+
# Check for toolbar button clicks
|
| 1056 |
+
button_clicked = False
|
| 1057 |
+
snippet_to_insert = ""
|
| 1058 |
|
| 1059 |
+
if st.session_state.get("btn_bold", False):
|
| 1060 |
+
snippet_to_insert = latex_snippets["bold"]
|
| 1061 |
+
button_clicked = True
|
| 1062 |
+
elif st.session_state.get("btn_italic", False):
|
| 1063 |
+
snippet_to_insert = latex_snippets["italic"]
|
| 1064 |
+
button_clicked = True
|
| 1065 |
+
elif st.session_state.get("btn_section", False):
|
| 1066 |
+
snippet_to_insert = latex_snippets["section"]
|
| 1067 |
+
button_clicked = True
|
| 1068 |
+
elif st.session_state.get("btn_subsection", False):
|
| 1069 |
+
snippet_to_insert = latex_snippets["subsection"]
|
| 1070 |
+
button_clicked = True
|
| 1071 |
+
elif st.session_state.get("btn_itemize", False):
|
| 1072 |
+
snippet_to_insert = latex_snippets["itemize"]
|
| 1073 |
+
button_clicked = True
|
| 1074 |
+
elif st.session_state.get("btn_enumerate", False):
|
| 1075 |
+
snippet_to_insert = latex_snippets["enumerate"]
|
| 1076 |
+
button_clicked = True
|
| 1077 |
+
elif st.session_state.get("btn_equation", False):
|
| 1078 |
+
snippet_to_insert = latex_snippets["equation"]
|
| 1079 |
+
button_clicked = True
|
| 1080 |
+
elif st.session_state.get("btn_table", False):
|
| 1081 |
+
snippet_to_insert = latex_snippets["table"]
|
| 1082 |
+
button_clicked = True
|
| 1083 |
+
|
| 1084 |
+
# Monaco Editor with LaTeX configuration
|
| 1085 |
+
st.markdown('<div class="monaco-editor-container">', unsafe_allow_html=True)
|
| 1086 |
+
|
| 1087 |
+
# Define LaTeX language configuration for Monaco
|
| 1088 |
+
latex_language = get_latex_language_definition()
|
| 1089 |
+
latex_completions = get_latex_completions()
|
| 1090 |
+
|
| 1091 |
+
# Create Monaco editor component
|
| 1092 |
+
latex_code = st_monaco(
|
| 1093 |
value=st.session_state.latex_code,
|
| 1094 |
height=500,
|
| 1095 |
+
language="latex",
|
| 1096 |
+
theme="vs-dark",
|
| 1097 |
+
options={
|
| 1098 |
+
"scrollBeyondLastLine": False,
|
| 1099 |
+
"minimap": {"enabled": True},
|
| 1100 |
+
"lineNumbers": "on",
|
| 1101 |
+
"tabSize": 2,
|
| 1102 |
+
"insertSpaces": True,
|
| 1103 |
+
"fontSize": 14,
|
| 1104 |
+
"wordWrap": "on",
|
| 1105 |
+
"automaticLayout": True,
|
| 1106 |
+
"suggestOnTriggerCharacters": True,
|
| 1107 |
+
"quickSuggestions": True,
|
| 1108 |
+
"snippetSuggestions": "inline",
|
| 1109 |
+
"folding": True,
|
| 1110 |
+
"bracketPairColorization.enabled": True,
|
| 1111 |
+
"renderWhitespace": "boundary"
|
| 1112 |
+
},
|
| 1113 |
+
key="monaco_editor"
|
| 1114 |
)
|
| 1115 |
+
|
| 1116 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 1117 |
+
|
| 1118 |
+
if latex_code is not None:
|
| 1119 |
+
st.session_state.latex_code = latex_code
|
| 1120 |
|
| 1121 |
# Status bar
|
| 1122 |
render_status_bar()
|