aboutsummaryrefslogtreecommitdiff
path: root/pw_ide/ts/pigweed-vscode/src/extension.ts
blob: 16e1ca51f2de81cbddac8fa61326e50fa6c4223f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
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.
}