jupyterlab_vscode_icons_extension 1.0.117 → 1.1.6
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/lib/index.js +172 -9
- package/package.json +1 -1
- package/src/index.ts +196 -12
- package/style/base.css +1 -1
package/lib/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
2
|
+
import { IDefaultFileBrowser } from '@jupyterlab/filebrowser';
|
|
2
3
|
import { LabIcon } from '@jupyterlab/ui-components';
|
|
3
4
|
import { getIconSVG } from './icons';
|
|
4
5
|
const PLUGIN_ID = 'jupyterlab_vscode_icons_extension:plugin';
|
|
@@ -358,8 +359,8 @@ const plugin = {
|
|
|
358
359
|
id: PLUGIN_ID,
|
|
359
360
|
description: 'Jupyterlab extension with a shameless rip-off of the vscode-icons into our beloved environment',
|
|
360
361
|
autoStart: true,
|
|
361
|
-
optional: [ISettingRegistry],
|
|
362
|
-
activate: (app, settingRegistry) => {
|
|
362
|
+
optional: [ISettingRegistry, IDefaultFileBrowser],
|
|
363
|
+
activate: (app, settingRegistry, defaultFileBrowser) => {
|
|
363
364
|
const { docRegistry } = app;
|
|
364
365
|
// Function to inject CSS that overrides Jupytext icons
|
|
365
366
|
const injectIconOverrideCSS = () => {
|
|
@@ -422,10 +423,24 @@ const plugin = {
|
|
|
422
423
|
const claudeDataUri = `data:image/svg+xml;base64,${btoa(claudeSvg)}`;
|
|
423
424
|
const readmeDataUri = `data:image/svg+xml;base64,${btoa(readmeSvg)}`;
|
|
424
425
|
const pdfDataUri = `data:image/svg+xml;base64,${btoa(pdfSvg)}`;
|
|
425
|
-
const wordDataUri = wordSvg
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
const
|
|
426
|
+
const wordDataUri = wordSvg
|
|
427
|
+
? `data:image/svg+xml;base64,${btoa(wordSvg)}`
|
|
428
|
+
: '';
|
|
429
|
+
const excelDataUri = excelSvg
|
|
430
|
+
? `data:image/svg+xml;base64,${btoa(excelSvg)}`
|
|
431
|
+
: '';
|
|
432
|
+
const powerpointDataUri = powerpointSvg
|
|
433
|
+
? `data:image/svg+xml;base64,${btoa(powerpointSvg)}`
|
|
434
|
+
: '';
|
|
435
|
+
const svgFileDataUri = svgFileSvg
|
|
436
|
+
? `data:image/svg+xml;base64,${btoa(svgFileSvg)}`
|
|
437
|
+
: '';
|
|
438
|
+
const uvSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 330 330"><rect height="100%" width="100%" rx="66" fill="#26102f"/><path fill="#d256dc" d="M 65,65 h92 v130 h16 v-130 h92 v200 h-16 v-20 h-8 a20,20 0 0 1 -20,20 h-136 a20,20 0 0 1 -20,-20 z"/></svg>';
|
|
439
|
+
const uvDataUri = `data:image/svg+xml;base64,${btoa(uvSvg)}`;
|
|
440
|
+
const pytestSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 474 542"><path fill="#696969" d="M21,43h431c12,0 21,9 21,21c0,12-9,21-21,21H21c-12,0-21-9-21-21c0-12,9-21,21-21z"/><path fill="#009fe3" d="M25,0h87v20H25z"/><path fill="#c7d302" d="M138,0h87v20h-87z"/><path fill="#f07e16" d="M250,0h87v20h-87z"/><path fill="#df2815" d="M362,0h87v20h-87z"/><path fill="#df2815" d="M362,107h87v147h-87z"/><path fill="#f07e16" d="M250,107h87v238h-87z"/><path fill="#c7d302" d="M138,107h87v357h-87z"/><path fill="#009fe3" d="M25,107h87v435h-87z"/></svg>';
|
|
441
|
+
const pytestDataUri = `data:image/svg+xml;base64,${btoa(pytestSvg)}`;
|
|
442
|
+
const pythonPackageSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><defs><linearGradient id="ppa" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0" stop-color="#387eb8"/><stop offset="1" stop-color="#366994"/></linearGradient><linearGradient id="ppb" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0" stop-color="#ffe052"/><stop offset="1" stop-color="#ffc331"/></linearGradient></defs><g transform="scale(-1,1) translate(-32,0)"><path d="M27.4,5.5H18.1L16,9.7H4.3V26.5H29.5V5.5Zm0,4.2H19.2l1.1-2.1h7.1Z" fill="#58af7b"/><path d="M20.9,11c-5.1,0-4.8,2.2-4.8,2.2v2.3H21v.7H14.2S11,15.8,11,21s2.9,5,2.9,5h1.7V23.6a2.7,2.7,0,0,1,2.8-2.9h4.8a2.6,2.6,0,0,0,2.7-2.6V13.7S26.2,11,20.9,11Zm-2.7,1.5a.9.9,0,1,1-.8.9.9.9,0,0,1,.8-.9Z" fill="url(#ppa)"/><path d="M21.1,31c5.1,0,4.8-2.2,4.8-2.2V26.5H21v-.7h6.8S31,26.1,31,21s-2.9-5-2.9-5h-1.7v2.4a2.7,2.7,0,0,1-2.8,2.9H18.8a2.6,2.6,0,0,0-2.7,2.6v4.4S15.7,31,21,31Zm2.7-1.5a.9.9,0,1,1,.8-.9.9.9,0,0,1-.8.9Z" fill="url(#ppb)"/></g></svg>';
|
|
443
|
+
const pythonPackageDataUri = `data:image/svg+xml;base64,${btoa(pythonPackageSvg)}`;
|
|
429
444
|
// Inject CSS that overrides icons for .py and .md files
|
|
430
445
|
// Note: Jupytext marks .py and .md files as type="notebook", so we need to
|
|
431
446
|
// use JavaScript to detect and mark these files for CSS targeting
|
|
@@ -581,6 +596,54 @@ const plugin = {
|
|
|
581
596
|
background-repeat: no-repeat;
|
|
582
597
|
background-position: center;
|
|
583
598
|
}
|
|
599
|
+
|
|
600
|
+
/* Override uv.lock file icon with UV icon */
|
|
601
|
+
.jp-DirListing-item[data-uv-lock] .jp-DirListing-itemIcon svg,
|
|
602
|
+
.jp-DirListing-item[data-uv-lock] .jp-DirListing-itemIcon img {
|
|
603
|
+
display: none !important;
|
|
604
|
+
}
|
|
605
|
+
.jp-DirListing-item[data-uv-lock] .jp-DirListing-itemIcon::before {
|
|
606
|
+
content: '';
|
|
607
|
+
display: inline-block;
|
|
608
|
+
width: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
609
|
+
height: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
610
|
+
background-image: url('${uvDataUri}');
|
|
611
|
+
background-size: contain;
|
|
612
|
+
background-repeat: no-repeat;
|
|
613
|
+
background-position: center;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/* Override pytest-related file icons */
|
|
617
|
+
.jp-DirListing-item[data-pytest] .jp-DirListing-itemIcon svg,
|
|
618
|
+
.jp-DirListing-item[data-pytest] .jp-DirListing-itemIcon img {
|
|
619
|
+
display: none !important;
|
|
620
|
+
}
|
|
621
|
+
.jp-DirListing-item[data-pytest] .jp-DirListing-itemIcon::before {
|
|
622
|
+
content: '';
|
|
623
|
+
display: inline-block;
|
|
624
|
+
width: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
625
|
+
height: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
626
|
+
background-image: url('${pytestDataUri}');
|
|
627
|
+
background-size: contain;
|
|
628
|
+
background-repeat: no-repeat;
|
|
629
|
+
background-position: center;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/* Override Python package folder icons */
|
|
633
|
+
.jp-DirListing-item[data-python-package] .jp-DirListing-itemIcon svg,
|
|
634
|
+
.jp-DirListing-item[data-python-package] .jp-DirListing-itemIcon img {
|
|
635
|
+
display: none !important;
|
|
636
|
+
}
|
|
637
|
+
.jp-DirListing-item[data-python-package] .jp-DirListing-itemIcon::before {
|
|
638
|
+
content: '';
|
|
639
|
+
display: inline-block;
|
|
640
|
+
width: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
641
|
+
height: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
642
|
+
background-image: url('${pythonPackageDataUri}');
|
|
643
|
+
background-size: contain;
|
|
644
|
+
background-repeat: no-repeat;
|
|
645
|
+
background-position: center;
|
|
646
|
+
}
|
|
584
647
|
`;
|
|
585
648
|
// Add CSS to make JavaScript and .env icons less bright
|
|
586
649
|
style.textContent += `
|
|
@@ -602,6 +665,69 @@ const plugin = {
|
|
|
602
665
|
opacity: 55% !important;
|
|
603
666
|
}
|
|
604
667
|
`;
|
|
668
|
+
// Cache for Python package folder checks
|
|
669
|
+
const pythonPackageCache = new Map();
|
|
670
|
+
// Folders to exclude from Python package checking
|
|
671
|
+
const excludedFolderPatterns = [
|
|
672
|
+
/^\.git$/,
|
|
673
|
+
/^\.hg$/,
|
|
674
|
+
/^\.svn$/,
|
|
675
|
+
/^\.venv$/,
|
|
676
|
+
/^venv$/,
|
|
677
|
+
/^\.env$/,
|
|
678
|
+
/^env$/,
|
|
679
|
+
/^node_modules$/,
|
|
680
|
+
/^__pycache__$/,
|
|
681
|
+
/^\.ipynb_checkpoints$/,
|
|
682
|
+
/^\.pytest_cache$/,
|
|
683
|
+
/^\.mypy_cache$/,
|
|
684
|
+
/^\.ruff_cache$/,
|
|
685
|
+
/^\.tox$/,
|
|
686
|
+
/^\.nox$/,
|
|
687
|
+
/^\.coverage$/,
|
|
688
|
+
/^htmlcov$/,
|
|
689
|
+
/^dist$/,
|
|
690
|
+
/^build$/,
|
|
691
|
+
/^\.eggs$/,
|
|
692
|
+
/\.egg-info$/,
|
|
693
|
+
/\.dist-info$/
|
|
694
|
+
];
|
|
695
|
+
const isExcludedFolder = (name) => {
|
|
696
|
+
return excludedFolderPatterns.some(pattern => pattern.test(name));
|
|
697
|
+
};
|
|
698
|
+
// Check if a folder is a Python package (contains __init__.py)
|
|
699
|
+
// Use the file browser's model to get the correct path
|
|
700
|
+
const checkPythonPackage = async (folderName) => {
|
|
701
|
+
var _a;
|
|
702
|
+
// Skip excluded folders
|
|
703
|
+
if (isExcludedFolder(folderName)) {
|
|
704
|
+
return false;
|
|
705
|
+
}
|
|
706
|
+
// Get the current path from the file browser model
|
|
707
|
+
if (!defaultFileBrowser) {
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
const currentPath = defaultFileBrowser.model.path;
|
|
711
|
+
const fullPath = currentPath
|
|
712
|
+
? `${currentPath}/${folderName}`
|
|
713
|
+
: folderName;
|
|
714
|
+
if (pythonPackageCache.has(fullPath)) {
|
|
715
|
+
return pythonPackageCache.get(fullPath);
|
|
716
|
+
}
|
|
717
|
+
try {
|
|
718
|
+
// Use JupyterLab's contents manager with the correct path from file browser
|
|
719
|
+
const contents = app.serviceManager.contents;
|
|
720
|
+
const model = await contents.get(fullPath, { content: true });
|
|
721
|
+
const hasInit = ((_a = model.content) === null || _a === void 0 ? void 0 : _a.some((item) => item.name === '__init__.py')) ||
|
|
722
|
+
false;
|
|
723
|
+
pythonPackageCache.set(fullPath, hasInit);
|
|
724
|
+
return hasInit;
|
|
725
|
+
}
|
|
726
|
+
catch (_b) {
|
|
727
|
+
pythonPackageCache.set(fullPath, false);
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
730
|
+
};
|
|
605
731
|
// Add a MutationObserver to mark special files in the file browser
|
|
606
732
|
const markSpecialFiles = () => {
|
|
607
733
|
// Process ALL items - clear wrong attributes and set correct ones
|
|
@@ -652,13 +778,17 @@ const plugin = {
|
|
|
652
778
|
if (nameLower.endsWith('.pdf')) {
|
|
653
779
|
item.setAttribute('data-vscode-pdf', 'true');
|
|
654
780
|
}
|
|
655
|
-
else if (nameLower.endsWith('.doc') ||
|
|
781
|
+
else if (nameLower.endsWith('.doc') ||
|
|
782
|
+
nameLower.endsWith('.docx')) {
|
|
656
783
|
item.setAttribute('data-vscode-word', 'true');
|
|
657
784
|
}
|
|
658
|
-
else if (nameLower.endsWith('.xls') ||
|
|
785
|
+
else if (nameLower.endsWith('.xls') ||
|
|
786
|
+
nameLower.endsWith('.xlsx') ||
|
|
787
|
+
nameLower.endsWith('.xlsm')) {
|
|
659
788
|
item.setAttribute('data-vscode-excel', 'true');
|
|
660
789
|
}
|
|
661
|
-
else if (nameLower.endsWith('.ppt') ||
|
|
790
|
+
else if (nameLower.endsWith('.ppt') ||
|
|
791
|
+
nameLower.endsWith('.pptx')) {
|
|
662
792
|
item.setAttribute('data-vscode-powerpoint', 'true');
|
|
663
793
|
}
|
|
664
794
|
// Force SVG icon for .svg files (override any incorrect file type detection)
|
|
@@ -666,6 +796,36 @@ const plugin = {
|
|
|
666
796
|
if (nameLower.endsWith('.svg')) {
|
|
667
797
|
item.setAttribute('data-vscode-svg-override', 'true');
|
|
668
798
|
}
|
|
799
|
+
// Force UV icon for uv.lock file
|
|
800
|
+
item.removeAttribute('data-uv-lock');
|
|
801
|
+
if (nameLower === 'uv.lock') {
|
|
802
|
+
item.setAttribute('data-uv-lock', 'true');
|
|
803
|
+
}
|
|
804
|
+
// Force pytest icon for pytest-related files
|
|
805
|
+
item.removeAttribute('data-pytest');
|
|
806
|
+
if (nameLower === '.coverage' ||
|
|
807
|
+
nameLower === 'pytest.ini' ||
|
|
808
|
+
nameLower === 'conftest.py') {
|
|
809
|
+
item.setAttribute('data-pytest', 'true');
|
|
810
|
+
}
|
|
811
|
+
// Check if this is a directory (folder)
|
|
812
|
+
const isDir = fileType === 'directory' ||
|
|
813
|
+
item.classList.contains('jp-DirListing-directory');
|
|
814
|
+
if (isDir) {
|
|
815
|
+
// Check if this folder is a Python package (async)
|
|
816
|
+
// Pass just the folder name - checkPythonPackage gets current path from file browser
|
|
817
|
+
checkPythonPackage(name).then(isPythonPackage => {
|
|
818
|
+
if (isPythonPackage) {
|
|
819
|
+
item.setAttribute('data-python-package', 'true');
|
|
820
|
+
}
|
|
821
|
+
else {
|
|
822
|
+
item.removeAttribute('data-python-package');
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
else {
|
|
827
|
+
item.removeAttribute('data-python-package');
|
|
828
|
+
}
|
|
669
829
|
});
|
|
670
830
|
};
|
|
671
831
|
// Watch for changes in the file browser
|
|
@@ -880,6 +1040,9 @@ const plugin = {
|
|
|
880
1040
|
icon: batchIcon
|
|
881
1041
|
});
|
|
882
1042
|
}
|
|
1043
|
+
// Note: uv.lock icon is handled via MutationObserver + CSS override
|
|
1044
|
+
// (see injectIconOverrideCSS function) since pattern-only registration
|
|
1045
|
+
// doesn't work reliably for files without standard extensions
|
|
883
1046
|
};
|
|
884
1047
|
// Debounce timer for settings change alert
|
|
885
1048
|
let settingsChangeTimeout = null;
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
JupyterFrontEndPlugin
|
|
4
4
|
} from '@jupyterlab/application';
|
|
5
5
|
import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
6
|
+
import { IDefaultFileBrowser } from '@jupyterlab/filebrowser';
|
|
6
7
|
import { LabIcon } from '@jupyterlab/ui-components';
|
|
7
8
|
import { getIconSVG } from './icons';
|
|
8
9
|
|
|
@@ -365,7 +366,8 @@ const fileTypeConfigs: IFileTypeConfig[] = [
|
|
|
365
366
|
group: 'enableConfigIcons'
|
|
366
367
|
},
|
|
367
368
|
{
|
|
368
|
-
pattern:
|
|
369
|
+
pattern:
|
|
370
|
+
'^(terraform\\.tfvars\\..*|\\.terraform\\.lock\\..*|\\.terraform\\.tfstate\\.lock\\..*)$',
|
|
369
371
|
extensions: [],
|
|
370
372
|
iconName: 'file-type-terraform',
|
|
371
373
|
group: 'enableConfigIcons'
|
|
@@ -395,16 +397,16 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
395
397
|
description:
|
|
396
398
|
'Jupyterlab extension with a shameless rip-off of the vscode-icons into our beloved environment',
|
|
397
399
|
autoStart: true,
|
|
398
|
-
optional: [ISettingRegistry],
|
|
400
|
+
optional: [ISettingRegistry, IDefaultFileBrowser],
|
|
399
401
|
activate: (
|
|
400
402
|
app: JupyterFrontEnd,
|
|
401
|
-
settingRegistry: ISettingRegistry | null
|
|
403
|
+
settingRegistry: ISettingRegistry | null,
|
|
404
|
+
defaultFileBrowser: IDefaultFileBrowser | null
|
|
402
405
|
) => {
|
|
403
406
|
const { docRegistry } = app;
|
|
404
407
|
|
|
405
408
|
// Function to inject CSS that overrides Jupytext icons
|
|
406
409
|
const injectIconOverrideCSS = () => {
|
|
407
|
-
|
|
408
410
|
// Get icons: Claude (VSCode), Office (VSCode)
|
|
409
411
|
const claudeIcon = createLabIcon('file-type-claude');
|
|
410
412
|
const wordIcon = createLabIcon('file-type-word');
|
|
@@ -470,10 +472,27 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
470
472
|
const claudeDataUri = `data:image/svg+xml;base64,${btoa(claudeSvg)}`;
|
|
471
473
|
const readmeDataUri = `data:image/svg+xml;base64,${btoa(readmeSvg)}`;
|
|
472
474
|
const pdfDataUri = `data:image/svg+xml;base64,${btoa(pdfSvg)}`;
|
|
473
|
-
const wordDataUri = wordSvg
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const
|
|
475
|
+
const wordDataUri = wordSvg
|
|
476
|
+
? `data:image/svg+xml;base64,${btoa(wordSvg)}`
|
|
477
|
+
: '';
|
|
478
|
+
const excelDataUri = excelSvg
|
|
479
|
+
? `data:image/svg+xml;base64,${btoa(excelSvg)}`
|
|
480
|
+
: '';
|
|
481
|
+
const powerpointDataUri = powerpointSvg
|
|
482
|
+
? `data:image/svg+xml;base64,${btoa(powerpointSvg)}`
|
|
483
|
+
: '';
|
|
484
|
+
const svgFileDataUri = svgFileSvg
|
|
485
|
+
? `data:image/svg+xml;base64,${btoa(svgFileSvg)}`
|
|
486
|
+
: '';
|
|
487
|
+
const uvSvg =
|
|
488
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 330 330"><rect height="100%" width="100%" rx="66" fill="#26102f"/><path fill="#d256dc" d="M 65,65 h92 v130 h16 v-130 h92 v200 h-16 v-20 h-8 a20,20 0 0 1 -20,20 h-136 a20,20 0 0 1 -20,-20 z"/></svg>';
|
|
489
|
+
const uvDataUri = `data:image/svg+xml;base64,${btoa(uvSvg)}`;
|
|
490
|
+
const pytestSvg =
|
|
491
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 474 542"><path fill="#696969" d="M21,43h431c12,0 21,9 21,21c0,12-9,21-21,21H21c-12,0-21-9-21-21c0-12,9-21,21-21z"/><path fill="#009fe3" d="M25,0h87v20H25z"/><path fill="#c7d302" d="M138,0h87v20h-87z"/><path fill="#f07e16" d="M250,0h87v20h-87z"/><path fill="#df2815" d="M362,0h87v20h-87z"/><path fill="#df2815" d="M362,107h87v147h-87z"/><path fill="#f07e16" d="M250,107h87v238h-87z"/><path fill="#c7d302" d="M138,107h87v357h-87z"/><path fill="#009fe3" d="M25,107h87v435h-87z"/></svg>';
|
|
492
|
+
const pytestDataUri = `data:image/svg+xml;base64,${btoa(pytestSvg)}`;
|
|
493
|
+
const pythonPackageSvg =
|
|
494
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><defs><linearGradient id="ppa" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0" stop-color="#387eb8"/><stop offset="1" stop-color="#366994"/></linearGradient><linearGradient id="ppb" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0" stop-color="#ffe052"/><stop offset="1" stop-color="#ffc331"/></linearGradient></defs><g transform="scale(-1,1) translate(-32,0)"><path d="M27.4,5.5H18.1L16,9.7H4.3V26.5H29.5V5.5Zm0,4.2H19.2l1.1-2.1h7.1Z" fill="#58af7b"/><path d="M20.9,11c-5.1,0-4.8,2.2-4.8,2.2v2.3H21v.7H14.2S11,15.8,11,21s2.9,5,2.9,5h1.7V23.6a2.7,2.7,0,0,1,2.8-2.9h4.8a2.6,2.6,0,0,0,2.7-2.6V13.7S26.2,11,20.9,11Zm-2.7,1.5a.9.9,0,1,1-.8.9.9.9,0,0,1,.8-.9Z" fill="url(#ppa)"/><path d="M21.1,31c5.1,0,4.8-2.2,4.8-2.2V26.5H21v-.7h6.8S31,26.1,31,21s-2.9-5-2.9-5h-1.7v2.4a2.7,2.7,0,0,1-2.8,2.9H18.8a2.6,2.6,0,0,0-2.7,2.6v4.4S15.7,31,21,31Zm2.7-1.5a.9.9,0,1,1,.8-.9.9.9,0,0,1-.8.9Z" fill="url(#ppb)"/></g></svg>';
|
|
495
|
+
const pythonPackageDataUri = `data:image/svg+xml;base64,${btoa(pythonPackageSvg)}`;
|
|
477
496
|
|
|
478
497
|
// Inject CSS that overrides icons for .py and .md files
|
|
479
498
|
// Note: Jupytext marks .py and .md files as type="notebook", so we need to
|
|
@@ -630,6 +649,54 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
630
649
|
background-repeat: no-repeat;
|
|
631
650
|
background-position: center;
|
|
632
651
|
}
|
|
652
|
+
|
|
653
|
+
/* Override uv.lock file icon with UV icon */
|
|
654
|
+
.jp-DirListing-item[data-uv-lock] .jp-DirListing-itemIcon svg,
|
|
655
|
+
.jp-DirListing-item[data-uv-lock] .jp-DirListing-itemIcon img {
|
|
656
|
+
display: none !important;
|
|
657
|
+
}
|
|
658
|
+
.jp-DirListing-item[data-uv-lock] .jp-DirListing-itemIcon::before {
|
|
659
|
+
content: '';
|
|
660
|
+
display: inline-block;
|
|
661
|
+
width: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
662
|
+
height: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
663
|
+
background-image: url('${uvDataUri}');
|
|
664
|
+
background-size: contain;
|
|
665
|
+
background-repeat: no-repeat;
|
|
666
|
+
background-position: center;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/* Override pytest-related file icons */
|
|
670
|
+
.jp-DirListing-item[data-pytest] .jp-DirListing-itemIcon svg,
|
|
671
|
+
.jp-DirListing-item[data-pytest] .jp-DirListing-itemIcon img {
|
|
672
|
+
display: none !important;
|
|
673
|
+
}
|
|
674
|
+
.jp-DirListing-item[data-pytest] .jp-DirListing-itemIcon::before {
|
|
675
|
+
content: '';
|
|
676
|
+
display: inline-block;
|
|
677
|
+
width: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
678
|
+
height: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
679
|
+
background-image: url('${pytestDataUri}');
|
|
680
|
+
background-size: contain;
|
|
681
|
+
background-repeat: no-repeat;
|
|
682
|
+
background-position: center;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/* Override Python package folder icons */
|
|
686
|
+
.jp-DirListing-item[data-python-package] .jp-DirListing-itemIcon svg,
|
|
687
|
+
.jp-DirListing-item[data-python-package] .jp-DirListing-itemIcon img {
|
|
688
|
+
display: none !important;
|
|
689
|
+
}
|
|
690
|
+
.jp-DirListing-item[data-python-package] .jp-DirListing-itemIcon::before {
|
|
691
|
+
content: '';
|
|
692
|
+
display: inline-block;
|
|
693
|
+
width: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
694
|
+
height: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
695
|
+
background-image: url('${pythonPackageDataUri}');
|
|
696
|
+
background-size: contain;
|
|
697
|
+
background-repeat: no-repeat;
|
|
698
|
+
background-position: center;
|
|
699
|
+
}
|
|
633
700
|
`;
|
|
634
701
|
|
|
635
702
|
// Add CSS to make JavaScript and .env icons less bright
|
|
@@ -653,6 +720,76 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
653
720
|
}
|
|
654
721
|
`;
|
|
655
722
|
|
|
723
|
+
// Cache for Python package folder checks
|
|
724
|
+
const pythonPackageCache: Map<string, boolean> = new Map();
|
|
725
|
+
|
|
726
|
+
// Folders to exclude from Python package checking
|
|
727
|
+
const excludedFolderPatterns = [
|
|
728
|
+
/^\.git$/,
|
|
729
|
+
/^\.hg$/,
|
|
730
|
+
/^\.svn$/,
|
|
731
|
+
/^\.venv$/,
|
|
732
|
+
/^venv$/,
|
|
733
|
+
/^\.env$/,
|
|
734
|
+
/^env$/,
|
|
735
|
+
/^node_modules$/,
|
|
736
|
+
/^__pycache__$/,
|
|
737
|
+
/^\.ipynb_checkpoints$/,
|
|
738
|
+
/^\.pytest_cache$/,
|
|
739
|
+
/^\.mypy_cache$/,
|
|
740
|
+
/^\.ruff_cache$/,
|
|
741
|
+
/^\.tox$/,
|
|
742
|
+
/^\.nox$/,
|
|
743
|
+
/^\.coverage$/,
|
|
744
|
+
/^htmlcov$/,
|
|
745
|
+
/^dist$/,
|
|
746
|
+
/^build$/,
|
|
747
|
+
/^\.eggs$/,
|
|
748
|
+
/\.egg-info$/,
|
|
749
|
+
/\.dist-info$/
|
|
750
|
+
];
|
|
751
|
+
|
|
752
|
+
const isExcludedFolder = (name: string): boolean => {
|
|
753
|
+
return excludedFolderPatterns.some(pattern => pattern.test(name));
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
// Check if a folder is a Python package (contains __init__.py)
|
|
757
|
+
// Use the file browser's model to get the correct path
|
|
758
|
+
const checkPythonPackage = async (
|
|
759
|
+
folderName: string
|
|
760
|
+
): Promise<boolean> => {
|
|
761
|
+
// Skip excluded folders
|
|
762
|
+
if (isExcludedFolder(folderName)) {
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Get the current path from the file browser model
|
|
767
|
+
if (!defaultFileBrowser) {
|
|
768
|
+
return false;
|
|
769
|
+
}
|
|
770
|
+
const currentPath = defaultFileBrowser.model.path;
|
|
771
|
+
const fullPath = currentPath
|
|
772
|
+
? `${currentPath}/${folderName}`
|
|
773
|
+
: folderName;
|
|
774
|
+
|
|
775
|
+
if (pythonPackageCache.has(fullPath)) {
|
|
776
|
+
return pythonPackageCache.get(fullPath)!;
|
|
777
|
+
}
|
|
778
|
+
try {
|
|
779
|
+
// Use JupyterLab's contents manager with the correct path from file browser
|
|
780
|
+
const contents = app.serviceManager.contents;
|
|
781
|
+
const model = await contents.get(fullPath, { content: true });
|
|
782
|
+
const hasInit =
|
|
783
|
+
model.content?.some((item: any) => item.name === '__init__.py') ||
|
|
784
|
+
false;
|
|
785
|
+
pythonPackageCache.set(fullPath, hasInit);
|
|
786
|
+
return hasInit;
|
|
787
|
+
} catch {
|
|
788
|
+
pythonPackageCache.set(fullPath, false);
|
|
789
|
+
return false;
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
|
|
656
793
|
// Add a MutationObserver to mark special files in the file browser
|
|
657
794
|
const markSpecialFiles = () => {
|
|
658
795
|
// Process ALL items - clear wrong attributes and set correct ones
|
|
@@ -707,11 +844,21 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
707
844
|
// Set the correct attribute based on extension
|
|
708
845
|
if (nameLower.endsWith('.pdf')) {
|
|
709
846
|
item.setAttribute('data-vscode-pdf', 'true');
|
|
710
|
-
} else if (
|
|
847
|
+
} else if (
|
|
848
|
+
nameLower.endsWith('.doc') ||
|
|
849
|
+
nameLower.endsWith('.docx')
|
|
850
|
+
) {
|
|
711
851
|
item.setAttribute('data-vscode-word', 'true');
|
|
712
|
-
} else if (
|
|
852
|
+
} else if (
|
|
853
|
+
nameLower.endsWith('.xls') ||
|
|
854
|
+
nameLower.endsWith('.xlsx') ||
|
|
855
|
+
nameLower.endsWith('.xlsm')
|
|
856
|
+
) {
|
|
713
857
|
item.setAttribute('data-vscode-excel', 'true');
|
|
714
|
-
} else if (
|
|
858
|
+
} else if (
|
|
859
|
+
nameLower.endsWith('.ppt') ||
|
|
860
|
+
nameLower.endsWith('.pptx')
|
|
861
|
+
) {
|
|
715
862
|
item.setAttribute('data-vscode-powerpoint', 'true');
|
|
716
863
|
}
|
|
717
864
|
|
|
@@ -720,6 +867,40 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
720
867
|
if (nameLower.endsWith('.svg')) {
|
|
721
868
|
item.setAttribute('data-vscode-svg-override', 'true');
|
|
722
869
|
}
|
|
870
|
+
|
|
871
|
+
// Force UV icon for uv.lock file
|
|
872
|
+
item.removeAttribute('data-uv-lock');
|
|
873
|
+
if (nameLower === 'uv.lock') {
|
|
874
|
+
item.setAttribute('data-uv-lock', 'true');
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// Force pytest icon for pytest-related files
|
|
878
|
+
item.removeAttribute('data-pytest');
|
|
879
|
+
if (
|
|
880
|
+
nameLower === '.coverage' ||
|
|
881
|
+
nameLower === 'pytest.ini' ||
|
|
882
|
+
nameLower === 'conftest.py'
|
|
883
|
+
) {
|
|
884
|
+
item.setAttribute('data-pytest', 'true');
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// Check if this is a directory (folder)
|
|
888
|
+
const isDir =
|
|
889
|
+
fileType === 'directory' ||
|
|
890
|
+
item.classList.contains('jp-DirListing-directory');
|
|
891
|
+
if (isDir) {
|
|
892
|
+
// Check if this folder is a Python package (async)
|
|
893
|
+
// Pass just the folder name - checkPythonPackage gets current path from file browser
|
|
894
|
+
checkPythonPackage(name).then(isPythonPackage => {
|
|
895
|
+
if (isPythonPackage) {
|
|
896
|
+
item.setAttribute('data-python-package', 'true');
|
|
897
|
+
} else {
|
|
898
|
+
item.removeAttribute('data-python-package');
|
|
899
|
+
}
|
|
900
|
+
});
|
|
901
|
+
} else {
|
|
902
|
+
item.removeAttribute('data-python-package');
|
|
903
|
+
}
|
|
723
904
|
});
|
|
724
905
|
};
|
|
725
906
|
|
|
@@ -971,6 +1152,10 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
971
1152
|
icon: batchIcon
|
|
972
1153
|
});
|
|
973
1154
|
}
|
|
1155
|
+
|
|
1156
|
+
// Note: uv.lock icon is handled via MutationObserver + CSS override
|
|
1157
|
+
// (see injectIconOverrideCSS function) since pattern-only registration
|
|
1158
|
+
// doesn't work reliably for files without standard extensions
|
|
974
1159
|
};
|
|
975
1160
|
|
|
976
1161
|
// Debounce timer for settings change alert
|
|
@@ -1000,7 +1185,6 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
1000
1185
|
}
|
|
1001
1186
|
});
|
|
1002
1187
|
|
|
1003
|
-
|
|
1004
1188
|
// Debounce the alert to show only once when multiple settings change
|
|
1005
1189
|
if (settingsChangeTimeout) {
|
|
1006
1190
|
clearTimeout(settingsChangeTimeout);
|
package/style/base.css
CHANGED