Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 43 additions & 11 deletions src/packages/frontend/frame-editors/latex-editor/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ export class Actions extends BaseActions<LatexEditorState> {
// PDF file watcher - watches directory for PDF file changes
private pdf_watcher?: PDFWatcher;

// Debounced version - initialized in _init2()
update_pdf: (time: number, force: boolean) => void;

// Auto-sync function for cursor position changes (forward sync: source → PDF)
private async handle_cursor_sync_to_pdf(
line: number,
Expand Down Expand Up @@ -179,6 +182,11 @@ export class Actions extends BaseActions<LatexEditorState> {

_init2(): void {
this.set_gutter = this.set_gutter.bind(this);
// Debounce update_pdf with 500ms delay, trailing only, has to work when PDF watcher fires during the build
this.update_pdf = debounce(this._update_pdf.bind(this), 500, {
leading: false,
trailing: true,
});
if (!this.is_public) {
this.init_bad_filename();
this.init_ext_filename(); // safe to set before syncstring init
Expand Down Expand Up @@ -210,7 +218,10 @@ export class Actions extends BaseActions<LatexEditorState> {
this.pdf_watcher = new PDFWatcher(
this.project_id,
pdfPath,
this.update_pdf.bind(this),
// We ignore the PDFs timestamp (mtime) and use last_save_time for consistency with build-triggered updates
(_mtime: number, force: boolean) => {
this.update_pdf(this.last_save_time(), force);
},
);
await this.pdf_watcher.init();
}
Expand Down Expand Up @@ -882,22 +893,25 @@ export class Actions extends BaseActions<LatexEditorState> {
if (this._has_frame_of_type("word_count")) {
run_word_count = this.word_count(time, force);
}
await this.run_latex(time, force);
// update_pdf=false, because it is deferred until the end
await this.run_latex(time, force, false);
// ... and then patch the synctex file to align the source line numberings
if (this.knitr) {
await this.run_patch_synctex(time, force);
}

const s = this.store.unsafe_getIn(["build_logs", "latex", "stdout"]);
let update_pdf = true;
if (typeof s == "string") {
const is_sagetex = s.indexOf("sagetex.sty") != -1;
const is_pythontex =
s.indexOf("pythontex.sty") != -1 || s.indexOf("PythonTeX") != -1;
if (is_sagetex || is_pythontex) {
if (this.ensure_output_directory_disabled()) {
// rebuild if build command changed
await this.run_latex(time, true);
await this.run_latex(time, true, false);
}
update_pdf = false;
if (is_sagetex) {
await this.run_sagetex(time, force);
}
Expand All @@ -908,6 +922,12 @@ export class Actions extends BaseActions<LatexEditorState> {
}
}

// we suppress a cycle of loading the PDF if sagetex or pythontex runs above
// because these two trigger a rebuild and update_pdf on their own at the end
if (update_pdf) {
this.update_pdf(time, force);
}

if (run_word_count != null) {
// and finally, wait for word count to finish -- to make clear the whole operation is done
await run_word_count;
Expand Down Expand Up @@ -994,7 +1014,11 @@ export class Actions extends BaseActions<LatexEditorState> {
}
}

private async run_latex(time: number, force: boolean): Promise<void> {
private async run_latex(
time: number,
force: boolean,
update_pdf: boolean = true,
): Promise<void> {
if (this.is_stopping) return;
let output: BuildLog;
let build_command: string | string[];
Expand Down Expand Up @@ -1051,6 +1075,10 @@ export class Actions extends BaseActions<LatexEditorState> {
this.check_for_fatal_error();
this.update_gutters();
this.update_gutters_soon();
// Explicit PDF reload after latex compilation
if (update_pdf) {
this.update_pdf(time, force);
}
}

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

update_pdf(time: number, force: boolean): void {
private _update_pdf(time: number, force: boolean): void {
const timestamp = this.make_timestamp(time, force);
// forget currently cached pdf
this._forget_pdf_document();
Expand Down Expand Up @@ -1230,11 +1258,13 @@ export class Actions extends BaseActions<LatexEditorState> {
this.get_output_directory(),
);
if (hash === this._last_sagetex_hash) {
// no change - nothing to do
// no change - nothing to do except updating the pdf preview
this.update_pdf(time, force);
return;
}
} catch (err) {
this.set_error(err);
this.update_pdf(time, force);
return;
} finally {
this.set_status("");
Expand Down Expand Up @@ -1264,6 +1294,7 @@ export class Actions extends BaseActions<LatexEditorState> {
await this.run_latex(time + 1, force);
} catch (err) {
this.set_error(err);
this.update_pdf(time, force);
} finally {
this._last_sagetex_hash = hash;
this.set_status("");
Expand Down Expand Up @@ -1303,6 +1334,7 @@ export class Actions extends BaseActions<LatexEditorState> {
} catch (err) {
this.set_error(err);
// this.setState({ pythontex_error: true });
this.update_pdf(time, force);
return;
} finally {
this.set_status("");
Expand Down Expand Up @@ -1469,15 +1501,15 @@ export class Actions extends BaseActions<LatexEditorState> {

_get_most_recent_output_panel(): string | undefined {
let result = this._get_most_recent_active_frame_id_of_type("output");
console.log(
"LaTeX: _get_most_recent_output_panel() via active history returning",
result,
);
// console.log(
// "LaTeX: _get_most_recent_output_panel() via active history returning",
// result,
// );

// If no recently active output panel found, look for any output panel
if (!result) {
result = this._get_any_frame_id_of_type("output");
console.log("LaTeX: _get_any_frame_id_of_type() returning", result);
//console.log("LaTeX: _get_any_frame_id_of_type() returning", result);
}

return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
BUILD_ON_SAVE_ICON_ENABLED,
BUILD_ON_SAVE_LABEL,
} from "@cocalc/frontend/frame-editors/frame-tree/commands/generic-commands";
import { server_time } from "@cocalc/frontend/frame-editors/generic/client";
import { editor, IntlMessage } from "@cocalc/frontend/i18n";
import { DARK_MODE_ICON } from "@cocalc/util/consts/ui";

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

const handleReloadPdf = () => {
actions.update_pdf(server_time().valueOf(), true);
};

const buildMenuItems: MenuProps["items"] = [
{
key: "force-build",
Expand All @@ -83,6 +88,12 @@ export function BuildControls({ actions, id, narrow }: BuildControlsProps) {
{
type: "divider",
},
{
key: "reload-pdf",
label: "Reload PDF",
icon: <Icon name="refresh" />,
onClick: handleReloadPdf,
},
{
key: "download-pdf",
label: intl.formatMessage(COMMANDS.download_pdf.label as IntlMessage),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import { defineMessage, useIntl } from "react-intl";

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

import { Actions } from "./actions";

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

Expand Down Expand Up @@ -183,7 +184,7 @@ export function SyncControls({
onClick={() => handleAutoSyncChange("autoSyncInverse")}
style={{ padding: CONTROL_BUTTON_PADDING }}
>
<Icon unicode={0x21b6} />
<Icon unicode={SYNC_INVERSE_ICON} />
</BSButton>
</Tip>
<Tip
Expand All @@ -196,11 +197,7 @@ export function SyncControls({
onClick={() => handleAutoSyncChange("autoSyncForward")}
style={{ padding: CONTROL_BUTTON_PADDING }}
>
<Icon
unicode={0x21b6}
rotate="180"
style={{ position: "relative", top: "-3px" }}
/>
<Icon unicode={SYNC_FORWARD_ICON} />
</BSButton>
</Tip>
<Tip
Expand All @@ -222,7 +219,11 @@ export function SyncControls({
title={intl.formatMessage(SYNC_HELP_MSG.title)}
placement="bottomLeft"
>
{intl.formatMessage(SYNC_HELP_MSG.content)}
{intl.formatMessage(SYNC_HELP_MSG.content, {
forwardIcon: String.fromCharCode(SYNC_FORWARD_ICON),
inverseIcon: String.fromCharCode(SYNC_INVERSE_ICON),
KB: (ch) => <Text code>{ch}</Text>,
})}
</HelpIcon>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ export class PDFWatcher {

// Listen for directory changes
this.directory_listings.on("change", async (paths: string[]) => {
if (this.directory_listings == null) return;
if (paths.includes(this.watch_dir)) {
try {
const updatedFiles: DirectoryListingEntry[] | undefined =
await this.directory_listings!.get(this.watch_dir);
await this.directory_listings.get(this.watch_dir);
const updatedPdfFile = updatedFiles?.find(
(f) => f.name === this.pdf_filename,
);
Expand Down
4 changes: 3 additions & 1 deletion src/packages/frontend/i18n/trans/ar_EG.json
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,7 @@
"editor.latex.pdf_controls.forward_sync.tooltip": "المزامنة التلقائية من المصدر إلى PDF: تحريك المؤشر يحرك ملف PDF",
"editor.latex.pdf_controls.inverse_sync.tooltip": "المزامنة التلقائية من PDF إلى المصدر: تحريك التمرير في PDF يحرك المؤشر",
"editor.latex.pdf_controls.sync_button.tooltip": "مزامنة عكسية لمرة واحدة إلى محرر المصدر",
"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>",
"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>",
"editor.latex.pdf_controls.sync_help.title": "مساعدة مزامنة LaTeX",
"editor.latex.pdf_embed.title": "PDF - أصلي",
"editor.latex.pdf_embed.title.short": "PDF (native)",
Expand All @@ -608,6 +608,8 @@
"editor.table_of_contents.name": "جدول المحتويات",
"editor.table_of_contents.short": "المحتويات",
"editor.terminal.cmd.help.title": "عرض الوثائق لاستخدام محطة لينكس في CoCalc",
"editor.toggle_pdf_dark_mode.label": "تبديل وضع PDF الداكن",
"editor.toggle_pdf_dark_mode.title": "إيقاف الوضع الداكن لملف PDF لرؤية الملف الأصلي",
"file_actions.compress.name": "ضغط",
"file_actions.copy.name": "نسخ",
"file_actions.create.name": "إنشاء",
Expand Down
4 changes: 3 additions & 1 deletion src/packages/frontend/i18n/trans/de_DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,7 @@
"editor.latex.pdf_controls.forward_sync.tooltip": "Automatische Synchronisierung vom Quelltext zum PDF: Cursorbewegungen scrollen das PDF",
"editor.latex.pdf_controls.inverse_sync.tooltip": "Automatische Synchronisierung von PDF zur Quelle: PDF-Scrollen bewegt den Cursor",
"editor.latex.pdf_controls.sync_button.tooltip": "Einmalige inverse Synchronisation mit dem Quell-Editor",
"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>",
"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>",
"editor.latex.pdf_controls.sync_help.title": "LaTeX-Synchronisierungshilfe",
"editor.latex.pdf_embed.title": "PDF - Native",
"editor.latex.pdf_embed.title.short": "PDF (nativ)",
Expand All @@ -608,6 +608,8 @@
"editor.table_of_contents.name": "Inhaltsverzeichnis",
"editor.table_of_contents.short": "Inhalt",
"editor.terminal.cmd.help.title": "Öffne die Dokumentation für die Nutzung des Linux-Terminals in CoCalc.",
"editor.toggle_pdf_dark_mode.label": "PDF-Dunkelmodus umschalten",
"editor.toggle_pdf_dark_mode.title": "Schalten Sie den Dunkelmodus der PDF aus, um die Originaldatei zu sehen",
"file_actions.compress.name": "Komprimieren",
"file_actions.copy.name": "Kopieren",
"file_actions.create.name": "Erstellen",
Expand Down
4 changes: 3 additions & 1 deletion src/packages/frontend/i18n/trans/es_ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,7 @@
"editor.latex.pdf_controls.forward_sync.tooltip": "Auto-sincronización de fuente a PDF: el cursor mueve el desplazamiento del PDF",
"editor.latex.pdf_controls.inverse_sync.tooltip": "Sincronización automática de PDF a fuente: el desplazamiento del PDF mueve el cursor",
"editor.latex.pdf_controls.sync_button.tooltip": "Sincronización inversa única con el editor de origen",
"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>",
"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>",
"editor.latex.pdf_controls.sync_help.title": "Ayuda de sincronización de LaTeX",
"editor.latex.pdf_embed.title": "PDF - Nativo",
"editor.latex.pdf_embed.title.short": "PDF (nativo)",
Expand All @@ -608,6 +608,8 @@
"editor.table_of_contents.name": "Tabla de Contenidos",
"editor.table_of_contents.short": "Contenido",
"editor.terminal.cmd.help.title": "Mostrar documentación para usar el Terminal de Linux en CoCalc",
"editor.toggle_pdf_dark_mode.label": "Alternar modo oscuro de PDF",
"editor.toggle_pdf_dark_mode.title": "Desactivar el modo oscuro del PDF para ver el archivo original",
"file_actions.compress.name": "Comprimir",
"file_actions.copy.name": "Copiar",
"file_actions.create.name": "Crear",
Expand Down
Loading