Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/editors/slate/elements/code-block/info-to-mode.ts
1698 views
1
import { file_associations } from "@cocalc/frontend/file-associations";
2
import detectLanguage from "@cocalc/frontend/misc/detect-language";
3
4
// Convert the info string for a fenced code block to a codemirror mode
5
// when preferKernel is true return the actual kernel name or language.
6
export default function infoToMode(
7
info: string | undefined | null,
8
options: { value?: string; preferKernel?: boolean } = {},
9
): string {
10
const { value, preferKernel } = options;
11
info = info?.trim().toLowerCase();
12
if (!info) {
13
if (!value) return ""; // no info
14
info = detectLanguage(value);
15
}
16
17
if (info == "mermaid") {
18
return "md";
19
}
20
21
// Format that seems to work well with github (unlike python-markdown and rmarkdown!), and we
22
// use internally, e.g.,
23
// py {kernel='sage-9.8'} or py {kernel="sage-9.8"}
24
// so we have extra info in braces. Github just looks at the "python" part.
25
if (preferKernel) {
26
// extra the string that is after kernel as in the examples above, e.g., sage-9.8
27
const kernelMatch = /kernel\s*=\s*[\'\"](.*?)[\'\"]/i.exec(info);
28
if (kernelMatch) {
29
return kernelMatch[1];
30
}
31
}
32
33
// Rmarkdown format -- looks like {r stuff,engine=python,stuff}.
34
// https://github.com/yihui/knitr-examples/blob/master/023-engine-python.Rmd
35
// ```{r test-python, engine='python'}
36
// ```{python}
37
// strip leading { and trailing }
38
// Also "python-markdown" uses these braces, though differently.
39
// https://python-markdown.github.io/extensions/fenced_code_blocks
40
// ``` { .html .foo .bar }
41
if (info[0] == "{") {
42
info = info.slice(1, -1).trim();
43
if (preferKernel) {
44
const i = info.indexOf("kernel=");
45
if (i != -1) {
46
let mode = firstWord(info.slice(i + "kernel=".length));
47
if (mode.startsWith("'") || mode.startsWith('"')) {
48
mode = mode.slice(1, -1);
49
}
50
return mode;
51
}
52
}
53
}
54
info = info.toLowerCase().trim(); // our file_associations data all assumes lower case.
55
56
// The mode specifier is then the first word before any blank
57
let mode = firstWord(info);
58
// mode can have a leading dot which we ignore, e.g., see
59
// https://python-markdown.github.io/extensions/fenced_code_blocks/
60
if (mode[0] == ".") {
61
mode = mode.slice(1);
62
}
63
64
if (mode == "r") {
65
// If the mode is R then they optionally use an 'engine=' option to specify a
66
// different mode entirely (in rmd), e.g., {r test-python, engine='python'}
67
const i = info.indexOf("engine=");
68
if (i != -1) {
69
mode = firstWord(info.slice(i + "engine=".length));
70
if (mode.startsWith("'") || mode.startsWith('"')) {
71
mode = mode.slice(1, -1);
72
}
73
}
74
}
75
76
if (
77
preferKernel &&
78
(mode.startsWith("sage") ||
79
mode.startsWith("octave") ||
80
mode == "m" ||
81
mode.startsWith("julia") ||
82
mode == "jl" ||
83
mode.startsWith("python"))
84
) {
85
if (mode == "sage") {
86
// it's nice for users to be able to type "sage" to get sage mode (since it's .sage file),
87
// but the language for the sage kernels is always "sagemath".
88
return "sagemath";
89
}
90
if (mode == "jl") {
91
// similar remark about julia as for sage above
92
return "julia";
93
}
94
if (mode == "m") {
95
return "octave";
96
}
97
return mode;
98
}
99
100
let spec = file_associations[mode];
101
102
if (preferKernel) {
103
if (spec?.opts.mode == "shell") {
104
// there is usually a bash kernel installed
105
return "bash";
106
}
107
}
108
109
if (spec == null) {
110
// the keys of file_associations is (mostly) just the filename extension.
111
// It's nice to also support matching the mime type of a codemirror mode partly, in case
112
// the extension isn't found.
113
for (const ext in file_associations) {
114
const cmmode = file_associations[ext].opts?.mode;
115
if (cmmode != null) {
116
if (
117
cmmode == mode ||
118
(cmmode.startsWith("text/x-") && cmmode == "text/x-" + mode)
119
) {
120
return cmmode;
121
}
122
}
123
}
124
}
125
126
return spec?.opts.mode ?? info; // if nothing in file associations, maybe info is the mode, e.g. "python".
127
}
128
129
// Return the first word in the string s, where words are separated by whitespace or commas
130
// @param s the string to extract first word from
131
// @returns the first word in the string
132
function firstWord(s: string): string {
133
// Use a regular expression to remove everything after the first comma, and then splits
134
// the remaining string at any whitespace to return the first word. - chatgpt
135
return s.replace(/,.*/, "").split(/\s+/)[0];
136
}
137
138