Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -13,7 +13,7 @@ import re
|
|
13 |
|
14 |
# Set page configuration
|
15 |
st.set_page_config(
|
16 |
-
page_title="
|
17 |
page_icon="📝",
|
18 |
layout="wide",
|
19 |
initial_sidebar_state="collapsed"
|
@@ -109,7 +109,7 @@ def parse_latex_errors(output):
|
|
109 |
|
110 |
return errors, warnings
|
111 |
|
112 |
-
# Function to extract document structure
|
113 |
def extract_document_structure(latex_code):
|
114 |
structure = []
|
115 |
|
@@ -149,6 +149,48 @@ def extract_document_structure(latex_code):
|
|
149 |
|
150 |
return structure
|
151 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
152 |
# Default LaTeX template
|
153 |
default_template = r"""\documentclass{article}
|
154 |
\usepackage[utf8]{inputenc}
|
@@ -158,7 +200,7 @@ default_template = r"""\documentclass{article}
|
|
158 |
\usepackage{hyperref}
|
159 |
\usepackage{xcolor}
|
160 |
|
161 |
-
\title{
|
162 |
\author{Your Name}
|
163 |
\date{\today}
|
164 |
|
@@ -244,7 +286,7 @@ Your conclusion here.
|
|
244 |
\end{document}
|
245 |
"""
|
246 |
|
247 |
-
# Enhanced VSCode-like advanced styling
|
248 |
st.markdown("""
|
249 |
<style>
|
250 |
/* Base theming - VS Code inspired */
|
@@ -267,8 +309,8 @@ st.markdown("""
|
|
267 |
background-color: #1177bb;
|
268 |
}
|
269 |
|
270 |
-
/* VS Code-like editor */
|
271 |
-
.
|
272 |
font-family: 'Consolas', 'Monaco', 'Courier New', monospace !important;
|
273 |
font-size: 14px !important;
|
274 |
line-height: 1.5 !important;
|
@@ -279,6 +321,45 @@ st.markdown("""
|
|
279 |
border: 1px solid #252526 !important;
|
280 |
}
|
281 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
282 |
/* VS Code-like editor container */
|
283 |
.editor-container {
|
284 |
background-color: #1e1e1e;
|
@@ -553,61 +634,236 @@ st.markdown("""
|
|
553 |
.info-value {
|
554 |
text-align: right;
|
555 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
556 |
</style>
|
557 |
""", unsafe_allow_html=True)
|
558 |
|
559 |
-
# VS Code-
|
560 |
-
def
|
561 |
-
tab_html = f"""
|
562 |
-
<div class="tab-bar">
|
563 |
-
<div class="tab active">
|
564 |
-
<span class="tab-icon">📄</span> {active_tab}
|
565 |
-
</div>
|
566 |
-
</div>
|
567 |
-
"""
|
568 |
-
st.markdown(tab_html, unsafe_allow_html=True)
|
569 |
-
|
570 |
-
# Build a VS Code-like toolbar
|
571 |
-
def render_toolbar():
|
572 |
toolbar_html = """
|
573 |
<div class="toolbar">
|
574 |
-
<button class="toolbar-button" title="Bold">
|
575 |
<strong>B</strong>
|
576 |
</button>
|
577 |
-
<button class="toolbar-button" title="Italic">
|
578 |
<em>I</em>
|
579 |
</button>
|
580 |
-
<button class="toolbar-button" title="Math">
|
581 |
∑
|
582 |
</button>
|
583 |
<div class="toolbar-separator"></div>
|
584 |
-
<button class="toolbar-button" title="Section">
|
585 |
§
|
586 |
</button>
|
587 |
-
<button class="toolbar-button" title="Subsection">
|
588 |
§§
|
589 |
</button>
|
590 |
<div class="toolbar-separator"></div>
|
591 |
-
<button class="toolbar-button" title="Itemize">
|
592 |
•
|
593 |
</button>
|
594 |
-
<button class="toolbar-button" title="Enumerate">
|
595 |
1.
|
596 |
</button>
|
597 |
<div class="toolbar-separator"></div>
|
598 |
-
<button class="toolbar-button" title="Equation">
|
599 |
=
|
600 |
</button>
|
601 |
-
<button class="toolbar-button" title="
|
|
|
|
|
|
|
602 |
🖼
|
603 |
</button>
|
604 |
-
<button class="toolbar-button" title="Table">
|
605 |
⊞
|
606 |
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
607 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
608 |
"""
|
609 |
st.markdown(toolbar_html, unsafe_allow_html=True)
|
610 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
611 |
# VS Code-style status bar
|
612 |
def render_status_bar():
|
613 |
status_html = """
|
@@ -619,6 +875,42 @@ def render_status_bar():
|
|
619 |
"""
|
620 |
st.markdown(status_html, unsafe_allow_html=True)
|
621 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
622 |
# Render a document outline based on section hierarchy
|
623 |
def render_document_outline(structure):
|
624 |
if not structure:
|
@@ -702,6 +994,7 @@ def render_document_info(latex_code):
|
|
702 |
|
703 |
# Count equations
|
704 |
equation_count = len(re.findall(r'\\begin\{equation', latex_code))
|
|
|
705 |
|
706 |
# Count figures
|
707 |
figure_count = len(re.findall(r'\\begin\{figure', latex_code))
|
@@ -709,6 +1002,13 @@ def render_document_info(latex_code):
|
|
709 |
# Count tables
|
710 |
table_count = len(re.findall(r'\\begin\{table', latex_code))
|
711 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
712 |
# Render the info panel
|
713 |
st.markdown('<div class="document-info">', unsafe_allow_html=True)
|
714 |
|
@@ -718,8 +1018,12 @@ def render_document_info(latex_code):
|
|
718 |
("Character Count", char_count),
|
719 |
("Line Count", line_count),
|
720 |
("Equations", equation_count),
|
|
|
721 |
("Figures", figure_count),
|
722 |
-
("Tables", table_count)
|
|
|
|
|
|
|
723 |
]
|
724 |
|
725 |
for label, value in info_rows:
|
@@ -733,6 +1037,81 @@ def render_document_info(latex_code):
|
|
733 |
|
734 |
st.markdown('</div>', unsafe_allow_html=True)
|
735 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
736 |
# Main application
|
737 |
def main():
|
738 |
# Initialize session state
|
@@ -746,6 +1125,8 @@ def main():
|
|
746 |
st.session_state.errors = []
|
747 |
if 'warnings' not in st.session_state:
|
748 |
st.session_state.warnings = []
|
|
|
|
|
749 |
|
750 |
# Check installation status
|
751 |
if not is_pdflatex_installed():
|
@@ -756,7 +1137,7 @@ def main():
|
|
756 |
|
757 |
with col1:
|
758 |
# Create tabs for main editing area with VS Code style
|
759 |
-
editor_tabs = st.tabs(["Editor", "Settings"])
|
760 |
|
761 |
with editor_tabs[0]:
|
762 |
# VS Code-like editor interface
|
@@ -765,10 +1146,11 @@ def main():
|
|
765 |
# Tab bar
|
766 |
render_editor_tabs()
|
767 |
|
768 |
-
# Toolbar
|
769 |
-
|
770 |
|
771 |
-
#
|
|
|
772 |
latex_code = st.text_area(
|
773 |
"",
|
774 |
value=st.session_state.latex_code,
|
@@ -776,6 +1158,7 @@ def main():
|
|
776 |
key="latex_editor",
|
777 |
label_visibility="collapsed"
|
778 |
)
|
|
|
779 |
st.session_state.latex_code = latex_code
|
780 |
|
781 |
# Status bar
|
@@ -804,6 +1187,14 @@ def main():
|
|
804 |
st.rerun()
|
805 |
|
806 |
with editor_tabs[1]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
807 |
st.markdown("<h3>LaTeX Settings</h3>", unsafe_allow_html=True)
|
808 |
|
809 |
st.markdown('<div class="control-panel">', unsafe_allow_html=True)
|
@@ -813,6 +1204,7 @@ def main():
|
|
813 |
st.checkbox("Auto-compile on save", value=False, key="auto_compile")
|
814 |
st.checkbox("Use pdflatex", value=True, key="use_pdflatex")
|
815 |
st.checkbox("Enable BibTeX", value=False, key="use_bibtex")
|
|
|
816 |
|
817 |
with col_b:
|
818 |
st.selectbox("Document Class",
|
@@ -821,12 +1213,18 @@ def main():
|
|
821 |
st.selectbox("PDF Engine",
|
822 |
["pdflatex", "xelatex", "lualatex"],
|
823 |
index=0, key="pdf_engine")
|
|
|
|
|
|
|
824 |
|
825 |
st.markdown('</div>', unsafe_allow_html=True)
|
|
|
|
|
|
|
826 |
|
827 |
with col2:
|
828 |
# Output tabs with VS Code style
|
829 |
-
output_tabs = st.tabs(["Output", "Outline", "
|
830 |
|
831 |
with output_tabs[0]:
|
832 |
# PDF compilation and output
|
@@ -920,18 +1318,36 @@ def main():
|
|
920 |
structure = extract_document_structure(st.session_state.latex_code)
|
921 |
else:
|
922 |
structure = []
|
923 |
-
|
|
|
|
|
|
|
|
|
924 |
|
925 |
with output_tabs[2]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
926 |
# Problems panel (errors & warnings)
|
927 |
if st.session_state.errors or st.session_state.warnings:
|
928 |
render_problems(st.session_state.errors, st.session_state.warnings)
|
929 |
else:
|
930 |
-
st.info("No problems detected")
|
931 |
-
|
932 |
-
with output_tabs[3]:
|
933 |
-
# Document information panel
|
934 |
-
render_document_info(st.session_state.latex_code)
|
935 |
|
936 |
if __name__ == "__main__":
|
937 |
main()
|
|
|
13 |
|
14 |
# Set page configuration
|
15 |
st.set_page_config(
|
16 |
+
page_title="Professional LaTeX Editor",
|
17 |
page_icon="📝",
|
18 |
layout="wide",
|
19 |
initial_sidebar_state="collapsed"
|
|
|
109 |
|
110 |
return errors, warnings
|
111 |
|
112 |
+
# Function to extract document structure
|
113 |
def extract_document_structure(latex_code):
|
114 |
structure = []
|
115 |
|
|
|
149 |
|
150 |
return structure
|
151 |
|
152 |
+
# Function to analyze LaTeX packages
|
153 |
+
def analyze_packages(latex_code):
|
154 |
+
if not isinstance(latex_code, str):
|
155 |
+
try:
|
156 |
+
latex_code = str(latex_code)
|
157 |
+
except:
|
158 |
+
return []
|
159 |
+
|
160 |
+
# Find all package imports
|
161 |
+
package_pattern = r'\\usepackage(?:\[.*?\])?\{([^}]+)\}'
|
162 |
+
matches = re.finditer(package_pattern, latex_code)
|
163 |
+
|
164 |
+
packages = []
|
165 |
+
for match in matches:
|
166 |
+
package_name = match.group(1)
|
167 |
+
packages.append(package_name)
|
168 |
+
|
169 |
+
return packages
|
170 |
+
|
171 |
+
# Function to find all environments in the document
|
172 |
+
def find_environments(latex_code):
|
173 |
+
if not isinstance(latex_code, str):
|
174 |
+
try:
|
175 |
+
latex_code = str(latex_code)
|
176 |
+
except:
|
177 |
+
return []
|
178 |
+
|
179 |
+
# Find all environments (begin-end pairs)
|
180 |
+
env_pattern = r'\\begin\{([^}]+)\}(.*?)\\end\{\1\}'
|
181 |
+
matches = re.finditer(env_pattern, latex_code, re.DOTALL)
|
182 |
+
|
183 |
+
environments = []
|
184 |
+
for match in matches:
|
185 |
+
env_name = match.group(1)
|
186 |
+
environments.append({
|
187 |
+
'name': env_name,
|
188 |
+
'position': match.start(),
|
189 |
+
'content': match.group(2)
|
190 |
+
})
|
191 |
+
|
192 |
+
return environments
|
193 |
+
|
194 |
# Default LaTeX template
|
195 |
default_template = r"""\documentclass{article}
|
196 |
\usepackage[utf8]{inputenc}
|
|
|
200 |
\usepackage{hyperref}
|
201 |
\usepackage{xcolor}
|
202 |
|
203 |
+
\title{Professional \LaTeX{} Document}
|
204 |
\author{Your Name}
|
205 |
\date{\today}
|
206 |
|
|
|
286 |
\end{document}
|
287 |
"""
|
288 |
|
289 |
+
# Enhanced VSCode-like advanced styling with syntax highlighting
|
290 |
st.markdown("""
|
291 |
<style>
|
292 |
/* Base theming - VS Code inspired */
|
|
|
309 |
background-color: #1177bb;
|
310 |
}
|
311 |
|
312 |
+
/* VS Code-like editor - with additional syntax highlighting */
|
313 |
+
.editor-area textarea {
|
314 |
font-family: 'Consolas', 'Monaco', 'Courier New', monospace !important;
|
315 |
font-size: 14px !important;
|
316 |
line-height: 1.5 !important;
|
|
|
321 |
border: 1px solid #252526 !important;
|
322 |
}
|
323 |
|
324 |
+
/* Add a special class for syntax highlighting in the preview */
|
325 |
+
.syntax-highlight {
|
326 |
+
background-color: #1e1e1e;
|
327 |
+
color: #d4d4d4;
|
328 |
+
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
329 |
+
font-size: 14px;
|
330 |
+
padding: 10px;
|
331 |
+
border-radius: 4px;
|
332 |
+
border: 1px solid #252526;
|
333 |
+
white-space: pre-wrap;
|
334 |
+
margin-top: 10px;
|
335 |
+
overflow-x: auto;
|
336 |
+
}
|
337 |
+
|
338 |
+
/* Syntax highlighting for different LaTeX elements */
|
339 |
+
.syntax-highlight .command {
|
340 |
+
color: #569cd6; /* Blue for commands */
|
341 |
+
}
|
342 |
+
|
343 |
+
.syntax-highlight .environment {
|
344 |
+
color: #4ec9b0; /* Teal for environments */
|
345 |
+
}
|
346 |
+
|
347 |
+
.syntax-highlight .bracket {
|
348 |
+
color: #d4d4d4; /* White for brackets */
|
349 |
+
}
|
350 |
+
|
351 |
+
.syntax-highlight .math {
|
352 |
+
color: #c586c0; /* Purple for math */
|
353 |
+
}
|
354 |
+
|
355 |
+
.syntax-highlight .comment {
|
356 |
+
color: #6a9955; /* Green for comments */
|
357 |
+
}
|
358 |
+
|
359 |
+
.syntax-highlight .package {
|
360 |
+
color: #dcdcaa; /* Yellow for package imports */
|
361 |
+
}
|
362 |
+
|
363 |
/* VS Code-like editor container */
|
364 |
.editor-container {
|
365 |
background-color: #1e1e1e;
|
|
|
634 |
.info-value {
|
635 |
text-align: right;
|
636 |
}
|
637 |
+
|
638 |
+
/* Packages panel */
|
639 |
+
.packages-panel {
|
640 |
+
background-color: #252526;
|
641 |
+
border: 1px solid #2d2d2d;
|
642 |
+
border-radius: 4px;
|
643 |
+
margin-top: 10px;
|
644 |
+
padding: 0;
|
645 |
+
}
|
646 |
+
|
647 |
+
.packages-header {
|
648 |
+
background-color: #2d2d2d;
|
649 |
+
padding: 5px 10px;
|
650 |
+
font-weight: bold;
|
651 |
+
font-size: 13px;
|
652 |
+
}
|
653 |
+
|
654 |
+
.package-item {
|
655 |
+
padding: 5px 10px;
|
656 |
+
border-bottom: 1px solid #2d2d2d;
|
657 |
+
display: flex;
|
658 |
+
align-items: center;
|
659 |
+
}
|
660 |
+
|
661 |
+
.package-name {
|
662 |
+
color: #dcdcaa;
|
663 |
+
margin-right: 10px;
|
664 |
+
font-family: monospace;
|
665 |
+
}
|
666 |
+
|
667 |
+
/* Environment analyzer */
|
668 |
+
.environments-panel {
|
669 |
+
background-color: #252526;
|
670 |
+
border: 1px solid #2d2d2d;
|
671 |
+
border-radius: 4px;
|
672 |
+
margin-top: 10px;
|
673 |
+
padding: 0;
|
674 |
+
}
|
675 |
+
|
676 |
+
.environments-header {
|
677 |
+
background-color: #2d2d2d;
|
678 |
+
padding: 5px 10px;
|
679 |
+
font-weight: bold;
|
680 |
+
font-size: 13px;
|
681 |
+
}
|
682 |
+
|
683 |
+
.environment-item {
|
684 |
+
padding: 5px 10px;
|
685 |
+
border-bottom: 1px solid #2d2d2d;
|
686 |
+
}
|
687 |
+
|
688 |
+
.environment-name {
|
689 |
+
color: #4ec9b0;
|
690 |
+
font-family: monospace;
|
691 |
+
font-weight: bold;
|
692 |
+
}
|
693 |
+
|
694 |
+
/* Syntax highlight code preview */
|
695 |
+
.code-preview {
|
696 |
+
background-color: #1e1e1e;
|
697 |
+
border: 1px solid #2d2d2d;
|
698 |
+
border-radius: 4px;
|
699 |
+
margin-top: 10px;
|
700 |
+
padding: 10px;
|
701 |
+
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
702 |
+
font-size: 14px;
|
703 |
+
line-height: 1.5;
|
704 |
+
overflow-x: auto;
|
705 |
+
white-space: pre-wrap;
|
706 |
+
}
|
707 |
+
|
708 |
+
/* Custom select styling */
|
709 |
+
.stSelectbox [data-baseweb="select"] {
|
710 |
+
background-color: #3c3c3c;
|
711 |
+
border-color: #3c3c3c;
|
712 |
+
}
|
713 |
+
|
714 |
+
.stSelectbox [data-baseweb="select"] > div {
|
715 |
+
color: #cccccc;
|
716 |
+
}
|
717 |
+
|
718 |
+
/* Keyboard shortcuts display */
|
719 |
+
.shortcuts-container {
|
720 |
+
background-color: #252526;
|
721 |
+
border: 1px solid #2d2d2d;
|
722 |
+
border-radius: 4px;
|
723 |
+
margin-top: 10px;
|
724 |
+
padding: 10px;
|
725 |
+
}
|
726 |
+
|
727 |
+
.shortcut-row {
|
728 |
+
display: flex;
|
729 |
+
justify-content: space-between;
|
730 |
+
margin-bottom: 8px;
|
731 |
+
border-bottom: 1px solid #333;
|
732 |
+
padding-bottom: 8px;
|
733 |
+
}
|
734 |
+
|
735 |
+
.shortcut-keys {
|
736 |
+
background-color: #3c3c3c;
|
737 |
+
padding: 2px 8px;
|
738 |
+
border-radius: 3px;
|
739 |
+
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
740 |
+
font-size: 12px;
|
741 |
+
}
|
742 |
+
|
743 |
+
.shortcut-description {
|
744 |
+
color: #cccccc;
|
745 |
+
font-size: 13px;
|
746 |
+
}
|
747 |
</style>
|
748 |
""", unsafe_allow_html=True)
|
749 |
|
750 |
+
# Function to create a functional VS Code-like toolbar
|
751 |
+
def render_functional_toolbar():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
752 |
toolbar_html = """
|
753 |
<div class="toolbar">
|
754 |
+
<button class="toolbar-button" title="Bold" onclick="insertAtCursor('\\\\textbf{', '}')">
|
755 |
<strong>B</strong>
|
756 |
</button>
|
757 |
+
<button class="toolbar-button" title="Italic" onclick="insertAtCursor('\\\\textit{', '}')">
|
758 |
<em>I</em>
|
759 |
</button>
|
760 |
+
<button class="toolbar-button" title="Inline Math" onclick="insertAtCursor('$', '$')">
|
761 |
∑
|
762 |
</button>
|
763 |
<div class="toolbar-separator"></div>
|
764 |
+
<button class="toolbar-button" title="Section" onclick="insertAtCursor('\\\\section{', '}')">
|
765 |
§
|
766 |
</button>
|
767 |
+
<button class="toolbar-button" title="Subsection" onclick="insertAtCursor('\\\\subsection{', '}')">
|
768 |
§§
|
769 |
</button>
|
770 |
<div class="toolbar-separator"></div>
|
771 |
+
<button class="toolbar-button" title="Itemize" onclick="insertAtCursor('\\\\begin{itemize}\\n \\\\item ', '\\n\\\\end{itemize}')">
|
772 |
•
|
773 |
</button>
|
774 |
+
<button class="toolbar-button" title="Enumerate" onclick="insertAtCursor('\\\\begin{enumerate}\\n \\\\item ', '\\n\\\\end{enumerate}')">
|
775 |
1.
|
776 |
</button>
|
777 |
<div class="toolbar-separator"></div>
|
778 |
+
<button class="toolbar-button" title="Equation" onclick="insertAtCursor('\\\\begin{equation}\\n ', '\\n\\\\end{equation}')">
|
779 |
=
|
780 |
</button>
|
781 |
+
<button class="toolbar-button" title="Align" onclick="insertAtCursor('\\\\begin{align}\\n ', '\\n\\\\end{align}')">
|
782 |
+
≡
|
783 |
+
</button>
|
784 |
+
<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}')">
|
785 |
🖼
|
786 |
</button>
|
787 |
+
<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}')">
|
788 |
⊞
|
789 |
</button>
|
790 |
+
<div class="toolbar-separator"></div>
|
791 |
+
<button class="toolbar-button" title="Fraction" onclick="insertAtCursor('\\\\frac{', '}{denominator}')">
|
792 |
+
⅟
|
793 |
+
</button>
|
794 |
+
<button class="toolbar-button" title="Square Root" onclick="insertAtCursor('\\\\sqrt{', '}')">
|
795 |
+
√
|
796 |
+
</button>
|
797 |
+
<button class="toolbar-button" title="Add Package" onclick="insertAtCursor('\\\\usepackage{', '}')">
|
798 |
+
📦
|
799 |
+
</button>
|
800 |
</div>
|
801 |
+
|
802 |
+
<script>
|
803 |
+
function insertAtCursor(before, after) {
|
804 |
+
// Find the textarea element
|
805 |
+
var textarea = document.querySelector('textarea');
|
806 |
+
if (!textarea) return;
|
807 |
+
|
808 |
+
// Get current cursor position
|
809 |
+
var startPos = textarea.selectionStart;
|
810 |
+
var endPos = textarea.selectionEnd;
|
811 |
+
|
812 |
+
// Get text before and after selection
|
813 |
+
var textBefore = textarea.value.substring(0, startPos);
|
814 |
+
var textSelected = textarea.value.substring(startPos, endPos);
|
815 |
+
var textAfter = textarea.value.substring(endPos);
|
816 |
+
|
817 |
+
// Insert the text
|
818 |
+
var newText;
|
819 |
+
if (textSelected) {
|
820 |
+
// If text is selected, wrap it with before and after
|
821 |
+
newText = textBefore + before + textSelected + after + textAfter;
|
822 |
+
} else {
|
823 |
+
// If no text is selected, just insert before and after
|
824 |
+
newText = textBefore + before + after + textAfter;
|
825 |
+
// Put cursor between before and after
|
826 |
+
var newCursorPos = startPos + before.length;
|
827 |
+
}
|
828 |
+
|
829 |
+
// Update textarea value
|
830 |
+
textarea.value = newText;
|
831 |
+
|
832 |
+
// Set selection range to position cursor appropriately
|
833 |
+
if (textSelected) {
|
834 |
+
// If text was selected, select the wrapped text
|
835 |
+
textarea.setSelectionRange(startPos, endPos + before.length + after.length);
|
836 |
+
} else {
|
837 |
+
// Otherwise position cursor between before and after
|
838 |
+
textarea.setSelectionRange(newCursorPos, newCursorPos);
|
839 |
+
}
|
840 |
+
|
841 |
+
// Focus on textarea
|
842 |
+
textarea.focus();
|
843 |
+
|
844 |
+
// Trigger an input event to update Streamlit
|
845 |
+
var event = new Event('input', { bubbles: true });
|
846 |
+
textarea.dispatchEvent(event);
|
847 |
+
|
848 |
+
// Trigger change event to update Streamlit session state
|
849 |
+
var changeEvent = new Event('change', { bubbles: true });
|
850 |
+
textarea.dispatchEvent(changeEvent);
|
851 |
+
}
|
852 |
+
</script>
|
853 |
"""
|
854 |
st.markdown(toolbar_html, unsafe_allow_html=True)
|
855 |
|
856 |
+
# VS Code-style editor tabs
|
857 |
+
def render_editor_tabs(active_tab="document.tex"):
|
858 |
+
tab_html = f"""
|
859 |
+
<div class="tab-bar">
|
860 |
+
<div class="tab active">
|
861 |
+
<span class="tab-icon">📄</span> {active_tab}
|
862 |
+
</div>
|
863 |
+
</div>
|
864 |
+
"""
|
865 |
+
st.markdown(tab_html, unsafe_allow_html=True)
|
866 |
+
|
867 |
# VS Code-style status bar
|
868 |
def render_status_bar():
|
869 |
status_html = """
|
|
|
875 |
"""
|
876 |
st.markdown(status_html, unsafe_allow_html=True)
|
877 |
|
878 |
+
# Create syntax highlighting for the code preview
|
879 |
+
def syntax_highlight_latex(latex_code):
|
880 |
+
if not isinstance(latex_code, str):
|
881 |
+
try:
|
882 |
+
latex_code = str(latex_code)
|
883 |
+
except:
|
884 |
+
return "<pre>Invalid code</pre>"
|
885 |
+
|
886 |
+
# Replace special HTML characters
|
887 |
+
latex_code = latex_code.replace("&", "&").replace("<", "<").replace(">", ">")
|
888 |
+
|
889 |
+
# Highlight LaTeX commands (starting with \)
|
890 |
+
latex_code = re.sub(r'(\\[a-zA-Z]+)(\{|\[|\s|$)', r'<span class="command">\1</span>\2', latex_code)
|
891 |
+
|
892 |
+
# Highlight environment tags
|
893 |
+
latex_code = re.sub(r'(\\begin\{[^}]+\}|\\end\{[^}]+\})', r'<span class="environment">\1</span>', latex_code)
|
894 |
+
|
895 |
+
# Highlight math modes
|
896 |
+
latex_code = re.sub(r'(\$\$.*?\$\$|\$.*?\$)', r'<span class="math">\1</span>', latex_code)
|
897 |
+
|
898 |
+
# Highlight comments
|
899 |
+
latex_code = re.sub(r'(%.*$)', r'<span class="comment">\1</span>', latex_code)
|
900 |
+
|
901 |
+
# Highlight package imports
|
902 |
+
latex_code = re.sub(r'(\\usepackage(\[.*?\])?\{[^}]+\})', r'<span class="package">\1</span>', latex_code)
|
903 |
+
|
904 |
+
# Highlight brackets
|
905 |
+
latex_code = re.sub(r'(\{|\}|\[|\])', r'<span class="bracket">\1</span>', latex_code)
|
906 |
+
|
907 |
+
return f'<div class="syntax-highlight">{latex_code}</div>'
|
908 |
+
|
909 |
+
# Function to display the code with syntax highlighting
|
910 |
+
def display_code_with_syntax_highlighting(latex_code):
|
911 |
+
highlighted_code = syntax_highlight_latex(latex_code)
|
912 |
+
st.markdown(highlighted_code, unsafe_allow_html=True)
|
913 |
+
|
914 |
# Render a document outline based on section hierarchy
|
915 |
def render_document_outline(structure):
|
916 |
if not structure:
|
|
|
994 |
|
995 |
# Count equations
|
996 |
equation_count = len(re.findall(r'\\begin\{equation', latex_code))
|
997 |
+
align_count = len(re.findall(r'\\begin\{align', latex_code))
|
998 |
|
999 |
# Count figures
|
1000 |
figure_count = len(re.findall(r'\\begin\{figure', latex_code))
|
|
|
1002 |
# Count tables
|
1003 |
table_count = len(re.findall(r'\\begin\{table', latex_code))
|
1004 |
|
1005 |
+
# Count custom command definitions
|
1006 |
+
command_count = len(re.findall(r'\\newcommand', latex_code))
|
1007 |
+
|
1008 |
+
# Count references
|
1009 |
+
ref_count = len(re.findall(r'\\ref\{', latex_code))
|
1010 |
+
cite_count = len(re.findall(r'\\cite\{', latex_code))
|
1011 |
+
|
1012 |
# Render the info panel
|
1013 |
st.markdown('<div class="document-info">', unsafe_allow_html=True)
|
1014 |
|
|
|
1018 |
("Character Count", char_count),
|
1019 |
("Line Count", line_count),
|
1020 |
("Equations", equation_count),
|
1021 |
+
("Align Environments", align_count),
|
1022 |
("Figures", figure_count),
|
1023 |
+
("Tables", table_count),
|
1024 |
+
("Custom Commands", command_count),
|
1025 |
+
("References", ref_count),
|
1026 |
+
("Citations", cite_count)
|
1027 |
]
|
1028 |
|
1029 |
for label, value in info_rows:
|
|
|
1037 |
|
1038 |
st.markdown('</div>', unsafe_allow_html=True)
|
1039 |
|
1040 |
+
# Display the packages used in the document
|
1041 |
+
def render_packages_panel(packages):
|
1042 |
+
if not packages:
|
1043 |
+
return
|
1044 |
+
|
1045 |
+
st.markdown('<div class="packages-panel">', unsafe_allow_html=True)
|
1046 |
+
st.markdown('<div class="packages-header">Imported Packages</div>', unsafe_allow_html=True)
|
1047 |
+
|
1048 |
+
for package in packages:
|
1049 |
+
st.markdown(
|
1050 |
+
f'<div class="package-item">'
|
1051 |
+
f'<span class="package-name">\\usepackage{{{package}}}</span>'
|
1052 |
+
f'</div>',
|
1053 |
+
unsafe_allow_html=True
|
1054 |
+
)
|
1055 |
+
|
1056 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1057 |
+
|
1058 |
+
# Display environments in the document
|
1059 |
+
def render_environments_panel(environments):
|
1060 |
+
if not environments:
|
1061 |
+
return
|
1062 |
+
|
1063 |
+
st.markdown('<div class="environments-panel">', unsafe_allow_html=True)
|
1064 |
+
st.markdown('<div class="environments-header">Environments</div>', unsafe_allow_html=True)
|
1065 |
+
|
1066 |
+
# Count environment types
|
1067 |
+
env_counts = {}
|
1068 |
+
for env in environments:
|
1069 |
+
env_name = env['name']
|
1070 |
+
if env_name in env_counts:
|
1071 |
+
env_counts[env_name] += 1
|
1072 |
+
else:
|
1073 |
+
env_counts[env_name] = 1
|
1074 |
+
|
1075 |
+
# Display counts
|
1076 |
+
for env_name, count in env_counts.items():
|
1077 |
+
st.markdown(
|
1078 |
+
f'<div class="environment-item">'
|
1079 |
+
f'<span class="environment-name">\\begin{{{env_name}}}</span> - {count} instances'
|
1080 |
+
f'</div>',
|
1081 |
+
unsafe_allow_html=True
|
1082 |
+
)
|
1083 |
+
|
1084 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1085 |
+
|
1086 |
+
# Display keyboard shortcuts
|
1087 |
+
def render_keyboard_shortcuts():
|
1088 |
+
st.markdown('<div class="shortcuts-container">', unsafe_allow_html=True)
|
1089 |
+
st.markdown('<div style="font-weight:bold;margin-bottom:10px;">Keyboard Shortcuts</div>', unsafe_allow_html=True)
|
1090 |
+
|
1091 |
+
shortcuts = [
|
1092 |
+
("Ctrl+B", "Insert bold text"),
|
1093 |
+
("Ctrl+I", "Insert italic text"),
|
1094 |
+
("Ctrl+M", "Insert inline math"),
|
1095 |
+
("Ctrl+E", "Insert equation"),
|
1096 |
+
("Ctrl+L", "Insert list"),
|
1097 |
+
("Ctrl+F", "Insert fraction"),
|
1098 |
+
("F5", "Compile document"),
|
1099 |
+
("Ctrl+S", "Save document"),
|
1100 |
+
("Ctrl+/", "Toggle comment"),
|
1101 |
+
("Tab", "Indent")
|
1102 |
+
]
|
1103 |
+
|
1104 |
+
for key, description in shortcuts:
|
1105 |
+
st.markdown(
|
1106 |
+
f'<div class="shortcut-row">'
|
1107 |
+
f'<span class="shortcut-keys">{key}</span>'
|
1108 |
+
f'<span class="shortcut-description">{description}</span>'
|
1109 |
+
f'</div>',
|
1110 |
+
unsafe_allow_html=True
|
1111 |
+
)
|
1112 |
+
|
1113 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1114 |
+
|
1115 |
# Main application
|
1116 |
def main():
|
1117 |
# Initialize session state
|
|
|
1125 |
st.session_state.errors = []
|
1126 |
if 'warnings' not in st.session_state:
|
1127 |
st.session_state.warnings = []
|
1128 |
+
if 'show_syntax_highlight' not in st.session_state:
|
1129 |
+
st.session_state.show_syntax_highlight = False
|
1130 |
|
1131 |
# Check installation status
|
1132 |
if not is_pdflatex_installed():
|
|
|
1137 |
|
1138 |
with col1:
|
1139 |
# Create tabs for main editing area with VS Code style
|
1140 |
+
editor_tabs = st.tabs(["Editor", "Preview", "Settings"])
|
1141 |
|
1142 |
with editor_tabs[0]:
|
1143 |
# VS Code-like editor interface
|
|
|
1146 |
# Tab bar
|
1147 |
render_editor_tabs()
|
1148 |
|
1149 |
+
# Toolbar with functional buttons
|
1150 |
+
render_functional_toolbar()
|
1151 |
|
1152 |
+
# Editor with VS Code styling
|
1153 |
+
st.markdown('<div class="editor-area">', unsafe_allow_html=True)
|
1154 |
latex_code = st.text_area(
|
1155 |
"",
|
1156 |
value=st.session_state.latex_code,
|
|
|
1158 |
key="latex_editor",
|
1159 |
label_visibility="collapsed"
|
1160 |
)
|
1161 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
1162 |
st.session_state.latex_code = latex_code
|
1163 |
|
1164 |
# Status bar
|
|
|
1187 |
st.rerun()
|
1188 |
|
1189 |
with editor_tabs[1]:
|
1190 |
+
# Show syntax highlighted preview
|
1191 |
+
st.markdown("<h3>Syntax Highlighted Preview</h3>", unsafe_allow_html=True)
|
1192 |
+
st.markdown("<p>This shows your LaTeX code with syntax highlighting</p>", unsafe_allow_html=True)
|
1193 |
+
|
1194 |
+
# Display code with syntax highlighting
|
1195 |
+
display_code_with_syntax_highlighting(st.session_state.latex_code)
|
1196 |
+
|
1197 |
+
with editor_tabs[2]:
|
1198 |
st.markdown("<h3>LaTeX Settings</h3>", unsafe_allow_html=True)
|
1199 |
|
1200 |
st.markdown('<div class="control-panel">', unsafe_allow_html=True)
|
|
|
1204 |
st.checkbox("Auto-compile on save", value=False, key="auto_compile")
|
1205 |
st.checkbox("Use pdflatex", value=True, key="use_pdflatex")
|
1206 |
st.checkbox("Enable BibTeX", value=False, key="use_bibtex")
|
1207 |
+
st.checkbox("Show line numbers", value=True, key="show_line_numbers")
|
1208 |
|
1209 |
with col_b:
|
1210 |
st.selectbox("Document Class",
|
|
|
1213 |
st.selectbox("PDF Engine",
|
1214 |
["pdflatex", "xelatex", "lualatex"],
|
1215 |
index=0, key="pdf_engine")
|
1216 |
+
st.selectbox("Editor Theme",
|
1217 |
+
["Dark", "Light", "High Contrast"],
|
1218 |
+
index=0, key="editor_theme")
|
1219 |
|
1220 |
st.markdown('</div>', unsafe_allow_html=True)
|
1221 |
+
|
1222 |
+
# Display keyboard shortcuts
|
1223 |
+
render_keyboard_shortcuts()
|
1224 |
|
1225 |
with col2:
|
1226 |
# Output tabs with VS Code style
|
1227 |
+
output_tabs = st.tabs(["Output", "Outline", "Analysis", "Problems"])
|
1228 |
|
1229 |
with output_tabs[0]:
|
1230 |
# PDF compilation and output
|
|
|
1318 |
structure = extract_document_structure(st.session_state.latex_code)
|
1319 |
else:
|
1320 |
structure = []
|
1321 |
+
|
1322 |
+
if structure:
|
1323 |
+
render_document_outline(structure)
|
1324 |
+
else:
|
1325 |
+
st.info("No document structure detected. Add sections using the toolbar.")
|
1326 |
|
1327 |
with output_tabs[2]:
|
1328 |
+
# Document analysis features
|
1329 |
+
|
1330 |
+
# Document info
|
1331 |
+
render_document_info(st.session_state.latex_code)
|
1332 |
+
|
1333 |
+
# Package analysis
|
1334 |
+
packages = analyze_packages(st.session_state.latex_code)
|
1335 |
+
if packages:
|
1336 |
+
render_packages_panel(packages)
|
1337 |
+
else:
|
1338 |
+
st.info("No packages detected. Add packages using \\usepackage{}.")
|
1339 |
+
|
1340 |
+
# Environment analysis
|
1341 |
+
environments = find_environments(st.session_state.latex_code)
|
1342 |
+
if environments:
|
1343 |
+
render_environments_panel(environments)
|
1344 |
+
|
1345 |
+
with output_tabs[3]:
|
1346 |
# Problems panel (errors & warnings)
|
1347 |
if st.session_state.errors or st.session_state.warnings:
|
1348 |
render_problems(st.session_state.errors, st.session_state.warnings)
|
1349 |
else:
|
1350 |
+
st.info("No problems detected in the document.")
|
|
|
|
|
|
|
|
|
1351 |
|
1352 |
if __name__ == "__main__":
|
1353 |
main()
|