| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // The module 'vscode' contains the VS Code extensibility API |
| // Import the module and reference it with the alias vscode in your code below |
| import * as vscode from 'vscode'; |
| |
| import { initAnalytics, setUpAnalyticsEvents } from './analytics/setup'; |
| import { spawn } from 'child_process'; |
| import { ToolFinder } from './tool_finder'; |
| import { Ffx } from './ffx'; |
| import { Fx } from './fx'; |
| import * as logger from './logger'; |
| import { TargetStatusBarItem } from './target_status_bar_item'; |
| import { FuchsiaDevice } from './ffx'; |
| import { setUpZxdb } from './zxdb'; |
| import { setUpComponentInteraction } from './components'; |
| import { setUpTestController } from './test_controller/test_controller'; |
| import { setUpFuchsiaTaskProvider } from './workflow/task_provider'; |
| import { setUpWorkflowInteraction } from './workflow'; |
| import { LogView } from './log_view'; |
| import { registerCommandWithAnalyticsEvent } from './analytics/vscode_events'; |
| import { FuchsiaDocumentLinkProvider } from './doc_provider'; |
| import { setUpGitHelper } from './git_helper'; |
| |
| /** |
| * contains common bits needed by the various setup functions below |
| */ |
| export class Setup { |
| /** |
| * a ToolFinder instance |
| */ |
| toolFinder: ToolFinder; |
| |
| /** |
| * a handle to interact with ffx |
| */ |
| ffx: Ffx; |
| |
| /** |
| * a handle to interact with fx |
| */ |
| fx: Fx; |
| |
| constructor() { |
| this.toolFinder = new ToolFinder(); |
| this.ffx = this.toolFinder.ffx; |
| this.fx = this.toolFinder.fx; |
| } |
| } |
| |
| /** |
| * called automatically by vscode when activating the plugin |
| * (see package.json for activation points) |
| */ |
| export async function activate(ctx: vscode.ExtensionContext) { |
| // Clean up old $FUCHSIA_NODENAME terminal environment variable contributions. |
| // Do this early since this extension's `activate()` call can race against |
| // terminal spawns on IDE startup. |
| // See https://fxbug.dev/409568762 for more information. |
| ctx.environmentVariableCollection.delete('FUCHSIA_NODENAME'); |
| |
| // Initialize the logging first so it can be used by all other code. |
| const log = vscode.window.createOutputChannel('Fuchsia Extension', { log: true }); |
| logger.initLogger(log); |
| |
| // Selects extension channel to provide languageID later |
| const fuchsiaOutput: vscode.DocumentSelector = { |
| scheme: 'output', |
| pattern: 'fuchsia-authors.vscode-fuchsia.Fuchsia Extension', |
| }; |
| |
| // Init analytics before possible calls to other Fuchsia tools (e.g. ffx), |
| // such that the extension could display the analytics notices properly. |
| try { |
| await initAnalytics(ctx); |
| } catch (err) { |
| // if analytics fails to initialize, it's not catastrophic, |
| // so just continue on after logging |
| logger.error('Unable to set up analytics, boldly continuing', undefined, err); |
| }; |
| |
| const setup = new Setup(); |
| |
| // TODO(fxbug.dev/98651): we're currently doing this every program |
| // start -- we should find a better activation point |
| ctx.subscriptions.push( |
| vscode.commands.registerCommand('fuchsia.sendFeedback', () => { |
| // The code you place here will be executed every time your command is executed |
| void /* in bg */ vscode.env.openExternal(vscode.Uri.parse( |
| 'https://bugs.fuchsia.dev/p/fuchsia/issues/entry?template=Fuchsia+Editor+Tooling')); |
| }), |
| |
| registerCommandWithAnalyticsEvent('fuchsia.showOutput', () => { |
| logger.show(); |
| }), |
| |
| vscode.languages.registerDocumentLinkProvider(fuchsiaOutput, new FuchsiaDocumentLinkProvider(), |
| ), |
| ); |
| |
| setUpContext(ctx, setup); |
| setUpLogView(ctx, setup); |
| setUpEmulatorInteraction(ctx, setup); |
| setUpTargetInteraction(ctx, setup); |
| setUpZxdb(ctx, setup); |
| setUpComponentInteraction(ctx, setup); |
| setUpTestController(ctx, setup); |
| setUpWorkflowInteraction(ctx, setup); |
| setUpFuchsiaTaskProvider(); |
| setUpGitHelper(ctx); |
| |
| setUpAnalyticsEvents(ctx); |
| } |
| |
| /** |
| * called automatically by vscode when deactivating the plugin |
| */ |
| export function deactivate() { |
| // Currently noop |
| } |
| |
| /** |
| * initialize the logging view & related commands |
| */ |
| function setUpLogView(ctx: vscode.ExtensionContext, setup: Setup) { |
| const logView = new LogView(setup.ffx); |
| |
| ctx.subscriptions.push( |
| registerCommandWithAnalyticsEvent('fuchsia.viewLogs', () => { |
| logView.output?.show(); |
| }), |
| ); |
| } |
| |
| /** |
| * initialize the target interaction commands (reboot, set target, etc) |
| * and status bar item. |
| */ |
| function setUpEmulatorInteraction(ctx: vscode.ExtensionContext, setup: Setup) { |
| const registerSimpleEmulatorCommand = (commandName: string, |
| command: () => Promise<string>) => { |
| ctx.subscriptions.push( |
| registerCommandWithAnalyticsEvent(commandName, |
| () => { |
| const action = commandName.includes('start') ? 'starting' : 'stopping'; |
| void vscode.window.withProgress({ |
| location: vscode.ProgressLocation.Window, |
| title: `${action} emulator...`, |
| }, async () => { |
| try { |
| const output = await command(); |
| logger.info(output, 'fuchsia'); |
| } catch (err: unknown) { |
| logger.error(`${String(err)}`); |
| void vscode.window.showErrorMessage( |
| `error with ${commandName}, see output for details`); |
| } |
| }); |
| }), |
| ); |
| }; |
| |
| registerSimpleEmulatorCommand('fuchsia.emu.start', |
| () => setup.ffx.startEmulator(false)); |
| registerSimpleEmulatorCommand('fuchsia.emu.startHeadless', |
| () => setup.ffx.startEmulator(true)); |
| registerSimpleEmulatorCommand('fuchsia.emu.stop', |
| () => setup.ffx.stopEmulator()); |
| } |
| |
| /** |
| * initialize the target interaction commands (reboot, set target, etc) |
| * and status bar item. |
| */ |
| function setUpTargetInteraction(ctx: vscode.ExtensionContext, setup: Setup) { |
| const registerSimpleTargetCommand = ( |
| commandName: string, |
| message: (device?: FuchsiaDevice) => string, |
| command: (device?: FuchsiaDevice) => Promise<string>, |
| ) => ctx.subscriptions.push( |
| registerCommandWithAnalyticsEvent(commandName, |
| (device?: FuchsiaDevice) => { |
| void vscode.window.withProgress({ |
| location: vscode.ProgressLocation.Window, |
| title: message(device), |
| }, async () => { |
| try { |
| const output = await command(device); |
| logger.info(output, commandName); |
| } catch (err: unknown) { |
| logger.error(`${String(err)}`, commandName); |
| void vscode.window.showErrorMessage( |
| `error with ${commandName}, see output for details`); |
| } |
| }); |
| }), |
| ); |
| |
| // set up commands... |
| registerSimpleTargetCommand( |
| 'fuchsia.target.refresh', |
| () => 'Refreshing targets...', |
| async () => JSON.stringify(await setup.ffx.refreshTargets()), |
| ); |
| registerSimpleTargetCommand( |
| 'fuchsia.target.show', |
| device => `Dumping ${device?.nodeName ?? 'target'} info to Fuchsia Output...`, |
| device => setup.ffx.showTarget(device), |
| ); |
| registerSimpleTargetCommand( |
| 'fuchsia.target.reboot', |
| device => `Rebooting ${device?.nodeName ?? 'target'}...`, |
| device => setup.ffx.rebootTarget(device), |
| ); |
| registerSimpleTargetCommand( |
| 'fuchsia.target.powerOff', |
| device => `Powering off ${device?.nodeName ?? 'target'}...`, |
| device => setup.ffx.poweroffTarget(device), |
| ); |
| |
| ctx.subscriptions.push( |
| registerCommandWithAnalyticsEvent('fuchsia.target.snapshot', |
| async (device?: FuchsiaDevice) => { |
| const path = await setup.ffx.exportSnapshotToCWD(device); |
| const msg = `snapshot exported to ${path}`; |
| logger.info(msg, 'fuchsia'); |
| |
| if (await vscode.window.showInformationMessage(msg, 'Open folder')) { |
| spawn('xdg-open', [path]); |
| } |
| }), |
| ); |
| |
| ctx.subscriptions.push( |
| vscode.commands.registerCommand('fuchsia.internal.target.switch', |
| (device?: FuchsiaDevice) => { |
| if (!device) { |
| void vscode.window.showErrorMessage('A device must be selected to set as the default target.'); |
| return; |
| } |
| |
| setup.ffx.targetDevice = device; |
| }), |
| ); |
| |
| // ...and the status bar item |
| new TargetStatusBarItem(ctx.subscriptions, setup.toolFinder); |
| } |
| |
| /** |
| * Sets up the `fuchsia.isFuchsiaProject` context key. |
| */ |
| function setUpContext(ctx: vscode.ExtensionContext, setup: Setup) { |
| // eslint-disable-next-line prefer-const |
| let disposable: vscode.Disposable; |
| const updateFuchsiaContext = () => { |
| if (setup.toolFinder.fx.fuchsiaDir) { |
| void vscode.commands.executeCommand('setContext', 'fuchsia.isFuchsiaProject', true); |
| disposable?.dispose(); |
| } |
| }; |
| disposable = setup.toolFinder.onDidUpdateFfx(() => updateFuchsiaContext()); |
| ctx.subscriptions.push(disposable); |
| updateFuchsiaContext(); |
| } |