forked from qt-creator/qt-creator
Terminal: Add shell integration
Change-Id: Ic1e226b56f0103e5a6e7764073ab7ab241b67baa Reviewed-by: Cristian Adam <cristian.adam@qt.io>
This commit is contained in:
30
README.md
30
README.md
@@ -783,7 +783,6 @@ SQLite (https://www.sqlite.org) is in the Public Domain.
|
||||
|
||||
### libvterm
|
||||
|
||||
|
||||
An abstract C99 library which implements a VT220 or xterm-like terminal emulator.
|
||||
It doesn't use any particular graphics toolkit or output system, instead it invokes callback
|
||||
function pointers that its embedding program should provide it to draw on its behalf.
|
||||
@@ -813,3 +812,32 @@ SQLite (https://www.sqlite.org) is in the Public Domain.
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
### terminal/shellintegrations
|
||||
|
||||
The Terminal plugin uses scripts to integrate with the shell. The scripts are
|
||||
located in the Qt Creator source tree in src/plugins/terminal/shellintegrations.
|
||||
|
||||
https://github.com/microsoft/vscode/tree/main/src/vs/workbench/contrib/terminal/browser/media
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2015 - present Microsoft Corporation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
@@ -1046,5 +1046,18 @@
|
||||
|
||||
\include license-mit.qdocinc
|
||||
|
||||
\li \b terminal/shellintegrations
|
||||
|
||||
The Terminal plugin uses scripts to integrate with the shell. The scripts are
|
||||
located in the Qt Creator source tree in src/plugins/terminal/shellintegrations.
|
||||
|
||||
\list
|
||||
\li \l https://github.com/microsoft/vscode/tree/main/src/vs/workbench/contrib/terminal/browser/media
|
||||
\endlist
|
||||
|
||||
Distributed under the MIT license.
|
||||
|
||||
\include license-mit.qdocinc
|
||||
|
||||
\endlist
|
||||
*/
|
||||
|
@@ -5,7 +5,6 @@
|
||||
#include "savefile.h"
|
||||
|
||||
#include "algorithm.h"
|
||||
#include "hostosinfo.h"
|
||||
#include "qtcassert.h"
|
||||
#include "utilstr.h"
|
||||
|
||||
|
@@ -121,7 +121,6 @@ public:
|
||||
QString *selectedFilter = nullptr,
|
||||
QFileDialog::Options options = {});
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
// for actually finding out if e.g. directories are writable on Windows
|
||||
|
@@ -355,7 +355,9 @@ public:
|
||||
|
||||
bool startResult
|
||||
= m_ptyProcess->startProcess(program,
|
||||
arguments,
|
||||
HostOsInfo::isWindowsHost()
|
||||
? QStringList{m_setup.m_nativeArguments} << arguments
|
||||
: arguments,
|
||||
m_setup.m_workingDirectory.path(),
|
||||
m_setup.m_environment.toProcessEnvironment().toStringList(),
|
||||
m_setup.m_ptyData->size().width(),
|
||||
|
@@ -7,6 +7,7 @@ add_qtc_plugin(Terminal
|
||||
glyphcache.cpp glyphcache.h
|
||||
keys.cpp keys.h
|
||||
scrollback.cpp scrollback.h
|
||||
shellintegration.cpp shellintegration.h
|
||||
shellmodel.cpp shellmodel.h
|
||||
terminal.qrc
|
||||
terminalpane.cpp terminalpane.h
|
||||
|
143
src/plugins/terminal/shellintegration.cpp
Normal file
143
src/plugins/terminal/shellintegration.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "shellintegration.h"
|
||||
|
||||
#include <utils/environment.h>
|
||||
#include <utils/filepath.h>
|
||||
#include <utils/stringutils.h>
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
Q_LOGGING_CATEGORY(integrationLog, "qtc.terminal.shellintegration", QtWarningMsg)
|
||||
|
||||
using namespace Utils;
|
||||
|
||||
namespace Terminal {
|
||||
|
||||
struct FileToCopy
|
||||
{
|
||||
FilePath source;
|
||||
QString destName;
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
struct
|
||||
{
|
||||
struct
|
||||
{
|
||||
FilePath rcFile{":/terminal/shellintegrations/shellintegration-bash.sh"};
|
||||
} bash;
|
||||
struct
|
||||
{
|
||||
QList<FileToCopy> files{
|
||||
{":/terminal/shellintegrations/shellintegration-env.zsh", ".zshenv"},
|
||||
{":/terminal/shellintegrations/shellintegration-login.zsh", ".zlogin"},
|
||||
{":/terminal/shellintegrations/shellintegration-profile.zsh", ".zprofile"},
|
||||
{":/terminal/shellintegrations/shellintegration-rc.zsh", ".zshrc"}
|
||||
};
|
||||
} zsh;
|
||||
struct
|
||||
{
|
||||
FilePath script{":/terminal/shellintegrations/shellintegration.ps1"};
|
||||
} pwsh;
|
||||
|
||||
} filesToCopy;
|
||||
// clang-format on
|
||||
|
||||
bool ShellIntegration::canIntegrate(const Utils::CommandLine &cmdLine)
|
||||
{
|
||||
if (cmdLine.executable().needsDevice())
|
||||
return false; // TODO: Allow integration for remote shells
|
||||
|
||||
if (!cmdLine.arguments().isEmpty())
|
||||
return false;
|
||||
|
||||
if (cmdLine.executable().baseName() == "bash")
|
||||
return true;
|
||||
|
||||
if (cmdLine.executable().baseName() == "zsh")
|
||||
return true;
|
||||
|
||||
if (cmdLine.executable().baseName() == "pwsh"
|
||||
|| cmdLine.executable().baseName() == "powershell") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ShellIntegration::onOsc(int cmd, const VTermStringFragment &fragment)
|
||||
{
|
||||
QString d = QString::fromLocal8Bit(fragment.str, fragment.len);
|
||||
const auto [command, data] = Utils::splitAtFirst(d, ';');
|
||||
|
||||
if (cmd == 1337) {
|
||||
const auto [key, value] = Utils::splitAtFirst(command, '=');
|
||||
if (key == QStringView(u"CurrentDir"))
|
||||
emit currentDirChanged(FilePath::fromUserInput(value.toString()).path());
|
||||
|
||||
} else if (cmd == 7) {
|
||||
emit currentDirChanged(FilePath::fromUserInput(d).path());
|
||||
} else if (cmd == 133) {
|
||||
qCDebug(integrationLog) << "OSC 133:" << data;
|
||||
} else if (cmd == 633 && command.length() == 1) {
|
||||
if (command[0] == 'E') {
|
||||
CommandLine cmdLine = CommandLine::fromUserInput(data.toString());
|
||||
emit commandChanged(cmdLine);
|
||||
} else if (command[0] == 'D') {
|
||||
emit commandChanged({});
|
||||
} else if (command[0] == 'P') {
|
||||
const auto [key, value] = Utils::splitAtFirst(data, '=');
|
||||
if (key == QStringView(u"Cwd"))
|
||||
emit currentDirChanged(value.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShellIntegration::prepareProcess(Utils::QtcProcess &process)
|
||||
{
|
||||
Environment env = process.environment().hasChanges() ? process.environment()
|
||||
: Environment::systemEnvironment();
|
||||
CommandLine cmd = process.commandLine();
|
||||
|
||||
if (!canIntegrate(cmd))
|
||||
return;
|
||||
|
||||
env.set("VSCODE_INJECTION", "1");
|
||||
|
||||
if (cmd.executable().baseName() == "bash") {
|
||||
const FilePath rcPath = filesToCopy.bash.rcFile;
|
||||
const FilePath tmpRc = FilePath::fromUserInput(
|
||||
m_tempDir.filePath(filesToCopy.bash.rcFile.fileName()));
|
||||
rcPath.copyFile(tmpRc);
|
||||
|
||||
cmd.addArgs({"--init-file", tmpRc.nativePath()});
|
||||
} else if (cmd.executable().baseName() == "zsh") {
|
||||
for (const FileToCopy &file : filesToCopy.zsh.files) {
|
||||
const auto copyResult = file.source.copyFile(
|
||||
FilePath::fromUserInput(m_tempDir.filePath(file.destName)));
|
||||
QTC_ASSERT_EXPECTED(copyResult, return);
|
||||
}
|
||||
|
||||
const Utils::FilePath originalZdotDir = FilePath::fromUserInput(
|
||||
env.value_or("ZDOTDIR", QDir::homePath()));
|
||||
|
||||
env.set("ZDOTDIR", m_tempDir.path());
|
||||
env.set("USER_ZDOTDIR", originalZdotDir.nativePath());
|
||||
} else if (cmd.executable().baseName() == "pwsh"
|
||||
|| cmd.executable().baseName() == "powershell") {
|
||||
const FilePath rcPath = filesToCopy.pwsh.script;
|
||||
const FilePath tmpRc = FilePath::fromUserInput(
|
||||
m_tempDir.filePath(filesToCopy.pwsh.script.fileName()));
|
||||
rcPath.copyFile(tmpRc);
|
||||
|
||||
cmd.addArgs(QString("-noexit -command try { . \"%1\" } catch {}{1}").arg(tmpRc.nativePath()),
|
||||
CommandLine::Raw);
|
||||
}
|
||||
|
||||
process.setCommand(cmd);
|
||||
process.setEnvironment(env);
|
||||
}
|
||||
|
||||
} // namespace Terminal
|
34
src/plugins/terminal/shellintegration.h
Normal file
34
src/plugins/terminal/shellintegration.h
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH
|
||||
// Qt-GPL-exception-1.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utils/commandline.h>
|
||||
#include <utils/qtcprocess.h>
|
||||
|
||||
#include <vterm.h>
|
||||
|
||||
#include <QTemporaryDir>
|
||||
|
||||
namespace Terminal {
|
||||
|
||||
class ShellIntegration : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static bool canIntegrate(const Utils::CommandLine &cmdLine);
|
||||
|
||||
void onOsc(int cmd, const VTermStringFragment &fragment);
|
||||
|
||||
void prepareProcess(Utils::QtcProcess &process);
|
||||
|
||||
signals:
|
||||
void commandChanged(const Utils::CommandLine &command);
|
||||
void currentDirChanged(const QString &dir);
|
||||
|
||||
private:
|
||||
QTemporaryDir m_tempDir;
|
||||
};
|
||||
|
||||
} // namespace Terminal
|
252
src/plugins/terminal/shellintegrations/shellintegration-bash.sh
Executable file
252
src/plugins/terminal/shellintegrations/shellintegration-bash.sh
Executable file
@@ -0,0 +1,252 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
|
||||
# Prevent the script recursing when setting up
|
||||
if [[ -n "$VSCODE_SHELL_INTEGRATION" ]]; then
|
||||
builtin return
|
||||
fi
|
||||
|
||||
VSCODE_SHELL_INTEGRATION=1
|
||||
|
||||
# Run relevant rc/profile only if shell integration has been injected, not when run manually
|
||||
if [ "$VSCODE_INJECTION" == "1" ]; then
|
||||
if [ -z "$VSCODE_SHELL_LOGIN" ]; then
|
||||
if [ -r ~/.bashrc ]; then
|
||||
. ~/.bashrc
|
||||
fi
|
||||
else
|
||||
# Imitate -l because --init-file doesn't support it:
|
||||
# run the first of these files that exists
|
||||
if [ -r /etc/profile ]; then
|
||||
. /etc/profile
|
||||
fi
|
||||
# exceute the first that exists
|
||||
if [ -r ~/.bash_profile ]; then
|
||||
. ~/.bash_profile
|
||||
elif [ -r ~/.bash_login ]; then
|
||||
. ~/.bash_login
|
||||
elif [ -r ~/.profile ]; then
|
||||
. ~/.profile
|
||||
fi
|
||||
builtin unset VSCODE_SHELL_LOGIN
|
||||
|
||||
# Apply any explicit path prefix (see #99878)
|
||||
if [ -n "$VSCODE_PATH_PREFIX" ]; then
|
||||
export PATH=$VSCODE_PATH_PREFIX$PATH
|
||||
builtin unset VSCODE_PATH_PREFIX
|
||||
fi
|
||||
fi
|
||||
builtin unset VSCODE_INJECTION
|
||||
fi
|
||||
|
||||
if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then
|
||||
builtin return
|
||||
fi
|
||||
|
||||
__vsc_get_trap() {
|
||||
# 'trap -p DEBUG' outputs a shell command like `trap -- '…shellcode…' DEBUG`.
|
||||
# The terms are quoted literals, but are not guaranteed to be on a single line.
|
||||
# (Consider a trap like $'echo foo\necho \'bar\'').
|
||||
# To parse, we splice those terms into an expression capturing them into an array.
|
||||
# This preserves the quoting of those terms: when we `eval` that expression, they are preserved exactly.
|
||||
# This is different than simply exploding the string, which would split everything on IFS, oblivious to quoting.
|
||||
builtin local -a terms
|
||||
builtin eval "terms=( $(trap -p "${1:-DEBUG}") )"
|
||||
# |________________________|
|
||||
# |
|
||||
# \-------------------*--------------------/
|
||||
# terms=( trap -- '…arbitrary shellcode…' DEBUG )
|
||||
# |____||__| |_____________________| |_____|
|
||||
# | | | |
|
||||
# 0 1 2 3
|
||||
# |
|
||||
# \--------*----/
|
||||
builtin printf '%s' "${terms[2]:-}"
|
||||
}
|
||||
|
||||
# The property (P) and command (E) codes embed values which require escaping.
|
||||
# Backslashes are doubled. Non-alphanumeric characters are converted to escaped hex.
|
||||
__vsc_escape_value() {
|
||||
# Process text byte by byte, not by codepoint.
|
||||
builtin local LC_ALL=C str="${1}" i byte token out=''
|
||||
|
||||
for (( i=0; i < "${#str}"; ++i )); do
|
||||
byte="${str:$i:1}"
|
||||
|
||||
# Escape backslashes and semi-colons
|
||||
if [ "$byte" = "\\" ]; then
|
||||
token="\\\\"
|
||||
elif [ "$byte" = ";" ]; then
|
||||
token="\\x3b"
|
||||
else
|
||||
token="$byte"
|
||||
fi
|
||||
|
||||
out+="$token"
|
||||
done
|
||||
|
||||
builtin printf '%s\n' "${out}"
|
||||
}
|
||||
|
||||
# Send the IsWindows property if the environment looks like Windows
|
||||
if [[ "$(uname -s)" =~ ^CYGWIN*|MINGW*|MSYS* ]]; then
|
||||
builtin printf '\e]633;P;IsWindows=True\a'
|
||||
fi
|
||||
|
||||
# Allow verifying $BASH_COMMAND doesn't have aliases resolved via history when the right HISTCONTROL
|
||||
# configuration is used
|
||||
if [[ "$HISTCONTROL" =~ .*(erasedups|ignoreboth|ignoredups).* ]]; then
|
||||
__vsc_history_verify=0
|
||||
else
|
||||
__vsc_history_verify=1
|
||||
fi
|
||||
|
||||
__vsc_initialized=0
|
||||
__vsc_original_PS1="$PS1"
|
||||
__vsc_original_PS2="$PS2"
|
||||
__vsc_custom_PS1=""
|
||||
__vsc_custom_PS2=""
|
||||
__vsc_in_command_execution="1"
|
||||
__vsc_current_command=""
|
||||
|
||||
__vsc_prompt_start() {
|
||||
builtin printf '\e]633;A\a'
|
||||
}
|
||||
|
||||
__vsc_prompt_end() {
|
||||
builtin printf '\e]633;B\a'
|
||||
}
|
||||
|
||||
__vsc_update_cwd() {
|
||||
builtin printf '\e]633;P;Cwd=%s\a' "$(__vsc_escape_value "$PWD")"
|
||||
}
|
||||
|
||||
__vsc_command_output_start() {
|
||||
builtin printf '\e]633;C\a'
|
||||
builtin printf '\e]633;E;%s\a' "$(__vsc_escape_value "${__vsc_current_command}")"
|
||||
}
|
||||
|
||||
__vsc_continuation_start() {
|
||||
builtin printf '\e]633;F\a'
|
||||
}
|
||||
|
||||
__vsc_continuation_end() {
|
||||
builtin printf '\e]633;G\a'
|
||||
}
|
||||
|
||||
__vsc_command_complete() {
|
||||
if [ "$__vsc_current_command" = "" ]; then
|
||||
builtin printf '\e]633;D\a'
|
||||
else
|
||||
builtin printf '\e]633;D;%s\a' "$__vsc_status"
|
||||
fi
|
||||
__vsc_update_cwd
|
||||
}
|
||||
__vsc_update_prompt() {
|
||||
# in command execution
|
||||
if [ "$__vsc_in_command_execution" = "1" ]; then
|
||||
# Wrap the prompt if it is not yet wrapped, if the PS1 changed this this was last set it
|
||||
# means the user re-exported the PS1 so we should re-wrap it
|
||||
if [[ "$__vsc_custom_PS1" == "" || "$__vsc_custom_PS1" != "$PS1" ]]; then
|
||||
__vsc_original_PS1=$PS1
|
||||
__vsc_custom_PS1="\[$(__vsc_prompt_start)\]$__vsc_original_PS1\[$(__vsc_prompt_end)\]"
|
||||
PS1="$__vsc_custom_PS1"
|
||||
fi
|
||||
if [[ "$__vsc_custom_PS2" == "" || "$__vsc_custom_PS2" != "$PS2" ]]; then
|
||||
__vsc_original_PS2=$PS2
|
||||
__vsc_custom_PS2="\[$(__vsc_continuation_start)\]$__vsc_original_PS2\[$(__vsc_continuation_end)\]"
|
||||
PS2="$__vsc_custom_PS2"
|
||||
fi
|
||||
__vsc_in_command_execution="0"
|
||||
fi
|
||||
}
|
||||
|
||||
__vsc_precmd() {
|
||||
__vsc_command_complete "$__vsc_status"
|
||||
__vsc_current_command=""
|
||||
__vsc_update_prompt
|
||||
}
|
||||
|
||||
__vsc_preexec() {
|
||||
__vsc_initialized=1
|
||||
if [[ ! "$BASH_COMMAND" =~ ^__vsc_prompt* ]]; then
|
||||
# Use history if it's available to verify the command as BASH_COMMAND comes in with aliases
|
||||
# resolved
|
||||
if [ "$__vsc_history_verify" = "1" ]; then
|
||||
__vsc_current_command="$(builtin history 1 | sed 's/ *[0-9]* *//')"
|
||||
else
|
||||
__vsc_current_command=$BASH_COMMAND
|
||||
fi
|
||||
else
|
||||
__vsc_current_command=""
|
||||
fi
|
||||
__vsc_command_output_start
|
||||
}
|
||||
|
||||
# Debug trapping/preexec inspired by starship (ISC)
|
||||
if [[ -n "${bash_preexec_imported:-}" ]]; then
|
||||
__vsc_preexec_only() {
|
||||
if [ "$__vsc_in_command_execution" = "0" ]; then
|
||||
__vsc_in_command_execution="1"
|
||||
__vsc_preexec
|
||||
fi
|
||||
}
|
||||
precmd_functions+=(__vsc_prompt_cmd)
|
||||
preexec_functions+=(__vsc_preexec_only)
|
||||
else
|
||||
__vsc_dbg_trap="$(__vsc_get_trap DEBUG)"
|
||||
|
||||
if [[ -z "$__vsc_dbg_trap" ]]; then
|
||||
__vsc_preexec_only() {
|
||||
if [ "$__vsc_in_command_execution" = "0" ]; then
|
||||
__vsc_in_command_execution="1"
|
||||
__vsc_preexec
|
||||
fi
|
||||
}
|
||||
trap '__vsc_preexec_only "$_"' DEBUG
|
||||
elif [[ "$__vsc_dbg_trap" != '__vsc_preexec "$_"' && "$__vsc_dbg_trap" != '__vsc_preexec_all "$_"' ]]; then
|
||||
__vsc_preexec_all() {
|
||||
if [ "$__vsc_in_command_execution" = "0" ]; then
|
||||
__vsc_in_command_execution="1"
|
||||
builtin eval "${__vsc_dbg_trap}"
|
||||
__vsc_preexec
|
||||
fi
|
||||
}
|
||||
trap '__vsc_preexec_all "$_"' DEBUG
|
||||
fi
|
||||
fi
|
||||
|
||||
__vsc_update_prompt
|
||||
|
||||
__vsc_restore_exit_code() {
|
||||
return "$1"
|
||||
}
|
||||
|
||||
__vsc_prompt_cmd_original() {
|
||||
__vsc_status="$?"
|
||||
__vsc_restore_exit_code "${__vsc_status}"
|
||||
# Evaluate the original PROMPT_COMMAND similarly to how bash would normally
|
||||
# See https://unix.stackexchange.com/a/672843 for technique
|
||||
for cmd in "${__vsc_original_prompt_command[@]}"; do
|
||||
eval "${cmd:-}"
|
||||
done
|
||||
__vsc_precmd
|
||||
}
|
||||
|
||||
__vsc_prompt_cmd() {
|
||||
__vsc_status="$?"
|
||||
__vsc_precmd
|
||||
}
|
||||
|
||||
# PROMPT_COMMAND arrays and strings seem to be handled the same (handling only the first entry of
|
||||
# the array?)
|
||||
__vsc_original_prompt_command=$PROMPT_COMMAND
|
||||
|
||||
if [[ -z "${bash_preexec_imported:-}" ]]; then
|
||||
if [[ -n "$__vsc_original_prompt_command" && "$__vsc_original_prompt_command" != "__vsc_prompt_cmd" ]]; then
|
||||
PROMPT_COMMAND=__vsc_prompt_cmd_original
|
||||
else
|
||||
PROMPT_COMMAND=__vsc_prompt_cmd
|
||||
fi
|
||||
fi
|
@@ -0,0 +1,15 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
if [[ -f $USER_ZDOTDIR/.zshenv ]]; then
|
||||
VSCODE_ZDOTDIR=$ZDOTDIR
|
||||
ZDOTDIR=$USER_ZDOTDIR
|
||||
|
||||
# prevent recursion
|
||||
if [[ $USER_ZDOTDIR != $VSCODE_ZDOTDIR ]]; then
|
||||
. $USER_ZDOTDIR/.zshenv
|
||||
fi
|
||||
|
||||
USER_ZDOTDIR=$ZDOTDIR
|
||||
ZDOTDIR=$VSCODE_ZDOTDIR
|
||||
fi
|
@@ -0,0 +1,7 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
ZDOTDIR=$USER_ZDOTDIR
|
||||
if [[ $options[norcs] = off && -o "login" && -f $ZDOTDIR/.zlogin ]]; then
|
||||
. $ZDOTDIR/.zlogin
|
||||
fi
|
@@ -0,0 +1,15 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
if [[ $options[norcs] = off && -o "login" && -f $USER_ZDOTDIR/.zprofile ]]; then
|
||||
VSCODE_ZDOTDIR=$ZDOTDIR
|
||||
ZDOTDIR=$USER_ZDOTDIR
|
||||
. $USER_ZDOTDIR/.zprofile
|
||||
ZDOTDIR=$VSCODE_ZDOTDIR
|
||||
|
||||
# Apply any explicit path prefix (see #99878)
|
||||
if (( ${+VSCODE_PATH_PREFIX} )); then
|
||||
export PATH=$VSCODE_PATH_PREFIX$PATH
|
||||
fi
|
||||
builtin unset VSCODE_PATH_PREFIX
|
||||
fi
|
160
src/plugins/terminal/shellintegrations/shellintegration-rc.zsh
Normal file
160
src/plugins/terminal/shellintegrations/shellintegration-rc.zsh
Normal file
@@ -0,0 +1,160 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
builtin autoload -Uz add-zsh-hook
|
||||
|
||||
# Prevent the script recursing when setting up
|
||||
if [ -n "$VSCODE_SHELL_INTEGRATION" ]; then
|
||||
ZDOTDIR=$USER_ZDOTDIR
|
||||
builtin return
|
||||
fi
|
||||
|
||||
# This variable allows the shell to both detect that VS Code's shell integration is enabled as well
|
||||
# as disable it by unsetting the variable.
|
||||
VSCODE_SHELL_INTEGRATION=1
|
||||
|
||||
# By default, zsh will set the $HISTFILE to the $ZDOTDIR location automatically. In the case of the
|
||||
# shell integration being injected, this means that the terminal will use a different history file
|
||||
# to other terminals. To fix this issue, set $HISTFILE back to the default location before ~/.zshrc
|
||||
# is called as that may depend upon the value.
|
||||
if [[ "$VSCODE_INJECTION" == "1" ]]; then
|
||||
HISTFILE=$USER_ZDOTDIR/.zsh_history
|
||||
fi
|
||||
|
||||
# Only fix up ZDOTDIR if shell integration was injected (not manually installed) and has not been called yet
|
||||
if [[ "$VSCODE_INJECTION" == "1" ]]; then
|
||||
if [[ $options[norcs] = off && -f $USER_ZDOTDIR/.zshrc ]]; then
|
||||
VSCODE_ZDOTDIR=$ZDOTDIR
|
||||
ZDOTDIR=$USER_ZDOTDIR
|
||||
# A user's custom HISTFILE location might be set when their .zshrc file is sourced below
|
||||
. $USER_ZDOTDIR/.zshrc
|
||||
fi
|
||||
fi
|
||||
|
||||
# Shell integration was disabled by the shell, exit without warning assuming either the shell has
|
||||
# explicitly disabled shell integration as it's incompatible or it implements the protocol.
|
||||
if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then
|
||||
builtin return
|
||||
fi
|
||||
|
||||
# The property (P) and command (E) codes embed values which require escaping.
|
||||
# Backslashes are doubled. Non-alphanumeric characters are converted to escaped hex.
|
||||
__vsc_escape_value() {
|
||||
builtin emulate -L zsh
|
||||
|
||||
# Process text byte by byte, not by codepoint.
|
||||
builtin local LC_ALL=C str="$1" i byte token out=''
|
||||
|
||||
for (( i = 0; i < ${#str}; ++i )); do
|
||||
byte="${str:$i:1}"
|
||||
|
||||
# Escape backslashes and semi-colons
|
||||
if [ "$byte" = "\\" ]; then
|
||||
token="\\\\"
|
||||
elif [ "$byte" = ";" ]; then
|
||||
token="\\x3b"
|
||||
else
|
||||
token="$byte"
|
||||
fi
|
||||
|
||||
out+="$token"
|
||||
done
|
||||
|
||||
builtin print -r "$out"
|
||||
}
|
||||
|
||||
__vsc_in_command_execution="1"
|
||||
__vsc_current_command=""
|
||||
|
||||
__vsc_prompt_start() {
|
||||
builtin printf '\e]633;A\a'
|
||||
}
|
||||
|
||||
__vsc_prompt_end() {
|
||||
builtin printf '\e]633;B\a'
|
||||
}
|
||||
|
||||
__vsc_update_cwd() {
|
||||
builtin printf '\e]633;P;Cwd=%s\a' "$(__vsc_escape_value "${PWD}")"
|
||||
}
|
||||
|
||||
__vsc_command_output_start() {
|
||||
builtin printf '\e]633;C\a'
|
||||
builtin printf '\e]633;E;%s\a' "${__vsc_current_command}"
|
||||
}
|
||||
|
||||
__vsc_continuation_start() {
|
||||
builtin printf '\e]633;F\a'
|
||||
}
|
||||
|
||||
__vsc_continuation_end() {
|
||||
builtin printf '\e]633;G\a'
|
||||
}
|
||||
|
||||
__vsc_right_prompt_start() {
|
||||
builtin printf '\e]633;H\a'
|
||||
}
|
||||
|
||||
__vsc_right_prompt_end() {
|
||||
builtin printf '\e]633;I\a'
|
||||
}
|
||||
|
||||
__vsc_command_complete() {
|
||||
if [[ "$__vsc_current_command" == "" ]]; then
|
||||
builtin printf '\e]633;D\a'
|
||||
else
|
||||
builtin printf '\e]633;D;%s\a' "$__vsc_status"
|
||||
fi
|
||||
__vsc_update_cwd
|
||||
}
|
||||
|
||||
if [[ -o NOUNSET ]]; then
|
||||
if [ -z "${RPROMPT-}" ]; then
|
||||
RPROMPT=""
|
||||
fi
|
||||
fi
|
||||
__vsc_update_prompt() {
|
||||
__vsc_prior_prompt="$PS1"
|
||||
__vsc_prior_prompt2="$PS2"
|
||||
__vsc_in_command_execution=""
|
||||
PS1="%{$(__vsc_prompt_start)%}$PS1%{$(__vsc_prompt_end)%}"
|
||||
PS2="%{$(__vsc_continuation_start)%}$PS2%{$(__vsc_continuation_end)%}"
|
||||
if [ -n "$RPROMPT" ]; then
|
||||
__vsc_prior_rprompt="$RPROMPT"
|
||||
RPROMPT="%{$(__vsc_right_prompt_start)%}$RPROMPT%{$(__vsc_right_prompt_end)%}"
|
||||
fi
|
||||
}
|
||||
|
||||
__vsc_precmd() {
|
||||
local __vsc_status="$?"
|
||||
if [ -z "${__vsc_in_command_execution-}" ]; then
|
||||
# not in command execution
|
||||
__vsc_command_output_start
|
||||
fi
|
||||
|
||||
__vsc_command_complete "$__vsc_status"
|
||||
__vsc_current_command=""
|
||||
|
||||
# in command execution
|
||||
if [ -n "$__vsc_in_command_execution" ]; then
|
||||
# non null
|
||||
__vsc_update_prompt
|
||||
fi
|
||||
}
|
||||
|
||||
__vsc_preexec() {
|
||||
PS1="$__vsc_prior_prompt"
|
||||
PS2="$__vsc_prior_prompt2"
|
||||
if [ -n "$RPROMPT" ]; then
|
||||
RPROMPT="$__vsc_prior_rprompt"
|
||||
fi
|
||||
__vsc_in_command_execution="1"
|
||||
__vsc_current_command=$2
|
||||
__vsc_command_output_start
|
||||
}
|
||||
add-zsh-hook precmd __vsc_precmd
|
||||
add-zsh-hook preexec __vsc_preexec
|
||||
|
||||
if [[ $options[login] = off && $USER_ZDOTDIR != $VSCODE_ZDOTDIR ]]; then
|
||||
ZDOTDIR=$USER_ZDOTDIR
|
||||
fi
|
122
src/plugins/terminal/shellintegrations/shellintegration.fish
Normal file
122
src/plugins/terminal/shellintegrations/shellintegration.fish
Normal file
@@ -0,0 +1,122 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
# Visual Studio Code terminal integration for fish
|
||||
#
|
||||
# Manual installation:
|
||||
#
|
||||
# (1) Add the following to the end of `$__fish_config_dir/config.fish`:
|
||||
#
|
||||
# string match -q "$TERM_PROGRAM" "vscode"
|
||||
# and . (code --locate-shell-integration-path fish)
|
||||
#
|
||||
# (2) Restart fish.
|
||||
|
||||
# Don't run in scripts, other terminals, or more than once per session.
|
||||
status is-interactive
|
||||
and string match --quiet "$TERM_PROGRAM" "vscode"
|
||||
and ! set --query VSCODE_SHELL_INTEGRATION
|
||||
or exit
|
||||
|
||||
set --global VSCODE_SHELL_INTEGRATION 1
|
||||
|
||||
# Apply any explicit path prefix (see #99878)
|
||||
if status --is-login; and set -q VSCODE_PATH_PREFIX
|
||||
fish_add_path -p $VSCODE_PATH_PREFIX
|
||||
end
|
||||
set -e VSCODE_PATH_PREFIX
|
||||
|
||||
# Helper function
|
||||
function __vsc_esc -d "Emit escape sequences for VS Code shell integration"
|
||||
builtin printf "\e]633;%s\a" (string join ";" $argv)
|
||||
end
|
||||
|
||||
# Sent right before executing an interactive command.
|
||||
# Marks the beginning of command output.
|
||||
function __vsc_cmd_executed --on-event fish_preexec
|
||||
__vsc_esc C
|
||||
__vsc_esc E (__vsc_escape_value "$argv")
|
||||
|
||||
# Creates a marker to indicate a command was run.
|
||||
set --global _vsc_has_cmd
|
||||
end
|
||||
|
||||
|
||||
# Escape a value for use in the 'P' ("Property") or 'E' ("Command Line") sequences.
|
||||
# Backslashes are doubled and non-alphanumeric characters are hex encoded.
|
||||
function __vsc_escape_value
|
||||
# Escape backslashes and semi-colons
|
||||
echo $argv \
|
||||
| string replace --all '\\' '\\\\' \
|
||||
| string replace --all ';' '\\x3b' \
|
||||
;
|
||||
end
|
||||
|
||||
# Sent right after an interactive command has finished executing.
|
||||
# Marks the end of command output.
|
||||
function __vsc_cmd_finished --on-event fish_postexec
|
||||
__vsc_esc D $status
|
||||
end
|
||||
|
||||
# Sent when a command line is cleared or reset, but no command was run.
|
||||
# Marks the cleared line with neither success nor failure.
|
||||
function __vsc_cmd_clear --on-event fish_cancel
|
||||
__vsc_esc D
|
||||
end
|
||||
|
||||
# Sent whenever a new fish prompt is about to be displayed.
|
||||
# Updates the current working directory.
|
||||
function __vsc_update_cwd --on-event fish_prompt
|
||||
__vsc_esc P Cwd=(__vsc_escape_value "$PWD")
|
||||
|
||||
# If a command marker exists, remove it.
|
||||
# Otherwise, the commandline is empty and no command was run.
|
||||
if set --query _vsc_has_cmd
|
||||
set --erase _vsc_has_cmd
|
||||
else
|
||||
__vsc_cmd_clear
|
||||
end
|
||||
end
|
||||
|
||||
# Sent at the start of the prompt.
|
||||
# Marks the beginning of the prompt (and, implicitly, a new line).
|
||||
function __vsc_fish_prompt_start
|
||||
__vsc_esc A
|
||||
end
|
||||
|
||||
# Sent at the end of the prompt.
|
||||
# Marks the beginning of the user's command input.
|
||||
function __vsc_fish_cmd_start
|
||||
__vsc_esc B
|
||||
end
|
||||
|
||||
function __vsc_fish_has_mode_prompt -d "Returns true if fish_mode_prompt is defined and not empty"
|
||||
functions fish_mode_prompt | string match -rvq '^ *(#|function |end$|$)'
|
||||
end
|
||||
|
||||
# Preserve the user's existing prompt, to wrap in our escape sequences.
|
||||
functions --copy fish_prompt __vsc_fish_prompt
|
||||
|
||||
# Preserve and wrap fish_mode_prompt (which appears to the left of the regular
|
||||
# prompt), but only if it's not defined as an empty function (which is the
|
||||
# officially documented way to disable that feature).
|
||||
if __vsc_fish_has_mode_prompt
|
||||
functions --copy fish_mode_prompt __vsc_fish_mode_prompt
|
||||
|
||||
function fish_mode_prompt
|
||||
__vsc_fish_prompt_start
|
||||
__vsc_fish_mode_prompt
|
||||
end
|
||||
|
||||
function fish_prompt
|
||||
__vsc_fish_prompt
|
||||
__vsc_fish_cmd_start
|
||||
end
|
||||
else
|
||||
# No fish_mode_prompt, so put everything in fish_prompt.
|
||||
function fish_prompt
|
||||
__vsc_fish_prompt_start
|
||||
__vsc_fish_prompt
|
||||
__vsc_fish_cmd_start
|
||||
end
|
||||
end
|
158
src/plugins/terminal/shellintegrations/shellintegration.ps1
Normal file
158
src/plugins/terminal/shellintegrations/shellintegration.ps1
Normal file
@@ -0,0 +1,158 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Prevent installing more than once per session
|
||||
if (Test-Path variable:global:__VSCodeOriginalPrompt) {
|
||||
return;
|
||||
}
|
||||
|
||||
# Disable shell integration when the language mode is restricted
|
||||
if ($ExecutionContext.SessionState.LanguageMode -ne "FullLanguage") {
|
||||
return;
|
||||
}
|
||||
|
||||
$Global:__VSCodeOriginalPrompt = $function:Prompt
|
||||
|
||||
$Global:__LastHistoryId = -1
|
||||
|
||||
function Global:__VSCode-Escape-Value([string]$value) {
|
||||
# NOTE: In PowerShell v6.1+, this can be written `$value -replace '…', { … }` instead of `[regex]::Replace`.
|
||||
# Replace any non-alphanumeric characters.
|
||||
[regex]::Replace($value, '[\\\n;]', { param($match)
|
||||
# Encode the (ascii) matches as `\x<hex>`
|
||||
-Join (
|
||||
[System.Text.Encoding]::UTF8.GetBytes($match.Value) | ForEach-Object { '\x{0:x2}' -f $_ }
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function Global:Prompt() {
|
||||
$FakeCode = [int]!$global:?
|
||||
# NOTE: We disable strict mode for the scope of this function because it unhelpfully throws an
|
||||
# error when $LastHistoryEntry is null, and is not otherwise useful.
|
||||
Set-StrictMode -Off
|
||||
$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)
|
||||
$Result = "$([char]0x1b)]633;E`a"
|
||||
$Result += "$([char]0x1b)]633;D`a"
|
||||
} else {
|
||||
# Command finished command line
|
||||
# OSC 633 ; A ; <CommandLine?> ST
|
||||
$Result = "$([char]0x1b)]633;E;"
|
||||
# Sanitize the command line to ensure it can get transferred to the terminal and can be parsed
|
||||
# correctly. This isn't entirely safe but good for most cases, it's important for the Pt parameter
|
||||
# to only be composed of _printable_ characters as per the spec.
|
||||
if ($LastHistoryEntry.CommandLine) {
|
||||
$CommandLine = $LastHistoryEntry.CommandLine
|
||||
} else {
|
||||
$CommandLine = ""
|
||||
}
|
||||
$Result += $(__VSCode-Escape-Value $CommandLine)
|
||||
$Result += "`a"
|
||||
# Command finished exit code
|
||||
# OSC 633 ; D [; <ExitCode>] ST
|
||||
$Result += "$([char]0x1b)]633;D;$FakeCode`a"
|
||||
}
|
||||
}
|
||||
# Prompt started
|
||||
# OSC 633 ; A ST
|
||||
$Result += "$([char]0x1b)]633;A`a"
|
||||
# Current working directory
|
||||
# OSC 633 ; <Property>=<Value> ST
|
||||
$Result += if($pwd.Provider.Name -eq 'FileSystem'){"$([char]0x1b)]633;P;Cwd=$(__VSCode-Escape-Value $pwd.ProviderPath)`a"}
|
||||
# Before running the original prompt, put $? back to what it was:
|
||||
if ($FakeCode -ne 0) {
|
||||
Write-Error "failure" -ea ignore
|
||||
}
|
||||
# Run the original prompt
|
||||
$Result += $Global:__VSCodeOriginalPrompt.Invoke()
|
||||
# Write command started
|
||||
$Result += "$([char]0x1b)]633;B`a"
|
||||
$Global:__LastHistoryId = $LastHistoryEntry.Id
|
||||
return $Result
|
||||
}
|
||||
|
||||
# Only send the command executed sequence when PSReadLine is loaded, if not shell integration should
|
||||
# still work thanks to the command line sequence
|
||||
if (Get-Module -Name PSReadLine) {
|
||||
$__VSCodeOriginalPSConsoleHostReadLine = $function:PSConsoleHostReadLine
|
||||
function Global:PSConsoleHostReadLine {
|
||||
$tmp = $__VSCodeOriginalPSConsoleHostReadLine.Invoke()
|
||||
# Write command executed sequence directly to Console to avoid the new line from Write-Host
|
||||
[Console]::Write("$([char]0x1b)]633;C`a")
|
||||
$tmp
|
||||
}
|
||||
}
|
||||
|
||||
# Set IsWindows property
|
||||
[Console]::Write("$([char]0x1b)]633;P;IsWindows=$($IsWindows)`a")
|
||||
|
||||
# Set always on key handlers which map to default VS Code keybindings
|
||||
function Set-MappedKeyHandler {
|
||||
param ([string[]] $Chord, [string[]]$Sequence)
|
||||
try {
|
||||
$Handler = Get-PSReadLineKeyHandler -Chord $Chord | Select-Object -First 1
|
||||
} catch [System.Management.Automation.ParameterBindingException] {
|
||||
# PowerShell 5.1 ships with PSReadLine 2.0.0 which does not have -Chord,
|
||||
# so we check what's bound and filter it.
|
||||
$Handler = Get-PSReadLineKeyHandler -Bound | Where-Object -FilterScript { $_.Key -eq $Chord } | Select-Object -First 1
|
||||
}
|
||||
if ($Handler) {
|
||||
Set-PSReadLineKeyHandler -Chord $Sequence -Function $Handler.Function
|
||||
}
|
||||
}
|
||||
|
||||
function Set-MappedKeyHandlers {
|
||||
Set-MappedKeyHandler -Chord Ctrl+Spacebar -Sequence 'F12,a'
|
||||
Set-MappedKeyHandler -Chord Alt+Spacebar -Sequence 'F12,b'
|
||||
Set-MappedKeyHandler -Chord Shift+Enter -Sequence 'F12,c'
|
||||
Set-MappedKeyHandler -Chord Shift+End -Sequence 'F12,d'
|
||||
|
||||
# Conditionally enable suggestions
|
||||
if ($env:VSCODE_SUGGEST -eq '1') {
|
||||
Remove-Item Env:VSCODE_SUGGEST
|
||||
|
||||
# VS Code send completions request (may override Ctrl+Spacebar)
|
||||
Set-PSReadLineKeyHandler -Chord 'F12,e' -ScriptBlock {
|
||||
Send-Completions
|
||||
}
|
||||
|
||||
# Suggest trigger characters
|
||||
Set-PSReadLineKeyHandler -Chord "-" -ScriptBlock {
|
||||
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("-")
|
||||
Send-Completions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Send-Completions {
|
||||
$commandLine = ""
|
||||
$cursorIndex = 0
|
||||
# TODO: Since fuzzy matching exists, should completions be provided only for character after the
|
||||
# last space and then filter on the client side? That would let you trigger ctrl+space
|
||||
# anywhere on a word and have full completions available
|
||||
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$commandLine, [ref]$cursorIndex)
|
||||
$completionPrefix = $commandLine
|
||||
|
||||
# Get completions
|
||||
$result = "`e]633;Completions"
|
||||
if ($completionPrefix.Length -gt 0) {
|
||||
# Get and send completions
|
||||
$completions = TabExpansion2 -inputScript $completionPrefix -cursorColumn $cursorIndex
|
||||
if ($null -ne $completions.CompletionMatches) {
|
||||
$result += ";$($completions.ReplacementIndex);$($completions.ReplacementLength);$($cursorIndex);"
|
||||
$result += $completions.CompletionMatches | ConvertTo-Json -Compress
|
||||
}
|
||||
}
|
||||
$result += "`a"
|
||||
|
||||
Write-Host -NoNewLine $result
|
||||
}
|
||||
|
||||
# Register key handlers if PSReadLine is available
|
||||
if (Get-Module -Name PSReadLine) {
|
||||
Set-MappedKeyHandlers
|
||||
}
|
@@ -1,6 +1,13 @@
|
||||
<RCC>
|
||||
<qresource prefix="/terminal">
|
||||
<file>images/settingscategory_terminal.png</file>
|
||||
<file>images/settingscategory_terminal@2x.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="/terminal">
|
||||
<file>images/settingscategory_terminal.png</file>
|
||||
<file>images/settingscategory_terminal@2x.png</file>
|
||||
<file>shellintegrations/shellintegration-bash.sh</file>
|
||||
<file>shellintegrations/shellintegration-env.zsh</file>
|
||||
<file>shellintegrations/shellintegration-login.zsh</file>
|
||||
<file>shellintegrations/shellintegration-profile.zsh</file>
|
||||
<file>shellintegrations/shellintegration-rc.zsh</file>
|
||||
<file>shellintegrations/shellintegration.fish</file>
|
||||
<file>shellintegrations/shellintegration.ps1</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
@@ -188,13 +188,29 @@ void TerminalPane::setupTerminalWidget(TerminalWidget *terminal)
|
||||
|
||||
auto setTabText = [this](TerminalWidget * terminal) {
|
||||
auto index = m_tabWidget->indexOf(terminal);
|
||||
m_tabWidget->setTabText(index, terminal->shellName());
|
||||
const FilePath cwd = terminal->cwd();
|
||||
|
||||
const QString exe = terminal->currentCommand().isEmpty() ? terminal->shellName()
|
||||
: terminal->currentCommand().executable().fileName();
|
||||
|
||||
if (cwd.isEmpty())
|
||||
m_tabWidget->setTabText(index, exe);
|
||||
else
|
||||
m_tabWidget->setTabText(index, exe + " - " + cwd.fileName());
|
||||
};
|
||||
|
||||
connect(terminal, &TerminalWidget::started, [setTabText, terminal](qint64 /*pid*/) {
|
||||
setTabText(terminal);
|
||||
});
|
||||
|
||||
connect(terminal, &TerminalWidget::cwdChanged, [setTabText, terminal]() {
|
||||
setTabText(terminal);
|
||||
});
|
||||
|
||||
connect(terminal, &TerminalWidget::commandChanged, [setTabText, terminal]() {
|
||||
setTabText(terminal);
|
||||
});
|
||||
|
||||
if (!terminal->shellName().isEmpty())
|
||||
setTabText(terminal);
|
||||
|
||||
|
@@ -23,10 +23,13 @@ QColor toQColor(const VTermColor &c)
|
||||
|
||||
struct TerminalSurfacePrivate
|
||||
{
|
||||
TerminalSurfacePrivate(TerminalSurface *surface, const QSize &initialGridSize)
|
||||
TerminalSurfacePrivate(TerminalSurface *surface,
|
||||
const QSize &initialGridSize,
|
||||
ShellIntegration *shellIntegration)
|
||||
: m_vterm(vterm_new(initialGridSize.height(), initialGridSize.width()), vterm_free)
|
||||
, m_vtermScreen(vterm_obtain_screen(m_vterm.get()))
|
||||
, m_scrollback(std::make_unique<Internal::Scrollback>(5000))
|
||||
, m_shellIntegration(shellIntegration)
|
||||
, q(surface)
|
||||
{}
|
||||
|
||||
@@ -75,7 +78,15 @@ struct TerminalSurfacePrivate
|
||||
vterm_screen_set_damage_merge(m_vtermScreen, VTERM_DAMAGE_SCROLL);
|
||||
vterm_screen_enable_altscreen(m_vtermScreen, true);
|
||||
|
||||
memset(&m_vtermStateFallbacks, 0, sizeof(m_vtermStateFallbacks));
|
||||
|
||||
m_vtermStateFallbacks.osc = [](int cmd, VTermStringFragment fragment, void *user) {
|
||||
auto p = static_cast<TerminalSurfacePrivate *>(user);
|
||||
return p->osc(cmd, fragment);
|
||||
};
|
||||
|
||||
VTermState *vts = vterm_obtain_state(m_vterm.get());
|
||||
vterm_state_set_unrecognised_fallbacks(vts, &m_vtermStateFallbacks, this);
|
||||
vterm_state_set_bold_highbright(vts, true);
|
||||
|
||||
vterm_screen_reset(m_vtermScreen, 1);
|
||||
@@ -196,6 +207,14 @@ struct TerminalSurfacePrivate
|
||||
return 1;
|
||||
}
|
||||
|
||||
int osc(int cmd, const VTermStringFragment &fragment)
|
||||
{
|
||||
if (m_shellIntegration)
|
||||
m_shellIntegration->onOsc(cmd, fragment);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int setTerminalProperties(VTermProp prop, VTermValue *val)
|
||||
{
|
||||
switch (prop) {
|
||||
@@ -274,19 +293,23 @@ struct TerminalSurfacePrivate
|
||||
std::unique_ptr<VTerm, void (*)(VTerm *)> m_vterm;
|
||||
VTermScreen *m_vtermScreen;
|
||||
VTermScreenCallbacks m_vtermScreenCallbacks;
|
||||
VTermStateFallbacks m_vtermStateFallbacks;
|
||||
|
||||
QColor m_defaultBgColor;
|
||||
Cursor m_cursor;
|
||||
QString m_currentCommand;
|
||||
|
||||
bool m_altscreen{false};
|
||||
|
||||
std::unique_ptr<Internal::Scrollback> m_scrollback;
|
||||
|
||||
ShellIntegration *m_shellIntegration{nullptr};
|
||||
|
||||
TerminalSurface *q;
|
||||
};
|
||||
|
||||
TerminalSurface::TerminalSurface(QSize initialGridSize)
|
||||
: d(std::make_unique<TerminalSurfacePrivate>(this, initialGridSize))
|
||||
TerminalSurface::TerminalSurface(QSize initialGridSize, ShellIntegration *shellIntegration)
|
||||
: d(std::make_unique<TerminalSurfacePrivate>(this, initialGridSize, shellIntegration))
|
||||
{
|
||||
d->init();
|
||||
}
|
||||
@@ -478,6 +501,11 @@ QColor TerminalSurface::defaultBgColor() const
|
||||
return toQColor(d->defaultBgColor());
|
||||
}
|
||||
|
||||
ShellIntegration *TerminalSurface::shellIntegration() const
|
||||
{
|
||||
return d->m_shellIntegration;
|
||||
}
|
||||
|
||||
CellIterator TerminalSurface::begin() const
|
||||
{
|
||||
auto res = CellIterator(this, {0, 0});
|
||||
|
@@ -4,6 +4,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "celliterator.h"
|
||||
#include "shellintegration.h"
|
||||
|
||||
#include <QKeyEvent>
|
||||
#include <QSize>
|
||||
@@ -47,7 +48,7 @@ class TerminalSurface : public QObject
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
TerminalSurface(QSize initialGridSize);
|
||||
TerminalSurface(QSize initialGridSize, ShellIntegration *shellIntegration);
|
||||
~TerminalSurface();
|
||||
|
||||
public:
|
||||
@@ -95,6 +96,8 @@ public:
|
||||
|
||||
QColor defaultBgColor() const;
|
||||
|
||||
ShellIntegration *shellIntegration() const;
|
||||
|
||||
signals:
|
||||
void writeToPty(const QByteArray &data);
|
||||
void invalidated(QRect grid);
|
||||
|
@@ -12,6 +12,7 @@
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/environment.h>
|
||||
#include <utils/fileutils.h>
|
||||
#include <utils/hostosinfo.h>
|
||||
#include <utils/processinterface.h>
|
||||
#include <utils/stringutils.h>
|
||||
@@ -132,6 +133,10 @@ void TerminalWidget::setupPty()
|
||||
m_process->setWorkingDirectory(*m_openParameters.workingDirectory);
|
||||
m_process->setEnvironment(env);
|
||||
|
||||
if (m_surface->shellIntegration()) {
|
||||
m_surface->shellIntegration()->prepareProcess(*m_process.get());
|
||||
}
|
||||
|
||||
connect(m_process.get(), &QtcProcess::readyReadStandardOutput, this, [this]() {
|
||||
onReadyRead(false);
|
||||
});
|
||||
@@ -242,7 +247,8 @@ void TerminalWidget::setupActions()
|
||||
connect(&m_zoomInAction, &QAction::triggered, this, &TerminalWidget::zoomIn);
|
||||
connect(&m_zoomOutAction, &QAction::triggered, this, &TerminalWidget::zoomOut);
|
||||
|
||||
addActions({&m_copyAction, &m_pasteAction, &m_clearSelectionAction, &m_zoomInAction, &m_zoomOutAction});
|
||||
addActions(
|
||||
{&m_copyAction, &m_pasteAction, &m_clearSelectionAction, &m_zoomInAction, &m_zoomOutAction});
|
||||
}
|
||||
|
||||
void TerminalWidget::writeToPty(const QByteArray &data)
|
||||
@@ -253,7 +259,8 @@ void TerminalWidget::writeToPty(const QByteArray &data)
|
||||
|
||||
void TerminalWidget::setupSurface()
|
||||
{
|
||||
m_surface = std::make_unique<Internal::TerminalSurface>(QSize{80, 60});
|
||||
m_shellIntegration.reset(new ShellIntegration());
|
||||
m_surface = std::make_unique<Internal::TerminalSurface>(QSize{80, 60}, m_shellIntegration.get());
|
||||
|
||||
connect(m_surface.get(),
|
||||
&Internal::TerminalSurface::writeToPty,
|
||||
@@ -299,6 +306,22 @@ void TerminalWidget::setupSurface()
|
||||
connect(m_surface.get(), &Internal::TerminalSurface::unscroll, this, [this] {
|
||||
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
|
||||
});
|
||||
if (m_shellIntegration) {
|
||||
connect(m_shellIntegration.get(),
|
||||
&ShellIntegration::commandChanged,
|
||||
this,
|
||||
[this](const CommandLine &command) {
|
||||
m_currentCommand = command;
|
||||
emit commandChanged(m_currentCommand);
|
||||
});
|
||||
connect(m_shellIntegration.get(),
|
||||
&ShellIntegration::currentDirChanged,
|
||||
this,
|
||||
[this](const QString ¤tDir) {
|
||||
m_cwd = FilePath::fromUserInput(currentDir);
|
||||
emit cwdChanged(m_cwd);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalWidget::configBlinkTimer()
|
||||
@@ -470,6 +493,16 @@ QString TerminalWidget::shellName() const
|
||||
return m_shellName;
|
||||
}
|
||||
|
||||
FilePath TerminalWidget::cwd() const
|
||||
{
|
||||
return m_cwd;
|
||||
}
|
||||
|
||||
CommandLine TerminalWidget::currentCommand() const
|
||||
{
|
||||
return m_currentCommand;
|
||||
}
|
||||
|
||||
QPoint TerminalWidget::viewportToGlobal(QPoint p) const
|
||||
{
|
||||
int y = p.y() - topMargin();
|
||||
|
@@ -72,8 +72,13 @@ public:
|
||||
|
||||
QString shellName() const;
|
||||
|
||||
Utils::FilePath cwd() const;
|
||||
Utils::CommandLine currentCommand() const;
|
||||
|
||||
signals:
|
||||
void started(qint64 pid);
|
||||
void cwdChanged(const Utils::FilePath &cwd);
|
||||
void commandChanged(const Utils::CommandLine &cmd);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
@@ -158,6 +163,7 @@ protected:
|
||||
private:
|
||||
std::unique_ptr<Utils::QtcProcess> m_process;
|
||||
std::unique_ptr<Internal::TerminalSurface> m_surface;
|
||||
std::unique_ptr<ShellIntegration> m_shellIntegration;
|
||||
|
||||
QString m_shellName;
|
||||
|
||||
@@ -201,6 +207,9 @@ private:
|
||||
Internal::Cursor m_cursor;
|
||||
QTimer m_cursorBlinkTimer;
|
||||
bool m_cursorBlinkState{true};
|
||||
|
||||
Utils::FilePath m_cwd;
|
||||
Utils::CommandLine m_currentCommand;
|
||||
};
|
||||
|
||||
} // namespace Terminal
|
||||
|
70
src/plugins/terminal/tests/integration
Executable file
70
src/plugins/terminal/tests/integration
Executable file
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Testing integration response, best start this from a terminal that has no builtin integration"
|
||||
echo "e.g. 'sh'"
|
||||
echo
|
||||
|
||||
echo -e "\033[1m ⎆ Current dir should have changed to '/Some/Dir/Here'\033[0m"
|
||||
printf "\033]7;file:///Some/Dir/Here\033\\"
|
||||
|
||||
read -p " ⎆ Press enter to continue " -n1 -s
|
||||
echo
|
||||
echo
|
||||
|
||||
echo -e "\033[1m ⎆ Current dir should have changed to '/Some/Other/Dir/Here'\033[0m"
|
||||
printf "\033]1337;CurrentDir=/Some/Other/Dir/Here\033\\"
|
||||
|
||||
read -p " ⎆ Press enter to continue " -n1 -s
|
||||
echo
|
||||
echo
|
||||
|
||||
|
||||
echo -e "\033[1m ⎆ Current dir should have changed to '/VSCode/dir/with space'\033[0m"
|
||||
printf "\033]633P;Cwd=/VSCode/dir/with space\033\\"
|
||||
|
||||
read -p " ⎆ Press enter to continue " -n1 -s
|
||||
echo
|
||||
echo
|
||||
|
||||
echo -e "\033[1m ⎆ The current process should have changed to 'test'\033[0m"
|
||||
printf "\033]633E;test with arguments\033\\"
|
||||
|
||||
read -p " ⎆ Press enter to continue " -n1 -s
|
||||
echo
|
||||
echo
|
||||
|
||||
echo -e "\033[1m ⎆ The current process should have changed to 'test with space'\033[0m"
|
||||
printf "\033]633E;'test with space'\033\\"
|
||||
|
||||
read -p " ⎆ Press enter to continue " -n1 -s
|
||||
echo
|
||||
echo
|
||||
|
||||
echo -e "\033[1m ⎆ The current process should have changed to 'test with space v2'\033[0m"
|
||||
printf "\033]633E;\"test with space v2\"\033\\"
|
||||
|
||||
read -p " ⎆ Press enter to continue " -n1 -s
|
||||
echo
|
||||
echo
|
||||
|
||||
echo -e "\033[1m ⎆ The current process should have changed to 'test with space v3'\033[0m"
|
||||
printf "\033]633E;\"./test/test with space v3\" -argument\033\\"
|
||||
|
||||
read -p " ⎆ Press enter to continue " -n1 -s
|
||||
echo
|
||||
echo
|
||||
|
||||
echo -e "\033[1m ⎆ The current process should have changed to 'cat'\033[0m"
|
||||
printf "\033]633E;cat /dev/random | base64 -argument\033\\"
|
||||
|
||||
read -p " ⎆ Press enter to continue " -n1 -s
|
||||
echo
|
||||
echo
|
||||
|
||||
echo -e "\033[1m ⎆ The current process should have changed to 'cat me'\033[0m"
|
||||
printf "\033]633E;cat\\ me args \033\\"
|
||||
|
||||
read -p " ⎆ Press enter to continue " -n1 -s
|
||||
echo
|
||||
echo
|
||||
|
Reference in New Issue
Block a user