Skip to main content

oxide_cli/
completions.rs

1use std::{
2  collections::HashSet,
3  fs,
4  io::ErrorKind,
5  path::{Path, PathBuf},
6};
7
8use anyhow::{Context, Result};
9use colored::Colorize;
10
11use crate::addons::{cache::AddonsCache, manifest::AddonManifest};
12
13/// Install the shell completion script to the appropriate location automatically.
14pub fn install_completions(shell: &str) -> Result<()> {
15  match shell {
16    "bash" => install_bash(),
17    "zsh" => install_zsh(),
18    "fish" => install_fish(),
19    "powershell" => install_powershell(),
20    other => {
21      eprintln!("Unsupported shell '{other}'. Supported: bash, zsh, fish, powershell");
22      Ok(())
23    }
24  }
25}
26
27fn install_bash() -> Result<()> {
28  // ~/.local/share/bash-completion/completions/ is the standard user-level location
29  // that bash-completion picks up automatically — no .bashrc changes needed.
30  let dir = bash_completions_dir()?;
31  fs::create_dir_all(&dir)
32    .with_context(|| format!("Failed to create directory {}", dir.display()))?;
33  let dest = dir.join("oxide");
34  fs::write(&dest, BASH_SCRIPT).with_context(|| format!("Failed to write {}", dest.display()))?;
35
36  println!(
37    "{} Bash completions installed to {}",
38    "✔".green(),
39    dest.display()
40  );
41  println!(
42    "  Open a new terminal, or run: {}",
43    format!("source {}", dest.display()).cyan()
44  );
45  println!(
46    "  Note: requires the {} package to be installed.",
47    "bash-completion".cyan()
48  );
49  Ok(())
50}
51
52fn bash_completions_dir() -> Result<PathBuf> {
53  let home = dirs::home_dir().context("Could not determine home directory")?;
54  Ok(home.join(".local/share/bash-completion/completions"))
55}
56
57fn install_zsh() -> Result<()> {
58  // If the user has a $ZDOTDIR/completions/ directory (e.g. HyDE), write a
59  // directly-sourceable script there — no fpath changes needed.
60  // Otherwise fall back to the standard ~/.zfunc/_oxide fpath approach.
61  if let Some(dir) = zdotdir_completions_dir() {
62    fs::create_dir_all(&dir)
63      .with_context(|| format!("Failed to create directory {}", dir.display()))?;
64    let dest = dir.join("oxide.zsh");
65    fs::write(&dest, ZSH_SOURCED_SCRIPT)
66      .with_context(|| format!("Failed to write {}", dest.display()))?;
67    println!(
68      "{} Zsh completions installed to {}",
69      "✔".green(),
70      dest.display()
71    );
72    println!("  Restart your terminal or open a new tab — completions are active immediately.");
73  } else {
74    let dir = home_zfunc_dir()?;
75    fs::create_dir_all(&dir)
76      .with_context(|| format!("Failed to create directory {}", dir.display()))?;
77    let dest = dir.join("_oxide");
78    fs::write(&dest, ZSH_FPATH_SCRIPT)
79      .with_context(|| format!("Failed to write {}", dest.display()))?;
80    println!(
81      "{} Zsh completions installed to {}",
82      "✔".green(),
83      dest.display()
84    );
85    println!("  Ensure your ~/.zshrc contains:");
86    println!("    {}", "fpath=(~/.zfunc $fpath)".cyan());
87    println!("    {}", "autoload -Uz compinit && compinit".cyan());
88  }
89  Ok(())
90}
91
92/// Returns `$ZDOTDIR/completions/` only when it looks like a HyDE setup:
93/// the directory exists AND `$ZDOTDIR/.hyde.zshrc` or `hyde-cli` is present.
94fn zdotdir_completions_dir() -> Option<PathBuf> {
95  // Only check an explicit $ZDOTDIR — do not fall back to ~/.config/zsh,
96  // because that directory may exist on non-HyDE systems without any
97  // auto-sourcing of its contents.
98  let zdotdir = std::env::var("ZDOTDIR").map(PathBuf::from).ok()?;
99  let dir = zdotdir.join("completions");
100  if !dir.is_dir() {
101    return None;
102  }
103  // Confirm this is a HyDE environment before using the sourced-script path.
104  let is_hyde = zdotdir.join(".hyde.zshrc").exists() || which::which("hyde-cli").is_ok();
105  if is_hyde { Some(dir) } else { None }
106}
107
108fn home_zfunc_dir() -> Result<PathBuf> {
109  let home = dirs::home_dir().context("Could not determine home directory")?;
110  Ok(home.join(".zfunc"))
111}
112
113fn install_fish() -> Result<()> {
114  // ~/.config/fish/completions/ is the standard location for fish completions.
115  let dir = fish_completions_dir()?;
116  fs::create_dir_all(&dir)
117    .with_context(|| format!("Failed to create directory {}", dir.display()))?;
118  let dest = dir.join("oxide.fish");
119  fs::write(&dest, FISH_SCRIPT).with_context(|| format!("Failed to write {}", dest.display()))?;
120
121  println!(
122    "{} Fish completions installed to {}",
123    "✔".green(),
124    dest.display()
125  );
126  println!("  Completions are active immediately in new fish sessions.");
127  Ok(())
128}
129
130fn fish_completions_dir() -> Result<PathBuf> {
131  // Respect $XDG_CONFIG_HOME if set, otherwise fall back to ~/.config
132  let config_dir = std::env::var("XDG_CONFIG_HOME")
133    .map(PathBuf::from)
134    .unwrap_or_else(|_| {
135      dirs::home_dir()
136        .expect("Could not determine home directory")
137        .join(".config")
138    });
139  Ok(config_dir.join("fish/completions"))
140}
141
142fn install_powershell() -> Result<()> {
143  let script_path = powershell_script_path()?;
144  write_completion_script(&script_path, POWERSHELL_SCRIPT)?;
145
146  let profiles = powershell_profile_paths()?;
147  for profile in &profiles {
148    upsert_powershell_profile(profile, &script_path)?;
149  }
150
151  println!(
152    "{} PowerShell completions installed to {}",
153    "✔".green(),
154    script_path.display()
155  );
156  for profile in &profiles {
157    println!("  Registered in {}", profile.display());
158  }
159  println!("  Open a new PowerShell session to use completions.");
160  Ok(())
161}
162
163fn write_completion_script(path: &Path, script: &str) -> Result<()> {
164  let dir = path
165    .parent()
166    .context("Completion script path is missing a parent directory")?;
167  fs::create_dir_all(dir)
168    .with_context(|| format!("Failed to create directory {}", dir.display()))?;
169  fs::write(path, script).with_context(|| format!("Failed to write {}", path.display()))?;
170  Ok(())
171}
172
173fn powershell_script_path() -> Result<PathBuf> {
174  let home = dirs::home_dir().context("Could not determine home directory")?;
175  Ok(home.join(".oxide").join("completions").join("oxide.ps1"))
176}
177
178fn powershell_profile_paths() -> Result<Vec<PathBuf>> {
179  let documents_dir = dirs::document_dir()
180    .or_else(|| dirs::home_dir().map(|home| home.join("Documents")))
181    .context("Could not determine documents directory")?;
182  Ok(powershell_profile_paths_in(&documents_dir))
183}
184
185fn powershell_profile_paths_in(documents_dir: &Path) -> Vec<PathBuf> {
186  vec![
187    documents_dir
188      .join("PowerShell")
189      .join("Microsoft.PowerShell_profile.ps1"),
190    documents_dir
191      .join("WindowsPowerShell")
192      .join("Microsoft.PowerShell_profile.ps1"),
193  ]
194}
195
196fn upsert_powershell_profile(profile_path: &Path, script_path: &Path) -> Result<()> {
197  let dir = profile_path
198    .parent()
199    .context("Profile path is missing a parent directory")?;
200  fs::create_dir_all(dir)
201    .with_context(|| format!("Failed to create directory {}", dir.display()))?;
202
203  let existing = match fs::read_to_string(profile_path) {
204    Ok(content) => content,
205    Err(err) if err.kind() == ErrorKind::NotFound => String::new(),
206    Err(err) => {
207      return Err(err).with_context(|| format!("Failed to read {}", profile_path.display()));
208    }
209  };
210
211  let updated = upsert_managed_block(
212    &existing,
213    &powershell_profile_snippet(script_path),
214    POWERSHELL_PROFILE_START_MARKER,
215    POWERSHELL_PROFILE_END_MARKER,
216  );
217
218  if updated != existing {
219    fs::write(profile_path, updated)
220      .with_context(|| format!("Failed to write {}", profile_path.display()))?;
221  }
222
223  Ok(())
224}
225
226fn powershell_profile_snippet(script_path: &Path) -> String {
227  let script_path = powershell_single_quote(script_path);
228  format!(
229    "{POWERSHELL_PROFILE_START_MARKER}\n\
230$oxideCompletionScript = '{script_path}'\n\
231if (Test-Path $oxideCompletionScript) {{\n\
232  . $oxideCompletionScript\n\
233}}\n\
234{POWERSHELL_PROFILE_END_MARKER}"
235  )
236}
237
238#[doc(hidden)]
239pub fn powershell_profile_paths_in_for_tests(documents_dir: &Path) -> Vec<PathBuf> {
240  powershell_profile_paths_in(documents_dir)
241}
242
243#[doc(hidden)]
244pub fn powershell_profile_snippet_for_tests(script_path: &Path) -> String {
245  powershell_profile_snippet(script_path)
246}
247
248#[doc(hidden)]
249pub fn upsert_managed_block_for_tests(
250  content: &str,
251  block: &str,
252  start_marker: &str,
253  end_marker: &str,
254) -> String {
255  upsert_managed_block(content, block, start_marker, end_marker)
256}
257
258#[doc(hidden)]
259pub fn powershell_script_for_tests() -> &'static str {
260  POWERSHELL_SCRIPT
261}
262
263fn powershell_single_quote(path: &Path) -> String {
264  path.to_string_lossy().replace('\'', "''")
265}
266
267fn upsert_managed_block(
268  content: &str,
269  block: &str,
270  start_marker: &str,
271  end_marker: &str,
272) -> String {
273  let mut content = content.replace("\r\n", "\n");
274  let block = format!("{block}\n");
275
276  if let Some(start) = content.find(start_marker)
277    && let Some(end_rel) = content[start..].find(end_marker)
278  {
279    let end_marker_end = start + end_rel + end_marker.len();
280    let block_end = content[end_marker_end..]
281      .find('\n')
282      .map(|idx| end_marker_end + idx + 1)
283      .unwrap_or(content.len());
284    content.replace_range(start..block_end, &block);
285    return content;
286  }
287
288  if !content.is_empty() && !content.ends_with('\n') {
289    content.push('\n');
290  }
291  if !content.is_empty() {
292    content.push('\n');
293  }
294  content.push_str(&block);
295  content
296}
297
298/// Print dynamic completions to stdout — called by the generated completion scripts.
299///
300/// - No `addon_id`: prints installed addon IDs, one per line.
301/// - With `addon_id`: prints that addon's command names, one per line.
302///
303/// Errors are silently ignored so a broken cache never crashes the shell.
304pub fn print_dynamic_completions(addons_dir: &Path, addon_id: Option<&str>) {
305  match addon_id {
306    None => print_addon_ids(addons_dir),
307    Some(id) => print_addon_commands(addons_dir, id),
308  }
309}
310
311fn print_addon_ids(addons_dir: &Path) {
312  let index = addons_dir.join("oxide-addons.json");
313  let Ok(content) = std::fs::read_to_string(&index) else {
314    return;
315  };
316  let Ok(cache) = serde_json::from_str::<AddonsCache>(&content) else {
317    return;
318  };
319  for addon in &cache.addons {
320    println!("{}", addon.id);
321  }
322}
323
324fn print_addon_commands(addons_dir: &Path, addon_id: &str) {
325  let manifest_path = addons_dir.join(addon_id).join("oxide.addon.json");
326  let Ok(content) = std::fs::read_to_string(&manifest_path) else {
327    return;
328  };
329  let Ok(manifest) = serde_json::from_str::<AddonManifest>(&content) else {
330    return;
331  };
332  let mut seen: HashSet<String> = HashSet::new();
333  for variant in &manifest.variants {
334    for cmd in &variant.commands {
335      if seen.insert(cmd.name.clone()) {
336        println!("{}", cmd.name);
337      }
338    }
339  }
340}
341
342// ── Shell scripts ─────────────────────────────────────────────────────────────
343
344const BASH_SCRIPT: &str = r#"# oxide shell completions for bash
345# Source this file or append it to ~/.bashrc:
346#   oxide completions bash >> ~/.bashrc
347
348_oxide_completions() {
349  local cur prev
350  cur="${COMP_WORDS[COMP_CWORD]}"
351  prev="${COMP_WORDS[COMP_CWORD-1]}"
352
353  local static_top="new template addon login logout account completions"
354  local template_subs="install list remove publish update"
355  local addon_subs="install list remove publish update"
356
357  case $COMP_CWORD in
358    1)
359      local addon_ids
360      addon_ids=$(oxide _complete 2>/dev/null)
361      COMPREPLY=($(compgen -W "$static_top $addon_ids" -- "$cur"))
362      ;;
363    2)
364      case $prev in
365        template|t) COMPREPLY=($(compgen -W "$template_subs" -- "$cur")) ;;
366        addon|a)     COMPREPLY=($(compgen -W "$addon_subs" -- "$cur")) ;;
367        *)
368          local addon_cmds
369          addon_cmds=$(oxide _complete "$prev" 2>/dev/null)
370          if [ -n "$addon_cmds" ]; then
371            COMPREPLY=($(compgen -W "$addon_cmds" -- "$cur"))
372          fi
373          ;;
374      esac
375      ;;
376  esac
377}
378
379complete -F _oxide_completions oxide
380"#;
381
382// Used when saving to $ZDOTDIR/completions/ (e.g. HyDE) — sourced directly,
383// so no #compdef header; registers via `compdef` instead.
384const ZSH_SOURCED_SCRIPT: &str = r#"# oxide shell completions for zsh
385# Auto-installed by: oxide completions zsh
386# Loaded automatically by your shell config via _load_completions().
387
388if command -v oxide &>/dev/null; then
389  _oxide() {
390    local state
391
392    _arguments \
393      '1: :->cmd' \
394      '2: :->subcmd' && return 0
395
396    case $state in
397      cmd)
398        local static_cmds=(
399          'new:Create a new project from a template (oxide n)'
400          'template:Manage templates (oxide t)'
401          'addon:Manage addons (oxide a)'
402          'login:Log in to your Oxide account (oxide in)'
403          'logout:Log out of your Oxide account (oxide out)'
404          'account:Show account information'
405          'completions:Install shell completion script'
406        )
407        local addon_ids
408        addon_ids=(${(f)"$(oxide _complete 2>/dev/null)"})
409        _describe 'command' static_cmds
410        [[ ${#addon_ids[@]} -gt 0 ]] && compadd -- "${addon_ids[@]}"
411        ;;
412      subcmd)
413        case ${words[2]} in
414          template|t)
415            local subs=(
416              'install:Download and cache a template (i)'
417              'list:List installed templates (l)'
418              'remove:Remove a template from cache (r)'
419              'publish:Publish a GitHub repository as a template (p)'
420              'update:Update a published template (u)'
421            )
422            _describe 'subcommand' subs
423            ;;
424          addon|a)
425            local subs=(
426              'install:Install and cache an addon (i)'
427              'list:List installed addons (l)'
428              'remove:Remove a cached addon (r)'
429              'publish:Publish a GitHub repository as an addon (p)'
430              'update:Update a published addon (u)'
431            )
432            _describe 'subcommand' subs
433            ;;
434          *)
435            local addon_cmds
436            addon_cmds=(${(f)"$(oxide _complete "${words[2]}" 2>/dev/null)"})
437            [[ ${#addon_cmds[@]} -gt 0 ]] && compadd -- "${addon_cmds[@]}"
438            ;;
439        esac
440        ;;
441    esac
442  }
443
444  compdef _oxide oxide
445fi
446"#;
447
448// Used when saving to ~/.zfunc/_oxide — loaded via fpath, needs #compdef header.
449const ZSH_FPATH_SCRIPT: &str = r#"#compdef oxide
450# oxide shell completions for zsh
451# Auto-installed by: oxide completions zsh
452# Requires ~/.zfunc in your fpath and compinit called in ~/.zshrc.
453
454_oxide() {
455  local state
456
457  _arguments \
458    '1: :->cmd' \
459    '2: :->subcmd' && return 0
460
461  case $state in
462    cmd)
463      local static_cmds=(
464        'new:Create a new project from a template (oxide n)'
465        'template:Manage templates (oxide t)'
466        'addon:Manage addons (oxide a)'
467        'login:Log in to your Oxide account (oxide in)'
468        'logout:Log out of your Oxide account (oxide out)'
469        'account:Show account information'
470        'completions:Install shell completion script'
471      )
472      local addon_ids
473      addon_ids=(${(f)"$(oxide _complete 2>/dev/null)"})
474      _describe 'command' static_cmds
475      [[ ${#addon_ids[@]} -gt 0 ]] && compadd -- "${addon_ids[@]}"
476      ;;
477    subcmd)
478      case ${words[2]} in
479        template|t)
480          local subs=(
481            'install:Download and cache a template (i)'
482            'list:List installed templates (l)'
483            'remove:Remove a template from cache (r)'
484            'publish:Publish a GitHub repository as a template (p)'
485            'update:Update a published template (u)'
486          )
487          _describe 'subcommand' subs
488          ;;
489        addon|a)
490          local subs=(
491            'install:Install and cache an addon (i)'
492            'list:List installed addons (l)'
493            'remove:Remove a cached addon (r)'
494            'publish:Publish a GitHub repository as an addon (p)'
495            'update:Update a published addon (u)'
496          )
497          _describe 'subcommand' subs
498          ;;
499        *)
500          local addon_cmds
501          addon_cmds=(${(f)"$(oxide _complete "${words[2]}" 2>/dev/null)"})
502          [[ ${#addon_cmds[@]} -gt 0 ]] && compadd -- "${addon_cmds[@]}"
503          ;;
504      esac
505      ;;
506  esac
507}
508
509_oxide
510"#;
511
512const FISH_SCRIPT: &str = r#"# oxide shell completions for fish
513# Save to your fish completions directory:
514#   oxide completions fish > ~/.config/fish/completions/oxide.fish
515
516# Disable file completions for oxide globally
517complete -c oxide -f
518
519# ── Top-level subcommands ──────────────────────────────────────────────────────
520complete -c oxide -n '__fish_use_subcommand' -a 'new'         -d 'Create a new project from a template (oxide n)'
521complete -c oxide -n '__fish_use_subcommand' -a 'template'    -d 'Manage templates (oxide t)'
522complete -c oxide -n '__fish_use_subcommand' -a 'addon'       -d 'Manage addons (oxide a)'
523complete -c oxide -n '__fish_use_subcommand' -a 'login'       -d 'Log in to your Oxide account (oxide in)'
524complete -c oxide -n '__fish_use_subcommand' -a 'logout'      -d 'Log out of your Oxide account (oxide out)'
525complete -c oxide -n '__fish_use_subcommand' -a 'account'     -d 'Show account information'
526complete -c oxide -n '__fish_use_subcommand' -a 'completions' -d 'Install shell completion script'
527
528# Dynamic addon IDs from cache (automatically updated as addons are installed)
529complete -c oxide -n '__fish_use_subcommand' -a '(oxide _complete 2>/dev/null)'
530
531# ── template subcommands ───────────────────────────────────────────────────────
532complete -c oxide -n '__fish_seen_subcommand_from template t' -a 'install' -d 'Download and cache a template (i)'
533complete -c oxide -n '__fish_seen_subcommand_from template t' -a 'list'    -d 'List installed templates (l)'
534complete -c oxide -n '__fish_seen_subcommand_from template t' -a 'remove'  -d 'Remove a template from cache (r)'
535complete -c oxide -n '__fish_seen_subcommand_from template t' -a 'publish' -d 'Publish a GitHub repository as a template (p)'
536complete -c oxide -n '__fish_seen_subcommand_from template t' -a 'update'  -d 'Update a published template (u)'
537
538# ── addon subcommands ──────────────────────────────────────────────────────────
539complete -c oxide -n '__fish_seen_subcommand_from addon a' -a 'install' -d 'Install and cache an addon (i)'
540complete -c oxide -n '__fish_seen_subcommand_from addon a' -a 'list'    -d 'List installed addons (l)'
541complete -c oxide -n '__fish_seen_subcommand_from addon a' -a 'remove'  -d 'Remove a cached addon (r)'
542complete -c oxide -n '__fish_seen_subcommand_from addon a' -a 'publish' -d 'Publish a GitHub repository as an addon (p)'
543complete -c oxide -n '__fish_seen_subcommand_from addon a' -a 'update'  -d 'Update a published addon (u)'
544
545# ── Dynamic addon commands ─────────────────────────────────────────────────────
546# When the second token is an installed addon ID, complete with its commands.
547# This fires automatically after every `oxide addon install <id>` — no extra steps needed.
548complete -c oxide \
549  -n 'test (count (commandline -opc)) -eq 2; and not contains -- (commandline -opc)[2] new template addon login logout account completions' \
550  -a '(oxide _complete (commandline -opc)[2] 2>/dev/null)'
551"#;
552
553const POWERSHELL_PROFILE_START_MARKER: &str = "# oxide completions start";
554const POWERSHELL_PROFILE_END_MARKER: &str = "# oxide completions end";
555
556const POWERSHELL_SCRIPT: &str = r#"# oxide shell completions for PowerShell
557
558function New-OxideCompletionResult {
559  param(
560    [string] $Value,
561    [string] $ToolTip
562  )
563
564  [System.Management.Automation.CompletionResult]::new($Value, $Value, 'ParameterValue', $ToolTip)
565}
566
567$registerOxideCompleter = @{
568  CommandName = 'oxide'
569  ScriptBlock = {
570    param($wordToComplete, $commandAst, $cursorPosition)
571
572    if ($null -eq $wordToComplete) {
573      $wordToComplete = ''
574    }
575
576    $commandName = if ($commandAst.CommandElements.Count -gt 0) {
577      $commandAst.CommandElements[0].Extent.Text
578    } else {
579      'oxide'
580    }
581
582    $tokens = @($commandAst.CommandElements | Select-Object -Skip 1 | ForEach-Object {
583      $_.Extent.Text
584    })
585
586    $topLevel = @(
587      @{ Value = 'new';         ToolTip = 'Create a new project from a template (oxide n)' }
588      @{ Value = 'template';    ToolTip = 'Manage templates (oxide t)' }
589      @{ Value = 'addon';       ToolTip = 'Manage addons (oxide a)' }
590      @{ Value = 'login';       ToolTip = 'Log in to your Oxide account (oxide in)' }
591      @{ Value = 'logout';      ToolTip = 'Log out of your Oxide account (oxide out)' }
592      @{ Value = 'account';     ToolTip = 'Show account information' }
593      @{ Value = 'completions'; ToolTip = 'Install shell completions' }
594      @{ Value = 'n';           ToolTip = 'Alias for new' }
595      @{ Value = 't';           ToolTip = 'Alias for template' }
596      @{ Value = 'a';           ToolTip = 'Alias for addon' }
597      @{ Value = 'in';          ToolTip = 'Alias for login' }
598      @{ Value = 'out';         ToolTip = 'Alias for logout' }
599    )
600
601    $templateSubcommands = @(
602      @{ Value = 'install'; ToolTip = 'Download and cache a template (i)' }
603      @{ Value = 'list';    ToolTip = 'List installed templates (l)' }
604      @{ Value = 'remove';  ToolTip = 'Remove a template from cache (r)' }
605      @{ Value = 'publish'; ToolTip = 'Publish a GitHub repository as a template (p)' }
606      @{ Value = 'update';  ToolTip = 'Update a published template (u)' }
607    )
608
609    $addonSubcommands = @(
610      @{ Value = 'install'; ToolTip = 'Install and cache an addon (i)' }
611      @{ Value = 'list';    ToolTip = 'List installed addons (l)' }
612      @{ Value = 'remove';  ToolTip = 'Remove a cached addon (r)' }
613      @{ Value = 'publish'; ToolTip = 'Publish a GitHub repository as an addon (p)' }
614      @{ Value = 'update';  ToolTip = 'Update a published addon (u)' }
615    )
616
617    $completionShells = @(
618      @{ Value = 'bash';       ToolTip = 'Install bash completions' }
619      @{ Value = 'zsh';        ToolTip = 'Install zsh completions' }
620      @{ Value = 'fish';       ToolTip = 'Install fish completions' }
621      @{ Value = 'powershell'; ToolTip = 'Install PowerShell completions' }
622    )
623
624    function Complete-OxideItems {
625      param([object[]] $Items)
626
627      foreach ($item in $Items) {
628        if ($item.Value -like "$wordToComplete*") {
629          New-OxideCompletionResult -Value $item.Value -ToolTip $item.ToolTip
630        }
631      }
632    }
633
634    if ($tokens.Count -le 1) {
635      Complete-OxideItems $topLevel
636      $addonIds = & $commandName _complete 2>$null
637      foreach ($addonId in $addonIds) {
638        if ($addonId -like "$wordToComplete*") {
639          New-OxideCompletionResult -Value $addonId -ToolTip 'Installed addon'
640        }
641      }
642      return
643    }
644
645    $first = $tokens[0]
646    if ($tokens.Count -eq 2) {
647      switch ($first) {
648        'template' { Complete-OxideItems $templateSubcommands; return }
649        't' { Complete-OxideItems $templateSubcommands; return }
650        'addon' { Complete-OxideItems $addonSubcommands; return }
651        'a' { Complete-OxideItems $addonSubcommands; return }
652        'completions' { Complete-OxideItems $completionShells; return }
653        default {
654          $addonCommands = & $commandName _complete $first 2>$null
655          foreach ($addonCommand in $addonCommands) {
656            if ($addonCommand -like "$wordToComplete*") {
657              New-OxideCompletionResult -Value $addonCommand -ToolTip 'Addon command'
658            }
659          }
660          return
661        }
662      }
663    }
664  }
665}
666
667if ((Get-Command Register-ArgumentCompleter).Parameters.ContainsKey('Native')) {
668  $registerOxideCompleter.Native = $true
669}
670
671Register-ArgumentCompleter @registerOxideCompleter
672"#;