Skip to content

Commit 5747662

Browse files
committed
frontend/latex: first idea for async compilation
1 parent 10a363a commit 5747662

File tree

8 files changed

+238
-85
lines changed

8 files changed

+238
-85
lines changed

src/packages/frontend/client/project.ts

+9-7
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ import { appBasePath } from "@cocalc/frontend/customize/app-base-path";
1212
import { ipywidgetsGetBufferUrl } from "@cocalc/frontend/jupyter/server-urls";
1313
import { HOME_ROOT } from "@cocalc/util/consts/files";
1414
import type { ApiKey } from "@cocalc/util/db-schema/api-keys";
15-
import type { ExecOpts, ExecOutput } from "@cocalc/util/db-schema/projects";
15+
import {
16+
isExecOptsBlocking,
17+
type ExecOpts,
18+
type ExecOutput,
19+
} from "@cocalc/util/db-schema/projects";
1620
import * as message from "@cocalc/util/message";
1721
import {
1822
coerce_codomain_to_numbers,
@@ -216,12 +220,10 @@ export class ProjectClient {
216220
});
217221
}
218222

219-
if (
220-
!(await ensure_project_running(
221-
opts.project_id,
222-
`execute the command ${opts.command}`,
223-
))
224-
) {
223+
const msg = isExecOptsBlocking(opts)
224+
? `execute the command ${opts.command}`
225+
: `getting job ${opts.async_get}`;
226+
if (!(await ensure_project_running(opts.project_id, msg))) {
225227
return {
226228
type: "blocking",
227229
stdout: "",

src/packages/frontend/frame-editors/generic/client.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,14 @@ import { webapp_client } from "@cocalc/frontend/webapp-client";
1414
import { CompressedPatch } from "@cocalc/sync/editor/generic/types";
1515
import { callback2 } from "@cocalc/util/async-utils";
1616
import { Config as FormatterConfig } from "@cocalc/util/code-formatter";
17-
import type { ExecOpts, ExecOutput } from "@cocalc/util/db-schema/projects";
1817
import { FakeSyncstring } from "./syncstring-fake";
1918

19+
import type { ExecOpts, ExecOutput } from "@cocalc/util/db-schema/projects";
2020
export type { ExecOpts, ExecOutput };
2121

22-
const schema = require("@cocalc/util/schema");
22+
import * as schema from "@cocalc/util/schema";
2323

24-
const DEFAULT_FONT_SIZE: number =
25-
require("@cocalc/util/db-schema").DEFAULT_FONT_SIZE;
24+
import { DEFAULT_FONT_SIZE } from "@cocalc/util/db-schema";
2625

2726
export function server_time(): Date {
2827
return webapp_client.time_client.server_time();

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

+40-8
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { fromJS, List, Map } from "immutable";
2525
import { debounce, union } from "lodash";
2626
import { normalize as path_normalize } from "path";
2727

28-
import { Store } from "@cocalc/frontend/app-framework";
28+
import { Store, TypedMap } from "@cocalc/frontend/app-framework";
2929
import {
3030
TableOfContentsEntry,
3131
TableOfContentsEntryList,
@@ -53,8 +53,9 @@ import {
5353
} from "@cocalc/util/misc";
5454
import { reuseInFlight } from "@cocalc/util/reuse-in-flight";
5555

56+
import { ExecOutput } from "@cocalc/util/db-schema/projects";
57+
import { ExecuteCodeOutputAsync } from "@cocalc/util/types/execute-code";
5658
import { bibtex } from "./bibtex";
57-
import { IBuildSpecs } from "./build";
5859
import { clean } from "./clean";
5960
import { KNITR_EXTS } from "./constants";
6061
import { count_words } from "./count_words";
@@ -75,6 +76,8 @@ import { parseTableOfContents } from "./table-of-contents";
7576
import {
7677
BuildLog,
7778
BuildLogs,
79+
IBuildSpecs,
80+
ProcessInfos,
7881
ScrollIntoViewMap,
7982
ScrollIntoViewRecord,
8083
} from "./types";
@@ -94,6 +97,7 @@ interface LatexEditorState extends CodeEditorState {
9497
includeError?: string;
9598
build_command_hardcoded?: boolean; // if true, an % !TeX cocalc = ... directive sets the command via the document itself
9699
contents?: TableOfContentsEntryList; // table of contents data.
100+
proc_infos: ProcessInfos;
97101
}
98102

99103
export class Actions extends BaseActions<LatexEditorState> {
@@ -694,7 +698,8 @@ export class Actions extends BaseActions<LatexEditorState> {
694698
}
695699

696700
async run_build(time: number, force: boolean): Promise<void> {
697-
(this as unknown as any).setState({ build_logs: Map() });
701+
this.setState({ build_logs: Map() });
702+
this.setState({ proc_infos: Map() });
698703

699704
if (this.bad_filename) {
700705
const err = `ERROR: It is not possible to compile this LaTeX file with the name '${this.path}'.
@@ -846,6 +851,7 @@ export class Actions extends BaseActions<LatexEditorState> {
846851
}
847852
this.set_error("");
848853
this.set_build_logs({ latex: undefined });
854+
this.set_proc_infos({ latex: undefined });
849855
if (typeof s == "string") {
850856
build_command = s;
851857
} else {
@@ -854,14 +860,18 @@ export class Actions extends BaseActions<LatexEditorState> {
854860
const status = (s) => this.set_status(`Running Latex... ${s}`);
855861
status("");
856862
try {
857-
output = await latexmk(
863+
const [info, job] = await latexmk(
858864
this.project_id,
859865
this.path,
860866
build_command,
861867
timestamp,
862868
status,
863869
this.get_output_directory(),
864870
);
871+
this.set_proc_infos({ latex: info });
872+
output = await job;
873+
if (output.type !== "async") throw new Error("not an asnyc job");
874+
this.set_proc_infos({ latex: output });
865875
} catch (err) {
866876
this.set_error(err);
867877
return;
@@ -1080,6 +1090,7 @@ export class Actions extends BaseActions<LatexEditorState> {
10801090
status,
10811091
this.get_output_directory(),
10821092
);
1093+
if (!output) throw new Error("Unable to run SageTeX.");
10831094
if (output.stderr.indexOf("sagetex.VersionError") != -1) {
10841095
// See https://github.com/sagemathinc/cocalc/issues/4432
10851096
throw Error(
@@ -1274,16 +1285,34 @@ export class Actions extends BaseActions<LatexEditorState> {
12741285
});
12751286
}
12761287

1288+
set_proc_infos(obj: {
1289+
[K in keyof IBuildSpecs]?: ExecuteCodeOutputAsync;
1290+
}): void {
1291+
let proc_infos: ProcessInfos = this.store.get("proc_infos");
1292+
if (!proc_infos) {
1293+
return;
1294+
}
1295+
for (const k in obj) {
1296+
const v: ExecuteCodeOutputAsync = obj[k];
1297+
if (!v) continue;
1298+
proc_infos = proc_infos.set(
1299+
k as any,
1300+
fromJS(v) as any as TypedMap<ExecOutput>,
1301+
);
1302+
}
1303+
this.setState({ proc_infos });
1304+
}
1305+
12771306
set_build_logs(obj: { [K in keyof IBuildSpecs]?: BuildLog }): void {
12781307
let build_logs: BuildLogs = this.store.get("build_logs");
12791308
if (!build_logs) {
12801309
// may have already been closed.
12811310
return;
12821311
}
1283-
let k: string;
1284-
for (k in obj) {
1312+
for (const k in obj) {
12851313
const v: BuildLog = obj[k];
1286-
build_logs = build_logs.set(k, fromJS(v));
1314+
if (!v) continue;
1315+
build_logs = build_logs.set(k, fromJS(v) as any as TypedMap<BuildLog>);
12871316
}
12881317
this.setState({ build_logs });
12891318
}
@@ -1296,7 +1325,10 @@ export class Actions extends BaseActions<LatexEditorState> {
12961325
log += s + "\n";
12971326
const build_logs: BuildLogs = this.store.get("build_logs");
12981327
this.setState({
1299-
build_logs: build_logs.set("clean", fromJS({ output: log })),
1328+
build_logs: build_logs.set(
1329+
"clean",
1330+
fromJS({ output: log }) as any as TypedMap<BuildLog>,
1331+
),
13001332
});
13011333
};
13021334

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

+12-20
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,12 @@ import Ansi from "@cocalc/ansi-to-react";
1111

1212
import { AntdTabItem, Tab, Tabs } from "@cocalc/frontend/antd-bootstrap";
1313
import { React, Rendered, useRedux } from "@cocalc/frontend/app-framework";
14-
import { IconName, Loading } from "@cocalc/frontend/components";
14+
import { Loading } from "@cocalc/frontend/components";
1515
import { path_split } from "@cocalc/util/misc";
1616
import { COLORS } from "@cocalc/util/theme";
1717
import { BuildCommand } from "./build-command";
18-
import { use_build_logs } from "./hooks";
19-
import { BuildLogs } from "./types";
20-
21-
interface IBuildSpec {
22-
button: boolean;
23-
label: string;
24-
icon: IconName;
25-
tip: string;
26-
}
27-
28-
export interface IBuildSpecs {
29-
build: IBuildSpec;
30-
latex: IBuildSpec;
31-
bibtex: IBuildSpec;
32-
sagetex: IBuildSpec;
33-
pythontex: IBuildSpec;
34-
knitr: IBuildSpec;
35-
clean: IBuildSpec;
36-
}
18+
import { use_build_logs, use_proc_infos } from "./hooks";
19+
import { BuildLogs, IBuildSpecs, ProcessInfos } from "./types";
3720

3821
const BUILD_SPECS: IBuildSpecs = {
3922
build: {
@@ -99,6 +82,7 @@ export const Build: React.FC<Props> = React.memo((props) => {
9982

10083
const font_size = 0.8 * font_size_orig;
10184
const build_logs: BuildLogs = use_build_logs(name);
85+
const proc_infos: ProcessInfos = use_proc_infos(name);
10286
const build_command = useRedux([name, "build_command"]);
10387
const build_command_hardcoded =
10488
useRedux([name, "build_command_hardcoded"]) ?? false;
@@ -218,6 +202,13 @@ export const Build: React.FC<Props> = React.memo((props) => {
218202
);
219203
}
220204

205+
function render_procs(): Rendered {
206+
if (!proc_infos) return;
207+
return (
208+
<div style={{ margin: "15px" }}>procs={JSON.stringify(proc_infos)}</div>
209+
);
210+
}
211+
221212
function render_status(): Rendered {
222213
if (status) {
223214
return (
@@ -251,6 +242,7 @@ export const Build: React.FC<Props> = React.memo((props) => {
251242
>
252243
{render_build_command()}
253244
{render_status()}
245+
{render_procs()}
254246
{logs}
255247
</div>
256248
);

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

+23-13
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,36 @@
44
*/
55

66
import { Map } from "immutable";
7+
import { isEqual } from "lodash";
78

89
import { React, useRedux } from "@cocalc/frontend/app-framework";
9-
import { BuildLogs } from "./types";
10+
import { BuildLogs, ProcessInfos } from "./types";
1011

1112
export function use_build_logs(name: string): BuildLogs {
12-
const build_logs_next: BuildLogs =
13-
useRedux([name, "build_logs"]) ?? Map<string, any>();
14-
const [build_logs, set_build_logs] = React.useState<BuildLogs>(
15-
Map<string, any>(),
16-
);
13+
return use_infos<BuildLogs>(name, "build_logs");
14+
}
15+
16+
export function use_proc_infos(name: string): ProcessInfos {
17+
return use_infos<ProcessInfos>(name, "proc_infos");
18+
}
19+
20+
function use_infos<T extends Map<string, any>>(
21+
name: string,
22+
aspect: "build_logs" | "proc_infos",
23+
) {
24+
const data_next: T = useRedux([name, aspect]) ?? Map<string, any>();
25+
const [data, set_data] = React.useState<T>(Map<string, any>() as any as T);
1726

18-
// only update if any parsed logs differ
27+
// only update if any parsed logs or process infos differ
1928
for (const key of ["latex", "knitr", "pythontex", "sagetex"]) {
20-
if (
21-
build_logs_next.getIn([key, "parse"]) != build_logs.getIn([key, "parse"])
22-
) {
23-
set_build_logs(build_logs_next);
24-
break;
29+
const isDiff =
30+
aspect === "build_logs"
31+
? data_next.getIn([key, "parse"]) != data.getIn([key, "parse"])
32+
: !isEqual(data_next.get(key), data.get(key));
33+
if (isDiff) {
34+
set_data(data_next);
2535
}
2636
}
2737

28-
return build_logs;
38+
return data;
2939
}

0 commit comments

Comments
 (0)