| // Copyright 2023 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| import * as vscode from 'vscode'; |
| |
| import { getExtensionsJson } from './config'; |
| import { launchBootstrapTerminal, launchTerminal } from './terminal'; |
| |
| const bugUrl = |
| 'https://issues.pigweed.dev/issues/new?component=1194524&template=1911548'; |
| |
| /** |
| * Open the bug report template in the user's browser. |
| */ |
| function fileBug() { |
| vscode.env.openExternal(vscode.Uri.parse(bugUrl)); |
| } |
| |
| /** |
| * Open the extensions sidebar and show the provided extensions. |
| * @param extensions - A list of extension IDs |
| */ |
| function showExtensions(extensions: string[]) { |
| vscode.commands.executeCommand( |
| 'workbench.extensions.search', |
| '@id:' + extensions.join(', @id:'), |
| ); |
| } |
| |
| /** |
| * Given a list of extensions, return the subset that are not installed or are |
| * disabled. |
| * @param extensions - A list of extension IDs |
| * @returns A list of extension IDs |
| */ |
| function getUnavailableExtensions(extensions: string[]): string[] { |
| const unavailableExtensions: string[] = []; |
| const available = vscode.extensions.all; |
| |
| // TODO(chadnorvell): Verify that this includes disabled extensions |
| extensions.map(async (extId) => { |
| const ext = available.find((ext) => ext.id == extId); |
| |
| if (!ext) { |
| unavailableExtensions.push(extId); |
| } |
| }); |
| |
| return unavailableExtensions; |
| } |
| |
| /** |
| * If there are recommended extensions that are not installed or enabled in the |
| * current workspace, prompt the user to install them. This is "sticky" in the |
| * sense that it will keep bugging the user to enable those extensions until |
| * they enable them all, or until they explicitly cancel. |
| * @param recs - A list of extension IDs |
| */ |
| async function installRecommendedExtensions(recs: string[]): Promise<void> { |
| let unavailableRecs = getUnavailableExtensions(recs); |
| const totalNumUnavailableRecs = unavailableRecs.length; |
| let numUnavailableRecs = totalNumUnavailableRecs; |
| |
| const update = () => { |
| unavailableRecs = getUnavailableExtensions(recs); |
| numUnavailableRecs = unavailableRecs.length; |
| }; |
| |
| const wait = async () => new Promise((resolve) => setTimeout(resolve, 2500)); |
| |
| const progressIncrement = (num: number) => |
| 1 - (num / totalNumUnavailableRecs) * 100; |
| |
| // All recommendations are installed; we're done. |
| if (totalNumUnavailableRecs == 0) { |
| console.log('User has all recommended extensions'); |
| |
| return; |
| } |
| |
| showExtensions(unavailableRecs); |
| |
| vscode.window.showInformationMessage( |
| `This Pigweed project needs you to install ${totalNumUnavailableRecs} ` + |
| 'required extensions. ' + |
| 'Install the extensions shown in the extensions tab.', |
| { modal: true }, |
| 'Ok', |
| ); |
| |
| vscode.window.withProgress( |
| { |
| location: vscode.ProgressLocation.Notification, |
| title: |
| 'Install these extensions! This Pigweed project needs these recommended extensions to be installed.', |
| cancellable: true, |
| }, |
| async (progress, token) => { |
| while (numUnavailableRecs > 0) { |
| // TODO(chadnorvell): Wait for vscode.extensions.onDidChange |
| await wait(); |
| update(); |
| |
| progress.report({ |
| increment: progressIncrement(numUnavailableRecs), |
| }); |
| |
| if (numUnavailableRecs > 0) { |
| console.log( |
| `User lacks ${numUnavailableRecs} recommended extensions`, |
| ); |
| |
| showExtensions(unavailableRecs); |
| } |
| |
| if (token.isCancellationRequested) { |
| console.log('User cancelled recommended extensions check'); |
| |
| break; |
| } |
| } |
| |
| console.log('All recommended extensions are enabled'); |
| vscode.commands.executeCommand( |
| 'workbench.action.toggleSidebarVisibility', |
| ); |
| progress.report({ increment: 100 }); |
| }, |
| ); |
| } |
| |
| /** |
| * Given a list of extensions, return the subset that are enabled. |
| * @param extensions - A list of extension IDs |
| * @returns A list of extension IDs |
| */ |
| function getEnabledExtensions(extensions: string[]): string[] { |
| const enabledExtensions: string[] = []; |
| const available = vscode.extensions.all; |
| |
| // TODO(chadnorvell): Verify that this excludes disabled extensions |
| extensions.map(async (extId) => { |
| const ext = available.find((ext) => ext.id == extId); |
| |
| if (ext) { |
| enabledExtensions.push(extId); |
| } |
| }); |
| |
| return enabledExtensions; |
| } |
| |
| /** |
| * If there are unwanted extensions that are enabled in the current workspace, |
| * prompt the user to disable them. This is "sticky" in the sense that it will |
| * keep bugging the user to disable those extensions until they disable them |
| * all, or until they explicitly cancel. |
| * @param recs - A list of extension IDs |
| */ |
| async function disableUnwantedExtensions(unwanted: string[]) { |
| let enabledUnwanted = getEnabledExtensions(unwanted); |
| const totalNumEnabledUnwanted = enabledUnwanted.length; |
| let numEnabledUnwanted = totalNumEnabledUnwanted; |
| |
| const update = () => { |
| enabledUnwanted = getEnabledExtensions(unwanted); |
| numEnabledUnwanted = enabledUnwanted.length; |
| }; |
| |
| const wait = async () => new Promise((resolve) => setTimeout(resolve, 2500)); |
| |
| const progressIncrement = (num: number) => |
| 1 - (num / totalNumEnabledUnwanted) * 100; |
| |
| // All unwanted are disabled; we're done. |
| if (totalNumEnabledUnwanted == 0) { |
| console.log('User has no unwanted extensions enabled'); |
| |
| return; |
| } |
| |
| showExtensions(enabledUnwanted); |
| |
| vscode.window.showInformationMessage( |
| `This Pigweed project needs you to disable ${totalNumEnabledUnwanted} ` + |
| 'incompatible extensions. ' + |
| 'Disable the extensions shown the extensions tab.', |
| { modal: true }, |
| 'Ok', |
| ); |
| |
| vscode.window.withProgress( |
| { |
| location: vscode.ProgressLocation.Notification, |
| title: |
| 'Disable these extensions! This Pigweed project needs these extensions to be disabled.', |
| cancellable: true, |
| }, |
| async (progress, token) => { |
| while (numEnabledUnwanted > 0) { |
| // TODO(chadnorvell): Wait for vscode.extensions.onDidChange |
| await wait(); |
| update(); |
| |
| progress.report({ |
| increment: progressIncrement(numEnabledUnwanted), |
| }); |
| |
| if (numEnabledUnwanted > 0) { |
| console.log( |
| `User has ${numEnabledUnwanted} unwanted extensions enabled`, |
| ); |
| |
| showExtensions(enabledUnwanted); |
| } |
| |
| if (token.isCancellationRequested) { |
| console.log('User cancelled unwanted extensions check'); |
| |
| break; |
| } |
| } |
| |
| console.log('All unwanted extensions are disabled'); |
| vscode.commands.executeCommand( |
| 'workbench.action.toggleSidebarVisibility', |
| ); |
| progress.report({ increment: 100 }); |
| }, |
| ); |
| } |
| |
| async function checkExtensions() { |
| const extensions = await getExtensionsJson(); |
| |
| const num_recommendations = extensions?.recommendations?.length ?? 0; |
| const num_unwanted = extensions?.unwantedRecommendations?.length ?? 0; |
| |
| if (extensions && num_recommendations > 0) { |
| await installRecommendedExtensions(extensions.recommendations as string[]); |
| } |
| |
| if (extensions && num_unwanted > 0) { |
| await disableUnwantedExtensions( |
| extensions.unwantedRecommendations as string[], |
| ); |
| } |
| } |
| |
| function registerCommands(context: vscode.ExtensionContext) { |
| context.subscriptions.push( |
| vscode.commands.registerCommand('pigweed.file-bug', () => fileBug()), |
| ); |
| |
| context.subscriptions.push( |
| vscode.commands.registerCommand('pigweed.check-extensions', () => |
| checkExtensions(), |
| ), |
| ); |
| |
| context.subscriptions.push( |
| vscode.commands.registerCommand('pigweed.launch-terminal', () => |
| launchTerminal(), |
| ), |
| ); |
| |
| context.subscriptions.push( |
| vscode.commands.registerCommand('pigweed.bootstrap-terminal', () => |
| launchBootstrapTerminal(), |
| ), |
| ); |
| } |
| |
| export async function activate(context: vscode.ExtensionContext) { |
| registerCommands(context); |
| |
| const shouldEnforce = vscode.workspace |
| .getConfiguration('pigweed') |
| .get('enforceExtensionRecommendations') as string; |
| |
| if (shouldEnforce === 'true') { |
| console.log('pigweed.enforceExtensionRecommendations: true'); |
| await checkExtensions(); |
| } |
| } |
| |
| export function deactivate() { |
| // Do nothing. |
| } |