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/frontend/codemirror/extensions/latex-code-folding.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
// LaTeX code folding (isn't included in CodeMirror)
7
8
import * as CodeMirror from "codemirror";
9
import { startswith } from "@cocalc/util/misc";
10
import { trimStart } from "lodash";
11
12
function get_latex_environ(s: string): string | undefined {
13
const i = s.indexOf("{");
14
const j = s.indexOf("}");
15
if (i !== -1 && j !== -1) {
16
return s.slice(i + 1, j).trim();
17
} else {
18
return undefined;
19
}
20
}
21
22
CodeMirror.registerHelper("fold", "stex", function (cm, start) {
23
let line = trimStart(cm.getLine(start.line));
24
const find_close = function () {
25
const BEGIN = "\\begin";
26
if (startswith(line, BEGIN)) {
27
// \begin{foo}
28
// ...
29
// \end{foo}
30
// find environment close
31
const environ = get_latex_environ(line.slice(BEGIN.length));
32
if (environ == null) {
33
return [undefined, undefined];
34
}
35
// find environment close
36
const END = "\\end";
37
let level = 0;
38
let begin, end;
39
try {
40
begin = new RegExp(`\\\\begin\\s*{${environ}}`);
41
end = new RegExp(`\\\\end\\s*{${environ}}`);
42
} catch (_err) {
43
// This can happen, e.g., if somebody puts something totally wrong for the environment.
44
// See https://github.com/sagemathinc/cocalc/issues/5794
45
// Here's a reasonable fallback:
46
return [undefined, undefined];
47
}
48
for (let i = start.line; i <= cm.lastLine(); i++) {
49
const cur = cm.getLine(i);
50
const m = cur.search(begin);
51
const j = cur.search(end);
52
if (m !== -1 && (j === -1 || m < j)) {
53
level += 1;
54
}
55
if (j !== -1) {
56
level -= 1;
57
if (level === 0) {
58
return [i, j + END.length - 1];
59
}
60
}
61
}
62
} else if (startswith(line, "\\[")) {
63
for (let i = start.line + 1; i <= cm.lastLine(); i++) {
64
if (startswith(trimStart(cm.getLine(i)), "\\]")) {
65
return [i, 0];
66
}
67
}
68
} else if (startswith(line, "\\(")) {
69
for (let i = start.line + 1; i <= cm.lastLine(); i++) {
70
if (startswith(trimStart(cm.getLine(i)), "\\)")) {
71
return [i, 0];
72
}
73
}
74
} else if (startswith(line, "\\documentclass")) {
75
// pre-amble
76
for (let i = start.line + 1; i <= cm.lastLine(); i++) {
77
if (startswith(trimStart(cm.getLine(i)), "\\begin{document}")) {
78
return [i - 1, 0];
79
}
80
}
81
} else if (startswith(line, "\\chapter")) {
82
// book chapter
83
for (let i = start.line + 1; i <= cm.lastLine(); i++) {
84
if (
85
startswith(trimStart(cm.getLine(i)), ["\\chapter", "\\end{document}"])
86
) {
87
return [i - 1, 0];
88
}
89
}
90
return [cm.lastLine(), 0];
91
} else if (startswith(line, "\\section")) {
92
// article section
93
for (let i = start.line + 1; i <= cm.lastLine(); i++) {
94
if (
95
startswith(trimStart(cm.getLine(i)), [
96
"\\chapter",
97
"\\section",
98
"\\end{document}",
99
])
100
) {
101
return [i - 1, 0];
102
}
103
}
104
return [cm.lastLine(), 0];
105
} else if (startswith(line, "\\subsection")) {
106
// article subsection
107
for (let i = start.line + 1; i <= cm.lastLine(); i++) {
108
if (
109
startswith(trimStart(cm.getLine(i)), [
110
"\\chapter",
111
"\\section",
112
"\\subsection",
113
"\\end{document}",
114
])
115
) {
116
return [i - 1, 0];
117
}
118
}
119
return [cm.lastLine(), 0];
120
} else if (startswith(line, "\\subsubsection")) {
121
// article subsubsection
122
for (let i = start.line + 1; i <= cm.lastLine(); i++) {
123
if (
124
startswith(trimStart(cm.getLine(i)), [
125
"\\chapter",
126
"\\section",
127
"\\subsection",
128
"\\subsubsection",
129
"\\end{document}",
130
])
131
) {
132
return [i - 1, 0];
133
}
134
}
135
return [cm.lastLine(), 0];
136
} else if (startswith(line, "\\subsubsubsection")) {
137
// article subsubsubsection
138
for (let i = start.line + 1; i <= cm.lastLine(); i++) {
139
if (
140
startswith(trimStart(cm.getLine(i)), [
141
"\\chapter",
142
"\\section",
143
"\\subsection",
144
"\\subsubsection",
145
"\\subsubsubsection",
146
"\\end{document}",
147
])
148
) {
149
return [i - 1, 0];
150
}
151
}
152
return [cm.lastLine(), 0];
153
} else if (startswith(line, "%\\begin{}")) {
154
// support what texmaker supports for custom folding -- http://tex.stackexchange.com/questions/44022/code-folding-in-latex
155
for (let i = start.line + 1; i <= cm.lastLine(); i++) {
156
if (startswith(trimStart(cm.getLine(i)), "%\\end{}")) {
157
return [i, 0];
158
}
159
}
160
}
161
162
return [undefined, undefined]; // no folding here...
163
};
164
165
const [i, j] = find_close();
166
if (i != null) {
167
line = cm.getLine(start.line);
168
let k = line.indexOf("}");
169
if (k === -1) {
170
k = line.length;
171
}
172
const range = {
173
from: CodeMirror.Pos(start.line, k + 1),
174
to: CodeMirror.Pos(i, j),
175
};
176
return range;
177
} else {
178
// nothing to fold
179
return undefined;
180
}
181
});
182
183