Integrace prostředí

Počínaje terminálem 1.15 Preview se terminál Windows začal experimentálně podporovat některé funkce integrace prostředí. Tyto funkce usnadňují používání příkazového řádku. V dřívějších verzích jsme povolili prostředí, abychom terminálu řekli, co je aktuální pracovní adresář. Teď jsme přidali podporu dalších sekvencí, které vašemu prostředí umožní sémanticky popsat části výstupu terminálu jako "prompt", "command" nebo "output". Prostředí také může terminálu sdělit, jestli byl příkaz úspěšný nebo neúspěšný.

Toto je průvodce některými funkcemi integrace prostředí, které jsme zavedli v terminálové verzi 1.18. V budoucnu plánujeme vytvářet ještě více funkcí, takže rádi získáme další zpětnou vazbu o tom, jak je lidé používají.

Poznámka: Od terminálu 1.21 jsou značky nyní stabilní funkcí. Před verzí 1.21 byly značky povoleny pouze pro buildy Preview terminálu. Pokud používáte verzi terminálu před verzí 1.21, showMarksOnScrollbar nastavení bylo pojmenováno experimental.showMarksOnScrollbara autoMarkPrompts bylo pojmenováno experimental.autoMarkPrompts.

Jak to funguje?

Integrace prostředí funguje tak, že prostředí (nebo libovolná aplikace příkazového řádku) zapíše do terminálu speciální řídicí sekvence. Tyto řídicí sekvence nejsou vytištěny do terminálu – místo toho poskytují bity metadat, které terminál může použít, aby věděli více o tom, co se děje v aplikaci. Když tyto sekvence připnete do příkazového řádku prostředí, můžete prostředí průběžně poskytovat informace do terminálu, který zná jenom prostředí.

Pro následující sekvence:

  • OSC je řetězec "\x1b]" – řídicí znak následovaný řídicím znakem. ]
  • ST je "ukončovací znak řetězce" a může být buď \x1b\ (znak ESC, následovaný \) nebo \x7 (znak BEL).
  • Mezery jsou jen ilustrativní.
  • Řetězce jsou <> parametry, které by měly být nahrazeny jinou hodnotou.

Příslušné podporované sekvence integrace prostředí v terminálové verzi 1.18 jsou následující:

  • OSC 133 ; A ST ("FTCS_PROMPT") – začátek výzvy.
  • OSC 133 ; B ST ("FTCS_COMMAND_START") – začátek příkazového řádku (READ: konec příkazového řádku).
  • OSC 133 ; C ST ("FTCS_COMMAND_EXECUTED") – začátek výstupu příkazu / konec příkazového řádku.
  • OSC 133 ; D ; <ExitCode> ST ("FTCS_COMMAND_FINISHED") – konec příkazu. ExitCode Pokud ExitCode je k dispozici, terminál bude považovat 0 za úspěch a cokoli jiného jako chybu. Pokud tento parametr vynecháte, terminál ponechá výchozí barvu.

Jak povolit integrační značky prostředí

Podpora těchto funkcí vyžaduje spolupráci mezi prostředím a terminálem. Abyste mohli tyto nové funkce používat, musíte v terminálu povolit nastavení i upravit výzvu prostředí.

Pokud chcete povolit tyto funkce v terminálu, budete chtít do nastavení přidat následující položky:

"profiles":
{
    "defaults":
    {
        // Enable marks on the scrollbar
        "showMarksOnScrollbar": true,

        // Needed for both pwsh, CMD and bash shell integration
        "autoMarkPrompts": true,

        // Add support for a right-click context menu
        // You can also just bind the `showContextMenu` action
        "experimental.rightClickContextMenu": true,
    },
}
"actions":
[
    // Scroll between prompts
    { "keys": "ctrl+up",   "command": { "action": "scrollToMark", "direction": "previous" }, },
    { "keys": "ctrl+down", "command": { "action": "scrollToMark", "direction": "next" }, },

    // Add the ability to select a whole command (or its output)
    { "command": { "action": "selectOutput", "direction": "prev" }, },
    { "command": { "action": "selectOutput", "direction": "next" }, },

    { "command": { "action": "selectCommand", "direction": "prev" }, },
    { "command": { "action": "selectCommand", "direction": "next" }, },
]

Jak povolíte tyto značky v prostředí, se liší od prostředí po prostředí. Níže jsou uvedené kurzy pro CMD, PowerShell a Zsh.

PowerShell (pwsh.exe)

Pokud jste ještě nikdy nezměnili příkazový řádek PowerShellu, měli byste si nejdřív prohlédnout about_Prompts .

Budeme muset upravit vaši prompt stránku, abychom zajistili, že terminál řekneme o CWD a označíme výzvu odpovídajícími značkami. PowerShell také umožňuje zahrnout kód chyby z předchozího příkazu v 133;D sekvenci, který umožní terminálu automaticky obarvit značku na základě toho, jestli byl příkaz úspěšný nebo neúspěšný.

Do profilu PowerShellu přidejte následující položky:

$Global:__LastHistoryId = -1

function Global:__Terminal-Get-LastExitCode {
  if ($? -eq $True) {
    return 0
  }
  $LastHistoryEntry = $(Get-History -Count 1)
  $IsPowerShellError = $Error[0].InvocationInfo.HistoryId -eq $LastHistoryEntry.Id
  if ($IsPowerShellError) {
    return -1
  }
  return $LastExitCode
}

function prompt {

  # First, emit a mark for the _end_ of the previous command.

  $gle = $(__Terminal-Get-LastExitCode);
  $LastHistoryEntry = $(Get-History -Count 1)
  # Skip finishing the command if the first command has not yet started
  if ($Global:__LastHistoryId -ne -1) {
    if ($LastHistoryEntry.Id -eq $Global:__LastHistoryId) {
      # Don't provide a command line or exit code if there was no history entry (eg. ctrl+c, enter on no command)
      $out += "`e]133;D`a"
    } else {
      $out += "`e]133;D;$gle`a"
    }
  }


  $loc = $($executionContext.SessionState.Path.CurrentLocation);

  # Prompt started
  $out += "`e]133;A$([char]07)";

  # CWD
  $out += "`e]9;9;`"$loc`"$([char]07)";

  # (your prompt here)
  $out += "PWSH $loc$('>' * ($nestedPromptLevel + 1)) ";

  # Prompt ended, Command started
  $out += "`e]133;B$([char]07)";

  $Global:__LastHistoryId = $LastHistoryEntry.Id

  return $out
}

Oh My Posh setup

Používáte oh-my-posh? Budete chtít mírně upravit předchozí příkaz, aby se původní výzva odložil, a pak ji přidat zpět doprostřed řídicích sekvencí integrace prostředí.

# initialize oh-my-posh at the top of your profile.ps1
oh-my-posh init pwsh --config "$env:POSH_THEMES_PATH\gruvbox.omp.json" | Invoke-Expression
# then stash away the prompt() that oh-my-posh sets
$Global:__OriginalPrompt = $function:Prompt

function Global:__Terminal-Get-LastExitCode {
  if ($? -eq $True) { return 0 }
  $LastHistoryEntry = $(Get-History -Count 1)
  $IsPowerShellError = $Error[0].InvocationInfo.HistoryId -eq $LastHistoryEntry.Id
  if ($IsPowerShellError) { return -1 }
  return $LastExitCode
}

function prompt {
  $gle = $(__Terminal-Get-LastExitCode);
  $LastHistoryEntry = $(Get-History -Count 1)
  if ($Global:__LastHistoryId -ne -1) {
    if ($LastHistoryEntry.Id -eq $Global:__LastHistoryId) {
      $out += "`e]133;D`a"
    } else {
      $out += "`e]133;D;$gle`a"
    }
  }
  $loc = $($executionContext.SessionState.Path.CurrentLocation);
  $out += "`e]133;A$([char]07)";
  $out += "`e]9;9;`"$loc`"$([char]07)";
  
  $out += $Global:__OriginalPrompt.Invoke(); # <-- This line adds the original prompt back

  $out += "`e]133;B$([char]07)";
  $Global:__LastHistoryId = $LastHistoryEntry.Id
  return $out
}

Příkazový řádek

Příkazový řádek z příkazového řádku z proměnné prostředí vysílaný z příkazového PROMPT řádku. CMD.exe přečte $e jako ESC znak. CMD.exe bohužel nemá způsob, jak na příkazovém řádku získat návratový kód předchozího příkazu, takže ve výzev CMD nemůžeme poskytnout informace o úspěchu nebo chybě.

Spuštěním příkazu můžete změnit výzvu pro aktuální instanci CMD.exe:

PROMPT $e]133;D$e\$e]133;A$e\$e]9;9;$P$e\$P$G$e]133;B$e\

Nebo můžete proměnnou nastavit z příkazového řádku pro všechny budoucí relace:

setx PROMPT $e]133;D$e\$e]133;A$e\$e]9;9;$P$e\$P$G$e]133;B$e\

Tyto příklady předpokládají, že váš aktuální PROMPT je právě $P$G. Místo toho se můžete rozhodnout, že chcete aktuální výzvu zabalit přibližně takto:

PROMPT $e]133;D$e\$e]133;A$e\$e]9;9;$P$e\%PROMPT%$e]133;B$e\

Bash

Následující skript můžete vytvořit do aktivního prostředí s integrovanými source příkazy nebo .bash ho přidat na konec prostředí ${HOME}/.bash_profile (pro přihlašovací prostředí) nebo ${HOME}/.bashrc (pro prostředí bez přihlášení) a povolit úplnou integraci bash prostředí s verzemi většími nebo rovnou bash-4.4 (kde PS0 byla původně implementována integrovaná proměnná). Úplná integrace prostředí znamená, že každá ohlášená funkce terminálu funguje podle návrhu.

Poznámka:

Je třeba zdůraznit, že pokud jsou PROMPT_COMMANDk dispozici proměnné nebo PS2PS0PS1 proměnné již přiřazené k jakýmkoli jiným než výchozím hodnotám, které mohou vést k nepředvídatelným výsledkům. Nejprve by bylo lepší otestovat skript s prostředím "clean" spuštěním env --ignore-environment bash --noprofile --norc a získáním popsaného souboru, jak bylo uvedeno dříve.

# .bash_profile | .bashrc

function __set_ps1() {
    local PS1_TMP="${__PS1_BASE}"
    if [ ! -z "${__IS_WT}" ]; then
        local __FTCS_CMD_FINISHED='\e]133;D;'"${1}"'\e\\'
        PS1_TMP="\[${__FTCS_CMD_FINISHED}\]${__PS1_BASE}"
    fi
    printf '%s' "${PS1_TMP}"
}

function __prompt_command() {
    # Must be first in the list otherwise the exit status will be overwritten.
    local PS1_EXIT_STATUS=${?}
    PS1="$(__set_ps1 ${PS1_EXIT_STATUS})"
}

# ---------------------------------------------------------------------------
# PROMPT (PS0..PS2).

# The given variable might be linked to a function detecting whether `bash`
# actually runs under `Microsoft Terminal` otherwise unexpected garbage might
# be displayed on the user screen.
__IS_WT='true'

printf -v __BASH_V '%d' ${BASH_VERSINFO[*]:0:2}

if [ ${__BASH_V} -ge 44 ]; then
    __PS0_BASE=''
fi

# The following assignments reflect the default values.
__PS1_BASE='\s-\v\$ '
__PS2_BASE='> '

if [ ! -z "${__IS_WT}" ]; then
    __FTCS_PROMPT='\e]133;A\e\\'
    __FTCS_CMD_START='\e]133;B\e\\'
    if [ ${__BASH_V} -ge 44 ]; then
        __FTCS_CMD_EXECUTED='\e]133;C\e\\'
        __PS0_BASE="\[${__FTCS_CMD_EXECUTED}\]"
    fi
    __PS1_BASE="\[${__FTCS_PROMPT}\]${__PS1_BASE}\[${__FTCS_CMD_START}\]"
    # Required, otherwise the `PS2` prefix will split and corrupt a long
    # command.
    __PS2_BASE=''
fi

PROMPT_COMMAND=__prompt_command

if [ ${__BASH_V} -ge 44 ]; then
    PS0="${__PS0_BASE}"
fi
# `PS1` is set with the `__prompt_command` function call.
PS2="${__PS2_BASE}"

Tím se zabalí veškerý sortiment proměnných bash výzvy (PS0PS1aPS2) s potřebnými sekvencemi, aby bylo možné dokončit integraci prostředí.

Kromě toho ${HOME}/.inputrc může být potřeba upravit také znaky "oznámení režimu úprav" a "změněné řádky":

# .inputrc

set mark-modified-lines Off
set show-mode-in-prompt Off

To by mělo vypadat, když všechno funguje správně:

$ env --ignore-environment bash --noprofile --norc
bash-5.2$ . /tmp/msft-terminal-bash.sh
bash-5.2$ echo "|${PS0}|"
|\[\e]133;C\e\\\]|
bash-5.2$ echo "|${PS1}|"
|\[\e]133;D;0\e\\\]\[\e]133;A\e\\\]\s-\v\$ \[\e]133;B\e\\\]|
bash-5.2$ echo "|${PS2}|"
||

Poznámka: Nevidíte tady vaše oblíbené prostředí? Pokud to zjistíte, můžete přispět řešením pro vaše upřednostňované prostředí.

Funkce integrace prostředí

Otevření nových karet ve stejném pracovním adresáři

Otevření nových karet ve stejném pracovním adresáři

Zobrazit značky pro každý příkaz na posuvníku

Zobrazit značky pro každý příkaz na posuvníku

Automatické přeskakování mezi příkazy

Akce se používají scrollToMark tak, jak jsou definovány výše.

Automatické přeskakování mezi příkazy

Výběr celého výstupu příkazu

V tomto obrázku selectOutput GIF použijeme akci vázanou k ctrl+g výběru celého výstupu příkazu. Výběr celého výstupu příkazu

Následující nastavení používá experimental.rightClickContextMenu k povolení místní nabídky v terminálu po kliknutí pravým tlačítkem myši. Pokud je tato integrace prostředí povolená, můžete kliknutím pravým tlačítkem na příkaz vybrat celý příkaz nebo jeho výstup.

Výběr příkazu pomocí místní nabídky po kliknutí pravým tlačítkem myši

Poslední návrhy příkazů

S povolenou integrací prostředí je možné uživatelské rozhraní Návrhy nakonfigurovat tak, aby zobrazoval také nedávné příkazy.

Uživatelské rozhraní návrhů zobrazující poslední příkazy v něm

Tuto nabídku můžete otevřít pomocí následující akce:

{
    "command": { "action": "showSuggestions", "source": "recentCommands", "useCommandline": true },
},

(Další informace najdete v dokumentaci návrhy)

Dodatečné zdroje