jupyterlab_vscode_icons_extension 1.0.117 → 1.1.8
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 +178 -14
- package/package.json +1 -1
- package/src/index.ts +202 -17
- 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 = () => {
|
|
@@ -368,10 +369,10 @@ const plugin = {
|
|
|
368
369
|
const wordIcon = createLabIcon('file-type-word');
|
|
369
370
|
const excelIcon = createLabIcon('file-type-excel');
|
|
370
371
|
const powerpointIcon = createLabIcon('file-type-powerpoint');
|
|
371
|
-
// Custom Markdown icon (from markdown.svg - purple M with arrow,
|
|
372
|
+
// Custom Markdown icon (from markdown.svg - purple M with arrow, #8a2ea5)
|
|
372
373
|
const markdownSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 309 327">
|
|
373
|
-
<path fill="#
|
|
374
|
-
<path fill="#
|
|
374
|
+
<path fill="#8a2ea5" opacity="1" stroke="none" d="m 138.68393,230.48651 c 36.58836,3.1e-4 72.68422,3.1e-4 108.78008,3.1e-4 0.13988,0.49669 0.0821,0.12537 -3.34406,3.81472 -27.34165,24.16766 -54.43119,49.41695 -81.72391,73.62893 -2.65146,2.35216 -4.5582,3.21609 -7.64686,0.37229 -26.89754,-24.76539 -75.191307,-68.40096 -80.889724,-74.12425 -0.744118,-0.74735 -1.274501,-1.57204 -2.95867,-3.69233 23.309236,0 45.299954,0 67.783144,3.3e-4 z"/>
|
|
375
|
+
<path fill="#8a2ea5" d="m 61.156397,14.443673 h 69.176263 q 14.81059,56.661581 23.29958,97.452667 l 5.96036,-27.150338 q 3.61233,-15.870486 7.76652,-30.954008 l 10.6564,-39.348321 H 248.6367 L 276.09047,189.5437 H 221.90541 L 207.27544,69.137838 173.50009,189.5437 H 136.47364 L 101.07273,68.875516 86.984609,189.5437 H 35.147571 Z"/>
|
|
375
376
|
</svg>`;
|
|
376
377
|
// Custom PDF icon (document with folded corner and red PDF banner)
|
|
377
378
|
const pdfSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 16">
|
|
@@ -405,9 +406,10 @@ const plugin = {
|
|
|
405
406
|
<path fill="url(#py-b)" d="M55,0C29,0,29,10,29,12v13h27v4H19C11,29,0,34,0,55c0,20,8,27,16,27h9V69c0-6,3-16,16-16h26c4,0,15-2,15-14V14C82,11,82,0,55,0zM40,8c3,0,5,2,5,5s-2,5-5,5-5-2-5-5S37,8,40,8z"/>
|
|
406
407
|
<path fill="url(#py-y)" d="M55,110c26,0,26-10,26-12V85H54v-4h37c8,0,18-5,18-26 0-23-11-27-16-27h-9v13c0,6-3,16-16,16H42c-4,0-15,2-15,14v24c0,3,0,14,28,14zM70,101c-3,0-5-2-5-5s2-5,5-5 5,2,5,5S73,101,70,101z"/>
|
|
407
408
|
</svg>`;
|
|
408
|
-
// Custom README icon (info icon -
|
|
409
|
+
// Custom README icon (info icon - purple background #912bac, gray "i" #bdbdbd)
|
|
409
410
|
const readmeSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
|
410
|
-
<
|
|
411
|
+
<rect x="9.5" y="9.3" width="82" height="81.2" rx="4" fill="#912bac"/>
|
|
412
|
+
<path fill="#bdbdbd" d="m 45.736283,20.566722 h 11.01003 l 2.116667,2.116667 0,7.619586 -2.116667,2.116667 h -11.01003 l -2.116667,-2.116667 v -7.619586 z m -6.168542,19.478681 h 23.084596 l 2.116667,2.116667 v 5.700733 l -2.116597,2.09951 -3.175139,-0.02574 -2.116597,2.09951 V 67.32187 l 2.116667,2.116667 h 4.211629 l 2.116667,2.116667 v 5.567219 L 63.688967,79.23909 H 38.509408 l -2.116667,-2.116667 v -6.096386 l 2.116667,-2.116667 h 4.968955 L 45.59503,66.792703 V 52.096294 l -2.116667,-2.116667 h -3.910622 l -2.116667,-2.116667 0,-5.70089 z"/>
|
|
411
413
|
</svg>`;
|
|
412
414
|
// Get SVG content from VSCode icons
|
|
413
415
|
const claudeSvg = claudeIcon.svgstr;
|
|
@@ -422,10 +424,24 @@ const plugin = {
|
|
|
422
424
|
const claudeDataUri = `data:image/svg+xml;base64,${btoa(claudeSvg)}`;
|
|
423
425
|
const readmeDataUri = `data:image/svg+xml;base64,${btoa(readmeSvg)}`;
|
|
424
426
|
const pdfDataUri = `data:image/svg+xml;base64,${btoa(pdfSvg)}`;
|
|
425
|
-
const wordDataUri = wordSvg
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
const
|
|
427
|
+
const wordDataUri = wordSvg
|
|
428
|
+
? `data:image/svg+xml;base64,${btoa(wordSvg)}`
|
|
429
|
+
: '';
|
|
430
|
+
const excelDataUri = excelSvg
|
|
431
|
+
? `data:image/svg+xml;base64,${btoa(excelSvg)}`
|
|
432
|
+
: '';
|
|
433
|
+
const powerpointDataUri = powerpointSvg
|
|
434
|
+
? `data:image/svg+xml;base64,${btoa(powerpointSvg)}`
|
|
435
|
+
: '';
|
|
436
|
+
const svgFileDataUri = svgFileSvg
|
|
437
|
+
? `data:image/svg+xml;base64,${btoa(svgFileSvg)}`
|
|
438
|
+
: '';
|
|
439
|
+
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>';
|
|
440
|
+
const uvDataUri = `data:image/svg+xml;base64,${btoa(uvSvg)}`;
|
|
441
|
+
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>';
|
|
442
|
+
const pytestDataUri = `data:image/svg+xml;base64,${btoa(pytestSvg)}`;
|
|
443
|
+
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>';
|
|
444
|
+
const pythonPackageDataUri = `data:image/svg+xml;base64,${btoa(pythonPackageSvg)}`;
|
|
429
445
|
// Inject CSS that overrides icons for .py and .md files
|
|
430
446
|
// Note: Jupytext marks .py and .md files as type="notebook", so we need to
|
|
431
447
|
// use JavaScript to detect and mark these files for CSS targeting
|
|
@@ -581,6 +597,54 @@ const plugin = {
|
|
|
581
597
|
background-repeat: no-repeat;
|
|
582
598
|
background-position: center;
|
|
583
599
|
}
|
|
600
|
+
|
|
601
|
+
/* Override uv.lock file icon with UV icon */
|
|
602
|
+
.jp-DirListing-item[data-uv-lock] .jp-DirListing-itemIcon svg,
|
|
603
|
+
.jp-DirListing-item[data-uv-lock] .jp-DirListing-itemIcon img {
|
|
604
|
+
display: none !important;
|
|
605
|
+
}
|
|
606
|
+
.jp-DirListing-item[data-uv-lock] .jp-DirListing-itemIcon::before {
|
|
607
|
+
content: '';
|
|
608
|
+
display: inline-block;
|
|
609
|
+
width: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
610
|
+
height: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
611
|
+
background-image: url('${uvDataUri}');
|
|
612
|
+
background-size: contain;
|
|
613
|
+
background-repeat: no-repeat;
|
|
614
|
+
background-position: center;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/* Override pytest-related file icons */
|
|
618
|
+
.jp-DirListing-item[data-pytest] .jp-DirListing-itemIcon svg,
|
|
619
|
+
.jp-DirListing-item[data-pytest] .jp-DirListing-itemIcon img {
|
|
620
|
+
display: none !important;
|
|
621
|
+
}
|
|
622
|
+
.jp-DirListing-item[data-pytest] .jp-DirListing-itemIcon::before {
|
|
623
|
+
content: '';
|
|
624
|
+
display: inline-block;
|
|
625
|
+
width: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
626
|
+
height: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
627
|
+
background-image: url('${pytestDataUri}');
|
|
628
|
+
background-size: contain;
|
|
629
|
+
background-repeat: no-repeat;
|
|
630
|
+
background-position: center;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/* Override Python package folder icons */
|
|
634
|
+
.jp-DirListing-item[data-python-package] .jp-DirListing-itemIcon svg,
|
|
635
|
+
.jp-DirListing-item[data-python-package] .jp-DirListing-itemIcon img {
|
|
636
|
+
display: none !important;
|
|
637
|
+
}
|
|
638
|
+
.jp-DirListing-item[data-python-package] .jp-DirListing-itemIcon::before {
|
|
639
|
+
content: '';
|
|
640
|
+
display: inline-block;
|
|
641
|
+
width: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
642
|
+
height: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
643
|
+
background-image: url('${pythonPackageDataUri}');
|
|
644
|
+
background-size: contain;
|
|
645
|
+
background-repeat: no-repeat;
|
|
646
|
+
background-position: center;
|
|
647
|
+
}
|
|
584
648
|
`;
|
|
585
649
|
// Add CSS to make JavaScript and .env icons less bright
|
|
586
650
|
style.textContent += `
|
|
@@ -602,6 +666,69 @@ const plugin = {
|
|
|
602
666
|
opacity: 55% !important;
|
|
603
667
|
}
|
|
604
668
|
`;
|
|
669
|
+
// Cache for Python package folder checks
|
|
670
|
+
const pythonPackageCache = new Map();
|
|
671
|
+
// Folders to exclude from Python package checking
|
|
672
|
+
const excludedFolderPatterns = [
|
|
673
|
+
/^\.git$/,
|
|
674
|
+
/^\.hg$/,
|
|
675
|
+
/^\.svn$/,
|
|
676
|
+
/^\.venv$/,
|
|
677
|
+
/^venv$/,
|
|
678
|
+
/^\.env$/,
|
|
679
|
+
/^env$/,
|
|
680
|
+
/^node_modules$/,
|
|
681
|
+
/^__pycache__$/,
|
|
682
|
+
/^\.ipynb_checkpoints$/,
|
|
683
|
+
/^\.pytest_cache$/,
|
|
684
|
+
/^\.mypy_cache$/,
|
|
685
|
+
/^\.ruff_cache$/,
|
|
686
|
+
/^\.tox$/,
|
|
687
|
+
/^\.nox$/,
|
|
688
|
+
/^\.coverage$/,
|
|
689
|
+
/^htmlcov$/,
|
|
690
|
+
/^dist$/,
|
|
691
|
+
/^build$/,
|
|
692
|
+
/^\.eggs$/,
|
|
693
|
+
/\.egg-info$/,
|
|
694
|
+
/\.dist-info$/
|
|
695
|
+
];
|
|
696
|
+
const isExcludedFolder = (name) => {
|
|
697
|
+
return excludedFolderPatterns.some(pattern => pattern.test(name));
|
|
698
|
+
};
|
|
699
|
+
// Check if a folder is a Python package (contains __init__.py)
|
|
700
|
+
// Use the file browser's model to get the correct path
|
|
701
|
+
const checkPythonPackage = async (folderName) => {
|
|
702
|
+
var _a;
|
|
703
|
+
// Skip excluded folders
|
|
704
|
+
if (isExcludedFolder(folderName)) {
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
// Get the current path from the file browser model
|
|
708
|
+
if (!defaultFileBrowser) {
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
711
|
+
const currentPath = defaultFileBrowser.model.path;
|
|
712
|
+
const fullPath = currentPath
|
|
713
|
+
? `${currentPath}/${folderName}`
|
|
714
|
+
: folderName;
|
|
715
|
+
if (pythonPackageCache.has(fullPath)) {
|
|
716
|
+
return pythonPackageCache.get(fullPath);
|
|
717
|
+
}
|
|
718
|
+
try {
|
|
719
|
+
// Use JupyterLab's contents manager with the correct path from file browser
|
|
720
|
+
const contents = app.serviceManager.contents;
|
|
721
|
+
const model = await contents.get(fullPath, { content: true });
|
|
722
|
+
const hasInit = ((_a = model.content) === null || _a === void 0 ? void 0 : _a.some((item) => item.name === '__init__.py')) ||
|
|
723
|
+
false;
|
|
724
|
+
pythonPackageCache.set(fullPath, hasInit);
|
|
725
|
+
return hasInit;
|
|
726
|
+
}
|
|
727
|
+
catch (_b) {
|
|
728
|
+
pythonPackageCache.set(fullPath, false);
|
|
729
|
+
return false;
|
|
730
|
+
}
|
|
731
|
+
};
|
|
605
732
|
// Add a MutationObserver to mark special files in the file browser
|
|
606
733
|
const markSpecialFiles = () => {
|
|
607
734
|
// Process ALL items - clear wrong attributes and set correct ones
|
|
@@ -652,13 +779,17 @@ const plugin = {
|
|
|
652
779
|
if (nameLower.endsWith('.pdf')) {
|
|
653
780
|
item.setAttribute('data-vscode-pdf', 'true');
|
|
654
781
|
}
|
|
655
|
-
else if (nameLower.endsWith('.doc') ||
|
|
782
|
+
else if (nameLower.endsWith('.doc') ||
|
|
783
|
+
nameLower.endsWith('.docx')) {
|
|
656
784
|
item.setAttribute('data-vscode-word', 'true');
|
|
657
785
|
}
|
|
658
|
-
else if (nameLower.endsWith('.xls') ||
|
|
786
|
+
else if (nameLower.endsWith('.xls') ||
|
|
787
|
+
nameLower.endsWith('.xlsx') ||
|
|
788
|
+
nameLower.endsWith('.xlsm')) {
|
|
659
789
|
item.setAttribute('data-vscode-excel', 'true');
|
|
660
790
|
}
|
|
661
|
-
else if (nameLower.endsWith('.ppt') ||
|
|
791
|
+
else if (nameLower.endsWith('.ppt') ||
|
|
792
|
+
nameLower.endsWith('.pptx')) {
|
|
662
793
|
item.setAttribute('data-vscode-powerpoint', 'true');
|
|
663
794
|
}
|
|
664
795
|
// Force SVG icon for .svg files (override any incorrect file type detection)
|
|
@@ -666,6 +797,36 @@ const plugin = {
|
|
|
666
797
|
if (nameLower.endsWith('.svg')) {
|
|
667
798
|
item.setAttribute('data-vscode-svg-override', 'true');
|
|
668
799
|
}
|
|
800
|
+
// Force UV icon for uv.lock file
|
|
801
|
+
item.removeAttribute('data-uv-lock');
|
|
802
|
+
if (nameLower === 'uv.lock') {
|
|
803
|
+
item.setAttribute('data-uv-lock', 'true');
|
|
804
|
+
}
|
|
805
|
+
// Force pytest icon for pytest-related files
|
|
806
|
+
item.removeAttribute('data-pytest');
|
|
807
|
+
if (nameLower === '.coverage' ||
|
|
808
|
+
nameLower === 'pytest.ini' ||
|
|
809
|
+
nameLower === 'conftest.py') {
|
|
810
|
+
item.setAttribute('data-pytest', 'true');
|
|
811
|
+
}
|
|
812
|
+
// Check if this is a directory (folder)
|
|
813
|
+
const isDir = fileType === 'directory' ||
|
|
814
|
+
item.classList.contains('jp-DirListing-directory');
|
|
815
|
+
if (isDir) {
|
|
816
|
+
// Check if this folder is a Python package (async)
|
|
817
|
+
// Pass just the folder name - checkPythonPackage gets current path from file browser
|
|
818
|
+
checkPythonPackage(name).then(isPythonPackage => {
|
|
819
|
+
if (isPythonPackage) {
|
|
820
|
+
item.setAttribute('data-python-package', 'true');
|
|
821
|
+
}
|
|
822
|
+
else {
|
|
823
|
+
item.removeAttribute('data-python-package');
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
else {
|
|
828
|
+
item.removeAttribute('data-python-package');
|
|
829
|
+
}
|
|
669
830
|
});
|
|
670
831
|
};
|
|
671
832
|
// Watch for changes in the file browser
|
|
@@ -880,6 +1041,9 @@ const plugin = {
|
|
|
880
1041
|
icon: batchIcon
|
|
881
1042
|
});
|
|
882
1043
|
}
|
|
1044
|
+
// Note: uv.lock icon is handled via MutationObserver + CSS override
|
|
1045
|
+
// (see injectIconOverrideCSS function) since pattern-only registration
|
|
1046
|
+
// doesn't work reliably for files without standard extensions
|
|
883
1047
|
};
|
|
884
1048
|
// Debounce timer for settings change alert
|
|
885
1049
|
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,26 +397,26 @@ 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');
|
|
411
413
|
const excelIcon = createLabIcon('file-type-excel');
|
|
412
414
|
const powerpointIcon = createLabIcon('file-type-powerpoint');
|
|
413
415
|
|
|
414
|
-
// Custom Markdown icon (from markdown.svg - purple M with arrow,
|
|
416
|
+
// Custom Markdown icon (from markdown.svg - purple M with arrow, #8a2ea5)
|
|
415
417
|
const markdownSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 309 327">
|
|
416
|
-
<path fill="#
|
|
417
|
-
<path fill="#
|
|
418
|
+
<path fill="#8a2ea5" opacity="1" stroke="none" d="m 138.68393,230.48651 c 36.58836,3.1e-4 72.68422,3.1e-4 108.78008,3.1e-4 0.13988,0.49669 0.0821,0.12537 -3.34406,3.81472 -27.34165,24.16766 -54.43119,49.41695 -81.72391,73.62893 -2.65146,2.35216 -4.5582,3.21609 -7.64686,0.37229 -26.89754,-24.76539 -75.191307,-68.40096 -80.889724,-74.12425 -0.744118,-0.74735 -1.274501,-1.57204 -2.95867,-3.69233 23.309236,0 45.299954,0 67.783144,3.3e-4 z"/>
|
|
419
|
+
<path fill="#8a2ea5" d="m 61.156397,14.443673 h 69.176263 q 14.81059,56.661581 23.29958,97.452667 l 5.96036,-27.150338 q 3.61233,-15.870486 7.76652,-30.954008 l 10.6564,-39.348321 H 248.6367 L 276.09047,189.5437 H 221.90541 L 207.27544,69.137838 173.50009,189.5437 H 136.47364 L 101.07273,68.875516 86.984609,189.5437 H 35.147571 Z"/>
|
|
418
420
|
</svg>`;
|
|
419
421
|
|
|
420
422
|
// Custom PDF icon (document with folded corner and red PDF banner)
|
|
@@ -451,9 +453,10 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
451
453
|
<path fill="url(#py-y)" d="M55,110c26,0,26-10,26-12V85H54v-4h37c8,0,18-5,18-26 0-23-11-27-16-27h-9v13c0,6-3,16-16,16H42c-4,0-15,2-15,14v24c0,3,0,14,28,14zM70,101c-3,0-5-2-5-5s2-5,5-5 5,2,5,5S73,101,70,101z"/>
|
|
452
454
|
</svg>`;
|
|
453
455
|
|
|
454
|
-
// Custom README icon (info icon -
|
|
456
|
+
// Custom README icon (info icon - purple background #912bac, gray "i" #bdbdbd)
|
|
455
457
|
const readmeSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
|
456
|
-
<
|
|
458
|
+
<rect x="9.5" y="9.3" width="82" height="81.2" rx="4" fill="#912bac"/>
|
|
459
|
+
<path fill="#bdbdbd" d="m 45.736283,20.566722 h 11.01003 l 2.116667,2.116667 0,7.619586 -2.116667,2.116667 h -11.01003 l -2.116667,-2.116667 v -7.619586 z m -6.168542,19.478681 h 23.084596 l 2.116667,2.116667 v 5.700733 l -2.116597,2.09951 -3.175139,-0.02574 -2.116597,2.09951 V 67.32187 l 2.116667,2.116667 h 4.211629 l 2.116667,2.116667 v 5.567219 L 63.688967,79.23909 H 38.509408 l -2.116667,-2.116667 v -6.096386 l 2.116667,-2.116667 h 4.968955 L 45.59503,66.792703 V 52.096294 l -2.116667,-2.116667 h -3.910622 l -2.116667,-2.116667 0,-5.70089 z"/>
|
|
457
460
|
</svg>`;
|
|
458
461
|
|
|
459
462
|
// Get SVG content from VSCode icons
|
|
@@ -470,10 +473,27 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
470
473
|
const claudeDataUri = `data:image/svg+xml;base64,${btoa(claudeSvg)}`;
|
|
471
474
|
const readmeDataUri = `data:image/svg+xml;base64,${btoa(readmeSvg)}`;
|
|
472
475
|
const pdfDataUri = `data:image/svg+xml;base64,${btoa(pdfSvg)}`;
|
|
473
|
-
const wordDataUri = wordSvg
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const
|
|
476
|
+
const wordDataUri = wordSvg
|
|
477
|
+
? `data:image/svg+xml;base64,${btoa(wordSvg)}`
|
|
478
|
+
: '';
|
|
479
|
+
const excelDataUri = excelSvg
|
|
480
|
+
? `data:image/svg+xml;base64,${btoa(excelSvg)}`
|
|
481
|
+
: '';
|
|
482
|
+
const powerpointDataUri = powerpointSvg
|
|
483
|
+
? `data:image/svg+xml;base64,${btoa(powerpointSvg)}`
|
|
484
|
+
: '';
|
|
485
|
+
const svgFileDataUri = svgFileSvg
|
|
486
|
+
? `data:image/svg+xml;base64,${btoa(svgFileSvg)}`
|
|
487
|
+
: '';
|
|
488
|
+
const uvSvg =
|
|
489
|
+
'<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>';
|
|
490
|
+
const uvDataUri = `data:image/svg+xml;base64,${btoa(uvSvg)}`;
|
|
491
|
+
const pytestSvg =
|
|
492
|
+
'<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>';
|
|
493
|
+
const pytestDataUri = `data:image/svg+xml;base64,${btoa(pytestSvg)}`;
|
|
494
|
+
const pythonPackageSvg =
|
|
495
|
+
'<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>';
|
|
496
|
+
const pythonPackageDataUri = `data:image/svg+xml;base64,${btoa(pythonPackageSvg)}`;
|
|
477
497
|
|
|
478
498
|
// Inject CSS that overrides icons for .py and .md files
|
|
479
499
|
// Note: Jupytext marks .py and .md files as type="notebook", so we need to
|
|
@@ -630,6 +650,54 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
630
650
|
background-repeat: no-repeat;
|
|
631
651
|
background-position: center;
|
|
632
652
|
}
|
|
653
|
+
|
|
654
|
+
/* Override uv.lock file icon with UV icon */
|
|
655
|
+
.jp-DirListing-item[data-uv-lock] .jp-DirListing-itemIcon svg,
|
|
656
|
+
.jp-DirListing-item[data-uv-lock] .jp-DirListing-itemIcon img {
|
|
657
|
+
display: none !important;
|
|
658
|
+
}
|
|
659
|
+
.jp-DirListing-item[data-uv-lock] .jp-DirListing-itemIcon::before {
|
|
660
|
+
content: '';
|
|
661
|
+
display: inline-block;
|
|
662
|
+
width: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
663
|
+
height: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
664
|
+
background-image: url('${uvDataUri}');
|
|
665
|
+
background-size: contain;
|
|
666
|
+
background-repeat: no-repeat;
|
|
667
|
+
background-position: center;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/* Override pytest-related file icons */
|
|
671
|
+
.jp-DirListing-item[data-pytest] .jp-DirListing-itemIcon svg,
|
|
672
|
+
.jp-DirListing-item[data-pytest] .jp-DirListing-itemIcon img {
|
|
673
|
+
display: none !important;
|
|
674
|
+
}
|
|
675
|
+
.jp-DirListing-item[data-pytest] .jp-DirListing-itemIcon::before {
|
|
676
|
+
content: '';
|
|
677
|
+
display: inline-block;
|
|
678
|
+
width: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
679
|
+
height: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
680
|
+
background-image: url('${pytestDataUri}');
|
|
681
|
+
background-size: contain;
|
|
682
|
+
background-repeat: no-repeat;
|
|
683
|
+
background-position: center;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/* Override Python package folder icons */
|
|
687
|
+
.jp-DirListing-item[data-python-package] .jp-DirListing-itemIcon svg,
|
|
688
|
+
.jp-DirListing-item[data-python-package] .jp-DirListing-itemIcon img {
|
|
689
|
+
display: none !important;
|
|
690
|
+
}
|
|
691
|
+
.jp-DirListing-item[data-python-package] .jp-DirListing-itemIcon::before {
|
|
692
|
+
content: '';
|
|
693
|
+
display: inline-block;
|
|
694
|
+
width: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
695
|
+
height: calc(var(--jp-ui-font-size1, 13px) * var(--jp-custom-icon-scale, 1.5));
|
|
696
|
+
background-image: url('${pythonPackageDataUri}');
|
|
697
|
+
background-size: contain;
|
|
698
|
+
background-repeat: no-repeat;
|
|
699
|
+
background-position: center;
|
|
700
|
+
}
|
|
633
701
|
`;
|
|
634
702
|
|
|
635
703
|
// Add CSS to make JavaScript and .env icons less bright
|
|
@@ -653,6 +721,76 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
653
721
|
}
|
|
654
722
|
`;
|
|
655
723
|
|
|
724
|
+
// Cache for Python package folder checks
|
|
725
|
+
const pythonPackageCache: Map<string, boolean> = new Map();
|
|
726
|
+
|
|
727
|
+
// Folders to exclude from Python package checking
|
|
728
|
+
const excludedFolderPatterns = [
|
|
729
|
+
/^\.git$/,
|
|
730
|
+
/^\.hg$/,
|
|
731
|
+
/^\.svn$/,
|
|
732
|
+
/^\.venv$/,
|
|
733
|
+
/^venv$/,
|
|
734
|
+
/^\.env$/,
|
|
735
|
+
/^env$/,
|
|
736
|
+
/^node_modules$/,
|
|
737
|
+
/^__pycache__$/,
|
|
738
|
+
/^\.ipynb_checkpoints$/,
|
|
739
|
+
/^\.pytest_cache$/,
|
|
740
|
+
/^\.mypy_cache$/,
|
|
741
|
+
/^\.ruff_cache$/,
|
|
742
|
+
/^\.tox$/,
|
|
743
|
+
/^\.nox$/,
|
|
744
|
+
/^\.coverage$/,
|
|
745
|
+
/^htmlcov$/,
|
|
746
|
+
/^dist$/,
|
|
747
|
+
/^build$/,
|
|
748
|
+
/^\.eggs$/,
|
|
749
|
+
/\.egg-info$/,
|
|
750
|
+
/\.dist-info$/
|
|
751
|
+
];
|
|
752
|
+
|
|
753
|
+
const isExcludedFolder = (name: string): boolean => {
|
|
754
|
+
return excludedFolderPatterns.some(pattern => pattern.test(name));
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
// Check if a folder is a Python package (contains __init__.py)
|
|
758
|
+
// Use the file browser's model to get the correct path
|
|
759
|
+
const checkPythonPackage = async (
|
|
760
|
+
folderName: string
|
|
761
|
+
): Promise<boolean> => {
|
|
762
|
+
// Skip excluded folders
|
|
763
|
+
if (isExcludedFolder(folderName)) {
|
|
764
|
+
return false;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Get the current path from the file browser model
|
|
768
|
+
if (!defaultFileBrowser) {
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
const currentPath = defaultFileBrowser.model.path;
|
|
772
|
+
const fullPath = currentPath
|
|
773
|
+
? `${currentPath}/${folderName}`
|
|
774
|
+
: folderName;
|
|
775
|
+
|
|
776
|
+
if (pythonPackageCache.has(fullPath)) {
|
|
777
|
+
return pythonPackageCache.get(fullPath)!;
|
|
778
|
+
}
|
|
779
|
+
try {
|
|
780
|
+
// Use JupyterLab's contents manager with the correct path from file browser
|
|
781
|
+
const contents = app.serviceManager.contents;
|
|
782
|
+
const model = await contents.get(fullPath, { content: true });
|
|
783
|
+
const hasInit =
|
|
784
|
+
model.content?.some((item: any) => item.name === '__init__.py') ||
|
|
785
|
+
false;
|
|
786
|
+
pythonPackageCache.set(fullPath, hasInit);
|
|
787
|
+
return hasInit;
|
|
788
|
+
} catch {
|
|
789
|
+
pythonPackageCache.set(fullPath, false);
|
|
790
|
+
return false;
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
|
|
656
794
|
// Add a MutationObserver to mark special files in the file browser
|
|
657
795
|
const markSpecialFiles = () => {
|
|
658
796
|
// Process ALL items - clear wrong attributes and set correct ones
|
|
@@ -707,11 +845,21 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
707
845
|
// Set the correct attribute based on extension
|
|
708
846
|
if (nameLower.endsWith('.pdf')) {
|
|
709
847
|
item.setAttribute('data-vscode-pdf', 'true');
|
|
710
|
-
} else if (
|
|
848
|
+
} else if (
|
|
849
|
+
nameLower.endsWith('.doc') ||
|
|
850
|
+
nameLower.endsWith('.docx')
|
|
851
|
+
) {
|
|
711
852
|
item.setAttribute('data-vscode-word', 'true');
|
|
712
|
-
} else if (
|
|
853
|
+
} else if (
|
|
854
|
+
nameLower.endsWith('.xls') ||
|
|
855
|
+
nameLower.endsWith('.xlsx') ||
|
|
856
|
+
nameLower.endsWith('.xlsm')
|
|
857
|
+
) {
|
|
713
858
|
item.setAttribute('data-vscode-excel', 'true');
|
|
714
|
-
} else if (
|
|
859
|
+
} else if (
|
|
860
|
+
nameLower.endsWith('.ppt') ||
|
|
861
|
+
nameLower.endsWith('.pptx')
|
|
862
|
+
) {
|
|
715
863
|
item.setAttribute('data-vscode-powerpoint', 'true');
|
|
716
864
|
}
|
|
717
865
|
|
|
@@ -720,6 +868,40 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
720
868
|
if (nameLower.endsWith('.svg')) {
|
|
721
869
|
item.setAttribute('data-vscode-svg-override', 'true');
|
|
722
870
|
}
|
|
871
|
+
|
|
872
|
+
// Force UV icon for uv.lock file
|
|
873
|
+
item.removeAttribute('data-uv-lock');
|
|
874
|
+
if (nameLower === 'uv.lock') {
|
|
875
|
+
item.setAttribute('data-uv-lock', 'true');
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// Force pytest icon for pytest-related files
|
|
879
|
+
item.removeAttribute('data-pytest');
|
|
880
|
+
if (
|
|
881
|
+
nameLower === '.coverage' ||
|
|
882
|
+
nameLower === 'pytest.ini' ||
|
|
883
|
+
nameLower === 'conftest.py'
|
|
884
|
+
) {
|
|
885
|
+
item.setAttribute('data-pytest', 'true');
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// Check if this is a directory (folder)
|
|
889
|
+
const isDir =
|
|
890
|
+
fileType === 'directory' ||
|
|
891
|
+
item.classList.contains('jp-DirListing-directory');
|
|
892
|
+
if (isDir) {
|
|
893
|
+
// Check if this folder is a Python package (async)
|
|
894
|
+
// Pass just the folder name - checkPythonPackage gets current path from file browser
|
|
895
|
+
checkPythonPackage(name).then(isPythonPackage => {
|
|
896
|
+
if (isPythonPackage) {
|
|
897
|
+
item.setAttribute('data-python-package', 'true');
|
|
898
|
+
} else {
|
|
899
|
+
item.removeAttribute('data-python-package');
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
} else {
|
|
903
|
+
item.removeAttribute('data-python-package');
|
|
904
|
+
}
|
|
723
905
|
});
|
|
724
906
|
};
|
|
725
907
|
|
|
@@ -971,6 +1153,10 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
971
1153
|
icon: batchIcon
|
|
972
1154
|
});
|
|
973
1155
|
}
|
|
1156
|
+
|
|
1157
|
+
// Note: uv.lock icon is handled via MutationObserver + CSS override
|
|
1158
|
+
// (see injectIconOverrideCSS function) since pattern-only registration
|
|
1159
|
+
// doesn't work reliably for files without standard extensions
|
|
974
1160
|
};
|
|
975
1161
|
|
|
976
1162
|
// Debounce timer for settings change alert
|
|
@@ -1000,7 +1186,6 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
1000
1186
|
}
|
|
1001
1187
|
});
|
|
1002
1188
|
|
|
1003
|
-
|
|
1004
1189
|
// Debounce the alert to show only once when multiple settings change
|
|
1005
1190
|
if (settingsChangeTimeout) {
|
|
1006
1191
|
clearTimeout(settingsChangeTimeout);
|
package/style/base.css
CHANGED