playroom 0.28.2 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/Playroom/CodeEditor/CodeEditor.tsx +153 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# playroom
|
|
2
2
|
|
|
3
|
+
## 0.29.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 9fc8c0d: Adds VSCode-style keybindings for move line up/down and copy line up/down.
|
|
8
|
+
Works for selections as well as single lines.
|
|
9
|
+
|
|
10
|
+
See the VSCode keyboard shortcut reference for details ([Mac]/[Windows]).
|
|
11
|
+
|
|
12
|
+
[mac]: https://code.visualstudio.com/shortcuts/keyboard-shortcuts-macos.pdf
|
|
13
|
+
[windows]: https://code.visualstudio.com/shortcuts/keyboard-shortcuts-windows.pdf
|
|
14
|
+
|
|
3
15
|
## 0.28.2
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useRef, useContext, useEffect, useCallback } from 'react';
|
|
2
2
|
import { useDebouncedCallback } from 'use-debounce';
|
|
3
|
-
import CodeMirror, { Editor } from 'codemirror';
|
|
3
|
+
import CodeMirror, { Editor, Pos } from 'codemirror';
|
|
4
4
|
import 'codemirror/lib/codemirror.css';
|
|
5
5
|
import 'codemirror/theme/neo.css';
|
|
6
6
|
|
|
@@ -25,6 +25,154 @@ import 'codemirror/addon/fold/foldcode';
|
|
|
25
25
|
import 'codemirror/addon/fold/foldgutter';
|
|
26
26
|
import 'codemirror/addon/fold/brace-fold';
|
|
27
27
|
|
|
28
|
+
const directionToMethod = {
|
|
29
|
+
up: 'to',
|
|
30
|
+
down: 'from',
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
type DuplicationDirection = keyof typeof directionToMethod;
|
|
34
|
+
|
|
35
|
+
const getNewPosition = (
|
|
36
|
+
range: CodeMirror.Range,
|
|
37
|
+
direction: DuplicationDirection
|
|
38
|
+
) => {
|
|
39
|
+
const currentLine = range[directionToMethod[direction]]().line;
|
|
40
|
+
|
|
41
|
+
const newLine = direction === 'up' ? currentLine + 1 : currentLine;
|
|
42
|
+
return new Pos(newLine, 0);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const duplicateLine = (direction: DuplicationDirection) => (cm: Editor) =>
|
|
46
|
+
cm.operation(function () {
|
|
47
|
+
const ranges = cm.listSelections();
|
|
48
|
+
|
|
49
|
+
if (ranges.length > 1) {
|
|
50
|
+
// eslint-disable-next-line no-console
|
|
51
|
+
console.warn(
|
|
52
|
+
"The duplicate line command doesn't support multiple cursors yet. Please ask for this feature."
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const range = ranges[0];
|
|
57
|
+
|
|
58
|
+
const existingContent = cm.getRange(
|
|
59
|
+
new Pos(range.from().line, 0),
|
|
60
|
+
new Pos(range.to().line)
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const newContentParts = [existingContent, '\n'];
|
|
64
|
+
|
|
65
|
+
// Copy up on the last line has some unusual behaviour
|
|
66
|
+
if (range.to().line === cm.lastLine() && direction === 'up') {
|
|
67
|
+
newContentParts.reverse();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const newContent = newContentParts.join('');
|
|
71
|
+
|
|
72
|
+
cm.replaceRange(newContent, getNewPosition(range, direction));
|
|
73
|
+
|
|
74
|
+
// Copy up doesn't always handle its cursors correctly
|
|
75
|
+
if (direction === 'up') {
|
|
76
|
+
cm.setSelection(range.anchor, range.head);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
cm.scrollIntoView(null);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const swapLineUp = (cm: Editor) => {
|
|
83
|
+
if (cm.isReadOnly()) {
|
|
84
|
+
return CodeMirror.Pass;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const ranges = cm.listSelections();
|
|
88
|
+
|
|
89
|
+
if (ranges.length > 1) {
|
|
90
|
+
// eslint-disable-next-line no-console
|
|
91
|
+
console.warn(
|
|
92
|
+
"The swap line command doesn't support multiple cursors yet. Please ask for this feature."
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const range = ranges[0];
|
|
97
|
+
|
|
98
|
+
// If we're already at the top, do nothing
|
|
99
|
+
if (range.from().line > 0) {
|
|
100
|
+
const switchLineNumber = range.from().line - 1;
|
|
101
|
+
const switchLineContent = cm.getLine(switchLineNumber);
|
|
102
|
+
|
|
103
|
+
// Expand to the end of the selected lines
|
|
104
|
+
const rangeStart = new Pos(range.from().line, 0);
|
|
105
|
+
const rangeEnd = new Pos(range.to().line, undefined);
|
|
106
|
+
|
|
107
|
+
const rangeContent = cm.getRange(rangeStart, rangeEnd);
|
|
108
|
+
|
|
109
|
+
cm.operation(() => {
|
|
110
|
+
// Switch the order of the range and the preceding line
|
|
111
|
+
const newContent = [rangeContent, switchLineContent].join('\n');
|
|
112
|
+
|
|
113
|
+
cm.replaceRange(
|
|
114
|
+
newContent,
|
|
115
|
+
new Pos(switchLineNumber, 0),
|
|
116
|
+
rangeEnd,
|
|
117
|
+
'+swapLine'
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// Shift the selection up by one line to match the moved content
|
|
121
|
+
cm.setSelection(
|
|
122
|
+
new Pos(range.anchor.line - 1, range.anchor.ch),
|
|
123
|
+
new Pos(range.head.line - 1, range.head.ch)
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const swapLineDown = (cm: Editor) => {
|
|
130
|
+
if (cm.isReadOnly()) {
|
|
131
|
+
return CodeMirror.Pass;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const ranges = cm.listSelections();
|
|
135
|
+
|
|
136
|
+
if (ranges.length > 1) {
|
|
137
|
+
// eslint-disable-next-line no-console
|
|
138
|
+
console.warn(
|
|
139
|
+
"The swap line command doesn't support multiple cursors yet. Please ask for this feature."
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const range = ranges[0];
|
|
144
|
+
|
|
145
|
+
// If we're already at the bottom, do nothing
|
|
146
|
+
if (range.to().line < cm.lastLine()) {
|
|
147
|
+
const switchLineNumber = range.to().line + 1;
|
|
148
|
+
const switchLineContent = cm.getLine(switchLineNumber);
|
|
149
|
+
|
|
150
|
+
// Expand to the end of the selected lines
|
|
151
|
+
const rangeStart = new Pos(range.from().line, 0);
|
|
152
|
+
const rangeEnd = new Pos(range.to().line, undefined);
|
|
153
|
+
|
|
154
|
+
const rangeContent = cm.getRange(rangeStart, rangeEnd);
|
|
155
|
+
|
|
156
|
+
cm.operation(() => {
|
|
157
|
+
// Switch the order of the range and the preceding line
|
|
158
|
+
const newContent = [switchLineContent, rangeContent].join('\n');
|
|
159
|
+
|
|
160
|
+
cm.replaceRange(
|
|
161
|
+
newContent,
|
|
162
|
+
rangeStart,
|
|
163
|
+
new Pos(switchLineNumber),
|
|
164
|
+
'+swapLine'
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// Shift the selection down by one line to match the moved content
|
|
168
|
+
cm.setSelection(
|
|
169
|
+
new Pos(range.anchor.line + 1, range.anchor.ch),
|
|
170
|
+
new Pos(range.head.line + 1, range.head.ch)
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
28
176
|
const completeAfter = (cm: Editor, predicate?: () => boolean) => {
|
|
29
177
|
if (!predicate || predicate()) {
|
|
30
178
|
setTimeout(() => {
|
|
@@ -284,6 +432,10 @@ export const CodeEditor = ({ code, onChange, previewCode, hints }: Props) => {
|
|
|
284
432
|
"'/'": completeIfAfterLt,
|
|
285
433
|
"' '": completeIfInTag,
|
|
286
434
|
"'='": completeIfInTag,
|
|
435
|
+
'Alt-Up': swapLineUp,
|
|
436
|
+
'Alt-Down': swapLineDown,
|
|
437
|
+
'Shift-Alt-Up': duplicateLine('up'),
|
|
438
|
+
'Shift-Alt-Down': duplicateLine('down'),
|
|
287
439
|
},
|
|
288
440
|
}}
|
|
289
441
|
/>
|