Udostępnij przez


Integracja powłoki

Począwszy od terminalu 1.15 (wersja zapoznawcza), terminal systemu Windows rozpoczął eksperymentalnie obsługę niektórych funkcji "integracji powłoki". Te funkcje ułatwiają korzystanie z wiersza polecenia. We wcześniejszych wersjach włączyliśmy powłokę, aby poinformować terminal o bieżącym katalogu roboczym. Teraz dodaliśmy obsługę większej liczby sekwencji, aby umożliwić powłoki semantycznie opisywanie części danych wyjściowych terminalu jako "wiersz", "polecenie" lub "dane wyjściowe". Powłoka może również poinformować terminal o tym, czy polecenie zakończyło się powodzeniem, czy niepowodzeniem.

Jest to przewodnik po niektórych funkcjach integracji powłoki, które wdrożyliśmy w wersji 1.18 terminalu. Planujemy utworzenie jeszcze większej liczby funkcji na tych tematach w przyszłości, więc chcielibyśmy uzyskać dodatkową opinię na temat tego, jak ludzie z nich korzystają.

Uwaga: Od wersji Terminal 1.21 znaczniki są teraz stabilną funkcją. Przed wersją 1.21 znaki były włączone tylko dla kompilacji w wersji zapoznawczej terminalu. Jeśli używasz wersji terminalu przed wersją 1.21, showMarksOnScrollbar ustawienie miało nazwę i autoMarkPrompts nosiło nazwę experimental.showMarksOnScrollbarexperimental.autoMarkPrompts.

Jak to działa?

Integracja powłoki działa, ponieważ powłoka (lub dowolna aplikacja wiersza polecenia) zapisuje specjalne "sekwencje ucieczki" w terminalu. Te sekwencje ucieczki nie są drukowane w terminalu — zamiast tego udostępniają bity metadanych, których terminal może użyć, aby dowiedzieć się więcej o tym, co dzieje się w aplikacji. Trzymając te sekwencje w wierszu polecenia powłoki, możesz mieć powłokę stale dostarczać informacje do terminalu, który zna tylko powłoka.

W przypadku następujących sekwencji:

  • OSC to ciąg "\x1b]" — znak ucieczki, po którym następuje znak ucieczki ]
  • ST jest "terminatorem ciągu" i może być \x1b\ (znak ESC, a następnie \) lub \x7 (znak BEL)
  • Spacje są jedynie ilustracyjne.
  • Ciągi w pliku <> to parametry, które powinny zostać zastąpione przez inną wartość.

Odpowiednie obsługiwane sekwencje integracji powłoki w wersji 1.18 to:

  • OSC 133 ; A ST ("FTCS_PROMPT") — początek monitu.
  • OSC 133 ; B ST ("FTCS_COMMAND_START") — początek wiersza polecenia (ODCZYT: koniec wiersza polecenia).
  • OSC 133 ; C ST ("FTCS_COMMAND_EXECUTED") — początek danych wyjściowych polecenia / koniec wiersza polecenia.
  • OSC 133 ; D ; <ExitCode> ST ("FTCS_COMMAND_FINISHED") — koniec polecenia. ExitCode Jeśli ExitCode zostanie podany, terminal będzie traktować 0 jako "sukces" i wszystkie inne elementy jako błąd. W przypadku pominięcia terminal po prostu pozostawi znacznik koloru domyślnego.

Jak włączyć znaczniki integracji powłoki

Obsługa tych funkcji wymaga współpracy między powłoką a terminalem. Aby korzystać z tych nowych funkcji, należy włączyć zarówno ustawienia w terminalu, jak i zmodyfikować monit powłoki.

Aby włączyć te funkcje w terminalu, należy dodać następujące elementy do ustawień:

"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" }, },
]

Sposób włączania tych znaków w powłoce różni się od powłoki do powłoki. Poniżej przedstawiono samouczki dotyczące usług CMD, PowerShell i Zsh.

PowerShell (pwsh.exe)

Jeśli nigdy wcześniej nie zmieniono monitu programu PowerShell, najpierw należy wyewidencjonować about_Prompts .

Musimy edytować, prompt aby upewnić się, że informujemy terminal o CWD i oznaczymy monit odpowiednimi znakami. Program PowerShell umożliwia również dołączenie kodu błędu z poprzedniego polecenia w 133;D sekwencji, co pozwoli terminalowi automatycznie kolorować znacznik na podstawie, jeśli polecenie zakończyło się pomyślnie lub nie powiodło się.

Dodaj następujące elementy do profilu programu PowerShell:

$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

Czy używasz oh-my-posh? Należy nieco zmodyfikować powyższe polecenie, aby ukryć oryginalny monit, a następnie dodać go z powrotem w środku sekwencji ucieczki integracji powłoki.

# 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
}

Wiersz polecenia

Wiersz polecenia źródła jego wiersza polecenia ze zmiennej środowiskowej PROMPT . CMD.exe odczytuje $e jako ESC znak. Niestety, CMD.exe nie ma sposobu na uzyskanie kodu zwrotnego poprzedniego polecenia w wierszu polecenia, więc nie możemy podać informacji o powodzeniu/błędzie w wierszach polecenia.

Możesz zmienić monit dotyczący bieżącego wystąpienia CMD.exe, uruchamiając polecenie:

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

Możesz też ustawić zmienną z wiersza polecenia dla wszystkich przyszłych sesji:

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

W tych przykładach przyjęto założenie, że bieżąca wartość PROMPT to tylko $P$G. Zamiast tego możesz opakowować bieżący monit za pomocą następującego polecenia:

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

Bash

Możesz użyć następującego skryptu do aktywnej powłoki z source wbudowanymi bash poleceniami lub . dodać go na końcu ${HOME}/.bash_profile (w przypadku powłok logowania) lub ${HOME}/.bashrc (w przypadku powłok innych niż logowania), aby umożliwić pełną integrację powłoki z bash wersjami większymi lub równymi bash-4.4 (gdzie PS0 początkowo zaimplementowano wbudowaną zmienną). Kompletna integracja powłoki oznacza, że każda ogłoszona funkcja terminalu działa zgodnie z projektem.

Uwaga / Notatka

Należy zwrócić uwagę, że jeśli istnieją PROMPT_COMMANDzmienne , PS0lub PS1PS2 już przypisane do żadnych wartości innych niż domyślne , które mogą prowadzić do nieprzewidywalnych wyników. Lepszym rozwiązaniem byłoby przetestowanie skryptu za pomocą powłoki "clean" najpierw przez wykonanie env --ignore-environment bash --noprofile --norc i określenie źródła opisanego pliku, jak wskazano wcześniej.

# .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}"

To opakowuje cały asortyment zmiennych monitu bash (PS0PS1i PS2) z niezbędnymi sekwencjami, aby umożliwić pełną integrację powłoki.

Ponadto ${HOME}/.inputrc może być również konieczne dostosowanie w celu usunięcia znaków "powiadomienia w trybie edycji" i "zmodyfikowanych wierszy":

# .inputrc

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

Tak powinno wyglądać, jeśli wszystko jest wykonywane poprawnie:

$ 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}|"
||

Uwaga: Nie widzisz tutaj ulubionej powłoki? Jeśli go dowiesz się, możesz współtworzyć rozwiązanie dla preferowanej powłoki.

Funkcje integracji powłoki

Otwieranie nowych kart w tym samym katalogu roboczym

Otwieranie nowych kart w tym samym katalogu roboczym

Pokaż znaczniki dla każdego polecenia na pasku przewijania

Pokaż znaczniki dla każdego polecenia na pasku przewijania

Automatyczne przechodzenie między poleceniami

Spowoduje to użycie scrollToMark akcji zdefiniowanych powyżej.

Automatyczne przechodzenie między poleceniami

Wybierz całe dane wyjściowe polecenia

W tym pliku GIF użyjemy akcji powiązanej selectOutput , aby ctrl+g wybrać całe dane wyjściowe polecenia. Wybierz całe dane wyjściowe polecenia

Poniższe ustawienie używa experimental.rightClickContextMenu ustawienia, aby włączyć menu kontekstowe kliknij prawym przyciskiem myszy w terminalu. Po włączeniu integracji z powłoką i możesz kliknąć prawym przyciskiem myszy polecenie, aby wybrać całe polecenie lub jego dane wyjściowe.

Wybierz polecenie przy użyciu menu kontekstowego prawym przyciskiem myszy

Najnowsze sugestie poleceń

Po włączeniu integracji powłoki można skonfigurować interfejs użytkownika sugestii, aby również wyświetlać ostatnie polecenia.

Interfejs użytkownika sugestii przedstawiający ostatnie polecenia w nim

Możesz otworzyć to menu za pomocą następującej akcji:

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

(Aby uzyskać więcej informacji, zobacz dokumentację Sugestie)

Dodatkowe zasoby