jupyterlab_vscode_icons_extension 1.1.48 → 1.1.50
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 +1 -1
- package/lib/__tests__/jupyterlab_vscode_icons_extension.spec.d.ts +3 -0
- package/lib/__tests__/jupyterlab_vscode_icons_extension.spec.js +9 -0
- package/lib/__tests__/parsers.spec.d.ts +4 -0
- package/lib/__tests__/parsers.spec.js +314 -0
- package/lib/hotfixes/index.d.ts +5 -0
- package/lib/hotfixes/index.js +5 -0
- package/lib/hotfixes/jupytext-1.19.1.d.ts +18 -0
- package/lib/hotfixes/jupytext-1.19.1.js +168 -0
- package/lib/index.js +31 -0
- package/package.json +1 -1
- package/src/hotfixes/index.ts +8 -0
- package/src/hotfixes/jupytext-1.19.1.ts +183 -0
- package/src/index.ts +34 -0
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ This extension brings 1414 beautiful file type icons from the vscode-icons proje
|
|
|
23
23
|
- Automatic icon detection based on file extensions and names
|
|
24
24
|
- Zero configuration required - just install and enjoy
|
|
25
25
|
- Lightweight integration using Iconify's JSON icon format
|
|
26
|
-
- Compatible with Jupytext - properly displays Python and Markdown icons for .py and .md notebook files
|
|
26
|
+
- Compatible with Jupytext - properly displays Python and Markdown icons for .py and .md notebook files; includes hotfix for jupytext 1.19.1 catch-all pattern bug that breaks standard file icons
|
|
27
27
|
- Python package folder detection - folders declared in `pyproject.toml` or `setup.py` get a special Python package icon
|
|
28
28
|
|
|
29
29
|
## Requirements
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Python package configuration file parsers
|
|
3
|
+
*/
|
|
4
|
+
import { parsePyprojectToml, parseSetupPy } from '../parsers';
|
|
5
|
+
describe('parsePyprojectToml', () => {
|
|
6
|
+
describe('project name parsing', () => {
|
|
7
|
+
it('should parse project name with double quotes', () => {
|
|
8
|
+
const content = `
|
|
9
|
+
[project]
|
|
10
|
+
name = "my-package"
|
|
11
|
+
version = "1.0.0"
|
|
12
|
+
`;
|
|
13
|
+
const result = parsePyprojectToml(content);
|
|
14
|
+
expect(result.has('my-package')).toBe(true);
|
|
15
|
+
expect(result.has('my_package')).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
it('should parse project name with single quotes', () => {
|
|
18
|
+
const content = `
|
|
19
|
+
[project]
|
|
20
|
+
name = 'my-package'
|
|
21
|
+
version = '1.0.0'
|
|
22
|
+
`;
|
|
23
|
+
const result = parsePyprojectToml(content);
|
|
24
|
+
expect(result.has('my-package')).toBe(true);
|
|
25
|
+
expect(result.has('my_package')).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
it('should handle name without hyphens', () => {
|
|
28
|
+
const content = `
|
|
29
|
+
[project]
|
|
30
|
+
name = "mypackage"
|
|
31
|
+
`;
|
|
32
|
+
const result = parsePyprojectToml(content);
|
|
33
|
+
expect(result.has('mypackage')).toBe(true);
|
|
34
|
+
// underscore variant is same as original
|
|
35
|
+
expect(result.size).toBe(1);
|
|
36
|
+
});
|
|
37
|
+
it('should handle name with multiple hyphens', () => {
|
|
38
|
+
const content = `
|
|
39
|
+
[project]
|
|
40
|
+
name = "my-awesome-package"
|
|
41
|
+
`;
|
|
42
|
+
const result = parsePyprojectToml(content);
|
|
43
|
+
expect(result.has('my-awesome-package')).toBe(true);
|
|
44
|
+
expect(result.has('my_awesome_package')).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
it('should handle whitespace around equals sign', () => {
|
|
47
|
+
const content = `
|
|
48
|
+
[project]
|
|
49
|
+
name="my-package"
|
|
50
|
+
`;
|
|
51
|
+
const result = parsePyprojectToml(content);
|
|
52
|
+
expect(result.has('my-package')).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
it('should handle extra whitespace', () => {
|
|
55
|
+
const content = `
|
|
56
|
+
[project]
|
|
57
|
+
name = "my-package"
|
|
58
|
+
`;
|
|
59
|
+
const result = parsePyprojectToml(content);
|
|
60
|
+
expect(result.has('my-package')).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe('packages array parsing', () => {
|
|
64
|
+
it('should parse packages array with double quotes', () => {
|
|
65
|
+
const content = `
|
|
66
|
+
[tool.setuptools]
|
|
67
|
+
packages = ["pkg1", "pkg2", "pkg3"]
|
|
68
|
+
`;
|
|
69
|
+
const result = parsePyprojectToml(content);
|
|
70
|
+
expect(result.has('pkg1')).toBe(true);
|
|
71
|
+
expect(result.has('pkg2')).toBe(true);
|
|
72
|
+
expect(result.has('pkg3')).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
it('should parse packages array with single quotes', () => {
|
|
75
|
+
const content = `
|
|
76
|
+
[tool.setuptools]
|
|
77
|
+
packages = ['pkg1', 'pkg2']
|
|
78
|
+
`;
|
|
79
|
+
const result = parsePyprojectToml(content);
|
|
80
|
+
expect(result.has('pkg1')).toBe(true);
|
|
81
|
+
expect(result.has('pkg2')).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
it('should parse packages array with mixed quotes', () => {
|
|
84
|
+
const content = `
|
|
85
|
+
packages = ["pkg1", 'pkg2']
|
|
86
|
+
`;
|
|
87
|
+
const result = parsePyprojectToml(content);
|
|
88
|
+
expect(result.has('pkg1')).toBe(true);
|
|
89
|
+
expect(result.has('pkg2')).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
it('should parse multiline packages array', () => {
|
|
92
|
+
const content = `
|
|
93
|
+
packages = [
|
|
94
|
+
"pkg1",
|
|
95
|
+
"pkg2",
|
|
96
|
+
"pkg3"
|
|
97
|
+
]
|
|
98
|
+
`;
|
|
99
|
+
const result = parsePyprojectToml(content);
|
|
100
|
+
expect(result.has('pkg1')).toBe(true);
|
|
101
|
+
expect(result.has('pkg2')).toBe(true);
|
|
102
|
+
expect(result.has('pkg3')).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
it('should parse single package in array', () => {
|
|
105
|
+
const content = `
|
|
106
|
+
packages = ["single_pkg"]
|
|
107
|
+
`;
|
|
108
|
+
const result = parsePyprojectToml(content);
|
|
109
|
+
expect(result.has('single_pkg')).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
describe('combined parsing', () => {
|
|
113
|
+
it('should parse both project name and packages array', () => {
|
|
114
|
+
const content = `
|
|
115
|
+
[project]
|
|
116
|
+
name = "main-package"
|
|
117
|
+
version = "1.0.0"
|
|
118
|
+
|
|
119
|
+
[tool.setuptools]
|
|
120
|
+
packages = ["subpkg1", "subpkg2"]
|
|
121
|
+
`;
|
|
122
|
+
const result = parsePyprojectToml(content);
|
|
123
|
+
expect(result.has('main-package')).toBe(true);
|
|
124
|
+
expect(result.has('main_package')).toBe(true);
|
|
125
|
+
expect(result.has('subpkg1')).toBe(true);
|
|
126
|
+
expect(result.has('subpkg2')).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
it('should handle real-world pyproject.toml', () => {
|
|
129
|
+
const content = `
|
|
130
|
+
[build-system]
|
|
131
|
+
requires = ["hatchling>=1.5.0", "jupyterlab>=4.0.0,<5"]
|
|
132
|
+
build-backend = "hatchling.build"
|
|
133
|
+
|
|
134
|
+
[project]
|
|
135
|
+
name = "jupyterlab_vscode_icons_extension"
|
|
136
|
+
version = "1.1.9"
|
|
137
|
+
description = "VSCode-style file icons for JupyterLab"
|
|
138
|
+
readme = "README.md"
|
|
139
|
+
license = { file = "LICENSE" }
|
|
140
|
+
requires-python = ">=3.8"
|
|
141
|
+
classifiers = [
|
|
142
|
+
"Framework :: Jupyter",
|
|
143
|
+
"Framework :: Jupyter :: JupyterLab",
|
|
144
|
+
"Framework :: Jupyter :: JupyterLab :: 4",
|
|
145
|
+
"License :: OSI Approved :: BSD License",
|
|
146
|
+
"Programming Language :: Python",
|
|
147
|
+
"Programming Language :: Python :: 3",
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
[tool.hatch.build.targets.wheel.shared-data]
|
|
151
|
+
"jupyterlab_vscode_icons_extension/labextension" = "share/jupyter/labextensions/jupyterlab_vscode_icons_extension"
|
|
152
|
+
`;
|
|
153
|
+
const result = parsePyprojectToml(content);
|
|
154
|
+
expect(result.has('jupyterlab_vscode_icons_extension')).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
describe('edge cases', () => {
|
|
158
|
+
it('should return empty set for empty content', () => {
|
|
159
|
+
const result = parsePyprojectToml('');
|
|
160
|
+
expect(result.size).toBe(0);
|
|
161
|
+
});
|
|
162
|
+
it('should return empty set for content without project section', () => {
|
|
163
|
+
const content = `
|
|
164
|
+
[build-system]
|
|
165
|
+
requires = ["hatchling"]
|
|
166
|
+
`;
|
|
167
|
+
const result = parsePyprojectToml(content);
|
|
168
|
+
expect(result.size).toBe(0);
|
|
169
|
+
});
|
|
170
|
+
it('should not match name outside [project] section', () => {
|
|
171
|
+
const content = `
|
|
172
|
+
[other-section]
|
|
173
|
+
name = "wrong-package"
|
|
174
|
+
|
|
175
|
+
[project]
|
|
176
|
+
version = "1.0.0"
|
|
177
|
+
`;
|
|
178
|
+
const result = parsePyprojectToml(content);
|
|
179
|
+
expect(result.has('wrong-package')).toBe(false);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
describe('parseSetupPy', () => {
|
|
184
|
+
describe('name parsing', () => {
|
|
185
|
+
it('should parse name with double quotes', () => {
|
|
186
|
+
const content = `
|
|
187
|
+
from setuptools import setup
|
|
188
|
+
|
|
189
|
+
setup(
|
|
190
|
+
name="my-package",
|
|
191
|
+
version="1.0.0",
|
|
192
|
+
)
|
|
193
|
+
`;
|
|
194
|
+
const result = parseSetupPy(content);
|
|
195
|
+
expect(result.has('my-package')).toBe(true);
|
|
196
|
+
expect(result.has('my_package')).toBe(true);
|
|
197
|
+
});
|
|
198
|
+
it('should parse name with single quotes', () => {
|
|
199
|
+
const content = `
|
|
200
|
+
setup(
|
|
201
|
+
name='my-package',
|
|
202
|
+
)
|
|
203
|
+
`;
|
|
204
|
+
const result = parseSetupPy(content);
|
|
205
|
+
expect(result.has('my-package')).toBe(true);
|
|
206
|
+
});
|
|
207
|
+
it('should handle name without hyphens', () => {
|
|
208
|
+
const content = `
|
|
209
|
+
setup(name="mypackage")
|
|
210
|
+
`;
|
|
211
|
+
const result = parseSetupPy(content);
|
|
212
|
+
expect(result.has('mypackage')).toBe(true);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
describe('packages array parsing', () => {
|
|
216
|
+
it('should parse packages array inline', () => {
|
|
217
|
+
const content = `
|
|
218
|
+
setup(
|
|
219
|
+
packages=["pkg1", "pkg2", "pkg3"],
|
|
220
|
+
)
|
|
221
|
+
`;
|
|
222
|
+
const result = parseSetupPy(content);
|
|
223
|
+
expect(result.has('pkg1')).toBe(true);
|
|
224
|
+
expect(result.has('pkg2')).toBe(true);
|
|
225
|
+
expect(result.has('pkg3')).toBe(true);
|
|
226
|
+
});
|
|
227
|
+
it('should parse packages array multiline', () => {
|
|
228
|
+
const content = `
|
|
229
|
+
setup(
|
|
230
|
+
packages=[
|
|
231
|
+
"pkg1",
|
|
232
|
+
"pkg2",
|
|
233
|
+
],
|
|
234
|
+
)
|
|
235
|
+
`;
|
|
236
|
+
const result = parseSetupPy(content);
|
|
237
|
+
expect(result.has('pkg1')).toBe(true);
|
|
238
|
+
expect(result.has('pkg2')).toBe(true);
|
|
239
|
+
});
|
|
240
|
+
it('should parse packages with single quotes', () => {
|
|
241
|
+
const content = `
|
|
242
|
+
packages=['pkg1', 'pkg2']
|
|
243
|
+
`;
|
|
244
|
+
const result = parseSetupPy(content);
|
|
245
|
+
expect(result.has('pkg1')).toBe(true);
|
|
246
|
+
expect(result.has('pkg2')).toBe(true);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
describe('combined parsing', () => {
|
|
250
|
+
it('should parse both name and packages', () => {
|
|
251
|
+
const content = `
|
|
252
|
+
from setuptools import setup
|
|
253
|
+
|
|
254
|
+
setup(
|
|
255
|
+
name="main-package",
|
|
256
|
+
version="1.0.0",
|
|
257
|
+
packages=["subpkg1", "subpkg2"],
|
|
258
|
+
)
|
|
259
|
+
`;
|
|
260
|
+
const result = parseSetupPy(content);
|
|
261
|
+
expect(result.has('main-package')).toBe(true);
|
|
262
|
+
expect(result.has('main_package')).toBe(true);
|
|
263
|
+
expect(result.has('subpkg1')).toBe(true);
|
|
264
|
+
expect(result.has('subpkg2')).toBe(true);
|
|
265
|
+
});
|
|
266
|
+
it('should handle real-world setup.py', () => {
|
|
267
|
+
const content = `
|
|
268
|
+
#!/usr/bin/env python
|
|
269
|
+
from setuptools import setup, find_packages
|
|
270
|
+
|
|
271
|
+
setup(
|
|
272
|
+
name="my-awesome-lib",
|
|
273
|
+
version="2.0.0",
|
|
274
|
+
author="Developer",
|
|
275
|
+
author_email="dev@example.com",
|
|
276
|
+
description="An awesome library",
|
|
277
|
+
packages=find_packages(),
|
|
278
|
+
python_requires=">=3.8",
|
|
279
|
+
install_requires=[
|
|
280
|
+
"numpy>=1.0",
|
|
281
|
+
"pandas>=1.0",
|
|
282
|
+
],
|
|
283
|
+
)
|
|
284
|
+
`;
|
|
285
|
+
const result = parseSetupPy(content);
|
|
286
|
+
expect(result.has('my-awesome-lib')).toBe(true);
|
|
287
|
+
expect(result.has('my_awesome_lib')).toBe(true);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
describe('edge cases', () => {
|
|
291
|
+
it('should return empty set for empty content', () => {
|
|
292
|
+
const result = parseSetupPy('');
|
|
293
|
+
expect(result.size).toBe(0);
|
|
294
|
+
});
|
|
295
|
+
it('should return empty set for content with find_packages() only', () => {
|
|
296
|
+
const content = `
|
|
297
|
+
setup(
|
|
298
|
+
packages=find_packages(),
|
|
299
|
+
)
|
|
300
|
+
`;
|
|
301
|
+
const result = parseSetupPy(content);
|
|
302
|
+
// find_packages() is dynamic, not a literal array
|
|
303
|
+
expect(result.size).toBe(0);
|
|
304
|
+
});
|
|
305
|
+
it('should handle whitespace variations', () => {
|
|
306
|
+
const content = `
|
|
307
|
+
setup(name = "my-pkg",packages = ["pkg1"])
|
|
308
|
+
`;
|
|
309
|
+
const result = parseSetupPy(content);
|
|
310
|
+
expect(result.has('my-pkg')).toBe(true);
|
|
311
|
+
expect(result.has('pkg1')).toBe(true);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hotfix for jupytext 1.19.1 breaking change
|
|
3
|
+
*
|
|
4
|
+
* Jupytext 1.19.1 registers a catch-all file type with pattern
|
|
5
|
+
* `^(?!.*\\.(excludedExtensions)$).*$` that overrides icons for ALL
|
|
6
|
+
* standard file types (yml, js, png, zip, etc.) because they forgot
|
|
7
|
+
* to exclude them.
|
|
8
|
+
*
|
|
9
|
+
* This hotfix monkey-patches jupytext's file type registration by
|
|
10
|
+
* modifying its pattern to exclude standard file extensions.
|
|
11
|
+
*
|
|
12
|
+
* Remove this file when jupytext releases a fix.
|
|
13
|
+
*
|
|
14
|
+
* @see https://github.com/mwouts/jupytext/blob/v1.19.1/jupyterlab/packages/jupyterlab-jupytext-extension/src/registry.ts
|
|
15
|
+
*/
|
|
16
|
+
import { DocumentRegistry } from '@jupyterlab/docregistry';
|
|
17
|
+
export declare const applyJupytext1191Hotfix: (docRegistry?: DocumentRegistry) => void;
|
|
18
|
+
export declare const removeJupytext1191Hotfix: () => void;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hotfix for jupytext 1.19.1 breaking change
|
|
3
|
+
*
|
|
4
|
+
* Jupytext 1.19.1 registers a catch-all file type with pattern
|
|
5
|
+
* `^(?!.*\\.(excludedExtensions)$).*$` that overrides icons for ALL
|
|
6
|
+
* standard file types (yml, js, png, zip, etc.) because they forgot
|
|
7
|
+
* to exclude them.
|
|
8
|
+
*
|
|
9
|
+
* This hotfix monkey-patches jupytext's file type registration by
|
|
10
|
+
* modifying its pattern to exclude standard file extensions.
|
|
11
|
+
*
|
|
12
|
+
* Remove this file when jupytext releases a fix.
|
|
13
|
+
*
|
|
14
|
+
* @see https://github.com/mwouts/jupytext/blob/v1.19.1/jupyterlab/packages/jupyterlab-jupytext-extension/src/registry.ts
|
|
15
|
+
*/
|
|
16
|
+
const JUPYTEXT_1191_STYLE_ID = 'vscode-icons-jupytext-1191-hotfix';
|
|
17
|
+
const HOTFIX_CSS = `
|
|
18
|
+
/* Reset jupytext 1.19.1 ::after pseudo-element (orange borders on markdown) */
|
|
19
|
+
.jp-DirListing-item .jp-DirListing-itemIcon::after {
|
|
20
|
+
content: none !important;
|
|
21
|
+
display: none !important;
|
|
22
|
+
border: none !important;
|
|
23
|
+
outline: none !important;
|
|
24
|
+
background: none !important;
|
|
25
|
+
position: static !important;
|
|
26
|
+
width: 0 !important;
|
|
27
|
+
height: 0 !important;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Ensure icon container displays normally */
|
|
31
|
+
.jp-DirListing-item .jp-DirListing-itemIcon {
|
|
32
|
+
overflow: visible !important;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Ensure SVG and img icons inside are visible */
|
|
36
|
+
.jp-DirListing-item .jp-DirListing-itemIcon > svg,
|
|
37
|
+
.jp-DirListing-item .jp-DirListing-itemIcon > img {
|
|
38
|
+
display: inline-block !important;
|
|
39
|
+
visibility: visible !important;
|
|
40
|
+
opacity: 1 !important;
|
|
41
|
+
}
|
|
42
|
+
`;
|
|
43
|
+
// Extensions that should NOT be caught by jupytext's catch-all
|
|
44
|
+
const KNOWN_EXTENSIONS = [
|
|
45
|
+
// Images
|
|
46
|
+
'jpg',
|
|
47
|
+
'jpeg',
|
|
48
|
+
'png',
|
|
49
|
+
'gif',
|
|
50
|
+
'bmp',
|
|
51
|
+
'svg',
|
|
52
|
+
'webp',
|
|
53
|
+
'ico',
|
|
54
|
+
'tiff',
|
|
55
|
+
'tif',
|
|
56
|
+
// Data
|
|
57
|
+
'json',
|
|
58
|
+
'yaml',
|
|
59
|
+
'yml',
|
|
60
|
+
'xml',
|
|
61
|
+
'csv',
|
|
62
|
+
'tsv',
|
|
63
|
+
// Web
|
|
64
|
+
'html',
|
|
65
|
+
'htm',
|
|
66
|
+
'css',
|
|
67
|
+
// Archives
|
|
68
|
+
'zip',
|
|
69
|
+
'tar',
|
|
70
|
+
'gz',
|
|
71
|
+
'bz2',
|
|
72
|
+
'xz',
|
|
73
|
+
'7z',
|
|
74
|
+
'rar',
|
|
75
|
+
// Documents
|
|
76
|
+
'pdf',
|
|
77
|
+
'doc',
|
|
78
|
+
'docx',
|
|
79
|
+
'xls',
|
|
80
|
+
'xlsx',
|
|
81
|
+
'xlsm',
|
|
82
|
+
'ppt',
|
|
83
|
+
'pptx',
|
|
84
|
+
'odt',
|
|
85
|
+
'ods',
|
|
86
|
+
'odp',
|
|
87
|
+
// Programming (not handled by jupytext)
|
|
88
|
+
'js',
|
|
89
|
+
'mjs',
|
|
90
|
+
'cjs',
|
|
91
|
+
'ts',
|
|
92
|
+
'mts',
|
|
93
|
+
'cts',
|
|
94
|
+
'jsx',
|
|
95
|
+
'tsx',
|
|
96
|
+
'java',
|
|
97
|
+
'c',
|
|
98
|
+
'cpp',
|
|
99
|
+
'h',
|
|
100
|
+
'hpp',
|
|
101
|
+
'cs',
|
|
102
|
+
'go',
|
|
103
|
+
'rs',
|
|
104
|
+
'rb',
|
|
105
|
+
'php',
|
|
106
|
+
'swift',
|
|
107
|
+
'kt',
|
|
108
|
+
'scala',
|
|
109
|
+
'pl',
|
|
110
|
+
'pm',
|
|
111
|
+
'lua',
|
|
112
|
+
'sh',
|
|
113
|
+
'bash',
|
|
114
|
+
'zsh',
|
|
115
|
+
'fish',
|
|
116
|
+
'csh',
|
|
117
|
+
'nu',
|
|
118
|
+
'bat',
|
|
119
|
+
'cmd',
|
|
120
|
+
'ps1',
|
|
121
|
+
// Config
|
|
122
|
+
'toml',
|
|
123
|
+
'ini',
|
|
124
|
+
'cfg',
|
|
125
|
+
'conf',
|
|
126
|
+
'env',
|
|
127
|
+
'lock',
|
|
128
|
+
'tf',
|
|
129
|
+
'tfvars',
|
|
130
|
+
// Text/Docs
|
|
131
|
+
'txt',
|
|
132
|
+
'log',
|
|
133
|
+
'rst',
|
|
134
|
+
'tex',
|
|
135
|
+
// Notebooks (handled separately by jupytext)
|
|
136
|
+
'ipynb'
|
|
137
|
+
];
|
|
138
|
+
export const applyJupytext1191Hotfix = (docRegistry) => {
|
|
139
|
+
// Inject CSS fixes
|
|
140
|
+
if (!document.getElementById(JUPYTEXT_1191_STYLE_ID)) {
|
|
141
|
+
const styleElement = document.createElement('style');
|
|
142
|
+
styleElement.id = JUPYTEXT_1191_STYLE_ID;
|
|
143
|
+
styleElement.textContent = HOTFIX_CSS;
|
|
144
|
+
document.head.appendChild(styleElement);
|
|
145
|
+
}
|
|
146
|
+
// Monkey-patch jupytext's file type by accessing the internal registry
|
|
147
|
+
if (docRegistry) {
|
|
148
|
+
// Access internal file types array (private but accessible)
|
|
149
|
+
const fileTypes = docRegistry._fileTypes;
|
|
150
|
+
if (fileTypes && Array.isArray(fileTypes)) {
|
|
151
|
+
// Find jupytext's catch-all file type
|
|
152
|
+
const jupytextCatchAll = fileTypes.find((ft) => ft.name === 'jupytext-notebook-file');
|
|
153
|
+
if (jupytextCatchAll) {
|
|
154
|
+
// Build corrected pattern that excludes known extensions
|
|
155
|
+
const extPattern = KNOWN_EXTENSIONS.join('|');
|
|
156
|
+
const correctedPattern = `^(?!.*\\.(${extPattern})$).*$`;
|
|
157
|
+
// Monkey-patch the pattern
|
|
158
|
+
jupytextCatchAll.pattern = correctedPattern;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
export const removeJupytext1191Hotfix = () => {
|
|
164
|
+
const styleElement = document.getElementById(JUPYTEXT_1191_STYLE_ID);
|
|
165
|
+
if (styleElement) {
|
|
166
|
+
styleElement.remove();
|
|
167
|
+
}
|
|
168
|
+
};
|
package/lib/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { IDefaultFileBrowser } from '@jupyterlab/filebrowser';
|
|
|
4
4
|
import { LabIcon } from '@jupyterlab/ui-components';
|
|
5
5
|
import { getIconSVG } from './icons';
|
|
6
6
|
import { parsePyprojectToml, parseSetupPy } from './parsers';
|
|
7
|
+
import { applyJupytext1191Hotfix } from './hotfixes';
|
|
7
8
|
const PLUGIN_ID = 'jupyterlab_vscode_icons_extension:plugin';
|
|
8
9
|
/**
|
|
9
10
|
* Create a LabIcon from vscode-icons SVG data
|
|
@@ -959,6 +960,8 @@ const plugin = {
|
|
|
959
960
|
// Wait for DOM to be ready, then inject CSS
|
|
960
961
|
app.started.then(() => {
|
|
961
962
|
setTimeout(injectIconOverrideCSS, 500);
|
|
963
|
+
// Apply hotfix for jupytext 1.19.1 (re-registers standard file types broken by catch-all pattern)
|
|
964
|
+
applyJupytext1191Hotfix(docRegistry);
|
|
962
965
|
});
|
|
963
966
|
// Default settings
|
|
964
967
|
const settings = {
|
|
@@ -1085,6 +1088,34 @@ const plugin = {
|
|
|
1085
1088
|
contentType: 'file',
|
|
1086
1089
|
icon: readmeIcon
|
|
1087
1090
|
});
|
|
1091
|
+
// Register CHANGELOG with document + list icon
|
|
1092
|
+
const changelogSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
1093
|
+
<path style="opacity:0.2" d="m 14.5,8 c -1.385,0 -2.5,1.115 -2.5,2.5 v 45 c 0,1.385 1.115,2.5 2.5,2.5 h 35 C 50.885,58 52,56.885 52,55.5 V 23 L 38.25,21.75 37,8 Z"/>
|
|
1094
|
+
<path fill="#e4e4e4" d="m14.5 7c-1.385 0-2.5 1.115-2.5 2.5v45c0 1.385 1.115 2.5 2.5 2.5h35c1.385 0 2.5-1.115 2.5-2.5v-32.5l-13.75-1.25-1.25-13.75z"/>
|
|
1095
|
+
<path style="opacity:0.2" d="M 37,8 V 20.5 c 0,1.3808 1.1193,2.5 2.5,2.5 H 52 Z"/>
|
|
1096
|
+
<path fill="#fafafa" d="m37 7v12.5c0 1.3808 1.1193 2.5 2.5 2.5h12.5l-15-15z"/>
|
|
1097
|
+
<rect style="opacity:0.5" width="19" height="3" x="25" y="35"/>
|
|
1098
|
+
<rect style="opacity:0.5" width="19" height="3" x="25" y="40"/>
|
|
1099
|
+
<rect style="opacity:0.5" width="19" height="3" x="25" y="45"/>
|
|
1100
|
+
<rect style="opacity:0.5" width="3" height="3" x="20" y="35"/>
|
|
1101
|
+
<rect style="opacity:0.5" width="3" height="3" x="20" y="40"/>
|
|
1102
|
+
<rect style="opacity:0.5" width="3" height="3" x="20" y="45"/>
|
|
1103
|
+
<rect style="opacity:0.5" width="19" height="3" x="25" y="30"/>
|
|
1104
|
+
<rect style="opacity:0.5" width="3" height="3" x="20" y="30"/>
|
|
1105
|
+
<path style="opacity:0.2;fill:#ffffff" d="m 14.5,7 c -1.385,0 -2.5,1.115 -2.5,2.5 V 10.5 C 12,9.115 13.115,8 14.5,8 H 37 c 0,-1 0,0 0,-1 z"/>
|
|
1106
|
+
</svg>`;
|
|
1107
|
+
const changelogIcon = new LabIcon({
|
|
1108
|
+
name: 'changelog-icon',
|
|
1109
|
+
svgstr: changelogSvg
|
|
1110
|
+
});
|
|
1111
|
+
docRegistry.addFileType({
|
|
1112
|
+
name: 'vscode-changelog',
|
|
1113
|
+
displayName: 'Changelog',
|
|
1114
|
+
pattern: '^CHANGELOG(\\.md)?$',
|
|
1115
|
+
fileFormat: 'text',
|
|
1116
|
+
contentType: 'file',
|
|
1117
|
+
icon: changelogIcon
|
|
1118
|
+
});
|
|
1088
1119
|
// Register Draw.io files with custom orange diagram icon
|
|
1089
1120
|
if (settings.enableConfigIcons) {
|
|
1090
1121
|
const drawioSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 161.6 161.6">
|
package/package.json
CHANGED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hotfix for jupytext 1.19.1 breaking change
|
|
3
|
+
*
|
|
4
|
+
* Jupytext 1.19.1 registers a catch-all file type with pattern
|
|
5
|
+
* `^(?!.*\\.(excludedExtensions)$).*$` that overrides icons for ALL
|
|
6
|
+
* standard file types (yml, js, png, zip, etc.) because they forgot
|
|
7
|
+
* to exclude them.
|
|
8
|
+
*
|
|
9
|
+
* This hotfix monkey-patches jupytext's file type registration by
|
|
10
|
+
* modifying its pattern to exclude standard file extensions.
|
|
11
|
+
*
|
|
12
|
+
* Remove this file when jupytext releases a fix.
|
|
13
|
+
*
|
|
14
|
+
* @see https://github.com/mwouts/jupytext/blob/v1.19.1/jupyterlab/packages/jupyterlab-jupytext-extension/src/registry.ts
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { DocumentRegistry } from '@jupyterlab/docregistry';
|
|
18
|
+
|
|
19
|
+
const JUPYTEXT_1191_STYLE_ID = 'vscode-icons-jupytext-1191-hotfix';
|
|
20
|
+
|
|
21
|
+
const HOTFIX_CSS = `
|
|
22
|
+
/* Reset jupytext 1.19.1 ::after pseudo-element (orange borders on markdown) */
|
|
23
|
+
.jp-DirListing-item .jp-DirListing-itemIcon::after {
|
|
24
|
+
content: none !important;
|
|
25
|
+
display: none !important;
|
|
26
|
+
border: none !important;
|
|
27
|
+
outline: none !important;
|
|
28
|
+
background: none !important;
|
|
29
|
+
position: static !important;
|
|
30
|
+
width: 0 !important;
|
|
31
|
+
height: 0 !important;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Ensure icon container displays normally */
|
|
35
|
+
.jp-DirListing-item .jp-DirListing-itemIcon {
|
|
36
|
+
overflow: visible !important;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Ensure SVG and img icons inside are visible */
|
|
40
|
+
.jp-DirListing-item .jp-DirListing-itemIcon > svg,
|
|
41
|
+
.jp-DirListing-item .jp-DirListing-itemIcon > img {
|
|
42
|
+
display: inline-block !important;
|
|
43
|
+
visibility: visible !important;
|
|
44
|
+
opacity: 1 !important;
|
|
45
|
+
}
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
// Extensions that should NOT be caught by jupytext's catch-all
|
|
49
|
+
const KNOWN_EXTENSIONS = [
|
|
50
|
+
// Images
|
|
51
|
+
'jpg',
|
|
52
|
+
'jpeg',
|
|
53
|
+
'png',
|
|
54
|
+
'gif',
|
|
55
|
+
'bmp',
|
|
56
|
+
'svg',
|
|
57
|
+
'webp',
|
|
58
|
+
'ico',
|
|
59
|
+
'tiff',
|
|
60
|
+
'tif',
|
|
61
|
+
// Data
|
|
62
|
+
'json',
|
|
63
|
+
'yaml',
|
|
64
|
+
'yml',
|
|
65
|
+
'xml',
|
|
66
|
+
'csv',
|
|
67
|
+
'tsv',
|
|
68
|
+
// Web
|
|
69
|
+
'html',
|
|
70
|
+
'htm',
|
|
71
|
+
'css',
|
|
72
|
+
// Archives
|
|
73
|
+
'zip',
|
|
74
|
+
'tar',
|
|
75
|
+
'gz',
|
|
76
|
+
'bz2',
|
|
77
|
+
'xz',
|
|
78
|
+
'7z',
|
|
79
|
+
'rar',
|
|
80
|
+
// Documents
|
|
81
|
+
'pdf',
|
|
82
|
+
'doc',
|
|
83
|
+
'docx',
|
|
84
|
+
'xls',
|
|
85
|
+
'xlsx',
|
|
86
|
+
'xlsm',
|
|
87
|
+
'ppt',
|
|
88
|
+
'pptx',
|
|
89
|
+
'odt',
|
|
90
|
+
'ods',
|
|
91
|
+
'odp',
|
|
92
|
+
// Programming (not handled by jupytext)
|
|
93
|
+
'js',
|
|
94
|
+
'mjs',
|
|
95
|
+
'cjs',
|
|
96
|
+
'ts',
|
|
97
|
+
'mts',
|
|
98
|
+
'cts',
|
|
99
|
+
'jsx',
|
|
100
|
+
'tsx',
|
|
101
|
+
'java',
|
|
102
|
+
'c',
|
|
103
|
+
'cpp',
|
|
104
|
+
'h',
|
|
105
|
+
'hpp',
|
|
106
|
+
'cs',
|
|
107
|
+
'go',
|
|
108
|
+
'rs',
|
|
109
|
+
'rb',
|
|
110
|
+
'php',
|
|
111
|
+
'swift',
|
|
112
|
+
'kt',
|
|
113
|
+
'scala',
|
|
114
|
+
'pl',
|
|
115
|
+
'pm',
|
|
116
|
+
'lua',
|
|
117
|
+
'sh',
|
|
118
|
+
'bash',
|
|
119
|
+
'zsh',
|
|
120
|
+
'fish',
|
|
121
|
+
'csh',
|
|
122
|
+
'nu',
|
|
123
|
+
'bat',
|
|
124
|
+
'cmd',
|
|
125
|
+
'ps1',
|
|
126
|
+
// Config
|
|
127
|
+
'toml',
|
|
128
|
+
'ini',
|
|
129
|
+
'cfg',
|
|
130
|
+
'conf',
|
|
131
|
+
'env',
|
|
132
|
+
'lock',
|
|
133
|
+
'tf',
|
|
134
|
+
'tfvars',
|
|
135
|
+
// Text/Docs
|
|
136
|
+
'txt',
|
|
137
|
+
'log',
|
|
138
|
+
'rst',
|
|
139
|
+
'tex',
|
|
140
|
+
// Notebooks (handled separately by jupytext)
|
|
141
|
+
'ipynb'
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
export const applyJupytext1191Hotfix = (
|
|
145
|
+
docRegistry?: DocumentRegistry
|
|
146
|
+
): void => {
|
|
147
|
+
// Inject CSS fixes
|
|
148
|
+
if (!document.getElementById(JUPYTEXT_1191_STYLE_ID)) {
|
|
149
|
+
const styleElement = document.createElement('style');
|
|
150
|
+
styleElement.id = JUPYTEXT_1191_STYLE_ID;
|
|
151
|
+
styleElement.textContent = HOTFIX_CSS;
|
|
152
|
+
document.head.appendChild(styleElement);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Monkey-patch jupytext's file type by accessing the internal registry
|
|
156
|
+
if (docRegistry) {
|
|
157
|
+
// Access internal file types array (private but accessible)
|
|
158
|
+
const fileTypes = (docRegistry as any)._fileTypes as any[];
|
|
159
|
+
|
|
160
|
+
if (fileTypes && Array.isArray(fileTypes)) {
|
|
161
|
+
// Find jupytext's catch-all file type
|
|
162
|
+
const jupytextCatchAll = fileTypes.find(
|
|
163
|
+
(ft: any) => ft.name === 'jupytext-notebook-file'
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
if (jupytextCatchAll) {
|
|
167
|
+
// Build corrected pattern that excludes known extensions
|
|
168
|
+
const extPattern = KNOWN_EXTENSIONS.join('|');
|
|
169
|
+
const correctedPattern = `^(?!.*\\.(${extPattern})$).*$`;
|
|
170
|
+
|
|
171
|
+
// Monkey-patch the pattern
|
|
172
|
+
jupytextCatchAll.pattern = correctedPattern;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export const removeJupytext1191Hotfix = (): void => {
|
|
179
|
+
const styleElement = document.getElementById(JUPYTEXT_1191_STYLE_ID);
|
|
180
|
+
if (styleElement) {
|
|
181
|
+
styleElement.remove();
|
|
182
|
+
}
|
|
183
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { IDefaultFileBrowser } from '@jupyterlab/filebrowser';
|
|
|
8
8
|
import { LabIcon } from '@jupyterlab/ui-components';
|
|
9
9
|
import { getIconSVG } from './icons';
|
|
10
10
|
import { parsePyprojectToml, parseSetupPy } from './parsers';
|
|
11
|
+
import { applyJupytext1191Hotfix } from './hotfixes';
|
|
11
12
|
|
|
12
13
|
const PLUGIN_ID = 'jupyterlab_vscode_icons_extension:plugin';
|
|
13
14
|
|
|
@@ -1059,6 +1060,8 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
1059
1060
|
// Wait for DOM to be ready, then inject CSS
|
|
1060
1061
|
app.started.then(() => {
|
|
1061
1062
|
setTimeout(injectIconOverrideCSS, 500);
|
|
1063
|
+
// Apply hotfix for jupytext 1.19.1 (re-registers standard file types broken by catch-all pattern)
|
|
1064
|
+
applyJupytext1191Hotfix(docRegistry);
|
|
1062
1065
|
});
|
|
1063
1066
|
|
|
1064
1067
|
// Default settings
|
|
@@ -1204,6 +1207,37 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
1204
1207
|
icon: readmeIcon
|
|
1205
1208
|
});
|
|
1206
1209
|
|
|
1210
|
+
// Register CHANGELOG with document + list icon
|
|
1211
|
+
const changelogSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
1212
|
+
<path style="opacity:0.2" d="m 14.5,8 c -1.385,0 -2.5,1.115 -2.5,2.5 v 45 c 0,1.385 1.115,2.5 2.5,2.5 h 35 C 50.885,58 52,56.885 52,55.5 V 23 L 38.25,21.75 37,8 Z"/>
|
|
1213
|
+
<path fill="#e4e4e4" d="m14.5 7c-1.385 0-2.5 1.115-2.5 2.5v45c0 1.385 1.115 2.5 2.5 2.5h35c1.385 0 2.5-1.115 2.5-2.5v-32.5l-13.75-1.25-1.25-13.75z"/>
|
|
1214
|
+
<path style="opacity:0.2" d="M 37,8 V 20.5 c 0,1.3808 1.1193,2.5 2.5,2.5 H 52 Z"/>
|
|
1215
|
+
<path fill="#fafafa" d="m37 7v12.5c0 1.3808 1.1193 2.5 2.5 2.5h12.5l-15-15z"/>
|
|
1216
|
+
<rect style="opacity:0.5" width="19" height="3" x="25" y="35"/>
|
|
1217
|
+
<rect style="opacity:0.5" width="19" height="3" x="25" y="40"/>
|
|
1218
|
+
<rect style="opacity:0.5" width="19" height="3" x="25" y="45"/>
|
|
1219
|
+
<rect style="opacity:0.5" width="3" height="3" x="20" y="35"/>
|
|
1220
|
+
<rect style="opacity:0.5" width="3" height="3" x="20" y="40"/>
|
|
1221
|
+
<rect style="opacity:0.5" width="3" height="3" x="20" y="45"/>
|
|
1222
|
+
<rect style="opacity:0.5" width="19" height="3" x="25" y="30"/>
|
|
1223
|
+
<rect style="opacity:0.5" width="3" height="3" x="20" y="30"/>
|
|
1224
|
+
<path style="opacity:0.2;fill:#ffffff" d="m 14.5,7 c -1.385,0 -2.5,1.115 -2.5,2.5 V 10.5 C 12,9.115 13.115,8 14.5,8 H 37 c 0,-1 0,0 0,-1 z"/>
|
|
1225
|
+
</svg>`;
|
|
1226
|
+
|
|
1227
|
+
const changelogIcon = new LabIcon({
|
|
1228
|
+
name: 'changelog-icon',
|
|
1229
|
+
svgstr: changelogSvg
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
docRegistry.addFileType({
|
|
1233
|
+
name: 'vscode-changelog',
|
|
1234
|
+
displayName: 'Changelog',
|
|
1235
|
+
pattern: '^CHANGELOG(\\.md)?$',
|
|
1236
|
+
fileFormat: 'text',
|
|
1237
|
+
contentType: 'file',
|
|
1238
|
+
icon: changelogIcon
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1207
1241
|
// Register Draw.io files with custom orange diagram icon
|
|
1208
1242
|
if (settings.enableConfigIcons) {
|
|
1209
1243
|
const drawioSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 161.6 161.6">
|