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/jupyter/util/cell-utils.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
Misc utility functions for manipulating and working wth cells.
8
*/
9
10
import { List, Map } from "immutable";
11
import { field_cmp, len } from "@cocalc/util/misc";
12
13
export function positions_between(
14
before_pos: number | undefined,
15
after_pos: number | undefined,
16
num: number
17
) {
18
// Return an array of num equally spaced positions starting after
19
// before_pos and ending before after_pos, so
20
// [before_pos+delta, before_pos+2*delta, ..., after_pos-delta]
21
// where delta is a function of the endpoints and num.
22
let delta: number, pos: number;
23
if (before_pos != null && after_pos != null && before_pos > after_pos) {
24
[before_pos, after_pos] = [after_pos, before_pos];
25
}
26
if (before_pos == null) {
27
if (after_pos == null) {
28
pos = 0;
29
delta = 1;
30
} else {
31
pos = after_pos - num;
32
delta = 1;
33
}
34
} else {
35
if (after_pos == null) {
36
pos = before_pos + 1;
37
delta = 1;
38
} else {
39
delta = (after_pos - before_pos) / (num + 1);
40
pos = before_pos + delta;
41
}
42
}
43
const v: number[] = [];
44
for (
45
let i = 0, end = num, asc = 0 <= end;
46
asc ? i < end : i > end;
47
asc ? i++ : i--
48
) {
49
v.push(pos);
50
pos += delta;
51
}
52
return v;
53
}
54
55
export function sorted_cell_list(cells: Map<string, any>): List<string> {
56
// Given an immutable Map from id's to cells, returns an immutable List whose
57
// entries are the id's in the correct order, as defined by the pos field (a float).
58
if (cells == null) {
59
return List([]);
60
}
61
return cells
62
.map((record, id) => ({ id, pos: record.get("pos", -1) }))
63
.filter((x) => x.id != null)
64
.sort(field_cmp("pos"))
65
.map((x) => x.id)
66
.toList();
67
}
68
69
export function ensure_positions_are_unique(cells?: Map<string, any>) {
70
// Verify that pos's of cells are distinct. If not
71
// return map from id's to new unique positions.
72
if (cells == null) {
73
return;
74
}
75
const v: any = {};
76
let all_unique = true;
77
cells.forEach((cell) => {
78
const pos = cell.get("pos");
79
if (pos == null || v[pos]) {
80
// dup! (or not defined)
81
all_unique = false;
82
return false;
83
}
84
v[pos] = true;
85
});
86
if (all_unique) {
87
return;
88
}
89
let pos = 0;
90
const new_pos: { [id: string]: number } = {};
91
sorted_cell_list(cells).forEach((id) => {
92
new_pos[id] = pos;
93
pos += 1;
94
});
95
return new_pos;
96
}
97
98
export function new_cell_pos(
99
cells: Map<string, any>,
100
cell_list: List<string>,
101
cur_id: string,
102
delta: -1 | 1
103
): number {
104
/*
105
Returns pos for a new cell whose position
106
is relative to the cell with cur_id.
107
108
cells = immutable map id --> pos
109
cell_list = immutable sorted list of id's (derived from cells)
110
cur_id = one of the ids
111
delta = -1 (above) or +1 (below)
112
113
Returned undefined whenever don't really know what to do; then caller
114
just makes up a pos, and it'll get sorted out.
115
*/
116
let cell_list_0: List<string>;
117
if (cell_list == null) {
118
cell_list_0 = sorted_cell_list(cells)!;
119
} else {
120
cell_list_0 = cell_list;
121
}
122
let adjacent_id: string | undefined;
123
cell_list_0.forEach((id, i) => {
124
if (id === cur_id) {
125
const j = i + delta;
126
if (j >= 0 && j < cell_list_0.size) {
127
adjacent_id = cell_list_0.get(j);
128
}
129
return false; // break iteration
130
}
131
});
132
const adjacent_pos = cells.getIn([adjacent_id, "pos"]) as number | undefined;
133
const current_pos = cells.getIn([cur_id, "pos"]) as number;
134
let pos: number;
135
if (adjacent_pos != null) {
136
// there is a cell after (or before) cur_id cell
137
pos = (adjacent_pos + current_pos) / 2;
138
} else {
139
// no cell after (or before)
140
pos = current_pos + delta;
141
}
142
return pos;
143
}
144
145
export function move_selected_cells(
146
v?: string[],
147
selected?: { [id: string]: true },
148
delta?: number
149
) {
150
/*
151
- v = ordered js array of all cell id's
152
- selected = js map from ids to true
153
- delta = integer
154
155
Returns new ordered js array of all cell id's or undefined if nothing to do.
156
*/
157
if (v == null || selected == null || !delta || len(selected) === 0) {
158
return; // nothing to do
159
}
160
const w: string[] = [];
161
// put selected cells in their proper new positions
162
for (let i = 0; i < v.length; i++) {
163
if (selected[v[i]]) {
164
const n = i + delta;
165
if (n < 0 || n >= v.length) {
166
// would move cells out of document, so nothing to do
167
return;
168
}
169
w[n] = v[i];
170
}
171
}
172
// now put non-selected in remaining places
173
let k = 0;
174
for (let i = 0; i < v.length; i++) {
175
if (!selected[v[i]]) {
176
while (w[k] != null) {
177
k += 1;
178
}
179
w[k] = v[i];
180
}
181
}
182
return w;
183
}
184
185
export function moveCell({
186
oldIndex,
187
newIndex,
188
getPos,
189
size,
190
}: {
191
oldIndex: number;
192
newIndex: number;
193
getPos: (index: number) => number;
194
size: number;
195
}): number {
196
if (oldIndex == newIndex) {
197
// no-op -- better to not call in this case.
198
return getPos(newIndex);
199
}
200
const pos = getPos(newIndex);
201
if (newIndex == 0) {
202
// easy special case: to beginning
203
return pos - 1;
204
} else if (newIndex >= size - 1) {
205
// the end
206
return pos + 1;
207
} else {
208
const pos1 = getPos(oldIndex < newIndex ? newIndex + 1 : newIndex - 1);
209
return (pos + pos1) / 2;
210
}
211
}
212
213