aboutsummaryrefslogtreecommitdiff
path: root/pw_ide/ts/pigweed-vscode/src/extension.ts
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2024-01-17 22:13:58 -0800
committerXin Li <delphij@google.com>2024-01-17 22:13:58 -0800
commit28d03a2a1cabbe01d7bcb6cf5166c10e50d3c2c6 (patch)
treec1643be8ab17fc607cea748a8bb1d621a5964873 /pw_ide/ts/pigweed-vscode/src/extension.ts
parentec2628a6ba2d0ecbe3ac10c8c772f6fc6acc345d (diff)
parentf054515492af5132f685cb23fe11891ee77104c9 (diff)
downloadpigweed-28d03a2a1cabbe01d7bcb6cf5166c10e50d3c2c6.tar.gz
Merge Android 24Q1 Release (ab/11220357)temp_319669529
Bug: 319669529 Merged-In: Iba357b308a79d0c8b560acd4f72b5423c9c83294 Change-Id: Icdf552029fb97a34e83c6dd7799433fc473a2506
Diffstat (limited to 'pw_ide/ts/pigweed-vscode/src/extension.ts')
-rw-r--r--pw_ide/ts/pigweed-vscode/src/extension.ts242
1 files changed, 242 insertions, 0 deletions
diff --git a/pw_ide/ts/pigweed-vscode/src/extension.ts b/pw_ide/ts/pigweed-vscode/src/extension.ts
new file mode 100644
index 000000000..16e1ca51f
--- /dev/null
+++ b/pw_ide/ts/pigweed-vscode/src/extension.ts
@@ -0,0 +1,242 @@
+// 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.
+}