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/addon/fill-paragraph.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
/* Analogue of fill-paragraph from emacs. It's basically what is described
7
in here, but not *exactly*.
8
9
https://www.gnu.org/software/emacs/manual/html_node/emacs/Fill-Commands.html
10
*/
11
12
import { defineExtension, Editor } from "codemirror";
13
import { Pos } from "./types";
14
import { split } from "@cocalc/util/misc";
15
16
function is_white_space(s: string): boolean {
17
return s?.trim() == "";
18
}
19
20
defineExtension("fill_paragraph", function (opts: { cols?: number }): void {
21
// @ts-ignore
22
const cm: Editor = this;
23
const pos: Pos = cm.getCursor();
24
25
const cols = opts?.cols ?? 80;
26
27
let n = pos.line;
28
// enforce emacs rule when cursor is between paragraphs: go to next para
29
while (is_white_space(cm.getLine(n)) && n < cm.lastLine()) {
30
n += 1;
31
}
32
33
// find the start line and end line of the current paragraph
34
let start = n;
35
while (start > cm.firstLine() && !is_white_space(cm.getLine(start))) {
36
start -= 1;
37
}
38
while (is_white_space(cm.getLine(start)) && start < cm.lastLine()) {
39
start += 1;
40
}
41
let end = n;
42
while (end < cm.lastLine() && !is_white_space(cm.getLine(end))) {
43
end += 1;
44
}
45
while (is_white_space(cm.getLine(end)) && end > cm.firstLine()) {
46
end -= 1;
47
}
48
49
// Now start and end are valid lines in the document and they are where the
50
// paragraph actually starts and ends. They are not whitespace lines, unless
51
// document is entirely whitespace.
52
const from = { line: start, ch: 0 };
53
const to = { line: end, ch: cm.getLine(end).length + 1 };
54
let para = cm.getRange(from, to);
55
56
// Find a single character not in the range and put it at the cursor, so
57
// we can track where the cursor goes when we do the replacement.
58
// We only have to do this if the cursor is in the range.
59
let cursor: string = "";
60
if (pos.line >= start && pos.line <= end) {
61
let code = 1000;
62
cursor = String.fromCharCode(code);
63
while (para.indexOf(cursor) != -1) {
64
code += 1;
65
cursor = String.fromCharCode(code);
66
}
67
cm.replaceRange(cursor, pos);
68
para = cm.getRange(from, to); // now it has the sentinel character in it.
69
}
70
71
const words = split(para);
72
let formatted = "";
73
let k = 0;
74
for (const word of words) {
75
let next = (k > 0 ? " " : "") + word;
76
if (k > 0 && k + next.length > cols) {
77
formatted += "\n";
78
k = 0;
79
next = word;
80
}
81
formatted += next;
82
k += next.length;
83
}
84
cm.replaceRange(formatted, from, to);
85
86
if (cursor != "") {
87
for (let line = from.line; line <= to.line; line++) {
88
const x = cm.getLine(line);
89
if (x == null) continue;
90
const ch = x.indexOf(cursor);
91
if (ch != -1) {
92
const before = x.slice(0, ch);
93
let after = x.slice(ch + cursor.length);
94
if (
95
is_white_space(after[0]) &&
96
is_white_space(before[before.length - 1])
97
) {
98
after = after.slice(1);
99
}
100
cm.replaceRange(
101
before + after,
102
{ line, ch: 0 },
103
{ line, ch: cm.getLine(line).length }
104
);
105
cm.setCursor({ ch, line });
106
break;
107
}
108
}
109
}
110
});
111
112