Skip to content

Commit ae5d36c

Browse files
Merge pull request #8616 from sagemathinc/latex-output-fixes3
frontend/latex/output: change auto forward/inverse sync icons
2 parents 5d43ee5 + 018fede commit ae5d36c

File tree

23 files changed

+129
-42
lines changed

23 files changed

+129
-42
lines changed

src/packages/frontend/frame-editors/latex-editor/actions.ts

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ export class Actions extends BaseActions<LatexEditorState> {
144144
// PDF file watcher - watches directory for PDF file changes
145145
private pdf_watcher?: PDFWatcher;
146146

147+
// Debounced version - initialized in _init2()
148+
update_pdf: (time: number, force: boolean) => void;
149+
147150
// Auto-sync function for cursor position changes (forward sync: source → PDF)
148151
private async handle_cursor_sync_to_pdf(
149152
line: number,
@@ -179,6 +182,11 @@ export class Actions extends BaseActions<LatexEditorState> {
179182

180183
_init2(): void {
181184
this.set_gutter = this.set_gutter.bind(this);
185+
// Debounce update_pdf with 500ms delay, trailing only, has to work when PDF watcher fires during the build
186+
this.update_pdf = debounce(this._update_pdf.bind(this), 500, {
187+
leading: false,
188+
trailing: true,
189+
});
182190
if (!this.is_public) {
183191
this.init_bad_filename();
184192
this.init_ext_filename(); // safe to set before syncstring init
@@ -210,7 +218,10 @@ export class Actions extends BaseActions<LatexEditorState> {
210218
this.pdf_watcher = new PDFWatcher(
211219
this.project_id,
212220
pdfPath,
213-
this.update_pdf.bind(this),
221+
// We ignore the PDFs timestamp (mtime) and use last_save_time for consistency with build-triggered updates
222+
(_mtime: number, force: boolean) => {
223+
this.update_pdf(this.last_save_time(), force);
224+
},
214225
);
215226
await this.pdf_watcher.init();
216227
}
@@ -882,22 +893,25 @@ export class Actions extends BaseActions<LatexEditorState> {
882893
if (this._has_frame_of_type("word_count")) {
883894
run_word_count = this.word_count(time, force);
884895
}
885-
await this.run_latex(time, force);
896+
// update_pdf=false, because it is deferred until the end
897+
await this.run_latex(time, force, false);
886898
// ... and then patch the synctex file to align the source line numberings
887899
if (this.knitr) {
888900
await this.run_patch_synctex(time, force);
889901
}
890902

891903
const s = this.store.unsafe_getIn(["build_logs", "latex", "stdout"]);
904+
let update_pdf = true;
892905
if (typeof s == "string") {
893906
const is_sagetex = s.indexOf("sagetex.sty") != -1;
894907
const is_pythontex =
895908
s.indexOf("pythontex.sty") != -1 || s.indexOf("PythonTeX") != -1;
896909
if (is_sagetex || is_pythontex) {
897910
if (this.ensure_output_directory_disabled()) {
898911
// rebuild if build command changed
899-
await this.run_latex(time, true);
912+
await this.run_latex(time, true, false);
900913
}
914+
update_pdf = false;
901915
if (is_sagetex) {
902916
await this.run_sagetex(time, force);
903917
}
@@ -908,6 +922,12 @@ export class Actions extends BaseActions<LatexEditorState> {
908922
}
909923
}
910924

925+
// we suppress a cycle of loading the PDF if sagetex or pythontex runs above
926+
// because these two trigger a rebuild and update_pdf on their own at the end
927+
if (update_pdf) {
928+
this.update_pdf(time, force);
929+
}
930+
911931
if (run_word_count != null) {
912932
// and finally, wait for word count to finish -- to make clear the whole operation is done
913933
await run_word_count;
@@ -994,7 +1014,11 @@ export class Actions extends BaseActions<LatexEditorState> {
9941014
}
9951015
}
9961016

997-
private async run_latex(time: number, force: boolean): Promise<void> {
1017+
private async run_latex(
1018+
time: number,
1019+
force: boolean,
1020+
update_pdf: boolean = true,
1021+
): Promise<void> {
9981022
if (this.is_stopping) return;
9991023
let output: BuildLog;
10001024
let build_command: string | string[];
@@ -1051,6 +1075,10 @@ export class Actions extends BaseActions<LatexEditorState> {
10511075
this.check_for_fatal_error();
10521076
this.update_gutters();
10531077
this.update_gutters_soon();
1078+
// Explicit PDF reload after latex compilation
1079+
if (update_pdf) {
1080+
this.update_pdf(time, force);
1081+
}
10541082
}
10551083

10561084
// this *merges* errors from log into an eventually already existing this.parsed_output_log
@@ -1186,7 +1214,7 @@ export class Actions extends BaseActions<LatexEditorState> {
11861214
});
11871215
}
11881216

1189-
update_pdf(time: number, force: boolean): void {
1217+
private _update_pdf(time: number, force: boolean): void {
11901218
const timestamp = this.make_timestamp(time, force);
11911219
// forget currently cached pdf
11921220
this._forget_pdf_document();
@@ -1230,11 +1258,13 @@ export class Actions extends BaseActions<LatexEditorState> {
12301258
this.get_output_directory(),
12311259
);
12321260
if (hash === this._last_sagetex_hash) {
1233-
// no change - nothing to do
1261+
// no change - nothing to do except updating the pdf preview
1262+
this.update_pdf(time, force);
12341263
return;
12351264
}
12361265
} catch (err) {
12371266
this.set_error(err);
1267+
this.update_pdf(time, force);
12381268
return;
12391269
} finally {
12401270
this.set_status("");
@@ -1264,6 +1294,7 @@ export class Actions extends BaseActions<LatexEditorState> {
12641294
await this.run_latex(time + 1, force);
12651295
} catch (err) {
12661296
this.set_error(err);
1297+
this.update_pdf(time, force);
12671298
} finally {
12681299
this._last_sagetex_hash = hash;
12691300
this.set_status("");
@@ -1303,6 +1334,7 @@ export class Actions extends BaseActions<LatexEditorState> {
13031334
} catch (err) {
13041335
this.set_error(err);
13051336
// this.setState({ pythontex_error: true });
1337+
this.update_pdf(time, force);
13061338
return;
13071339
} finally {
13081340
this.set_status("");
@@ -1469,15 +1501,15 @@ export class Actions extends BaseActions<LatexEditorState> {
14691501

14701502
_get_most_recent_output_panel(): string | undefined {
14711503
let result = this._get_most_recent_active_frame_id_of_type("output");
1472-
console.log(
1473-
"LaTeX: _get_most_recent_output_panel() via active history returning",
1474-
result,
1475-
);
1504+
// console.log(
1505+
// "LaTeX: _get_most_recent_output_panel() via active history returning",
1506+
// result,
1507+
// );
14761508

14771509
// If no recently active output panel found, look for any output panel
14781510
if (!result) {
14791511
result = this._get_any_frame_id_of_type("output");
1480-
console.log("LaTeX: _get_any_frame_id_of_type() returning", result);
1512+
//console.log("LaTeX: _get_any_frame_id_of_type() returning", result);
14811513
}
14821514

14831515
return result;

src/packages/frontend/frame-editors/latex-editor/output-control-build.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
BUILD_ON_SAVE_ICON_ENABLED,
2323
BUILD_ON_SAVE_LABEL,
2424
} from "@cocalc/frontend/frame-editors/frame-tree/commands/generic-commands";
25+
import { server_time } from "@cocalc/frontend/frame-editors/generic/client";
2526
import { editor, IntlMessage } from "@cocalc/frontend/i18n";
2627
import { DARK_MODE_ICON } from "@cocalc/util/consts/ui";
2728

@@ -67,6 +68,10 @@ export function BuildControls({ actions, id, narrow }: BuildControlsProps) {
6768
set_account_table({ editor_settings: { build_on_save: !buildOnSave } });
6869
};
6970

71+
const handleReloadPdf = () => {
72+
actions.update_pdf(server_time().valueOf(), true);
73+
};
74+
7075
const buildMenuItems: MenuProps["items"] = [
7176
{
7277
key: "force-build",
@@ -83,6 +88,12 @@ export function BuildControls({ actions, id, narrow }: BuildControlsProps) {
8388
{
8489
type: "divider",
8590
},
91+
{
92+
key: "reload-pdf",
93+
label: "Reload PDF",
94+
icon: <Icon name="refresh" />,
95+
onClick: handleReloadPdf,
96+
},
8697
{
8798
key: "download-pdf",
8899
label: intl.formatMessage(COMMANDS.download_pdf.label as IntlMessage),

src/packages/frontend/frame-editors/latex-editor/output-control-sync.tsx

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import { defineMessage, useIntl } from "react-intl";
1414

1515
import { Button as BSButton } from "@cocalc/frontend/antd-bootstrap";
1616
import { useRedux } from "@cocalc/frontend/app-framework";
17-
import { HelpIcon, Icon, Tip } from "@cocalc/frontend/components";
17+
import { HelpIcon, Icon, Text, Tip } from "@cocalc/frontend/components";
18+
import { SYNC_FORWARD_ICON, SYNC_INVERSE_ICON } from "@cocalc/util/consts/ui";
1819

1920
import { Actions } from "./actions";
2021

@@ -52,17 +53,17 @@ const SYNC_HELP_MSG = {
5253
id: "editor.latex.pdf_controls.sync_help.content",
5354
defaultMessage: `<p><strong>Manual Mode:</strong></p>
5455
<ul>
55-
<li>Use ALT+Return in source document to jump to corresponding PDF location</li>
56+
<li>Use <KB>Alt+Return</KB> (<KB>Option+Return</KB> on Mac) in source document to jump to corresponding PDF location</li>
5657
<li>Double-click in PDF or the "Sync" button for inverse search to source</li>
5758
</ul>
5859
<p><strong>Automatic Mode:</strong></p>
5960
<ul>
60-
<li>Forward Sync (): Syncs automatically from cursor changes in source to PDF</li>
61-
<li>Inverse Sync (): Moving the PDF viewport moves the cursor in source</li>
61+
<li>Forward Sync ({forwardIcon}): Syncs automatically from cursor position changes in source to PDF</li>
62+
<li>Inverse Sync ({inverseIcon}): Scrolling the PDF changes the cursor position in source</li>
6263
</ul>
6364
<p>This functionality uses SyncTeX to coordinate between LaTeX source and PDF output.</p>`,
6465
description:
65-
"Complete explanation of LaTeX sync functionality including manual and automatic modes",
66+
"Explanation of LaTeX sync functionality including manual and automatic modes",
6667
}),
6768
};
6869

@@ -183,7 +184,7 @@ export function SyncControls({
183184
onClick={() => handleAutoSyncChange("autoSyncInverse")}
184185
style={{ padding: CONTROL_BUTTON_PADDING }}
185186
>
186-
<Icon unicode={0x21b6} />
187+
<Icon unicode={SYNC_INVERSE_ICON} />
187188
</BSButton>
188189
</Tip>
189190
<Tip
@@ -196,11 +197,7 @@ export function SyncControls({
196197
onClick={() => handleAutoSyncChange("autoSyncForward")}
197198
style={{ padding: CONTROL_BUTTON_PADDING }}
198199
>
199-
<Icon
200-
unicode={0x21b6}
201-
rotate="180"
202-
style={{ position: "relative", top: "-3px" }}
203-
/>
200+
<Icon unicode={SYNC_FORWARD_ICON} />
204201
</BSButton>
205202
</Tip>
206203
<Tip
@@ -222,7 +219,11 @@ export function SyncControls({
222219
title={intl.formatMessage(SYNC_HELP_MSG.title)}
223220
placement="bottomLeft"
224221
>
225-
{intl.formatMessage(SYNC_HELP_MSG.content)}
222+
{intl.formatMessage(SYNC_HELP_MSG.content, {
223+
forwardIcon: String.fromCharCode(SYNC_FORWARD_ICON),
224+
inverseIcon: String.fromCharCode(SYNC_INVERSE_ICON),
225+
KB: (ch) => <Text code>{ch}</Text>,
226+
})}
226227
</HelpIcon>
227228
)}
228229
</div>

src/packages/frontend/frame-editors/latex-editor/pdf-watcher.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,11 @@ export class PDFWatcher {
5050

5151
// Listen for directory changes
5252
this.directory_listings.on("change", async (paths: string[]) => {
53+
if (this.directory_listings == null) return;
5354
if (paths.includes(this.watch_dir)) {
5455
try {
5556
const updatedFiles: DirectoryListingEntry[] | undefined =
56-
await this.directory_listings!.get(this.watch_dir);
57+
await this.directory_listings.get(this.watch_dir);
5758
const updatedPdfFile = updatedFiles?.find(
5859
(f) => f.name === this.pdf_filename,
5960
);

src/packages/frontend/i18n/trans/ar_EG.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@
595595
"editor.latex.pdf_controls.forward_sync.tooltip": "المزامنة التلقائية من المصدر إلى PDF: تحريك المؤشر يحرك ملف PDF",
596596
"editor.latex.pdf_controls.inverse_sync.tooltip": "المزامنة التلقائية من PDF إلى المصدر: تحريك التمرير في PDF يحرك المؤشر",
597597
"editor.latex.pdf_controls.sync_button.tooltip": "مزامنة عكسية لمرة واحدة إلى محرر المصدر",
598-
"editor.latex.pdf_controls.sync_help.content": "<p><strong>الوضع اليدوي:</strong></p> <ul> <li>استخدم ALT+Return في المستند المصدر للقفز إلى الموقع المقابل في PDF</li> <li>انقر نقرًا مزدوجًا في PDF أو على زر \"المزامنة\" للبحث العكسي في المصدر</li> </ul> <p><strong>الوضع التلقائي:</strong></p> <ul> <li>المزامنة الأمامية (): تقوم بالمزامنة تلقائيًا من تغييرات المؤشر في المصدر إلى PDF</li> <li>المزامنة العكسية (): تحريك عرض PDF يحرك المؤشر في المصدر</li> </ul> <p>تستخدم هذه الوظيفة SyncTeX للتنسيق بين مصدر LaTeX ومخرجات PDF.</p>",
598+
"editor.latex.pdf_controls.sync_help.content": "<p><strong>الوضع اليدوي:</strong></p> <ul> <li>استخدم <KB>Alt+Return</KB> (<KB>Option+Return</KB> على ماك) في المستند المصدر للانتقال إلى الموقع المقابل في PDF</li> <li>انقر نقرًا مزدوجًا في PDF أو زر \"المزامنة\" للبحث العكسي إلى المصدر</li> </ul> <p><strong>الوضع التلقائي:</strong></p> <ul> <li>المزامنة الأمامية ({forwardIcon}): تتم المزامنة تلقائيًا من تغييرات موضع المؤشر في المصدر إلى PDF</li> <li>المزامنة العكسية ({inverseIcon}): يؤدي التمرير في PDF إلى تغيير موضع المؤشر في المصدر</li> </ul> <p>تستخدم هذه الوظيفة SyncTeX للتنسيق بين مصدر LaTeX وإخراج PDF.</p>",
599599
"editor.latex.pdf_controls.sync_help.title": "مساعدة مزامنة LaTeX",
600600
"editor.latex.pdf_embed.title": "PDF - أصلي",
601601
"editor.latex.pdf_embed.title.short": "PDF (native)",
@@ -608,6 +608,8 @@
608608
"editor.table_of_contents.name": "جدول المحتويات",
609609
"editor.table_of_contents.short": "المحتويات",
610610
"editor.terminal.cmd.help.title": "عرض الوثائق لاستخدام محطة لينكس في CoCalc",
611+
"editor.toggle_pdf_dark_mode.label": "تبديل وضع PDF الداكن",
612+
"editor.toggle_pdf_dark_mode.title": "إيقاف الوضع الداكن لملف PDF لرؤية الملف الأصلي",
611613
"file_actions.compress.name": "ضغط",
612614
"file_actions.copy.name": "نسخ",
613615
"file_actions.create.name": "إنشاء",

src/packages/frontend/i18n/trans/de_DE.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@
595595
"editor.latex.pdf_controls.forward_sync.tooltip": "Automatische Synchronisierung vom Quelltext zum PDF: Cursorbewegungen scrollen das PDF",
596596
"editor.latex.pdf_controls.inverse_sync.tooltip": "Automatische Synchronisierung von PDF zur Quelle: PDF-Scrollen bewegt den Cursor",
597597
"editor.latex.pdf_controls.sync_button.tooltip": "Einmalige inverse Synchronisation mit dem Quell-Editor",
598-
"editor.latex.pdf_controls.sync_help.content": "<p><strong>Manueller Modus:</strong></p> <ul> <li>Verwenden Sie ALT+Return im Quelldokument, um zur entsprechenden PDF-Position zu springen</li> <li>Doppelklicken Sie im PDF oder auf die Schaltfläche \"Sync\" für die inverse Suche zur Quelle</li> </ul> <p><strong>Automatischer Modus:</strong></p> <ul> <li>Vorwärtssynchronisierung (): Synchronisiert automatisch von Cursoränderungen in der Quelle zum PDF</li> <li>Inverse Synchronisierung (): Verschieben des PDF-Ansichtsbereichs verschiebt den Cursor in der Quelle</li> </ul> <p>Diese Funktionalität verwendet SyncTeX, um zwischen LaTeX-Quelle und PDF-Ausgabe zu koordinieren.</p>",
598+
"editor.latex.pdf_controls.sync_help.content": "<p><strong>Manueller Modus:</strong></p> <ul> <li>Verwenden Sie <KB>Alt+Return</KB> (<KB>Option+Return</KB> auf Mac) im Quelldokument, um zur entsprechenden PDF-Position zu springen</li> <li>Doppelklicken Sie im PDF oder auf die \"Sync\"-Schaltfläche für inverse Suche zur Quelle</li> </ul> <p><strong>Automatischer Modus:</strong></p> <ul> <li>Vorwärtssynchronisierung ({forwardIcon}): Synchronisiert automatisch von Cursorpositionsänderungen in der Quelle zum PDF</li> <li>Inverse Synchronisierung ({inverseIcon}): Durch Scrollen im PDF ändert sich die Cursorposition in der Quelle</li> </ul> <p>Diese Funktionalität verwendet SyncTeX, um zwischen LaTeX-Quelle und PDF-Ausgabe zu koordinieren.</p>",
599599
"editor.latex.pdf_controls.sync_help.title": "LaTeX-Synchronisierungshilfe",
600600
"editor.latex.pdf_embed.title": "PDF - Native",
601601
"editor.latex.pdf_embed.title.short": "PDF (nativ)",
@@ -608,6 +608,8 @@
608608
"editor.table_of_contents.name": "Inhaltsverzeichnis",
609609
"editor.table_of_contents.short": "Inhalt",
610610
"editor.terminal.cmd.help.title": "Öffne die Dokumentation für die Nutzung des Linux-Terminals in CoCalc.",
611+
"editor.toggle_pdf_dark_mode.label": "PDF-Dunkelmodus umschalten",
612+
"editor.toggle_pdf_dark_mode.title": "Schalten Sie den Dunkelmodus der PDF aus, um die Originaldatei zu sehen",
611613
"file_actions.compress.name": "Komprimieren",
612614
"file_actions.copy.name": "Kopieren",
613615
"file_actions.create.name": "Erstellen",

src/packages/frontend/i18n/trans/es_ES.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@
595595
"editor.latex.pdf_controls.forward_sync.tooltip": "Auto-sincronización de fuente a PDF: el cursor mueve el desplazamiento del PDF",
596596
"editor.latex.pdf_controls.inverse_sync.tooltip": "Sincronización automática de PDF a fuente: el desplazamiento del PDF mueve el cursor",
597597
"editor.latex.pdf_controls.sync_button.tooltip": "Sincronización inversa única con el editor de origen",
598-
"editor.latex.pdf_controls.sync_help.content": "<p><strong>Modo Manual:</strong></p> <ul> <li>Usa ALT+Return en el documento fuente para saltar a la ubicación correspondiente en el PDF</li> <li>Haz doble clic en el PDF o en el botón \"Sincronizar\" para buscar inversamente en la fuente</li> </ul> <p><strong>Modo Automático:</strong></p> <ul> <li>Sincronización directa (→): Sincroniza automáticamente los cambios de cursor en la fuente al PDF</li> <li>Sincronización inversa (←): Mover la vista del PDF mueve el cursor en la fuente</li> </ul> <p>Esta funcionalidad utiliza SyncTeX para coordinar entre la fuente LaTeX y la salida PDF.</p>",
598+
"editor.latex.pdf_controls.sync_help.content": "<p><strong>Modo Manual:</strong></p> <ul> <li>Usa <KB>Alt+Return</KB> (<KB>Option+Return</KB> en Mac) en el documento fuente para saltar a la ubicación correspondiente en el PDF</li> <li>Haz doble clic en el PDF o en el botón \"Sync\" para la búsqueda inversa en la fuente</li> </ul> <p><strong>Modo Automático:</strong></p> <ul> <li>Sincronización Directa ({forwardIcon}): Sincroniza automáticamente desde los cambios de posición del cursor en la fuente al PDF</li> <li>Sincronización Inversa ({inverseIcon}): Desplazar el PDF cambia la posición del cursor en la fuente</li> </ul> <p>Esta funcionalidad utiliza SyncTeX para coordinar entre la fuente LaTeX y la salida PDF.</p>",
599599
"editor.latex.pdf_controls.sync_help.title": "Ayuda de sincronización de LaTeX",
600600
"editor.latex.pdf_embed.title": "PDF - Nativo",
601601
"editor.latex.pdf_embed.title.short": "PDF (nativo)",
@@ -608,6 +608,8 @@
608608
"editor.table_of_contents.name": "Tabla de Contenidos",
609609
"editor.table_of_contents.short": "Contenido",
610610
"editor.terminal.cmd.help.title": "Mostrar documentación para usar el Terminal de Linux en CoCalc",
611+
"editor.toggle_pdf_dark_mode.label": "Alternar modo oscuro de PDF",
612+
"editor.toggle_pdf_dark_mode.title": "Desactivar el modo oscuro del PDF para ver el archivo original",
611613
"file_actions.compress.name": "Comprimir",
612614
"file_actions.copy.name": "Copiar",
613615
"file_actions.create.name": "Crear",

0 commit comments

Comments
 (0)