jupyterlab_vscode_icons_extension 1.1.49 → 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 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,3 @@
1
+ /**
2
+ * Example of [Jest](https://jestjs.io/docs/getting-started) unit tests
3
+ */
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ /**
3
+ * Example of [Jest](https://jestjs.io/docs/getting-started) unit tests
4
+ */
5
+ describe('jupyterlab_vscode_icons_extension', () => {
6
+ it('should be tested', () => {
7
+ expect(1 + 1).toEqual(2);
8
+ });
9
+ });
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Tests for Python package configuration file parsers
3
+ */
4
+ export {};
@@ -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,5 @@
1
+ /**
2
+ * Hotfixes module - temporary fixes for third-party extension issues.
3
+ * Each hotfix in its own file for easy removal.
4
+ */
5
+ export { applyJupytext1191Hotfix, removeJupytext1191Hotfix } from './jupytext-1.19.1';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Hotfixes module - temporary fixes for third-party extension issues.
3
+ * Each hotfix in its own file for easy removal.
4
+ */
5
+ export { applyJupytext1191Hotfix, removeJupytext1191Hotfix } from './jupytext-1.19.1';
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyterlab_vscode_icons_extension",
3
- "version": "1.1.49",
3
+ "version": "1.1.50",
4
4
  "description": "Jupyterlab extension with a shameless rip-off of the vscode-icons into our beloved environment",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Hotfixes module - temporary fixes for third-party extension issues.
3
+ * Each hotfix in its own file for easy removal.
4
+ */
5
+ export {
6
+ applyJupytext1191Hotfix,
7
+ removeJupytext1191Hotfix
8
+ } from './jupytext-1.19.1';
@@ -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">