Files
vscode/cli/src/commands/args.rs
2023-06-16 10:42:13 -07:00

689 lines
18 KiB
Rust

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
use std::{fmt, path::PathBuf};
use crate::{constants, log, options, tunnels::code_server::CodeServerArgs};
use clap::{Args, Parser, Subcommand, ValueEnum};
use const_format::concatcp;
const CLI_NAME: &str = concatcp!(constants::PRODUCT_NAME_LONG, " CLI");
const HELP_COMMANDS: &str = "Usage: {name} [options][paths...]
To read output from another program, append '-' (e.g. 'echo Hello World | {name} -')";
const STANDALONE_TEMPLATE: &str = concatcp!(
CLI_NAME,
" Standalone - {version}
",
HELP_COMMANDS,
"
Running editor commands requires installing ",
constants::QUALITYLESS_PRODUCT_NAME,
", and may differ slightly.
{all-args}"
);
const INTEGRATED_TEMPLATE: &str = concatcp!(
CLI_NAME,
" - {version}
",
HELP_COMMANDS,
"
{all-args}"
);
const COMMIT_IN_VERSION: &str = match constants::VSCODE_CLI_COMMIT {
Some(c) => c,
None => "unknown",
};
const NUMBER_IN_VERSION: &str = match constants::VSCODE_CLI_VERSION {
Some(c) => c,
None => "dev",
};
const VERSION: &str = concatcp!(NUMBER_IN_VERSION, " (commit ", COMMIT_IN_VERSION, ")");
#[derive(Parser, Debug, Default)]
#[clap(
help_template = INTEGRATED_TEMPLATE,
long_about = None,
version = VERSION,
)]
pub struct IntegratedCli {
#[clap(flatten)]
pub core: CliCore,
}
/// Common CLI shared between intergated and standalone interfaces.
#[derive(Args, Debug, Default, Clone)]
pub struct CliCore {
/// One or more files, folders, or URIs to open.
#[clap(name = "paths")]
pub open_paths: Vec<String>,
#[clap(flatten, next_help_heading = Some("EDITOR OPTIONS"))]
pub editor_options: EditorOptions,
#[clap(flatten, next_help_heading = Some("EDITOR TROUBLESHOOTING"))]
pub troubleshooting: EditorTroubleshooting,
#[clap(flatten, next_help_heading = Some("GLOBAL OPTIONS"))]
pub global_options: GlobalOptions,
#[clap(subcommand)]
pub subcommand: Option<Commands>,
}
#[derive(Parser, Debug, Default)]
#[clap(
help_template = STANDALONE_TEMPLATE,
long_about = None,
version = VERSION,
)]
pub struct StandaloneCli {
#[clap(flatten)]
pub core: CliCore,
#[clap(subcommand)]
pub subcommand: Option<StandaloneCommands>,
}
pub enum AnyCli {
Integrated(IntegratedCli),
Standalone(StandaloneCli),
}
impl AnyCli {
pub fn core(&self) -> &CliCore {
match self {
AnyCli::Integrated(cli) => &cli.core,
AnyCli::Standalone(cli) => &cli.core,
}
}
}
impl CliCore {
pub fn get_base_code_args(&self) -> Vec<String> {
let mut args = self.open_paths.clone();
self.editor_options.add_code_args(&mut args);
self.troubleshooting.add_code_args(&mut args);
self.global_options.add_code_args(&mut args);
args
}
}
impl<'a> From<&'a CliCore> for CodeServerArgs {
fn from(cli: &'a CliCore) -> Self {
let mut args = CodeServerArgs {
log: cli.global_options.log,
accept_server_license_terms: true,
..Default::default()
};
args.log = cli.global_options.log;
args.accept_server_license_terms = true;
if cli.global_options.verbose {
args.verbose = true;
}
if cli.global_options.disable_telemetry {
args.telemetry_level = Some(options::TelemetryLevel::Off);
} else if cli.global_options.telemetry_level.is_some() {
args.telemetry_level = cli.global_options.telemetry_level;
}
args
}
}
#[derive(Subcommand, Debug, Clone)]
pub enum StandaloneCommands {
/// Updates the CLI.
Update(StandaloneUpdateArgs),
}
#[derive(Args, Debug, Clone)]
pub struct StandaloneUpdateArgs {
/// Only check for updates, without actually updating the CLI.
#[clap(long)]
pub check: bool,
}
#[derive(Subcommand, Debug, Clone)]
pub enum Commands {
/// Create a tunnel that's accessible on vscode.dev from anywhere.
/// Run `code tunnel --help` for more usage info.
Tunnel(TunnelArgs),
/// Manage editor extensions.
#[clap(name = "ext")]
Extension(ExtensionArgs),
/// Print process usage and diagnostics information.
Status,
/// Changes the version of the editor you're using.
Version(VersionArgs),
/// Runs the control server on process stdin/stdout
#[clap(hide = true)]
CommandShell,
}
#[derive(Args, Debug, Clone)]
pub struct ExtensionArgs {
#[clap(subcommand)]
pub subcommand: ExtensionSubcommand,
#[clap(flatten)]
pub desktop_code_options: DesktopCodeOptions,
}
impl ExtensionArgs {
pub fn add_code_args(&self, target: &mut Vec<String>) {
self.desktop_code_options.add_code_args(target);
self.subcommand.add_code_args(target);
}
}
#[derive(Subcommand, Debug, Clone)]
pub enum ExtensionSubcommand {
/// List installed extensions.
List(ListExtensionArgs),
/// Install an extension.
Install(InstallExtensionArgs),
/// Uninstall an extension.
Uninstall(UninstallExtensionArgs),
}
impl ExtensionSubcommand {
pub fn add_code_args(&self, target: &mut Vec<String>) {
match self {
ExtensionSubcommand::List(args) => {
target.push("--list-extensions".to_string());
if args.show_versions {
target.push("--show-versions".to_string());
}
if let Some(category) = &args.category {
target.push(format!("--category={}", category));
}
}
ExtensionSubcommand::Install(args) => {
for id in args.id_or_path.iter() {
target.push(format!("--install-extension={}", id));
}
if args.pre_release {
target.push("--pre-release".to_string());
}
if args.force {
target.push("--force".to_string());
}
}
ExtensionSubcommand::Uninstall(args) => {
for id in args.id.iter() {
target.push(format!("--uninstall-extension={}", id));
}
}
}
}
}
#[derive(Args, Debug, Clone)]
pub struct ListExtensionArgs {
/// Filters installed extensions by provided category, when using --list-extensions.
#[clap(long, value_name = "category")]
pub category: Option<String>,
/// Show versions of installed extensions, when using --list-extensions.
#[clap(long)]
pub show_versions: bool,
}
#[derive(Args, Debug, Clone)]
pub struct InstallExtensionArgs {
/// Either an extension id or a path to a VSIX. The identifier of an
/// extension is '${publisher}.${name}'. Use '--force' argument to update
/// to latest version. To install a specific version provide '@${version}'.
/// For example: 'vscode.csharp@1.2.3'.
#[clap(name = "ext-id | id")]
pub id_or_path: Vec<String>,
/// Installs the pre-release version of the extension
#[clap(long)]
pub pre_release: bool,
/// Update to the latest version of the extension if it's already installed.
#[clap(long)]
pub force: bool,
}
#[derive(Args, Debug, Clone)]
pub struct UninstallExtensionArgs {
/// One or more extension identifiers to uninstall. The identifier of an
/// extension is '${publisher}.${name}'. Use '--force' argument to update
/// to latest version.
#[clap(name = "ext-id")]
pub id: Vec<String>,
}
#[derive(Args, Debug, Clone)]
pub struct VersionArgs {
#[clap(subcommand)]
pub subcommand: VersionSubcommand,
}
#[derive(Subcommand, Debug, Clone)]
pub enum VersionSubcommand {
/// Switches the version of the editor in use.
Use(UseVersionArgs),
/// Shows the currently configured editor version.
Show,
}
#[derive(Args, Debug, Clone)]
pub struct UseVersionArgs {
/// The version of the editor you want to use. Can be "stable", "insiders",
/// a version number, or an absolute path to an existing install.
#[clap(value_name = "stable | insiders | x.y.z | path")]
pub name: String,
/// The directory where the version can be found.
#[clap(long, value_name = "path")]
pub install_dir: Option<String>,
}
#[derive(Args, Debug, Default, Clone)]
pub struct EditorOptions {
/// Compare two files with each other.
#[clap(short, long, value_names = &["file", "file"])]
pub diff: Vec<String>,
/// Add folder(s) to the last active window.
#[clap(short, long, value_name = "folder")]
pub add: Option<String>,
/// Open a file at the path on the specified line and character position.
#[clap(short, long, value_name = "file:line[:character]")]
pub goto: Option<String>,
/// Force to open a new window.
#[clap(short, long)]
pub new_window: bool,
/// Force to open a file or folder in an
#[clap(short, long)]
pub reuse_window: bool,
/// Wait for the files to be closed before returning.
#[clap(short, long)]
pub wait: bool,
/// The locale to use (e.g. en-US or zh-TW).
#[clap(long, value_name = "locale")]
pub locale: Option<String>,
/// Enables proposed API features for extensions. Can receive one or
/// more extension IDs to enable individually.
#[clap(long, value_name = "ext-id")]
pub enable_proposed_api: Vec<String>,
#[clap(flatten)]
pub code_options: DesktopCodeOptions,
}
impl EditorOptions {
pub fn add_code_args(&self, target: &mut Vec<String>) {
if !self.diff.is_empty() {
target.push("--diff".to_string());
for file in self.diff.iter() {
target.push(file.clone());
}
}
if let Some(add) = &self.add {
target.push("--add".to_string());
target.push(add.clone());
}
if let Some(goto) = &self.goto {
target.push("--goto".to_string());
target.push(goto.clone());
}
if self.new_window {
target.push("--new-window".to_string());
}
if self.reuse_window {
target.push("--reuse-window".to_string());
}
if self.wait {
target.push("--wait".to_string());
}
if let Some(locale) = &self.locale {
target.push(format!("--locale={}", locale));
}
if !self.enable_proposed_api.is_empty() {
for id in self.enable_proposed_api.iter() {
target.push(format!("--enable-proposed-api={}", id));
}
}
self.code_options.add_code_args(target);
}
}
/// Arguments applicable whenever the desktop editor is launched
#[derive(Args, Debug, Default, Clone)]
pub struct DesktopCodeOptions {
/// Set the root path for extensions.
#[clap(long, value_name = "dir")]
pub extensions_dir: Option<String>,
/// Specifies the directory that user data is kept in. Can be used to
/// open multiple distinct instances of the editor.
#[clap(long, value_name = "dir")]
pub user_data_dir: Option<String>,
/// Sets the editor version to use for this command. The preferred version
/// can be persisted with `code version use <version>`. Can be "stable",
/// "insiders", a version number, or an absolute path to an existing install.
#[clap(long, value_name = "stable | insiders | x.y.z | path")]
pub use_version: Option<String>,
}
/// Argument specifying the output format.
#[derive(Args, Debug, Clone)]
pub struct OutputFormatOptions {
/// Set the data output formats.
#[clap(value_enum, long, value_name = "format", default_value_t = OutputFormat::Text)]
pub format: OutputFormat,
}
impl DesktopCodeOptions {
pub fn add_code_args(&self, target: &mut Vec<String>) {
if let Some(extensions_dir) = &self.extensions_dir {
target.push(format!("--extensions-dir={}", extensions_dir));
}
if let Some(user_data_dir) = &self.user_data_dir {
target.push(format!("--user-data-dir={}", user_data_dir));
}
}
}
#[derive(Args, Debug, Default, Clone)]
pub struct GlobalOptions {
/// Directory where CLI metadata should be stored.
#[clap(long, env = "VSCODE_CLI_DATA_DIR", global = true)]
pub cli_data_dir: Option<String>,
/// Print verbose output (implies --wait).
#[clap(long, global = true)]
pub verbose: bool,
/// Log to a file in addition to stdout. Used when running as a service.
#[clap(long, global = true, hide = true)]
pub log_to_file: Option<PathBuf>,
/// Log level to use.
#[clap(long, value_enum, value_name = "level", global = true)]
pub log: Option<log::Level>,
/// Disable telemetry for the current command, even if it was previously
/// accepted as part of the license prompt or specified in '--telemetry-level'
#[clap(long, global = true, hide = true)]
pub disable_telemetry: bool,
/// Sets the initial telemetry level
#[clap(value_enum, long, global = true, hide = true)]
pub telemetry_level: Option<options::TelemetryLevel>,
}
impl GlobalOptions {
pub fn add_code_args(&self, target: &mut Vec<String>) {
if self.verbose {
target.push("--verbose".to_string());
}
if let Some(log) = self.log {
target.push(format!("--log={}", log));
}
if self.disable_telemetry {
target.push("--disable-telemetry".to_string());
}
if let Some(telemetry_level) = &self.telemetry_level {
target.push(format!("--telemetry-level={}", telemetry_level));
}
}
}
#[derive(Args, Debug, Default, Clone)]
pub struct EditorTroubleshooting {
/// Run CPU profiler during startup.
#[clap(long)]
pub prof_startup: bool,
/// Disable all installed extensions.
#[clap(long)]
pub disable_extensions: bool,
/// Disable an extension.
#[clap(long, value_name = "ext-id")]
pub disable_extension: Vec<String>,
/// Turn sync on or off.
#[clap(value_enum, long, value_name = "on | off")]
pub sync: Option<SyncState>,
/// Allow debugging and profiling of extensions. Check the developer tools for the connection URI.
#[clap(long, value_name = "port")]
pub inspect_extensions: Option<u16>,
/// Allow debugging and profiling of extensions with the extension host
/// being paused after start. Check the developer tools for the connection URI.
#[clap(long, value_name = "port")]
pub inspect_brk_extensions: Option<u16>,
/// Disable GPU hardware acceleration.
#[clap(long)]
pub disable_gpu: bool,
/// Shows all telemetry events which the editor collects.
#[clap(long)]
pub telemetry: bool,
}
impl EditorTroubleshooting {
pub fn add_code_args(&self, target: &mut Vec<String>) {
if self.prof_startup {
target.push("--prof-startup".to_string());
}
if self.disable_extensions {
target.push("--disable-extensions".to_string());
}
for id in self.disable_extension.iter() {
target.push(format!("--disable-extension={}", id));
}
if let Some(sync) = &self.sync {
target.push(format!("--sync={}", sync));
}
if let Some(port) = &self.inspect_extensions {
target.push(format!("--inspect-extensions={}", port));
}
if let Some(port) = &self.inspect_brk_extensions {
target.push(format!("--inspect-brk-extensions={}", port));
}
if self.disable_gpu {
target.push("--disable-gpu".to_string());
}
if self.telemetry {
target.push("--telemetry".to_string());
}
}
}
#[derive(ValueEnum, Clone, Copy, Debug)]
pub enum SyncState {
On,
Off,
}
impl fmt::Display for SyncState {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
SyncState::Off => write!(f, "off"),
SyncState::On => write!(f, "on"),
}
}
}
#[derive(ValueEnum, Clone, Copy, Debug)]
pub enum OutputFormat {
Json,
Text,
}
#[derive(Args, Clone, Debug, Default)]
pub struct ExistingTunnelArgs {
/// Name you'd like to assign preexisting tunnel to use to connect the tunnel
#[clap(long, hide = true)]
pub tunnel_name: Option<String>,
/// Token to authenticate and use preexisting tunnel
#[clap(long, hide = true)]
pub host_token: Option<String>,
/// ID of preexisting tunnel to use to connect the tunnel
#[clap(long, hide = true)]
pub tunnel_id: Option<String>,
/// Cluster of preexisting tunnel to use to connect the tunnel
#[clap(long, hide = true)]
pub cluster: Option<String>,
}
#[derive(Args, Debug, Clone, Default)]
pub struct TunnelServeArgs {
/// Optional details to connect to an existing tunnel
#[clap(flatten, next_help_heading = Some("ADVANCED OPTIONS"))]
pub tunnel: ExistingTunnelArgs,
/// Randomly name machine for port forwarding service
#[clap(long)]
pub random_name: bool,
/// Prevents the machine going to sleep while this command runs.
#[clap(long)]
pub no_sleep: bool,
/// Sets the machine name for port forwarding service
#[clap(long)]
pub name: Option<String>,
/// Optional parent process id. If provided, the server will be stopped when the process of the given pid no longer exists
#[clap(long, hide = true)]
pub parent_process_id: Option<String>,
/// If set, the user accepts the server license terms and the server will be started without a user prompt.
#[clap(long)]
pub accept_server_license_terms: bool,
}
#[derive(Args, Debug, Clone)]
pub struct TunnelArgs {
#[clap(subcommand)]
pub subcommand: Option<TunnelSubcommand>,
#[clap(flatten)]
pub serve_args: TunnelServeArgs,
}
#[derive(Subcommand, Debug, Clone)]
pub enum TunnelSubcommand {
/// Delete all servers which are currently not running.
Prune,
/// Stops any running tunnel on the system.
Kill,
/// Restarts any running tunnel on the system.
Restart,
/// Gets whether there is a tunnel running on the current machineiou.
Status,
/// Rename the name of this machine associated with port forwarding service.
Rename(TunnelRenameArgs),
/// Remove this machine's association with the port forwarding service.
Unregister,
#[clap(subcommand)]
User(TunnelUserSubCommands),
/// (Preview) Manages the tunnel when installed as a system service,
#[clap(subcommand)]
Service(TunnelServiceSubCommands),
}
#[derive(Subcommand, Debug, Clone)]
pub enum TunnelServiceSubCommands {
/// Installs or re-installs the tunnel service on the machine.
Install(TunnelServiceInstallArgs),
/// Uninstalls and stops the tunnel service.
Uninstall,
/// Shows logs for the running service.
Log,
/// Internal command for running the service
#[clap(hide = true)]
InternalRun,
}
#[derive(Args, Debug, Clone)]
pub struct TunnelServiceInstallArgs {
/// If set, the user accepts the server license terms and the server will be started without a user prompt.
#[clap(long)]
pub accept_server_license_terms: bool,
}
#[derive(Args, Debug, Clone)]
pub struct TunnelRenameArgs {
/// The name you'd like to rename your machine to.
pub name: String,
}
#[derive(Subcommand, Debug, Clone)]
pub enum TunnelUserSubCommands {
/// Log in to port forwarding service
Login(LoginArgs),
/// Log out of port forwarding service
Logout,
/// Show the account that's logged into port forwarding service
Show,
}
#[derive(Args, Debug, Clone)]
pub struct LoginArgs {
/// An access token to store for authentication. Note: this will not be
/// refreshed if it expires!
#[clap(long, requires = "provider")]
pub access_token: Option<String>,
/// The auth provider to use. If not provided, a prompt will be shown.
#[clap(value_enum, long)]
pub provider: Option<AuthProvider>,
}
#[derive(clap::ValueEnum, Debug, Clone, Copy)]
pub enum AuthProvider {
Microsoft,
Github,
}