1use std::{fmt, path::PathBuf};
7
8use crate::{constants, log, options, tunnels::code_server::CodeServerArgs};
9use clap::{Args, Parser, Subcommand, ValueEnum};
10use const_format::concatcp;
11
12const CLI_NAME: &str = concatcp!(constants::PRODUCT_NAME_LONG, " CLI");
13const HELP_COMMANDS: &str = concatcp!(
14 "Usage: ",
15 constants::APPLICATION_NAME,
16 " [options][paths...]
17
18To read output from another program, append '-' (e.g. 'echo Hello World | {name} -')"
19);
20
21const STANDALONE_TEMPLATE: &str = concatcp!(
22 CLI_NAME,
23 " Standalone - {version}
24
25",
26 HELP_COMMANDS,
27 "
28Running editor commands requires installing ",
29 constants::QUALITYLESS_PRODUCT_NAME,
30 ", and may differ slightly.
31
32{all-args}"
33);
34const INTEGRATED_TEMPLATE: &str = concatcp!(
35 CLI_NAME,
36 " - {version}
37
38",
39 HELP_COMMANDS,
40 "
41
42{all-args}"
43);
44
45const COMMIT_IN_VERSION: &str = match constants::VSCODE_CLI_COMMIT {
46 Some(c) => c,
47 None => "unknown",
48};
49const NUMBER_IN_VERSION: &str = match constants::VSCODE_CLI_VERSION {
50 Some(c) => c,
51 None => "dev",
52};
53const VERSION: &str = concatcp!(NUMBER_IN_VERSION, " (commit ", COMMIT_IN_VERSION, ")");
54
55#[derive(Parser, Debug, Default)]
56#[clap(
57 help_template = INTEGRATED_TEMPLATE,
58 long_about = None,
59 name = constants::APPLICATION_NAME,
60 version = VERSION,
61 )]
62pub struct IntegratedCli {
63 #[clap(flatten)]
64 pub core: CliCore,
65}
66
67#[derive(Args, Debug, Default, Clone)]
69pub struct CliCore {
70 #[clap(name = "paths")]
72 pub open_paths: Vec<String>,
73
74 #[clap(flatten, next_help_heading = Some("EDITOR OPTIONS"))]
75 pub editor_options: EditorOptions,
76
77 #[clap(flatten, next_help_heading = Some("EDITOR TROUBLESHOOTING"))]
78 pub troubleshooting: EditorTroubleshooting,
79
80 #[clap(flatten, next_help_heading = Some("GLOBAL OPTIONS"))]
81 pub global_options: GlobalOptions,
82
83 #[clap(subcommand)]
84 pub subcommand: Option<Commands>,
85}
86
87#[derive(Parser, Debug, Default)]
88#[clap(
89 help_template = STANDALONE_TEMPLATE,
90 long_about = None,
91 version = VERSION,
92 name = constants::APPLICATION_NAME,
93 )]
94pub struct StandaloneCli {
95 #[clap(flatten)]
96 pub core: CliCore,
97
98 #[clap(subcommand)]
99 pub subcommand: Option<StandaloneCommands>,
100}
101
102pub enum AnyCli {
103 Integrated(IntegratedCli),
104 Standalone(StandaloneCli),
105}
106
107impl AnyCli {
108 pub fn core(&self) -> &CliCore {
109 match self {
110 AnyCli::Integrated(cli) => &cli.core,
111 AnyCli::Standalone(cli) => &cli.core,
112 }
113 }
114}
115
116impl CliCore {
117 pub fn get_base_code_args(&self) -> Vec<String> {
118 let mut args = self.open_paths.clone();
119 self.editor_options.add_code_args(&mut args);
120 self.troubleshooting.add_code_args(&mut args);
121 self.global_options.add_code_args(&mut args);
122 args
123 }
124}
125
126impl<'a> From<&'a CliCore> for CodeServerArgs {
127 fn from(cli: &'a CliCore) -> Self {
128 let mut args = CodeServerArgs {
129 log: cli.global_options.log,
130 accept_server_license_terms: true,
131 ..Default::default()
132 };
133
134 args.log = cli.global_options.log;
135 args.accept_server_license_terms = true;
136
137 if cli.global_options.verbose {
138 args.verbose = true;
139 }
140
141 if cli.global_options.disable_telemetry {
142 args.telemetry_level = Some(options::TelemetryLevel::Off);
143 } else if cli.global_options.telemetry_level.is_some() {
144 args.telemetry_level = cli.global_options.telemetry_level;
145 }
146
147 args
148 }
149}
150
151#[derive(Subcommand, Debug, Clone)]
152pub enum StandaloneCommands {
153 Update(StandaloneUpdateArgs),
155}
156
157#[derive(Args, Debug, Clone)]
158pub struct StandaloneUpdateArgs {
159 #[clap(long)]
161 pub check: bool,
162}
163
164#[derive(Subcommand, Debug, Clone)]
165
166pub enum Commands {
167 Tunnel(TunnelArgs),
170
171 #[clap(name = "ext")]
173 Extension(ExtensionArgs),
174
175 Status,
177
178 Version(VersionArgs),
180
181 #[clap(about = concatcp!("Runs a local web version of ", constants::PRODUCT_NAME_LONG))]
183 ServeWeb(ServeWebArgs),
184
185 #[clap(hide = true)]
187 CommandShell(CommandShellArgs),
188}
189
190#[derive(Args, Debug, Clone)]
191pub struct ServeWebArgs {
192 #[clap(long)]
194 pub host: Option<String>,
195 #[clap(long)]
197 pub socket_path: Option<String>,
198 #[clap(long, default_value_t = 8000)]
200 pub port: u16,
201 #[clap(long)]
203 pub connection_token: Option<String>,
204 #[clap(long)]
206 pub connection_token_file: Option<String>,
207 #[clap(long)]
209 pub without_connection_token: bool,
210 #[clap(long)]
212 pub accept_server_license_terms: bool,
213 #[clap(long)]
215 pub server_base_path: Option<String>,
216 #[clap(long)]
218 pub server_data_dir: Option<String>,
219 #[clap(long)]
221 pub user_data_dir: Option<String>,
222 #[clap(long)]
224 pub extensions_dir: Option<String>,
225}
226
227#[derive(Args, Debug, Clone)]
228pub struct CommandShellArgs {
229 #[clap(long)]
231 pub on_socket: bool,
232 #[clap(long, num_args = 0..=1, default_missing_value = "0")]
234 pub on_port: Option<u16>,
235 #[clap(long, env = "VSCODE_CLI_REQUIRE_TOKEN")]
237 pub require_token: Option<String>,
238 #[clap(long, hide = true)]
240 pub parent_process_id: Option<String>,
241}
242
243#[derive(Args, Debug, Clone)]
244pub struct ExtensionArgs {
245 #[clap(subcommand)]
246 pub subcommand: ExtensionSubcommand,
247
248 #[clap(flatten)]
249 pub desktop_code_options: DesktopCodeOptions,
250}
251
252impl ExtensionArgs {
253 pub fn add_code_args(&self, target: &mut Vec<String>) {
254 self.desktop_code_options.add_code_args(target);
255 self.subcommand.add_code_args(target);
256 }
257}
258
259#[derive(Subcommand, Debug, Clone)]
260pub enum ExtensionSubcommand {
261 List(ListExtensionArgs),
263 Install(InstallExtensionArgs),
265 Uninstall(UninstallExtensionArgs),
267 Update,
269}
270
271impl ExtensionSubcommand {
272 pub fn add_code_args(&self, target: &mut Vec<String>) {
273 match self {
274 ExtensionSubcommand::List(args) => {
275 target.push("--list-extensions".to_string());
276 if args.show_versions {
277 target.push("--show-versions".to_string());
278 }
279 if let Some(category) = &args.category {
280 target.push(format!("--category={}", category));
281 }
282 }
283 ExtensionSubcommand::Install(args) => {
284 for id in args.id_or_path.iter() {
285 target.push(format!("--install-extension={}", id));
286 }
287 if args.pre_release {
288 target.push("--pre-release".to_string());
289 }
290 if args.force {
291 target.push("--force".to_string());
292 }
293 }
294 ExtensionSubcommand::Uninstall(args) => {
295 for id in args.id.iter() {
296 target.push(format!("--uninstall-extension={}", id));
297 }
298 }
299 ExtensionSubcommand::Update => {
300 target.push("--update-extensions".to_string());
301 }
302 }
303 }
304}
305
306#[derive(Args, Debug, Clone)]
307pub struct ListExtensionArgs {
308 #[clap(long, value_name = "category")]
310 pub category: Option<String>,
311
312 #[clap(long)]
314 pub show_versions: bool,
315}
316
317#[derive(Args, Debug, Clone)]
318pub struct InstallExtensionArgs {
319 #[clap(name = "ext-id | id")]
324 pub id_or_path: Vec<String>,
325
326 #[clap(long)]
328 pub pre_release: bool,
329
330 #[clap(long)]
332 pub force: bool,
333}
334
335#[derive(Args, Debug, Clone)]
336pub struct UninstallExtensionArgs {
337 #[clap(name = "ext-id")]
341 pub id: Vec<String>,
342}
343
344#[derive(Args, Debug, Clone)]
345pub struct VersionArgs {
346 #[clap(subcommand)]
347 pub subcommand: VersionSubcommand,
348}
349
350#[derive(Subcommand, Debug, Clone)]
351pub enum VersionSubcommand {
352 Use(UseVersionArgs),
354
355 Show,
357}
358
359#[derive(Args, Debug, Clone)]
360pub struct UseVersionArgs {
361 #[clap(value_name = "stable | insiders | x.y.z | path")]
364 pub name: String,
365
366 #[clap(long, value_name = "path")]
368 pub install_dir: Option<String>,
369}
370
371#[derive(Args, Debug, Default, Clone)]
372pub struct EditorOptions {
373 #[clap(short, long, value_names = &["file", "file"])]
375 pub diff: Vec<String>,
376
377 #[clap(short, long, value_name = "folder")]
379 pub add: Option<String>,
380
381 #[clap(short, long, value_name = "file:line[:character]")]
383 pub goto: Option<String>,
384
385 #[clap(short, long)]
387 pub new_window: bool,
388
389 #[clap(short, long)]
391 pub reuse_window: bool,
392
393 #[clap(short, long)]
395 pub wait: bool,
396
397 #[clap(long, value_name = "locale")]
399 pub locale: Option<String>,
400
401 #[clap(long, value_name = "ext-id")]
404 pub enable_proposed_api: Vec<String>,
405
406 #[clap(flatten)]
407 pub code_options: DesktopCodeOptions,
408}
409
410impl EditorOptions {
411 pub fn add_code_args(&self, target: &mut Vec<String>) {
412 if !self.diff.is_empty() {
413 target.push("--diff".to_string());
414 for file in self.diff.iter() {
415 target.push(file.clone());
416 }
417 }
418 if let Some(add) = &self.add {
419 target.push("--add".to_string());
420 target.push(add.clone());
421 }
422 if let Some(goto) = &self.goto {
423 target.push("--goto".to_string());
424 target.push(goto.clone());
425 }
426 if self.new_window {
427 target.push("--new-window".to_string());
428 }
429 if self.reuse_window {
430 target.push("--reuse-window".to_string());
431 }
432 if self.wait {
433 target.push("--wait".to_string());
434 }
435 if let Some(locale) = &self.locale {
436 target.push(format!("--locale={}", locale));
437 }
438 if !self.enable_proposed_api.is_empty() {
439 for id in self.enable_proposed_api.iter() {
440 target.push(format!("--enable-proposed-api={}", id));
441 }
442 }
443 self.code_options.add_code_args(target);
444 }
445}
446
447#[derive(Args, Debug, Default, Clone)]
449pub struct DesktopCodeOptions {
450 #[clap(long, value_name = "dir")]
452 pub extensions_dir: Option<String>,
453
454 #[clap(long, value_name = "dir")]
457 pub user_data_dir: Option<String>,
458
459 #[clap(long, value_name = "stable | insiders | x.y.z | path")]
463 pub use_version: Option<String>,
464}
465
466#[derive(Args, Debug, Clone)]
468pub struct OutputFormatOptions {
469 #[clap(value_enum, long, value_name = "format", default_value_t = OutputFormat::Text)]
471 pub format: OutputFormat,
472}
473
474impl DesktopCodeOptions {
475 pub fn add_code_args(&self, target: &mut Vec<String>) {
476 if let Some(extensions_dir) = &self.extensions_dir {
477 target.push(format!("--extensions-dir={}", extensions_dir));
478 }
479 if let Some(user_data_dir) = &self.user_data_dir {
480 target.push(format!("--user-data-dir={}", user_data_dir));
481 }
482 }
483}
484
485#[derive(Args, Debug, Default, Clone)]
486pub struct GlobalOptions {
487 #[clap(long, env = "VSCODE_CLI_DATA_DIR", global = true)]
489 pub cli_data_dir: Option<String>,
490
491 #[clap(long, global = true)]
493 pub verbose: bool,
494
495 #[clap(long, global = true, hide = true)]
497 pub log_to_file: Option<PathBuf>,
498
499 #[clap(long, value_enum, value_name = "level", global = true)]
501 pub log: Option<log::Level>,
502
503 #[clap(long, global = true, hide = true)]
506 pub disable_telemetry: bool,
507
508 #[clap(value_enum, long, global = true, hide = true)]
510 pub telemetry_level: Option<options::TelemetryLevel>,
511}
512
513impl GlobalOptions {
514 pub fn add_code_args(&self, target: &mut Vec<String>) {
515 if self.verbose {
516 target.push("--verbose".to_string());
517 }
518 if let Some(log) = self.log {
519 target.push(format!("--log={}", log));
520 }
521 if self.disable_telemetry {
522 target.push("--disable-telemetry".to_string());
523 }
524 if let Some(telemetry_level) = &self.telemetry_level {
525 target.push(format!("--telemetry-level={}", telemetry_level));
526 }
527 }
528}
529
530#[derive(Args, Debug, Default, Clone)]
531pub struct EditorTroubleshooting {
532 #[clap(long)]
534 pub prof_startup: bool,
535
536 #[clap(long)]
538 pub disable_extensions: bool,
539
540 #[clap(long, value_name = "ext-id")]
542 pub disable_extension: Vec<String>,
543
544 #[clap(value_enum, long, value_name = "on | off")]
546 pub sync: Option<SyncState>,
547
548 #[clap(long, value_name = "port")]
550 pub inspect_extensions: Option<u16>,
551
552 #[clap(long, value_name = "port")]
555 pub inspect_brk_extensions: Option<u16>,
556
557 #[clap(long)]
559 pub disable_gpu: bool,
560
561 #[clap(long)]
563 pub telemetry: bool,
564}
565
566impl EditorTroubleshooting {
567 pub fn add_code_args(&self, target: &mut Vec<String>) {
568 if self.prof_startup {
569 target.push("--prof-startup".to_string());
570 }
571 if self.disable_extensions {
572 target.push("--disable-extensions".to_string());
573 }
574 for id in self.disable_extension.iter() {
575 target.push(format!("--disable-extension={}", id));
576 }
577 if let Some(sync) = &self.sync {
578 target.push(format!("--sync={}", sync));
579 }
580 if let Some(port) = &self.inspect_extensions {
581 target.push(format!("--inspect-extensions={}", port));
582 }
583 if let Some(port) = &self.inspect_brk_extensions {
584 target.push(format!("--inspect-brk-extensions={}", port));
585 }
586 if self.disable_gpu {
587 target.push("--disable-gpu".to_string());
588 }
589 if self.telemetry {
590 target.push("--telemetry".to_string());
591 }
592 }
593}
594
595#[derive(ValueEnum, Clone, Copy, Debug)]
596pub enum SyncState {
597 On,
598 Off,
599}
600
601impl fmt::Display for SyncState {
602 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
603 match self {
604 SyncState::Off => write!(f, "off"),
605 SyncState::On => write!(f, "on"),
606 }
607 }
608}
609
610#[derive(ValueEnum, Clone, Copy, Debug)]
611pub enum OutputFormat {
612 Json,
613 Text,
614}
615
616#[derive(Args, Clone, Debug, Default)]
617pub struct ExistingTunnelArgs {
618 #[clap(long, hide = true)]
621 pub tunnel_name: Option<String>,
622
623 #[clap(long, hide = true)]
625 pub host_token: Option<String>,
626
627 #[clap(long, hide = true)]
629 pub tunnel_id: Option<String>,
630
631 #[clap(long, hide = true)]
633 pub cluster: Option<String>,
634}
635
636#[derive(Args, Debug, Clone, Default)]
637pub struct TunnelServeArgs {
638 #[clap(flatten, next_help_heading = Some("ADVANCED OPTIONS"))]
640 pub tunnel: ExistingTunnelArgs,
641
642 #[clap(long)]
644 pub random_name: bool,
645
646 #[clap(long)]
648 pub no_sleep: bool,
649
650 #[clap(long)]
652 pub name: Option<String>,
653
654 #[clap(long, hide = true)]
656 pub parent_process_id: Option<String>,
657
658 #[clap(long)]
660 pub accept_server_license_terms: bool,
661
662 #[clap(long)]
664 pub install_extension: Vec<String>,
665}
666
667impl TunnelServeArgs {
668 pub fn apply_to_server_args(&self, csa: &mut CodeServerArgs) {
669 csa.install_extensions
670 .extend_from_slice(&self.install_extension);
671 }
672}
673
674#[derive(Args, Debug, Clone)]
675pub struct TunnelArgs {
676 #[clap(subcommand)]
677 pub subcommand: Option<TunnelSubcommand>,
678
679 #[clap(flatten)]
680 pub serve_args: TunnelServeArgs,
681}
682
683#[derive(Subcommand, Debug, Clone)]
684pub enum TunnelSubcommand {
685 Prune,
687
688 Kill,
690
691 Restart,
693
694 Status,
696
697 Rename(TunnelRenameArgs),
699
700 Unregister,
702
703 #[clap(subcommand)]
704 User(TunnelUserSubCommands),
705
706 #[clap(subcommand)]
708 Service(TunnelServiceSubCommands),
709
710 #[clap(hide = true)]
712 ForwardInternal(TunnelForwardArgs),
713}
714
715#[derive(Subcommand, Debug, Clone)]
716pub enum TunnelServiceSubCommands {
717 Install(TunnelServiceInstallArgs),
719
720 Uninstall,
722
723 Log,
725
726 #[clap(hide = true)]
728 InternalRun,
729}
730
731#[derive(Args, Debug, Clone)]
732pub struct TunnelServiceInstallArgs {
733 #[clap(long)]
735 pub accept_server_license_terms: bool,
736
737 #[clap(long)]
739 pub name: Option<String>,
740}
741
742#[derive(Args, Debug, Clone)]
743pub struct TunnelRenameArgs {
744 pub name: String,
746}
747
748#[derive(Args, Debug, Clone)]
749pub struct TunnelForwardArgs {
750 pub ports: Vec<u16>,
752
753 #[clap(flatten)]
755 pub login: LoginArgs,
756}
757
758#[derive(Subcommand, Debug, Clone)]
759pub enum TunnelUserSubCommands {
760 Login(LoginArgs),
762
763 Logout,
765
766 Show,
768}
769
770#[derive(Args, Debug, Clone)]
771pub struct LoginArgs {
772 #[clap(long, requires = "provider")]
775 pub access_token: Option<String>,
776
777 #[clap(value_enum, long)]
779 pub provider: Option<AuthProvider>,
780}
781
782#[derive(clap::ValueEnum, Debug, Clone, Copy)]
783pub enum AuthProvider {
784 Microsoft,
785 Github,
786}