@isopodlabs/vscode_utils 0.0.2

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/README.md ADDED
@@ -0,0 +1,8 @@
1
+ # @isopodlabs/vscode_utils
2
+
3
+ This package provides a set of utilities for vscode.
4
+
5
+
6
+ ## License
7
+
8
+ This project is licensed under the MIT License.
@@ -0,0 +1,204 @@
1
+ @font-face {
2
+ font-family: "codicon";
3
+ src: url("./codicon.ttf?9642aa1d48ab4e55aa1bf3f0b8678aa1") format("truetype");
4
+ }
5
+
6
+ html {
7
+ scrollbar-width: none;
8
+ /*user-select: none;*/
9
+ }
10
+
11
+ body {
12
+ font-family: var(--vscode-font-family);
13
+ font-size: var(--vscode-font-size);
14
+ font-weight: var(--vscode-font-weight);
15
+ color: var(--vscode-foreground);
16
+ --icon-color: var(--vscode-foreground);
17
+ }
18
+
19
+ .loading-container {
20
+ position: fixed;
21
+ top: 0;
22
+ left: 0;
23
+ width: 100%;
24
+ height: 100%;
25
+ display: flex;
26
+ flex-direction: column;
27
+ align-items: center;
28
+ justify-content: center;
29
+ }
30
+
31
+ .spinner {
32
+ width: 40px;
33
+ height: 40px;
34
+ margin-bottom: 16px;
35
+ border: 4px solid var(--vscode-button-background);
36
+ border-top: 4px solid transparent;
37
+ border-radius: 50%;
38
+ animation: spin 1s linear infinite;
39
+ }
40
+
41
+ .loading-text {
42
+ font-family: var(--vscode-font-family);
43
+ font-size: 14px;
44
+ }
45
+
46
+ @keyframes spin {
47
+ 0% { transform: rotate(0deg); }
48
+ 100% { transform: rotate(360deg); }
49
+ }
50
+
51
+ /* For font icons (character in icon attribute)*/
52
+ [icon]::before {
53
+ content: attr(icon);
54
+ font: normal normal normal 16px/1 codicon;
55
+ display: inline-block;
56
+ color: var(--icon-color);
57
+ text-decoration: none;
58
+ text-rendering: auto;
59
+ text-align: center;
60
+ vertical-align: text-top;
61
+ -webkit-font-smoothing: antialiased;
62
+ -moz-osx-font-smoothing: grayscale;
63
+ user-select: none;
64
+ -webkit-user-select: none;
65
+ -ms-user-select: none;
66
+ }
67
+
68
+ /* For img icons (img in --icon)*/
69
+ .icon::before {
70
+ content: '';
71
+ display: inline-block;
72
+ text-decoration: none;
73
+ width: 16px;
74
+ height: 16px;
75
+ background-image: var(--icon);
76
+ margin-right: 5px;
77
+ }
78
+
79
+ .codicon {
80
+ font: normal normal normal 16px/1 codicon;
81
+ display: inline-block;
82
+ text-decoration: none;
83
+ text-rendering: auto;
84
+ text-align: center;
85
+ vertical-align: text-top;
86
+ -webkit-font-smoothing: antialiased;
87
+ -moz-osx-font-smoothing: grayscale;
88
+ user-select: none;
89
+ -webkit-user-select: none;
90
+ -ms-user-select: none;
91
+ }
92
+
93
+ button[icon] {
94
+ border: none;
95
+ background-color: transparent;
96
+ padding: 0;
97
+ cursor: pointer;
98
+ color: var(--vscode-icon-foreground);
99
+
100
+ &:hover {
101
+ background-color: var(--vscode-list-hoverBackground);
102
+ }
103
+ }
104
+
105
+ button {
106
+ color: var(--vscode-button-foreground);
107
+ background-color: var(--vscode-button-background);
108
+ border: 1px solid var(--vscode-button-border, transparent);
109
+ border-radius: 2px;
110
+ padding: 4px 8px;
111
+ cursor: pointer;
112
+ &:hover {
113
+ background-color: var(--vscode-button-hoverBackground);
114
+ }
115
+ }
116
+
117
+ button.codicon {
118
+ border: none;
119
+ background-color: transparent;
120
+ padding: 0;
121
+ cursor: pointer;
122
+ color: var(--vscode-icon-foreground);
123
+ &:hover {
124
+ opacity: 0.8;
125
+ }
126
+ }
127
+
128
+ .select {
129
+ cursor: pointer;
130
+ width: fit-content;
131
+ &:hover {
132
+ text-decoration: underline;
133
+ }
134
+ }
135
+
136
+ .selected {
137
+ background-color: var(--vscode-editor-selectionBackground);
138
+ }
139
+
140
+ /* Splitter */
141
+
142
+ .splitter {
143
+ position: relative;
144
+ width: 5px;
145
+ flex: none;
146
+ cursor:ew-resize;
147
+
148
+ &::before {
149
+ content: '';
150
+ position: absolute;
151
+ top: 0;
152
+ bottom: 0;
153
+ left: 2px;
154
+ width: 1px;
155
+ height: 100%;
156
+ background-color: var(--vscode-editorGroup-border);
157
+ transition: background-color 0.3s;
158
+ }
159
+
160
+ &:hover::before {
161
+ left: 0;
162
+ width: 100%;
163
+ background-color: var(--vscode-sash-hoverBorder);
164
+ }
165
+ }
166
+
167
+ /* Scrollbar */
168
+
169
+ .vscrollbar, .hscrollbar {
170
+ position: fixed;
171
+ z-index: 20;
172
+ background: var(--vscode-scrollbarSlider-background);
173
+
174
+ opacity: 0;
175
+ transition: opacity .8s linear;
176
+
177
+ html:hover &, .resizing & {
178
+ opacity: 1;
179
+ transition: opacity .1s linear;
180
+
181
+ &:hover {
182
+ background: var(--vscode-scrollbarSlider-hoverBackground);
183
+ }
184
+ &.active {
185
+ background: var(--vscode-scrollbarSlider-activeBackground)
186
+ }
187
+
188
+ &.invisible {
189
+ opacity: 0;
190
+ transition: opacity .8s linear;
191
+ pointer-events: none;
192
+ }
193
+ }
194
+ }
195
+
196
+ .vscrollbar {
197
+ right: 0;
198
+ width: 14px;
199
+ }
200
+
201
+ .hscrollbar {
202
+ bottom: 0;
203
+ height: 14px;
204
+ }
@@ -0,0 +1,336 @@
1
+ const vscode = acquireVsCodeApi();
2
+
3
+ //fix up icons in attributes
4
+ document.querySelectorAll('[icon]').forEach(element => {
5
+ const value = element.getAttribute('icon');
6
+ if (value.includes('/')) {
7
+ element.removeAttribute('icon');
8
+ element.classList.add('icon');
9
+ element.style.setProperty('--icon', `url(${value})`);
10
+ }
11
+ const col = element.getAttribute('color');
12
+ if (col) {
13
+ element.removeAttribute('color');
14
+ element.style.setProperty('--icon-color', col);
15
+ }
16
+ });
17
+
18
+
19
+ document.querySelectorAll('.select').forEach(item => {
20
+ item.addEventListener('click', event => {
21
+ if (event.target === item) {
22
+ vscode.postMessage({
23
+ command: 'select',
24
+ selector: generateSelector(item),
25
+ text: item.textContent,
26
+ ...item.dataset
27
+ });
28
+ event.stopPropagation();
29
+ }
30
+ });
31
+ });
32
+
33
+ function createElement(tag, options) {
34
+ const e = document.createElement(tag);
35
+ if (options) {
36
+ for (const [k,v] of Object.entries(options))
37
+ e[k] = v;
38
+ }
39
+ return e;
40
+ }
41
+
42
+ function getFirstText(element) {
43
+ for (const node of element.childNodes) {
44
+ if (node.nodeType === Node.TEXT_NODE) {
45
+ const text = node.textContent.trim();
46
+ if (text)
47
+ return text;
48
+ }
49
+ }
50
+ }
51
+
52
+ class Pool {
53
+ pool = [];
54
+ constructor(make) { this.make = make; }
55
+
56
+ get() {
57
+ if (this.pool.length === 0)
58
+ this.pool.push(this.make());
59
+
60
+ return this.pool.length === 1
61
+ ? this.pool[0].cloneNode(true)
62
+ : this.pool.pop();
63
+ }
64
+
65
+ discard(item) {
66
+ this.pool.push(item);
67
+ }
68
+ discardElement(item) {
69
+ this.discard(item);
70
+ item.remove();
71
+ }
72
+
73
+ }
74
+
75
+ //-------------------------------------
76
+ // splitter
77
+ //-------------------------------------
78
+
79
+ class Splitter {
80
+ splitter;
81
+
82
+ constructor(splitter, notify) {
83
+ this.splitter = splitter;
84
+
85
+ splitter.addEventListener('pointerdown', e => {
86
+ e.preventDefault();
87
+ splitter.setPointerCapture(e.pointerId);
88
+
89
+ const left = splitter.previousSibling;
90
+ const style = getComputedStyle(left);
91
+ let split = left.clientWidth - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight);
92
+ const offset = split - e.clientX;
93
+
94
+ const min = parseFloat(style.minWidth);
95
+ const max = Math.min(parseFloat(style.maxWidth), splitter.parentNode.clientWidth - 300);
96
+
97
+ left.style.width = `${split}px`;
98
+ left.style.flex = 'none';
99
+
100
+ function resizePanels(e) {
101
+ split = Math.min(Math.max(e.clientX + offset, min), max);
102
+ left.style.width = `${split}px`;
103
+ }
104
+
105
+ function stopResizing(e) {
106
+ notify(split);
107
+ //state.split = split;
108
+ //vscode.setState(state);
109
+
110
+ splitter.releasePointerCapture(e.pointerId);
111
+ document.removeEventListener('pointermove', resizePanels);
112
+ document.removeEventListener('pointerup', stopResizing);
113
+ }
114
+
115
+ document.addEventListener('pointermove', resizePanels);
116
+ document.addEventListener('pointerup', stopResizing);
117
+ });
118
+ }
119
+
120
+ set(x) {
121
+ const left = this.splitter.previousSibling;
122
+ left.style.width = `${x}px`;
123
+ left.style.flex = 'none';
124
+ }
125
+
126
+ }
127
+
128
+ //-------------------------------------
129
+ // Scrollbar
130
+ //-------------------------------------
131
+
132
+ /*
133
+ interface Container {
134
+ clientOffset: number; //pixel offset for top of track
135
+ clientPixels: number; //pixel size of track
136
+ clientSize: number; //unit size of visible region
137
+ scrollOffset: number; //scroll position in units
138
+ scrollSize: number; //size of overall region in units
139
+ setScroll(x: number);//set scroll position in units
140
+ }
141
+ */
142
+
143
+ function VScrollContainer(container, offset = 0) {
144
+ return {
145
+ clientOffset: Math.max(container.clientTop, 0) + offset,
146
+ get clientPixels() { return container.clientHeight - offset; },
147
+ get clientSize() { return container.clientHeight - offset; },
148
+ get scrollOffset() { return container.scrollTop; },
149
+ get scrollSize() { return container.scrollHeight; },
150
+ setScroll(x) { container.scrollTop = x; },
151
+ };
152
+ }
153
+
154
+ function HScrollContainer(container, offset = 0) {
155
+ return {
156
+ clientOffset: Math.max(container.clientLeft, 0) + offset,
157
+ get clientPixels() { return container.clientWidth - offset; },
158
+ get clientSize() { return container.clientWidth - offset; },
159
+ get scrollOffset() { return container.scrollLeft; },
160
+ get scrollSize() { return container.scrollWidth; },
161
+ setScroll(x) { container.scrollLeft = x; },
162
+ };
163
+ }
164
+
165
+ class ScrollBar {
166
+ thumbSize;
167
+
168
+ constructor(parent, container, horizontal) {
169
+ if (container instanceof HTMLElement) {
170
+ container = horizontal ? HScrollContainer(container) : VScrollContainer(container);
171
+ }
172
+ const thumb = createElement('div', {className: horizontal ? 'hscrollbar' : 'vscrollbar'});
173
+ parent.appendChild(thumb);
174
+
175
+ this.thumb = thumb;
176
+ this.container = container;
177
+ this.horizontal = horizontal;
178
+ this.update();
179
+
180
+ thumb.addEventListener("lostpointercapture", e => {
181
+ if (thumb.onMouseUp)
182
+ thumb.onMouseUp(e);
183
+ });
184
+
185
+ thumb.addEventListener('pointerdown', event => {
186
+ console.log('down');
187
+ const pointerOffset = horizontal ? thumb.offsetLeft - event.clientX : thumb.offsetTop - event.clientY;
188
+ thumb.classList.add('active');
189
+
190
+ const onPointerMove = event => {
191
+ this.setThumbPixel(pointerOffset + (horizontal ? event.clientX : event.clientY));
192
+ };
193
+
194
+ const onPointerUp = () => {
195
+ console.log('up');
196
+ thumb.classList.remove('active');
197
+ window.removeEventListener('pointermove', onPointerMove);
198
+ window.removeEventListener('pointerup', onPointerUp);
199
+ };
200
+
201
+ if (thumb.setPointerCapture)
202
+ thumb.setPointerCapture(event.pointerId);
203
+
204
+ window.addEventListener('pointermove', onPointerMove);
205
+ window.addEventListener('pointerup', onPointerUp);
206
+ });
207
+ }
208
+
209
+ update() {
210
+ this.setThumb(this.container.scrollOffset);
211
+ }
212
+
213
+ setScroll(scroll) {
214
+ this.container.setScroll(scroll);
215
+ this.setThumb(scroll);
216
+ }
217
+
218
+ setThumbSize(size) {
219
+ if (size != this.thumbSize) {
220
+ this.thumbSize = size;
221
+ if (this.horizontal)
222
+ this.thumb.style.width = `${size}px`;
223
+ else
224
+ this.thumb.style.height = `${size}px`;
225
+ }
226
+ }
227
+
228
+ setThumb(scroll) {
229
+ const clientPixels = this.container.clientPixels;
230
+ const clientSize = this.container.clientSize;
231
+ const scrollSize = this.container.scrollSize;
232
+ const clientOffset = this.container.clientOffset;
233
+ let thumbPos, thumbSize;
234
+
235
+ if (clientSize >= scrollSize) {
236
+ this.thumb.classList.add('invisible');
237
+ thumbSize = scrollSize;
238
+ thumbPos = clientOffset;
239
+ } else {
240
+ this.thumb.classList.remove('invisible');
241
+ thumbSize = Math.max(clientPixels * clientSize / scrollSize, 20);
242
+ thumbPos = clientOffset + scroll * (clientPixels - thumbSize) / (scrollSize - clientSize);
243
+ }
244
+
245
+ this.setThumbSize(thumbSize);
246
+
247
+ if (this.horizontal)
248
+ this.thumb.style.left = `${thumbPos}px`;
249
+ else
250
+ this.thumb.style.top = `${thumbPos}px`;
251
+ }
252
+
253
+ setThumbPixel(pos) {
254
+ const clientPixels = this.container.clientPixels;
255
+ const clientSize = this.container.clientSize;
256
+ const scrollSize = this.container.scrollSize;
257
+ const clientOffset = this.container.clientOffset;
258
+ const thumbPos = Math.min(Math.max(pos, clientOffset), clientOffset + clientPixels - this.thumbSize);
259
+ const scroll = (thumbPos - clientOffset) * (scrollSize - clientSize) / (clientPixels - this.thumbSize);
260
+
261
+ this.container.setScroll(scroll);
262
+ if (this.horizontal) {
263
+ this.thumb.style.left = `${thumbPos}px`;
264
+ } else {
265
+ this.thumb.style.top = `${thumbPos}px`;
266
+ }
267
+ }
268
+ }
269
+
270
+ let resizeTimeout;
271
+
272
+ window.addEventListener('resize', () => {
273
+ document.documentElement.classList.add('resizing');
274
+ clearTimeout(resizeTimeout);
275
+ resizeTimeout = setTimeout(() => document.documentElement.classList.remove('resizing'), 500);
276
+ });
277
+
278
+
279
+ //-------------------------------------
280
+ // template
281
+ //-------------------------------------
282
+
283
+ function replace(text, re, process) {
284
+ let i = 0;
285
+ let result = '';
286
+ for (let m; (m = re.exec(text)); i = re.lastIndex)
287
+ result += text.substring(i, m.index) + process(m);
288
+ return result + text.substring(i);
289
+ }
290
+
291
+ function replace_in_element(e, re, process) {
292
+ if (e.id)
293
+ e.id = replace(e.id, re, process);
294
+ if (e.attributes.name)
295
+ e.attributes.name.value = replace(e.attributes.name.value, re, process);
296
+ const childNodes = e.childNodes;
297
+ for (let i = 0; i < childNodes.length; i++) {
298
+ const node = childNodes[i];
299
+ if (node.nodeType === window.Node.TEXT_NODE)
300
+ node.textContent = replace(node.textContent, re, process);
301
+ else if (node.nodeType === window.Node.ELEMENT_NODE)
302
+ replace_in_element(node, re, process);
303
+ }
304
+ }
305
+
306
+ function template(template, parent, values) {
307
+ const newnodes = values.map(i => {
308
+ const child = template.cloneNode(true);
309
+ child.hidden = false;
310
+ replace_in_element(child, /\$\((.*)\)/g, m => i[m[1]]);
311
+ return child;
312
+ });
313
+
314
+ // const parent = after.parentNode;
315
+ // const before = after.nextSibling;
316
+ const before = null;
317
+ for (const i of newnodes)
318
+ parent.insertBefore(i, before);
319
+ }
320
+
321
+ function generateSelector(e) {
322
+ const path = [];
323
+ while (e && e.nodeType === Node.ELEMENT_NODE) {
324
+ let index = 1;
325
+ for (let s = e; (s = s.previousElementSibling);) {
326
+ if (s.tagName === e.tagName)
327
+ index++;
328
+ }
329
+ //const selector = e.tagName.toLowerCase() + (index > 1 ? `:nth-of-type(${index})` : '');
330
+ const selector = `${e.tagName.toLowerCase()}:nth-of-type(${index})`;
331
+ path.unshift(selector);
332
+ e = e.parentNode;
333
+ }
334
+ return path.join(' > ');
335
+ }
336
+
@@ -0,0 +1,24 @@
1
+ .caret {
2
+ &::before {
3
+ font-family: 'codicon';
4
+ content: '\eab6';
5
+ margin-right: 6px;
6
+ pointer-events: auto;
7
+ cursor: pointer;
8
+ }
9
+
10
+ &.caret-down::before {
11
+ content: '\eab4';
12
+ }
13
+
14
+ &:has(> span:first-child > .codicon) > .children {
15
+ padding-left: 32px;
16
+ }
17
+
18
+ &:not(.caret-down) div {
19
+ display: none;
20
+ }
21
+ &:not(.caret-down) ul {
22
+ display: none;
23
+ }
24
+ }
package/assets/tree.js ADDED
@@ -0,0 +1,81 @@
1
+ class Tree {
2
+ root;
3
+
4
+ constructor(root, notify) {
5
+ this.root = root;
6
+
7
+ root.querySelectorAll('.caret').forEach(caret => {
8
+ caret.addEventListener('click', event => {
9
+ if (event.target === caret) {
10
+ caret.classList.toggle('caret-down');
11
+ notify(caret, caret.classList.contains('caret-down'));
12
+ event.stopPropagation();
13
+ }
14
+ });
15
+ });
16
+ }
17
+
18
+ open(element) {
19
+ element?.classList.add('caret-down');
20
+ }
21
+ close(element) {
22
+ element?.classList.remove('caret-down');
23
+ }
24
+
25
+ close_all() {
26
+ this.root.querySelectorAll('.caret-down').forEach(e => e.classList.remove('caret-down'));
27
+ }
28
+
29
+ all_open() {
30
+ return Array.from(this.root.querySelectorAll('.caret-down'), element => generateSelector(element));
31
+ }
32
+
33
+ reveal(element) {
34
+ if (element) {
35
+ for (let parent = element.parentNode; parent; parent = parent.parentNode) {
36
+ if (parent.classList?.contains('caret'))
37
+ parent.classList.add('caret-down');
38
+ }
39
+ element.scrollIntoView({behavior: 'smooth', block: 'center'});
40
+ }
41
+ }
42
+
43
+ lastStuck() {
44
+ return lastStuck(this.root);
45
+ }
46
+ }
47
+
48
+ function lastStuck(tree) {
49
+ const x = tree.clientWidth - 20;
50
+ let y = 5;
51
+
52
+ let last_stuck;
53
+ for(;;) {
54
+ const e = document.elementFromPoint(x, y);
55
+ if (!e || getComputedStyle(e).getPropertyValue('position') != 'sticky')
56
+ break;
57
+
58
+ const bottom = e.getBoundingClientRect().bottom;
59
+ if (e.nextElementSibling.getBoundingClientRect().top >= bottom)
60
+ break;
61
+
62
+ last_stuck = e;
63
+ y = bottom + 5;
64
+ }
65
+
66
+ return last_stuck;
67
+ }
68
+
69
+ let prev_stuck;
70
+ function updateStuck() {
71
+ const last_stuck = lastStuck(document.querySelector('.tree'));
72
+ if (last_stuck !== prev_stuck) {
73
+ if (prev_stuck)
74
+ prev_stuck.classList.remove('stuck');
75
+
76
+ if (last_stuck)
77
+ last_stuck.classList.add('stuck');
78
+
79
+ prev_stuck = last_stuck;
80
+ }
81
+ }