Shell-integrering

Från och med förhandsversionen av Terminal 1.15 har Windows-terminalen börjat stödja vissa funktioner för "gränssnittsintegrering". De här funktionerna gör kommandoraden enklare att använda. I tidigare versioner har vi aktiverat shell för att berätta för terminalen vad den aktuella arbetskatalogen är. Nu har vi lagt till stöd för fler sekvenser så att gränssnittet kan semantiskt beskriva delar av terminalutdata som en "prompt", ett "kommando" eller "utdata". Gränssnittet kan också tala om för terminalen om ett kommando lyckades eller misslyckades.

Det här är en guide till några av de gränssnittsintegreringsfunktioner som vi har distribuerat från och med Terminal v1.18. Vi planerar att skapa ännu fler funktioner utöver dessa i framtiden, så vi vill gärna få lite ytterligare feedback om hur folk använder dem.

Obs! Från och med terminal 1.21 är märken nu en stabil funktion. Före 1.21 aktiverades endast markeringar för förhandsversioner av terminalen. Om du använder en version av Terminal före 1.21 showMarksOnScrollbar fick inställningen namnet experimental.showMarksOnScrollbar, och autoMarkPrompts fick namnet experimental.autoMarkPrompts.

Hur fungerar det här?

Shell-integrering fungerar genom att gränssnittet (eller något kommandoradsprogram) skriver särskilda "escape-sekvenser" till terminalen. Dessa escape-sekvenser skrivs inte ut till terminalen – i stället tillhandahåller de bitar av metadata som terminalen kan använda för att veta mer om vad som händer i programmet. Genom att hålla dessa sekvenser i gränssnittets prompt kan du låta gränssnittet kontinuerligt tillhandahålla information till terminalen som bara gränssnittet känner till.

För följande sekvenser:

  • OSC är strängen "\x1b]" – ett escape-tecken följt av ]
  • ST är "strängavslutaren" och kan vara antingen \x1b\ (ett ESC-tecken följt av \) eller \x7 (BEL-tecknet)
  • Blanksteg är bara illustrativa.
  • Strängar i <> är parametrar som ska ersättas av något annat värde.

Relevanta gränssnittsintegreringssekvenser som stöds från och med Terminal v1.18 är:

  • OSC 133 ; A ST ("FTCS_PROMPT") – Början på en fråga.
  • OSC 133 ; B ST ("FTCS_COMMAND_START") – Början av en kommandorad (LÄS: slutet av kommandotolken).
  • OSC 133 ; C ST ("FTCS_COMMAND_EXECUTED") – Början av kommandots utdata/slutet av kommandoraden.
  • OSC 133 ; D ; <ExitCode> ST ("FTCS_COMMAND_FINISHED") – slutet på ett kommando. ExitCode Om ExitCode anges behandlas 0 terminalen som "lyckad" och allt annat som ett fel. Om den utelämnas lämnar terminalen bara standardfärgen.

Så här aktiverar du gränssnittsintegreringsmärken

Stöd för dessa funktioner kräver samarbete mellan gränssnittet och terminalen. Du måste både aktivera inställningar i terminalen för att använda dessa nya funktioner, samt ändra kommandotolken i gränssnittet.

Om du vill aktivera dessa funktioner i terminalen vill du lägga till följande i inställningarna:

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

Hur du aktiverar dessa märken i gränssnittet varierar från gränssnitt till gränssnitt. Nedan visas självstudier för CMD, PowerShell och Zsh.

PowerShell (pwsh.exe)

Om du aldrig har ändrat din PowerShell-fråga tidigare bör du kolla in about_Prompts först.

Vi måste redigera din prompt för att se till att vi berättar för terminalen om CWD och markerar prompten med lämpliga märken. Med PowerShell kan vi även inkludera felkoden från föregående kommando i sekvensen 133;D , vilket gör att terminalen automatiskt kan färglägga markeringen baserat på om kommandot lyckades eller misslyckades.

Lägg till följande i din PowerShell-profil:

$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

Använder du oh-my-posh? Du vill ändra ovanstående något, för att gömma undan den ursprungliga prompten och sedan lägga till den i mitten av gränssnittsintegrerings escape-sekvenserna.

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

Kommandotolkfönster

Kommandotolken hämtar kommandotolken från PROMPT miljövariabeln. CMD.exe läser $e som ESC tecken. Tyvärr har CMD.exe inte något sätt att hämta returkoden för föregående kommando i kommandotolken, så vi kan inte tillhandahålla information om lyckade/fel i CMD-prompter.

Du kan ändra kommandotolken för den aktuella CMD.exe-instansen genom att köra:

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

Du kan också ange variabeln från kommandoraden för alla framtida sessioner:

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

De här exemplen förutsätter att din aktuella PROMPT är bara $P$G. Du kan i stället välja att omsluta den aktuella prompten med något i stil med:

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

Smäll

Du kan hämta följande skript till ett aktivt gränssnitt med source eller . inbyggda kommandon eller lägga till det i bash slutet av dina ${HOME}/.bash_profile (för inloggningsgränssnitt) eller ${HOME}/.bashrc (för gränssnitt som inte är inloggningsgränssnitt) för att aktivera en fullständig gränssnittsintegrering med bash versioner som är större eller lika bash-4.4 med (där den PS0 inbyggda variabeln implementerades initialt). Fullständig gränssnittsintegrering innebär att varje tillkännagiven terminalfunktion fungerar som den är utformad.

Anmärkning

Det bör påpekas att om det finns PROMPT_COMMAND, PS0PS1 eller PS2 variabler som redan har tilldelats till värden som inte är standardvärden som kan leda till oförutsägbara resultat. Det vore bättre att testa skriptet med det "rena" gränssnittet först genom att env --ignore-environment bash --noprofile --norc köra och köpa den beskrivna filen som det angavs tidigare.

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

Det omsluter alla sortiment av bash promptvariabler (PS0och PS1PS2) med de sekvenser som krävs för att aktivera fullständig gränssnittsintegrering.

Dessutom ${HOME}/.inputrc kan det också behövas en justering för att ta bort tecknen "meddelande i redigeringsläge" och "ändrade rader":

# .inputrc

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

Det är så det ska se ut om allt görs på rätt sätt:

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

Obs! Ser du inte ditt favoritgränssnitt här? Om du räknar ut det kan du bidra med en lösning för ditt önskade gränssnitt!

Gränssnittsintegreringsfunktioner

Öppna nya flikar i samma arbetskatalog

Öppna nya flikar i samma arbetskatalog

Visa markeringar för varje kommando i rullningslisten

Visa markeringar för varje kommando i rullningslisten

Hoppa automatiskt mellan kommandon

Detta använder åtgärderna scrollToMark som vi har definierat ovan.

Hoppa automatiskt mellan kommandon

Välj hela utdata för ett kommando

I den här gif-filen använder vi den selectOutput åtgärd som är bunden till för att ctrl+g välja hela utdata för ett kommando. Välj hela utdata för ett kommando

Följande använder inställningen experimental.rightClickContextMenu för att aktivera en snabbmeny med högerklicka i terminalen. Med det och gränssnittsintegrering aktiverat kan du högerklicka på ett kommando för att välja hela kommandot eller dess utdata.

Välj kommandot med snabbmenyn högerklicka

Senaste kommandoförslag

När gränssnittsintegrering är aktiverat kan användargränssnittet förslag konfigureras för att även visa de senaste kommandona.

Förslagsgränssnittet som visar de senaste kommandona i det

Du kan öppna den här menyn med följande åtgärd:

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

(Mer information finns i dokumentationen förslag)

Ytterligare resurser