| // 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. |
| |
| import * as vscode from 'vscode'; |
| import { Ffx, FfxEventType, FuchsiaDevice } from './ffx'; |
| import * as logger from './logger'; |
| import { registerCommandWithAnalyticsEvent } from './analytics/vscode_events'; |
| import { ToolFinder } from './tool_finder'; |
| |
| const STATUS_BAR_PRIORITY = 0; |
| |
| // Exported so it is accessible in the test module. |
| export const STATUS_PREFIX_EMPTY = '$(circle-slash)'; |
| export const STATUS_PREFIX_NOT_CONNECTED = '$(vm-outline)'; |
| export const STATUS_PREFIX_CONNECTED = '$(vm)'; |
| /** |
| * Class that holds quick pick item data for the function showQuickPick(). Each instance is |
| * presented as a row in a menu created when clicking on the target status bar widget. |
| */ |
| export class TargetPickAction implements vscode.QuickPickItem { |
| label: string; |
| picked?: boolean | undefined; |
| device: FuchsiaDevice; |
| command: string; |
| kind?: vscode.QuickPickItemKind | undefined; |
| |
| constructor( |
| label: string, device: FuchsiaDevice, command: string, kind?: vscode.QuickPickItemKind) { |
| this.label = label; |
| this.device = device; |
| this.command = command; |
| this.kind = kind; |
| } |
| } |
| |
| /** |
| * Status bar widget to query and send commands to the target (fuchsia device). |
| */ |
| export class TargetStatusBarItem { |
| private toolFinder: ToolFinder; |
| private ffxTools: Ffx; |
| private fuchsiaStatusBarItem: vscode.StatusBarItem; |
| |
| private setBarItemText(targetName: string | undefined, connected: boolean) { |
| let displayName: string; |
| let icon: string; |
| let backgroundColor: vscode.ThemeColor | undefined; |
| |
| if (targetName === undefined) { |
| icon = STATUS_PREFIX_EMPTY; |
| displayName = 'Not connected'; |
| backgroundColor = new vscode.ThemeColor('statusBarItem.offlineBackground'); |
| } else if (!connected) { |
| icon = STATUS_PREFIX_NOT_CONNECTED; |
| displayName = `${targetName} (not connected)`; |
| backgroundColor = new vscode.ThemeColor('statusBarItem.offlineBackground'); |
| } else { |
| icon = STATUS_PREFIX_CONNECTED; |
| displayName = targetName; |
| backgroundColor = undefined; |
| } |
| |
| this.fuchsiaStatusBarItem.text = `${icon} ${displayName}`; |
| this.fuchsiaStatusBarItem.tooltip = `Fuchsia target device: ${displayName}`; |
| this.fuchsiaStatusBarItem.backgroundColor = backgroundColor; |
| } |
| |
| // Build the list of QuickPickItems for targets. |
| // The first item in the list is the default target, followed by the actions |
| // that can be performed on that target. |
| // Items to set the other targets as the default follow. |
| private buildTargetQuickPickItems(targetList: Record<string, FuchsiaDevice>): |
| TargetPickAction[] { |
| let items: TargetPickAction[] = []; |
| const devices = Object.values(targetList); |
| for (const device of devices) { |
| const nodename = device.nodeName; |
| // Ignore devices that are not connected. |
| if (!device.connected) { |
| if (device.nodeName === 'fuchsia-emulator') { |
| items.push(new TargetPickAction('Start Fuchsia emulator', device, |
| 'fuchsia.emu.start')); |
| items.push(new TargetPickAction('Start Fuchsia emulator (headless)', device, |
| 'fuchsia.emu.startHeadless')); |
| } |
| continue; |
| } |
| |
| if (device.nodeName === this.ffxTools.targetDevice?.nodeName) { |
| // Add items to front of list. |
| const deviceItem = new TargetPickAction(`VSCode target device: ${nodename}`, device, |
| 'fuchsia.target.refresh'); |
| deviceItem.picked = true; |
| const rebootItem = new TargetPickAction(`Reboot ${nodename}`, device, |
| 'fuchsia.target.reboot'); |
| const poweroffItem = new TargetPickAction(`Power off ${nodename}`, device, |
| 'fuchsia.target.powerOff'); |
| const otaItem = new TargetPickAction(`OTA (build) for ${nodename}`, device, |
| 'fuchsia.fx.ota'); |
| const otaItemNoBuild = new TargetPickAction(`OTA (no build) for ${nodename}`, device, |
| 'fuchsia.fx.ota.nobuild'); |
| const logItem = new TargetPickAction(`Show log for ${nodename}`, device, |
| 'fuchsia.viewLogs'); |
| const snapshotItem = new TargetPickAction(`Capture snapshot for ${nodename}`, device, |
| 'fuchsia.target.snapshot'); |
| const separator = new TargetPickAction('', device, '', vscode.QuickPickItemKind.Separator); |
| items = [deviceItem, separator, |
| snapshotItem, logItem, separator, |
| otaItem, otaItemNoBuild, separator, |
| rebootItem, poweroffItem, ...items]; |
| } else { |
| items.push(new TargetPickAction(`Use target device: ${nodename}`, device, |
| 'fuchsia.internal.target.switch')); |
| } |
| } |
| return items; |
| } |
| |
| constructor(readonly contextSubscriptions: vscode.Disposable[], toolFinder: ToolFinder) { |
| this.toolFinder = toolFinder; |
| this.ffxTools = toolFinder.ffx; |
| |
| // Menu for target actions |
| contextSubscriptions.push( |
| registerCommandWithAnalyticsEvent('fuchsia.target.attach', async () => { |
| // Try to find the ffx path again if it is not set. |
| if (!this.ffxTools.hasValidFfxPath()) { |
| await this.toolFinder?.updateFfxPath(false); |
| } |
| |
| this.ffxTools.refreshTargets().then(async (targetList) => { |
| const items = this.buildTargetQuickPickItems(targetList); |
| if (items.length > 0) { |
| const action = await vscode.window.showQuickPick(items); |
| if (action === undefined) { |
| return; |
| } |
| await vscode.commands.executeCommand(action.command, action.device); |
| } else { |
| const msg = Object.keys(targetList).length > 0 ? |
| 'No available devices found.' : |
| 'No devices found.'; |
| logger.warn(msg); |
| void vscode.window.showWarningMessage(msg); |
| } |
| }) |
| // If there is a problem getting the list, just log it. The quickpick |
| // will be empty. |
| .catch((err) => { |
| logger.warn(`Could not build target list: ${err}`); |
| void vscode.window.showWarningMessage('Failed to retrieve devices.'); |
| }); |
| }), |
| ); |
| |
| // Target status-bar item |
| this.fuchsiaStatusBarItem = vscode.window |
| .createStatusBarItem(vscode.StatusBarAlignment.Left, STATUS_BAR_PRIORITY); |
| this.fuchsiaStatusBarItem.command = 'fuchsia.target.attach'; |
| this.fuchsiaStatusBarItem.tooltip = 'Fuchsia target device.'; |
| contextSubscriptions.push(this.fuchsiaStatusBarItem); |
| this.setBarItemText(/* targetName */ undefined, /* connected */ false); |
| this.fuchsiaStatusBarItem.show(); |
| |
| // Clear the default target if there is no ffx found. |
| this.ffxTools.onDidChangeConfiguration((eventType) => { |
| switch (eventType) { |
| case FfxEventType.ffxPathReset: |
| this.setBarItemText(/* targetName */ undefined, /* connected */ false); |
| break; |
| } |
| }); |
| |
| // When the default target is set, update the item text. |
| this.ffxTools.onSetTarget((target) => { |
| this.setBarItemText(target?.nodeName, target?.connected ?? false); |
| }); |
| } |
| } |