blob: 140ba6ff325ea22bcda57b26f197341073f1b812 [file] [log] [blame]
// 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);
});
}
}