Skip to content

Commit a81b5d9

Browse files
committed
Add command to show unused modules in dependency graph
- Introduced a new command `bibimbob.showUnusedModules` to display modules without dependents. - Updated the `generateDependencyGraph` function to handle the new analysis type for unused modules. - Enhanced the webview to include a legend for unused modules and updated the title based on the current mode. - Improved user interaction by allowing users to choose to view unused modules when a high module count is detected.
1 parent c6bce92 commit a81b5d9

File tree

2 files changed

+111
-29
lines changed

2 files changed

+111
-29
lines changed

vscode-rescriptdep/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@
3838
{
3939
"command": "bibimbob.focusModuleDependencies",
4040
"title": "Bibimbob: Focus On Module Dependencies"
41+
},
42+
{
43+
"command": "bibimbob.showUnusedModules",
44+
"title": "Bibimbob: Show Unused Modules"
4145
}
4246
]
4347
},

vscode-rescriptdep/src/extension.ts

Lines changed: 107 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as os from 'os';
77
// Command IDs
88
const SHOW_DEPENDENCY_GRAPH = 'bibimbob.showDependencyGraph';
99
const FOCUS_MODULE_DEPENDENCIES = 'bibimbob.focusModuleDependencies';
10+
const SHOW_UNUSED_MODULES = 'bibimbob.showUnusedModules';
1011

1112
// Track the current webview panel
1213
let currentPanel: vscode.WebviewPanel | undefined = undefined;
@@ -16,7 +17,6 @@ let currentIsFocusedMode: boolean = false;
1617
let currentCenterModule: string | undefined = undefined;
1718

1819
export function activate(context: vscode.ExtensionContext) {
19-
// console.log('Bibimbob is activated'); // Removed log
2020
// Command for full dependency graph
2121
let fullGraphCommand = vscode.commands.registerCommand(SHOW_DEPENDENCY_GRAPH, async () => {
2222
await generateDependencyGraph(context);
@@ -27,8 +27,14 @@ export function activate(context: vscode.ExtensionContext) {
2727
await generateDependencyGraph(context, true);
2828
});
2929

30+
// Command for showing modules with no dependents (unused modules)
31+
let unusedModulesCommand = vscode.commands.registerCommand(SHOW_UNUSED_MODULES, async () => {
32+
await generateDependencyGraph(context, false, true);
33+
});
34+
3035
context.subscriptions.push(fullGraphCommand);
3136
context.subscriptions.push(focusModuleCommand);
37+
context.subscriptions.push(unusedModulesCommand);
3238
}
3339

3440
// Helper function to get current module name from active editor
@@ -163,11 +169,13 @@ async function selectMonorepoProject(projects: vscode.Uri[]): Promise<string | u
163169
}
164170

165171
// Integrated common logic into a single function
166-
async function generateDependencyGraph(context: vscode.ExtensionContext, focusOnModule: boolean = false) {
172+
async function generateDependencyGraph(context: vscode.ExtensionContext, focusOnModule: boolean = false, showUnusedModules: boolean = false) {
167173
// Use withProgress API to show a progress notification in the bottom right
168174
return vscode.window.withProgress({
169175
location: vscode.ProgressLocation.Notification,
170-
title: focusOnModule ? 'ReScript: Analyzing module dependencies...' : 'ReScript: Analyzing dependency graph...',
176+
title: focusOnModule ? 'ReScript: Analyzing module dependencies...' :
177+
showUnusedModules ? 'ReScript: Finding unused modules...' :
178+
'ReScript: Analyzing dependency graph...',
171179
cancellable: true
172180
}, async (progress, token) => {
173181
const workspaceFolders = vscode.workspace.workspaceFolders;
@@ -268,7 +276,7 @@ async function generateDependencyGraph(context: vscode.ExtensionContext, focusOn
268276
const cliPath = await findRescriptDepCLI(context);
269277

270278
// Check project size first if not focusing on a specific module
271-
if (!focusOnModule) {
279+
if (!focusOnModule && !showUnusedModules) {
272280
progress.report({ message: 'Checking project size...' });
273281
try {
274282
// Get a simple DOT output to estimate the number of modules
@@ -283,20 +291,21 @@ async function generateDependencyGraph(context: vscode.ExtensionContext, focusOn
283291
const moduleNodes = nodeMatches.filter(match => !match.startsWith('"node ['));
284292
const moduleCount = moduleNodes.length;
285293

286-
// Log module count for debugging
287-
console.log(`Detected approximately ${moduleCount} modules in the project`);
288-
289294
// Prompt user if module count is high
290295
if (moduleCount > 1000) {
291296
const response = await vscode.window.showWarningMessage(
292297
`This project contains approximately ${moduleCount} modules, which may cause performance issues or visualization errors.`,
293-
'Continue Anyway', 'Focus on Module', 'Cancel'
298+
'Continue Anyway', 'Focus on Module', 'Show Unused Modules', 'Cancel'
294299
);
295300

296301
if (response === 'Focus on Module') {
297302
// User chose to focus on a specific module
298303
await vscode.commands.executeCommand(FOCUS_MODULE_DEPENDENCIES);
299304
return;
305+
} else if (response === 'Show Unused Modules') {
306+
// User chose to show unused modules
307+
await vscode.commands.executeCommand(SHOW_UNUSED_MODULES);
308+
return;
300309
} else if (response !== 'Continue Anyway') {
301310
// User chose to cancel
302311
return;
@@ -307,7 +316,7 @@ async function generateDependencyGraph(context: vscode.ExtensionContext, focusOn
307316
if (token.isCancellationRequested) { return; }
308317

309318
if (dotOutput) {
310-
showDotGraphWebview(context, dotOutput, focusOnModule, moduleName);
319+
showDotGraphWebview(context, dotOutput, focusOnModule, moduleName, showUnusedModules);
311320
return;
312321
}
313322
}
@@ -318,15 +327,21 @@ async function generateDependencyGraph(context: vscode.ExtensionContext, focusOn
318327
}
319328

320329
// Run the CLI command with the determined bsDir and moduleName (if applicable)
321-
progress.report({ message: 'Running rescriptdep CLI...' });
330+
progress.report({ message: showUnusedModules ? 'Finding unused modules...' : 'Running dependency analysis...' });
322331
if (token.isCancellationRequested) { return; }
323332

324-
// Define CLI arguments - Use DOT format instead of JSON for better performance
325-
const args: string[] = ['--format=dot'];
333+
// Define CLI arguments based on the analysis type
334+
let args: string[];
326335

327-
// Add module focus if specified
328-
if (moduleName) {
329-
args.push('--module', moduleName);
336+
if (showUnusedModules) {
337+
// 1. Unused modules analysis
338+
args = ['--format=dot', '--no-dependents'];
339+
} else if (focusOnModule) {
340+
// 2. Focus on specific module
341+
args = ['--format=dot', '--module', moduleName!];
342+
} else {
343+
// 3. Full dependency graph
344+
args = ['--format=dot'];
330345
}
331346

332347
// Add bsDir target
@@ -340,7 +355,7 @@ async function generateDependencyGraph(context: vscode.ExtensionContext, focusOn
340355
if (token.isCancellationRequested) { return; }
341356

342357
if (dotContent) {
343-
showDotGraphWebview(context, dotContent, focusOnModule, moduleName);
358+
showDotGraphWebview(context, dotContent, focusOnModule, moduleName, showUnusedModules);
344359
} else {
345360
vscode.window.showErrorMessage('Failed to generate dependency visualization (CLI returned no content).');
346361
}
@@ -481,7 +496,6 @@ async function runRescriptDep(cliPath: string, args: string[], context?: vscode.
481496
cpuLimitedArgs = [command, ...args];
482497
cpuLimitedCommand = 'nice';
483498
}
484-
// Note: Windows doesn't have a simple equivalent to 'nice' via command line
485499

486500
cp.execFile(cpuLimitedCommand, cpuLimitedArgs, options, (error, stdout, stderr) => {
487501
if (error) {
@@ -513,7 +527,7 @@ async function runRescriptDep(cliPath: string, args: string[], context?: vscode.
513527
}
514528

515529
// Function to display DOT format graph in webview
516-
function showDotGraphWebview(context: vscode.ExtensionContext, dotContent: string, isFocusedMode: boolean = false, centerModuleName?: string) {
530+
function showDotGraphWebview(context: vscode.ExtensionContext, dotContent: string, isFocusedMode: boolean = false, centerModuleName?: string, isUnusedModulesMode: boolean = false) {
517531
// Save current state to global variables
518532
currentDotContent = dotContent;
519533
currentIsFocusedMode = isFocusedMode;
@@ -780,6 +794,7 @@ function showDotGraphWebview(context: vscode.ExtensionContext, dotContent: strin
780794
/* These variables will be assigned at runtime based on theme */
781795
--dependents-color: lightblue;
782796
--dependencies-color: lightcoral;
797+
--unused-color: #ff6666;
783798
}
784799
785800
/* Dark theme arrow colors */
@@ -896,14 +911,18 @@ function showDotGraphWebview(context: vscode.ExtensionContext, dotContent: strin
896911
<body class="${isDarkTheme ? 'vscode-dark' : 'vscode-light'}">
897912
<div class="top-controls-wrapper">
898913
<div class="legend">
899-
<div class="legend-item">
914+
<div class="legend-item" id="dependents-legend" style="display: none;">
900915
<div class="legend-line" style="background-color: var(--dependents-color, lightblue);"></div>
901916
<span>Dependents (modules that use the center module)</span>
902917
</div>
903-
<div class="legend-item">
918+
<div class="legend-item" id="dependencies-legend" style="display: none;">
904919
<div class="legend-line" style="background-color: var(--dependencies-color, lightcoral);"></div>
905920
<span>Dependencies (modules used by the center module)</span>
906921
</div>
922+
<div class="legend-item" id="unused-modules-legend" style="display: none;">
923+
<div class="legend-line" style="background-color: var(--unused-color, #ff6666);"></div>
924+
<span>Modules with no dependents (unused modules)</span>
925+
</div>
907926
</div>
908927
<div class="search-container">
909928
<input type="text" class="search-input" id="module-search" placeholder="Search for module..." />
@@ -938,6 +957,7 @@ function showDotGraphWebview(context: vscode.ExtensionContext, dotContent: strin
938957
let dotSrc = '';
939958
let isFocusedMode = false;
940959
let centerModule = null;
960+
let isUnusedModulesMode = false;
941961
let allModuleNodes = []; // Store all available module names
942962
943963
// Theme-related variables - detect theme from body class during initialization
@@ -946,6 +966,7 @@ function showDotGraphWebview(context: vscode.ExtensionContext, dotContent: strin
946966
// Set colors based on theme
947967
document.documentElement.style.setProperty('--dependents-color', isDarkTheme ? 'steelblue' : 'lightblue');
948968
document.documentElement.style.setProperty('--dependencies-color', isDarkTheme ? 'indianred' : 'lightcoral');
969+
document.documentElement.style.setProperty('--unused-color', '#ff6666');
949970
950971
// Function to update SVG styles to match the theme
951972
function updateSvgStylesForTheme(svg, isDark) {
@@ -1095,6 +1116,7 @@ function showDotGraphWebview(context: vscode.ExtensionContext, dotContent: strin
10951116
// Update theme-based color settings
10961117
document.documentElement.style.setProperty('--dependents-color', isDarkTheme ? 'steelblue' : 'lightblue');
10971118
document.documentElement.style.setProperty('--dependencies-color', isDarkTheme ? 'indianred' : 'lightcoral');
1119+
document.documentElement.style.setProperty('--unused-color', '#ff6666');
10981120
10991121
// Directly set body background color
11001122
document.body.style.backgroundColor = isDarkTheme ? '#1e1e1e' : '#ffffff';
@@ -1152,13 +1174,22 @@ function showDotGraphWebview(context: vscode.ExtensionContext, dotContent: strin
11521174
11531175
// Direct SVG modification - change all node background colors
11541176
const nodeRects = svgElement.querySelectorAll('.node rect, .node polygon');
1155-
const bgColor = isDarkTheme ? '#1e1e1e' : '#f0f0f0';
1177+
1178+
// Base colors based on theme
1179+
const standardBgColor = isDarkTheme ? '#1e1e1e' : '#f0f0f0';
1180+
const unusedModulesBgColor = isDarkTheme ? '#4b1d1d' : '#ffeeee'; // Reddish background
1181+
1182+
// Choose color based on mode
1183+
const bgColor = isUnusedModulesMode ? unusedModulesBgColor : standardBgColor;
11561184
11571185
nodeRects.forEach(rect => {
11581186
rect.setAttribute('fill', bgColor);
11591187
// Also set border clearly
1160-
rect.setAttribute('stroke', isDarkTheme ? '#aaaaaa' : '#666666');
1161-
rect.setAttribute('stroke-width', '1px');
1188+
const borderColor = isUnusedModulesMode ?
1189+
(isDarkTheme ? '#cc6666' : '#cc6666') :
1190+
(isDarkTheme ? '#aaaaaa' : '#666666');
1191+
rect.setAttribute('stroke', borderColor);
1192+
rect.setAttribute('stroke-width', isUnusedModulesMode ? '1.5px' : '1px');
11621193
// Add rounded corners
11631194
if (rect.tagName.toLowerCase() === 'rect') {
11641195
rect.setAttribute('rx', '4');
@@ -1382,8 +1413,8 @@ function showDotGraphWebview(context: vscode.ExtensionContext, dotContent: strin
13821413
const moduleName = titleEl.textContent.trim();
13831414
if (!moduleName) return;
13841415
1385-
// Check if this is the center module
1386-
if (centerModule && moduleName === centerModule) {
1416+
// Check if this is the center module (only in focused mode)
1417+
if (isFocusedMode && centerModule && moduleName === centerModule) {
13871418
// Only apply click prevention style to center module
13881419
node.style.cursor = 'default';
13891420
@@ -1841,6 +1872,10 @@ function showDotGraphWebview(context: vscode.ExtensionContext, dotContent: strin
18411872
dotSrc = message.dotContent;
18421873
isFocusedMode = message.isFocusedMode;
18431874
centerModule = message.centerModule;
1875+
isUnusedModulesMode = message.isUnusedModulesMode || false;
1876+
1877+
// Update legend display based on mode
1878+
updateLegendDisplay();
18441879
18451880
// Now that we have data, render the graph
18461881
renderGraph();
@@ -1852,6 +1887,10 @@ function showDotGraphWebview(context: vscode.ExtensionContext, dotContent: strin
18521887
dotSrc = message.dotContent;
18531888
isFocusedMode = message.isFocusedMode;
18541889
centerModule = message.centerModule;
1890+
isUnusedModulesMode = message.isUnusedModulesMode || false;
1891+
1892+
// Update legend display based on mode
1893+
updateLegendDisplay();
18551894
18561895
// Clear search input when graph is redrawn
18571896
const searchInput = document.getElementById('module-search');
@@ -1885,6 +1924,7 @@ function showDotGraphWebview(context: vscode.ExtensionContext, dotContent: strin
18851924
isDarkTheme ? 'steelblue' : 'lightblue');
18861925
document.documentElement.style.setProperty('--dependencies-color',
18871926
isDarkTheme ? 'indianred' : 'lightcoral');
1927+
document.documentElement.style.setProperty('--unused-color', '#ff6666');
18881928
18891929
// Update class of document
18901930
if (isDarkTheme) {
@@ -1927,6 +1967,32 @@ function showDotGraphWebview(context: vscode.ExtensionContext, dotContent: strin
19271967
}
19281968
});
19291969
1970+
// Function to update legend display based on current mode
1971+
function updateLegendDisplay() {
1972+
const dependentsLegend = document.getElementById('dependents-legend');
1973+
const dependenciesLegend = document.getElementById('dependencies-legend');
1974+
const unusedModulesLegend = document.getElementById('unused-modules-legend');
1975+
1976+
if (!dependentsLegend || !dependenciesLegend || !unusedModulesLegend) return;
1977+
1978+
if (isUnusedModulesMode) {
1979+
// Show only unused modules legend in unused modules mode
1980+
dependentsLegend.style.display = 'none';
1981+
dependenciesLegend.style.display = 'none';
1982+
unusedModulesLegend.style.display = 'flex';
1983+
} else if (isFocusedMode) {
1984+
// Show both legends in focused mode
1985+
dependentsLegend.style.display = 'flex';
1986+
dependenciesLegend.style.display = 'flex';
1987+
unusedModulesLegend.style.display = 'none';
1988+
} else {
1989+
// Show no legend in regular mode
1990+
dependentsLegend.style.display = 'none';
1991+
dependenciesLegend.style.display = 'none';
1992+
unusedModulesLegend.style.display = 'none';
1993+
}
1994+
}
1995+
19301996
// Notify VS Code that webview is ready
19311997
vscode.postMessage({ command: 'webviewReady' });
19321998
</script>
@@ -1937,7 +2003,16 @@ function showDotGraphWebview(context: vscode.ExtensionContext, dotContent: strin
19372003
if (currentPanel) {
19382004
// If we already have a panel, just update its html
19392005
currentPanel.webview.html = htmlContent;
1940-
currentPanel.title = isFocusedMode && centerModuleName ? `Module: ${centerModuleName} Dependencies` : 'ReScript Dependencies';
2006+
2007+
// Set appropriate title based on mode
2008+
if (isUnusedModulesMode) {
2009+
currentPanel.title = 'ReScript: Unused Modules';
2010+
} else if (isFocusedMode && centerModuleName) {
2011+
currentPanel.title = `Module: ${centerModuleName} Dependencies`;
2012+
} else {
2013+
currentPanel.title = 'ReScript Dependencies';
2014+
}
2015+
19412016
currentPanel.reveal(vscode.ViewColumn.One);
19422017

19432018
// Send the graph data after the webview is loaded
@@ -1948,7 +2023,8 @@ function showDotGraphWebview(context: vscode.ExtensionContext, dotContent: strin
19482023
command: 'initGraph',
19492024
dotContent: themedDotContent,
19502025
isFocusedMode: isFocusedMode,
1951-
centerModule: centerModuleName
2026+
centerModule: centerModuleName,
2027+
isUnusedModulesMode: isUnusedModulesMode
19522028
});
19532029
}
19542030
}
@@ -1957,7 +2033,8 @@ function showDotGraphWebview(context: vscode.ExtensionContext, dotContent: strin
19572033
// Create a new panel
19582034
currentPanel = vscode.window.createWebviewPanel(
19592035
'bibimbobVisualizer',
1960-
isFocusedMode && centerModuleName ? `Module: ${centerModuleName} Dependencies` : 'ReScript Dependencies',
2036+
isUnusedModulesMode ? 'ReScript: Unused Modules' :
2037+
(isFocusedMode && centerModuleName ? `Module: ${centerModuleName} Dependencies` : 'ReScript Dependencies'),
19612038
vscode.ViewColumn.One,
19622039
{
19632040
enableScripts: true,
@@ -1982,7 +2059,8 @@ function showDotGraphWebview(context: vscode.ExtensionContext, dotContent: strin
19822059
command: 'initGraph',
19832060
dotContent: themedDotContent,
19842061
isFocusedMode: isFocusedMode,
1985-
centerModule: centerModuleName
2062+
centerModule: centerModuleName,
2063+
isUnusedModulesMode: isUnusedModulesMode
19862064
});
19872065
}
19882066
}

0 commit comments

Comments
 (0)