| // 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'; |
| |
| /** |
| * 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.withProgress( |
| { |
| location: vscode.ProgressLocation.Notification, |
| // TODO(chadnorvell): Make this look better |
| 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'); |
| 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.withProgress( |
| { |
| location: vscode.ProgressLocation.Notification, |
| // TODO(chadnorvell): Make this look better |
| 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'); |
| 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 (num_recommendations > 0) { |
| await installRecommendedExtensions(extensions.recommendations as string[]); |
| } |
| |
| if (num_unwanted > 0) { |
| await disableUnwantedExtensions( |
| extensions.unwantedRecommendations as string[], |
| ); |
| } |
| } |
| |
| export function activate(context: vscode.ExtensionContext) { |
| const pwCheckExtensions = vscode.commands.registerCommand( |
| 'pigweed.check-extensions', |
| () => checkExtensions(), |
| ); |
| |
| context.subscriptions.push(pwCheckExtensions); |
| checkExtensions(); |
| } |
| |
| export function deactivate() { |
| // Do nothing. |
| } |