jupyterlab_vscode_icons_extension 1.0.112 → 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 +214 -45
- package/package.json +1 -1
- package/src/index.ts +244 -46
- 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';
|
|
@@ -126,16 +127,8 @@ const fileTypeConfigs = [
|
|
|
126
127
|
iconName: 'file-type-perl',
|
|
127
128
|
group: 'enableLanguageIcons'
|
|
128
129
|
},
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
iconName: 'file-type-shell',
|
|
132
|
-
group: 'enableLanguageIcons'
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
extensions: ['.bat', '.cmd'],
|
|
136
|
-
iconName: 'file-type-shell',
|
|
137
|
-
group: 'enableLanguageIcons'
|
|
138
|
-
},
|
|
130
|
+
// Shell scripts (.sh, .bash, .zsh) and batch files (.bat, .cmd) use custom icons with black backgrounds
|
|
131
|
+
// Registered separately below with custom SVGs
|
|
139
132
|
{
|
|
140
133
|
extensions: ['.ps1'],
|
|
141
134
|
iconName: 'file-type-powershell',
|
|
@@ -366,8 +359,8 @@ const plugin = {
|
|
|
366
359
|
id: PLUGIN_ID,
|
|
367
360
|
description: 'Jupyterlab extension with a shameless rip-off of the vscode-icons into our beloved environment',
|
|
368
361
|
autoStart: true,
|
|
369
|
-
optional: [ISettingRegistry],
|
|
370
|
-
activate: (app, settingRegistry) => {
|
|
362
|
+
optional: [ISettingRegistry, IDefaultFileBrowser],
|
|
363
|
+
activate: (app, settingRegistry, defaultFileBrowser) => {
|
|
371
364
|
const { docRegistry } = app;
|
|
372
365
|
// Function to inject CSS that overrides Jupytext icons
|
|
373
366
|
const injectIconOverrideCSS = () => {
|
|
@@ -430,10 +423,24 @@ const plugin = {
|
|
|
430
423
|
const claudeDataUri = `data:image/svg+xml;base64,${btoa(claudeSvg)}`;
|
|
431
424
|
const readmeDataUri = `data:image/svg+xml;base64,${btoa(readmeSvg)}`;
|
|
432
425
|
const pdfDataUri = `data:image/svg+xml;base64,${btoa(pdfSvg)}`;
|
|
433
|
-
const wordDataUri = wordSvg
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
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)}`;
|
|
437
444
|
// Inject CSS that overrides icons for .py and .md files
|
|
438
445
|
// Note: Jupytext marks .py and .md files as type="notebook", so we need to
|
|
439
446
|
// use JavaScript to detect and mark these files for CSS targeting
|
|
@@ -589,6 +596,54 @@ const plugin = {
|
|
|
589
596
|
background-repeat: no-repeat;
|
|
590
597
|
background-position: center;
|
|
591
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
|
+
}
|
|
592
647
|
`;
|
|
593
648
|
// Add CSS to make JavaScript and .env icons less bright
|
|
594
649
|
style.textContent += `
|
|
@@ -604,21 +659,75 @@ const plugin = {
|
|
|
604
659
|
filter: brightness(0.85) saturate(0.75);
|
|
605
660
|
}
|
|
606
661
|
|
|
607
|
-
/* Color shell script icons - JupyterLab orange for Linux shells (.sh, .bash, .zsh) */
|
|
608
|
-
.jp-DirListing-item[data-file-type="vscode-file-type-shell"][data-shell-type="linux"] .jp-DirListing-itemIcon svg {
|
|
609
|
-
filter: brightness(0) saturate(100%) invert(58%) sepia(76%) saturate(3113%) hue-rotate(1deg) brightness(101%) contrast(101%);
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
/* Color shell script icons - pale blue for Windows shells (.bat, .cmd) */
|
|
613
|
-
.jp-DirListing-item[data-file-type="vscode-file-type-shell"][data-shell-type="windows"] .jp-DirListing-itemIcon svg {
|
|
614
|
-
filter: hue-rotate(180deg) saturate(0.6) brightness(1.2);
|
|
615
|
-
}
|
|
616
662
|
|
|
617
663
|
/* Make hidden items darker (items starting with .) */
|
|
618
664
|
.jp-DirListing-item[data-is-dot] {
|
|
619
665
|
opacity: 55% !important;
|
|
620
666
|
}
|
|
621
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
|
+
};
|
|
622
731
|
// Add a MutationObserver to mark special files in the file browser
|
|
623
732
|
const markSpecialFiles = () => {
|
|
624
733
|
// Process ALL items - clear wrong attributes and set correct ones
|
|
@@ -658,23 +767,6 @@ const plugin = {
|
|
|
658
767
|
item.removeAttribute('data-jupytext-py');
|
|
659
768
|
item.removeAttribute('data-jupytext-md');
|
|
660
769
|
}
|
|
661
|
-
// Handle shell script files - ONLY set attribute if BOTH conditions match
|
|
662
|
-
if (fileType === 'vscode-file-type-shell') {
|
|
663
|
-
if (name.endsWith('.sh') || name.endsWith('.bash') || name.endsWith('.zsh')) {
|
|
664
|
-
item.setAttribute('data-shell-type', 'linux');
|
|
665
|
-
}
|
|
666
|
-
else if (name.endsWith('.bat') || name.endsWith('.cmd')) {
|
|
667
|
-
item.setAttribute('data-shell-type', 'windows');
|
|
668
|
-
}
|
|
669
|
-
else {
|
|
670
|
-
// Shell file type but wrong extension - clear attribute
|
|
671
|
-
item.removeAttribute('data-shell-type');
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
else {
|
|
675
|
-
// Not a shell file - always clear shell-type attribute
|
|
676
|
-
item.removeAttribute('data-shell-type');
|
|
677
|
-
}
|
|
678
770
|
// Handle PDF and Office files by extension (override native JupyterLab icons)
|
|
679
771
|
const nameLower = name.toLowerCase();
|
|
680
772
|
// Clear all office/pdf attributes first
|
|
@@ -686,13 +778,17 @@ const plugin = {
|
|
|
686
778
|
if (nameLower.endsWith('.pdf')) {
|
|
687
779
|
item.setAttribute('data-vscode-pdf', 'true');
|
|
688
780
|
}
|
|
689
|
-
else if (nameLower.endsWith('.doc') ||
|
|
781
|
+
else if (nameLower.endsWith('.doc') ||
|
|
782
|
+
nameLower.endsWith('.docx')) {
|
|
690
783
|
item.setAttribute('data-vscode-word', 'true');
|
|
691
784
|
}
|
|
692
|
-
else if (nameLower.endsWith('.xls') ||
|
|
785
|
+
else if (nameLower.endsWith('.xls') ||
|
|
786
|
+
nameLower.endsWith('.xlsx') ||
|
|
787
|
+
nameLower.endsWith('.xlsm')) {
|
|
693
788
|
item.setAttribute('data-vscode-excel', 'true');
|
|
694
789
|
}
|
|
695
|
-
else if (nameLower.endsWith('.ppt') ||
|
|
790
|
+
else if (nameLower.endsWith('.ppt') ||
|
|
791
|
+
nameLower.endsWith('.pptx')) {
|
|
696
792
|
item.setAttribute('data-vscode-powerpoint', 'true');
|
|
697
793
|
}
|
|
698
794
|
// Force SVG icon for .svg files (override any incorrect file type detection)
|
|
@@ -700,6 +796,36 @@ const plugin = {
|
|
|
700
796
|
if (nameLower.endsWith('.svg')) {
|
|
701
797
|
item.setAttribute('data-vscode-svg-override', 'true');
|
|
702
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
|
+
}
|
|
703
829
|
});
|
|
704
830
|
};
|
|
705
831
|
// Watch for changes in the file browser
|
|
@@ -874,6 +1000,49 @@ const plugin = {
|
|
|
874
1000
|
icon: mcpIcon
|
|
875
1001
|
});
|
|
876
1002
|
}
|
|
1003
|
+
// Register shell scripts with custom black background and desaturated orange icon
|
|
1004
|
+
if (settings.enableLanguageIcons) {
|
|
1005
|
+
const shellSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
1006
|
+
<rect x="1" y="3" width="30" height="26" rx="2" fill="#1a1a1a"/>
|
|
1007
|
+
<path fill="#e8b070" d="M29.4 27.6H2.5V4.5h26.9Zm-25.9-1h24.9V5.5H3.5Z"/>
|
|
1008
|
+
<path fill="#e8b070" d="m6.077 19.316l-.555-.832l4.844-3.229l-4.887-4.071l.641-.768l5.915 4.928zM12.7 18.2h7.8v1h-7.8zM2.5 5.5h26.9v1.9H2.5z"/>
|
|
1009
|
+
</svg>`;
|
|
1010
|
+
const shellIcon = new LabIcon({
|
|
1011
|
+
name: 'shell-icon',
|
|
1012
|
+
svgstr: shellSvg
|
|
1013
|
+
});
|
|
1014
|
+
docRegistry.addFileType({
|
|
1015
|
+
name: 'vscode-shell',
|
|
1016
|
+
displayName: 'Shell Script',
|
|
1017
|
+
extensions: ['.sh', '.bash', '.zsh'],
|
|
1018
|
+
fileFormat: 'text',
|
|
1019
|
+
contentType: 'file',
|
|
1020
|
+
icon: shellIcon
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
// Register batch files with custom black background and desaturated blue icon
|
|
1024
|
+
if (settings.enableLanguageIcons) {
|
|
1025
|
+
const batchSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
1026
|
+
<rect x="1" y="3" width="30" height="26" rx="2" fill="#1a1a1a"/>
|
|
1027
|
+
<path fill="#80c8f0" d="M29.4 27.6H2.5V4.5h26.9Zm-25.9-1h24.9V5.5H3.5Z"/>
|
|
1028
|
+
<path fill="#80c8f0" d="m6.077 19.316l-.555-.832l4.844-3.229l-4.887-4.071l.641-.768l5.915 4.928zM12.7 18.2h7.8v1h-7.8zM2.5 5.5h26.9v1.9H2.5z"/>
|
|
1029
|
+
</svg>`;
|
|
1030
|
+
const batchIcon = new LabIcon({
|
|
1031
|
+
name: 'batch-icon',
|
|
1032
|
+
svgstr: batchSvg
|
|
1033
|
+
});
|
|
1034
|
+
docRegistry.addFileType({
|
|
1035
|
+
name: 'vscode-batch',
|
|
1036
|
+
displayName: 'Batch File',
|
|
1037
|
+
extensions: ['.bat', '.cmd'],
|
|
1038
|
+
fileFormat: 'text',
|
|
1039
|
+
contentType: 'file',
|
|
1040
|
+
icon: batchIcon
|
|
1041
|
+
});
|
|
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
|
|
877
1046
|
};
|
|
878
1047
|
// Debounce timer for settings change alert
|
|
879
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
|
|
|
@@ -155,16 +156,8 @@ const fileTypeConfigs: IFileTypeConfig[] = [
|
|
|
155
156
|
iconName: 'file-type-perl',
|
|
156
157
|
group: 'enableLanguageIcons'
|
|
157
158
|
},
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
iconName: 'file-type-shell',
|
|
161
|
-
group: 'enableLanguageIcons'
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
extensions: ['.bat', '.cmd'],
|
|
165
|
-
iconName: 'file-type-shell',
|
|
166
|
-
group: 'enableLanguageIcons'
|
|
167
|
-
},
|
|
159
|
+
// Shell scripts (.sh, .bash, .zsh) and batch files (.bat, .cmd) use custom icons with black backgrounds
|
|
160
|
+
// Registered separately below with custom SVGs
|
|
168
161
|
{
|
|
169
162
|
extensions: ['.ps1'],
|
|
170
163
|
iconName: 'file-type-powershell',
|
|
@@ -373,7 +366,8 @@ const fileTypeConfigs: IFileTypeConfig[] = [
|
|
|
373
366
|
group: 'enableConfigIcons'
|
|
374
367
|
},
|
|
375
368
|
{
|
|
376
|
-
pattern:
|
|
369
|
+
pattern:
|
|
370
|
+
'^(terraform\\.tfvars\\..*|\\.terraform\\.lock\\..*|\\.terraform\\.tfstate\\.lock\\..*)$',
|
|
377
371
|
extensions: [],
|
|
378
372
|
iconName: 'file-type-terraform',
|
|
379
373
|
group: 'enableConfigIcons'
|
|
@@ -403,16 +397,16 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
403
397
|
description:
|
|
404
398
|
'Jupyterlab extension with a shameless rip-off of the vscode-icons into our beloved environment',
|
|
405
399
|
autoStart: true,
|
|
406
|
-
optional: [ISettingRegistry],
|
|
400
|
+
optional: [ISettingRegistry, IDefaultFileBrowser],
|
|
407
401
|
activate: (
|
|
408
402
|
app: JupyterFrontEnd,
|
|
409
|
-
settingRegistry: ISettingRegistry | null
|
|
403
|
+
settingRegistry: ISettingRegistry | null,
|
|
404
|
+
defaultFileBrowser: IDefaultFileBrowser | null
|
|
410
405
|
) => {
|
|
411
406
|
const { docRegistry } = app;
|
|
412
407
|
|
|
413
408
|
// Function to inject CSS that overrides Jupytext icons
|
|
414
409
|
const injectIconOverrideCSS = () => {
|
|
415
|
-
|
|
416
410
|
// Get icons: Claude (VSCode), Office (VSCode)
|
|
417
411
|
const claudeIcon = createLabIcon('file-type-claude');
|
|
418
412
|
const wordIcon = createLabIcon('file-type-word');
|
|
@@ -478,10 +472,27 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
478
472
|
const claudeDataUri = `data:image/svg+xml;base64,${btoa(claudeSvg)}`;
|
|
479
473
|
const readmeDataUri = `data:image/svg+xml;base64,${btoa(readmeSvg)}`;
|
|
480
474
|
const pdfDataUri = `data:image/svg+xml;base64,${btoa(pdfSvg)}`;
|
|
481
|
-
const wordDataUri = wordSvg
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
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)}`;
|
|
485
496
|
|
|
486
497
|
// Inject CSS that overrides icons for .py and .md files
|
|
487
498
|
// Note: Jupytext marks .py and .md files as type="notebook", so we need to
|
|
@@ -638,6 +649,54 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
638
649
|
background-repeat: no-repeat;
|
|
639
650
|
background-position: center;
|
|
640
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
|
+
}
|
|
641
700
|
`;
|
|
642
701
|
|
|
643
702
|
// Add CSS to make JavaScript and .env icons less bright
|
|
@@ -654,15 +713,6 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
654
713
|
filter: brightness(0.85) saturate(0.75);
|
|
655
714
|
}
|
|
656
715
|
|
|
657
|
-
/* Color shell script icons - JupyterLab orange for Linux shells (.sh, .bash, .zsh) */
|
|
658
|
-
.jp-DirListing-item[data-file-type="vscode-file-type-shell"][data-shell-type="linux"] .jp-DirListing-itemIcon svg {
|
|
659
|
-
filter: brightness(0) saturate(100%) invert(58%) sepia(76%) saturate(3113%) hue-rotate(1deg) brightness(101%) contrast(101%);
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
/* Color shell script icons - pale blue for Windows shells (.bat, .cmd) */
|
|
663
|
-
.jp-DirListing-item[data-file-type="vscode-file-type-shell"][data-shell-type="windows"] .jp-DirListing-itemIcon svg {
|
|
664
|
-
filter: hue-rotate(180deg) saturate(0.6) brightness(1.2);
|
|
665
|
-
}
|
|
666
716
|
|
|
667
717
|
/* Make hidden items darker (items starting with .) */
|
|
668
718
|
.jp-DirListing-item[data-is-dot] {
|
|
@@ -670,6 +720,76 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
670
720
|
}
|
|
671
721
|
`;
|
|
672
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
|
+
|
|
673
793
|
// Add a MutationObserver to mark special files in the file browser
|
|
674
794
|
const markSpecialFiles = () => {
|
|
675
795
|
// Process ALL items - clear wrong attributes and set correct ones
|
|
@@ -712,21 +832,6 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
712
832
|
item.removeAttribute('data-jupytext-md');
|
|
713
833
|
}
|
|
714
834
|
|
|
715
|
-
// Handle shell script files - ONLY set attribute if BOTH conditions match
|
|
716
|
-
if (fileType === 'vscode-file-type-shell') {
|
|
717
|
-
if (name.endsWith('.sh') || name.endsWith('.bash') || name.endsWith('.zsh')) {
|
|
718
|
-
item.setAttribute('data-shell-type', 'linux');
|
|
719
|
-
} else if (name.endsWith('.bat') || name.endsWith('.cmd')) {
|
|
720
|
-
item.setAttribute('data-shell-type', 'windows');
|
|
721
|
-
} else {
|
|
722
|
-
// Shell file type but wrong extension - clear attribute
|
|
723
|
-
item.removeAttribute('data-shell-type');
|
|
724
|
-
}
|
|
725
|
-
} else {
|
|
726
|
-
// Not a shell file - always clear shell-type attribute
|
|
727
|
-
item.removeAttribute('data-shell-type');
|
|
728
|
-
}
|
|
729
|
-
|
|
730
835
|
// Handle PDF and Office files by extension (override native JupyterLab icons)
|
|
731
836
|
const nameLower = name.toLowerCase();
|
|
732
837
|
|
|
@@ -739,11 +844,21 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
739
844
|
// Set the correct attribute based on extension
|
|
740
845
|
if (nameLower.endsWith('.pdf')) {
|
|
741
846
|
item.setAttribute('data-vscode-pdf', 'true');
|
|
742
|
-
} else if (
|
|
847
|
+
} else if (
|
|
848
|
+
nameLower.endsWith('.doc') ||
|
|
849
|
+
nameLower.endsWith('.docx')
|
|
850
|
+
) {
|
|
743
851
|
item.setAttribute('data-vscode-word', 'true');
|
|
744
|
-
} else if (
|
|
852
|
+
} else if (
|
|
853
|
+
nameLower.endsWith('.xls') ||
|
|
854
|
+
nameLower.endsWith('.xlsx') ||
|
|
855
|
+
nameLower.endsWith('.xlsm')
|
|
856
|
+
) {
|
|
745
857
|
item.setAttribute('data-vscode-excel', 'true');
|
|
746
|
-
} else if (
|
|
858
|
+
} else if (
|
|
859
|
+
nameLower.endsWith('.ppt') ||
|
|
860
|
+
nameLower.endsWith('.pptx')
|
|
861
|
+
) {
|
|
747
862
|
item.setAttribute('data-vscode-powerpoint', 'true');
|
|
748
863
|
}
|
|
749
864
|
|
|
@@ -752,6 +867,40 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
752
867
|
if (nameLower.endsWith('.svg')) {
|
|
753
868
|
item.setAttribute('data-vscode-svg-override', 'true');
|
|
754
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
|
+
}
|
|
755
904
|
});
|
|
756
905
|
};
|
|
757
906
|
|
|
@@ -957,6 +1106,56 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
957
1106
|
icon: mcpIcon
|
|
958
1107
|
});
|
|
959
1108
|
}
|
|
1109
|
+
|
|
1110
|
+
// Register shell scripts with custom black background and desaturated orange icon
|
|
1111
|
+
if (settings.enableLanguageIcons) {
|
|
1112
|
+
const shellSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
1113
|
+
<rect x="1" y="3" width="30" height="26" rx="2" fill="#1a1a1a"/>
|
|
1114
|
+
<path fill="#e8b070" d="M29.4 27.6H2.5V4.5h26.9Zm-25.9-1h24.9V5.5H3.5Z"/>
|
|
1115
|
+
<path fill="#e8b070" d="m6.077 19.316l-.555-.832l4.844-3.229l-4.887-4.071l.641-.768l5.915 4.928zM12.7 18.2h7.8v1h-7.8zM2.5 5.5h26.9v1.9H2.5z"/>
|
|
1116
|
+
</svg>`;
|
|
1117
|
+
|
|
1118
|
+
const shellIcon = new LabIcon({
|
|
1119
|
+
name: 'shell-icon',
|
|
1120
|
+
svgstr: shellSvg
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
docRegistry.addFileType({
|
|
1124
|
+
name: 'vscode-shell',
|
|
1125
|
+
displayName: 'Shell Script',
|
|
1126
|
+
extensions: ['.sh', '.bash', '.zsh'],
|
|
1127
|
+
fileFormat: 'text',
|
|
1128
|
+
contentType: 'file',
|
|
1129
|
+
icon: shellIcon
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// Register batch files with custom black background and desaturated blue icon
|
|
1134
|
+
if (settings.enableLanguageIcons) {
|
|
1135
|
+
const batchSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
1136
|
+
<rect x="1" y="3" width="30" height="26" rx="2" fill="#1a1a1a"/>
|
|
1137
|
+
<path fill="#80c8f0" d="M29.4 27.6H2.5V4.5h26.9Zm-25.9-1h24.9V5.5H3.5Z"/>
|
|
1138
|
+
<path fill="#80c8f0" d="m6.077 19.316l-.555-.832l4.844-3.229l-4.887-4.071l.641-.768l5.915 4.928zM12.7 18.2h7.8v1h-7.8zM2.5 5.5h26.9v1.9H2.5z"/>
|
|
1139
|
+
</svg>`;
|
|
1140
|
+
|
|
1141
|
+
const batchIcon = new LabIcon({
|
|
1142
|
+
name: 'batch-icon',
|
|
1143
|
+
svgstr: batchSvg
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
docRegistry.addFileType({
|
|
1147
|
+
name: 'vscode-batch',
|
|
1148
|
+
displayName: 'Batch File',
|
|
1149
|
+
extensions: ['.bat', '.cmd'],
|
|
1150
|
+
fileFormat: 'text',
|
|
1151
|
+
contentType: 'file',
|
|
1152
|
+
icon: batchIcon
|
|
1153
|
+
});
|
|
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
|
|
960
1159
|
};
|
|
961
1160
|
|
|
962
1161
|
// Debounce timer for settings change alert
|
|
@@ -986,7 +1185,6 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
986
1185
|
}
|
|
987
1186
|
});
|
|
988
1187
|
|
|
989
|
-
|
|
990
1188
|
// Debounce the alert to show only once when multiple settings change
|
|
991
1189
|
if (settingsChangeTimeout) {
|
|
992
1190
|
clearTimeout(settingsChangeTimeout);
|
package/style/base.css
CHANGED