Partilhar via


Integração Shell

A partir do Terminal 1.15 Preview, o Terminal do Windows começou a suportar experimentalmente alguns recursos de "integração de shell". Esses recursos tornam a linha de comando mais fácil de usar. Em versões anteriores, habilitamos o shell para informar ao Terminal qual é o diretório de trabalho atual. Agora, adicionamos suporte para mais sequências para permitir que seu shell descreva semanticamente partes da saída do terminal como um "prompt", um "comando" ou "saída". O shell também pode dizer ao terminal se um comando foi bem-sucedido ou falhou.

Este é um guia para alguns dos recursos de integração de shell que lançamos a partir do Terminal v1.18. Estamos planejando criar ainda mais recursos em cima deles no futuro, então adoraríamos receber alguns comentários adicionais sobre como as pessoas os usam.

Nota: A partir do Terminal 1.21, as marcas são agora uma funcionalidade estável. Antes da versão 1.21, as marcas só eram ativadas para compilações de visualização do Terminal. Se você estiver usando uma versão do Terminal anterior à 1.21, a showMarksOnScrollbar configuração foi nomeada experimental.showMarksOnScrollbar, e autoMarkPrompts foi nomeada experimental.autoMarkPrompts.

Como funciona?

A integração do Shell funciona fazendo com que o shell (ou qualquer aplicativo de linha de comando) escreva "sequências de escape" especiais no Terminal. Essas sequências de escape não são impressas no Terminal - em vez disso, fornecem bits de metadados que o terminal pode usar para saber mais sobre o que está acontecendo no aplicativo. Ao colar essas sequências no prompt do shell, você pode fazer com que o shell forneça continuamente informações ao terminal que apenas o shell conhece.

Para as seguintes sequências:

  • OSC é a cadeia de caracteres "\x1b]" - um caractere de escape, seguido por ]
  • ST é o "terminador de cadeia de caracteres", e pode ser ( \x1b\ um caractere ESC, seguido por \) ou \x7 (o caractere BEL)
  • Os espaços são meramente ilustrativos.
  • Strings in <> são parâmetros que devem ser substituídos por algum outro valor.

As sequências de integração de shell suportadas relevantes a partir do Terminal v1.18 são:

  • OSC 133 ; A ST ("FTCS_PROMPT") - O início de um prompt.
  • OSC 133 ; B ST ("FTCS_COMMAND_START") - O início de uma linha de comando (LEIA: o final do prompt).
  • OSC 133 ; C ST ("FTCS_COMMAND_EXECUTED") - O início da saída do comando / o final da linha de comando.
  • OSC 133 ; D ; <ExitCode> ST ("FTCS_COMMAND_FINISHED") - o fim de um comando. ExitCode Se ExitCode for fornecido, então o Terminal será tratado 0 como "sucesso" e qualquer outra coisa como um erro. Se omitido, o terminal apenas deixará a marca da cor padrão.

Como habilitar marcas de integração de shell

O suporte a esses recursos requer cooperação entre seu shell e o Terminal. Você precisará habilitar as configurações no Terminal para usar esses novos recursos, bem como modificar o prompt do shell.

Para ativar esses recursos no Terminal, você desejará adicionar o seguinte às suas configurações:

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

A forma como você habilita essas marcas em seu shell varia de shell para shell. Abaixo estão tutoriais para CMD, PowerShell e Zsh.

PowerShell (pwsh.exe)

Se você nunca alterou seu prompt do PowerShell antes, você deve fazer check-out about_Prompts primeiro.

Precisaremos editá-lo prompt para ter certeza de que informamos o Terminal sobre o CWD e marcar o prompt com as marcas apropriadas. O PowerShell também nos permite incluir o código de erro do comando anterior na sequência, o 133;D que permitirá que o terminal colora automaticamente a marca com base se o comando for bem-sucedido ou falhar.

Adicione o seguinte ao seu perfil do 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 configuração

Usando oh-my-posh? Você vai querer modificar ligeiramente o acima, para esconder o prompt original e, em seguida, adicioná-lo de volta no meio das sequências de escape de integração do shell.

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

Linha de comandos

O Prompt de Comando origina seu prompt da PROMPT variável de ambiente. CMD.exe lê-se $e como a ESC personagem. Infelizmente, CMD.exe não tem uma maneira de obter o código de retorno do comando anterior no prompt, portanto, não somos capazes de fornecer informações de sucesso / erro em prompts CMD.

Você pode alterar o prompt para a instância CMD.exe atual executando:

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

Ou, você pode definir a variável a partir da linha de comando para todas as sessões futuras:

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

Estes exemplos pressupõem que o seu atual PROMPT é apenas $P$G. Em vez disso, você pode optar por encapsular seu prompt atual com algo como:

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

Bash

Você pode originar o script a seguir para um shell ativo com source ou . comandos internos ou adicioná-lo bash ao final do seu ${HOME}/.bash_profile (para shells de login) ou ${HOME}/.bashrc (para shells não login) para habilitar uma integração completa do shell com bash versões maiores ou iguais a bash-4.4 (onde a variável interna foi implementadaPS0 inicialmente). A integração completa do shell significa que cada recurso de terminal anunciado funciona como projetado.

Observação

Deve-se salientar que, se houver PROMPT_COMMAND, PS0PS1 , ou PS2 variáveis já atribuídas a quaisquer valores não padrão que podem levar a resultados imprevisíveis. Seria melhor testar o script com o shell "limpo" primeiro, executando env --ignore-environment bash --noprofile --norc e fornecendo o arquivo descrito como foi indicado anteriormente.

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

Isso envolve toda a variedade de variáveis de bash prompt (PS0, PS1 e PS2) com as sequências necessárias para permitir a integração completa do shell.

Além disso, ${HOME}/.inputrc também pode precisar de um ajuste para remover os sinais de "notificação de modo de edição" e "linhas modificadas":

# .inputrc

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

É assim que deve ser se tudo for feito corretamente:

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

Nota: Não vê a sua concha favorita aqui? Se você descobrir, sinta-se à vontade para contribuir com uma solução para o seu shell preferido!

Recursos de integração do Shell

Abrir novos separadores no mesmo diretório de trabalho

Abrir novos separadores no mesmo diretório de trabalho

Mostrar marcas para cada comando na barra de rolagem

Mostrar marcas para cada comando na barra de rolagem

Saltar automaticamente entre comandos

Isso usa as scrollToMark ações como as definimos acima.

Saltar automaticamente entre comandos

Selecione toda a saída de um comando

Neste gif, usamos a selectOutput ação vinculada a ctrl+g para selecionar toda a saída de um comando. Selecione toda a saída de um comando

O seguinte usa a experimental.rightClickContextMenu configuração para ativar um menu de contexto do botão direito do mouse no Terminal. Com isso e a integração de shell habilitada, você pode clicar com o botão direito do mouse em um comando para selecionar o comando inteiro ou sua saída.

Selecione o comando usando o menu de contexto do botão direito do mouse

Sugestões de comandos recentes

Com a integração de shell habilitada, a interface do usuário de sugestões pode ser configurada para também mostrar seus comandos recentes.

A interface do usuário de sugestões mostrando comandos recentes nela

Você pode abrir este menu com a seguinte ação:

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

(Para mais informações, consulte a documentação de Sugestões)

Recursos adicionais