CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/project/formatters/index.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
/*
7
Use a formatter like prettier to reformat a syncstring.
8
9
This very nicely use the in-memory node module to prettyify code, by simply modifying the syncstring
10
on the backend. This avoids having to send the whole file back and forth, worrying about multiple users
11
and their cursors, file state etc. -- it just merges in the prettification at a point in time.
12
Also, by doing this on the backend we don't add 5MB (!) to the webpack frontend bundle, to install
13
something that is not supported on the frontend anyway.
14
*/
15
16
declare let require: any;
17
18
import { make_patch } from "@cocalc/sync/editor/generic/util";
19
import { math_escape, math_unescape } from "@cocalc/util/markdown-utils";
20
import { filename_extension } from "@cocalc/util/misc";
21
import { bib_format } from "./bib-format";
22
import { clang_format } from "./clang-format";
23
import genericFormat from "./generic-format";
24
import { gofmt } from "./gofmt";
25
import { latex_format } from "./latex-format";
26
import { python_format } from "./python-format";
27
import { r_format } from "./r-format";
28
import { rust_format } from "./rust-format";
29
import { xml_format } from "./xml-format";
30
// mathjax-utils is from upstream project Jupyter
31
import { once } from "@cocalc/util/async-utils";
32
import { remove_math, replace_math } from "@cocalc/util/mathjax-utils";
33
import { get_prettier } from "./prettier-lib";
34
import type {
35
Syntax as FormatterSyntax,
36
Config,
37
Options,
38
} from "@cocalc/util/code-formatter";
39
export type { Config, Options, FormatterSyntax };
40
41
export async function run_formatter(
42
client: any,
43
path: string,
44
options: Options,
45
logger: any,
46
): Promise<object> {
47
// What we do is edit the syncstring with the given path to be "prettier" if possible...
48
const syncstring = client.syncdoc({ path });
49
if (syncstring == null || syncstring.get_state() == "closed") {
50
return {
51
status: "error",
52
error: "document not fully opened",
53
phase: "format",
54
};
55
}
56
if (syncstring.get_state() != "ready") {
57
await once(syncstring, "ready");
58
}
59
const doc = syncstring.get_doc();
60
let formatted, math, input0;
61
let input = (input0 = doc.to_str());
62
if (options.parser === "markdown") {
63
[input, math] = remove_math(math_escape(input));
64
}
65
try {
66
formatted = await run_formatter_string(path, input, options, logger);
67
} catch (err) {
68
logger.debug(`run_formatter error: ${err.message}`);
69
return { status: "error", phase: "format", error: err.message };
70
}
71
if (options.parser === "markdown") {
72
formatted = math_unescape(replace_math(formatted, math));
73
}
74
// NOTE: the code used to make the change here on the backend.
75
// See https://github.com/sagemathinc/cocalc/issues/4335 for why
76
// that leads to confusion.
77
const patch = make_patch(input0, formatted);
78
return { status: "ok", patch };
79
}
80
81
export async function run_formatter_string(
82
path: string | undefined,
83
input: string,
84
options: Options,
85
logger: any,
86
): Promise<string> {
87
let formatted;
88
logger.debug(`run_formatter options.parser: "${options.parser}"`);
89
switch (options.parser) {
90
case "latex":
91
case "latexindent":
92
formatted = await latex_format(input, options);
93
break;
94
case "python":
95
case "yapf":
96
formatted = await python_format(input, options, logger);
97
break;
98
case "zig":
99
formatted = await genericFormat({
100
command: "zig",
101
args: (tmp) => ["fmt", tmp],
102
input,
103
timeout_s: 30,
104
});
105
break;
106
case "r":
107
case "formatR":
108
formatted = await r_format(input, options, logger);
109
break;
110
case "xml-tidy":
111
formatted = await xml_format(input, options, logger);
112
break;
113
case "bib-biber":
114
formatted = await bib_format(input, options, logger);
115
break;
116
case "clang-format":
117
const ext = filename_extension(path != null ? path : "");
118
formatted = await clang_format(input, ext, options, logger);
119
break;
120
case "gofmt":
121
formatted = await gofmt(input, options, logger);
122
break;
123
case "rust":
124
case "rustfmt":
125
formatted = await rust_format(input, options, logger);
126
break;
127
default:
128
const prettier = get_prettier();
129
if (prettier != null) {
130
formatted = prettier.format(input, options);
131
} else {
132
throw Error("Could not load 'prettier'");
133
}
134
}
135
return formatted;
136
}
137
138