feat: expand Galaxy agent and remote tooling
Add Wormhole remote helpers, provider and agent improvements, filesystem diagnostics, model metadata support, and schema-aware settings IntelliSense.
This commit is contained in:
@@ -338,7 +338,7 @@ The code editor has full LSP-powered autocompletion with documentation resolutio
|
|||||||
**Behavior:**
|
**Behavior:**
|
||||||
- Auto-completes as you type (triggered by alphanumeric/underscore with 50ms debounce)
|
- Auto-completes as you type (triggered by alphanumeric/underscore with 50ms debounce)
|
||||||
- Trigger characters: `.` and `::` fire immediately
|
- Trigger characters: `.` and `::` fire immediately
|
||||||
- Manual trigger: `Ctrl+Alt+Space`
|
- Manual trigger: `Ctrl+Space` while the code editor is focused
|
||||||
- Keyboard navigation: Up/Down to select, Tab/Enter to confirm
|
- Keyboard navigation: Up/Down to select, Tab/Enter to confirm
|
||||||
- Mouse: hover an item to select it and show docs, click to confirm
|
- Mouse: hover an item to select it and show docs, click to confirm
|
||||||
- Documentation panel appears beside the menu when the LSP returns docs for the selected item (via `completionItem/resolve`)
|
- Documentation panel appears beside the menu when the LSP returns docs for the selected item (via `completionItem/resolve`)
|
||||||
|
|||||||
+2
-2
@@ -591,7 +591,7 @@ default = [
|
|||||||
"revert_to_checkpoints",
|
"revert_to_checkpoints",
|
||||||
"rewind_slash_command",
|
"rewind_slash_command",
|
||||||
"hoa_code_review",
|
"hoa_code_review",
|
||||||
"warpify_footer",
|
"wormhole_footer",
|
||||||
"hoa_notifications",
|
"hoa_notifications",
|
||||||
"hoa_onboarding_flow",
|
"hoa_onboarding_flow",
|
||||||
"agent_toolbar_editor",
|
"agent_toolbar_editor",
|
||||||
@@ -936,7 +936,7 @@ solo_user_byok = []
|
|||||||
billing_and_usage_page_v2 = []
|
billing_and_usage_page_v2 = []
|
||||||
gpt_configurable_context_window = []
|
gpt_configurable_context_window = []
|
||||||
configurable_toolbar = []
|
configurable_toolbar = []
|
||||||
warpify_footer = []
|
wormhole_footer = []
|
||||||
hoa_onboarding_flow = []
|
hoa_onboarding_flow = []
|
||||||
git_operations_in_code_review = []
|
git_operations_in_code_review = []
|
||||||
hoa_remote_control = []
|
hoa_remote_control = []
|
||||||
|
|||||||
@@ -371,7 +371,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
|
|||||||
new_home='~'
|
new_home='~'
|
||||||
bash_term_tab_title="${PWD/#$HOME/$new_home}"
|
bash_term_tab_title="${PWD/#$HOME/$new_home}"
|
||||||
|
|
||||||
if [[ $WARP_IS_LOCAL_SHELL_SESSION == "1" ]]; then
|
if [[ $GALAXY_IS_LOCAL_SHELL_SESSION == "1" ]]; then
|
||||||
warp_title "$bash_term_tab_title"
|
warp_title "$bash_term_tab_title"
|
||||||
else
|
else
|
||||||
bash_term_tab_title_remote="${HOSTNAME%%.*}:$bash_term_tab_title"
|
bash_term_tab_title_remote="${HOSTNAME%%.*}:$bash_term_tab_title"
|
||||||
@@ -962,7 +962,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
|
|||||||
|
|
||||||
# The SSH logic only applies to local sessions, because we don't yet have support for bootstrapping
|
# The SSH logic only applies to local sessions, because we don't yet have support for bootstrapping
|
||||||
# recursive SSH sessions.
|
# recursive SSH sessions.
|
||||||
if [[ $WARP_IS_LOCAL_SHELL_SESSION == "1" ]]; then
|
if [[ $GALAXY_IS_LOCAL_SHELL_SESSION == "1" ]]; then
|
||||||
# This helper function determines whether the user's ssh arguments imply
|
# This helper function determines whether the user's ssh arguments imply
|
||||||
# creation of a non-interactive session or otherwise would conflict with
|
# creation of a non-interactive session or otherwise would conflict with
|
||||||
# our SSH wrapper. Returns 0 for an interactive session; >0 otherwise.
|
# our SSH wrapper. Returns 0 for an interactive session; >0 otherwise.
|
||||||
@@ -1025,7 +1025,7 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
|
|||||||
local control_path="$SSH_SOCKET_DIR/$WARP_SESSION_ID"
|
local control_path="$SSH_SOCKET_DIR/$WARP_SESSION_ID"
|
||||||
local control_master_mode="yes"
|
local control_master_mode="yes"
|
||||||
local external_control_master="false"
|
local external_control_master="false"
|
||||||
if [[ "$WARP_SSH_REUSE_CONTROL_MASTER" == "1" ]]; then
|
if [[ "$GALAXY_SSH_REUSE_CONTROL_MASTER" == "1" ]]; then
|
||||||
local user_control_path=$(command ssh -G "${@:1}" 2>/dev/null | command -p sed -n 's/^controlpath //p')
|
local user_control_path=$(command ssh -G "${@:1}" 2>/dev/null | command -p sed -n 's/^controlpath //p')
|
||||||
case "$user_control_path" in
|
case "$user_control_path" in
|
||||||
"" | none)
|
"" | none)
|
||||||
@@ -1064,9 +1064,9 @@ export TERM_PROGRAM='WarpTerminal'
|
|||||||
# body can distinguish it from local shells. Used to gate the ExitShell
|
# body can distinguish it from local shells. Used to gate the ExitShell
|
||||||
# hook which tears down the remote-server-proxy subprocess.
|
# hook which tears down the remote-server-proxy subprocess.
|
||||||
export WARP_IS_SSH='1'
|
export WARP_IS_SSH='1'
|
||||||
test -n '$WARP_CLIENT_VERSION' && export WARP_CLIENT_VERSION='$WARP_CLIENT_VERSION'
|
test -n '$GALAXY_CLIENT_VERSION' && export GALAXY_CLIENT_VERSION='$GALAXY_CLIENT_VERSION'
|
||||||
# Only forward the protocol version if it was set locally (i.e. the HOANotifications feature flag is on).
|
# Only forward the protocol version if it was set locally (i.e. the HOANotifications feature flag is on).
|
||||||
test -n '$WARP_CLI_AGENT_PROTOCOL_VERSION' && export WARP_CLI_AGENT_PROTOCOL_VERSION='$WARP_CLI_AGENT_PROTOCOL_VERSION'
|
test -n '$GALAXY_CLI_AGENT_PROTOCOL_VERSION' && export GALAXY_CLI_AGENT_PROTOCOL_VERSION='$GALAXY_CLI_AGENT_PROTOCOL_VERSION'
|
||||||
|
|
||||||
hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$control_path'\", \"remote_shell\": \"%s\", \"session_id\": '"$WARP_SESSION_ID"', \"remote_session_id\": '"$remote_session_id"', \"external_control_master\": '"$external_control_master"'}}" "${SHELL##*/}" | command -p od -An -v -tx1 | command -p tr -d " \n")'"
|
hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$control_path'\", \"remote_shell\": \"%s\", \"session_id\": '"$WARP_SESSION_ID"', \"remote_session_id\": '"$remote_session_id"', \"external_control_master\": '"$external_control_master"'}}" "${SHELL##*/}" | command -p od -An -v -tx1 | command -p tr -d " \n")'"
|
||||||
printf '$OSC_START$DCS_JSON_MARKER$OSC_PARAM_SEPARATOR%s$OSC_END' "'$hook'"
|
printf '$OSC_START$DCS_JSON_MARKER$OSC_PARAM_SEPARATOR%s$OSC_END' "'$hook'"
|
||||||
@@ -1137,7 +1137,7 @@ esac
|
|||||||
warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {\"session_id\": $WARP_SESSION_ID}}"
|
warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {\"session_id\": $WARP_SESSION_ID}}"
|
||||||
|
|
||||||
# If the SSH wrapper is not enabled for this session, don't use it.
|
# If the SSH wrapper is not enabled for this session, don't use it.
|
||||||
if [ "$WARP_USE_SSH_WRAPPER" = "1" ]; then
|
if [ "$GALAXY_USE_SSH_WRAPPER" = "1" ]; then
|
||||||
local TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE=""
|
local TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE=""
|
||||||
if [[ "$WARP_SHELL_DEBUG_MODE" == "1" ]]; then
|
if [[ "$WARP_SHELL_DEBUG_MODE" == "1" ]]; then
|
||||||
TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE="-x"
|
TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE="-x"
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
echo -e '\n# Auto-Warpify\n[[ "$-" == *i* ]] && printf '\''\eP$f{"hook": "SourcedRcFileForWarp", "value": { "shell": "%", "uname": "'$(uname)'" }}\x1b\x5c'\'' ' >> %
|
echo -e '\n# Auto-Wormhole\n[[ "$-" == *i* ]] && printf '\''\eP$f{"hook": "SourcedRcFileForWarp", "value": { "shell": "%", "uname": "'$(uname)'" }}\x1b\x5c'\'' ' >> %
|
||||||
|
|||||||
@@ -603,7 +603,7 @@ end
|
|||||||
|
|
||||||
# The SSH logic only applies to local sessions, because we don't yet have support for bootstrapping
|
# The SSH logic only applies to local sessions, because we don't yet have support for bootstrapping
|
||||||
# recursive SSH sessions.
|
# recursive SSH sessions.
|
||||||
if test "$WARP_IS_LOCAL_SHELL_SESSION" = "1"
|
if test "$GALAXY_IS_LOCAL_SHELL_SESSION" = "1"
|
||||||
function is_interactive_ssh_session
|
function is_interactive_ssh_session
|
||||||
# Parse through all ssh options, as defined in the ssh man pages. Send
|
# Parse through all ssh options, as defined in the ssh man pages. Send
|
||||||
# stderr to /dev/null to silence argparse output when an option is invalid.
|
# stderr to /dev/null to silence argparse output when an option is invalid.
|
||||||
@@ -652,7 +652,7 @@ if test "$WARP_IS_LOCAL_SHELL_SESSION" = "1"
|
|||||||
set -l control_path "$SSH_SOCKET_DIR/$WARP_SESSION_ID"
|
set -l control_path "$SSH_SOCKET_DIR/$WARP_SESSION_ID"
|
||||||
set -l control_master_mode "yes"
|
set -l control_master_mode "yes"
|
||||||
set -l external_control_master "false"
|
set -l external_control_master "false"
|
||||||
if test "$WARP_SSH_REUSE_CONTROL_MASTER" = "1"
|
if test "$GALAXY_SSH_REUSE_CONTROL_MASTER" = "1"
|
||||||
set -l user_control_path (command ssh -G $argv 2>/dev/null | command sed -n 's/^controlpath //p')
|
set -l user_control_path (command ssh -G $argv 2>/dev/null | command sed -n 's/^controlpath //p')
|
||||||
# Skip when no ControlPath is configured, and reject resolved
|
# Skip when no ControlPath is configured, and reject resolved
|
||||||
# paths containing characters we cannot safely embed in the SSH
|
# paths containing characters we cannot safely embed in the SSH
|
||||||
@@ -681,9 +681,9 @@ if test "$WARP_IS_LOCAL_SHELL_SESSION" = "1"
|
|||||||
-t $argv \
|
-t $argv \
|
||||||
"
|
"
|
||||||
export TERM_PROGRAM='WarpTerminal'
|
export TERM_PROGRAM='WarpTerminal'
|
||||||
test -n '$WARP_CLIENT_VERSION' && export WARP_CLIENT_VERSION='$WARP_CLIENT_VERSION'
|
test -n '$GALAXY_CLIENT_VERSION' && export GALAXY_CLIENT_VERSION='$GALAXY_CLIENT_VERSION'
|
||||||
# Only forward the protocol version if it was set locally (i.e. the HOANotifications feature flag is on).
|
# Only forward the protocol version if it was set locally (i.e. the HOANotifications feature flag is on).
|
||||||
test -n '$WARP_CLI_AGENT_PROTOCOL_VERSION' && export WARP_CLI_AGENT_PROTOCOL_VERSION='$WARP_CLI_AGENT_PROTOCOL_VERSION'
|
test -n '$GALAXY_CLI_AGENT_PROTOCOL_VERSION' && export GALAXY_CLI_AGENT_PROTOCOL_VERSION='$GALAXY_CLI_AGENT_PROTOCOL_VERSION'
|
||||||
hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$control_path'\", \"remote_shell\": \"%s\", \"session_id\": '"$WARP_SESSION_ID"', \"remote_session_id\": '"$remote_session_id"', \"external_control_master\": '"$external_control_master"'}}" "${SHELL##*/}" | command od -An -v -tx1 | command tr -d " \n")'"
|
hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$control_path'\", \"remote_shell\": \"%s\", \"session_id\": '"$WARP_SESSION_ID"', \"remote_session_id\": '"$remote_session_id"', \"external_control_master\": '"$external_control_master"'}}" "${SHELL##*/}" | command od -An -v -tx1 | command tr -d " \n")'"
|
||||||
printf '$DCS_START$DCS_JSON_MARKER%s$DCS_END' "'$hook'"
|
printf '$DCS_START$DCS_JSON_MARKER%s$DCS_END' "'$hook'"
|
||||||
|
|
||||||
@@ -753,7 +753,7 @@ esac
|
|||||||
if is_interactive_ssh_session $argv
|
if is_interactive_ssh_session $argv
|
||||||
warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {\"session_id\": $WARP_SESSION_ID}}"
|
warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {\"session_id\": $WARP_SESSION_ID}}"
|
||||||
|
|
||||||
if [ "$WARP_USE_SSH_WRAPPER" = "1" ]
|
if [ "$GALAXY_USE_SSH_WRAPPER" = "1" ]
|
||||||
if test $WARP_SHELL_DEBUG_MODE
|
if test $WARP_SHELL_DEBUG_MODE
|
||||||
set -g TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE "-x"
|
set -g TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE "-x"
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
echo -e '\n# Auto-Warpify\nstatus --is-interactive; and printf '\''\eP$f{"hook": "SourcedRcFileForWarp", "value": { "shell": "%", "uname": "'$(uname)'" }}\x1b\x5c'\'' ' >> %
|
echo -e '\n# Auto-Wormhole\nstatus --is-interactive; and printf '\''\eP$f{"hook": "SourcedRcFileForWarp", "value": { "shell": "%", "uname": "'$(uname)'" }}\x1b\x5c'\'' ' >> %
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ if ($PSEdition -eq 'Desktop' -or $IsWindows) {
|
|||||||
$EP = [Microsoft.PowerShell.ExecutionPolicy]
|
$EP = [Microsoft.PowerShell.ExecutionPolicy]
|
||||||
# MachinePolicy and UserPolicy scopes cannot be overridden. If either is Restricted, there's nothing we can do.
|
# MachinePolicy and UserPolicy scopes cannot be overridden. If either is Restricted, there's nothing we can do.
|
||||||
if ((Get-ExecutionPolicy -Scope MachinePolicy) -eq $EP::Restricted -or (Get-ExecutionPolicy -Scope UserPolicy) -eq $EP::Restricted) {
|
if ((Get-ExecutionPolicy -Scope MachinePolicy) -eq $EP::Restricted -or (Get-ExecutionPolicy -Scope UserPolicy) -eq $EP::Restricted) {
|
||||||
Write-Error 'ExecutionPolicy is Restricted. Unable to Warpify this PowerShell session.'
|
Write-Error 'ExecutionPolicy is Restricted. Unable to Wormhole this PowerShell session.'
|
||||||
} elseif ((Get-ExecutionPolicy) -eq $EP::Restricted -and (Get-ExecutionPolicy -Scope MachinePolicy) -eq $EP::Undefined -and (Get-ExecutionPolicy -Scope UserPolicy) -eq $EP::Undefined) {
|
} elseif ((Get-ExecutionPolicy) -eq $EP::Restricted -and (Get-ExecutionPolicy -Scope MachinePolicy) -eq $EP::Undefined -and (Get-ExecutionPolicy -Scope UserPolicy) -eq $EP::Undefined) {
|
||||||
$global:_warp_PSProcessExecPolicy = $(Get-ExecutionPolicy -Scope Process)
|
$global:_warp_PSProcessExecPolicy = $(Get-ExecutionPolicy -Scope Process)
|
||||||
Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned -Force
|
Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned -Force
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
[1;32mSuccess![0m This subshell spawned by [1;34m%[0m has been Warpified.
|
[1;32mSuccess![0m This subshell spawned by [1;34m%[0m has been Wormholed.
|
||||||
@@ -583,7 +583,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
|
|||||||
# set the WARP_DISABLE_AUTO_TITLE flag.
|
# set the WARP_DISABLE_AUTO_TITLE flag.
|
||||||
[[ "${WARP_DISABLE_AUTO_TITLE:-}" != true ]] || return
|
[[ "${WARP_DISABLE_AUTO_TITLE:-}" != true ]] || return
|
||||||
|
|
||||||
if [[ $WARP_IS_LOCAL_SHELL_SESSION == "1" ]]; then
|
if [[ $GALAXY_IS_LOCAL_SHELL_SESSION == "1" ]]; then
|
||||||
warp_title "$ZSH_THEME_TERM_TITLE_IDLE"
|
warp_title "$ZSH_THEME_TERM_TITLE_IDLE"
|
||||||
else
|
else
|
||||||
warp_title "$ZSH_THEME_TERM_TAB_TITLE_IDLE_REMOTE"
|
warp_title "$ZSH_THEME_TERM_TAB_TITLE_IDLE_REMOTE"
|
||||||
@@ -892,7 +892,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
|
|||||||
|
|
||||||
# The SSH logic only applies to local sessions, because we don't yet have support for bootstrapping
|
# The SSH logic only applies to local sessions, because we don't yet have support for bootstrapping
|
||||||
# recursive SSH sessions.
|
# recursive SSH sessions.
|
||||||
if [[ $WARP_IS_LOCAL_SHELL_SESSION == "1" ]]; then
|
if [[ $GALAXY_IS_LOCAL_SHELL_SESSION == "1" ]]; then
|
||||||
# This helper function determines whether the user's ssh arguments imply
|
# This helper function determines whether the user's ssh arguments imply
|
||||||
# creation of a non-interactive session or otherwise would conflict with
|
# creation of a non-interactive session or otherwise would conflict with
|
||||||
# our SSH wrapper. Returns 0 for an interactive session; >0 otherwise.
|
# our SSH wrapper. Returns 0 for an interactive session; >0 otherwise.
|
||||||
@@ -952,7 +952,7 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
|
|||||||
local control_path="$SSH_SOCKET_DIR/$WARP_SESSION_ID"
|
local control_path="$SSH_SOCKET_DIR/$WARP_SESSION_ID"
|
||||||
local control_master_mode="yes"
|
local control_master_mode="yes"
|
||||||
local external_control_master="false"
|
local external_control_master="false"
|
||||||
if [[ "$WARP_SSH_REUSE_CONTROL_MASTER" == "1" ]]; then
|
if [[ "$GALAXY_SSH_REUSE_CONTROL_MASTER" == "1" ]]; then
|
||||||
local user_control_path=$(command ssh -G "${@:1}" 2>/dev/null | command -p sed -n 's/^controlpath //p')
|
local user_control_path=$(command ssh -G "${@:1}" 2>/dev/null | command -p sed -n 's/^controlpath //p')
|
||||||
case "$user_control_path" in
|
case "$user_control_path" in
|
||||||
"" | none)
|
"" | none)
|
||||||
@@ -991,9 +991,9 @@ export TERM_PROGRAM='WarpTerminal'
|
|||||||
# body can distinguish it from local shells. Used to gate the ExitShell
|
# body can distinguish it from local shells. Used to gate the ExitShell
|
||||||
# hook which tears down the remote-server-proxy subprocess.
|
# hook which tears down the remote-server-proxy subprocess.
|
||||||
export WARP_IS_SSH='1'
|
export WARP_IS_SSH='1'
|
||||||
test -n '$WARP_CLIENT_VERSION' && export WARP_CLIENT_VERSION='$WARP_CLIENT_VERSION'
|
test -n '$GALAXY_CLIENT_VERSION' && export GALAXY_CLIENT_VERSION='$GALAXY_CLIENT_VERSION'
|
||||||
# Only forward the protocol version if it was set locally (i.e. the HOANotifications feature flag is on).
|
# Only forward the protocol version if it was set locally (i.e. the HOANotifications feature flag is on).
|
||||||
test -n '$WARP_CLI_AGENT_PROTOCOL_VERSION' && export WARP_CLI_AGENT_PROTOCOL_VERSION='$WARP_CLI_AGENT_PROTOCOL_VERSION'
|
test -n '$GALAXY_CLI_AGENT_PROTOCOL_VERSION' && export GALAXY_CLI_AGENT_PROTOCOL_VERSION='$GALAXY_CLI_AGENT_PROTOCOL_VERSION'
|
||||||
hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$control_path'\", \"remote_shell\": \"%s\", \"session_id\": '"$WARP_SESSION_ID"', \"remote_session_id\": '"$remote_session_id"', \"external_control_master\": '"$external_control_master"'}}" "${SHELL##*/}" | command -p od -An -v -tx1 | command -p tr -d " \n")'"
|
hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$control_path'\", \"remote_shell\": \"%s\", \"session_id\": '"$WARP_SESSION_ID"', \"remote_session_id\": '"$remote_session_id"', \"external_control_master\": '"$external_control_master"'}}" "${SHELL##*/}" | command -p od -An -v -tx1 | command -p tr -d " \n")'"
|
||||||
printf '$OSC_START$DCS_JSON_MARKER$OSC_PARAM_SEPARATOR%s$OSC_END' "'$hook'"
|
printf '$OSC_START$DCS_JSON_MARKER$OSC_PARAM_SEPARATOR%s$OSC_END' "'$hook'"
|
||||||
|
|
||||||
@@ -1065,7 +1065,7 @@ esac
|
|||||||
warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {\"session_id\": $WARP_SESSION_ID}}"
|
warp_send_json_message "{\"hook\": \"PreInteractiveSSHSession\", \"value\": {\"session_id\": $WARP_SESSION_ID}}"
|
||||||
|
|
||||||
# If the SSH wrapper is not enabled for this session, don't use it.
|
# If the SSH wrapper is not enabled for this session, don't use it.
|
||||||
if [ "$WARP_USE_SSH_WRAPPER" = "1" ]; then
|
if [ "$GALAXY_USE_SSH_WRAPPER" = "1" ]; then
|
||||||
local TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE=""
|
local TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE=""
|
||||||
if [[ "$WARP_SHELL_DEBUG_MODE" == "1" ]]; then
|
if [[ "$WARP_SHELL_DEBUG_MODE" == "1" ]]; then
|
||||||
TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE="-x"
|
TRACE_FLAG_IF_WARP_SHELL_DEBUG_MODE="-x"
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ fn get_supported_tools(params: &RequestParams) -> Vec<api::ToolType> {
|
|||||||
api::ToolType::SearchCodebase,
|
api::ToolType::SearchCodebase,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
Some(SessionType::WarpifiedRemote { host_id: Some(_) }) => {
|
Some(SessionType::WormholedRemote { host_id: Some(_) }) => {
|
||||||
// Remote session with a known host — enable tools that route
|
// Remote session with a known host — enable tools that route
|
||||||
// through RemoteServerClient. The host_id is only populated
|
// through RemoteServerClient. The host_id is only populated
|
||||||
// after a successful connection handshake, so its presence is a
|
// after a successful connection handshake, so its presence is a
|
||||||
@@ -81,7 +81,7 @@ fn get_supported_tools(params: &RequestParams) -> Vec<api::ToolType> {
|
|||||||
supported_tools.push(api::ToolType::SearchCodebase);
|
supported_tools.push(api::ToolType::SearchCodebase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(SessionType::WarpifiedRemote { host_id: None }) => {}
|
Some(SessionType::WormholedRemote { host_id: None }) => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if FeatureFlag::ListSkills.is_enabled() {
|
if FeatureFlag::ListSkills.is_enabled() {
|
||||||
@@ -113,13 +113,13 @@ fn get_supported_cli_agent_tools(params: &RequestParams) -> Vec<api::ToolType> {
|
|||||||
supported_cli_agent_tools
|
supported_cli_agent_tools
|
||||||
.extend(&[api::ToolType::ReadFiles, api::ToolType::SearchCodebase]);
|
.extend(&[api::ToolType::ReadFiles, api::ToolType::SearchCodebase]);
|
||||||
}
|
}
|
||||||
Some(SessionType::WarpifiedRemote { host_id: Some(_) }) => {
|
Some(SessionType::WormholedRemote { host_id: Some(_) }) => {
|
||||||
supported_cli_agent_tools.push(api::ToolType::ReadFiles);
|
supported_cli_agent_tools.push(api::ToolType::ReadFiles);
|
||||||
if FeatureFlag::RemoteCodebaseIndexing.is_enabled() {
|
if FeatureFlag::RemoteCodebaseIndexing.is_enabled() {
|
||||||
supported_cli_agent_tools.push(api::ToolType::SearchCodebase);
|
supported_cli_agent_tools.push(api::ToolType::SearchCodebase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(SessionType::WarpifiedRemote { host_id: None }) => {}
|
Some(SessionType::WormholedRemote { host_id: None }) => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
supported_cli_agent_tools
|
supported_cli_agent_tools
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ fn request_params_with_ask_user_question_enabled(ask_user_question_enabled: bool
|
|||||||
fn request_params_for_remote(host_id: Option<HostId>) -> RequestParams {
|
fn request_params_for_remote(host_id: Option<HostId>) -> RequestParams {
|
||||||
let mut params = request_params_with_ask_user_question_enabled(false);
|
let mut params = request_params_with_ask_user_question_enabled(false);
|
||||||
params.session_context =
|
params.session_context =
|
||||||
SessionContext::new_with_session_type_for_test(Some(SessionType::WarpifiedRemote {
|
SessionContext::new_with_session_type_for_test(Some(SessionType::WormholedRemote {
|
||||||
host_id,
|
host_id,
|
||||||
}));
|
}));
|
||||||
params
|
params
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ static DEFAULT_TIPS: LazyLock<Vec<AgentTip>> = LazyLock::new(|| {
|
|||||||
},
|
},
|
||||||
AgentTip {
|
AgentTip {
|
||||||
description: "Wormhole a remote SSH session to enable the agent inside that environment.".to_string(),
|
description: "Wormhole a remote SSH session to enable the agent inside that environment.".to_string(),
|
||||||
link: Some("https://docs.warp.dev/terminal/warpify".to_string()),
|
link: None,
|
||||||
binding_name: None,
|
binding_name: None,
|
||||||
action: None,
|
action: None,
|
||||||
kind: AgentTipKind::General,
|
kind: AgentTipKind::General,
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ impl ReadFilesExecutor {
|
|||||||
// Check if this is a remote session with a connected host.
|
// Check if this is a remote session with a connected host.
|
||||||
let session_type = self.active_session.as_ref(ctx).session_type(ctx);
|
let session_type = self.active_session.as_ref(ctx).session_type(ctx);
|
||||||
let host_request_handle = match &session_type {
|
let host_request_handle = match &session_type {
|
||||||
Some(SessionType::WarpifiedRemote {
|
Some(SessionType::WormholedRemote {
|
||||||
host_id: Some(host_id),
|
host_id: Some(host_id),
|
||||||
}) => Some(
|
}) => Some(
|
||||||
remote_server::manager::RemoteServerManager::as_ref(ctx)
|
remote_server::manager::RemoteServerManager::as_ref(ctx)
|
||||||
@@ -127,7 +127,7 @@ impl ReadFilesExecutor {
|
|||||||
// Remote session without a usable remote server connection. File reading
|
// Remote session without a usable remote server connection. File reading
|
||||||
// requires either local access or a connected remote server, neither
|
// requires either local access or a connected remote server, neither
|
||||||
// of which is available.
|
// of which is available.
|
||||||
if matches!(session_type, Some(SessionType::WarpifiedRemote { .. }))
|
if matches!(session_type, Some(SessionType::WormholedRemote { .. }))
|
||||||
&& host_request_handle.is_none()
|
&& host_request_handle.is_none()
|
||||||
{
|
{
|
||||||
return ActionExecution::Sync(AIAgentActionResultType::ReadFiles(
|
return ActionExecution::Sync(AIAgentActionResultType::ReadFiles(
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ fn disconnected_remote_session_does_not_fall_back_to_client_global_bundled_skill
|
|||||||
sessions.register_session_for_test(
|
sessions.register_session_for_test(
|
||||||
SessionInfo::new_for_test()
|
SessionInfo::new_for_test()
|
||||||
.with_id(session_id)
|
.with_id(session_id)
|
||||||
.with_session_type(BootstrapSessionType::WarpifiedRemote),
|
.with_session_type(BootstrapSessionType::WormholedRemote),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
let (_model_events_tx, model_events_rx) = unbounded();
|
let (_model_events_tx, model_events_rx) = unbounded();
|
||||||
@@ -235,7 +235,7 @@ fn remote_session_reads_remote_bundled_skill_catalog() {
|
|||||||
sessions.register_session_for_test(
|
sessions.register_session_for_test(
|
||||||
SessionInfo::new_for_test()
|
SessionInfo::new_for_test()
|
||||||
.with_id(session_id)
|
.with_id(session_id)
|
||||||
.with_session_type(BootstrapSessionType::WarpifiedRemote),
|
.with_session_type(BootstrapSessionType::WormholedRemote),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
let session = sessions
|
let session = sessions
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ impl RequestFileEditsExecutor {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let diff_session_type = match self.active_session.as_ref(ctx).session_type(ctx) {
|
let diff_session_type = match self.active_session.as_ref(ctx).session_type(ctx) {
|
||||||
Some(SessionType::WarpifiedRemote {
|
Some(SessionType::WormholedRemote {
|
||||||
host_id: Some(host_id),
|
host_id: Some(host_id),
|
||||||
}) => DiffSessionType::Remote(host_id.clone()),
|
}) => DiffSessionType::Remote(host_id.clone()),
|
||||||
_ => DiffSessionType::Local,
|
_ => DiffSessionType::Local,
|
||||||
|
|||||||
@@ -360,7 +360,7 @@ fn format_session_location(session: &Session, working_directory: Option<&str>) -
|
|||||||
let hostname = session.hostname();
|
let hostname = session.hostname();
|
||||||
match session_type {
|
match session_type {
|
||||||
SessionType::Local => Some(display_path),
|
SessionType::Local => Some(display_path),
|
||||||
SessionType::WarpifiedRemote { .. } => Some(format!("{user}@{hostname}:{display_path}")),
|
SessionType::WormholedRemote { .. } => Some(format!("{user}@{hostname}:{display_path}")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,7 +510,7 @@ fn current_working_directory_for_zero_state(terminal_model: &TerminalModel) -> O
|
|||||||
.is_some_and(|pending_session_info| {
|
.is_some_and(|pending_session_info| {
|
||||||
matches!(
|
matches!(
|
||||||
pending_session_info.session_type,
|
pending_session_info.session_type,
|
||||||
BootstrapSessionType::WarpifiedRemote
|
BootstrapSessionType::WormholedRemote
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
(!terminal_model.block_list().is_bootstrapped() && !is_bootstrapping_remote_shell)
|
(!terminal_model.block_list().is_bootstrapped() && !is_bootstrapping_remote_shell)
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ use crate::terminal::model_events::{ModelEvent, ModelEventDispatcher};
|
|||||||
use crate::terminal::view::ambient_agent::{
|
use crate::terminal::view::ambient_agent::{
|
||||||
is_cloud_agent_pre_first_exchange, AmbientAgentViewModel, AmbientAgentViewModelEvent,
|
is_cloud_agent_pre_first_exchange, AmbientAgentViewModel, AmbientAgentViewModelEvent,
|
||||||
};
|
};
|
||||||
use crate::terminal::warpify::render::LEFT_STRIPE_WIDTH;
|
use crate::terminal::wormhole::render::LEFT_STRIPE_WIDTH;
|
||||||
use crate::terminal::{
|
use crate::terminal::{
|
||||||
TerminalModel, CANCEL_COMMAND_KEYBINDING, TOGGLE_AUTOEXECUTE_MODE_KEYBINDING,
|
TerminalModel, CANCEL_COMMAND_KEYBINDING, TOGGLE_AUTOEXECUTE_MODE_KEYBINDING,
|
||||||
TOGGLE_HIDE_CLI_RESPONSES_KEYBINDING, TOGGLE_QUEUE_NEXT_PROMPT_KEYBINDING,
|
TOGGLE_HIDE_CLI_RESPONSES_KEYBINDING, TOGGLE_QUEUE_NEXT_PROMPT_KEYBINDING,
|
||||||
|
|||||||
@@ -314,11 +314,11 @@ impl SessionContext {
|
|||||||
&self.current_working_directory
|
&self.current_working_directory
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the remote host ID if this is a `WarpifiedRemote` session with
|
/// Returns the remote host ID if this is a `WormholedRemote` session with
|
||||||
/// a connected `RemoteServerClient`.
|
/// a connected `RemoteServerClient`.
|
||||||
pub fn host_id(&self) -> Option<&galaxy_core::HostId> {
|
pub fn host_id(&self) -> Option<&galaxy_core::HostId> {
|
||||||
match &self.session_type {
|
match &self.session_type {
|
||||||
Some(SessionType::WarpifiedRemote { host_id }) => host_id.as_ref(),
|
Some(SessionType::WormholedRemote { host_id }) => host_id.as_ref(),
|
||||||
Some(SessionType::Local) | None => None,
|
Some(SessionType::Local) | None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -326,17 +326,17 @@ impl SessionContext {
|
|||||||
/// Returns `true` if this is a remote session (regardless of whether
|
/// Returns `true` if this is a remote session (regardless of whether
|
||||||
/// the remote server client is connected).
|
/// the remote server client is connected).
|
||||||
pub fn is_remote(&self) -> bool {
|
pub fn is_remote(&self) -> bool {
|
||||||
matches!(self.session_type, Some(SessionType::WarpifiedRemote { .. }))
|
matches!(self.session_type, Some(SessionType::WormholedRemote { .. }))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn skill_path_origin(&self) -> SkillPathOrigin {
|
pub fn skill_path_origin(&self) -> SkillPathOrigin {
|
||||||
match &self.session_type {
|
match &self.session_type {
|
||||||
Some(SessionType::WarpifiedRemote {
|
Some(SessionType::WormholedRemote {
|
||||||
host_id: Some(host_id),
|
host_id: Some(host_id),
|
||||||
}) => SkillPathOrigin::Remote {
|
}) => SkillPathOrigin::Remote {
|
||||||
host_id: host_id.clone(),
|
host_id: host_id.clone(),
|
||||||
},
|
},
|
||||||
Some(SessionType::WarpifiedRemote { host_id: None }) => SkillPathOrigin::Unavailable,
|
Some(SessionType::WormholedRemote { host_id: None }) => SkillPathOrigin::Unavailable,
|
||||||
Some(SessionType::Local) | None => SkillPathOrigin::Local,
|
Some(SessionType::Local) | None => SkillPathOrigin::Local,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! This module contains rendering functions for various requested inline actions that have not yet
|
//! This module contains rendering functions for various requested inline actions that have not yet
|
||||||
//! been transformed into a [`View`] component. This currently encompasses UI for file retrieval,
|
//! been transformed into a [`View`] component. This currently encompasses UI for file retrieval,
|
||||||
//! environmental variable collection, and SSH Warpification, to name a few.
|
//! environmental variable collection, and SSH Wormholing, to name a few.
|
||||||
//!
|
//!
|
||||||
//! There's quite a bit of duplication between function-based inline actions and view-based inline
|
//! There's quite a bit of duplication between function-based inline actions and view-based inline
|
||||||
//! actions. Moreover, the header rendering functions here don't make use of the HeaderConfig.
|
//! actions. Moreover, the header rendering functions here don't make use of the HeaderConfig.
|
||||||
|
|||||||
@@ -408,7 +408,7 @@ impl PassiveSuggestionsModel {
|
|||||||
.active_session
|
.active_session
|
||||||
.as_ref(ctx)
|
.as_ref(ctx)
|
||||||
.session_type(ctx)
|
.session_type(ctx)
|
||||||
.map(|session_type| matches!(session_type, SessionType::WarpifiedRemote { .. }))
|
.map(|session_type| matches!(session_type, SessionType::WormholedRemote { .. }))
|
||||||
.unwrap_or(true);
|
.unwrap_or(true);
|
||||||
if !can_read_file || should_skip_for_remote {
|
if !can_read_file || should_skip_for_remote {
|
||||||
let reason = if !can_read_file {
|
let reason = if !can_read_file {
|
||||||
|
|||||||
@@ -1037,6 +1037,157 @@ impl PersistedWorkspace {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensures Galaxy's own settings file has schema-backed TOML language support.
|
||||||
|
/// This managed server is intentionally not persisted as a code workspace.
|
||||||
|
#[cfg(feature = "local_fs")]
|
||||||
|
pub fn ensure_settings_toml_lsp(&mut self, file_path: PathBuf, ctx: &mut ModelContext<Self>) {
|
||||||
|
if file_path != crate::settings::user_preferences_toml_file_path() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let server_type = LSPServerType::Tombi;
|
||||||
|
let Some(workspace_root) = file_path.parent().map(Path::to_path_buf) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if LspManagerModel::as_ref(ctx).server_registered(&workspace_root, server_type, ctx) {
|
||||||
|
LspManagerModel::handle(ctx).update(ctx, |manager, ctx| {
|
||||||
|
manager.start_all(workspace_root, ctx);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.lsp_installation_status.get(&server_type)
|
||||||
|
== Some(&LSPInstallationStatus::Installing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.lsp_installation_status
|
||||||
|
.insert(server_type, LSPInstallationStatus::Installing);
|
||||||
|
ctx.emit(PersistedWorkspaceEvent::InstallStatusUpdate {
|
||||||
|
server_type,
|
||||||
|
status: LSPInstallationStatus::Installing,
|
||||||
|
});
|
||||||
|
|
||||||
|
let path_future = LocalShellState::handle(ctx).update(ctx, |shell_state, ctx| {
|
||||||
|
shell_state.get_interactive_path_env_var(ctx)
|
||||||
|
});
|
||||||
|
let http_client = ServerApiProvider::as_ref(ctx).get_http_client();
|
||||||
|
let file_path_for_install = file_path.clone();
|
||||||
|
|
||||||
|
ctx.spawn(
|
||||||
|
async move {
|
||||||
|
let path_env_var = path_future.await;
|
||||||
|
let executor = lsp::CommandBuilder::new(path_env_var.clone());
|
||||||
|
let candidate = server_type.candidate(http_client.clone());
|
||||||
|
|
||||||
|
if !candidate.is_installed(&executor).await {
|
||||||
|
let metadata = candidate.fetch_latest_server_metadata().await?;
|
||||||
|
candidate.install(metadata, &executor).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema_path = crate::settings::schema_export::ensure_runtime_settings_schema()?;
|
||||||
|
let schema_uri = url::Url::from_file_path(&schema_path)
|
||||||
|
.map_err(|()| anyhow::anyhow!("Invalid settings schema path: {}", schema_path.display()))?;
|
||||||
|
let file_match = file_path_for_install.to_string_lossy().into_owned();
|
||||||
|
|
||||||
|
Ok::<_, anyhow::Error>((
|
||||||
|
path_env_var,
|
||||||
|
schema_uri.to_string(),
|
||||||
|
file_match,
|
||||||
|
))
|
||||||
|
},
|
||||||
|
move |me, result, ctx| match result {
|
||||||
|
Ok((path_env_var, schema_uri, file_match)) => {
|
||||||
|
me.lsp_installation_status
|
||||||
|
.insert(server_type, LSPInstallationStatus::Installed);
|
||||||
|
ctx.emit(PersistedWorkspaceEvent::InstallStatusUpdate {
|
||||||
|
server_type,
|
||||||
|
status: LSPInstallationStatus::Installed,
|
||||||
|
});
|
||||||
|
|
||||||
|
let log_relative_path =
|
||||||
|
crate::code::lsp_logs::relative_log_path(server_type, &workspace_root);
|
||||||
|
let http_client = ServerApiProvider::as_ref(ctx).get_http_client();
|
||||||
|
let config = LspServerConfig::new(
|
||||||
|
server_type,
|
||||||
|
workspace_root.clone(),
|
||||||
|
path_env_var,
|
||||||
|
ChannelState::app_id().application_name().to_string(),
|
||||||
|
http_client,
|
||||||
|
)
|
||||||
|
.with_log_relative_path(log_relative_path)
|
||||||
|
.with_post_initialize_notification(
|
||||||
|
"tombi/associateSchema",
|
||||||
|
serde_json::json!({
|
||||||
|
"title": "Galaxy Settings",
|
||||||
|
"description": "Galaxy's generated settings schema",
|
||||||
|
"uri": schema_uri,
|
||||||
|
"fileMatch": [file_match],
|
||||||
|
"tomlVersion": "v1.1.0",
|
||||||
|
"force": true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
let manager = LspManagerModel::handle(ctx);
|
||||||
|
manager.update(ctx, |manager, ctx| {
|
||||||
|
manager.register(workspace_root.clone(), config, ctx);
|
||||||
|
});
|
||||||
|
|
||||||
|
let workspace_root_display = workspace_root.display().to_string();
|
||||||
|
if let Some(server) = manager
|
||||||
|
.as_ref(ctx)
|
||||||
|
.servers_for_workspace(&workspace_root)
|
||||||
|
.and_then(|servers| servers.last())
|
||||||
|
.cloned()
|
||||||
|
{
|
||||||
|
ctx.subscribe_to_model(&server, move |_, _, event, ctx| {
|
||||||
|
if let LspEvent::Failed(error) = event {
|
||||||
|
if let Some(window_id) = WindowManager::as_ref(ctx).active_window() {
|
||||||
|
ToastStack::handle(ctx).update(ctx, |toast_stack, ctx| {
|
||||||
|
toast_stack.add_ephemeral_toast(
|
||||||
|
DismissibleToast::error(format!(
|
||||||
|
"Failed to start TOML language support for {workspace_root_display}: {error}"
|
||||||
|
)),
|
||||||
|
window_id,
|
||||||
|
ctx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.update(ctx, |manager, ctx| {
|
||||||
|
manager.start_all(workspace_root, ctx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
log::warn!("Failed to prepare TOML language support: {error:#}");
|
||||||
|
me.lsp_installation_status
|
||||||
|
.insert(server_type, LSPInstallationStatus::NotInstalled);
|
||||||
|
ctx.emit(PersistedWorkspaceEvent::InstallStatusUpdate {
|
||||||
|
server_type,
|
||||||
|
status: LSPInstallationStatus::NotInstalled,
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(window_id) = WindowManager::as_ref(ctx).active_window() {
|
||||||
|
ToastStack::handle(ctx).update(ctx, |toast_stack, ctx| {
|
||||||
|
toast_stack.add_ephemeral_toast(
|
||||||
|
DismissibleToast::error(format!(
|
||||||
|
"Failed to prepare TOML language support: {error}"
|
||||||
|
)),
|
||||||
|
window_id,
|
||||||
|
ctx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Starts all enabled LSP servers for the given file path.
|
/// Starts all enabled LSP servers for the given file path.
|
||||||
/// This looks up the workspace root and starts any servers that are enabled but not yet running.
|
/// This looks up the workspace root and starts any servers that are enabled but not yet running.
|
||||||
#[cfg(feature = "local_fs")]
|
#[cfg(feature = "local_fs")]
|
||||||
|
|||||||
@@ -159,12 +159,14 @@ mod appimage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mod package_manager {
|
mod package_manager {
|
||||||
|
use anyhow::{bail, Result};
|
||||||
use galaxyui::elements::{Container, FormattedTextElement, HighlightedHyperlink};
|
use galaxyui::elements::{Container, FormattedTextElement, HighlightedHyperlink};
|
||||||
use galaxyui::{Element, SingletonEntity as _};
|
use galaxyui::{Element, SingletonEntity as _};
|
||||||
use markdown_parser::{
|
use markdown_parser::{
|
||||||
FormattedText, FormattedTextFragment, FormattedTextHeader, FormattedTextLine,
|
FormattedText, FormattedTextFragment, FormattedTextHeader, FormattedTextLine,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::{PackageManager, CURRENT_EXE};
|
||||||
use crate::appearance::Appearance;
|
use crate::appearance::Appearance;
|
||||||
|
|
||||||
pub struct AutoupdateContextBlock {
|
pub struct AutoupdateContextBlock {
|
||||||
|
|||||||
@@ -1,160 +1,34 @@
|
|||||||
//! Generates a JSON Schema file describing Warp's user-facing settings.
|
//! Generates a JSON Schema file describing Galaxy's user-facing settings.
|
||||||
//!
|
//!
|
||||||
//! Usage:
|
//! Usage:
|
||||||
//! ```
|
//! ```
|
||||||
//! cargo run --bin generate_settings_schema -- [--channel dev|preview|stable] [output_path]
|
//! cargo run --bin generate_settings_schema -- [--channel dev|preview|stable] [output_path]
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use galaxy_core::features::{
|
use galaxy::settings::schema_export::generate_settings_schema;
|
||||||
FeatureFlag, DEBUG_FLAGS, DOGFOOD_FLAGS, PREVIEW_FLAGS, RELEASE_FLAGS,
|
|
||||||
};
|
|
||||||
use schemars::SchemaGenerator;
|
|
||||||
use serde_json::{Map, Value};
|
|
||||||
use settings::schema::SettingSchemaEntry;
|
|
||||||
|
|
||||||
/// Ensures all `inventory::submit!` registrations from the app crate's
|
/// Ensures all `inventory::submit!` registrations from the app crate's
|
||||||
/// dependency tree are linked into the binary.
|
/// dependency tree are linked into the binary.
|
||||||
///
|
|
||||||
/// Binary targets only link crate code that is transitively referenced.
|
|
||||||
/// Without an explicit reference to the `warp` library, the linker will
|
|
||||||
/// not include most of the app's object files and the `inventory`
|
|
||||||
/// submissions they contain.
|
|
||||||
fn ensure_settings_linked() {
|
fn ensure_settings_linked() {
|
||||||
let _ = std::hint::black_box(galaxy::settings::RESTORE_SESSION);
|
let _ = std::hint::black_box(galaxy::settings::RESTORE_SESSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Recursively strips `minimum`, `maximum`, and `format` from integer and
|
|
||||||
/// number schemas. schemars derives these from Rust type bounds (e.g. `u8`
|
|
||||||
/// → `minimum: 0, maximum: 255, format: "uint8"`), which are misleading
|
|
||||||
/// for settings whose valid domain is narrower than the type allows.
|
|
||||||
fn strip_numeric_metadata(value: &mut Value) {
|
|
||||||
match value {
|
|
||||||
Value::Object(map) => {
|
|
||||||
let is_numeric = map
|
|
||||||
.get("type")
|
|
||||||
.and_then(Value::as_str)
|
|
||||||
.is_some_and(|t| t == "integer" || t == "number");
|
|
||||||
|
|
||||||
if is_numeric {
|
|
||||||
map.remove("minimum");
|
|
||||||
map.remove("maximum");
|
|
||||||
map.remove("format");
|
|
||||||
}
|
|
||||||
|
|
||||||
for val in map.values_mut() {
|
|
||||||
strip_numeric_metadata(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Array(arr) => {
|
|
||||||
for val in arr {
|
|
||||||
strip_numeric_metadata(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes `{"enum": [], "type": "string"}` entries from `oneOf` arrays.
|
|
||||||
/// schemars emits an empty enum bucket for externally-tagged enums when all
|
|
||||||
/// unit variants have individual descriptions (and are therefore promoted to
|
|
||||||
/// separate `oneOf` branches with `const`). The empty bucket is unreachable
|
|
||||||
/// and confuses schema consumers.
|
|
||||||
fn strip_empty_enum_entries(value: &mut Value) {
|
|
||||||
match value {
|
|
||||||
Value::Object(map) => {
|
|
||||||
if let Some(Value::Array(one_of)) = map.get_mut("oneOf") {
|
|
||||||
one_of.retain(|entry| {
|
|
||||||
!matches!(entry, Value::Object(obj)
|
|
||||||
if obj.get("enum").is_some_and(|e| e.as_array().is_some_and(|a| a.is_empty()))
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for val in map.values_mut() {
|
|
||||||
strip_empty_enum_entries(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Array(arr) => {
|
|
||||||
for val in arr {
|
|
||||||
strip_empty_enum_entries(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn active_flags_for_channel(channel: &str) -> HashSet<FeatureFlag> {
|
|
||||||
let mut flags = HashSet::new();
|
|
||||||
|
|
||||||
let flag_lists: &[&[FeatureFlag]] = match channel {
|
|
||||||
"stable" => &[RELEASE_FLAGS],
|
|
||||||
"preview" => &[RELEASE_FLAGS, PREVIEW_FLAGS],
|
|
||||||
"dev" => &[RELEASE_FLAGS, PREVIEW_FLAGS, DOGFOOD_FLAGS, DEBUG_FLAGS],
|
|
||||||
other => {
|
|
||||||
eprintln!("Unknown channel '{other}', defaulting to dev");
|
|
||||||
&[RELEASE_FLAGS, PREVIEW_FLAGS, DOGFOOD_FLAGS, DEBUG_FLAGS]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for list in flag_lists {
|
|
||||||
for flag in *list {
|
|
||||||
flags.insert(*flag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
flags
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates intermediate hierarchy objects so that a setting at e.g.
|
|
||||||
/// `appearance.text` is nested under `properties.appearance.properties.text.properties`.
|
|
||||||
fn ensure_hierarchy<'a>(
|
|
||||||
root_properties: &'a mut Map<String, Value>,
|
|
||||||
hierarchy: &str,
|
|
||||||
) -> &'a mut Map<String, Value> {
|
|
||||||
let segments: Vec<&str> = hierarchy.split('.').collect();
|
|
||||||
let mut current = root_properties;
|
|
||||||
|
|
||||||
for segment in segments {
|
|
||||||
// Ensure the segment object exists
|
|
||||||
let entry = current.entry(segment.to_string()).or_insert_with(|| {
|
|
||||||
Value::Object({
|
|
||||||
let mut m = Map::new();
|
|
||||||
m.insert("type".to_string(), Value::String("object".to_string()));
|
|
||||||
m.insert("properties".to_string(), Value::Object(Map::new()));
|
|
||||||
m
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
// Navigate into its properties
|
|
||||||
current = entry
|
|
||||||
.as_object_mut()
|
|
||||||
.expect("hierarchy node should be an object")
|
|
||||||
.entry("properties")
|
|
||||||
.or_insert_with(|| Value::Object(Map::new()))
|
|
||||||
.as_object_mut()
|
|
||||||
.expect("properties should be an object");
|
|
||||||
}
|
|
||||||
|
|
||||||
current
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
ensure_settings_linked();
|
ensure_settings_linked();
|
||||||
|
|
||||||
let args: Vec<String> = std::env::args().collect();
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
|
||||||
let mut channel = "dev";
|
let mut channel = "dev";
|
||||||
let mut output_path: Option<&str> = None;
|
let mut output_path: Option<&str> = None;
|
||||||
let mut i = 1;
|
let mut index = 1;
|
||||||
while i < args.len() {
|
|
||||||
match args[i].as_str() {
|
while index < args.len() {
|
||||||
|
match args[index].as_str() {
|
||||||
"--channel" => {
|
"--channel" => {
|
||||||
i += 1;
|
index += 1;
|
||||||
if i < args.len() {
|
if index < args.len() {
|
||||||
channel = &args[i];
|
channel = &args[index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
arg if !arg.starts_with('-') => {
|
arg if !arg.starts_with('-') => {
|
||||||
@@ -165,101 +39,16 @@ fn main() {
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i += 1;
|
index += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let active_flags = active_flags_for_channel(channel);
|
let (output, entry_count) = generate_settings_schema(channel);
|
||||||
let mut generator = SchemaGenerator::default();
|
|
||||||
let mut root_properties = Map::new();
|
|
||||||
let mut entry_count = 0;
|
|
||||||
|
|
||||||
for entry in inventory::iter::<SettingSchemaEntry> {
|
|
||||||
// Skip private settings
|
|
||||||
if entry.is_private {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip settings whose feature flag is not active
|
|
||||||
if let Some(flag) = entry.feature_flag {
|
|
||||||
if !active_flags.contains(&flag) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let type_schema = (entry.schema_fn)(&mut generator);
|
|
||||||
|
|
||||||
let mut schema_value: Value = type_schema.to_value();
|
|
||||||
|
|
||||||
// Compute default value — prefer file default over serde default
|
|
||||||
let default_json = (entry.file_default_value_fn)();
|
|
||||||
|
|
||||||
if let Ok(default_value) = serde_json::from_str::<Value>(&default_json) {
|
|
||||||
if let Some(obj) = schema_value.as_object_mut() {
|
|
||||||
obj.insert("default".to_string(), default_value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always overwrite description with the macro-provided one
|
|
||||||
if !entry.description.is_empty() {
|
|
||||||
if let Some(obj) = schema_value.as_object_mut() {
|
|
||||||
obj.insert(
|
|
||||||
"description".to_string(),
|
|
||||||
Value::String(entry.description.to_string()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Place the setting in the hierarchy
|
|
||||||
let target = if let Some(hierarchy) = entry.hierarchy {
|
|
||||||
ensure_hierarchy(&mut root_properties, hierarchy)
|
|
||||||
} else {
|
|
||||||
&mut root_properties
|
|
||||||
};
|
|
||||||
|
|
||||||
target.insert(entry.storage_key.to_string(), schema_value);
|
|
||||||
entry_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect $defs from the generator
|
|
||||||
let defs_map = generator.take_definitions(true);
|
|
||||||
|
|
||||||
// Assemble the root document
|
|
||||||
let mut root = Map::new();
|
|
||||||
root.insert(
|
|
||||||
"$schema".to_string(),
|
|
||||||
Value::String("https://json-schema.org/draft/2020-12/schema".to_string()),
|
|
||||||
);
|
|
||||||
root.insert(
|
|
||||||
"title".to_string(),
|
|
||||||
Value::String("Galaxy Settings".to_string()),
|
|
||||||
);
|
|
||||||
root.insert(
|
|
||||||
"description".to_string(),
|
|
||||||
Value::String(format!(
|
|
||||||
"JSON Schema for Galaxy settings ({channel} channel, {entry_count} settings)"
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
root.insert("type".to_string(), Value::String("object".to_string()));
|
|
||||||
root.insert("properties".to_string(), Value::Object(root_properties));
|
|
||||||
|
|
||||||
if !defs_map.is_empty() {
|
|
||||||
root.insert("$defs".to_string(), Value::Object(defs_map));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strip type-derived numeric metadata (minimum, maximum, format) that
|
|
||||||
// schemars emits from Rust primitive bounds (e.g. u8 → max 255).
|
|
||||||
// These leak implementation details rather than semantic constraints.
|
|
||||||
let mut root_value = Value::Object(root);
|
|
||||||
strip_numeric_metadata(&mut root_value);
|
|
||||||
strip_empty_enum_entries(&mut root_value);
|
|
||||||
|
|
||||||
let output = serde_json::to_string_pretty(&root_value).expect("schema should serialize");
|
|
||||||
|
|
||||||
if let Some(path) = output_path {
|
if let Some(path) = output_path {
|
||||||
let mut file = std::fs::File::create(path)
|
let mut file = std::fs::File::create(path)
|
||||||
.unwrap_or_else(|e| panic!("Failed to create output file '{path}': {e}"));
|
.unwrap_or_else(|error| panic!("Failed to create output file '{path}': {error}"));
|
||||||
file.write_all(output.as_bytes())
|
file.write_all(output.as_bytes())
|
||||||
.unwrap_or_else(|e| panic!("Failed to write to '{path}': {e}"));
|
.unwrap_or_else(|error| panic!("Failed to write to '{path}': {error}"));
|
||||||
eprintln!("Wrote {entry_count} settings to {path}");
|
eprintln!("Wrote {entry_count} settings to {path}");
|
||||||
} else {
|
} else {
|
||||||
println!("{output}");
|
println!("{output}");
|
||||||
|
|||||||
+42
-18
@@ -187,6 +187,13 @@ fn fuzzy_match(target: &str, query: &str) -> bool {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn completion_documentation(item: &CompletionItem) -> Option<String> {
|
||||||
|
match item.documentation.as_ref()? {
|
||||||
|
lsp_types::Documentation::String(documentation) => Some(documentation.clone()),
|
||||||
|
lsp_types::Documentation::MarkupContent(documentation) => Some(documentation.value.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl LocalCodeEditorView {
|
impl LocalCodeEditorView {
|
||||||
pub(super) fn is_completion_enabled() -> bool {
|
pub(super) fn is_completion_enabled() -> bool {
|
||||||
FeatureFlag::LspCompletion.is_enabled()
|
FeatureFlag::LspCompletion.is_enabled()
|
||||||
@@ -254,12 +261,13 @@ impl LocalCodeEditorView {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(trigger) = trigger {
|
if let Some(trigger) = trigger {
|
||||||
self.request_completion(cursor_offset, trigger, ctx);
|
self.request_completion(cursor_offset, cursor_offset, trigger, ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn request_completion(
|
pub(super) fn request_completion(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
request_offset: CharOffset,
|
||||||
trigger_offset: CharOffset,
|
trigger_offset: CharOffset,
|
||||||
trigger: CompletionTrigger,
|
trigger: CompletionTrigger,
|
||||||
ctx: &mut ViewContext<Self>,
|
ctx: &mut ViewContext<Self>,
|
||||||
@@ -279,7 +287,7 @@ impl LocalCodeEditorView {
|
|||||||
let lsp_position = self
|
let lsp_position = self
|
||||||
.editor()
|
.editor()
|
||||||
.as_ref(ctx)
|
.as_ref(ctx)
|
||||||
.offset_to_lsp_position(trigger_offset, ctx);
|
.offset_to_lsp_position(request_offset, ctx);
|
||||||
|
|
||||||
let future =
|
let future =
|
||||||
match lsp_server
|
match lsp_server
|
||||||
@@ -318,7 +326,7 @@ impl LocalCodeEditorView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let word_start = self.find_word_start(offset, ctx);
|
let word_start = self.find_word_start(offset, ctx);
|
||||||
self.request_completion(word_start, CompletionTrigger::Invoked, ctx);
|
self.request_completion(offset, word_start, CompletionTrigger::Invoked, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the start of the current identifier word by walking backwards from `offset`.
|
/// Find the start of the current identifier word by walking backwards from `offset`.
|
||||||
@@ -440,7 +448,7 @@ impl LocalCodeEditorView {
|
|||||||
|
|
||||||
/// Resolve documentation for the currently selected completion item.
|
/// Resolve documentation for the currently selected completion item.
|
||||||
pub(super) fn resolve_selected_completion_docs(&mut self, ctx: &mut ViewContext<Self>) {
|
pub(super) fn resolve_selected_completion_docs(&mut self, ctx: &mut ViewContext<Self>) {
|
||||||
let raw_item = match &self.completion_state {
|
let (item_index, raw_item) = match &self.completion_state {
|
||||||
CompletionState::Showing {
|
CompletionState::Showing {
|
||||||
items,
|
items,
|
||||||
filtered_indices,
|
filtered_indices,
|
||||||
@@ -458,14 +466,22 @@ impl LocalCodeEditorView {
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
items[item_idx].raw_item.clone()
|
(item_idx, items[item_idx].raw_item.clone())
|
||||||
}
|
}
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(documentation) = completion_documentation(&raw_item) {
|
||||||
|
self.set_resolved_completion_docs(item_index, documentation, ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let Some(lsp_server) = &self.lsp_server else {
|
let Some(lsp_server) = &self.lsp_server else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
if !lsp_server.as_ref(ctx).supports_completion_resolve() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let future = match lsp_server.as_ref(ctx).completion_resolve(raw_item) {
|
let future = match lsp_server.as_ref(ctx).completion_resolve(raw_item) {
|
||||||
Ok(future) => future,
|
Ok(future) => future,
|
||||||
@@ -473,8 +489,8 @@ impl LocalCodeEditorView {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let abort_handle = ctx
|
let abort_handle = ctx
|
||||||
.spawn(future, |me, result, ctx| {
|
.spawn(future, move |me, result, ctx| {
|
||||||
me.handle_completion_resolve_response(result, ctx);
|
me.handle_completion_resolve_response(item_index, result, ctx);
|
||||||
})
|
})
|
||||||
.abort_handle();
|
.abort_handle();
|
||||||
|
|
||||||
@@ -492,6 +508,7 @@ impl LocalCodeEditorView {
|
|||||||
|
|
||||||
fn handle_completion_resolve_response(
|
fn handle_completion_resolve_response(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
item_index: usize,
|
||||||
result: anyhow::Result<CompletionItem>,
|
result: anyhow::Result<CompletionItem>,
|
||||||
ctx: &mut ViewContext<Self>,
|
ctx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
@@ -500,22 +517,29 @@ impl LocalCodeEditorView {
|
|||||||
Err(_) => return,
|
Err(_) => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
let doc_string = match resolved_item.documentation {
|
let Some(documentation) = completion_documentation(&resolved_item) else {
|
||||||
Some(lsp_types::Documentation::String(s)) => s,
|
return;
|
||||||
Some(lsp_types::Documentation::MarkupContent(m)) => m.value,
|
|
||||||
None => return,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if doc_string.trim().is_empty() {
|
self.set_resolved_completion_docs(item_index, documentation, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_resolved_completion_docs(
|
||||||
|
&mut self,
|
||||||
|
item_index: usize,
|
||||||
|
documentation: String,
|
||||||
|
ctx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
if documentation.trim().is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let formatted = match markdown_parser::parse_markdown(&doc_string) {
|
let formatted = match markdown_parser::parse_markdown(&documentation) {
|
||||||
Ok(text) => text,
|
Ok(text) => text,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
use markdown_parser::{FormattedTextFragment, FormattedTextLine};
|
use markdown_parser::{FormattedTextFragment, FormattedTextLine};
|
||||||
FormattedText::new([FormattedTextLine::Line(vec![
|
FormattedText::new([FormattedTextLine::Line(vec![
|
||||||
FormattedTextFragment::plain_text(doc_string),
|
FormattedTextFragment::plain_text(documentation),
|
||||||
])])
|
])])
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -529,9 +553,9 @@ impl LocalCodeEditorView {
|
|||||||
} = &mut self.completion_state
|
} = &mut self.completion_state
|
||||||
{
|
{
|
||||||
*resolve_abort_handle = None;
|
*resolve_abort_handle = None;
|
||||||
if let Some(&item_idx) = filtered_indices.get(*selected_index) {
|
if filtered_indices.get(*selected_index) == Some(&item_index) {
|
||||||
*resolved_docs = Some(ResolvedDocumentation {
|
*resolved_docs = Some(ResolvedDocumentation {
|
||||||
item_index: item_idx,
|
item_index,
|
||||||
text: formatted,
|
text: formatted,
|
||||||
scroll_state: ClippedScrollStateHandle::default(),
|
scroll_state: ClippedScrollStateHandle::default(),
|
||||||
});
|
});
|
||||||
@@ -552,7 +576,7 @@ impl LocalCodeEditorView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Manually trigger completion (Ctrl+Alt+Space).
|
/// Manually trigger completion (Ctrl+Space in the code editor).
|
||||||
pub(super) fn trigger_completion_manually(&mut self, ctx: &mut ViewContext<Self>) {
|
pub(super) fn trigger_completion_manually(&mut self, ctx: &mut ViewContext<Self>) {
|
||||||
if !Self::is_completion_enabled() {
|
if !Self::is_completion_enabled() {
|
||||||
return;
|
return;
|
||||||
@@ -562,7 +586,7 @@ impl LocalCodeEditorView {
|
|||||||
}
|
}
|
||||||
let cursor_offset = self.editor().as_ref(ctx).cursor_head_offset(ctx);
|
let cursor_offset = self.editor().as_ref(ctx).cursor_head_offset(ctx);
|
||||||
let word_start = self.find_word_start(cursor_offset, ctx);
|
let word_start = self.find_word_start(cursor_offset, ctx);
|
||||||
self.request_completion(word_start, CompletionTrigger::Invoked, ctx);
|
self.request_completion(cursor_offset, word_start, CompletionTrigger::Invoked, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn render_completion_menu(&self, app: &AppContext) -> Option<Box<dyn Element>> {
|
pub(super) fn render_completion_menu(&self, app: &AppContext) -> Option<Box<dyn Element>> {
|
||||||
|
|||||||
@@ -158,6 +158,8 @@ pub enum CodeEditorEvent {
|
|||||||
CompletionNavigateDown,
|
CompletionNavigateDown,
|
||||||
/// Emitted when Tab/Enter is pressed and completion_intercept_keys is active.
|
/// Emitted when Tab/Enter is pressed and completion_intercept_keys is active.
|
||||||
CompletionConfirm,
|
CompletionConfirm,
|
||||||
|
/// Emitted when the manual completion keybinding is pressed.
|
||||||
|
CompletionTrigger,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Store all states related to displaying the editor content.
|
/// Store all states related to displaying the editor content.
|
||||||
|
|||||||
@@ -78,6 +78,11 @@ pub fn init(app: &mut AppContext) {
|
|||||||
CodeEditorViewAction::VimShiftEnter,
|
CodeEditorViewAction::VimShiftEnter,
|
||||||
text_entry.clone() & id!("Vim"),
|
text_entry.clone() & id!("Vim"),
|
||||||
),
|
),
|
||||||
|
FixedBinding::new(
|
||||||
|
"ctrl-space",
|
||||||
|
CodeEditorViewAction::TriggerCompletion,
|
||||||
|
editable_state.clone(),
|
||||||
|
),
|
||||||
FixedBinding::new(
|
FixedBinding::new(
|
||||||
"backspace",
|
"backspace",
|
||||||
CodeEditorViewAction::Backspace,
|
CodeEditorViewAction::Backspace,
|
||||||
@@ -711,6 +716,7 @@ pub enum CodeEditorViewAction {
|
|||||||
ShiftTab,
|
ShiftTab,
|
||||||
ShowFindBar,
|
ShowFindBar,
|
||||||
ShowGoToLine,
|
ShowGoToLine,
|
||||||
|
TriggerCompletion,
|
||||||
Escape,
|
Escape,
|
||||||
VimEnter,
|
VimEnter,
|
||||||
VimTab,
|
VimTab,
|
||||||
@@ -795,6 +801,7 @@ impl CodeEditorViewAction {
|
|||||||
| Self::Copy
|
| Self::Copy
|
||||||
| Self::ShowFindBar
|
| Self::ShowFindBar
|
||||||
| Self::ShowGoToLine
|
| Self::ShowGoToLine
|
||||||
|
| Self::TriggerCompletion
|
||||||
| Self::Escape
|
| Self::Escape
|
||||||
| Self::HiddenSectionExpansion { .. }
|
| Self::HiddenSectionExpansion { .. }
|
||||||
| Self::AddDiffHunkContext { .. }
|
| Self::AddDiffHunkContext { .. }
|
||||||
@@ -1064,6 +1071,7 @@ impl TypedActionView for CodeEditorView {
|
|||||||
|
|
||||||
ShowFindBar => self.show_find_bar(ctx),
|
ShowFindBar => self.show_find_bar(ctx),
|
||||||
ShowGoToLine => self.show_goto_line(ctx),
|
ShowGoToLine => self.show_goto_line(ctx),
|
||||||
|
TriggerCompletion => ctx.emit(CodeEditorEvent::CompletionTrigger),
|
||||||
Escape => self.escape(ctx),
|
Escape => self.escape(ctx),
|
||||||
HiddenSectionExpansion {
|
HiddenSectionExpansion {
|
||||||
line_range,
|
line_range,
|
||||||
|
|||||||
@@ -2965,7 +2965,7 @@ impl View for FileTreeView {
|
|||||||
if let CodingPanelEnablementState::RemoteSession { has_remote_server } = self.enablement
|
if let CodingPanelEnablementState::RemoteSession { has_remote_server } = self.enablement
|
||||||
{
|
{
|
||||||
// When the session has a remote server connection (Auto SSH
|
// When the session has a remote server connection (Auto SSH
|
||||||
// Warpification / mode 1), show a loading state — the server
|
// Wormholing / mode 1), show a loading state — the server
|
||||||
// may push repo metadata momentarily. For other SSH modes
|
// may push repo metadata momentarily. For other SSH modes
|
||||||
// (tmux, subshell) no data will arrive, so show the disabled
|
// (tmux, subshell) no data will arrive, so show the disabled
|
||||||
// error instead.
|
// error instead.
|
||||||
|
|||||||
@@ -1562,13 +1562,23 @@ impl GlobalBufferModel {
|
|||||||
.flatten();
|
.flatten();
|
||||||
|
|
||||||
// If we have a previous version that wasn't synced, we need to do a full sync.
|
// If we have a previous version that wasn't synced, we need to do a full sync.
|
||||||
let needs_full_sync = previous_version.is_some_and(|prev| {
|
let server_requires_full_sync = lsp_server.as_ref(ctx).requires_full_document_sync();
|
||||||
|
let needs_full_sync = server_requires_full_sync
|
||||||
|
|| previous_version.is_some_and(|prev| {
|
||||||
last_synced.is_none() || last_synced.is_some_and(|synced| synced < prev)
|
last_synced.is_none() || last_synced.is_some_and(|synced| synced < prev)
|
||||||
});
|
});
|
||||||
|
|
||||||
let deltas_len = deltas.len();
|
let deltas_len = deltas.len();
|
||||||
|
|
||||||
if needs_full_sync {
|
if server_requires_full_sync {
|
||||||
|
lsp_server.as_ref(ctx).log_to_server_log(
|
||||||
|
LspServerLogLevel::Debug,
|
||||||
|
format!(
|
||||||
|
"didChange -> server: REQUIRED full-sync file={} send_version={current_version} deltas={deltas_len}",
|
||||||
|
path.display()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if needs_full_sync {
|
||||||
lsp_server.as_ref(ctx).log_to_server_log(
|
lsp_server.as_ref(ctx).log_to_server_log(
|
||||||
LspServerLogLevel::Info,
|
LspServerLogLevel::Info,
|
||||||
format!(
|
format!(
|
||||||
|
|||||||
@@ -114,11 +114,6 @@ pub fn init(app: &mut AppContext) {
|
|||||||
LocalCodeEditorAction::StartRename,
|
LocalCodeEditorAction::StartRename,
|
||||||
id!("LocalCodeEditorView"),
|
id!("LocalCodeEditorView"),
|
||||||
),
|
),
|
||||||
FixedBinding::new(
|
|
||||||
"ctrl-alt-space",
|
|
||||||
LocalCodeEditorAction::TriggerCompletion,
|
|
||||||
id!("LocalCodeEditorView"),
|
|
||||||
),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,8 +211,6 @@ pub enum LocalCodeEditorAction {
|
|||||||
OpenCodeActions,
|
OpenCodeActions,
|
||||||
/// Start LSP rename at cursor (F2).
|
/// Start LSP rename at cursor (F2).
|
||||||
StartRename,
|
StartRename,
|
||||||
/// Manually trigger completion (Ctrl+Alt+Space).
|
|
||||||
TriggerCompletion,
|
|
||||||
/// Hover over a completion item by display index.
|
/// Hover over a completion item by display index.
|
||||||
CompletionHoverItem(usize),
|
CompletionHoverItem(usize),
|
||||||
/// Confirm completion via mouse click.
|
/// Confirm completion via mouse click.
|
||||||
@@ -510,6 +503,9 @@ impl LocalCodeEditorView {
|
|||||||
CodeEditorEvent::CompletionConfirm => {
|
CodeEditorEvent::CompletionConfirm => {
|
||||||
me.confirm_completion(ctx);
|
me.confirm_completion(ctx);
|
||||||
}
|
}
|
||||||
|
CodeEditorEvent::CompletionTrigger => {
|
||||||
|
me.trigger_completion_manually(ctx);
|
||||||
|
}
|
||||||
CodeEditorEvent::VimGotoDefinition
|
CodeEditorEvent::VimGotoDefinition
|
||||||
| CodeEditorEvent::VimFindReferences
|
| CodeEditorEvent::VimFindReferences
|
||||||
| CodeEditorEvent::VimShowHover => {
|
| CodeEditorEvent::VimShowHover => {
|
||||||
@@ -995,9 +991,12 @@ impl LocalCodeEditorView {
|
|||||||
// If the LSP is not registered, try to start it via PersistedWorkspace.
|
// If the LSP is not registered, try to start it via PersistedWorkspace.
|
||||||
#[cfg(feature = "local_fs")]
|
#[cfg(feature = "local_fs")]
|
||||||
{
|
{
|
||||||
use crate::ai::persisted_workspace::LspTask;
|
|
||||||
PersistedWorkspace::handle(ctx).update(ctx, |workspace, ctx| {
|
PersistedWorkspace::handle(ctx).update(ctx, |workspace, ctx| {
|
||||||
|
if path == crate::settings::user_preferences_toml_file_path() {
|
||||||
|
workspace.ensure_settings_toml_lsp(path, ctx);
|
||||||
|
} else {
|
||||||
workspace.execute_lsp_task(LspTask::Spawn { file_path: path }, ctx);
|
workspace.execute_lsp_task(LspTask::Spawn { file_path: path }, ctx);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -2487,9 +2486,6 @@ impl TypedActionView for LocalCodeEditorView {
|
|||||||
LocalCodeEditorAction::StartRename => {
|
LocalCodeEditorAction::StartRename => {
|
||||||
self.start_rename(ctx);
|
self.start_rename(ctx);
|
||||||
}
|
}
|
||||||
LocalCodeEditorAction::TriggerCompletion => {
|
|
||||||
self.trigger_completion_manually(ctx);
|
|
||||||
}
|
|
||||||
LocalCodeEditorAction::CompletionHoverItem(display_index) => {
|
LocalCodeEditorAction::CompletionHoverItem(display_index) => {
|
||||||
self.handle_completion_hover_item(*display_index, ctx);
|
self.handle_completion_hover_item(*display_index, ctx);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ pub(crate) enum CodingPanelEnablementState {
|
|||||||
/// The active session is on a remote host.
|
/// The active session is on a remote host.
|
||||||
///
|
///
|
||||||
/// `has_remote_server` is `true` when the session is registered with
|
/// `has_remote_server` is `true` when the session is registered with
|
||||||
/// `RemoteServerManager` (i.e. Auto SSH Warpification / mode 1). When
|
/// `RemoteServerManager` (i.e. Auto SSH Wormholing / mode 1). When
|
||||||
/// `true`, remote repo metadata may arrive and the file tree should show
|
/// `true`, remote repo metadata may arrive and the file tree should show
|
||||||
/// a loading state. When `false` (tmux or subshell SSH), no data will
|
/// a loading state. When `false` (tmux or subshell SSH), no data will
|
||||||
/// arrive and the file tree should show a disabled message.
|
/// arrive and the file tree should show a disabled message.
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ impl SessionContext {
|
|||||||
.filter_map(|res| res.and_then(EngineDirEntry::try_from).ok())
|
.filter_map(|res| res.and_then(EngineDirEntry::try_from).ok())
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
SessionType::WarpifiedRemote { .. } => {
|
SessionType::WormholedRemote { .. } => {
|
||||||
let env_vars = self
|
let env_vars = self
|
||||||
.session
|
.session
|
||||||
.path()
|
.path()
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ pub fn ssh_session(ctx: &GeneratorContext) -> Option<ChipValue> {
|
|||||||
if session.is_ssh_wrapper_session()
|
if session.is_ssh_wrapper_session()
|
||||||
|| matches!(
|
|| matches!(
|
||||||
session.session_type(),
|
session.session_type(),
|
||||||
crate::terminal::model::session::SessionType::WarpifiedRemote { .. }
|
crate::terminal::model::session::SessionType::WormholedRemote { .. }
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
let user = session.user();
|
let user = session.user();
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ fn test_remote_sessions() {
|
|||||||
let local_session = Session::test();
|
let local_session = Session::test();
|
||||||
let remote_session = Session::new(
|
let remote_session = Session::new(
|
||||||
SessionInfo::new_for_test()
|
SessionInfo::new_for_test()
|
||||||
.with_session_type(BootstrapSessionType::WarpifiedRemote)
|
.with_session_type(BootstrapSessionType::WormholedRemote)
|
||||||
.with_hostname("remote-host".to_string())
|
.with_hostname("remote-host".to_string())
|
||||||
.with_user("remote-user".to_string()),
|
.with_user("remote-user".to_string()),
|
||||||
Arc::new(TestCommandExecutor {}),
|
Arc::new(TestCommandExecutor {}),
|
||||||
|
|||||||
@@ -1638,10 +1638,10 @@ impl DisplayChip {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|ctx| match ctx.session.session_type() {
|
.map(|ctx| match ctx.session.session_type() {
|
||||||
SessionType::Local => true,
|
SessionType::Local => true,
|
||||||
SessionType::WarpifiedRemote { host_id: Some(_) } => {
|
SessionType::WormholedRemote { host_id: Some(_) } => {
|
||||||
FeatureFlag::RemoteCodeReview.is_enabled()
|
FeatureFlag::RemoteCodeReview.is_enabled()
|
||||||
}
|
}
|
||||||
SessionType::WarpifiedRemote { host_id: None } => false,
|
SessionType::WormholedRemote { host_id: None } => false,
|
||||||
})
|
})
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -465,8 +465,8 @@ fn enabled_features() -> HashSet<FeatureFlag> {
|
|||||||
FeatureFlag::CLIAgentRichInput,
|
FeatureFlag::CLIAgentRichInput,
|
||||||
#[cfg(feature = "transfer_control_tool")]
|
#[cfg(feature = "transfer_control_tool")]
|
||||||
FeatureFlag::TransferControlTool,
|
FeatureFlag::TransferControlTool,
|
||||||
#[cfg(feature = "warpify_footer")]
|
#[cfg(feature = "wormhole_footer")]
|
||||||
FeatureFlag::WarpifyFooter,
|
FeatureFlag::WormholeFooter,
|
||||||
#[cfg(feature = "solo_user_byok")]
|
#[cfg(feature = "solo_user_byok")]
|
||||||
FeatureFlag::SoloUserByok,
|
FeatureFlag::SoloUserByok,
|
||||||
#[cfg(feature = "billing_and_usage_page_v2")]
|
#[cfg(feature = "billing_and_usage_page_v2")]
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ pub fn enter_local_subshell_command(shell: &str) -> TestStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn assert_subshell_banner_is_showing() -> TestStep {
|
pub fn assert_subshell_banner_is_showing() -> TestStep {
|
||||||
TestStep::new("Assert the Warpify banner is visible")
|
TestStep::new("Assert the Wormhole banner is visible")
|
||||||
.add_assertion(move |app, window_id| {
|
.add_assertion(move |app, window_id| {
|
||||||
let terminal_view = single_terminal_view(app, window_id);
|
let terminal_view = single_terminal_view(app, window_id);
|
||||||
terminal_view.read(app, |view, _ctx| {
|
terminal_view.read(app, |view, _ctx| {
|
||||||
@@ -102,7 +102,7 @@ pub fn assert_subshell_banner_is_showing() -> TestStep {
|
|||||||
.block_list_mut()
|
.block_list_mut()
|
||||||
.active_block()
|
.active_block()
|
||||||
.block_banner(),
|
.block_banner(),
|
||||||
Some(WithinBlockBanner::WarpifyBanner(..))
|
Some(WithinBlockBanner::WormholeBanner(..))
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -132,10 +132,10 @@ pub fn assert_subshell_is_bootstrapped(tab_index: usize, pane_index: usize) -> T
|
|||||||
};
|
};
|
||||||
|
|
||||||
match rich_content_type {
|
match rich_content_type {
|
||||||
Some(RichContentType::WarpifySuccessBlock) => {}
|
Some(RichContentType::WormholeSuccessBlock) => {}
|
||||||
_ => {
|
_ => {
|
||||||
return AssertionOutcome::failure(
|
return AssertionOutcome::failure(
|
||||||
"Warpify success block wasn't added to the blocklist".to_owned(),
|
"Wormhole success block wasn't added to the blocklist".to_owned(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,7 +152,19 @@ impl RemoteTransport for SshTransport {
|
|||||||
fn check_binary(&self) -> Pin<Box<dyn Future<Output = Result<bool, Error>> + Send>> {
|
fn check_binary(&self) -> Pin<Box<dyn Future<Output = Result<bool, Error>> + Send>> {
|
||||||
let socket_path = self.socket_path.clone();
|
let socket_path = self.socket_path.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let cmd = remote_server::setup::binary_check_command();
|
let binary = remote_server::setup::remote_server_binary();
|
||||||
|
let expected_helper_version = if remote_server::setup::uses_static_linux_helper() {
|
||||||
|
let platform = detect_remote_platform(&socket_path).await?;
|
||||||
|
installation::local_helper_version(&platform).await
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let cmd = match expected_helper_version {
|
||||||
|
Some(version) => format!(
|
||||||
|
"{binary} --version >/dev/null && test \"$(cat {binary}.wormhole-version 2>/dev/null)\" = \"{version}\""
|
||||||
|
),
|
||||||
|
None => remote_server::setup::binary_check_command(),
|
||||||
|
};
|
||||||
log::info!("Running binary check: {cmd}");
|
log::info!("Running binary check: {cmd}");
|
||||||
let output = remote_server::ssh::run_ssh_command(
|
let output = remote_server::ssh::run_ssh_command(
|
||||||
&socket_path,
|
&socket_path,
|
||||||
@@ -161,16 +173,18 @@ impl RemoteTransport for SshTransport {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
// `<binary> --version` exits 0 when present, executable, and
|
// `<binary> --version` exits 0 when present, executable, and
|
||||||
// functional. Exit 127 means the binary was not found, and 126
|
// functional. Static helpers additionally compare their bundled
|
||||||
// means it exists but is not executable. Any other non-zero
|
// build marker, where exit 1 means the remote copy is stale.
|
||||||
// exit (e.g. SSH exit 255 for a dead connection, or signal
|
// Exit 127 means the binary was not found, and 126 means it exists
|
||||||
// termination) is treated as a transport-level failure.
|
// but is not executable. Any other non-zero exit (e.g. SSH exit
|
||||||
|
// 255 for a dead connection, or signal termination) is treated as
|
||||||
|
// a transport-level failure.
|
||||||
let code = output.status.code();
|
let code = output.status.code();
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
log::info!("Binary check result: exit={code:?} stdout={stdout}");
|
log::info!("Binary check result: exit={code:?} stdout={stdout}");
|
||||||
match code {
|
match code {
|
||||||
Some(0) => Ok(true),
|
Some(0) => Ok(true),
|
||||||
Some(126) | Some(127) => Ok(false),
|
Some(1) | Some(126) | Some(127) => Ok(false),
|
||||||
Some(code) => {
|
Some(code) => {
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
Err(Error::Other(anyhow::anyhow!(
|
Err(Error::Other(anyhow::anyhow!(
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ mod scp_fallback;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use galaxy_core::channel::{Channel, ChannelState};
|
||||||
|
use remote_server::setup::RemotePlatform;
|
||||||
use remote_server::ssh::SshCommandError;
|
use remote_server::ssh::SshCommandError;
|
||||||
use remote_server::transport::{Error, InstallOutcome, InstallSource};
|
use remote_server::transport::{Error, InstallOutcome, InstallSource};
|
||||||
|
|
||||||
@@ -13,7 +15,16 @@ use remote_server::transport::{Error, InstallOutcome, InstallSource};
|
|||||||
pub(super) async fn install_binary(socket_path: &Path) -> InstallOutcome {
|
pub(super) async fn install_binary(socket_path: &Path) -> InstallOutcome {
|
||||||
let binary_path = remote_server::setup::remote_server_binary();
|
let binary_path = remote_server::setup::remote_server_binary();
|
||||||
log::info!("Installing remote server binary to {binary_path}");
|
log::info!("Installing remote server binary to {binary_path}");
|
||||||
let mut outcome = match install_on_server(socket_path).await {
|
let mut outcome = if matches!(ChannelState::channel(), Channel::Local | Channel::Oss) {
|
||||||
|
// Local-first builds never contact Warp's release service. Their
|
||||||
|
// statically linked Linux helpers are bundled with Galaxy and copied
|
||||||
|
// through the SSH connection instead.
|
||||||
|
InstallOutcome {
|
||||||
|
source: Some(InstallSource::Client),
|
||||||
|
result: scp_fallback::install_local_helper(socket_path).await,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match install_on_server(socket_path).await {
|
||||||
Ok(()) => InstallOutcome {
|
Ok(()) => InstallOutcome {
|
||||||
source: Some(InstallSource::Server),
|
source: Some(InstallSource::Server),
|
||||||
result: Ok(()),
|
result: Ok(()),
|
||||||
@@ -38,6 +49,7 @@ pub(super) async fn install_binary(socket_path: &Path) -> InstallOutcome {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Post-install verification: confirm the binary actually landed at the
|
// Post-install verification: confirm the binary actually landed at the
|
||||||
@@ -73,6 +85,13 @@ pub(super) async fn install_binary(socket_path: &Path) -> InstallOutcome {
|
|||||||
outcome
|
outcome
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the build marker for the static helper bundled for `platform`, if
|
||||||
|
/// this client has one. The SSH transport uses this to avoid copying the helper
|
||||||
|
/// when the matching build is already installed remotely.
|
||||||
|
pub(super) async fn local_helper_version(platform: &RemotePlatform) -> Option<String> {
|
||||||
|
scp_fallback::local_helper_version(platform).await
|
||||||
|
}
|
||||||
|
|
||||||
/// Runs the install script on the remote host to download and install the
|
/// Runs the install script on the remote host to download and install the
|
||||||
/// binary directly from the CDN.
|
/// binary directly from the CDN.
|
||||||
async fn install_on_server(socket_path: &Path) -> Result<(), Error> {
|
async fn install_on_server(socket_path: &Path) -> Result<(), Error> {
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ use remote_server::setup::RemotePlatform;
|
|||||||
use remote_server::transport::Error;
|
use remote_server::transport::Error;
|
||||||
|
|
||||||
const REMOTE_SERVER_TARBALL_CACHE_FILE_NAME: &str = "oz.tar.gz";
|
const REMOTE_SERVER_TARBALL_CACHE_FILE_NAME: &str = "oz.tar.gz";
|
||||||
|
const WORMHOLE_HELPER_TARBALL_FILE_NAME: &str = "galaxy-wormhole.tar.gz";
|
||||||
|
const WORMHOLE_HELPER_VERSION_FILE_NAME: &str = "galaxy-wormhole.version";
|
||||||
|
const WORMHOLE_HELPERS_DIR_ENV: &str = "GALAXY_WORMHOLE_HELPERS_DIR";
|
||||||
|
|
||||||
const REMOTE_SERVER_TARBALL_DOWNLOAD_ATTEMPTS: usize = 3;
|
const REMOTE_SERVER_TARBALL_DOWNLOAD_ATTEMPTS: usize = 3;
|
||||||
// The local SCP fallback download can run over slow or captive networks. Match
|
// The local SCP fallback download can run over slow or captive networks. Match
|
||||||
@@ -25,6 +28,29 @@ pub(super) fn should_try_install(error: &Error) -> bool {
|
|||||||
!matches!(error, Error::ScriptFailed { exit_code, .. } if *exit_code == 2)
|
!matches!(error, Error::ScriptFailed { exit_code, .. } if *exit_code == 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Installs the static Linux helper shipped with local-first Galaxy builds.
|
||||||
|
/// No network download is attempted: the selected artifact is copied directly
|
||||||
|
/// through the existing SSH control connection.
|
||||||
|
pub(super) async fn install_local_helper(socket_path: &Path) -> Result<(), Error> {
|
||||||
|
let platform = super::super::detect_remote_platform(socket_path).await?;
|
||||||
|
let client_tarball_path = local_helper_tarball(&platform).ok_or_else(|| {
|
||||||
|
Error::Other(anyhow::anyhow!(
|
||||||
|
"Galaxy does not contain a Wormhole helper for Linux {}. Expected {} under the bundled resources or {}.",
|
||||||
|
platform.arch.as_str(),
|
||||||
|
helper_relative_path(&platform)
|
||||||
|
.map(|path| path.display().to_string())
|
||||||
|
.unwrap_or_else(|| "a supported Linux platform directory".to_string()),
|
||||||
|
WORMHOLE_HELPERS_DIR_ENV,
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"Using bundled Wormhole helper at {}",
|
||||||
|
client_tarball_path.display()
|
||||||
|
);
|
||||||
|
install_tarball(socket_path, &client_tarball_path).await
|
||||||
|
}
|
||||||
|
|
||||||
/// Installs the remote server via SCP fallback.
|
/// Installs the remote server via SCP fallback.
|
||||||
///
|
///
|
||||||
/// The tarball is downloaded or reused from the local cache first, then uploaded
|
/// The tarball is downloaded or reused from the local cache first, then uploaded
|
||||||
@@ -36,6 +62,10 @@ pub(super) async fn install(socket_path: &Path) -> Result<(), Error> {
|
|||||||
let client_tarball_path = cached_remote_server_tarball(&platform)
|
let client_tarball_path = cached_remote_server_tarball(&platform)
|
||||||
.await
|
.await
|
||||||
.map_err(Error::Other)?;
|
.map_err(Error::Other)?;
|
||||||
|
install_tarball(socket_path, &client_tarball_path).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn install_tarball(socket_path: &Path, client_tarball_path: &Path) -> Result<(), Error> {
|
||||||
let timeout = remote_server::setup::SCP_INSTALL_TIMEOUT;
|
let timeout = remote_server::setup::SCP_INSTALL_TIMEOUT;
|
||||||
let install_dir = remote_server::setup::remote_server_dir();
|
let install_dir = remote_server::setup::remote_server_dir();
|
||||||
let remote_tarball_name = format!("oz-upload-{}.tar.gz", uuid::Uuid::new_v4());
|
let remote_tarball_name = format!("oz-upload-{}.tar.gz", uuid::Uuid::new_v4());
|
||||||
@@ -63,7 +93,7 @@ pub(super) async fn install(socket_path: &Path) -> Result<(), Error> {
|
|||||||
log::info!("Uploading tarball to remote at {remote_tarball_path}");
|
log::info!("Uploading tarball to remote at {remote_tarball_path}");
|
||||||
remote_server::ssh::scp_upload(
|
remote_server::ssh::scp_upload(
|
||||||
socket_path,
|
socket_path,
|
||||||
&client_tarball_path,
|
client_tarball_path,
|
||||||
&remote_tarball_path,
|
&remote_tarball_path,
|
||||||
timeout,
|
timeout,
|
||||||
)
|
)
|
||||||
@@ -88,6 +118,58 @@ pub(super) async fn install(socket_path: &Path) -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn helper_relative_path(platform: &RemotePlatform) -> Option<PathBuf> {
|
||||||
|
if !matches!(&platform.os, remote_server::setup::RemoteOs::Linux) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(
|
||||||
|
PathBuf::from(format!("linux-{}", platform.arch.as_str()))
|
||||||
|
.join(WORMHOLE_HELPER_TARBALL_FILE_NAME),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn helper_roots() -> Vec<PathBuf> {
|
||||||
|
let mut roots = Vec::new();
|
||||||
|
if let Some(path) = std::env::var_os(WORMHOLE_HELPERS_DIR_ENV) {
|
||||||
|
roots.push(path.into());
|
||||||
|
}
|
||||||
|
if let Some(resources_dir) = galaxy_core::paths::bundled_resources_dir() {
|
||||||
|
roots.push(resources_dir.join("wormhole-helpers"));
|
||||||
|
}
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
roots.push(
|
||||||
|
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.parent()
|
||||||
|
.expect("app manifest directory should have a workspace parent")
|
||||||
|
.join("resources")
|
||||||
|
.join("wormhole-helpers"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
roots
|
||||||
|
}
|
||||||
|
|
||||||
|
fn local_helper_tarball(platform: &RemotePlatform) -> Option<PathBuf> {
|
||||||
|
let relative_path = helper_relative_path(platform)?;
|
||||||
|
helper_roots()
|
||||||
|
.into_iter()
|
||||||
|
.map(|root| root.join(&relative_path))
|
||||||
|
.find(|path| {
|
||||||
|
std::fs::metadata(path).is_ok_and(|metadata| metadata.is_file() && metadata.len() > 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn local_helper_version(platform: &RemotePlatform) -> Option<String> {
|
||||||
|
let tarball = local_helper_tarball(platform)?;
|
||||||
|
let version_path = tarball.parent()?.join(WORMHOLE_HELPER_VERSION_FILE_NAME);
|
||||||
|
let version = async_fs::read_to_string(version_path).await.ok()?;
|
||||||
|
let version = version.trim();
|
||||||
|
if version.is_empty() || !version.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(version.to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
fn remote_server_tarball_cache_root() -> PathBuf {
|
fn remote_server_tarball_cache_root() -> PathBuf {
|
||||||
galaxy_core::paths::cache_dir()
|
galaxy_core::paths::cache_dir()
|
||||||
.join("remote-server")
|
.join("remote-server")
|
||||||
@@ -124,6 +206,11 @@ async fn is_valid_cached_tarball(path: &Path) -> bool {
|
|||||||
/// Reuses an existing cached tarball when available; otherwise downloads the
|
/// Reuses an existing cached tarball when available; otherwise downloads the
|
||||||
/// tarball into the cache and returns the newly cached path.
|
/// tarball into the cache and returns the newly cached path.
|
||||||
async fn cached_remote_server_tarball(platform: &RemotePlatform) -> anyhow::Result<PathBuf> {
|
async fn cached_remote_server_tarball(platform: &RemotePlatform) -> anyhow::Result<PathBuf> {
|
||||||
|
if let Some(path) = local_helper_tarball(platform) {
|
||||||
|
log::info!("Using bundled Wormhole helper at {}", path.display());
|
||||||
|
return Ok(path);
|
||||||
|
}
|
||||||
|
|
||||||
let cache_path = remote_server_tarball_cache_path(platform);
|
let cache_path = remote_server_tarball_cache_path(platform);
|
||||||
if is_valid_cached_tarball(&cache_path).await {
|
if is_valid_cached_tarball(&cache_path).await {
|
||||||
log::info!(
|
log::info!(
|
||||||
|
|||||||
@@ -2749,7 +2749,7 @@ impl RootView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a command that should create a subshell. If we support bootstrapping AKA
|
/// Insert a command that should create a subshell. If we support bootstrapping AKA
|
||||||
/// "warpifying" its [`ShellType`], set a flag to automatically bootstrap it when the command's
|
/// "wormholing" its [`ShellType`], set a flag to automatically bootstrap it when the command's
|
||||||
/// block receives the [`AfterBlockStarted`] event.
|
/// block receives the [`AfterBlockStarted`] event.
|
||||||
pub fn insert_subshell_command_and_bootstrap_if_supported(
|
pub fn insert_subshell_command_and_bootstrap_if_supported(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|||||||
@@ -1739,7 +1739,7 @@ pub enum TelemetryEvent {
|
|||||||
AddAddedSubshellCommand,
|
AddAddedSubshellCommand,
|
||||||
RemoveAddedSubshellCommand,
|
RemoveAddedSubshellCommand,
|
||||||
ReceivedSubshellRcFileDcs,
|
ReceivedSubshellRcFileDcs,
|
||||||
ToggleSshWarpification {
|
ToggleSshWormholing {
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
},
|
},
|
||||||
/// User changed the SSH extension install mode.
|
/// User changed the SSH extension install mode.
|
||||||
@@ -1751,11 +1751,11 @@ pub enum TelemetryEvent {
|
|||||||
SshRemoteServerChoiceDoNotAskAgainToggled {
|
SshRemoteServerChoiceDoNotAskAgainToggled {
|
||||||
checked: bool,
|
checked: bool,
|
||||||
},
|
},
|
||||||
WarpifyFooterShown {
|
WormholeFooterShown {
|
||||||
is_ssh: bool,
|
is_ssh: bool,
|
||||||
},
|
},
|
||||||
AgentToolbarDismissed,
|
AgentToolbarDismissed,
|
||||||
WarpifyFooterAcceptedWarpify {
|
WormholeFooterAcceptedWormhole {
|
||||||
is_ssh: bool,
|
is_ssh: bool,
|
||||||
},
|
},
|
||||||
ShowAliasExpansionBanner,
|
ShowAliasExpansionBanner,
|
||||||
@@ -1861,7 +1861,7 @@ pub enum TelemetryEvent {
|
|||||||
team_uid: ServerId,
|
team_uid: ServerId,
|
||||||
},
|
},
|
||||||
CopyObjectToClipboard(TelemetryCloudObjectType),
|
CopyObjectToClipboard(TelemetryCloudObjectType),
|
||||||
OpenAndWarpifyDockerSubshell {
|
OpenAndWormholeDockerSubshell {
|
||||||
/// Some variant if we support this shell type, and None otherwise.
|
/// Some variant if we support this shell type, and None otherwise.
|
||||||
shell_type: Option<ShellType>,
|
shell_type: Option<ShellType>,
|
||||||
},
|
},
|
||||||
@@ -3438,8 +3438,8 @@ impl TelemetryEvent {
|
|||||||
Some(json!({ "remember": remember }))
|
Some(json!({ "remember": remember }))
|
||||||
}
|
}
|
||||||
TelemetryEvent::AgentToolbarDismissed => None,
|
TelemetryEvent::AgentToolbarDismissed => None,
|
||||||
TelemetryEvent::WarpifyFooterShown { is_ssh }
|
TelemetryEvent::WormholeFooterShown { is_ssh }
|
||||||
| TelemetryEvent::WarpifyFooterAcceptedWarpify { is_ssh } => {
|
| TelemetryEvent::WormholeFooterAcceptedWormhole { is_ssh } => {
|
||||||
Some(json!({ "is_ssh": is_ssh }))
|
Some(json!({ "is_ssh": is_ssh }))
|
||||||
}
|
}
|
||||||
TelemetryEvent::ToggleSameLinePrompt { enabled } => Some(json!({ "enabled": enabled })),
|
TelemetryEvent::ToggleSameLinePrompt { enabled } => Some(json!({ "enabled": enabled })),
|
||||||
@@ -3512,7 +3512,7 @@ impl TelemetryEvent {
|
|||||||
TelemetryEvent::CopyObjectToClipboard(object_type) => {
|
TelemetryEvent::CopyObjectToClipboard(object_type) => {
|
||||||
Some(json!({ "object_type": object_type }))
|
Some(json!({ "object_type": object_type }))
|
||||||
}
|
}
|
||||||
TelemetryEvent::OpenAndWarpifyDockerSubshell { shell_type } => {
|
TelemetryEvent::OpenAndWormholeDockerSubshell { shell_type } => {
|
||||||
Some(json!({ "shell_type": shell_type }))
|
Some(json!({ "shell_type": shell_type }))
|
||||||
}
|
}
|
||||||
TelemetryEvent::ToggleBlockFilterQuery { enabled, source } => {
|
TelemetryEvent::ToggleBlockFilterQuery { enabled, source } => {
|
||||||
@@ -3536,7 +3536,7 @@ impl TelemetryEvent {
|
|||||||
TelemetryEvent::ToggleNewWindowsAtCustomSize { enabled } => {
|
TelemetryEvent::ToggleNewWindowsAtCustomSize { enabled } => {
|
||||||
Some(json!({"enabled": enabled}))
|
Some(json!({"enabled": enabled}))
|
||||||
}
|
}
|
||||||
TelemetryEvent::ToggleSshWarpification { enabled } => Some(json!({"enabled": enabled})),
|
TelemetryEvent::ToggleSshWormholing { enabled } => Some(json!({"enabled": enabled})),
|
||||||
TelemetryEvent::SetSshExtensionInstallMode { mode } => Some(json!({"mode": mode})),
|
TelemetryEvent::SetSshExtensionInstallMode { mode } => Some(json!({"mode": mode})),
|
||||||
TelemetryEvent::SshRemoteServerChoiceDoNotAskAgainToggled { checked } => {
|
TelemetryEvent::SshRemoteServerChoiceDoNotAskAgainToggled { checked } => {
|
||||||
Some(json!({"checked": checked}))
|
Some(json!({"checked": checked}))
|
||||||
@@ -5053,9 +5053,9 @@ impl TelemetryEvent {
|
|||||||
| TelemetryEvent::AddAddedSubshellCommand
|
| TelemetryEvent::AddAddedSubshellCommand
|
||||||
| TelemetryEvent::RemoveAddedSubshellCommand
|
| TelemetryEvent::RemoveAddedSubshellCommand
|
||||||
| TelemetryEvent::ReceivedSubshellRcFileDcs
|
| TelemetryEvent::ReceivedSubshellRcFileDcs
|
||||||
| TelemetryEvent::WarpifyFooterShown { .. }
|
| TelemetryEvent::WormholeFooterShown { .. }
|
||||||
| TelemetryEvent::AgentToolbarDismissed
|
| TelemetryEvent::AgentToolbarDismissed
|
||||||
| TelemetryEvent::WarpifyFooterAcceptedWarpify { .. }
|
| TelemetryEvent::WormholeFooterAcceptedWormhole { .. }
|
||||||
| TelemetryEvent::ShowAliasExpansionBanner
|
| TelemetryEvent::ShowAliasExpansionBanner
|
||||||
| TelemetryEvent::EnableAliasExpansionFromBanner
|
| TelemetryEvent::EnableAliasExpansionFromBanner
|
||||||
| TelemetryEvent::DismissAliasExpansionBanner
|
| TelemetryEvent::DismissAliasExpansionBanner
|
||||||
@@ -5099,7 +5099,7 @@ impl TelemetryEvent {
|
|||||||
| TelemetryEvent::LogOut
|
| TelemetryEvent::LogOut
|
||||||
| TelemetryEvent::InviteTeammates { .. }
|
| TelemetryEvent::InviteTeammates { .. }
|
||||||
| TelemetryEvent::CopyObjectToClipboard(_)
|
| TelemetryEvent::CopyObjectToClipboard(_)
|
||||||
| TelemetryEvent::OpenAndWarpifyDockerSubshell { .. }
|
| TelemetryEvent::OpenAndWormholeDockerSubshell { .. }
|
||||||
| TelemetryEvent::UpdateBlockFilterQuery
|
| TelemetryEvent::UpdateBlockFilterQuery
|
||||||
| TelemetryEvent::UpdateBlockFilterQueryContextLines { .. }
|
| TelemetryEvent::UpdateBlockFilterQueryContextLines { .. }
|
||||||
| TelemetryEvent::ToggleBlockFilterQuery { .. }
|
| TelemetryEvent::ToggleBlockFilterQuery { .. }
|
||||||
@@ -5174,7 +5174,7 @@ impl TelemetryEvent {
|
|||||||
| TelemetryEvent::MCPServerSpawned { .. }
|
| TelemetryEvent::MCPServerSpawned { .. }
|
||||||
| TelemetryEvent::MCPToolCallAccepted { .. }
|
| TelemetryEvent::MCPToolCallAccepted { .. }
|
||||||
| TelemetryEvent::ExecutedWarpDrivePrompt { .. }
|
| TelemetryEvent::ExecutedWarpDrivePrompt { .. }
|
||||||
| TelemetryEvent::ToggleSshWarpification { .. }
|
| TelemetryEvent::ToggleSshWormholing { .. }
|
||||||
| TelemetryEvent::SetSshExtensionInstallMode { .. }
|
| TelemetryEvent::SetSshExtensionInstallMode { .. }
|
||||||
| TelemetryEvent::SshRemoteServerChoiceDoNotAskAgainToggled { .. }
|
| TelemetryEvent::SshRemoteServerChoiceDoNotAskAgainToggled { .. }
|
||||||
| TelemetryEvent::SettingsImportInitiated
|
| TelemetryEvent::SettingsImportInitiated
|
||||||
@@ -5604,12 +5604,12 @@ impl TelemetryEventDesc for TelemetryEventDiscriminants {
|
|||||||
Self::TriggerSubshellBootstrap => EnablementState::Always,
|
Self::TriggerSubshellBootstrap => EnablementState::Always,
|
||||||
Self::AddDenylistedSubshellCommand => EnablementState::Always,
|
Self::AddDenylistedSubshellCommand => EnablementState::Always,
|
||||||
Self::RemoveDenylistedSubshellCommand => EnablementState::Always,
|
Self::RemoveDenylistedSubshellCommand => EnablementState::Always,
|
||||||
Self::ToggleSshWarpification => EnablementState::Always,
|
Self::ToggleSshWormholing => EnablementState::Always,
|
||||||
Self::SetSshExtensionInstallMode => EnablementState::Always,
|
Self::SetSshExtensionInstallMode => EnablementState::Always,
|
||||||
Self::SshRemoteServerChoiceDoNotAskAgainToggled => EnablementState::Always,
|
Self::SshRemoteServerChoiceDoNotAskAgainToggled => EnablementState::Always,
|
||||||
Self::WarpifyFooterShown
|
Self::WormholeFooterShown
|
||||||
| Self::AgentToolbarDismissed
|
| Self::AgentToolbarDismissed
|
||||||
| Self::WarpifyFooterAcceptedWarpify => EnablementState::Always,
|
| Self::WormholeFooterAcceptedWormhole => EnablementState::Always,
|
||||||
Self::AddAddedSubshellCommand => EnablementState::Always,
|
Self::AddAddedSubshellCommand => EnablementState::Always,
|
||||||
Self::RemoveAddedSubshellCommand => EnablementState::Always,
|
Self::RemoveAddedSubshellCommand => EnablementState::Always,
|
||||||
Self::ReceivedSubshellRcFileDcs => EnablementState::Always,
|
Self::ReceivedSubshellRcFileDcs => EnablementState::Always,
|
||||||
@@ -5640,7 +5640,7 @@ impl TelemetryEventDesc for TelemetryEventDiscriminants {
|
|||||||
Self::SettingsImportInitiated => EnablementState::Always,
|
Self::SettingsImportInitiated => EnablementState::Always,
|
||||||
Self::InviteTeammates => EnablementState::Always,
|
Self::InviteTeammates => EnablementState::Always,
|
||||||
Self::CopyObjectToClipboard => EnablementState::Always,
|
Self::CopyObjectToClipboard => EnablementState::Always,
|
||||||
Self::OpenAndWarpifyDockerSubshell => EnablementState::Always,
|
Self::OpenAndWormholeDockerSubshell => EnablementState::Always,
|
||||||
Self::UpdateBlockFilterQuery => EnablementState::Always,
|
Self::UpdateBlockFilterQuery => EnablementState::Always,
|
||||||
Self::UpdateBlockFilterQueryContextLines => EnablementState::Always,
|
Self::UpdateBlockFilterQueryContextLines => EnablementState::Always,
|
||||||
Self::ToggleBlockFilterQuery => EnablementState::Always,
|
Self::ToggleBlockFilterQuery => EnablementState::Always,
|
||||||
@@ -6110,14 +6110,14 @@ impl TelemetryEventDesc for TelemetryEventDiscriminants {
|
|||||||
Self::AddAddedSubshellCommand => "Add Added Subshell Command",
|
Self::AddAddedSubshellCommand => "Add Added Subshell Command",
|
||||||
Self::RemoveAddedSubshellCommand => "Remove Added Subshell Command",
|
Self::RemoveAddedSubshellCommand => "Remove Added Subshell Command",
|
||||||
Self::ReceivedSubshellRcFileDcs => "Received Subshell RC File DCS",
|
Self::ReceivedSubshellRcFileDcs => "Received Subshell RC File DCS",
|
||||||
Self::ToggleSshWarpification => "Toggle SSH Warpification",
|
Self::ToggleSshWormholing => "Toggle SSH Wormholing",
|
||||||
Self::SetSshExtensionInstallMode => "Set SSH Extension Install Mode",
|
Self::SetSshExtensionInstallMode => "Set SSH Extension Install Mode",
|
||||||
Self::SshRemoteServerChoiceDoNotAskAgainToggled => {
|
Self::SshRemoteServerChoiceDoNotAskAgainToggled => {
|
||||||
"SSH Remote Server Choice Do Not Ask Again Toggled"
|
"SSH Remote Server Choice Do Not Ask Again Toggled"
|
||||||
}
|
}
|
||||||
Self::WarpifyFooterShown => "Warpify Footer Shown",
|
Self::WormholeFooterShown => "Wormhole Footer Shown",
|
||||||
Self::AgentToolbarDismissed => "Agent Toolbar Dismissed",
|
Self::AgentToolbarDismissed => "Agent Toolbar Dismissed",
|
||||||
Self::WarpifyFooterAcceptedWarpify => "Warpify Footer Accepted Warpify",
|
Self::WormholeFooterAcceptedWormhole => "Wormhole Footer Accepted Wormhole",
|
||||||
Self::ShowAliasExpansionBanner => "Show Alias Expansion Banner",
|
Self::ShowAliasExpansionBanner => "Show Alias Expansion Banner",
|
||||||
Self::DismissAliasExpansionBanner => "Dismiss Alias Expansion Banner",
|
Self::DismissAliasExpansionBanner => "Dismiss Alias Expansion Banner",
|
||||||
Self::EnableAliasExpansionFromBanner => "Enable Alias Expansion From Banner",
|
Self::EnableAliasExpansionFromBanner => "Enable Alias Expansion From Banner",
|
||||||
@@ -6155,7 +6155,7 @@ impl TelemetryEventDesc for TelemetryEventDiscriminants {
|
|||||||
Self::SettingsImportInitiated => "Settings Import Initiated",
|
Self::SettingsImportInitiated => "Settings Import Initiated",
|
||||||
Self::InviteTeammates => "Invited Teammates",
|
Self::InviteTeammates => "Invited Teammates",
|
||||||
Self::CopyObjectToClipboard => "Copy Object To Clipboard",
|
Self::CopyObjectToClipboard => "Copy Object To Clipboard",
|
||||||
Self::OpenAndWarpifyDockerSubshell => "OpenAndWarpifyDockerSubshell",
|
Self::OpenAndWormholeDockerSubshell => "OpenAndWormholeDockerSubshell",
|
||||||
Self::UpdateBlockFilterQuery => "Update Block Filter Query",
|
Self::UpdateBlockFilterQuery => "Update Block Filter Query",
|
||||||
Self::ToggleBlockFilterQuery => "Toggle Block Filter Query",
|
Self::ToggleBlockFilterQuery => "Toggle Block Filter Query",
|
||||||
Self::ToggleBlockFilterCaseSensitivity => "Toggle Block Filter Case Sensitivity",
|
Self::ToggleBlockFilterCaseSensitivity => "Toggle Block Filter Case Sensitivity",
|
||||||
@@ -6790,28 +6790,28 @@ impl TelemetryEventDesc for TelemetryEventDiscriminants {
|
|||||||
"Enabled or disabled preserving the active tab color"
|
"Enabled or disabled preserving the active tab color"
|
||||||
}
|
}
|
||||||
Self::ShowSubshellBanner => {
|
Self::ShowSubshellBanner => {
|
||||||
"Displayed the banner asking whether Warp should Warpify the current session via Warp's subshell wrapper"
|
"Displayed the banner asking whether Galaxy should Wormhole the current session via Galaxy's subshell wrapper"
|
||||||
}
|
}
|
||||||
Self::DeclineSubshellBootstrap => {
|
Self::DeclineSubshellBootstrap => {
|
||||||
"Developer declined the Warp banner to Warpify the current session"
|
"Developer declined the Galaxy banner to Wormhole the current session"
|
||||||
}
|
}
|
||||||
Self::TriggerSubshellBootstrap => {
|
Self::TriggerSubshellBootstrap => {
|
||||||
"Attempted to Warpify the current session via Warp's subshell wrapper"
|
"Attempted to Wormhole the current session via Galaxy's subshell wrapper"
|
||||||
}
|
}
|
||||||
Self::AddDenylistedSubshellCommand => {
|
Self::AddDenylistedSubshellCommand => {
|
||||||
"Explicitly prevent a command from being Warpified via Warp's subshell wrapper"
|
"Explicitly prevent a command from being Wormholed via Galaxy's subshell wrapper"
|
||||||
}
|
}
|
||||||
Self::RemoveDenylistedSubshellCommand => {
|
Self::RemoveDenylistedSubshellCommand => {
|
||||||
"Removed a command from the list of commands to IGNORE when trying to Warpify via Warp's subshell wrapper"
|
"Removed a command from the list of commands to IGNORE when trying to Wormhole via Galaxy's subshell wrapper"
|
||||||
}
|
}
|
||||||
Self::AddAddedSubshellCommand => {
|
Self::AddAddedSubshellCommand => {
|
||||||
"Added a command to be automatically Warpified via Warp's subshell wrapper"
|
"Added a command to be automatically Wormholed via Galaxy's subshell wrapper"
|
||||||
}
|
}
|
||||||
Self::RemoveAddedSubshellCommand => {
|
Self::RemoveAddedSubshellCommand => {
|
||||||
"Removed a command from the list of commands to automatically Warpify via Warp's subshell wrapper"
|
"Removed a command from the list of commands to automatically Wormhole via Galaxy's subshell wrapper"
|
||||||
}
|
}
|
||||||
Self::ReceivedSubshellRcFileDcs => "Spawned a subshell to be automatically Warpified",
|
Self::ReceivedSubshellRcFileDcs => "Spawned a subshell to be automatically Wormholed",
|
||||||
Self::ToggleSshWarpification => "Changed the setting for SSH sessions to be warified",
|
Self::ToggleSshWormholing => "Changed the setting for SSH sessions to be wormholed",
|
||||||
Self::SetSshExtensionInstallMode => {
|
Self::SetSshExtensionInstallMode => {
|
||||||
"Changed the SSH extension install mode (always ask / always allow / always skip)"
|
"Changed the SSH extension install mode (always ask / always allow / always skip)"
|
||||||
}
|
}
|
||||||
@@ -6819,11 +6819,11 @@ impl TelemetryEventDesc for TelemetryEventDiscriminants {
|
|||||||
"Toggled the 'Don't ask me this again' checkbox on the SSH remote-server choice block"
|
"Toggled the 'Don't ask me this again' checkbox on the SSH remote-server choice block"
|
||||||
}
|
}
|
||||||
Self::AgentModeRatedResponse => "User rated an Agent Mode response",
|
Self::AgentModeRatedResponse => "User rated an Agent Mode response",
|
||||||
Self::WarpifyFooterShown => {
|
Self::WormholeFooterShown => {
|
||||||
"Displayed the warpify footer for a detected subshell or SSH session"
|
"Displayed the wormhole footer for a detected subshell or SSH session"
|
||||||
}
|
}
|
||||||
Self::AgentToolbarDismissed => "User dismissed the use-agent toolbar",
|
Self::AgentToolbarDismissed => "User dismissed the use-agent toolbar",
|
||||||
Self::WarpifyFooterAcceptedWarpify => "User clicked Warpify in the warpify footer",
|
Self::WormholeFooterAcceptedWormhole => "User clicked Wormhole in the wormhole footer",
|
||||||
Self::ShowAliasExpansionBanner => {
|
Self::ShowAliasExpansionBanner => {
|
||||||
"Displayed the banner asking whether Warp should automatically expand aliases within the Input Editor"
|
"Displayed the banner asking whether Warp should automatically expand aliases within the Input Editor"
|
||||||
}
|
}
|
||||||
@@ -6903,8 +6903,8 @@ impl TelemetryEventDesc for TelemetryEventDiscriminants {
|
|||||||
Self::SettingsImportInitiated => "Started the import settings flow for new users",
|
Self::SettingsImportInitiated => "Started the import settings flow for new users",
|
||||||
Self::InviteTeammates => "Sent emails to invite teammates to join Warp Drive team",
|
Self::InviteTeammates => "Sent emails to invite teammates to join Warp Drive team",
|
||||||
Self::CopyObjectToClipboard => "Copied an object to the user's keyboard",
|
Self::CopyObjectToClipboard => "Copied an object to the user's keyboard",
|
||||||
Self::OpenAndWarpifyDockerSubshell => {
|
Self::OpenAndWormholeDockerSubshell => {
|
||||||
"Warpifying a docker subshell from using the docker extension"
|
"Wormholing a docker subshell from using the docker extension"
|
||||||
}
|
}
|
||||||
Self::UpdateBlockFilterQuery => "When a new filter is applied to a block",
|
Self::UpdateBlockFilterQuery => "When a new filter is applied to a block",
|
||||||
Self::UpdateBlockFilterQueryContextLines => {
|
Self::UpdateBlockFilterQueryContextLines => {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ use crate::terminal::safe_mode_settings::SafeModeSettings;
|
|||||||
use crate::terminal::session_settings::{SessionSettings, SessionSettingsChangedEvent};
|
use crate::terminal::session_settings::{SessionSettings, SessionSettingsChangedEvent};
|
||||||
use crate::terminal::settings::TerminalSettings;
|
use crate::terminal::settings::TerminalSettings;
|
||||||
use crate::terminal::shared_session::settings::SharedSessionSettings;
|
use crate::terminal::shared_session::settings::SharedSessionSettings;
|
||||||
use crate::terminal::warpify::settings::WarpifySettings;
|
use crate::terminal::wormhole::settings::WormholeSettings;
|
||||||
use crate::terminal::BlockListSettings;
|
use crate::terminal::BlockListSettings;
|
||||||
use crate::undo_close::UndoCloseSettings;
|
use crate::undo_close::UndoCloseSettings;
|
||||||
use crate::window_settings::WindowSettings;
|
use crate::window_settings::WindowSettings;
|
||||||
@@ -86,7 +86,7 @@ pub fn register_all_settings(ctx: &mut AppContext) {
|
|||||||
AppIconSettings::register(ctx);
|
AppIconSettings::register(ctx);
|
||||||
AppEditorSettings::register(ctx);
|
AppEditorSettings::register(ctx);
|
||||||
InputSettings::register(ctx);
|
InputSettings::register(ctx);
|
||||||
WarpifySettings::register(ctx);
|
WormholeSettings::register(ctx);
|
||||||
AltScreenReporting::register(ctx);
|
AltScreenReporting::register(ctx);
|
||||||
UndoCloseSettings::register(ctx);
|
UndoCloseSettings::register(ctx);
|
||||||
SshSettings::register(ctx);
|
SshSettings::register(ctx);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use galaxyui::platform::linux;
|
use galaxyui::platform::linux;
|
||||||
use settings::macros::define_settings_group;
|
use settings::macros::define_settings_group;
|
||||||
use settings::{SupportedPlatforms, SyncToCloud};
|
use settings::{Setting as _, SupportedPlatforms, SyncToCloud};
|
||||||
|
|
||||||
define_settings_group!(LinuxAppConfiguration,
|
define_settings_group!(LinuxAppConfiguration,
|
||||||
settings: [
|
settings: [
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ mod onboarding;
|
|||||||
mod pane;
|
mod pane;
|
||||||
mod privacy;
|
mod privacy;
|
||||||
mod same_line_prompt_block;
|
mod same_line_prompt_block;
|
||||||
|
pub mod schema_export;
|
||||||
mod scroll;
|
mod scroll;
|
||||||
mod select;
|
mod select;
|
||||||
mod ssh;
|
mod ssh;
|
||||||
|
|||||||
@@ -0,0 +1,230 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use galaxy_core::channel::{Channel, ChannelState};
|
||||||
|
use galaxy_core::features::{
|
||||||
|
FeatureFlag, DEBUG_FLAGS, DOGFOOD_FLAGS, PREVIEW_FLAGS, RELEASE_FLAGS,
|
||||||
|
};
|
||||||
|
use schemars::SchemaGenerator;
|
||||||
|
use serde_json::{Map, Value};
|
||||||
|
use settings::schema::SettingSchemaEntry;
|
||||||
|
|
||||||
|
fn strip_numeric_metadata(value: &mut Value) {
|
||||||
|
match value {
|
||||||
|
Value::Object(map) => {
|
||||||
|
let is_numeric = map
|
||||||
|
.get("type")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.is_some_and(|value_type| value_type == "integer" || value_type == "number");
|
||||||
|
|
||||||
|
if is_numeric {
|
||||||
|
map.remove("minimum");
|
||||||
|
map.remove("maximum");
|
||||||
|
map.remove("format");
|
||||||
|
}
|
||||||
|
|
||||||
|
for value in map.values_mut() {
|
||||||
|
strip_numeric_metadata(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Array(values) => {
|
||||||
|
for value in values {
|
||||||
|
strip_numeric_metadata(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn strip_empty_enum_entries(value: &mut Value) {
|
||||||
|
match value {
|
||||||
|
Value::Object(map) => {
|
||||||
|
if let Some(Value::Array(one_of)) = map.get_mut("oneOf") {
|
||||||
|
one_of.retain(|entry| {
|
||||||
|
!matches!(entry, Value::Object(object)
|
||||||
|
if object.get("enum").is_some_and(|value| value.as_array().is_some_and(|values| values.is_empty())))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for value in map.values_mut() {
|
||||||
|
strip_empty_enum_entries(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Array(values) => {
|
||||||
|
for value in values {
|
||||||
|
strip_empty_enum_entries(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn active_flags_for_channel(channel: &str) -> HashSet<FeatureFlag> {
|
||||||
|
let mut flags = HashSet::new();
|
||||||
|
|
||||||
|
let flag_lists: &[&[FeatureFlag]] = match channel {
|
||||||
|
"stable" => &[RELEASE_FLAGS],
|
||||||
|
"preview" => &[RELEASE_FLAGS, PREVIEW_FLAGS],
|
||||||
|
"dev" => &[RELEASE_FLAGS, PREVIEW_FLAGS, DOGFOOD_FLAGS, DEBUG_FLAGS],
|
||||||
|
other => {
|
||||||
|
log::warn!("Unknown settings schema channel '{other}', defaulting to dev");
|
||||||
|
&[RELEASE_FLAGS, PREVIEW_FLAGS, DOGFOOD_FLAGS, DEBUG_FLAGS]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for list in flag_lists {
|
||||||
|
flags.extend(*list);
|
||||||
|
}
|
||||||
|
|
||||||
|
flags
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_hierarchy<'a>(
|
||||||
|
root_properties: &'a mut Map<String, Value>,
|
||||||
|
hierarchy: &str,
|
||||||
|
) -> &'a mut Map<String, Value> {
|
||||||
|
let mut current = root_properties;
|
||||||
|
|
||||||
|
for segment in hierarchy.split('.') {
|
||||||
|
let entry = current.entry(segment.to_string()).or_insert_with(|| {
|
||||||
|
Value::Object({
|
||||||
|
let mut map = Map::new();
|
||||||
|
map.insert("type".to_string(), Value::String("object".to_string()));
|
||||||
|
map.insert("properties".to_string(), Value::Object(Map::new()));
|
||||||
|
map
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
current = entry
|
||||||
|
.as_object_mut()
|
||||||
|
.expect("hierarchy node should be an object")
|
||||||
|
.entry("properties")
|
||||||
|
.or_insert_with(|| Value::Object(Map::new()))
|
||||||
|
.as_object_mut()
|
||||||
|
.expect("properties should be an object");
|
||||||
|
}
|
||||||
|
|
||||||
|
current
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates the user-facing JSON schema for Galaxy's TOML settings.
|
||||||
|
pub fn generate_settings_schema(channel: &str) -> (String, usize) {
|
||||||
|
let active_flags = active_flags_for_channel(channel);
|
||||||
|
let mut generator = SchemaGenerator::default();
|
||||||
|
let mut root_properties = Map::new();
|
||||||
|
let mut entry_count = 0;
|
||||||
|
|
||||||
|
for entry in inventory::iter::<SettingSchemaEntry> {
|
||||||
|
if entry.is_private {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(flag) = entry.feature_flag {
|
||||||
|
if !active_flags.contains(&flag) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let type_schema = (entry.schema_fn)(&mut generator);
|
||||||
|
let mut schema_value: Value = type_schema.to_value();
|
||||||
|
let default_json = (entry.file_default_value_fn)();
|
||||||
|
|
||||||
|
if let Ok(default_value) = serde_json::from_str::<Value>(&default_json) {
|
||||||
|
if let Some(object) = schema_value.as_object_mut() {
|
||||||
|
object.insert("default".to_string(), default_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !entry.description.is_empty() {
|
||||||
|
if let Some(object) = schema_value.as_object_mut() {
|
||||||
|
object.insert(
|
||||||
|
"description".to_string(),
|
||||||
|
Value::String(entry.description.to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let target = if let Some(hierarchy) = entry.hierarchy {
|
||||||
|
ensure_hierarchy(&mut root_properties, hierarchy)
|
||||||
|
} else {
|
||||||
|
&mut root_properties
|
||||||
|
};
|
||||||
|
|
||||||
|
target.insert(entry.storage_key.to_string(), schema_value);
|
||||||
|
entry_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let definitions = generator.take_definitions(true);
|
||||||
|
let mut root = Map::new();
|
||||||
|
root.insert(
|
||||||
|
"$schema".to_string(),
|
||||||
|
Value::String("https://json-schema.org/draft/2020-12/schema".to_string()),
|
||||||
|
);
|
||||||
|
root.insert(
|
||||||
|
"title".to_string(),
|
||||||
|
Value::String("Galaxy Settings".to_string()),
|
||||||
|
);
|
||||||
|
root.insert(
|
||||||
|
"description".to_string(),
|
||||||
|
Value::String(format!(
|
||||||
|
"JSON Schema for Galaxy settings ({channel} channel, {entry_count} settings)"
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
root.insert("type".to_string(), Value::String("object".to_string()));
|
||||||
|
root.insert("properties".to_string(), Value::Object(root_properties));
|
||||||
|
|
||||||
|
if !definitions.is_empty() {
|
||||||
|
root.insert("$defs".to_string(), Value::Object(definitions));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut root_value = Value::Object(root);
|
||||||
|
strip_numeric_metadata(&mut root_value);
|
||||||
|
strip_empty_enum_entries(&mut root_value);
|
||||||
|
|
||||||
|
(
|
||||||
|
serde_json::to_string_pretty(&root_value).expect("settings schema should serialize"),
|
||||||
|
entry_count,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn runtime_schema_channel() -> &'static str {
|
||||||
|
match ChannelState::channel() {
|
||||||
|
Channel::Stable => "stable",
|
||||||
|
Channel::Preview => "preview",
|
||||||
|
Channel::Dev | Channel::Local | Channel::Oss | Channel::Integration => "dev",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the bundled settings schema, or generates a current local copy for development runs.
|
||||||
|
pub fn ensure_runtime_settings_schema() -> anyhow::Result<PathBuf> {
|
||||||
|
if let Some(schema_path) = galaxy_core::paths::bundled_resources_dir()
|
||||||
|
.map(|resources| resources.join("settings_schema.json"))
|
||||||
|
.filter(|path| path.is_file())
|
||||||
|
{
|
||||||
|
return Ok(schema_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema_path = galaxy_core::paths::config_local_dir().join("settings_schema.json");
|
||||||
|
let (schema, _) = generate_settings_schema(runtime_schema_channel());
|
||||||
|
let existing_schema = std::fs::read_to_string(&schema_path).ok();
|
||||||
|
|
||||||
|
if existing_schema.as_deref() != Some(schema.as_str()) {
|
||||||
|
if let Some(parent) = schema_path.parent() {
|
||||||
|
std::fs::create_dir_all(parent).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to create settings schema directory {}",
|
||||||
|
parent.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
std::fs::write(&schema_path, schema).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to write Galaxy settings schema to {}",
|
||||||
|
schema_path.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(schema_path)
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ define_settings_group!(SshSettings,
|
|||||||
sync_to_cloud: SyncToCloud::Never,
|
sync_to_cloud: SyncToCloud::Never,
|
||||||
private: false,
|
private: false,
|
||||||
storage_key: "ReuseExistingSshControlMaster",
|
storage_key: "ReuseExistingSshControlMaster",
|
||||||
toml_path: "warpify.ssh.reuse_existing_control_master",
|
toml_path: "wormhole.ssh.reuse_existing_control_master",
|
||||||
description: "Whether the legacy SSH wrapper attaches to an existing SSH ControlMaster for the destination host instead of always creating its own.",
|
description: "Whether the legacy SSH wrapper attaches to an existing SSH ControlMaster for the destination host instead of always creating its own.",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ use settings_page::{
|
|||||||
HEADER_PADDING,
|
HEADER_PADDING,
|
||||||
};
|
};
|
||||||
use teams_page::{TeamsPageView, TeamsPageViewEvent};
|
use teams_page::{TeamsPageView, TeamsPageViewEvent};
|
||||||
use warpify_page::{WarpifyPageAction, WarpifyPageView};
|
use wormhole_page::{WormholePageAction, WormholePageView};
|
||||||
|
|
||||||
use self::telemetry::SettingsTelemetryEvent;
|
use self::telemetry::SettingsTelemetryEvent;
|
||||||
use crate::ai::custom_model_routers::CustomModelRouter;
|
use crate::ai::custom_model_routers::CustomModelRouter;
|
||||||
@@ -95,7 +95,7 @@ mod teams_page;
|
|||||||
mod telemetry;
|
mod telemetry;
|
||||||
pub mod update_environment_form;
|
pub mod update_environment_form;
|
||||||
mod warp_drive_page;
|
mod warp_drive_page;
|
||||||
mod warpify_page;
|
mod wormhole_page;
|
||||||
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
#[cfg(not(target_family = "wasm"))]
|
||||||
pub(crate) use ai_page::cli_agent_settings_widget_id;
|
pub(crate) use ai_page::cli_agent_settings_widget_id;
|
||||||
@@ -233,7 +233,7 @@ pub enum SettingsSection {
|
|||||||
Scripting,
|
Scripting,
|
||||||
Teams,
|
Teams,
|
||||||
WarpDrive,
|
WarpDrive,
|
||||||
Warpify,
|
Wormhole,
|
||||||
/// Internal backing-page identifier for AISettingsPageView. Multiple subpages
|
/// Internal backing-page identifier for AISettingsPageView. Multiple subpages
|
||||||
/// (WarpAgent, AgentProfiles, Knowledge, ThirdPartyCLIAgents) share this single
|
/// (WarpAgent, AgentProfiles, Knowledge, ThirdPartyCLIAgents) share this single
|
||||||
/// backing page, so this variant is needed as the key in `settings_pages`.
|
/// backing page, so this variant is needed as the key in `settings_pages`.
|
||||||
@@ -286,7 +286,7 @@ impl Display for SettingsSection {
|
|||||||
SettingsSection::ProviderChatGPTSubscription => write!(f, "ChatGPT Subscription"),
|
SettingsSection::ProviderChatGPTSubscription => write!(f, "ChatGPT Subscription"),
|
||||||
SettingsSection::ProviderBedrock => write!(f, "Bedrock"),
|
SettingsSection::ProviderBedrock => write!(f, "Bedrock"),
|
||||||
SettingsSection::ProviderACP => write!(f, "ACP"),
|
SettingsSection::ProviderACP => write!(f, "ACP"),
|
||||||
SettingsSection::Warpify => write!(f, "Wormhole"),
|
SettingsSection::Wormhole => write!(f, "Wormhole"),
|
||||||
SettingsSection::CodeIndexing => write!(f, "Indexing and projects"),
|
SettingsSection::CodeIndexing => write!(f, "Indexing and projects"),
|
||||||
SettingsSection::EditorAndCodeReview => write!(f, "Editor and Code Review"),
|
SettingsSection::EditorAndCodeReview => write!(f, "Editor and Code Review"),
|
||||||
_ => write!(f, "{self:?}"),
|
_ => write!(f, "{self:?}"),
|
||||||
@@ -407,7 +407,6 @@ impl FromStr for SettingsSection {
|
|||||||
"Privacy" => Ok(Self::Privacy),
|
"Privacy" => Ok(Self::Privacy),
|
||||||
"Galaxy Control" | "Scripting" => Ok(Self::Scripting),
|
"Galaxy Control" | "Scripting" => Ok(Self::Scripting),
|
||||||
"Teams" => Ok(Self::Teams),
|
"Teams" => Ok(Self::Teams),
|
||||||
"Warpify" => Ok(Self::Warpify),
|
|
||||||
"WarpDrive" | "Galaxy Drive" => Ok(Self::WarpDrive),
|
"WarpDrive" | "Galaxy Drive" => Ok(Self::WarpDrive),
|
||||||
"Oz" | "Warp Agent" | "Galaxy Agent" => Ok(Self::WarpAgent),
|
"Oz" | "Warp Agent" | "Galaxy Agent" => Ok(Self::WarpAgent),
|
||||||
"Profiles" | "AgentProfiles" => Ok(Self::AgentProfiles),
|
"Profiles" | "AgentProfiles" => Ok(Self::AgentProfiles),
|
||||||
@@ -423,7 +422,7 @@ impl FromStr for SettingsSection {
|
|||||||
"Indexing and projects" | "CodeIndexing" => Ok(Self::CodeIndexing),
|
"Indexing and projects" | "CodeIndexing" => Ok(Self::CodeIndexing),
|
||||||
"Editor and Code Review" | "EditorAndCodeReview" => Ok(Self::EditorAndCodeReview),
|
"Editor and Code Review" | "EditorAndCodeReview" => Ok(Self::EditorAndCodeReview),
|
||||||
"Experiments" => Ok(Self::Experiments),
|
"Experiments" => Ok(Self::Experiments),
|
||||||
"Wormhole" => Ok(Self::Warpify),
|
"Wormhole" => Ok(Self::Wormhole),
|
||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -488,7 +487,7 @@ pub mod flags {
|
|||||||
pub const SCROLL_REPORTING_CONTEXT_FLAG: &str = "Scroll_Reporting";
|
pub const SCROLL_REPORTING_CONTEXT_FLAG: &str = "Scroll_Reporting";
|
||||||
pub const FOCUS_REPORTING_CONTEXT_FLAG: &str = "Focus_Reporting";
|
pub const FOCUS_REPORTING_CONTEXT_FLAG: &str = "Focus_Reporting";
|
||||||
pub const SSH_REUSE_CONTROL_MASTER_CONTEXT_FLAG: &str = "SSH_Reuse_Control_Master";
|
pub const SSH_REUSE_CONTROL_MASTER_CONTEXT_FLAG: &str = "SSH_Reuse_Control_Master";
|
||||||
pub const SSH_WARPIFICATION_CONTEXT_FLAG: &str = "SSH_Warpification";
|
pub const SSH_WORMHOLING_CONTEXT_FLAG: &str = "SSH_Wormholing";
|
||||||
pub const NOTIFICATIONS_CONTEXT_FLAG: &str = "Notifications_Enabled";
|
pub const NOTIFICATIONS_CONTEXT_FLAG: &str = "Notifications_Enabled";
|
||||||
pub const LONG_RUNNING_NOTIFICATIONS_FLAG: &str = "Long_Running_Notifications";
|
pub const LONG_RUNNING_NOTIFICATIONS_FLAG: &str = "Long_Running_Notifications";
|
||||||
pub const AGENT_TASK_COMPLETED_NOTIFICATIONS_FLAG: &str = "Agent_Task_Completed_Notifications";
|
pub const AGENT_TASK_COMPLETED_NOTIFICATIONS_FLAG: &str = "Agent_Task_Completed_Notifications";
|
||||||
@@ -610,7 +609,7 @@ pub mod flags {
|
|||||||
pub const IS_AUTOINDEXING_ENABLED: &str = "IsAutoIndexingEnabled";
|
pub const IS_AUTOINDEXING_ENABLED: &str = "IsAutoIndexingEnabled";
|
||||||
pub const LIGATURE_RENDERING_CONTEXT_FLAG: &str = "Ligature_Rendering_Enabled";
|
pub const LIGATURE_RENDERING_CONTEXT_FLAG: &str = "Ligature_Rendering_Enabled";
|
||||||
pub const HAS_SETTINGS_TO_IMPORT_FLAG: &str = "HasSettingsToImport";
|
pub const HAS_SETTINGS_TO_IMPORT_FLAG: &str = "HasSettingsToImport";
|
||||||
/// The user's setting enabled UDI, but we may show a classic input (e.g. ssh/subshell warpification)
|
/// The user's setting enabled UDI, but we may show a classic input (e.g. ssh/subshell wormholing)
|
||||||
pub const UNIVERSAL_DEVELOPER_INPUT_ENABLED: &str = "UniversalDeveloperInputEnabled";
|
pub const UNIVERSAL_DEVELOPER_INPUT_ENABLED: &str = "UniversalDeveloperInputEnabled";
|
||||||
pub const AGENT_MODE_INPUT: &str = "InputAgentMode";
|
pub const AGENT_MODE_INPUT: &str = "InputAgentMode";
|
||||||
pub const TERMINAL_MODE_INPUT: &str = "InputTerminalMode";
|
pub const TERMINAL_MODE_INPUT: &str = "InputTerminalMode";
|
||||||
@@ -657,7 +656,7 @@ pub fn init_actions_from_parent_view<T: Action + Clone>(
|
|||||||
) {
|
) {
|
||||||
appearance_page::init_actions_from_parent_view(app, context, builder);
|
appearance_page::init_actions_from_parent_view(app, context, builder);
|
||||||
features_page::init_actions_from_parent_view(app, context, builder);
|
features_page::init_actions_from_parent_view(app, context, builder);
|
||||||
warpify_page::init_actions_from_parent_view(app, context, builder);
|
wormhole_page::init_actions_from_parent_view(app, context, builder);
|
||||||
privacy_page::init_actions_from_parent_view(app, context, builder);
|
privacy_page::init_actions_from_parent_view(app, context, builder);
|
||||||
ai_page::init_actions_from_parent_view(app, context, builder);
|
ai_page::init_actions_from_parent_view(app, context, builder);
|
||||||
code_page::init_actions_from_parent_view(app, context, builder);
|
code_page::init_actions_from_parent_view(app, context, builder);
|
||||||
@@ -965,7 +964,7 @@ pub enum SettingsAction {
|
|||||||
AI(AISettingsPageAction),
|
AI(AISettingsPageAction),
|
||||||
Code(CodeSettingsPageAction),
|
Code(CodeSettingsPageAction),
|
||||||
WarpDrive(warp_drive_page::WarpDriveSettingsPageAction),
|
WarpDrive(warp_drive_page::WarpDriveSettingsPageAction),
|
||||||
WarpifyPageToggle(WarpifyPageAction),
|
WormholePageToggle(WormholePageAction),
|
||||||
Tab,
|
Tab,
|
||||||
Split(Direction),
|
Split(Direction),
|
||||||
ToggleMaximizePane,
|
ToggleMaximizePane,
|
||||||
@@ -1108,7 +1107,7 @@ macro_rules! update_page {
|
|||||||
SettingsPageViewHandle::Appearance(handle) => $ctx.update_view(handle, $update),
|
SettingsPageViewHandle::Appearance(handle) => $ctx.update_view(handle, $update),
|
||||||
SettingsPageViewHandle::Features(handle) => $ctx.update_view(handle, $update),
|
SettingsPageViewHandle::Features(handle) => $ctx.update_view(handle, $update),
|
||||||
SettingsPageViewHandle::Keybindings(handle) => $ctx.update_view(handle, $update),
|
SettingsPageViewHandle::Keybindings(handle) => $ctx.update_view(handle, $update),
|
||||||
SettingsPageViewHandle::Warpify(handle) => $ctx.update_view(handle, $update),
|
SettingsPageViewHandle::Wormhole(handle) => $ctx.update_view(handle, $update),
|
||||||
SettingsPageViewHandle::Privacy(handle) => $ctx.update_view(handle, $update),
|
SettingsPageViewHandle::Privacy(handle) => $ctx.update_view(handle, $update),
|
||||||
SettingsPageViewHandle::Scripting(handle) => $ctx.update_view(handle, $update),
|
SettingsPageViewHandle::Scripting(handle) => $ctx.update_view(handle, $update),
|
||||||
SettingsPageViewHandle::AI(handle) => $ctx.update_view(handle, $update),
|
SettingsPageViewHandle::AI(handle) => $ctx.update_view(handle, $update),
|
||||||
@@ -1195,9 +1194,9 @@ impl SettingsView {
|
|||||||
me.handle_code_page_event(event, ctx);
|
me.handle_code_page_event(event, ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
let warpify_page_handle = ctx.add_typed_action_view(WarpifyPageView::new);
|
let wormhole_page_handle = ctx.add_typed_action_view(WormholePageView::new);
|
||||||
ctx.subscribe_to_view(&warpify_page_handle, |me, _, event, ctx| {
|
ctx.subscribe_to_view(&wormhole_page_handle, |me, _, event, ctx| {
|
||||||
me.handle_warpify_page_event(event, ctx);
|
me.handle_wormhole_page_event(event, ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Render the privacy page only if telemetry opt-out is enabled.
|
// Render the privacy page only if telemetry opt-out is enabled.
|
||||||
@@ -1256,7 +1255,7 @@ impl SettingsView {
|
|||||||
SettingsPage::new(appearance_page_handle),
|
SettingsPage::new(appearance_page_handle),
|
||||||
SettingsPage::new(features_page_handle),
|
SettingsPage::new(features_page_handle),
|
||||||
SettingsPage::new(keybindings_handle),
|
SettingsPage::new(keybindings_handle),
|
||||||
SettingsPage::new(warpify_page_handle),
|
SettingsPage::new(wormhole_page_handle),
|
||||||
SettingsPage::new(warp_drive_page_handle),
|
SettingsPage::new(warp_drive_page_handle),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -1291,7 +1290,7 @@ impl SettingsView {
|
|||||||
SettingsNavItem::Page(SettingsSection::Appearance),
|
SettingsNavItem::Page(SettingsSection::Appearance),
|
||||||
SettingsNavItem::Page(SettingsSection::Features),
|
SettingsNavItem::Page(SettingsSection::Features),
|
||||||
SettingsNavItem::Page(SettingsSection::Keybindings),
|
SettingsNavItem::Page(SettingsSection::Keybindings),
|
||||||
SettingsNavItem::Page(SettingsSection::Warpify),
|
SettingsNavItem::Page(SettingsSection::Wormhole),
|
||||||
SettingsNavItem::Page(SettingsSection::WarpDrive),
|
SettingsNavItem::Page(SettingsSection::WarpDrive),
|
||||||
SettingsNavItem::Page(SettingsSection::Privacy),
|
SettingsNavItem::Page(SettingsSection::Privacy),
|
||||||
SettingsNavItem::Page(SettingsSection::About),
|
SettingsNavItem::Page(SettingsSection::About),
|
||||||
@@ -1702,7 +1701,7 @@ impl SettingsView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_warpify_page_event(
|
fn handle_wormhole_page_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: &SettingsPageEvent,
|
event: &SettingsPageEvent,
|
||||||
ctx: &mut ViewContext<Self>,
|
ctx: &mut ViewContext<Self>,
|
||||||
@@ -1938,7 +1937,7 @@ impl SettingsView {
|
|||||||
SettingsPageViewHandle::Appearance(v) => v.as_ref(app).should_render(app),
|
SettingsPageViewHandle::Appearance(v) => v.as_ref(app).should_render(app),
|
||||||
SettingsPageViewHandle::About(v) => v.as_ref(app).should_render(app),
|
SettingsPageViewHandle::About(v) => v.as_ref(app).should_render(app),
|
||||||
SettingsPageViewHandle::Privacy(v) => v.as_ref(app).should_render(app),
|
SettingsPageViewHandle::Privacy(v) => v.as_ref(app).should_render(app),
|
||||||
SettingsPageViewHandle::Warpify(v) => v.as_ref(app).should_render(app),
|
SettingsPageViewHandle::Wormhole(v) => v.as_ref(app).should_render(app),
|
||||||
SettingsPageViewHandle::Scripting(v) => v.as_ref(app).should_render(app),
|
SettingsPageViewHandle::Scripting(v) => v.as_ref(app).should_render(app),
|
||||||
SettingsPageViewHandle::AI(v) => v.as_ref(app).should_render(app),
|
SettingsPageViewHandle::AI(v) => v.as_ref(app).should_render(app),
|
||||||
SettingsPageViewHandle::MCPServers(v) => v.as_ref(app).should_render(app),
|
SettingsPageViewHandle::MCPServers(v) => v.as_ref(app).should_render(app),
|
||||||
@@ -2518,11 +2517,11 @@ impl TypedActionView for SettingsView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SettingsAction::WarpifyPageToggle(warpify_action) => {
|
SettingsAction::WormholePageToggle(wormhole_action) => {
|
||||||
if let Some(warpify_page) = self.settings_page(SettingsSection::Warpify) {
|
if let Some(wormhole_page) = self.settings_page(SettingsSection::Wormhole) {
|
||||||
if let SettingsPageViewHandle::Warpify(view) = &warpify_page.view_handle {
|
if let SettingsPageViewHandle::Wormhole(view) = &wormhole_page.view_handle {
|
||||||
view.update(ctx, |view, ctx| {
|
view.update(ctx, |view, ctx| {
|
||||||
view.handle_action(warpify_action, ctx);
|
view.handle_action(wormhole_action, ctx);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ fn top_level_sections_map_to_themselves() {
|
|||||||
SettingsSection::Scripting,
|
SettingsSection::Scripting,
|
||||||
SettingsSection::Teams,
|
SettingsSection::Teams,
|
||||||
SettingsSection::WarpDrive,
|
SettingsSection::WarpDrive,
|
||||||
SettingsSection::Warpify,
|
SettingsSection::Wormhole,
|
||||||
] {
|
] {
|
||||||
assert!(!section.is_subpage(), "{section:?} should be top-level");
|
assert!(!section.is_subpage(), "{section:?} should be top-level");
|
||||||
assert_eq!(section.parent_page_section(), section);
|
assert_eq!(section.parent_page_section(), section);
|
||||||
@@ -101,7 +101,7 @@ fn current_settings_display_names_round_trip() {
|
|||||||
for (section, display_name) in [
|
for (section, display_name) in [
|
||||||
(SettingsSection::Scripting, "Galaxy Control"),
|
(SettingsSection::Scripting, "Galaxy Control"),
|
||||||
(SettingsSection::WarpDrive, "Galaxy Drive"),
|
(SettingsSection::WarpDrive, "Galaxy Drive"),
|
||||||
(SettingsSection::Warpify, "Wormhole"),
|
(SettingsSection::Wormhole, "Wormhole"),
|
||||||
(SettingsSection::WarpAgent, "Galaxy Agent"),
|
(SettingsSection::WarpAgent, "Galaxy Agent"),
|
||||||
(SettingsSection::AgentProfiles, "Profiles"),
|
(SettingsSection::AgentProfiles, "Profiles"),
|
||||||
(SettingsSection::AgentMCPServers, "MCP servers"),
|
(SettingsSection::AgentMCPServers, "MCP servers"),
|
||||||
@@ -135,7 +135,7 @@ fn legacy_settings_names_remain_parseable() {
|
|||||||
for (name, expected) in [
|
for (name, expected) in [
|
||||||
("Scripting", SettingsSection::Scripting),
|
("Scripting", SettingsSection::Scripting),
|
||||||
("WarpDrive", SettingsSection::WarpDrive),
|
("WarpDrive", SettingsSection::WarpDrive),
|
||||||
("Warpify", SettingsSection::Warpify),
|
("Wormhole", SettingsSection::Wormhole),
|
||||||
("Oz", SettingsSection::WarpAgent),
|
("Oz", SettingsSection::WarpAgent),
|
||||||
("Warp Agent", SettingsSection::WarpAgent),
|
("Warp Agent", SettingsSection::WarpAgent),
|
||||||
("AgentProfiles", SettingsSection::AgentProfiles),
|
("AgentProfiles", SettingsSection::AgentProfiles),
|
||||||
@@ -191,7 +191,7 @@ fn realistic_nav_items() -> Vec<SettingsNavItem> {
|
|||||||
SettingsNavItem::Page(SettingsSection::Appearance),
|
SettingsNavItem::Page(SettingsSection::Appearance),
|
||||||
SettingsNavItem::Page(SettingsSection::Features),
|
SettingsNavItem::Page(SettingsSection::Features),
|
||||||
SettingsNavItem::Page(SettingsSection::Keybindings),
|
SettingsNavItem::Page(SettingsSection::Keybindings),
|
||||||
SettingsNavItem::Page(SettingsSection::Warpify),
|
SettingsNavItem::Page(SettingsSection::Wormhole),
|
||||||
SettingsNavItem::Page(SettingsSection::WarpDrive),
|
SettingsNavItem::Page(SettingsSection::WarpDrive),
|
||||||
SettingsNavItem::Page(SettingsSection::Privacy),
|
SettingsNavItem::Page(SettingsSection::Privacy),
|
||||||
SettingsNavItem::Page(SettingsSection::About),
|
SettingsNavItem::Page(SettingsSection::About),
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ use super::privacy_page::PrivacyPageView;
|
|||||||
use super::scripting_page::ScriptingSettingsPageView;
|
use super::scripting_page::ScriptingSettingsPageView;
|
||||||
use super::teams_page::TeamsPageView;
|
use super::teams_page::TeamsPageView;
|
||||||
use super::warp_drive_page::WarpDriveSettingsPageView;
|
use super::warp_drive_page::WarpDriveSettingsPageView;
|
||||||
use super::warpify_page::WarpifyPageView;
|
use super::wormhole_page::WormholePageView;
|
||||||
use super::SettingsSection;
|
use super::SettingsSection;
|
||||||
use crate::appearance::Appearance;
|
use crate::appearance::Appearance;
|
||||||
use crate::settings::CloudPreferencesSettings;
|
use crate::settings::CloudPreferencesSettings;
|
||||||
@@ -100,7 +100,7 @@ pub enum SettingsPageViewHandle {
|
|||||||
About(ViewHandle<AboutPageView>),
|
About(ViewHandle<AboutPageView>),
|
||||||
Code(ViewHandle<CodeSettingsPageView>),
|
Code(ViewHandle<CodeSettingsPageView>),
|
||||||
Privacy(ViewHandle<PrivacyPageView>),
|
Privacy(ViewHandle<PrivacyPageView>),
|
||||||
Warpify(ViewHandle<WarpifyPageView>),
|
Wormhole(ViewHandle<WormholePageView>),
|
||||||
Scripting(ViewHandle<ScriptingSettingsPageView>),
|
Scripting(ViewHandle<ScriptingSettingsPageView>),
|
||||||
AI(ViewHandle<AISettingsPageView>),
|
AI(ViewHandle<AISettingsPageView>),
|
||||||
MCPServers(ViewHandle<MCPServersSettingsPageView>),
|
MCPServers(ViewHandle<MCPServersSettingsPageView>),
|
||||||
@@ -119,7 +119,7 @@ impl SettingsPageViewHandle {
|
|||||||
About(view_handle) => ChildView::new(view_handle).finish(),
|
About(view_handle) => ChildView::new(view_handle).finish(),
|
||||||
Code(view_handle) => ChildView::new(view_handle).finish(),
|
Code(view_handle) => ChildView::new(view_handle).finish(),
|
||||||
Privacy(view_handle) => ChildView::new(view_handle).finish(),
|
Privacy(view_handle) => ChildView::new(view_handle).finish(),
|
||||||
Warpify(view_handle) => ChildView::new(view_handle).finish(),
|
Wormhole(view_handle) => ChildView::new(view_handle).finish(),
|
||||||
Scripting(view_handle) => ChildView::new(view_handle).finish(),
|
Scripting(view_handle) => ChildView::new(view_handle).finish(),
|
||||||
AI(view_handle) => ChildView::new(view_handle).finish(),
|
AI(view_handle) => ChildView::new(view_handle).finish(),
|
||||||
MCPServers(view_handle) => ChildView::new(view_handle).finish(),
|
MCPServers(view_handle) => ChildView::new(view_handle).finish(),
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ use std::collections::HashMap;
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use galaxy_core::features::FeatureFlag;
|
use galaxy_core::features::FeatureFlag;
|
||||||
use galaxyui::elements::{
|
use galaxyui::elements::{Container, Flex, MouseStateHandle, ParentElement, Text};
|
||||||
Container, Flex, FormattedTextElement, HighlightedHyperlink, MouseStateHandle, ParentElement,
|
|
||||||
};
|
|
||||||
use galaxyui::keymap::ContextPredicate;
|
use galaxyui::keymap::ContextPredicate;
|
||||||
use galaxyui::presenter::ChildView;
|
use galaxyui::presenter::ChildView;
|
||||||
use galaxyui::ui_components::components::{Coords, UiComponent, UiComponentStyles};
|
use galaxyui::ui_components::components::{Coords, UiComponent, UiComponentStyles};
|
||||||
@@ -14,7 +12,6 @@ use galaxyui::{
|
|||||||
Action, AppContext, Element, Entity, ModelHandle, SingletonEntity, TypedActionView, View,
|
Action, AppContext, Element, Entity, ModelHandle, SingletonEntity, TypedActionView, View,
|
||||||
ViewContext, ViewHandle,
|
ViewContext, ViewHandle,
|
||||||
};
|
};
|
||||||
use markdown_parser::{FormattedText, FormattedTextFragment, FormattedTextLine};
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use settings::{Setting, ToggleableSetting};
|
use settings::{Setting, ToggleableSetting};
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
@@ -29,9 +26,9 @@ use super::{flags, SettingsAction, SettingsSection, ToggleSettingActionPair};
|
|||||||
use crate::appearance::Appearance;
|
use crate::appearance::Appearance;
|
||||||
use crate::server::telemetry::TelemetryEvent;
|
use crate::server::telemetry::TelemetryEvent;
|
||||||
use crate::settings::{ReuseExistingSshControlMaster, SshSettings};
|
use crate::settings::{ReuseExistingSshControlMaster, SshSettings};
|
||||||
use crate::terminal::warpify::settings::{
|
use crate::terminal::wormhole::settings::{
|
||||||
EnableSshWarpification, SshExtensionInstallMode, SshExtensionInstallModeSetting,
|
EnableSshWormholing, SshExtensionInstallMode, SshExtensionInstallModeSetting, WormholeSettings,
|
||||||
WarpifySettings, WarpifySettingsChangedEvent,
|
WormholeSettingsChangedEvent,
|
||||||
};
|
};
|
||||||
use crate::ui_components::blended_colors;
|
use crate::ui_components::blended_colors;
|
||||||
use crate::view_components::dropdown::{Dropdown, DropdownItem};
|
use crate::view_components::dropdown::{Dropdown, DropdownItem};
|
||||||
@@ -43,19 +40,19 @@ pub fn init_actions_from_parent_view<T: Action + Clone>(
|
|||||||
context: &ContextPredicate,
|
context: &ContextPredicate,
|
||||||
builder: fn(SettingsAction) -> T,
|
builder: fn(SettingsAction) -> T,
|
||||||
) {
|
) {
|
||||||
// Add all of the toggle settings from the Warpify Page that you want to show up on the Command Palette here.
|
// Add all of the toggle settings from the Wormhole Page that you want to show up on the Command Palette here.
|
||||||
let mut toggle_binding_pairs = vec![];
|
let mut toggle_binding_pairs = vec![];
|
||||||
if WarpifySettings::as_ref(app)
|
if WormholeSettings::as_ref(app)
|
||||||
.enable_ssh_warpification
|
.enable_ssh_wormholing
|
||||||
.is_supported_on_current_platform()
|
.is_supported_on_current_platform()
|
||||||
{
|
{
|
||||||
toggle_binding_pairs.push(ToggleSettingActionPair::new(
|
toggle_binding_pairs.push(ToggleSettingActionPair::new(
|
||||||
"SSH Warpification",
|
"SSH Wormholing",
|
||||||
builder(SettingsAction::WarpifyPageToggle(
|
builder(SettingsAction::WormholePageToggle(
|
||||||
WarpifyPageAction::ToggleSshWarpification,
|
WormholePageAction::ToggleSshWormholing,
|
||||||
)),
|
)),
|
||||||
context,
|
context,
|
||||||
flags::SSH_WARPIFICATION_CONTEXT_FLAG,
|
flags::SSH_WORMHOLING_CONTEXT_FLAG,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,17 +65,17 @@ const ITEM_VERTICAL_SPACING: f32 = 24.;
|
|||||||
const BUILT_IN_TEXT_INPUT_MARGIN: f32 = 10.;
|
const BUILT_IN_TEXT_INPUT_MARGIN: f32 = 10.;
|
||||||
const SPACE_AFTER_TEXT_INPUT: f32 = ITEM_VERTICAL_SPACING - BUILT_IN_TEXT_INPUT_MARGIN;
|
const SPACE_AFTER_TEXT_INPUT: f32 = ITEM_VERTICAL_SPACING - BUILT_IN_TEXT_INPUT_MARGIN;
|
||||||
|
|
||||||
const SSH_REUSE_CONTROL_MASTER_DESCRIPTION: &str = "Attach to a live SSH ControlMaster you already have configured for the destination host instead of creating a Warp-owned one. Takes effect in new tabs.";
|
const SSH_REUSE_CONTROL_MASTER_DESCRIPTION: &str = "Attach to a live SSH ControlMaster you already have configured for the destination host instead of creating a Galaxy-owned one. Takes effect in new tabs.";
|
||||||
|
|
||||||
const SSH_EXTENSION_INSTALL_MODE_DESCRIPTION: &str =
|
const SSH_EXTENSION_INSTALL_MODE_DESCRIPTION: &str =
|
||||||
"Controls the installation behavior for Galaxy's SSH extension when a remote host doesn't have it installed.";
|
"Controls how Galaxy installs the Wormhole helper when a remote host doesn't have it.";
|
||||||
|
|
||||||
/// This page lets users configure when they get asked to warpify a session. Some shell commands
|
/// This page lets users configure when they get asked to wormhole a session. Some shell commands
|
||||||
/// are recognized by default. Users can add new shell commands, or prevent the default ones from
|
/// are recognized by default. Users can add new shell commands, or prevent the default ones from
|
||||||
/// asking. Users can also enable the SSH wrapper, and add hosts to a denylist.
|
/// asking. Users can also enable the SSH wrapper, and add hosts to a denylist.
|
||||||
/// This page is essentially the View for the SubshellSettings model, as well as the SshSettings
|
/// This page is essentially the View for the SubshellSettings model, as well as the SshSettings
|
||||||
/// related to warpification.
|
/// related to wormholing.
|
||||||
pub struct WarpifyPageView {
|
pub struct WormholePageView {
|
||||||
page: PageType<Self>,
|
page: PageType<Self>,
|
||||||
/// This needs to mirror the length of SubshellSettings::added_remove_button_states.
|
/// This needs to mirror the length of SubshellSettings::added_remove_button_states.
|
||||||
remove_added_command_button_states: Vec<MouseStateHandle>,
|
remove_added_command_button_states: Vec<MouseStateHandle>,
|
||||||
@@ -87,19 +84,19 @@ pub struct WarpifyPageView {
|
|||||||
remove_denylisted_command_button_states: Vec<MouseStateHandle>,
|
remove_denylisted_command_button_states: Vec<MouseStateHandle>,
|
||||||
add_denylisted_commands_editor: ViewHandle<SubmittableTextInput>,
|
add_denylisted_commands_editor: ViewHandle<SubmittableTextInput>,
|
||||||
|
|
||||||
ssh_extension_install_mode_dropdown: ViewHandle<Dropdown<WarpifyPageAction>>,
|
ssh_extension_install_mode_dropdown: ViewHandle<Dropdown<WormholePageAction>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WarpifyPageView {
|
impl WormholePageView {
|
||||||
pub fn new(ctx: &mut ViewContext<Self>) -> Self {
|
pub fn new(ctx: &mut ViewContext<Self>) -> Self {
|
||||||
let warpify_settings_handle = WarpifySettings::handle(ctx);
|
let wormhole_settings_handle = WormholeSettings::handle(ctx);
|
||||||
|
|
||||||
ctx.observe(&warpify_settings_handle, Self::update_button_states);
|
ctx.observe(&wormhole_settings_handle, Self::update_button_states);
|
||||||
ctx.subscribe_to_model(&warpify_settings_handle, move |me, model, event, ctx| {
|
ctx.subscribe_to_model(&wormhole_settings_handle, move |me, model, event, ctx| {
|
||||||
me.update_button_states(model, ctx);
|
me.update_button_states(model, ctx);
|
||||||
if matches!(
|
if matches!(
|
||||||
event,
|
event,
|
||||||
WarpifySettingsChangedEvent::SshExtensionInstallModeSetting { .. }
|
WormholeSettingsChangedEvent::SshExtensionInstallModeSetting { .. }
|
||||||
) {
|
) {
|
||||||
me.update_dropdown(ctx);
|
me.update_dropdown(ctx);
|
||||||
}
|
}
|
||||||
@@ -143,20 +140,20 @@ impl WarpifyPageView {
|
|||||||
ssh_extension_install_mode_dropdown,
|
ssh_extension_install_mode_dropdown,
|
||||||
};
|
};
|
||||||
|
|
||||||
instance.update_button_states(warpify_settings_handle, ctx);
|
instance.update_button_states(wormhole_settings_handle, ctx);
|
||||||
instance
|
instance
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_page(ctx: &mut ViewContext<Self>) -> PageType<Self> {
|
fn build_page(ctx: &mut ViewContext<Self>) -> PageType<Self> {
|
||||||
let mut categories = vec![
|
let mut categories = vec![
|
||||||
Category::new("", vec![Box::new(TitleWidget::default())]),
|
Category::new("", vec![Box::new(TitleWidget)]),
|
||||||
Category::new("Subshells", vec![Box::new(SubshellsWidget::default())])
|
Category::new("Subshells", vec![Box::new(SubshellsWidget::default())])
|
||||||
.with_subtitle("Subshells supported: bash, zsh, and fish."),
|
.with_subtitle("Subshells supported: bash, zsh, and fish."),
|
||||||
];
|
];
|
||||||
|
|
||||||
let warpify_settings = WarpifySettings::as_ref(ctx);
|
let wormhole_settings = WormholeSettings::as_ref(ctx);
|
||||||
if warpify_settings
|
if wormhole_settings
|
||||||
.enable_ssh_warpification
|
.enable_ssh_wormholing
|
||||||
.is_supported_on_current_platform()
|
.is_supported_on_current_platform()
|
||||||
{
|
{
|
||||||
categories.push(
|
categories.push(
|
||||||
@@ -171,16 +168,16 @@ impl WarpifyPageView {
|
|||||||
/// its delete button in the View.
|
/// its delete button in the View.
|
||||||
fn update_button_states(
|
fn update_button_states(
|
||||||
&mut self,
|
&mut self,
|
||||||
warpify_settings_handle: ModelHandle<WarpifySettings>,
|
wormhole_settings_handle: ModelHandle<WormholeSettings>,
|
||||||
ctx: &mut ViewContext<Self>,
|
ctx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
let warpify_settings = warpify_settings_handle.as_ref(ctx);
|
let wormhole_settings = wormhole_settings_handle.as_ref(ctx);
|
||||||
self.remove_denylisted_command_button_states = warpify_settings
|
self.remove_denylisted_command_button_states = wormhole_settings
|
||||||
.subshell_command_denylist
|
.subshell_command_denylist
|
||||||
.iter()
|
.iter()
|
||||||
.map(|_| Default::default())
|
.map(|_| Default::default())
|
||||||
.collect();
|
.collect();
|
||||||
self.remove_added_command_button_states = warpify_settings
|
self.remove_added_command_button_states = wormhole_settings
|
||||||
.added_subshell_commands
|
.added_subshell_commands
|
||||||
.iter()
|
.iter()
|
||||||
.map(|_| Default::default())
|
.map(|_| Default::default())
|
||||||
@@ -189,16 +186,16 @@ impl WarpifyPageView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Syncs the install-mode dropdown selection with the current
|
/// Syncs the install-mode dropdown selection with the current
|
||||||
/// `WarpifySettings::ssh_extension_install_mode` value (e.g. after it
|
/// `WormholeSettings::ssh_extension_install_mode` value (e.g. after it
|
||||||
/// was changed from the SSH remote server choice view).
|
/// was changed from the SSH remote server choice view).
|
||||||
fn update_dropdown(&mut self, ctx: &mut ViewContext<Self>) {
|
fn update_dropdown(&mut self, ctx: &mut ViewContext<Self>) {
|
||||||
let current_mode = *WarpifySettings::as_ref(ctx)
|
let current_mode = *WormholeSettings::as_ref(ctx)
|
||||||
.ssh_extension_install_mode
|
.ssh_extension_install_mode
|
||||||
.value();
|
.value();
|
||||||
self.ssh_extension_install_mode_dropdown
|
self.ssh_extension_install_mode_dropdown
|
||||||
.update(ctx, |dropdown, ctx| {
|
.update(ctx, |dropdown, ctx| {
|
||||||
dropdown.set_selected_by_action(
|
dropdown.set_selected_by_action(
|
||||||
WarpifyPageAction::SetSshExtensionInstallMode(current_mode),
|
WormholePageAction::SetSshExtensionInstallMode(current_mode),
|
||||||
ctx,
|
ctx,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -212,8 +209,8 @@ impl WarpifyPageView {
|
|||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
SubmittableTextInputEvent::Submit(new_command) => {
|
SubmittableTextInputEvent::Submit(new_command) => {
|
||||||
WarpifySettings::handle(ctx).update(ctx, |warpify_settings, ctx| {
|
WormholeSettings::handle(ctx).update(ctx, |wormhole_settings, ctx| {
|
||||||
warpify_settings.add_subshell_command(new_command, ctx);
|
wormhole_settings.add_subshell_command(new_command, ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
send_telemetry_from_ctx!(TelemetryEvent::AddAddedSubshellCommand, ctx);
|
send_telemetry_from_ctx!(TelemetryEvent::AddAddedSubshellCommand, ctx);
|
||||||
@@ -230,8 +227,8 @@ impl WarpifyPageView {
|
|||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
SubmittableTextInputEvent::Submit(new_command) => {
|
SubmittableTextInputEvent::Submit(new_command) => {
|
||||||
WarpifySettings::handle(ctx).update(ctx, |warpify_settings, ctx| {
|
WormholeSettings::handle(ctx).update(ctx, |wormhole_settings, ctx| {
|
||||||
warpify_settings.denylist_subshell_command(new_command, ctx);
|
wormhole_settings.denylist_subshell_command(new_command, ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
send_telemetry_from_ctx!(TelemetryEvent::AddDenylistedSubshellCommand, ctx);
|
send_telemetry_from_ctx!(TelemetryEvent::AddDenylistedSubshellCommand, ctx);
|
||||||
@@ -242,20 +239,20 @@ impl WarpifyPageView {
|
|||||||
|
|
||||||
fn remove_denylisted_command(&self, index: usize, ctx: &mut ViewContext<Self>) {
|
fn remove_denylisted_command(&self, index: usize, ctx: &mut ViewContext<Self>) {
|
||||||
send_telemetry_from_ctx!(TelemetryEvent::RemoveDenylistedSubshellCommand, ctx);
|
send_telemetry_from_ctx!(TelemetryEvent::RemoveDenylistedSubshellCommand, ctx);
|
||||||
WarpifySettings::handle(ctx).update(ctx, |warpify, ctx| {
|
WormholeSettings::handle(ctx).update(ctx, |wormhole, ctx| {
|
||||||
warpify.remove_denylisted_subshell_command(index, ctx)
|
wormhole.remove_denylisted_subshell_command(index, ctx)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_added_command(&self, index: usize, ctx: &mut ViewContext<Self>) {
|
fn remove_added_command(&self, index: usize, ctx: &mut ViewContext<Self>) {
|
||||||
send_telemetry_from_ctx!(TelemetryEvent::RemoveAddedSubshellCommand, ctx);
|
send_telemetry_from_ctx!(TelemetryEvent::RemoveAddedSubshellCommand, ctx);
|
||||||
WarpifySettings::handle(ctx).update(ctx, |warpify, ctx| {
|
WormholeSettings::handle(ctx).update(ctx, |wormhole, ctx| {
|
||||||
warpify.remove_added_subshell_command(index, ctx)
|
wormhole.remove_added_subshell_command(index, ctx)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for WarpifyPageView {
|
impl Entity for WormholePageView {
|
||||||
type Event = SettingsPageEvent;
|
type Event = SettingsPageEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,25 +269,23 @@ fn build_sub_sub_title(title: &str, appearance: &Appearance) -> Container {
|
|||||||
|
|
||||||
const SSH_EXTENSION_DROPDOWN_WIDTH: f32 = 250.;
|
const SSH_EXTENSION_DROPDOWN_WIDTH: f32 = 250.;
|
||||||
|
|
||||||
impl WarpifyPageView {
|
impl WormholePageView {
|
||||||
fn create_ssh_extension_install_mode_dropdown(
|
fn create_ssh_extension_install_mode_dropdown(
|
||||||
ctx: &mut ViewContext<Self>,
|
ctx: &mut ViewContext<Self>,
|
||||||
) -> ViewHandle<Dropdown<WarpifyPageAction>> {
|
) -> ViewHandle<Dropdown<WormholePageAction>> {
|
||||||
let items: Vec<DropdownItem<WarpifyPageAction>> = SshExtensionInstallMode::iter()
|
let items: Vec<DropdownItem<WormholePageAction>> = SshExtensionInstallMode::iter()
|
||||||
.map(|mode| {
|
.map(|mode| {
|
||||||
DropdownItem::new(
|
DropdownItem::new(
|
||||||
mode.display_name(),
|
mode.display_name(),
|
||||||
WarpifyPageAction::SetSshExtensionInstallMode(mode),
|
WormholePageAction::SetSshExtensionInstallMode(mode),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let current_mode = *WarpifySettings::as_ref(ctx)
|
let current_mode = *WormholeSettings::as_ref(ctx)
|
||||||
.ssh_extension_install_mode
|
.ssh_extension_install_mode
|
||||||
.value();
|
.value();
|
||||||
let enable_ssh_warpification = *WarpifySettings::as_ref(ctx)
|
let enable_ssh_wormholing = *WormholeSettings::as_ref(ctx).enable_ssh_wormholing.value();
|
||||||
.enable_ssh_warpification
|
|
||||||
.value();
|
|
||||||
|
|
||||||
ctx.add_typed_action_view(move |ctx| {
|
ctx.add_typed_action_view(move |ctx| {
|
||||||
let mut dropdown = Dropdown::new(ctx);
|
let mut dropdown = Dropdown::new(ctx);
|
||||||
@@ -298,10 +293,10 @@ impl WarpifyPageView {
|
|||||||
dropdown.set_menu_width(SSH_EXTENSION_DROPDOWN_WIDTH, ctx);
|
dropdown.set_menu_width(SSH_EXTENSION_DROPDOWN_WIDTH, ctx);
|
||||||
dropdown.add_items(items, ctx);
|
dropdown.add_items(items, ctx);
|
||||||
dropdown.set_selected_by_action(
|
dropdown.set_selected_by_action(
|
||||||
WarpifyPageAction::SetSshExtensionInstallMode(current_mode),
|
WormholePageAction::SetSshExtensionInstallMode(current_mode),
|
||||||
ctx,
|
ctx,
|
||||||
);
|
);
|
||||||
if !enable_ssh_warpification {
|
if !enable_ssh_wormholing {
|
||||||
dropdown.set_disabled(ctx);
|
dropdown.set_disabled(ctx);
|
||||||
}
|
}
|
||||||
dropdown
|
dropdown
|
||||||
@@ -352,9 +347,9 @@ impl WarpifyPageView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View for WarpifyPageView {
|
impl View for WormholePageView {
|
||||||
fn ui_name() -> &'static str {
|
fn ui_name() -> &'static str {
|
||||||
"WarpifyPageView"
|
"WormholePageView"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self, app: &AppContext) -> Box<dyn Element> {
|
fn render(&self, app: &AppContext) -> Box<dyn Element> {
|
||||||
@@ -363,41 +358,38 @@ impl View for WarpifyPageView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum WarpifyPageAction {
|
pub enum WormholePageAction {
|
||||||
RemoveAddedCommand(usize),
|
RemoveAddedCommand(usize),
|
||||||
RemoveDenylistedCommand(usize),
|
RemoveDenylistedCommand(usize),
|
||||||
ToggleSshWarpification,
|
ToggleSshWormholing,
|
||||||
/// Toggles whether the legacy SSH wrapper attaches to an existing
|
/// Toggles whether the legacy SSH wrapper attaches to an existing
|
||||||
/// ControlMaster for the destination host instead of creating its own.
|
/// ControlMaster for the destination host instead of creating its own.
|
||||||
ToggleReuseSshControlMaster,
|
ToggleReuseSshControlMaster,
|
||||||
/// Set the SSH extension installation mode (always ask / always install / always skip).
|
/// Set the SSH extension installation mode (always ask / always install / always skip).
|
||||||
SetSshExtensionInstallMode(SshExtensionInstallMode),
|
SetSshExtensionInstallMode(SshExtensionInstallMode),
|
||||||
OpenUrl(String),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypedActionView for WarpifyPageView {
|
impl TypedActionView for WormholePageView {
|
||||||
type Action = WarpifyPageAction;
|
type Action = WormholePageAction;
|
||||||
|
|
||||||
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
|
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
|
||||||
use WarpifyPageAction::*;
|
use WormholePageAction::*;
|
||||||
match action {
|
match action {
|
||||||
RemoveDenylistedCommand(index) => self.remove_denylisted_command(*index, ctx),
|
RemoveDenylistedCommand(index) => self.remove_denylisted_command(*index, ctx),
|
||||||
RemoveAddedCommand(index) => self.remove_added_command(*index, ctx),
|
RemoveAddedCommand(index) => self.remove_added_command(*index, ctx),
|
||||||
ToggleSshWarpification => {
|
ToggleSshWormholing => {
|
||||||
WarpifySettings::handle(ctx).update(ctx, |ssh_settings, ctx| {
|
WormholeSettings::handle(ctx).update(ctx, |ssh_settings, ctx| {
|
||||||
report_if_error!(ssh_settings
|
report_if_error!(ssh_settings
|
||||||
.enable_ssh_warpification
|
.enable_ssh_wormholing
|
||||||
.toggle_and_save_value(ctx));
|
.toggle_and_save_value(ctx));
|
||||||
send_telemetry_from_ctx!(
|
send_telemetry_from_ctx!(
|
||||||
TelemetryEvent::ToggleSshWarpification {
|
TelemetryEvent::ToggleSshWormholing {
|
||||||
enabled: *ssh_settings.enable_ssh_warpification.value(),
|
enabled: *ssh_settings.enable_ssh_wormholing.value(),
|
||||||
},
|
},
|
||||||
ctx
|
ctx
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
let enabled = *WarpifySettings::as_ref(ctx)
|
let enabled = *WormholeSettings::as_ref(ctx).enable_ssh_wormholing.value();
|
||||||
.enable_ssh_warpification
|
|
||||||
.value();
|
|
||||||
self.ssh_extension_install_mode_dropdown
|
self.ssh_extension_install_mode_dropdown
|
||||||
.update(ctx, |dropdown, ctx| {
|
.update(ctx, |dropdown, ctx| {
|
||||||
if enabled {
|
if enabled {
|
||||||
@@ -425,8 +417,8 @@ impl TypedActionView for WarpifyPageView {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
SetSshExtensionInstallMode(mode) => {
|
SetSshExtensionInstallMode(mode) => {
|
||||||
WarpifySettings::handle(ctx).update(ctx, |warpify_settings, ctx| {
|
WormholeSettings::handle(ctx).update(ctx, |wormhole_settings, ctx| {
|
||||||
report_if_error!(warpify_settings
|
report_if_error!(wormhole_settings
|
||||||
.ssh_extension_install_mode
|
.ssh_extension_install_mode
|
||||||
.set_value(*mode, ctx));
|
.set_value(*mode, ctx));
|
||||||
send_telemetry_from_ctx!(
|
send_telemetry_from_ctx!(
|
||||||
@@ -437,16 +429,13 @@ impl TypedActionView for WarpifyPageView {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
OpenUrl(url) => {
|
|
||||||
ctx.open_url(url.as_str());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SettingsPageMeta for WarpifyPageView {
|
impl SettingsPageMeta for WormholePageView {
|
||||||
fn section() -> SettingsSection {
|
fn section() -> SettingsSection {
|
||||||
SettingsSection::Warpify
|
SettingsSection::Wormhole
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_render(&self, _ctx: &AppContext) -> bool {
|
fn should_render(&self, _ctx: &AppContext) -> bool {
|
||||||
@@ -466,53 +455,39 @@ impl SettingsPageMeta for WarpifyPageView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ViewHandle<WarpifyPageView>> for SettingsPageViewHandle {
|
impl From<ViewHandle<WormholePageView>> for SettingsPageViewHandle {
|
||||||
fn from(view_handle: ViewHandle<WarpifyPageView>) -> Self {
|
fn from(view_handle: ViewHandle<WormholePageView>) -> Self {
|
||||||
SettingsPageViewHandle::Warpify(view_handle)
|
SettingsPageViewHandle::Wormhole(view_handle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct TitleWidget {
|
struct TitleWidget;
|
||||||
learn_more_highlight_index: HighlightedHyperlink,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TitleWidget {
|
impl TitleWidget {
|
||||||
fn render_top_of_page(&self, appearance: &Appearance, _app: &AppContext) -> Box<dyn Element> {
|
fn render_top_of_page(&self, appearance: &Appearance, _app: &AppContext) -> Box<dyn Element> {
|
||||||
let warpify_description = vec![
|
let wormhole_description = Text::new(
|
||||||
FormattedTextFragment::plain_text(
|
"Configure whether Galaxy attempts to \u{201c}Wormhole\u{201d} supported shells, adding blocks, full text editing, completions, and other Galaxy features."
|
||||||
"Configure whether Galaxy attempts to \u{201c}Wormhole\u{201d} (add support for blocks, \
|
.to_string(),
|
||||||
input modes, etc) certain shells. ",
|
appearance.ui_font_family(),
|
||||||
),
|
|
||||||
FormattedTextFragment::hyperlink(
|
|
||||||
"Learn more",
|
|
||||||
"https://docs.warp.dev/terminal/warpify/subshells",
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
let warpify_description = FormattedTextElement::new(
|
|
||||||
FormattedText::new([FormattedTextLine::Line(warpify_description)]),
|
|
||||||
CONTENT_FONT_SIZE,
|
CONTENT_FONT_SIZE,
|
||||||
appearance.ui_font_family(),
|
|
||||||
appearance.ui_font_family(),
|
|
||||||
blended_colors::text_sub(appearance.theme(), appearance.theme().surface_1()),
|
|
||||||
self.learn_more_highlight_index.clone(),
|
|
||||||
)
|
)
|
||||||
.with_hyperlink_font_color(appearance.theme().accent().into_solid())
|
.soft_wrap(true)
|
||||||
.register_default_click_handlers(|url, _, ctx| {
|
.with_color(blended_colors::text_sub(
|
||||||
ctx.open_url(&url.url);
|
appearance.theme(),
|
||||||
})
|
appearance.theme().surface_1(),
|
||||||
|
))
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
Flex::column()
|
Flex::column()
|
||||||
.with_child(render_page_title("Wormhole", HEADER_FONT_SIZE, appearance))
|
.with_child(render_page_title("Wormhole", HEADER_FONT_SIZE, appearance))
|
||||||
.with_child(warpify_description)
|
.with_child(wormhole_description)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SettingsWidget for TitleWidget {
|
impl SettingsWidget for TitleWidget {
|
||||||
type View = WarpifyPageView;
|
type View = WormholePageView;
|
||||||
|
|
||||||
fn search_terms(&self) -> &str {
|
fn search_terms(&self) -> &str {
|
||||||
"ssh subshell galaxify session"
|
"ssh subshell galaxify session"
|
||||||
@@ -536,20 +511,20 @@ struct SubshellsWidget {}
|
|||||||
impl SubshellsWidget {
|
impl SubshellsWidget {
|
||||||
fn render_subshells_section(
|
fn render_subshells_section(
|
||||||
&self,
|
&self,
|
||||||
view: &WarpifyPageView,
|
view: &WormholePageView,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
app: &AppContext,
|
app: &AppContext,
|
||||||
) -> Box<dyn Element> {
|
) -> Box<dyn Element> {
|
||||||
let mut column = Flex::column();
|
let mut column = Flex::column();
|
||||||
|
|
||||||
let warpify_settings = WarpifySettings::as_ref(app);
|
let wormhole_settings = WormholeSettings::as_ref(app);
|
||||||
|
|
||||||
column.add_child(
|
column.add_child(
|
||||||
view.build_input_list(
|
view.build_input_list(
|
||||||
"Added commands",
|
"Added commands",
|
||||||
&warpify_settings.added_subshell_commands,
|
&wormhole_settings.added_subshell_commands,
|
||||||
&view.remove_added_command_button_states,
|
&view.remove_added_command_button_states,
|
||||||
WarpifyPageAction::RemoveAddedCommand,
|
WormholePageAction::RemoveAddedCommand,
|
||||||
&view.add_added_commands_editor,
|
&view.add_added_commands_editor,
|
||||||
appearance,
|
appearance,
|
||||||
)
|
)
|
||||||
@@ -559,9 +534,9 @@ impl SubshellsWidget {
|
|||||||
column.add_child(
|
column.add_child(
|
||||||
view.build_input_list(
|
view.build_input_list(
|
||||||
"Denylisted commands",
|
"Denylisted commands",
|
||||||
&warpify_settings.subshell_command_denylist,
|
&wormhole_settings.subshell_command_denylist,
|
||||||
&view.remove_denylisted_command_button_states,
|
&view.remove_denylisted_command_button_states,
|
||||||
WarpifyPageAction::RemoveDenylistedCommand,
|
WormholePageAction::RemoveDenylistedCommand,
|
||||||
&view.add_denylisted_commands_editor,
|
&view.add_denylisted_commands_editor,
|
||||||
appearance,
|
appearance,
|
||||||
)
|
)
|
||||||
@@ -574,7 +549,7 @@ impl SubshellsWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SettingsWidget for SubshellsWidget {
|
impl SettingsWidget for SubshellsWidget {
|
||||||
type View = WarpifyPageView;
|
type View = WormholePageView;
|
||||||
|
|
||||||
fn search_terms(&self) -> &str {
|
fn search_terms(&self) -> &str {
|
||||||
"galaxify subshell"
|
"galaxify subshell"
|
||||||
@@ -594,13 +569,13 @@ impl SettingsWidget for SubshellsWidget {
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct SSHWidget {
|
struct SSHWidget {
|
||||||
enable_ssh_warpification_switch_state: SwitchStateHandle,
|
enable_ssh_wormholing_switch_state: SwitchStateHandle,
|
||||||
reuse_control_master_switch_state: SwitchStateHandle,
|
reuse_control_master_switch_state: SwitchStateHandle,
|
||||||
local_only_icon_tooltip_states: RefCell<HashMap<String, MouseStateHandle>>,
|
local_only_icon_tooltip_states: RefCell<HashMap<String, MouseStateHandle>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SettingsWidget for SSHWidget {
|
impl SettingsWidget for SSHWidget {
|
||||||
type View = WarpifyPageView;
|
type View = WormholePageView;
|
||||||
|
|
||||||
fn search_terms(&self) -> &str {
|
fn search_terms(&self) -> &str {
|
||||||
"galaxify ssh"
|
"galaxify ssh"
|
||||||
@@ -618,31 +593,29 @@ impl SettingsWidget for SSHWidget {
|
|||||||
.theme()
|
.theme()
|
||||||
.sub_text_color(appearance.theme().surface_2());
|
.sub_text_color(appearance.theme().surface_2());
|
||||||
|
|
||||||
let enable_ssh_warpification = *WarpifySettings::as_ref(app)
|
let enable_ssh_wormholing = *WormholeSettings::as_ref(app).enable_ssh_wormholing.value();
|
||||||
.enable_ssh_warpification
|
|
||||||
.value();
|
|
||||||
|
|
||||||
add_setting(
|
add_setting(
|
||||||
&mut column,
|
&mut column,
|
||||||
&WarpifySettings::as_ref(app).enable_ssh_warpification,
|
&WormholeSettings::as_ref(app).enable_ssh_wormholing,
|
||||||
move || {
|
move || {
|
||||||
render_body_item::<WarpifyPageAction>(
|
render_body_item::<WormholePageAction>(
|
||||||
"Wormhole SSH Sessions".into(),
|
"Wormhole SSH Sessions".into(),
|
||||||
None,
|
None,
|
||||||
LocalOnlyIconState::for_setting(
|
LocalOnlyIconState::for_setting(
|
||||||
EnableSshWarpification::storage_key(),
|
EnableSshWormholing::storage_key(),
|
||||||
EnableSshWarpification::sync_to_cloud(),
|
EnableSshWormholing::sync_to_cloud(),
|
||||||
&mut self.local_only_icon_tooltip_states.borrow_mut(),
|
&mut self.local_only_icon_tooltip_states.borrow_mut(),
|
||||||
app,
|
app,
|
||||||
),
|
),
|
||||||
ToggleState::Enabled,
|
ToggleState::Enabled,
|
||||||
appearance,
|
appearance,
|
||||||
ui_builder
|
ui_builder
|
||||||
.switch(self.enable_ssh_warpification_switch_state.clone())
|
.switch(self.enable_ssh_wormholing_switch_state.clone())
|
||||||
.check(enable_ssh_warpification)
|
.check(enable_ssh_wormholing)
|
||||||
.build()
|
.build()
|
||||||
.on_click(move |ctx, _, _| {
|
.on_click(move |ctx, _, _| {
|
||||||
ctx.dispatch_typed_action(WarpifyPageAction::ToggleSshWarpification);
|
ctx.dispatch_typed_action(WormholePageAction::ToggleSshWormholing);
|
||||||
})
|
})
|
||||||
.finish(),
|
.finish(),
|
||||||
None,
|
None,
|
||||||
@@ -651,18 +624,18 @@ impl SettingsWidget for SSHWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if FeatureFlag::SshRemoteServer.is_enabled() {
|
if FeatureFlag::SshRemoteServer.is_enabled() {
|
||||||
let label_color_override = if !enable_ssh_warpification {
|
let label_color_override = if !enable_ssh_wormholing {
|
||||||
Some(appearance.theme().disabled_ui_text_color())
|
Some(appearance.theme().disabled_ui_text_color())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
add_setting(
|
add_setting(
|
||||||
&mut column,
|
&mut column,
|
||||||
&WarpifySettings::as_ref(app).ssh_extension_install_mode,
|
&WormholeSettings::as_ref(app).ssh_extension_install_mode,
|
||||||
move || {
|
move || {
|
||||||
Container::new(render_dropdown_item(
|
Container::new(render_dropdown_item(
|
||||||
appearance,
|
appearance,
|
||||||
"Install SSH extension",
|
"Install Wormhole helper",
|
||||||
Some(SSH_EXTENSION_INSTALL_MODE_DESCRIPTION),
|
Some(SSH_EXTENSION_INSTALL_MODE_DESCRIPTION),
|
||||||
None,
|
None,
|
||||||
LocalOnlyIconState::for_setting(
|
LocalOnlyIconState::for_setting(
|
||||||
@@ -688,7 +661,7 @@ impl SettingsWidget for SSHWidget {
|
|||||||
&SshSettings::as_ref(app).reuse_existing_control_master,
|
&SshSettings::as_ref(app).reuse_existing_control_master,
|
||||||
move || {
|
move || {
|
||||||
let mut column = Flex::column();
|
let mut column = Flex::column();
|
||||||
column.add_child(render_body_item::<WarpifyPageAction>(
|
column.add_child(render_body_item::<WormholePageAction>(
|
||||||
"Reuse existing SSH ControlMaster".into(),
|
"Reuse existing SSH ControlMaster".into(),
|
||||||
None,
|
None,
|
||||||
LocalOnlyIconState::for_setting(
|
LocalOnlyIconState::for_setting(
|
||||||
@@ -697,19 +670,19 @@ impl SettingsWidget for SSHWidget {
|
|||||||
&mut self.local_only_icon_tooltip_states.borrow_mut(),
|
&mut self.local_only_icon_tooltip_states.borrow_mut(),
|
||||||
app,
|
app,
|
||||||
),
|
),
|
||||||
enable_ssh_warpification.into(),
|
enable_ssh_wormholing.into(),
|
||||||
appearance,
|
appearance,
|
||||||
ui_builder
|
ui_builder
|
||||||
.switch(self.reuse_control_master_switch_state.clone())
|
.switch(self.reuse_control_master_switch_state.clone())
|
||||||
.check(reuse_existing_control_master)
|
.check(reuse_existing_control_master)
|
||||||
.with_disabled(!enable_ssh_warpification)
|
.with_disabled(!enable_ssh_wormholing)
|
||||||
.build()
|
.build()
|
||||||
.on_click(move |ctx, _, _| {
|
.on_click(move |ctx, _, _| {
|
||||||
if !enable_ssh_warpification {
|
if !enable_ssh_wormholing {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ctx.dispatch_typed_action(
|
ctx.dispatch_typed_action(
|
||||||
WarpifyPageAction::ToggleReuseSshControlMaster,
|
WormholePageAction::ToggleReuseSshControlMaster,
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.finish(),
|
.finish(),
|
||||||
@@ -58,7 +58,7 @@ use super::view::{
|
|||||||
BlocklistAIRenderContext, InlineBannerId, RichContentMetadata, SeparatorId,
|
BlocklistAIRenderContext, InlineBannerId, RichContentMetadata, SeparatorId,
|
||||||
SharedSessionBanners, TerminalEditor, TerminalViewRenderContext, BLOCK_BANNER_HEIGHT,
|
SharedSessionBanners, TerminalEditor, TerminalViewRenderContext, BLOCK_BANNER_HEIGHT,
|
||||||
};
|
};
|
||||||
use super::warpify::render::{draw_flag_pole, render_subshell_flag};
|
use super::wormhole::render::{draw_flag_pole, render_subshell_flag};
|
||||||
use super::{heights_approx_eq, TerminalModel, HEIGHT_FUDGE_FACTOR_LINES};
|
use super::{heights_approx_eq, TerminalModel, HEIGHT_FUDGE_FACTOR_LINES};
|
||||||
use crate::ai::blocklist::agent_view::{agent_view_bg_fill, AgentViewState};
|
use crate::ai::blocklist::agent_view::{agent_view_bg_fill, AgentViewState};
|
||||||
use crate::ai::blocklist::{ai_brand_color, ATTACH_AS_AGENT_MODE_CONTEXT_TEXT};
|
use crate::ai::blocklist::{ai_brand_color, ATTACH_AS_AGENT_MODE_CONTEXT_TEXT};
|
||||||
@@ -86,7 +86,7 @@ use crate::terminal::model::selection::{SelectAction, SelectionPoint};
|
|||||||
use crate::terminal::model::terminal_model::BlockIndex;
|
use crate::terminal::model::terminal_model::BlockIndex;
|
||||||
use crate::terminal::safe_mode_settings::get_secret_obfuscation_mode;
|
use crate::terminal::safe_mode_settings::get_secret_obfuscation_mode;
|
||||||
use crate::terminal::view::TerminalAction;
|
use crate::terminal::view::TerminalAction;
|
||||||
use crate::terminal::warpify::SubshellSource;
|
use crate::terminal::wormhole::SubshellSource;
|
||||||
use crate::terminal::{grid_renderer, SizeInfo};
|
use crate::terminal::{grid_renderer, SizeInfo};
|
||||||
use crate::themes::theme::{Fill, WarpTheme};
|
use crate::themes::theme::{Fill, WarpTheme};
|
||||||
use crate::ui_components::{self, icons as UIIcon};
|
use crate::ui_components::{self, icons as UIIcon};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use rand::Rng;
|
|||||||
#[cfg(feature = "local_fs")]
|
#[cfg(feature = "local_fs")]
|
||||||
use super::{
|
use super::{
|
||||||
model::session::{BootstrapSessionType, SessionInfo},
|
model::session::{BootstrapSessionType, SessionInfo},
|
||||||
warpify::settings::{PIPENV_SUBSHELL_COMMAND_REGEX, POETRY_SUBSHELL_COMMAND_REGEX},
|
wormhole::settings::{PIPENV_SUBSHELL_COMMAND_REGEX, POETRY_SUBSHELL_COMMAND_REGEX},
|
||||||
};
|
};
|
||||||
use crate::env_vars::{EnvVar, EnvVarExt};
|
use crate::env_vars::{EnvVar, EnvVarExt};
|
||||||
use crate::terminal::session_settings::SessionSettings;
|
use crate::terminal::session_settings::SessionSettings;
|
||||||
@@ -99,7 +99,7 @@ pub fn should_use_rc_file_bootstrap_method(
|
|||||||
&& shell_type == ShellType::Zsh)
|
&& shell_type == ShellType::Zsh)
|
||||||
|| is_msys2
|
|| is_msys2
|
||||||
}
|
}
|
||||||
BootstrapSessionType::WarpifiedRemote => false,
|
BootstrapSessionType::WormholedRemote => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ pub struct CLIAgentEvent {
|
|||||||
const VERSIONED_PARSERS: &[EventParser] = &[v1::parse];
|
const VERSIONED_PARSERS: &[EventParser] = &[v1::parse];
|
||||||
|
|
||||||
/// The current CLI agent protocol version this build of Warp supports.
|
/// The current CLI agent protocol version this build of Warp supports.
|
||||||
/// Exported as the `WARP_CLI_AGENT_PROTOCOL_VERSION` env var on the PTY
|
/// Exported as the `GALAXY_CLI_AGENT_PROTOCOL_VERSION` env var on the PTY
|
||||||
/// so plugins can negotiate a compatible payload format.
|
/// so plugins can negotiate a compatible payload format.
|
||||||
#[cfg_attr(not(feature = "local_tty"), allow(dead_code))]
|
#[cfg_attr(not(feature = "local_tty"), allow(dead_code))]
|
||||||
pub const fn current_protocol_version() -> u32 {
|
pub const fn current_protocol_version() -> u32 {
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ pub struct CLIAgentSession {
|
|||||||
/// `None` if the plugin predates version reporting or Codex is using OSC9 fallback.
|
/// `None` if the plugin predates version reporting or Codex is using OSC9 fallback.
|
||||||
pub plugin_version: Option<String>,
|
pub plugin_version: Option<String>,
|
||||||
/// `None` when the session is local.
|
/// `None` when the session is local.
|
||||||
/// `Some("user@hostname")` when running over SSH (warpified or legacy).
|
/// `Some("user@hostname")` when running over SSH (wormholed or legacy).
|
||||||
/// Used as a key for per-host plugin install failure tracking.
|
/// Used as a key for per-host plugin install failure tracking.
|
||||||
pub remote_host: Option<String>,
|
pub remote_host: Option<String>,
|
||||||
/// Draft text saved from the rich input composer when it was closed.
|
/// Draft text saved from the rich input composer when it was closed.
|
||||||
|
|||||||
@@ -167,9 +167,9 @@ pub enum TerminalMode {
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum SshLoginStatus {
|
pub enum SshLoginStatus {
|
||||||
/// We have some evidence login is complete but should check again.
|
/// We have some evidence login is complete but should check again.
|
||||||
RecheckBeforeWarpifying,
|
RecheckBeforeWormholing,
|
||||||
/// We have high confidence login is complete.
|
/// We have high confidence login is complete.
|
||||||
ReadyToWarpify,
|
ReadyToWormhole,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -247,7 +247,7 @@ pub enum BlockType {
|
|||||||
/// This is a block containing background process output.
|
/// This is a block containing background process output.
|
||||||
Background(Arc<SerializedBlock>),
|
Background(Arc<SerializedBlock>),
|
||||||
|
|
||||||
/// This is a block containing static/hardcoded content (e.g. the subshell Warpification
|
/// This is a block containing static/hardcoded content (e.g. the subshell Wormholing
|
||||||
/// welcome block).
|
/// welcome block).
|
||||||
Static,
|
Static,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -505,7 +505,7 @@ fn test_multiple_machines() {
|
|||||||
SessionInfo::new_for_test()
|
SessionInfo::new_for_test()
|
||||||
.with_id(0)
|
.with_id(0)
|
||||||
.with_shell_type(ShellType::Zsh)
|
.with_shell_type(ShellType::Zsh)
|
||||||
.with_session_type(BootstrapSessionType::WarpifiedRemote)
|
.with_session_type(BootstrapSessionType::WormholedRemote)
|
||||||
.with_hostname("prod".to_string())
|
.with_hostname("prod".to_string())
|
||||||
.with_user("user".to_string())
|
.with_user("user".to_string())
|
||||||
.with_ssh_socket_path(PathBuf::from("~/.ssh/12345"))
|
.with_ssh_socket_path(PathBuf::from("~/.ssh/12345"))
|
||||||
@@ -517,7 +517,7 @@ fn test_multiple_machines() {
|
|||||||
SessionInfo::new_for_test()
|
SessionInfo::new_for_test()
|
||||||
.with_id(1)
|
.with_id(1)
|
||||||
.with_shell_type(ShellType::Zsh)
|
.with_shell_type(ShellType::Zsh)
|
||||||
.with_session_type(BootstrapSessionType::WarpifiedRemote)
|
.with_session_type(BootstrapSessionType::WormholedRemote)
|
||||||
.with_hostname("dev".to_string())
|
.with_hostname("dev".to_string())
|
||||||
.with_user("user2".to_string())
|
.with_user("user2".to_string())
|
||||||
.with_ssh_socket_path(PathBuf::from("~/.ssh/12345"))
|
.with_ssh_socket_path(PathBuf::from("~/.ssh/12345"))
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ use super::view::queued_prompts_panel::{QueuedPromptsPanelEvent, QueuedPromptsPa
|
|||||||
use super::view::{
|
use super::view::{
|
||||||
ExecuteCommandEvent, SyncInputType, TerminalAction, PADDING_LEFT as TERMINAL_VIEW_PADDING_LEFT,
|
ExecuteCommandEvent, SyncInputType, TerminalAction, PADDING_LEFT as TERMINAL_VIEW_PADDING_LEFT,
|
||||||
};
|
};
|
||||||
use super::warpify::SubshellSource;
|
use super::wormhole::SubshellSource;
|
||||||
use super::{prompt, History, HistoryEntry, SizeInfo, TerminalModel, UpArrowHistoryConfig};
|
use super::{prompt, History, HistoryEntry, SizeInfo, TerminalModel, UpArrowHistoryConfig};
|
||||||
use crate::ai::agent::conversation::AIConversationId;
|
use crate::ai::agent::conversation::AIConversationId;
|
||||||
use crate::ai::agent::{
|
use crate::ai::agent::{
|
||||||
@@ -11829,13 +11829,13 @@ impl Input {
|
|||||||
|
|
||||||
// CLI agent rich input in shell mode (! prefix) should allow completions
|
// CLI agent rich input in shell mode (! prefix) should allow completions
|
||||||
// even though the active block is a long-running command.
|
// even though the active block is a long-running command.
|
||||||
// However, completions are disabled on warpified remote hosts because
|
// However, completions are disabled on wormholed remote hosts because
|
||||||
// in-band generators don't work in this context (with CLI agent).
|
// in-band generators don't work in this context (with CLI agent).
|
||||||
let is_cli_agent_shell_mode = self.is_locked_in_shell_mode(ctx)
|
let is_cli_agent_shell_mode = self.is_locked_in_shell_mode(ctx)
|
||||||
&& CLIAgentSessionsModel::as_ref(ctx).is_input_open(self.terminal_view_id)
|
&& CLIAgentSessionsModel::as_ref(ctx).is_input_open(self.terminal_view_id)
|
||||||
&& !self
|
&& !self
|
||||||
.active_session(ctx)
|
.active_session(ctx)
|
||||||
.is_some_and(|s| matches!(s.session_type(), SessionType::WarpifiedRemote { .. }));
|
.is_some_and(|s| matches!(s.session_type(), SessionType::WormholedRemote { .. }));
|
||||||
|
|
||||||
// If the cursor is in a valid completion position, go into CompletionSuggestions mode
|
// If the cursor is in a valid completion position, go into CompletionSuggestions mode
|
||||||
if (is_command_grid_active || is_cli_agent_shell_mode) && self.can_query_history(ctx) {
|
if (is_command_grid_active || is_cli_agent_shell_mode) && self.can_query_history(ctx) {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ use crate::terminal::input::common::{
|
|||||||
use crate::terminal::input::{get_input_box_top_border_width, InputDropTargetData};
|
use crate::terminal::input::{get_input_box_top_border_width, InputDropTargetData};
|
||||||
use crate::terminal::settings::{SpacingMode, TerminalSettings};
|
use crate::terminal::settings::{SpacingMode, TerminalSettings};
|
||||||
use crate::terminal::view::TerminalAction;
|
use crate::terminal::view::TerminalAction;
|
||||||
use crate::terminal::warpify::render::{render_subshell_flag, render_subshell_flag_pole};
|
use crate::terminal::wormhole::render::{render_subshell_flag, render_subshell_flag_pole};
|
||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
/// Renders the classic input. This is used when the user has 'Honor PS1' enabled in settings,
|
/// Renders the classic input. This is used when the user has 'Honor PS1' enabled in settings,
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ pub struct LineEditorStatus {
|
|||||||
///
|
///
|
||||||
/// When receiving an end prompt marker in zsh, this is used as a proxy to determine if the
|
/// When receiving an end prompt marker in zsh, this is used as a proxy to determine if the
|
||||||
/// session is bootstrapped -- the prompt markers are emitted by zsh regardless of whether or
|
/// session is bootstrapped -- the prompt markers are emitted by zsh regardless of whether or
|
||||||
/// not its a Warpified session, so to in order properly signal downstream that the line editor
|
/// not its a Wormholed session, so to in order properly signal downstream that the line editor
|
||||||
/// (for Warpified sessions) is active, we must check if there was a corresponding precmd
|
/// (for Wormholed sessions) is active, we must check if there was a corresponding precmd
|
||||||
/// emitted prior to the end prompt marker.
|
/// emitted prior to the end prompt marker.
|
||||||
///
|
///
|
||||||
/// Precmd is always emitted before prompt markers.
|
/// Precmd is always emitted before prompt markers.
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ use crate::terminal::session_settings::{SessionSettings, ToolbarChipSelection};
|
|||||||
use crate::terminal::shared_session::sharer::network::Network;
|
use crate::terminal::shared_session::sharer::network::Network;
|
||||||
use crate::terminal::shared_session::{IsSharedSessionCreator, SharedSessionStatus};
|
use crate::terminal::shared_session::{IsSharedSessionCreator, SharedSessionStatus};
|
||||||
use crate::terminal::shell::ShellName;
|
use crate::terminal::shell::ShellName;
|
||||||
use crate::terminal::warpify::settings::WarpifySettings;
|
use crate::terminal::wormhole::settings::WormholeSettings;
|
||||||
use crate::terminal::writeable_pty::pty_controller::{EventLoopSendError, EventLoopSender};
|
use crate::terminal::writeable_pty::pty_controller::{EventLoopSendError, EventLoopSender};
|
||||||
use crate::terminal::writeable_pty::terminal_manager_util::{
|
use crate::terminal::writeable_pty::terminal_manager_util::{
|
||||||
init_pty_controller_model, init_remote_server_controller, wire_up_pty_controller_with_surface,
|
init_pty_controller_model, init_remote_server_controller, wire_up_pty_controller_with_surface,
|
||||||
@@ -740,13 +740,11 @@ impl<S> TerminalManager<S> {
|
|||||||
.contains(&ContextChipKind::NodeVersion)
|
.contains(&ContextChipKind::NodeVersion)
|
||||||
};
|
};
|
||||||
|
|
||||||
// `enable_ssh_warpification` is the single source of truth for whether the SSH
|
// `enable_ssh_wormholing` is the single source of truth for whether the SSH
|
||||||
// wrapper is active. The bootstrap scripts check `WARP_USE_SSH_WRAPPER` (derived
|
// wrapper is active. The bootstrap scripts check `GALAXY_USE_SSH_WRAPPER` (derived
|
||||||
// from this value) before invoking `warp_ssh_helper`, which spawns the ControlMaster
|
// from this value) before invoking `warp_ssh_helper`, which spawns the ControlMaster
|
||||||
// and opens agent-protocol channels.
|
// and opens agent-protocol channels.
|
||||||
let enable_ssh_wrapper = *WarpifySettings::as_ref(ctx)
|
let enable_ssh_wrapper = *WormholeSettings::as_ref(ctx).enable_ssh_wormholing.value();
|
||||||
.enable_ssh_warpification
|
|
||||||
.value();
|
|
||||||
|
|
||||||
// Only meaningful when the legacy ControlMaster wrapper is active.
|
// Only meaningful when the legacy ControlMaster wrapper is active.
|
||||||
let reuse_ssh_control_master = enable_ssh_wrapper
|
let reuse_ssh_control_master = enable_ssh_wrapper
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ fn build_host_shell_command(
|
|||||||
// Whether the SSH wrapper should attach to an existing ControlMaster
|
// Whether the SSH wrapper should attach to an existing ControlMaster
|
||||||
// for the destination host instead of always creating its own.
|
// for the destination host instead of always creating its own.
|
||||||
builder.env(
|
builder.env(
|
||||||
"WARP_SSH_REUSE_CONTROL_MASTER",
|
"GALAXY_SSH_REUSE_CONTROL_MASTER",
|
||||||
if reuse_ssh_control_master { "1" } else { "0" },
|
if reuse_ssh_control_master { "1" } else { "0" },
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -784,8 +784,8 @@ fn build_docker_sandbox_command(
|
|||||||
// TODO(advait): audit this list. It currently mirrors what the
|
// TODO(advait): audit this list. It currently mirrors what the
|
||||||
// pre-refactor host-shell `spawn` set when the starter happened to
|
// pre-refactor host-shell `spawn` set when the starter happened to
|
||||||
// be a Docker sandbox, so behaviour is unchanged from before the
|
// be a Docker sandbox, so behaviour is unchanged from before the
|
||||||
// split. Many of these (e.g. `WARP_USE_SSH_WRAPPER`,
|
// split. Many of these (e.g. `GALAXY_USE_SSH_WRAPPER`,
|
||||||
// `SSH_SOCKET_DIR`, `HISTFILESIZE`, `WARP_IS_LOCAL_SHELL_SESSION`)
|
// `SSH_SOCKET_DIR`, `HISTFILESIZE`, `GALAXY_IS_LOCAL_SHELL_SESSION`)
|
||||||
// are set on the *host* `sbx` process and may or may not propagate
|
// are set on the *host* `sbx` process and may or may not propagate
|
||||||
// into the container depending on `sbx`'s env passthrough rules.
|
// into the container depending on `sbx`'s env passthrough rules.
|
||||||
// Once we've validated what the container bootstrap actually needs,
|
// Once we've validated what the container bootstrap actually needs,
|
||||||
@@ -813,7 +813,7 @@ fn build_docker_sandbox_command(
|
|||||||
if enable_ssh_wrapper { "1" } else { "0" },
|
if enable_ssh_wrapper { "1" } else { "0" },
|
||||||
);
|
);
|
||||||
builder.env(
|
builder.env(
|
||||||
"WARP_SSH_REUSE_CONTROL_MASTER",
|
"GALAXY_SSH_REUSE_CONTROL_MASTER",
|
||||||
if reuse_ssh_control_master { "1" } else { "0" },
|
if reuse_ssh_control_master { "1" } else { "0" },
|
||||||
);
|
);
|
||||||
builder.env("SSH_SOCKET_DIR", ssh_socket_dir());
|
builder.env("SSH_SOCKET_DIR", ssh_socket_dir());
|
||||||
|
|||||||
@@ -18,15 +18,15 @@ use crate::terminal::local_tty::PtyOptions;
|
|||||||
const HONOR_PS1_NAME: &str = "WARP_HONOR_PS1";
|
const HONOR_PS1_NAME: &str = "WARP_HONOR_PS1";
|
||||||
const PROMPT_NODE_VERSION_ENABLED_NAME: &str = "WARP_PROMPT_NODE_VERSION_ENABLED";
|
const PROMPT_NODE_VERSION_ENABLED_NAME: &str = "WARP_PROMPT_NODE_VERSION_ENABLED";
|
||||||
const INITIAL_WORKING_DIR_NAME: &str = "WARP_INITIAL_WORKING_DIR";
|
const INITIAL_WORKING_DIR_NAME: &str = "WARP_INITIAL_WORKING_DIR";
|
||||||
const USE_SSH_WRAPPER_NAME: &str = "WARP_USE_SSH_WRAPPER";
|
const USE_SSH_WRAPPER_NAME: &str = "GALAXY_USE_SSH_WRAPPER";
|
||||||
const SSH_REUSE_CONTROL_MASTER_NAME: &str = "WARP_SSH_REUSE_CONTROL_MASTER";
|
const SSH_REUSE_CONTROL_MASTER_NAME: &str = "GALAXY_SSH_REUSE_CONTROL_MASTER";
|
||||||
const SHELL_DEBUG_MODE_NAME: &str = "WARP_SHELL_DEBUG_MODE";
|
const SHELL_DEBUG_MODE_NAME: &str = "WARP_SHELL_DEBUG_MODE";
|
||||||
const TERM_PROGRAM_NAME: &str = "TERM_PROGRAM";
|
const TERM_PROGRAM_NAME: &str = "TERM_PROGRAM";
|
||||||
const IS_LOCAL_SESSION_NAME: &str = "WARP_IS_LOCAL_SHELL_SESSION";
|
const IS_LOCAL_SESSION_NAME: &str = "GALAXY_IS_LOCAL_SHELL_SESSION";
|
||||||
const SSH_SOCKET_DIR: &str = "SSH_SOCKET_DIR";
|
const SSH_SOCKET_DIR: &str = "SSH_SOCKET_DIR";
|
||||||
const PATH_APPEND_NAME: &str = "WARP_PATH_APPEND";
|
const PATH_APPEND_NAME: &str = "WARP_PATH_APPEND";
|
||||||
const CLIENT_VERSION_NAME: &str = "WARP_CLIENT_VERSION";
|
const CLIENT_VERSION_NAME: &str = "GALAXY_CLIENT_VERSION";
|
||||||
const CLI_AGENT_PROTOCOL_VERSION_NAME: &str = "WARP_CLI_AGENT_PROTOCOL_VERSION";
|
const CLI_AGENT_PROTOCOL_VERSION_NAME: &str = "GALAXY_CLI_AGENT_PROTOCOL_VERSION";
|
||||||
const WSLENV: &str = "WSLENV";
|
const WSLENV: &str = "WSLENV";
|
||||||
const HISTIGNORE: &str = "HISTIGNORE";
|
const HISTIGNORE: &str = "HISTIGNORE";
|
||||||
|
|
||||||
|
|||||||
@@ -79,8 +79,8 @@ pub mod ssh;
|
|||||||
pub mod terminal_manager;
|
pub mod terminal_manager;
|
||||||
mod terminal_size_element;
|
mod terminal_size_element;
|
||||||
pub mod view;
|
pub mod view;
|
||||||
pub mod warpify;
|
|
||||||
mod waterfall_gap_element;
|
mod waterfall_gap_element;
|
||||||
|
pub mod wormhole;
|
||||||
mod writeable_pty;
|
mod writeable_pty;
|
||||||
#[cfg(feature = "tui")]
|
#[cfg(feature = "tui")]
|
||||||
pub use writeable_pty::{PtyIntent, PtyIntentEvent, TerminalSurface};
|
pub use writeable_pty::{PtyIntent, PtyIntentEvent, TerminalSurface};
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ use crate::terminal::model::block::BlockSection;
|
|||||||
use crate::terminal::model::index::{Direction, Point, Side};
|
use crate::terminal::model::index::{Direction, Point, Side};
|
||||||
use crate::terminal::model::selection::{ExpandedSelectionRange, Selection, SelectionDirection};
|
use crate::terminal::model::selection::{ExpandedSelectionRange, Selection, SelectionDirection};
|
||||||
use crate::terminal::model::terminal_model::{BlockIndex, WithinBlock};
|
use crate::terminal::model::terminal_model::{BlockIndex, WithinBlock};
|
||||||
use crate::terminal::warpify::success_block::WarpifySuccessBlock;
|
use crate::terminal::wormhole::success_block::WormholeSuccessBlock;
|
||||||
use crate::terminal::GridType;
|
use crate::terminal::GridType;
|
||||||
|
|
||||||
/// A selection that can span multiple blocks (and thus grids). Here row is the number of lines from
|
/// A selection that can span multiple blocks (and thus grids). Here row is the number of lines from
|
||||||
@@ -998,12 +998,13 @@ impl BlockList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(active_window_id) = app.windows().active_window() {
|
if let Some(active_window_id) = app.windows().active_window() {
|
||||||
if let Some(ssh_block) = app
|
if let Some(ssh_block) = app.view_with_id::<WormholeSuccessBlock>(
|
||||||
.view_with_id::<WarpifySuccessBlock>(active_window_id, *view_id)
|
active_window_id,
|
||||||
{
|
*view_id,
|
||||||
let warpify_success_block = app.view(&ssh_block);
|
) {
|
||||||
|
let wormhole_success_block = app.view(&ssh_block);
|
||||||
if let Some(selected_text) =
|
if let Some(selected_text) =
|
||||||
warpify_success_block.selected_text()
|
wormhole_success_block.selected_text()
|
||||||
{
|
{
|
||||||
selected_texts.push(selected_text);
|
selected_texts.push(selected_text);
|
||||||
}
|
}
|
||||||
@@ -1123,10 +1124,10 @@ impl BlockList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ssh_block) =
|
if let Some(ssh_block) =
|
||||||
app.view_with_id::<WarpifySuccessBlock>(active_window_id, view_id)
|
app.view_with_id::<WormholeSuccessBlock>(active_window_id, view_id)
|
||||||
{
|
{
|
||||||
let warpify_success_block = app.view(&ssh_block);
|
let wormhole_success_block = app.view(&ssh_block);
|
||||||
if let Some(selected_text) = warpify_success_block.selected_text() {
|
if let Some(selected_text) = wormhole_success_block.selected_text() {
|
||||||
selected_texts.push(selected_text);
|
selected_texts.push(selected_text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
pub enum RichContentType {
|
pub enum RichContentType {
|
||||||
AIBlock,
|
AIBlock,
|
||||||
EnterAgentView,
|
EnterAgentView,
|
||||||
WarpifySuccessBlock,
|
WormholeSuccessBlock,
|
||||||
InlineAgentViewHeader,
|
InlineAgentViewHeader,
|
||||||
AgentViewZeroState,
|
AgentViewZeroState,
|
||||||
TerminalViewZeroState,
|
TerminalViewZeroState,
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ use crate::remote_server::manager::{RemoteServerManager, RemoteServerManagerEven
|
|||||||
use crate::server::telemetry::{BootstrappingInfo, TelemetryEvent};
|
use crate::server::telemetry::{BootstrappingInfo, TelemetryEvent};
|
||||||
use crate::terminal::event::{ExecutedExecutorCommandEvent, RemoteServerSetupState};
|
use crate::terminal::event::{ExecutedExecutorCommandEvent, RemoteServerSetupState};
|
||||||
use crate::terminal::shell::{Shell, ShellType};
|
use crate::terminal::shell::{Shell, ShellType};
|
||||||
use crate::terminal::warpify::SubshellSource;
|
use crate::terminal::wormhole::SubshellSource;
|
||||||
use crate::terminal::{History, ShellHost, ShellLaunchData};
|
use crate::terminal::{History, ShellHost, ShellLaunchData};
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
@@ -361,7 +361,7 @@ impl Sessions {
|
|||||||
let session = Arc::new(session);
|
let session = Arc::new(session);
|
||||||
self.sessions.insert(session.id(), session.clone());
|
self.sessions.insert(session.id(), session.clone());
|
||||||
|
|
||||||
// For warpified-remote sessions, pick up the current host_id from
|
// For wormholed-remote sessions, pick up the current host_id from
|
||||||
// the manager so session.remote_host_id() is populated without
|
// the manager so session.remote_host_id() is populated without
|
||||||
// waiting for the next SessionConnected event. The
|
// waiting for the next SessionConnected event. The
|
||||||
// RemoteServerCommandExecutor already has its client baked in, so
|
// RemoteServerCommandExecutor already has its client baked in, so
|
||||||
@@ -370,7 +370,7 @@ impl Sessions {
|
|||||||
if FeatureFlag::SshRemoteServer.is_enabled()
|
if FeatureFlag::SshRemoteServer.is_enabled()
|
||||||
&& matches!(
|
&& matches!(
|
||||||
session_info.session_type,
|
session_info.session_type,
|
||||||
BootstrapSessionType::WarpifiedRemote
|
BootstrapSessionType::WormholedRemote
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if let Some(host_id) = RemoteServerManager::as_ref(ctx).host_id_for_session(session_id)
|
if let Some(host_id) = RemoteServerManager::as_ref(ctx).host_id_for_session(session_id)
|
||||||
@@ -518,7 +518,7 @@ impl Sessions {
|
|||||||
impl From<SessionType> for command_corrections::SessionType {
|
impl From<SessionType> for command_corrections::SessionType {
|
||||||
fn from(session_type: SessionType) -> Self {
|
fn from(session_type: SessionType) -> Self {
|
||||||
match session_type {
|
match session_type {
|
||||||
SessionType::WarpifiedRemote { .. } => command_corrections::SessionType::Remote,
|
SessionType::WormholedRemote { .. } => command_corrections::SessionType::Remote,
|
||||||
SessionType::Local => command_corrections::SessionType::Local,
|
SessionType::Local => command_corrections::SessionType::Local,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -527,20 +527,20 @@ impl From<SessionType> for command_corrections::SessionType {
|
|||||||
impl From<&SessionType> for command_corrections::SessionType {
|
impl From<&SessionType> for command_corrections::SessionType {
|
||||||
fn from(session_type: &SessionType) -> Self {
|
fn from(session_type: &SessionType) -> Self {
|
||||||
match session_type {
|
match session_type {
|
||||||
SessionType::WarpifiedRemote { .. } => command_corrections::SessionType::Remote,
|
SessionType::WormholedRemote { .. } => command_corrections::SessionType::Remote,
|
||||||
SessionType::Local => command_corrections::SessionType::Local,
|
SessionType::Local => command_corrections::SessionType::Local,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether a session was established by Warp's in-band SSH wrapper — the shell function our
|
/// Whether a session was established by Galaxy's in-band SSH wrapper — the shell function our
|
||||||
/// bootstrap injects that intercepts `ssh`, sets up a ControlMaster connection, and bootstraps
|
/// bootstrap injects that intercepts `ssh`, sets up a ControlMaster connection, and bootstraps
|
||||||
/// the remote shell. This applies to all SSH warpification today: the remote-server SSH
|
/// the remote shell. This applies to all SSH wormholing today: the remote-server SSH
|
||||||
/// extension also runs on top of a wrapper session (reusing the ControlMaster socket for its
|
/// extension also runs on top of a wrapper session (reusing the ControlMaster socket for its
|
||||||
/// proxy and for the `RemoteCommandExecutor` fallback).
|
/// proxy and for the `RemoteCommandExecutor` fallback).
|
||||||
///
|
///
|
||||||
/// `No` covers local sessions, subshells, and remote sessions warpified *without* the wrapper
|
/// `No` covers local sessions, subshells, and remote sessions wormholed *without* the wrapper
|
||||||
/// (e.g. via the auto-warpify RC snippet inside an unwrapped `ssh` session), which carry no
|
/// (e.g. via the auto-wormhole RC snippet inside an unwrapped `ssh` session), which carry no
|
||||||
/// ControlMaster socket.
|
/// ControlMaster socket.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum IsSSHWrapperSession {
|
pub enum IsSSHWrapperSession {
|
||||||
@@ -550,7 +550,7 @@ pub enum IsSSHWrapperSession {
|
|||||||
socket_path: PathBuf,
|
socket_path: PathBuf,
|
||||||
/// `true` when `socket_path` points at a ControlMaster the user
|
/// `true` when `socket_path` points at a ControlMaster the user
|
||||||
/// already had running (the SSH wrapper attached to it instead of
|
/// already had running (the SSH wrapper attached to it instead of
|
||||||
/// creating a Warp-owned one). Warp must not tear down such a
|
/// creating a Galaxy-owned one). Galaxy must not tear down such a
|
||||||
/// master on session exit.
|
/// master on session exit.
|
||||||
external_control_master: bool,
|
external_control_master: bool,
|
||||||
},
|
},
|
||||||
@@ -651,7 +651,7 @@ impl SessionInfo {
|
|||||||
matches!(&is_ssh_wrapper_session, IsSSHWrapperSession::Yes { .. }),
|
matches!(&is_ssh_wrapper_session, IsSSHWrapperSession::Yes { .. }),
|
||||||
);
|
);
|
||||||
|
|
||||||
let spawning_session_id = if matches!(session_type, BootstrapSessionType::WarpifiedRemote)
|
let spawning_session_id = if matches!(session_type, BootstrapSessionType::WormholedRemote)
|
||||||
|| subshell_info.is_some()
|
|| subshell_info.is_some()
|
||||||
{
|
{
|
||||||
active_block_session_id
|
active_block_session_id
|
||||||
@@ -699,7 +699,7 @@ impl SessionInfo {
|
|||||||
{
|
{
|
||||||
BootstrapSessionType::Local
|
BootstrapSessionType::Local
|
||||||
} else {
|
} else {
|
||||||
BootstrapSessionType::WarpifiedRemote
|
BootstrapSessionType::WormholedRemote
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -715,7 +715,7 @@ impl SessionInfo {
|
|||||||
_is_ssh_session: bool,
|
_is_ssh_session: bool,
|
||||||
) -> BootstrapSessionType {
|
) -> BootstrapSessionType {
|
||||||
// When the `remote_tty` feature is enabled--the session is always considered remote.
|
// When the `remote_tty` feature is enabled--the session is always considered remote.
|
||||||
BootstrapSessionType::WarpifiedRemote
|
BootstrapSessionType::WormholedRemote
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a fully populated [`SessionInfo`] containing data derived from the given
|
/// Returns a fully populated [`SessionInfo`] containing data derived from the given
|
||||||
@@ -859,26 +859,26 @@ impl SessionInfo {
|
|||||||
/// which happens *after* the session is bootstrapped.
|
/// which happens *after* the session is bootstrapped.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum BootstrapSessionType {
|
pub enum BootstrapSessionType {
|
||||||
/// The session host is the same host where Warp is running.
|
/// The session host is the same host where Galaxy is running.
|
||||||
Local,
|
Local,
|
||||||
|
|
||||||
/// The session host is a different host from where Warp is running.
|
/// The session host is a different host from where Galaxy is running.
|
||||||
WarpifiedRemote,
|
WormholedRemote,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum SessionType {
|
pub enum SessionType {
|
||||||
/// The session host is the same host where Warp is running.
|
/// The session host is the same host where Galaxy is running.
|
||||||
Local,
|
Local,
|
||||||
|
|
||||||
/// The session host is a different host from where Warp is running.
|
/// The session host is a different host from where Galaxy is running.
|
||||||
/// Note that we only know this for sure when we Warpify a block.
|
/// Note that we only know this for sure when we Wormhole a block.
|
||||||
///
|
///
|
||||||
/// `host_id` is `Some` when the remote server feature flag is enabled and
|
/// `host_id` is `Some` when the remote server feature flag is enabled and
|
||||||
/// `RemoteServerManager` has completed the connection handshake. It is
|
/// `RemoteServerManager` has completed the connection handshake. It is
|
||||||
/// `None` when the feature flag is off or the connection hasn't been
|
/// `None` when the feature flag is off or the connection hasn't been
|
||||||
/// established yet.
|
/// established yet.
|
||||||
WarpifiedRemote {
|
WormholedRemote {
|
||||||
host_id: Option<galaxy_core::HostId>,
|
host_id: Option<galaxy_core::HostId>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -887,7 +887,7 @@ impl From<BootstrapSessionType> for SessionType {
|
|||||||
fn from(bst: BootstrapSessionType) -> Self {
|
fn from(bst: BootstrapSessionType) -> Self {
|
||||||
match bst {
|
match bst {
|
||||||
BootstrapSessionType::Local => SessionType::Local,
|
BootstrapSessionType::Local => SessionType::Local,
|
||||||
BootstrapSessionType::WarpifiedRemote => SessionType::WarpifiedRemote { host_id: None },
|
BootstrapSessionType::WormholedRemote => SessionType::WormholedRemote { host_id: None },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -964,11 +964,11 @@ impl Session {
|
|||||||
self.session_type.lock().clone()
|
self.session_type.lock().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the `host_id` on a `WarpifiedRemote` session type after the
|
/// Updates the `host_id` on a `WormholedRemote` session type after the
|
||||||
/// remote server handshake completes (or clears it on disconnect).
|
/// remote server handshake completes (or clears it on disconnect).
|
||||||
pub fn set_remote_host_id(&self, host_id: Option<galaxy_core::HostId>) {
|
pub fn set_remote_host_id(&self, host_id: Option<galaxy_core::HostId>) {
|
||||||
let mut st = self.session_type.lock();
|
let mut st = self.session_type.lock();
|
||||||
if let SessionType::WarpifiedRemote { host_id: ref mut h } = *st {
|
if let SessionType::WormholedRemote { host_id: ref mut h } = *st {
|
||||||
*h = host_id;
|
*h = host_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1012,9 +1012,9 @@ impl Session {
|
|||||||
self.info.host_info.clone()
|
self.info.host_info.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether this session was established by Warp's in-band SSH wrapper (see
|
/// Returns whether this session was established by Galaxy's in-band SSH wrapper (see
|
||||||
/// [`IsSSHWrapperSession`]). Note this stays `false` for remote sessions warpified via
|
/// [`IsSSHWrapperSession`]). Note this stays `false` for remote sessions wormholed via
|
||||||
/// the auto-warpify RC snippet inside an unwrapped `ssh` session.
|
/// the auto-wormhole RC snippet inside an unwrapped `ssh` session.
|
||||||
pub fn is_ssh_wrapper_session(&self) -> bool {
|
pub fn is_ssh_wrapper_session(&self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
self.info.is_ssh_wrapper_session,
|
self.info.is_ssh_wrapper_session,
|
||||||
@@ -1023,7 +1023,7 @@ impl Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_subshell_or_ssh(&self) -> bool {
|
pub fn is_subshell_or_ssh(&self) -> bool {
|
||||||
matches!(self.session_type(), SessionType::WarpifiedRemote { .. })
|
matches!(self.session_type(), SessionType::WormholedRemote { .. })
|
||||||
|| self.is_ssh_wrapper_session()
|
|| self.is_ssh_wrapper_session()
|
||||||
|| self.subshell_info().is_some()
|
|| self.subshell_info().is_some()
|
||||||
}
|
}
|
||||||
@@ -1539,7 +1539,7 @@ impl Session {
|
|||||||
self.read_history_for_local_session(is_kaspersky_running)
|
self.read_history_for_local_session(is_kaspersky_running)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
BootstrapSessionType::WarpifiedRemote => self.read_history_for_remote_session().await,
|
BootstrapSessionType::WormholedRemote => self.read_history_for_remote_session().await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1635,22 +1635,22 @@ impl Session {
|
|||||||
/// Converts the given directory into a [`typed_path::TypedPathBuf`].
|
/// Converts the given directory into a [`typed_path::TypedPathBuf`].
|
||||||
pub fn convert_directory_to_typed_path_buf(&self, pwd: String) -> TypedPathBuf {
|
pub fn convert_directory_to_typed_path_buf(&self, pwd: String) -> TypedPathBuf {
|
||||||
// We need to determine whether this session requires windows file paths
|
// We need to determine whether this session requires windows file paths
|
||||||
// or unix file paths. This needs to be resilient to warpified ssh. Some examples:
|
// or unix file paths. This needs to be resilient to wormholed ssh. Some examples:
|
||||||
// - bash on mac ---> unix
|
// - bash on mac ---> unix
|
||||||
// - powershell on linux ---> unix
|
// - powershell on linux ---> unix
|
||||||
// - powershell on windows ---> windows
|
// - powershell on windows ---> windows
|
||||||
// - wsl on windows ---> unix
|
// - wsl on windows ---> unix
|
||||||
// - warpified zsh --> unix
|
// - wormholed zsh --> unix
|
||||||
|
|
||||||
// If the host architecture is unix, we can infer unix file paths. This would break
|
// If the host architecture is unix, we can infer unix file paths. This would break
|
||||||
// if we supported warpifying a powershell-on-windows SSH session.
|
// if we supported wormholing a powershell-on-windows SSH session.
|
||||||
if cfg!(unix) {
|
if cfg!(unix) {
|
||||||
return TypedPathBuf::from_unix(pwd);
|
return TypedPathBuf::from_unix(pwd);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We assume that we're on Windows.
|
// We assume that we're on Windows.
|
||||||
match self.shell_family() {
|
match self.shell_family() {
|
||||||
// Cases: WSL, MSYS2, warpified bash
|
// Cases: WSL, MSYS2, wormholed bash
|
||||||
ShellFamily::Posix => TypedPathBuf::from_unix(pwd),
|
ShellFamily::Posix => TypedPathBuf::from_unix(pwd),
|
||||||
// Cases: powershell sessions
|
// Cases: powershell sessions
|
||||||
ShellFamily::PowerShell => TypedPathBuf::from_windows(pwd),
|
ShellFamily::PowerShell => TypedPathBuf::from_windows(pwd),
|
||||||
@@ -1671,7 +1671,7 @@ impl Display for Session {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the hostname for the local machine where Warp is running.
|
/// Returns the hostname for the local machine where Galaxy is running.
|
||||||
pub fn get_local_hostname() -> Result<String> {
|
pub fn get_local_hostname() -> Result<String> {
|
||||||
cfg_if::cfg_if! {
|
cfg_if::cfg_if! {
|
||||||
if #[cfg(not(target_family = "wasm"))] {
|
if #[cfg(not(target_family = "wasm"))] {
|
||||||
@@ -1786,7 +1786,7 @@ pub mod testing {
|
|||||||
|
|
||||||
pub fn with_ssh_socket_path(mut self, socket_path: PathBuf) -> Self {
|
pub fn with_ssh_socket_path(mut self, socket_path: PathBuf) -> Self {
|
||||||
if let BootstrapSessionType::Local = self.session_type {
|
if let BootstrapSessionType::Local = self.session_type {
|
||||||
self.session_type = BootstrapSessionType::WarpifiedRemote;
|
self.session_type = BootstrapSessionType::WormholedRemote;
|
||||||
}
|
}
|
||||||
self.is_ssh_wrapper_session = IsSSHWrapperSession::Yes {
|
self.is_ssh_wrapper_session = IsSSHWrapperSession::Yes {
|
||||||
socket_path,
|
socket_path,
|
||||||
@@ -1856,7 +1856,7 @@ pub mod testing {
|
|||||||
|
|
||||||
pub fn test_remote() -> Self {
|
pub fn test_remote() -> Self {
|
||||||
let info = SessionInfo::new_for_test()
|
let info = SessionInfo::new_for_test()
|
||||||
.with_session_type(BootstrapSessionType::WarpifiedRemote)
|
.with_session_type(BootstrapSessionType::WormholedRemote)
|
||||||
.with_shell_type(ShellType::Bash); // We only support UNIX-based remote sessions.
|
.with_shell_type(ShellType::Bash); // We only support UNIX-based remote sessions.
|
||||||
let session_type = SessionType::from(info.session_type.clone());
|
let session_type = SessionType::from(info.session_type.clone());
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@@ -100,12 +100,12 @@ impl ActiveSession {
|
|||||||
/// the connected host ID.
|
/// the connected host ID.
|
||||||
pub fn location_for_path(&self, path: &str, app: &AppContext) -> Option<LocalOrRemotePath> {
|
pub fn location_for_path(&self, path: &str, app: &AppContext) -> Option<LocalOrRemotePath> {
|
||||||
match self.session_type(app) {
|
match self.session_type(app) {
|
||||||
Some(SessionType::WarpifiedRemote {
|
Some(SessionType::WormholedRemote {
|
||||||
host_id: Some(host_id),
|
host_id: Some(host_id),
|
||||||
}) => StandardizedPath::try_new(path)
|
}) => StandardizedPath::try_new(path)
|
||||||
.ok()
|
.ok()
|
||||||
.map(|path| LocalOrRemotePath::Remote(RemotePath::new(host_id, path))),
|
.map(|path| LocalOrRemotePath::Remote(RemotePath::new(host_id, path))),
|
||||||
Some(SessionType::WarpifiedRemote { host_id: None }) => None,
|
Some(SessionType::WormholedRemote { host_id: None }) => None,
|
||||||
Some(SessionType::Local) | None => {
|
Some(SessionType::Local) | None => {
|
||||||
let path =
|
let path =
|
||||||
dunce::canonicalize(Path::new(path)).unwrap_or_else(|_| PathBuf::from(path));
|
dunce::canonicalize(Path::new(path)).unwrap_or_else(|_| PathBuf::from(path));
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ fn new_command_executor_for_local_tty_session(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BootstrapSessionType::WarpifiedRemote
|
BootstrapSessionType::WormholedRemote
|
||||||
if is_ssh_wrapper_session
|
if is_ssh_wrapper_session
|
||||||
&& !FeatureFlag::InBandGeneratorsForSSH.is_enabled()
|
&& !FeatureFlag::InBandGeneratorsForSSH.is_enabled()
|
||||||
&& !force_use_in_band_generators =>
|
&& !force_use_in_band_generators =>
|
||||||
|
|||||||
@@ -114,11 +114,11 @@ fn test_malicious_histfile_path_does_not_execute_injected_commands() {
|
|||||||
let malicious_histfile = format!("/tmp/x'; touch {marker}; echo '");
|
let malicious_histfile = format!("/tmp/x'; touch {marker}; echo '");
|
||||||
|
|
||||||
let session_info = SessionInfo::new_for_test()
|
let session_info = SessionInfo::new_for_test()
|
||||||
.with_session_type(BootstrapSessionType::WarpifiedRemote)
|
.with_session_type(BootstrapSessionType::WormholedRemote)
|
||||||
.with_histfile(Some(malicious_histfile));
|
.with_histfile(Some(malicious_histfile));
|
||||||
let session = Session::new(session_info, Arc::new(TestCommandExecutor::default()));
|
let session = Session::new(session_info, Arc::new(TestCommandExecutor::default()));
|
||||||
|
|
||||||
// read_history for a WarpifiedRemote session calls read_history_from_file,
|
// read_history for a WormholedRemote session calls read_history_from_file,
|
||||||
// which builds `cat '{escaped_path}'` and executes it via TestCommandExecutor
|
// which builds `cat '{escaped_path}'` and executes it via TestCommandExecutor
|
||||||
let _ = session.read_history(false).await;
|
let _ = session.read_history(false).await;
|
||||||
|
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ enum IsReceivingHook {
|
|||||||
No,
|
No,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Information needed to render a warpify "success" block upon successful subshell bootstrap.
|
/// Information needed to render a wormhole "success" block upon successful subshell bootstrap.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SubshellSuccessBlockInfo {
|
pub struct SubshellSuccessBlockInfo {
|
||||||
/// The ID of the newly bootstrapped subshell session.
|
/// The ID of the newly bootstrapped subshell session.
|
||||||
@@ -2256,7 +2256,7 @@ impl TerminalModel {
|
|||||||
/// a line of output that is not a known SSH output, we consider that to be some mild evidence that
|
/// a line of output that is not a known SSH output, we consider that to be some mild evidence that
|
||||||
/// login is complete. Though, because that output line might be a false alarm (i.e., it could be
|
/// login is complete. Though, because that output line might be a false alarm (i.e., it could be
|
||||||
/// an SSH banner OR a line like "Permission denied."), we wait some amount of time and check again
|
/// an SSH banner OR a line like "Permission denied."), we wait some amount of time and check again
|
||||||
/// before indicating we're ready for warpification.
|
/// before indicating we're ready for wormholing.
|
||||||
pub fn check_for_end_of_ssh_login(&mut self, confirmation_check: bool) {
|
pub fn check_for_end_of_ssh_login(&mut self, confirmation_check: bool) {
|
||||||
let Some(mut ssh_login_state) = self.notify_on_end_of_ssh_login.clone() else {
|
let Some(mut ssh_login_state) = self.notify_on_end_of_ssh_login.clone() else {
|
||||||
return;
|
return;
|
||||||
@@ -2279,7 +2279,7 @@ impl TerminalModel {
|
|||||||
SshLoginState::LastLogin | SshLoginState::PromptDetected => {
|
SshLoginState::LastLogin | SshLoginState::PromptDetected => {
|
||||||
self.event_proxy
|
self.event_proxy
|
||||||
.send_terminal_event(Event::DetectedEndOfSshLogin(
|
.send_terminal_event(Event::DetectedEndOfSshLogin(
|
||||||
SshLoginStatus::ReadyToWarpify,
|
SshLoginStatus::ReadyToWormhole,
|
||||||
));
|
));
|
||||||
|
|
||||||
ssh_login_state.notification_state = SshLoginNotificationState::Completed;
|
ssh_login_state.notification_state = SshLoginNotificationState::Completed;
|
||||||
@@ -2290,7 +2290,7 @@ impl TerminalModel {
|
|||||||
if ssh_login_state.notification_state == SshLoginNotificationState::Monitoring {
|
if ssh_login_state.notification_state == SshLoginNotificationState::Monitoring {
|
||||||
self.event_proxy
|
self.event_proxy
|
||||||
.send_terminal_event(Event::DetectedEndOfSshLogin(
|
.send_terminal_event(Event::DetectedEndOfSshLogin(
|
||||||
SshLoginStatus::RecheckBeforeWarpifying,
|
SshLoginStatus::RecheckBeforeWormholing,
|
||||||
));
|
));
|
||||||
|
|
||||||
// We want to avoid emitting redundant events for the initial check.
|
// We want to avoid emitting redundant events for the initial check.
|
||||||
@@ -2300,7 +2300,7 @@ impl TerminalModel {
|
|||||||
} else {
|
} else {
|
||||||
self.event_proxy
|
self.event_proxy
|
||||||
.send_terminal_event(Event::DetectedEndOfSshLogin(
|
.send_terminal_event(Event::DetectedEndOfSshLogin(
|
||||||
SshLoginStatus::ReadyToWarpify,
|
SshLoginStatus::ReadyToWormhole,
|
||||||
));
|
));
|
||||||
|
|
||||||
ssh_login_state.notification_state = SshLoginNotificationState::Completed;
|
ssh_login_state.notification_state = SshLoginNotificationState::Completed;
|
||||||
|
|||||||
@@ -381,7 +381,7 @@ pub enum ModelEvent {
|
|||||||
ExecutedInBandCommand(ExecutedExecutorCommandEvent),
|
ExecutedInBandCommand(ExecutedExecutorCommandEvent),
|
||||||
/// Sent when a line of output from an interactive ssh session indicates login is complete.
|
/// Sent when a line of output from an interactive ssh session indicates login is complete.
|
||||||
/// A line such as "Last login: Wed Oct 30" for example indicates login is complete. This is
|
/// A line such as "Last login: Wed Oct 30" for example indicates login is complete. This is
|
||||||
/// useful for detecting when an ssh session becomes ready for warpification.
|
/// useful for detecting when an ssh session becomes ready for wormholing.
|
||||||
DetectedEndOfSshLogin(SshLoginStatus),
|
DetectedEndOfSshLogin(SshLoginStatus),
|
||||||
InitSubshell(InitSubshellEvent),
|
InitSubshell(InitSubshellEvent),
|
||||||
/// Emitted when the user's RC file has been executed in a subshell.
|
/// Emitted when the user's RC file has been executed in a subshell.
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ pub fn user_and_host_name_string(
|
|||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
match session_type {
|
match session_type {
|
||||||
SessionType::Local => None,
|
SessionType::Local => None,
|
||||||
SessionType::WarpifiedRemote { .. } => Some(format!("{user}@{hostname}:")),
|
SessionType::WormholedRemote { .. } => Some(format!("{user}@{hostname}:")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -251,13 +251,11 @@ impl PromptRenderHelper {
|
|||||||
RemoteServerSetupState::Checking => "Starting shell...".to_string(),
|
RemoteServerSetupState::Checking => "Starting shell...".to_string(),
|
||||||
RemoteServerSetupState::Installing {
|
RemoteServerSetupState::Installing {
|
||||||
progress_percent: Some(p),
|
progress_percent: Some(p),
|
||||||
} => format!("Installing Warp SSH Extension... ({p}%)"),
|
} => format!("Installing Wormhole helper... ({p}%)"),
|
||||||
RemoteServerSetupState::Installing {
|
RemoteServerSetupState::Installing {
|
||||||
progress_percent: None,
|
progress_percent: None,
|
||||||
} => "Installing Warp SSH Extension...".to_string(),
|
} => "Installing Wormhole helper...".to_string(),
|
||||||
RemoteServerSetupState::Updating => {
|
RemoteServerSetupState::Updating => "Updating Wormhole helper...".to_string(),
|
||||||
"Updating Warp SSH Extension...".to_string()
|
|
||||||
}
|
|
||||||
RemoteServerSetupState::Initializing => "Initializing...".to_string(),
|
RemoteServerSetupState::Initializing => "Initializing...".to_string(),
|
||||||
RemoteServerSetupState::Ready => "Starting shell...".to_string(),
|
RemoteServerSetupState::Ready => "Starting shell...".to_string(),
|
||||||
// Failed and Unsupported both fall back to the wrapper-only SSH
|
// Failed and Unsupported both fall back to the wrapper-only SSH
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use crate::appearance::Appearance;
|
use crate::appearance::Appearance;
|
||||||
use crate::terminal::model::ansi::WarpificationUnavailableReason;
|
use crate::terminal::model::ansi::WormholingUnavailableReason;
|
||||||
use crate::terminal::warpify;
|
use crate::terminal::wormhole;
|
||||||
use crate::terminal::warpify::render::apply_spacing_styles;
|
use crate::terminal::wormhole::render::apply_spacing_styles;
|
||||||
use crate::terminal::warpify::render::build_description_row;
|
use crate::terminal::wormhole::render::build_description_row;
|
||||||
use crate::terminal::warpify::settings::WarpifySettings;
|
use crate::terminal::wormhole::settings::WormholeSettings;
|
||||||
use crate::ui_components::icons::Icon as UiIcon;
|
use crate::ui_components::icons::Icon as UiIcon;
|
||||||
use galaxy_core::channel::ChannelState;
|
use galaxy_core::channel::ChannelState;
|
||||||
use galaxy_core::ui::theme::GalaxyTheme;
|
use galaxy_core::ui::theme::GalaxyTheme;
|
||||||
@@ -35,7 +35,7 @@ const UNSUPPORTED_TMUX_VERSION_ERROR: &str =
|
|||||||
"The tmux version available on the remote machine is below 3.0. Please install tmux 3.0 or greater using a different method and try again.";
|
"The tmux version available on the remote machine is below 3.0. Please install tmux 3.0 or greater using a different method and try again.";
|
||||||
const TMUX_FAILED_ERROR: &str =
|
const TMUX_FAILED_ERROR: &str =
|
||||||
"tmux failed to execute on the remote machine. Please re-install tmux and try again.";
|
"tmux failed to execute on the remote machine. Please re-install tmux and try again.";
|
||||||
const WARPIFY_TIMEOUT_ERROR: &str = "Wormholing the session hit a timeout.";
|
const WORMHOLE_TIMEOUT_ERROR: &str = "Wormholing the session hit a timeout.";
|
||||||
const UNSUPPORTED_SHELL_ERROR: &str =
|
const UNSUPPORTED_SHELL_ERROR: &str =
|
||||||
"Unsupported shell. Please set bash, zsh, or fish as your default shell and try again.";
|
"Unsupported shell. Please set bash, zsh, or fish as your default shell and try again.";
|
||||||
const TMUX_INSTALL_FAILED_ERROR: &str =
|
const TMUX_INSTALL_FAILED_ERROR: &str =
|
||||||
@@ -55,28 +55,28 @@ fn get_ssh_github_issue_url(title: &str) -> String {
|
|||||||
format!("{url}&title={title}")
|
format!("{url}&title={title}")
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WarpificationUnavailableReason {
|
impl WormholingUnavailableReason {
|
||||||
fn error_message(&self) -> &'static str {
|
fn error_message(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
WarpificationUnavailableReason::TmuxNotInstalled { .. } => TMUX_NOT_INSTALLED_ERROR,
|
WormholingUnavailableReason::TmuxNotInstalled { .. } => TMUX_NOT_INSTALLED_ERROR,
|
||||||
WarpificationUnavailableReason::UnsupportedTmuxVersion { .. } => {
|
WormholingUnavailableReason::UnsupportedTmuxVersion { .. } => {
|
||||||
UNSUPPORTED_TMUX_VERSION_ERROR
|
UNSUPPORTED_TMUX_VERSION_ERROR
|
||||||
}
|
}
|
||||||
WarpificationUnavailableReason::TmuxFailed => TMUX_FAILED_ERROR,
|
WormholingUnavailableReason::TmuxFailed => TMUX_FAILED_ERROR,
|
||||||
WarpificationUnavailableReason::Timeout { .. } => WARPIFY_TIMEOUT_ERROR,
|
WormholingUnavailableReason::Timeout { .. } => WORMHOLE_TIMEOUT_ERROR,
|
||||||
WarpificationUnavailableReason::UnsupportedShell { .. } => UNSUPPORTED_SHELL_ERROR,
|
WormholingUnavailableReason::UnsupportedShell { .. } => UNSUPPORTED_SHELL_ERROR,
|
||||||
WarpificationUnavailableReason::TmuxInstallFailed { .. } => TMUX_INSTALL_FAILED_ERROR,
|
WormholingUnavailableReason::TmuxInstallFailed { .. } => TMUX_INSTALL_FAILED_ERROR,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn error_title(&self) -> &'static str {
|
fn error_title(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
WarpificationUnavailableReason::TmuxNotInstalled { .. } => "tmux Not Installed",
|
WormholingUnavailableReason::TmuxNotInstalled { .. } => "tmux Not Installed",
|
||||||
WarpificationUnavailableReason::UnsupportedTmuxVersion { .. } => {
|
WormholingUnavailableReason::UnsupportedTmuxVersion { .. } => {
|
||||||
"Unsupported Tmux Version"
|
"Unsupported Tmux Version"
|
||||||
}
|
}
|
||||||
WarpificationUnavailableReason::TmuxFailed => "tmux Failed",
|
WormholingUnavailableReason::TmuxFailed => "tmux Failed",
|
||||||
WarpificationUnavailableReason::Timeout {
|
WormholingUnavailableReason::Timeout {
|
||||||
is_tmux_install, ..
|
is_tmux_install, ..
|
||||||
} => {
|
} => {
|
||||||
if *is_tmux_install {
|
if *is_tmux_install {
|
||||||
@@ -85,34 +85,34 @@ impl WarpificationUnavailableReason {
|
|||||||
"SSH Wormhole Timeout"
|
"SSH Wormhole Timeout"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WarpificationUnavailableReason::UnsupportedShell { .. } => "Unsupported Shell",
|
WormholingUnavailableReason::UnsupportedShell { .. } => "Unsupported Shell",
|
||||||
WarpificationUnavailableReason::TmuxInstallFailed { .. } => "tmux Install Failed",
|
WormholingUnavailableReason::TmuxInstallFailed { .. } => "tmux Install Failed",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum SshErrorBlockEvent {
|
pub enum SshErrorBlockEvent {
|
||||||
ContinueWithoutWarpification,
|
ContinueWithoutWormholing,
|
||||||
WarpifyWithoutTmux,
|
WormholeWithoutTmux,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum SshErrorBlockAction {
|
pub enum SshErrorBlockAction {
|
||||||
ContinueWithoutWarpification,
|
ContinueWithoutWormholing,
|
||||||
WarpifyWithoutTmux,
|
WormholeWithoutTmux,
|
||||||
OpenUrl(String),
|
OpenUrl(String),
|
||||||
AddSshHostToDenylist(String),
|
AddSshHostToDenylist(String),
|
||||||
Focus,
|
Focus,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SshErrorBlock {
|
pub struct SshErrorBlock {
|
||||||
error_reason: WarpificationUnavailableReason,
|
error_reason: WormholingUnavailableReason,
|
||||||
ssh_host: Option<String>,
|
ssh_host: Option<String>,
|
||||||
warpify_without_tmux_button_mouse_state: MouseStateHandle,
|
wormhole_without_tmux_button_mouse_state: MouseStateHandle,
|
||||||
continue_button_mouse_state: MouseStateHandle,
|
continue_button_mouse_state: MouseStateHandle,
|
||||||
report_link_highlight_index: HighlightedHyperlink,
|
report_link_highlight_index: HighlightedHyperlink,
|
||||||
never_warpify_mouse_state_handle: MouseStateHandle,
|
never_wormhole_mouse_state_handle: MouseStateHandle,
|
||||||
block_mouse_state: MouseStateHandle,
|
block_mouse_state: MouseStateHandle,
|
||||||
is_focused: bool,
|
is_focused: bool,
|
||||||
}
|
}
|
||||||
@@ -123,17 +123,17 @@ pub fn init(app: &mut AppContext) {
|
|||||||
app.register_fixed_bindings([
|
app.register_fixed_bindings([
|
||||||
FixedBinding::new(
|
FixedBinding::new(
|
||||||
"enter",
|
"enter",
|
||||||
SshErrorBlockAction::WarpifyWithoutTmux,
|
SshErrorBlockAction::WormholeWithoutTmux,
|
||||||
id!(SshErrorBlock::ui_name()),
|
id!(SshErrorBlock::ui_name()),
|
||||||
),
|
),
|
||||||
FixedBinding::new(
|
FixedBinding::new(
|
||||||
"escape",
|
"escape",
|
||||||
SshErrorBlockAction::ContinueWithoutWarpification,
|
SshErrorBlockAction::ContinueWithoutWormholing,
|
||||||
id!(SshErrorBlock::ui_name()),
|
id!(SshErrorBlock::ui_name()),
|
||||||
),
|
),
|
||||||
FixedBinding::new(
|
FixedBinding::new(
|
||||||
"ctrl-c",
|
"ctrl-c",
|
||||||
SshErrorBlockAction::ContinueWithoutWarpification,
|
SshErrorBlockAction::ContinueWithoutWormholing,
|
||||||
id!(SshErrorBlock::ui_name()),
|
id!(SshErrorBlock::ui_name()),
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
@@ -141,14 +141,14 @@ pub fn init(app: &mut AppContext) {
|
|||||||
|
|
||||||
impl SshErrorBlock {
|
impl SshErrorBlock {
|
||||||
#[allow(clippy::new_without_default)]
|
#[allow(clippy::new_without_default)]
|
||||||
pub fn new(error_reason: WarpificationUnavailableReason, ssh_host: Option<String>) -> Self {
|
pub fn new(error_reason: WormholingUnavailableReason, ssh_host: Option<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
error_reason,
|
error_reason,
|
||||||
ssh_host,
|
ssh_host,
|
||||||
warpify_without_tmux_button_mouse_state: Default::default(),
|
wormhole_without_tmux_button_mouse_state: Default::default(),
|
||||||
continue_button_mouse_state: Default::default(),
|
continue_button_mouse_state: Default::default(),
|
||||||
report_link_highlight_index: Default::default(),
|
report_link_highlight_index: Default::default(),
|
||||||
never_warpify_mouse_state_handle: Default::default(),
|
never_wormhole_mouse_state_handle: Default::default(),
|
||||||
block_mouse_state: Default::default(),
|
block_mouse_state: Default::default(),
|
||||||
is_focused: false,
|
is_focused: false,
|
||||||
}
|
}
|
||||||
@@ -162,8 +162,8 @@ impl SshErrorBlock {
|
|||||||
fn should_show_report_to_warp_button(&self) -> bool {
|
fn should_show_report_to_warp_button(&self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
self.error_reason,
|
self.error_reason,
|
||||||
WarpificationUnavailableReason::Timeout { .. }
|
WormholingUnavailableReason::Timeout { .. }
|
||||||
| WarpificationUnavailableReason::TmuxInstallFailed { .. }
|
| WormholingUnavailableReason::TmuxInstallFailed { .. }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +173,7 @@ impl SshErrorBlock {
|
|||||||
theme: &GalaxyTheme,
|
theme: &GalaxyTheme,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
) -> Box<dyn Element> {
|
) -> Box<dyn Element> {
|
||||||
let header_contents = warpify::render::build_header_row(
|
let header_contents = wormhole::render::build_header_row(
|
||||||
"Error Wormholing session",
|
"Error Wormholing session",
|
||||||
Icon::new(UiIcon::AlertTriangle.into(), theme.ui_error_color()),
|
Icon::new(UiIcon::AlertTriangle.into(), theme.ui_error_color()),
|
||||||
theme,
|
theme,
|
||||||
@@ -182,11 +182,11 @@ impl SshErrorBlock {
|
|||||||
.with_margin_right(8.)
|
.with_margin_right(8.)
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
let right_hand_size = warpify::render::render_never_warpify_ssh_link(
|
let right_hand_size = wormhole::render::render_never_wormhole_ssh_link(
|
||||||
&self.ssh_host,
|
&self.ssh_host,
|
||||||
app,
|
app,
|
||||||
appearance,
|
appearance,
|
||||||
self.never_warpify_mouse_state_handle.clone(),
|
self.never_wormhole_mouse_state_handle.clone(),
|
||||||
move |ctx, ssh_host| {
|
move |ctx, ssh_host| {
|
||||||
ctx.dispatch_typed_action(SshErrorBlockAction::AddSshHostToDenylist(
|
ctx.dispatch_typed_action(SshErrorBlockAction::AddSshHostToDenylist(
|
||||||
ssh_host.to_owned(),
|
ssh_host.to_owned(),
|
||||||
@@ -204,7 +204,7 @@ impl SshErrorBlock {
|
|||||||
row.add_child(right_hand_size);
|
row.add_child(right_hand_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
warpify::render::apply_spacing_styles(Container::new(row.finish())).finish()
|
wormhole::render::apply_spacing_styles(Container::new(row.finish())).finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,7 +227,7 @@ impl View for SshErrorBlock {
|
|||||||
|
|
||||||
content.add_child(self.render_title_ui(app, theme, appearance));
|
content.add_child(self.render_title_ui(app, theme, appearance));
|
||||||
|
|
||||||
content.add_child(warpify::render::description_row(
|
content.add_child(wormhole::render::description_row(
|
||||||
self.error_reason.error_message(),
|
self.error_reason.error_message(),
|
||||||
theme,
|
theme,
|
||||||
appearance,
|
appearance,
|
||||||
@@ -256,7 +256,7 @@ impl View for SshErrorBlock {
|
|||||||
ui_builder
|
ui_builder
|
||||||
.button(
|
.button(
|
||||||
ButtonVariant::Accent,
|
ButtonVariant::Accent,
|
||||||
self.warpify_without_tmux_button_mouse_state.clone(),
|
self.wormhole_without_tmux_button_mouse_state.clone(),
|
||||||
)
|
)
|
||||||
.with_centered_text_label("Wormhole without TMUX".into())
|
.with_centered_text_label("Wormhole without TMUX".into())
|
||||||
.with_style(UiComponentStyles {
|
.with_style(UiComponentStyles {
|
||||||
@@ -266,7 +266,7 @@ impl View for SshErrorBlock {
|
|||||||
.build()
|
.build()
|
||||||
.with_cursor(Cursor::PointingHand)
|
.with_cursor(Cursor::PointingHand)
|
||||||
.on_click(move |ctx, _, _| {
|
.on_click(move |ctx, _, _| {
|
||||||
ctx.dispatch_typed_action(SshErrorBlockAction::WarpifyWithoutTmux)
|
ctx.dispatch_typed_action(SshErrorBlockAction::WormholeWithoutTmux)
|
||||||
})
|
})
|
||||||
.finish(),
|
.finish(),
|
||||||
)
|
)
|
||||||
@@ -287,7 +287,7 @@ impl View for SshErrorBlock {
|
|||||||
.build()
|
.build()
|
||||||
.with_cursor(Cursor::PointingHand)
|
.with_cursor(Cursor::PointingHand)
|
||||||
.on_click(move |ctx, _, _| {
|
.on_click(move |ctx, _, _| {
|
||||||
ctx.dispatch_typed_action(SshErrorBlockAction::ContinueWithoutWarpification)
|
ctx.dispatch_typed_action(SshErrorBlockAction::ContinueWithoutWormholing)
|
||||||
})
|
})
|
||||||
.finish(),
|
.finish(),
|
||||||
);
|
);
|
||||||
@@ -331,21 +331,21 @@ impl TypedActionView for SshErrorBlock {
|
|||||||
|
|
||||||
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
|
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
|
||||||
match action {
|
match action {
|
||||||
SshErrorBlockAction::WarpifyWithoutTmux => {
|
SshErrorBlockAction::WormholeWithoutTmux => {
|
||||||
ctx.emit(SshErrorBlockEvent::WarpifyWithoutTmux)
|
ctx.emit(SshErrorBlockEvent::WormholeWithoutTmux)
|
||||||
}
|
}
|
||||||
SshErrorBlockAction::ContinueWithoutWarpification => {
|
SshErrorBlockAction::ContinueWithoutWormholing => {
|
||||||
ctx.emit(SshErrorBlockEvent::ContinueWithoutWarpification)
|
ctx.emit(SshErrorBlockEvent::ContinueWithoutWormholing)
|
||||||
}
|
}
|
||||||
SshErrorBlockAction::OpenUrl(url) => {
|
SshErrorBlockAction::OpenUrl(url) => {
|
||||||
ctx.open_url(url);
|
ctx.open_url(url);
|
||||||
}
|
}
|
||||||
SshErrorBlockAction::AddSshHostToDenylist(ssh_host) => {
|
SshErrorBlockAction::AddSshHostToDenylist(ssh_host) => {
|
||||||
let settings = WarpifySettings::handle(ctx);
|
let settings = WormholeSettings::handle(ctx);
|
||||||
settings.update(ctx, |warpify, ctx| {
|
settings.update(ctx, |wormhole, ctx| {
|
||||||
warpify.denylist_ssh_host(ssh_host, ctx);
|
wormhole.denylist_ssh_host(ssh_host, ctx);
|
||||||
});
|
});
|
||||||
ctx.emit(SshErrorBlockEvent::ContinueWithoutWarpification);
|
ctx.emit(SshErrorBlockEvent::ContinueWithoutWormholing);
|
||||||
ctx.notify()
|
ctx.notify()
|
||||||
}
|
}
|
||||||
SshErrorBlockAction::Focus => {
|
SshErrorBlockAction::Focus => {
|
||||||
|
|||||||
@@ -6,14 +6,13 @@ use crate::ai::blocklist::inline_action::requested_script::{RequestedScriptStatu
|
|||||||
use crate::appearance::Appearance;
|
use crate::appearance::Appearance;
|
||||||
use crate::terminal::model::ansi::SystemDetails;
|
use crate::terminal::model::ansi::SystemDetails;
|
||||||
use crate::terminal::model::escape_sequences;
|
use crate::terminal::model::escape_sequences;
|
||||||
use crate::terminal::warpify::render;
|
use crate::terminal::wormhole::render;
|
||||||
use crate::terminal::warpify::settings::WarpifySettings;
|
use crate::terminal::wormhole::settings::WormholeSettings;
|
||||||
use crate::ui_components::blended_colors;
|
use crate::ui_components::blended_colors;
|
||||||
use crate::ui_components::icons::Icon as UiIcon;
|
use crate::ui_components::icons::Icon as UiIcon;
|
||||||
use galaxy_core::ui::theme::GalaxyTheme;
|
use galaxy_core::ui::theme::GalaxyTheme;
|
||||||
use galaxyui::elements::{
|
use galaxyui::elements::{
|
||||||
FormattedTextElement, HighlightedHyperlink, Hoverable, Icon, MainAxisAlignment, MainAxisSize,
|
Hoverable, Icon, MainAxisAlignment, MainAxisSize, MouseStateHandle, Text,
|
||||||
MouseStateHandle,
|
|
||||||
};
|
};
|
||||||
use galaxyui::keymap::FixedBinding;
|
use galaxyui::keymap::FixedBinding;
|
||||||
use galaxyui::ui_components::toggle_menu::ToggleMenuStateHandle;
|
use galaxyui::ui_components::toggle_menu::ToggleMenuStateHandle;
|
||||||
@@ -22,10 +21,6 @@ use galaxyui::{
|
|||||||
AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext,
|
AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext,
|
||||||
};
|
};
|
||||||
use galaxyui::{BlurContext, FocusContext};
|
use galaxyui::{BlurContext, FocusContext};
|
||||||
use markdown_parser::{FormattedText, FormattedTextFragment, FormattedTextLine};
|
|
||||||
|
|
||||||
pub const WHY_INSTALL_TMUX_URL: &str =
|
|
||||||
"https://docs.warp.dev/terminal/warpify/ssh#why-do-i-need-tmux-on-the-remote-machine";
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TmuxInstallMethod {
|
pub struct TmuxInstallMethod {
|
||||||
@@ -35,7 +30,7 @@ pub struct TmuxInstallMethod {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum SshInstallTmuxBlockEvent {
|
pub enum SshInstallTmuxBlockEvent {
|
||||||
InstallTmuxAndWarpify(TmuxInstallMethod),
|
InstallTmuxAndWormhole(TmuxInstallMethod),
|
||||||
ToggleScriptVisibility,
|
ToggleScriptVisibility,
|
||||||
Cancel,
|
Cancel,
|
||||||
Interrupt,
|
Interrupt,
|
||||||
@@ -88,15 +83,14 @@ impl SshKeyEvent {
|
|||||||
|
|
||||||
pub struct SshInstallTmuxBlock {
|
pub struct SshInstallTmuxBlock {
|
||||||
requested_script_mouse_states: RequestedScriptMouseStates,
|
requested_script_mouse_states: RequestedScriptMouseStates,
|
||||||
why_install_tmux_highlight_index: HighlightedHyperlink,
|
never_wormhole_mouse_state_handle: MouseStateHandle,
|
||||||
never_warpify_mouse_state_handle: MouseStateHandle,
|
|
||||||
block_mouse_state: MouseStateHandle,
|
block_mouse_state: MouseStateHandle,
|
||||||
is_focused: bool,
|
is_focused: bool,
|
||||||
is_collapsed: bool,
|
is_collapsed: bool,
|
||||||
show_tmux_install_block: bool,
|
show_tmux_install_block: bool,
|
||||||
script_status: RequestedScriptStatus,
|
script_status: RequestedScriptStatus,
|
||||||
system_details: SystemDetails,
|
system_details: SystemDetails,
|
||||||
/// The script to install tmux locally, in a ~/.warp directory
|
/// The script to install tmux locally, in a ~/.galaxy directory
|
||||||
tmux_local_install_script: String,
|
tmux_local_install_script: String,
|
||||||
ssh_host: Option<String>,
|
ssh_host: Option<String>,
|
||||||
ssh_command: String,
|
ssh_command: String,
|
||||||
@@ -166,8 +160,7 @@ impl SshInstallTmuxBlock {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
requested_script_mouse_states: Default::default(),
|
requested_script_mouse_states: Default::default(),
|
||||||
why_install_tmux_highlight_index: Default::default(),
|
never_wormhole_mouse_state_handle: Default::default(),
|
||||||
never_warpify_mouse_state_handle: Default::default(),
|
|
||||||
block_mouse_state: Default::default(),
|
block_mouse_state: Default::default(),
|
||||||
is_focused: false,
|
is_focused: false,
|
||||||
is_collapsed: true,
|
is_collapsed: true,
|
||||||
@@ -220,7 +213,7 @@ impl SshInstallTmuxBlock {
|
|||||||
ctx: &mut ViewContext<Self>,
|
ctx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
self.script_status = RequestedScriptStatus::Running;
|
self.script_status = RequestedScriptStatus::Running;
|
||||||
ctx.emit(SshInstallTmuxBlockEvent::InstallTmuxAndWarpify(
|
ctx.emit(SshInstallTmuxBlockEvent::InstallTmuxAndWormhole(
|
||||||
install_method,
|
install_method,
|
||||||
));
|
));
|
||||||
ctx.notify()
|
ctx.notify()
|
||||||
@@ -261,7 +254,7 @@ impl SshInstallTmuxBlock {
|
|||||||
content: tmux_system_install_script.to_string(),
|
content: tmux_system_install_script.to_string(),
|
||||||
},
|
},
|
||||||
TitledScript {
|
TitledScript {
|
||||||
title: "Install to ~/.warp".to_string(),
|
title: "Install to ~/.galaxy".to_string(),
|
||||||
content: self.tmux_local_install_script.clone(),
|
content: self.tmux_local_install_script.clone(),
|
||||||
},
|
},
|
||||||
*is_first_script_active,
|
*is_first_script_active,
|
||||||
@@ -320,7 +313,7 @@ impl SshInstallTmuxBlock {
|
|||||||
) -> Box<dyn Element> {
|
) -> Box<dyn Element> {
|
||||||
let header_contents = render::build_header_row(
|
let header_contents = render::build_header_row(
|
||||||
"Install tmux?",
|
"Install tmux?",
|
||||||
Icon::new(UiIcon::Warp.into(), theme.active_ui_detail()),
|
Icon::new(UiIcon::GalaxyLogo.into(), theme.active_ui_detail()),
|
||||||
theme,
|
theme,
|
||||||
appearance,
|
appearance,
|
||||||
)
|
)
|
||||||
@@ -331,11 +324,11 @@ impl SshInstallTmuxBlock {
|
|||||||
|
|
||||||
let right_hand_size = is_awaiting_action
|
let right_hand_size = is_awaiting_action
|
||||||
.then(|| {
|
.then(|| {
|
||||||
render::render_never_warpify_ssh_link(
|
render::render_never_wormhole_ssh_link(
|
||||||
&self.ssh_host,
|
&self.ssh_host,
|
||||||
app,
|
app,
|
||||||
appearance,
|
appearance,
|
||||||
self.never_warpify_mouse_state_handle.clone(),
|
self.never_wormhole_mouse_state_handle.clone(),
|
||||||
move |ctx, ssh_host| {
|
move |ctx, ssh_host| {
|
||||||
ctx.dispatch_typed_action(SshInstallTmuxBlockAction::AddSshHostToDenylist(
|
ctx.dispatch_typed_action(SshInstallTmuxBlockAction::AddSshHostToDenylist(
|
||||||
ssh_host.to_owned(),
|
ssh_host.to_owned(),
|
||||||
@@ -382,30 +375,20 @@ impl View for SshInstallTmuxBlock {
|
|||||||
"In order to Wormhole your SSH session, tmux must be installed. "
|
"In order to Wormhole your SSH session, tmux must be installed. "
|
||||||
};
|
};
|
||||||
|
|
||||||
let warpify_description = vec![
|
|
||||||
FormattedTextFragment::plain_text(explanation),
|
|
||||||
FormattedTextFragment::hyperlink("Why do I need tmux?", WHY_INSTALL_TMUX_URL),
|
|
||||||
];
|
|
||||||
|
|
||||||
let text_color =
|
let text_color =
|
||||||
blended_colors::text_sub(appearance.theme(), appearance.theme().surface_1());
|
blended_colors::text_sub(appearance.theme(), appearance.theme().surface_1());
|
||||||
|
|
||||||
let warpify_description = FormattedTextElement::new(
|
let wormhole_description = Text::new(
|
||||||
FormattedText::new([FormattedTextLine::Line(warpify_description)]),
|
explanation.to_string(),
|
||||||
|
appearance.monospace_font_family(),
|
||||||
appearance.monospace_font_size(),
|
appearance.monospace_font_size(),
|
||||||
appearance.monospace_font_family(),
|
|
||||||
appearance.monospace_font_family(),
|
|
||||||
text_color,
|
|
||||||
self.why_install_tmux_highlight_index.clone(),
|
|
||||||
)
|
)
|
||||||
.with_hyperlink_font_color(appearance.theme().accent().into_solid())
|
.soft_wrap(true)
|
||||||
.register_default_click_handlers(|url, _, ctx| {
|
.with_color(text_color)
|
||||||
ctx.open_url(&url.url);
|
|
||||||
})
|
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
content
|
content
|
||||||
.add_child(render::apply_spacing_styles(Container::new(warpify_description)).finish());
|
.add_child(render::apply_spacing_styles(Container::new(wormhole_description)).finish());
|
||||||
|
|
||||||
if let Some(root_install_state) = &self.system_install_state {
|
if let Some(root_install_state) = &self.system_install_state {
|
||||||
content.add_child(self.render_system_install_ui(root_install_state, app));
|
content.add_child(self.render_system_install_ui(root_install_state, app));
|
||||||
@@ -490,9 +473,9 @@ impl TypedActionView for SshInstallTmuxBlock {
|
|||||||
ctx.emit(SshInstallTmuxBlockEvent::Interrupt);
|
ctx.emit(SshInstallTmuxBlockEvent::Interrupt);
|
||||||
}
|
}
|
||||||
(SshInstallTmuxBlockAction::AddSshHostToDenylist(ssh_host), true) => {
|
(SshInstallTmuxBlockAction::AddSshHostToDenylist(ssh_host), true) => {
|
||||||
let settings = WarpifySettings::handle(ctx);
|
let settings = WormholeSettings::handle(ctx);
|
||||||
settings.update(ctx, |warpify, ctx| {
|
settings.update(ctx, |wormhole, ctx| {
|
||||||
warpify.denylist_ssh_host(ssh_host, ctx);
|
wormhole.denylist_ssh_host(ssh_host, ctx);
|
||||||
});
|
});
|
||||||
ctx.emit(SshInstallTmuxBlockEvent::Cancel);
|
ctx.emit(SshInstallTmuxBlockEvent::Cancel);
|
||||||
ctx.notify();
|
ctx.notify();
|
||||||
@@ -519,16 +502,16 @@ pub fn install_tmux_script(system: &SystemDetails, app: &AppContext) -> Option<S
|
|||||||
system.shell.as_str(),
|
system.shell.as_str(),
|
||||||
) {
|
) {
|
||||||
("Linux", _, "bash" | "zsh") => {
|
("Linux", _, "bash" | "zsh") => {
|
||||||
bundled_asset!("ssh/bash_zsh/install_tmux_and_warpify_linux.sh")
|
bundled_asset!("ssh/bash_zsh/install_tmux_and_wormhole_linux.sh")
|
||||||
}
|
}
|
||||||
("Linux", _, "fish") => {
|
("Linux", _, "fish") => {
|
||||||
bundled_asset!("ssh/fish/install_tmux_and_warpify_linux.sh")
|
bundled_asset!("ssh/fish/install_tmux_and_wormhole_linux.sh")
|
||||||
}
|
}
|
||||||
("Darwin", "homebrew", "bash" | "zsh") => {
|
("Darwin", "homebrew", "bash" | "zsh") => {
|
||||||
bundled_asset!("ssh/bash_zsh/install_tmux_and_warpify_brew.sh")
|
bundled_asset!("ssh/bash_zsh/install_tmux_and_wormhole_brew.sh")
|
||||||
}
|
}
|
||||||
("Darwin", "homebrew", "fish") => {
|
("Darwin", "homebrew", "fish") => {
|
||||||
bundled_asset!("ssh/fish/install_tmux_and_warpify_brew.sh")
|
bundled_asset!("ssh/fish/install_tmux_and_wormhole_brew.sh")
|
||||||
}
|
}
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
@@ -555,19 +538,19 @@ pub fn install_root_tmux_script(
|
|||||||
system.shell.as_str(),
|
system.shell.as_str(),
|
||||||
) {
|
) {
|
||||||
("Linux", "apt", "bash" | "zsh") => {
|
("Linux", "apt", "bash" | "zsh") => {
|
||||||
bundled_asset!("ssh/bash_zsh/root/install_tmux_and_warpify_apt.sh")
|
bundled_asset!("ssh/bash_zsh/root/install_tmux_and_wormhole_apt.sh")
|
||||||
}
|
}
|
||||||
("Linux", "dnf", "bash" | "zsh") => {
|
("Linux", "dnf", "bash" | "zsh") => {
|
||||||
bundled_asset!("ssh/bash_zsh/root/install_tmux_and_warpify_dnf.sh")
|
bundled_asset!("ssh/bash_zsh/root/install_tmux_and_wormhole_dnf.sh")
|
||||||
}
|
}
|
||||||
("Linux", "pacman", "bash" | "zsh") => {
|
("Linux", "pacman", "bash" | "zsh") => {
|
||||||
bundled_asset!("ssh/bash_zsh/root/install_tmux_and_warpify_pacman.sh")
|
bundled_asset!("ssh/bash_zsh/root/install_tmux_and_wormhole_pacman.sh")
|
||||||
}
|
}
|
||||||
("Linux", "yum", "bash" | "zsh") => {
|
("Linux", "yum", "bash" | "zsh") => {
|
||||||
bundled_asset!("ssh/bash_zsh/root/install_tmux_and_warpify_yum.sh")
|
bundled_asset!("ssh/bash_zsh/root/install_tmux_and_wormhole_yum.sh")
|
||||||
}
|
}
|
||||||
("Linux", "zypper", "bash" | "zsh") => {
|
("Linux", "zypper", "bash" | "zsh") => {
|
||||||
bundled_asset!("ssh/bash_zsh/root/install_tmux_and_warpify_zypper.sh")
|
bundled_asset!("ssh/bash_zsh/root/install_tmux_and_wormhole_zypper.sh")
|
||||||
}
|
}
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use galaxy_core::{features::FeatureFlag, settings::Setting};
|
|||||||
use galaxy_util::path::ShellFamily;
|
use galaxy_util::path::ShellFamily;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::terminal::warpify::settings::WarpifySettings;
|
use crate::terminal::wormhole::settings::WormholeSettings;
|
||||||
|
|
||||||
/// The different possible outcomes of detecting an interactive SSH session.
|
/// The different possible outcomes of detecting an interactive SSH session.
|
||||||
/// Also the payload for the [`crate::server::telemetry::TelemetryEvent::SshInteractiveSessionDetected`] event.
|
/// Also the payload for the [`crate::server::telemetry::TelemetryEvent::SshInteractiveSessionDetected`] event.
|
||||||
@@ -12,8 +12,8 @@ pub enum SshInteractiveSessionDetected {
|
|||||||
FeatureDisabled,
|
FeatureDisabled,
|
||||||
#[serde(rename = "host_denylisted")]
|
#[serde(rename = "host_denylisted")]
|
||||||
HostDenylisted,
|
HostDenylisted,
|
||||||
#[serde(rename = "warpify_prompt")]
|
#[serde(rename = "wormhole_prompt")]
|
||||||
ShouldPromptWarpification {
|
ShouldPromptWormholing {
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
command: String,
|
command: String,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
@@ -21,17 +21,17 @@ pub enum SshInteractiveSessionDetected {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines whether a host could be warpified.
|
/// Determines whether a host could be wormholed.
|
||||||
pub fn evaluate_warpify_ssh_host(
|
pub fn evaluate_wormhole_ssh_host(
|
||||||
command: &str,
|
command: &str,
|
||||||
ssh_host: Option<&str>,
|
ssh_host: Option<&str>,
|
||||||
shell_family: ShellFamily,
|
shell_family: ShellFamily,
|
||||||
warpify_settings: &WarpifySettings,
|
wormhole_settings: &WormholeSettings,
|
||||||
) -> SshInteractiveSessionDetected {
|
) -> SshInteractiveSessionDetected {
|
||||||
let should_prompt_ssh_tmux_wrapper = *warpify_settings.enable_ssh_warpification.value()
|
let should_prompt_ssh_tmux_wrapper = *wormhole_settings.enable_ssh_wormholing.value()
|
||||||
&& *warpify_settings.use_ssh_tmux_wrapper.value();
|
&& *wormhole_settings.use_ssh_tmux_wrapper.value();
|
||||||
let matches_subshell = warpify_settings.is_denylisted_subshell_command(command)
|
let matches_subshell = wormhole_settings.is_denylisted_subshell_command(command)
|
||||||
|| warpify_settings.is_compatible_subshell_command(command, shell_family);
|
|| wormhole_settings.is_compatible_subshell_command(command, shell_family);
|
||||||
if !should_prompt_ssh_tmux_wrapper
|
if !should_prompt_ssh_tmux_wrapper
|
||||||
|| matches_subshell
|
|| matches_subshell
|
||||||
|| !FeatureFlag::SSHTmuxWrapper.is_enabled()
|
|| !FeatureFlag::SSHTmuxWrapper.is_enabled()
|
||||||
@@ -40,12 +40,12 @@ pub fn evaluate_warpify_ssh_host(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ssh_host) = ssh_host {
|
if let Some(ssh_host) = ssh_host {
|
||||||
if warpify_settings.is_ssh_host_denylisted(ssh_host) {
|
if wormhole_settings.is_ssh_host_denylisted(ssh_host) {
|
||||||
return SshInteractiveSessionDetected::HostDenylisted;
|
return SshInteractiveSessionDetected::HostDenylisted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SshInteractiveSessionDetected::ShouldPromptWarpification {
|
SshInteractiveSessionDetected::ShouldPromptWormholing {
|
||||||
host: ssh_host.map(|host| host.to_owned()),
|
host: ssh_host.map(|host| host.to_owned()),
|
||||||
command: command.to_string(),
|
command: command.to_string(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ pub fn check_ssh_login_state(block_output: &str) -> SshLoginState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the parsed components of an interactive SSH command.
|
/// Represents the parsed components of an interactive SSH command.
|
||||||
/// For some [`SshWarpifyCommand`]s, we do not support parsing
|
/// For some [`SshWormholeCommand`]s, we do not support parsing
|
||||||
/// a host or port In these cases, we can still parse to a valid
|
/// a host or port In these cases, we can still parse to a valid
|
||||||
/// empty `InteractiveSshCommand` to indicate that we did
|
/// empty `InteractiveSshCommand` to indicate that we did
|
||||||
/// successfully detect an interactive SSH command.
|
/// successfully detect an interactive SSH command.
|
||||||
@@ -150,25 +150,25 @@ pub enum SshLikeCommand {
|
|||||||
/// Represents the different kinds of commands we recognize as starting an interactive SSH
|
/// Represents the different kinds of commands we recognize as starting an interactive SSH
|
||||||
/// session. `Ssh` means a literal `ssh` command, where all other commands (e.g. `gcloud
|
/// session. `Ssh` means a literal `ssh` command, where all other commands (e.g. `gcloud
|
||||||
/// compute ssh`) are categorized as SSH-like commands.
|
/// compute ssh`) are categorized as SSH-like commands.
|
||||||
pub enum SshWarpifyCommand {
|
pub enum SshWormholeCommand {
|
||||||
Ssh,
|
Ssh,
|
||||||
SshLike(SshLikeCommand),
|
SshLike(SshLikeCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SshWarpifyCommand {
|
impl SshWormholeCommand {
|
||||||
/// Not a literal `ssh` command, but another command that starts an interactive SSH
|
/// Not a literal `ssh` command, but another command that starts an interactive SSH
|
||||||
/// session.
|
/// session.
|
||||||
pub fn is_ssh_like_command(&self) -> bool {
|
pub fn is_ssh_like_command(&self) -> bool {
|
||||||
matches!(self, SshWarpifyCommand::SshLike(_))
|
matches!(self, SshWormholeCommand::SshLike(_))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SshWarpifyCommand {
|
impl SshWormholeCommand {
|
||||||
pub fn matches(command: &str) -> Option<SshWarpifyCommand> {
|
pub fn matches(command: &str) -> Option<SshWormholeCommand> {
|
||||||
let tokens = normalized_command_tokens(command)?;
|
let tokens = normalized_command_tokens(command)?;
|
||||||
match tokens.as_slice() {
|
match tokens.as_slice() {
|
||||||
[command, arguments @ ..] if command == "ssh" && !arguments.is_empty() => {
|
[command, arguments @ ..] if command == "ssh" && !arguments.is_empty() => {
|
||||||
Some(SshWarpifyCommand::Ssh)
|
Some(SshWormholeCommand::Ssh)
|
||||||
}
|
}
|
||||||
[command, compute, ssh, arguments @ ..]
|
[command, compute, ssh, arguments @ ..]
|
||||||
if command == "gcloud"
|
if command == "gcloud"
|
||||||
@@ -176,12 +176,14 @@ impl SshWarpifyCommand {
|
|||||||
&& ssh == "ssh"
|
&& ssh == "ssh"
|
||||||
&& !arguments.is_empty() =>
|
&& !arguments.is_empty() =>
|
||||||
{
|
{
|
||||||
Some(SshWarpifyCommand::SshLike(SshLikeCommand::Gcloud))
|
Some(SshWormholeCommand::SshLike(SshLikeCommand::Gcloud))
|
||||||
}
|
}
|
||||||
[command, ssh, arguments @ ..]
|
[command, ssh, arguments @ ..]
|
||||||
if command == "eb" && ssh == "ssh" && !arguments.is_empty() =>
|
if command == "eb" && ssh == "ssh" && !arguments.is_empty() =>
|
||||||
{
|
{
|
||||||
Some(SshWarpifyCommand::SshLike(SshLikeCommand::ElasticBeanstalk))
|
Some(SshWormholeCommand::SshLike(
|
||||||
|
SshLikeCommand::ElasticBeanstalk,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
[command, compute, ssh, arguments @ ..]
|
[command, compute, ssh, arguments @ ..]
|
||||||
if command == "doctl"
|
if command == "doctl"
|
||||||
@@ -189,7 +191,7 @@ impl SshWarpifyCommand {
|
|||||||
&& ssh == "ssh"
|
&& ssh == "ssh"
|
||||||
&& !arguments.is_empty() =>
|
&& !arguments.is_empty() =>
|
||||||
{
|
{
|
||||||
Some(SshWarpifyCommand::SshLike(
|
Some(SshWormholeCommand::SshLike(
|
||||||
SshLikeCommand::DigitalOceanDroplet,
|
SshLikeCommand::DigitalOceanDroplet,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -199,15 +201,15 @@ impl SshWarpifyCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_interactive_ssh_command(command: &str) -> Option<InteractiveSshCommand> {
|
pub fn parse_interactive_ssh_command(command: &str) -> Option<InteractiveSshCommand> {
|
||||||
match SshWarpifyCommand::matches(command) {
|
match SshWormholeCommand::matches(command) {
|
||||||
Some(SshWarpifyCommand::Ssh) => InteractiveSshCommand::parse_ssh_command(command),
|
Some(SshWormholeCommand::Ssh) => InteractiveSshCommand::parse_ssh_command(command),
|
||||||
Some(SshWarpifyCommand::SshLike(SshLikeCommand::Gcloud)) => {
|
Some(SshWormholeCommand::SshLike(SshLikeCommand::Gcloud)) => {
|
||||||
Some(InteractiveSshCommand::default())
|
Some(InteractiveSshCommand::default())
|
||||||
}
|
}
|
||||||
Some(SshWarpifyCommand::SshLike(SshLikeCommand::ElasticBeanstalk)) => {
|
Some(SshWormholeCommand::SshLike(SshLikeCommand::ElasticBeanstalk)) => {
|
||||||
Some(InteractiveSshCommand::default())
|
Some(InteractiveSshCommand::default())
|
||||||
}
|
}
|
||||||
Some(SshWarpifyCommand::SshLike(SshLikeCommand::DigitalOceanDroplet)) => {
|
Some(SshWormholeCommand::SshLike(SshLikeCommand::DigitalOceanDroplet)) => {
|
||||||
Some(InteractiveSshCommand::default())
|
Some(InteractiveSshCommand::default())
|
||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
@@ -299,7 +301,7 @@ fn executable_name(executable: &str) -> String {
|
|||||||
.to_ascii_lowercase()
|
.to_ascii_lowercase()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an sftp command that copies a given local file into the pwd in the warpified ssh session.
|
/// Creates an sftp command that copies a given local file into the pwd in the wormholed ssh session.
|
||||||
pub fn transfer_file_sftp_command(
|
pub fn transfer_file_sftp_command(
|
||||||
local_file_path: String,
|
local_file_path: String,
|
||||||
ssh_host: String,
|
ssh_host: String,
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ use markdown_parser::{FormattedText, FormattedTextFragment, FormattedTextLine};
|
|||||||
use crate::ai::blocklist::inline_action::requested_action::RenderableAction;
|
use crate::ai::blocklist::inline_action::requested_action::RenderableAction;
|
||||||
use crate::appearance::Appearance;
|
use crate::appearance::Appearance;
|
||||||
use crate::terminal::shell::ShellType;
|
use crate::terminal::shell::ShellType;
|
||||||
use crate::terminal::warpify;
|
use crate::terminal::wormhole;
|
||||||
use crate::terminal::warpify::render::SSH_DOCS_URL;
|
|
||||||
use crate::ui_components::icons::Icon as UiIcon;
|
use crate::ui_components::icons::Icon as UiIcon;
|
||||||
use galaxyui::elements::{HighlightedHyperlink, Hoverable, Icon, MouseStateHandle};
|
use galaxyui::elements::{HighlightedHyperlink, Hoverable, Icon, MouseStateHandle};
|
||||||
use galaxyui::keymap::FixedBinding;
|
use galaxyui::keymap::FixedBinding;
|
||||||
@@ -18,19 +17,19 @@ use galaxyui::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum SshWarpifyBlockEvent {
|
pub enum SshWormholeBlockEvent {
|
||||||
WarpifySession,
|
WormholeSession,
|
||||||
Cancel,
|
Cancel,
|
||||||
Interrupt,
|
Interrupt,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub enum SshWarpifyBlockAction {
|
pub enum SshWormholeBlockAction {
|
||||||
Interrupt,
|
Interrupt,
|
||||||
Focus,
|
Focus,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SshWarpifyBlock {
|
pub struct SshWormholeBlock {
|
||||||
block_mouse_state: MouseStateHandle,
|
block_mouse_state: MouseStateHandle,
|
||||||
ssh_command: String,
|
ssh_command: String,
|
||||||
}
|
}
|
||||||
@@ -40,12 +39,12 @@ pub fn init(app: &mut AppContext) {
|
|||||||
|
|
||||||
app.register_fixed_bindings([FixedBinding::new(
|
app.register_fixed_bindings([FixedBinding::new(
|
||||||
"ctrl-c",
|
"ctrl-c",
|
||||||
SshWarpifyBlockAction::Interrupt,
|
SshWormholeBlockAction::Interrupt,
|
||||||
id!(SshWarpifyBlock::ui_name()),
|
id!(SshWormholeBlock::ui_name()),
|
||||||
)]);
|
)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SshWarpifyBlock {
|
impl SshWormholeBlock {
|
||||||
#[allow(clippy::new_without_default)]
|
#[allow(clippy::new_without_default)]
|
||||||
pub fn new(ssh_command: String) -> Self {
|
pub fn new(ssh_command: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -60,18 +59,18 @@ impl SshWarpifyBlock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for SshWarpifyBlock {
|
impl Entity for SshWormholeBlock {
|
||||||
type Event = SshWarpifyBlockEvent;
|
type Event = SshWormholeBlockEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SshWarpifyBlock {
|
impl SshWormholeBlock {
|
||||||
fn render_title_ui(&self, theme: &GalaxyTheme, appearance: &Appearance) -> Box<dyn Element> {
|
fn render_title_ui(&self, theme: &GalaxyTheme, appearance: &Appearance) -> Box<dyn Element> {
|
||||||
let icon = Icon::new(UiIcon::Warp.into(), theme.active_ui_detail());
|
let icon = Icon::new(UiIcon::GalaxyLogo.into(), theme.active_ui_detail());
|
||||||
warpify::render::header_row("Wormholing SSH Session...", icon, theme, appearance)
|
wormhole::render::header_row("Wormholing SSH Session...", icon, theme, appearance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn warpify_description(
|
pub fn wormhole_description(
|
||||||
app: &AppContext,
|
app: &AppContext,
|
||||||
hyperlink_index: &HighlightedHyperlink,
|
hyperlink_index: &HighlightedHyperlink,
|
||||||
) -> Box<dyn Element> {
|
) -> Box<dyn Element> {
|
||||||
@@ -80,21 +79,16 @@ pub fn warpify_description(
|
|||||||
|
|
||||||
let description = FormattedText::new(vec![FormattedTextLine::Line(vec![
|
let description = FormattedText::new(vec![FormattedTextLine::Line(vec![
|
||||||
FormattedTextFragment::plain_text(
|
FormattedTextFragment::plain_text(
|
||||||
"Bring Galaxy's features to your remote session. Blocks, full text editing, auto-complete, Oz, and more. "
|
"Bring Galaxy's features to your remote session: blocks, full text editing, completions, Oz, and more."
|
||||||
),
|
),
|
||||||
FormattedTextFragment::hyperlink("Learn more", SSH_DOCS_URL),
|
|
||||||
])]);
|
])]);
|
||||||
warpify::render::build_description_row(description, theme, appearance, hyperlink_index.clone())
|
wormhole::render::build_description_row(description, theme, appearance, hyperlink_index.clone())
|
||||||
.with_hyperlink_font_color(appearance.theme().accent().into_solid())
|
|
||||||
.register_default_click_handlers(|url, _, ctx| {
|
|
||||||
ctx.open_url(&url.url);
|
|
||||||
})
|
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View for SshWarpifyBlock {
|
impl View for SshWormholeBlock {
|
||||||
fn ui_name() -> &'static str {
|
fn ui_name() -> &'static str {
|
||||||
"SshWarpifyBlock"
|
"SshWormholeBlock"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self, app: &AppContext) -> Box<dyn Element> {
|
fn render(&self, app: &AppContext) -> Box<dyn Element> {
|
||||||
@@ -124,39 +118,39 @@ impl View for SshWarpifyBlock {
|
|||||||
.finish()
|
.finish()
|
||||||
})
|
})
|
||||||
.on_click(|ctx, _, _| {
|
.on_click(|ctx, _, _| {
|
||||||
ctx.dispatch_typed_action(SshWarpifyBlockAction::Focus);
|
ctx.dispatch_typed_action(SshWormholeBlockAction::Focus);
|
||||||
})
|
})
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypedActionView for SshWarpifyBlock {
|
impl TypedActionView for SshWormholeBlock {
|
||||||
type Action = SshWarpifyBlockAction;
|
type Action = SshWormholeBlockAction;
|
||||||
|
|
||||||
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
|
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
|
||||||
match action {
|
match action {
|
||||||
SshWarpifyBlockAction::Interrupt => {
|
SshWormholeBlockAction::Interrupt => {
|
||||||
ctx.emit(SshWarpifyBlockEvent::Interrupt);
|
ctx.emit(SshWormholeBlockEvent::Interrupt);
|
||||||
}
|
}
|
||||||
SshWarpifyBlockAction::Focus => {
|
SshWormholeBlockAction::Focus => {
|
||||||
self.focus(ctx);
|
self.focus(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert the begin_warpify_ssh_session script into a string.
|
/// Convert the begin_wormhole_ssh_session script into a string.
|
||||||
pub fn begin_warpify_ssh_session_command(app: &AppContext) -> String {
|
pub fn begin_wormhole_ssh_session_command(app: &AppContext) -> String {
|
||||||
let asset = bundled_asset!("bootstrap/unknown_init_subshell.sh");
|
let asset = bundled_asset!("bootstrap/unknown_init_subshell.sh");
|
||||||
|
|
||||||
match AssetCache::as_ref(app).load_asset::<String>(asset) {
|
match AssetCache::as_ref(app).load_asset::<String>(asset) {
|
||||||
AssetState::Loaded { data } => data.to_string().replace("HOOK_NAME", "InitSsh"),
|
AssetState::Loaded { data } => data.to_string().replace("HOOK_NAME", "InitSsh"),
|
||||||
_ => panic!("ssh begin warpify script should be available as a string"),
|
_ => panic!("ssh begin wormhole script should be available as a string"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert the warpify_ssh_session script into a string.
|
/// Convert the wormhole_ssh_session script into a string.
|
||||||
pub fn warpify_ssh_session_command(
|
pub fn wormhole_ssh_session_command(
|
||||||
uname: &str,
|
uname: &str,
|
||||||
shell_type: ShellType,
|
shell_type: ShellType,
|
||||||
app: &AppContext,
|
app: &AppContext,
|
||||||
@@ -164,14 +158,14 @@ pub fn warpify_ssh_session_command(
|
|||||||
let asset = match (uname, shell_type) {
|
let asset = match (uname, shell_type) {
|
||||||
// Mac scripts must be less than 1020 characters due to macOS 15+ pty issue
|
// Mac scripts must be less than 1020 characters due to macOS 15+ pty issue
|
||||||
("Darwin", ShellType::Zsh | ShellType::Bash) => {
|
("Darwin", ShellType::Zsh | ShellType::Bash) => {
|
||||||
bundled_asset!("ssh/bash_zsh/warpify_ssh_session_mac.sh")
|
bundled_asset!("ssh/bash_zsh/wormhole_ssh_session_mac.sh")
|
||||||
}
|
}
|
||||||
// Mac scripts must be less than 1020 characters due to macOS 15+ pty issue
|
// Mac scripts must be less than 1020 characters due to macOS 15+ pty issue
|
||||||
("Darwin", ShellType::Fish) => bundled_asset!("ssh/fish/warpify_ssh_session_mac.sh"),
|
("Darwin", ShellType::Fish) => bundled_asset!("ssh/fish/wormhole_ssh_session_mac.sh"),
|
||||||
(_, ShellType::Zsh | ShellType::Bash) => {
|
(_, ShellType::Zsh | ShellType::Bash) => {
|
||||||
bundled_asset!("ssh/bash_zsh/warpify_ssh_session.sh")
|
bundled_asset!("ssh/bash_zsh/wormhole_ssh_session.sh")
|
||||||
}
|
}
|
||||||
(_, ShellType::Fish) => bundled_asset!("ssh/fish/warpify_ssh_session.sh"),
|
(_, ShellType::Fish) => bundled_asset!("ssh/fish/wormhole_ssh_session.sh"),
|
||||||
// PowerShell is not supported yet.
|
// PowerShell is not supported yet.
|
||||||
(_, ShellType::PowerShell) => return None,
|
(_, ShellType::PowerShell) => return None,
|
||||||
};
|
};
|
||||||
@@ -179,9 +173,9 @@ pub fn warpify_ssh_session_command(
|
|||||||
// Todo(Jack): look into avoiding an allocation here.
|
// Todo(Jack): look into avoiding an allocation here.
|
||||||
match AssetCache::as_ref(app).load_asset::<String>(asset) {
|
match AssetCache::as_ref(app).load_asset::<String>(asset) {
|
||||||
AssetState::Loaded { data } => Some(data.to_string()),
|
AssetState::Loaded { data } => Some(data.to_string()),
|
||||||
_ => panic!("ssh warpify script should be available as a string"),
|
_ => panic!("ssh wormhole script should be available as a string"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "warpify_test.rs"]
|
#[path = "wormhole_test.rs"]
|
||||||
mod tests;
|
mod tests;
|
||||||
@@ -37,50 +37,50 @@ fn get_script(asset_source: AssetSource, ctx: &AppContext) -> String {
|
|||||||
#[cfg_attr(windows, ignore = "TODO(CORE-3626)")]
|
#[cfg_attr(windows, ignore = "TODO(CORE-3626)")]
|
||||||
#[test]
|
#[test]
|
||||||
/// See [assert_script_is_short_enough_mac] for more information.
|
/// See [assert_script_is_short_enough_mac] for more information.
|
||||||
fn test_mac_warpification_script_size() {
|
fn test_mac_wormholing_script_size() {
|
||||||
App::test(Assets, |mut app| async move {
|
App::test(Assets, |mut app| async move {
|
||||||
initialize_app(&mut app);
|
initialize_app(&mut app);
|
||||||
|
|
||||||
app.read(|ctx| {
|
app.read(|ctx| {
|
||||||
assert_script_is_short_enough_mac(
|
assert_script_is_short_enough_mac(
|
||||||
&begin_warpify_ssh_session_command(ctx),
|
&begin_wormhole_ssh_session_command(ctx),
|
||||||
"unknown_init_subshell.sh",
|
"unknown_init_subshell.sh",
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_script_is_short_enough_mac(
|
assert_script_is_short_enough_mac(
|
||||||
&get_script(
|
&get_script(
|
||||||
bundled_asset!("ssh/bash_zsh/install_tmux_and_warpify_brew.sh"),
|
bundled_asset!("ssh/bash_zsh/install_tmux_and_wormhole_brew.sh"),
|
||||||
ctx,
|
ctx,
|
||||||
),
|
),
|
||||||
"install_tmux_and_warpify_brew.sh",
|
"install_tmux_and_wormhole_brew.sh",
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
assert_script_is_short_enough_mac(
|
assert_script_is_short_enough_mac(
|
||||||
&get_script(
|
&get_script(
|
||||||
bundled_asset!("ssh/fish/install_tmux_and_warpify_brew.sh"),
|
bundled_asset!("ssh/fish/install_tmux_and_wormhole_brew.sh"),
|
||||||
ctx,
|
ctx,
|
||||||
),
|
),
|
||||||
"fish/install_tmux_and_warpify_brew.sh",
|
"fish/install_tmux_and_wormhole_brew.sh",
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_script_is_short_enough_mac(
|
assert_script_is_short_enough_mac(
|
||||||
&warpify_ssh_session_command("Darwin", ShellType::Zsh, ctx)
|
&wormhole_ssh_session_command("Darwin", ShellType::Zsh, ctx)
|
||||||
.expect("Should get Darwin zsh script"),
|
.expect("Should get Darwin zsh script"),
|
||||||
"zsh warpify",
|
"zsh wormhole",
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
assert_script_is_short_enough_mac(
|
assert_script_is_short_enough_mac(
|
||||||
&warpify_ssh_session_command("Darwin", ShellType::Bash, ctx)
|
&wormhole_ssh_session_command("Darwin", ShellType::Bash, ctx)
|
||||||
.expect("Should get Darwin bash script"),
|
.expect("Should get Darwin bash script"),
|
||||||
"bash warpify",
|
"bash wormhole",
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
assert_script_is_short_enough_mac(
|
assert_script_is_short_enough_mac(
|
||||||
&warpify_ssh_session_command("Darwin", ShellType::Fish, ctx)
|
&wormhole_ssh_session_command("Darwin", ShellType::Fish, ctx)
|
||||||
.expect("Should get Darwin fish script"),
|
.expect("Should get Darwin fish script"),
|
||||||
"fish warpify",
|
"fish wormhole",
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -126,23 +126,23 @@ impl AtContextMenuDisabledReason {
|
|||||||
let session_type = session.session_type();
|
let session_type = session.session_type();
|
||||||
let has_connected_remote_server = matches!(
|
let has_connected_remote_server = matches!(
|
||||||
session_type,
|
session_type,
|
||||||
SessionType::WarpifiedRemote { host_id: Some(_) }
|
SessionType::WormholedRemote { host_id: Some(_) }
|
||||||
);
|
);
|
||||||
// The @ menu requires repo metadata which is only available for:
|
// The @ menu requires repo metadata which is only available for:
|
||||||
// - Local sessions
|
// - Local sessions
|
||||||
// - WarpifiedRemote sessions with a connected remote server (host_id is Some)
|
// - WormholedRemote sessions with a connected remote server (host_id is Some)
|
||||||
//
|
//
|
||||||
// Block when:
|
// Block when:
|
||||||
// - SSH wrapper session without a remote server upgrade
|
// - SSH wrapper session without a remote server upgrade
|
||||||
// - WarpifiedRemote still connecting (host_id is None)
|
// - WormholedRemote still connecting (host_id is None)
|
||||||
//
|
//
|
||||||
// Note: is_ssh_wrapper_session() is set at bootstrap time and stays true
|
// Note: is_ssh_wrapper_session() is set at bootstrap time and stays true
|
||||||
// even after the session transitions to WarpifiedRemote with a host_id.
|
// even after the session transitions to WormholedRemote with a host_id.
|
||||||
// So we must check has_connected_remote_server first to avoid
|
// So we must check has_connected_remote_server first to avoid
|
||||||
// incorrectly blocking upgraded sessions.
|
// incorrectly blocking upgraded sessions.
|
||||||
let is_ssh_without_remote_server = !has_connected_remote_server
|
let is_ssh_without_remote_server = !has_connected_remote_server
|
||||||
&& (session.is_ssh_wrapper_session()
|
&& (session.is_ssh_wrapper_session()
|
||||||
|| matches!(session_type, SessionType::WarpifiedRemote { host_id: None }));
|
|| matches!(session_type, SessionType::WormholedRemote { host_id: None }));
|
||||||
let is_subshell = session.subshell_info().is_some();
|
let is_subshell = session.subshell_info().is_some();
|
||||||
(is_ssh_without_remote_server, is_subshell)
|
(is_ssh_without_remote_server, is_subshell)
|
||||||
})
|
})
|
||||||
|
|||||||
+144
-158
@@ -67,13 +67,13 @@ use std::sync::Arc;
|
|||||||
use std::thread::JoinHandle;
|
use std::thread::JoinHandle;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use action::RememberForWarpification;
|
use action::RememberForWormholing;
|
||||||
pub use action::{AgentOnboardingVersion, OnboardingIntention, OnboardingVersion, TerminalAction};
|
pub use action::{AgentOnboardingVersion, OnboardingIntention, OnboardingVersion, TerminalAction};
|
||||||
use ai::api_keys::{ApiKeyManager, AwsCredentialsState};
|
use ai::api_keys::{ApiKeyManager, AwsCredentialsState};
|
||||||
use ai::index::full_source_code_embedding::manager::{BuildSource, CodebaseIndexManager};
|
use ai::index::full_source_code_embedding::manager::{BuildSource, CodebaseIndexManager};
|
||||||
use async_channel::{Receiver, Sender};
|
use async_channel::{Receiver, Sender};
|
||||||
use base64::Engine as _;
|
use base64::Engine as _;
|
||||||
use block_banner::{render_warpification_banner, WarpifyBannerState};
|
use block_banner::{render_wormholing_banner, WormholeBannerState};
|
||||||
pub use block_banner::{WithinBlockBanner, BLOCK_BANNER_HEIGHT};
|
pub use block_banner::{WithinBlockBanner, BLOCK_BANNER_HEIGHT};
|
||||||
use block_onboarding::onboarding_drive_sharing_block::OnboardingDriveSharingBlock;
|
use block_onboarding::onboarding_drive_sharing_block::OnboardingDriveSharingBlock;
|
||||||
use bookmarks::render_floating_block_snapshot;
|
use bookmarks::render_floating_block_snapshot;
|
||||||
@@ -192,10 +192,9 @@ use super::model::secrets::RichContentSecretTooltipInfo;
|
|||||||
use super::model::selection::ExpandedSelectionRange;
|
use super::model::selection::ExpandedSelectionRange;
|
||||||
use super::model::session::SessionBootstrappedEvent;
|
use super::model::session::SessionBootstrappedEvent;
|
||||||
use super::settings::AltScreenPaddingMode;
|
use super::settings::AltScreenPaddingMode;
|
||||||
use super::ssh::util::{parse_interactive_ssh_command, InteractiveSshCommand, SshWarpifyCommand};
|
use super::ssh::util::{parse_interactive_ssh_command, InteractiveSshCommand, SshWormholeCommand};
|
||||||
use super::warpify::success_block::{WarpifySuccessBlock, WarpifySuccessBlockEvent};
|
use super::wormhole::success_block::{WormholeSuccessBlock, WormholeSuccessBlockEvent};
|
||||||
use super::warpify::trigger_state::{SshBlockState, WarpifyState};
|
use super::wormhole::trigger_state::{SshBlockState, WormholeState};
|
||||||
use super::warpify::WarpificationSource;
|
|
||||||
use super::{cli_agent, CLIAgent, GridType};
|
use super::{cli_agent, CLIAgent, GridType};
|
||||||
use crate::ai::agent::api::ServerConversationToken;
|
use crate::ai::agent::api::ServerConversationToken;
|
||||||
use crate::ai::agent::conversation::{AIConversation, AIConversationId, ConversationStatus};
|
use crate::ai::agent::conversation::{AIConversation, AIConversationId, ConversationStatus};
|
||||||
@@ -484,10 +483,10 @@ use crate::terminal::view::ssh_tmux_deprecation_banner::{
|
|||||||
};
|
};
|
||||||
use crate::terminal::view::telemetry::PromptSuggestionFallbackReason;
|
use crate::terminal::view::telemetry::PromptSuggestionFallbackReason;
|
||||||
use crate::terminal::view::zero_state_block::TerminalViewZeroStateBlock;
|
use crate::terminal::view::zero_state_block::TerminalViewZeroStateBlock;
|
||||||
use crate::terminal::warpify::render::render_subshell_separator;
|
|
||||||
use crate::terminal::warpify::settings::WarpifySettings;
|
|
||||||
use crate::terminal::warpify::SubshellSource;
|
|
||||||
use crate::terminal::waterfall_gap_element::WaterfallGapElement;
|
use crate::terminal::waterfall_gap_element::WaterfallGapElement;
|
||||||
|
use crate::terminal::wormhole::render::render_subshell_separator;
|
||||||
|
use crate::terminal::wormhole::settings::WormholeSettings;
|
||||||
|
use crate::terminal::wormhole::SubshellSource;
|
||||||
use crate::terminal::writeable_pty::{PtyIntent, PtyIntentEvent, TerminalSurface};
|
use crate::terminal::writeable_pty::{PtyIntent, PtyIntentEvent, TerminalSurface};
|
||||||
use crate::terminal::{
|
use crate::terminal::{
|
||||||
block_list_element::BlockHoverAction,
|
block_list_element::BlockHoverAction,
|
||||||
@@ -634,10 +633,6 @@ const KNOWN_ISSUES_URL: &str =
|
|||||||
const PROMPT_COMPATIBILITY_URL: &str =
|
const PROMPT_COMPATIBILITY_URL: &str =
|
||||||
"https://docs.warp.dev/terminal/appearance/prompt#custom-prompt-compatibility-table";
|
"https://docs.warp.dev/terminal/appearance/prompt#custom-prompt-compatibility-table";
|
||||||
|
|
||||||
/// Link to troubleshooting steps for ControlMaster errors.
|
|
||||||
const CONTROLMASTER_ISSUES_URL: &str =
|
|
||||||
"https://docs.warp.dev/terminal/warpify/ssh-legacy#troubleshooting";
|
|
||||||
|
|
||||||
/// Link to instructions on how to update p10k.
|
/// Link to instructions on how to update p10k.
|
||||||
const P10K_UPDATE_INSTRUCTIONS_URL: &str =
|
const P10K_UPDATE_INSTRUCTIONS_URL: &str =
|
||||||
"https://github.com/romkatv/powerlevel10k#how-do-i-update-powerlevel10k";
|
"https://github.com/romkatv/powerlevel10k#how-do-i-update-powerlevel10k";
|
||||||
@@ -676,10 +671,10 @@ enum Osc52ClipboardBlockedType {
|
|||||||
/// Key used in user defaults to save whether the user has seen the banner.
|
/// Key used in user defaults to save whether the user has seen the banner.
|
||||||
pub const ALIAS_EXPANSION_BANNER_SEEN_KEY: &str = "AliasExpansionBannerSeen";
|
pub const ALIAS_EXPANSION_BANNER_SEEN_KEY: &str = "AliasExpansionBannerSeen";
|
||||||
|
|
||||||
/// Delay between receiving preexec hook for a command we want to auto-warpify
|
/// Delay between receiving preexec hook for a command we want to auto-wormhole
|
||||||
/// and triggering the warpification (subshell bootstrapping).
|
/// and triggering the wormholing (subshell bootstrapping).
|
||||||
/// Reached this number after experimenting with different values to find a reliable delay.
|
/// Reached this number after experimenting with different values to find a reliable delay.
|
||||||
const AUTO_WARPIFY_DELAY: u64 = 1000;
|
const AUTO_WORMHOLE_DELAY: u64 = 1000;
|
||||||
|
|
||||||
/// Binding names to be customized if the user indicates they prefer
|
/// Binding names to be customized if the user indicates they prefer
|
||||||
/// Emacs-style keybindings instead of IDE-style keybindings.
|
/// Emacs-style keybindings instead of IDE-style keybindings.
|
||||||
@@ -2755,7 +2750,7 @@ pub struct TerminalView {
|
|||||||
|
|
||||||
onboarding_callout_view: Option<ViewHandle<onboarding::OnboardingCalloutView>>,
|
onboarding_callout_view: Option<ViewHandle<onboarding::OnboardingCalloutView>>,
|
||||||
|
|
||||||
/// The type of the subshell that we will bootstrap/"warpify"" on the next [`AfterBlockStarted`]
|
/// The type of the subshell that we will bootstrap/"wormhole"" on the next [`AfterBlockStarted`]
|
||||||
/// terminal model event. Will only be `Some` with a [`ShellType`] we can bootstrap.
|
/// terminal model event. Will only be `Some` with a [`ShellType`] we can bootstrap.
|
||||||
pending_auto_bootstrap_shell_type: Option<ShellType>,
|
pending_auto_bootstrap_shell_type: Option<ShellType>,
|
||||||
env_vars: Vec<EnvVar>,
|
env_vars: Vec<EnvVar>,
|
||||||
@@ -2817,7 +2812,7 @@ pub struct TerminalView {
|
|||||||
|
|
||||||
find_model: ModelHandle<TerminalFindModel>,
|
find_model: ModelHandle<TerminalFindModel>,
|
||||||
|
|
||||||
warpify_state: WarpifyState,
|
wormhole_state: WormholeState,
|
||||||
|
|
||||||
/// The keystroke bound to canceling a command.
|
/// The keystroke bound to canceling a command.
|
||||||
///
|
///
|
||||||
@@ -3947,12 +3942,12 @@ impl TerminalView {
|
|||||||
|
|
||||||
let control_master_error_banner = ctx.add_typed_action_view(|_| {
|
let control_master_error_banner = ctx.add_typed_action_view(|_| {
|
||||||
Banner::new_permanently_dismissible(BannerTextContent::formatted_text(vec![
|
Banner::new_permanently_dismissible(BannerTextContent::formatted_text(vec![
|
||||||
FormattedTextFragment::plain_text("Seems like your completions are not working ("),
|
FormattedTextFragment::plain_text(
|
||||||
FormattedTextFragment::hyperlink("more info", CONTROLMASTER_ISSUES_URL),
|
"Your completions may not be working. Enabling the Wormhole helper in ",
|
||||||
FormattedTextFragment::plain_text("). Enabling the SSH extension in "),
|
),
|
||||||
FormattedTextFragment::hyperlink_action(
|
FormattedTextFragment::hyperlink_action(
|
||||||
"settings",
|
"settings",
|
||||||
TerminalAction::ShowWarpifySettings,
|
TerminalAction::ShowWormholeSettings,
|
||||||
),
|
),
|
||||||
FormattedTextFragment::plain_text(" may resolve this issue."),
|
FormattedTextFragment::plain_text(" may resolve this issue."),
|
||||||
]))
|
]))
|
||||||
@@ -4436,7 +4431,7 @@ impl TerminalView {
|
|||||||
input_position_id,
|
input_position_id,
|
||||||
input_hoverable_handle: Default::default(),
|
input_hoverable_handle: Default::default(),
|
||||||
find_model,
|
find_model,
|
||||||
warpify_state: Default::default(),
|
wormhole_state: Default::default(),
|
||||||
cancel_command_keystroke: keybinding_name_to_keystroke(CANCEL_COMMAND_KEYBINDING, ctx),
|
cancel_command_keystroke: keybinding_name_to_keystroke(CANCEL_COMMAND_KEYBINDING, ctx),
|
||||||
is_file_drop_target: false,
|
is_file_drop_target: false,
|
||||||
is_ssh_file_uploader: false,
|
is_ssh_file_uploader: false,
|
||||||
@@ -4582,7 +4577,7 @@ impl TerminalView {
|
|||||||
me.show_ssh_remote_server_failed_banner(
|
me.show_ssh_remote_server_failed_banner(
|
||||||
*session_id,
|
*session_id,
|
||||||
remote_server::transport::UserFacingError {
|
remote_server::transport::UserFacingError {
|
||||||
body: "Failed to start SSH extension".into(),
|
body: "Failed to start Wormhole helper".into(),
|
||||||
detail: if error.is_empty() {
|
detail: if error.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
@@ -9116,7 +9111,7 @@ impl TerminalView {
|
|||||||
/// events, allow it to handle the event.
|
/// events, allow it to handle the event.
|
||||||
///
|
///
|
||||||
/// TODO(CORE-3415): We should probably remove the FixedBindings for ctrl-c
|
/// TODO(CORE-3415): We should probably remove the FixedBindings for ctrl-c
|
||||||
/// in the SSH warpification blocks and handle them here as well.
|
/// in the SSH wormholing blocks and handle them here as well.
|
||||||
fn maybe_handle_ctrl_c_in_rich_content_block(&mut self, ctx: &mut ViewContext<Self>) {
|
fn maybe_handle_ctrl_c_in_rich_content_block(&mut self, ctx: &mut ViewContext<Self>) {
|
||||||
if self.active_ai_block(ctx).is_some() {
|
if self.active_ai_block(ctx).is_some() {
|
||||||
self.cancel_active_conversation_via_status_bar(ctx);
|
self.cancel_active_conversation_via_status_bar(ctx);
|
||||||
@@ -9192,7 +9187,7 @@ impl TerminalView {
|
|||||||
/// the workspace to derive `PendingRemoteSession` without storing
|
/// the workspace to derive `PendingRemoteSession` without storing
|
||||||
/// mutable state on the workspace itself.
|
/// mutable state on the workspace itself.
|
||||||
pub fn has_pending_ssh_command(&self) -> bool {
|
pub fn has_pending_ssh_command(&self) -> bool {
|
||||||
self.warpify_state.get_pending_ssh_host().is_some() && self.is_long_running()
|
self.wormhole_state.get_pending_ssh_host().is_some() && self.is_long_running()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like `is_long_running`, but also requires the user to be in control of the command
|
/// Like `is_long_running`, but also requires the user to be in control of the command
|
||||||
@@ -9779,7 +9774,7 @@ impl TerminalView {
|
|||||||
.is_some_and(|session| {
|
.is_some_and(|session| {
|
||||||
matches!(
|
matches!(
|
||||||
session.session_type(),
|
session.session_type(),
|
||||||
SessionType::WarpifiedRemote {
|
SessionType::WormholedRemote {
|
||||||
host_id: Some(_),
|
host_id: Some(_),
|
||||||
..
|
..
|
||||||
}
|
}
|
||||||
@@ -9839,7 +9834,7 @@ impl TerminalView {
|
|||||||
triggered_by_rc_file_snippet: bool,
|
triggered_by_rc_file_snippet: bool,
|
||||||
ctx: &mut ViewContext<Self>,
|
ctx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
self.dismiss_warpify_banner(&RememberForWarpification::DoNotRememberSubshellCommand, ctx);
|
self.dismiss_wormhole_banner(&RememberForWormholing::DoNotRememberSubshellCommand, ctx);
|
||||||
|
|
||||||
// Record the active long-running block so we can hide it later once the remote
|
// Record the active long-running block so we can hide it later once the remote
|
||||||
// actually confirms subshell bootstrap is in progress.
|
// actually confirms subshell bootstrap is in progress.
|
||||||
@@ -9852,7 +9847,7 @@ impl TerminalView {
|
|||||||
.is_active_and_long_running()
|
.is_active_and_long_running()
|
||||||
{
|
{
|
||||||
let block_id = model.block_list().active_block_id().clone();
|
let block_id = model.block_list().active_block_id().clone();
|
||||||
self.warpify_state.set_block_id(block_id);
|
self.wormhole_state.set_block_id(block_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9875,7 +9870,7 @@ impl TerminalView {
|
|||||||
|
|
||||||
/// Util method to update the ssh block, with a lock
|
/// Util method to update the ssh block, with a lock
|
||||||
fn update_long_running_ssh_block_with_lock(&self, f: impl FnOnce(&mut Block)) -> bool {
|
fn update_long_running_ssh_block_with_lock(&self, f: impl FnOnce(&mut Block)) -> bool {
|
||||||
if let Some(block_id) = self.warpify_state.block_id() {
|
if let Some(block_id) = self.wormhole_state.block_id() {
|
||||||
if let Some(block) = self
|
if let Some(block) = self
|
||||||
.model
|
.model
|
||||||
.lock()
|
.lock()
|
||||||
@@ -9897,15 +9892,15 @@ impl TerminalView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn clear_ssh_blocks(&mut self, ctx: &mut ViewContext<Self>) {
|
fn clear_ssh_blocks(&mut self, ctx: &mut ViewContext<Self>) {
|
||||||
self.dismiss_warpify_banner(&RememberForWarpification::DoNotRememberSSHHost, ctx);
|
self.dismiss_wormhole_banner(&RememberForWormholing::DoNotRememberSSHHost, ctx);
|
||||||
if let Some(ssh_block) = self.warpify_state.ssh_block_state() {
|
if let Some(ssh_block) = self.wormhole_state.ssh_block_state() {
|
||||||
let view_id = ssh_block.get_block_view_id();
|
let view_id = ssh_block.get_block_view_id();
|
||||||
|
|
||||||
self.remove_ssh_block_by_id(view_id);
|
self.remove_ssh_block_by_id(view_id);
|
||||||
|
|
||||||
self.redetermine_global_focus(ctx);
|
self.redetermine_global_focus(ctx);
|
||||||
|
|
||||||
self.warpify_state.clear_ssh_block_state();
|
self.wormhole_state.clear_ssh_block_state();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9915,7 +9910,6 @@ impl TerminalView {
|
|||||||
spawning_command,
|
spawning_command,
|
||||||
subshell_info,
|
subshell_info,
|
||||||
shell,
|
shell,
|
||||||
session_type,
|
|
||||||
..
|
..
|
||||||
}: SessionBootstrappedEvent,
|
}: SessionBootstrappedEvent,
|
||||||
ctx: &mut ViewContext<Self>,
|
ctx: &mut ViewContext<Self>,
|
||||||
@@ -9929,18 +9923,8 @@ impl TerminalView {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let warpification_source = match session_type {
|
|
||||||
BootstrapSessionType::WarpifiedRemote => WarpificationSource::Ssh,
|
|
||||||
BootstrapSessionType::Local => WarpificationSource::Subshell,
|
|
||||||
};
|
|
||||||
let ssh_success_block_handle = ctx.add_typed_action_view(|ctx| {
|
let ssh_success_block_handle = ctx.add_typed_action_view(|ctx| {
|
||||||
WarpifySuccessBlock::new(
|
WormholeSuccessBlock::new(spawning_command, subshell_info, shell, ctx)
|
||||||
warpification_source,
|
|
||||||
spawning_command,
|
|
||||||
subshell_info,
|
|
||||||
shell,
|
|
||||||
ctx,
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
ctx.subscribe_to_view(&ssh_success_block_handle, move |me, _, event, ctx| {
|
ctx.subscribe_to_view(&ssh_success_block_handle, move |me, _, event, ctx| {
|
||||||
me.handle_ssh_success_block_events(event, ctx);
|
me.handle_ssh_success_block_events(event, ctx);
|
||||||
@@ -9948,9 +9932,9 @@ impl TerminalView {
|
|||||||
|
|
||||||
self.clear_ssh_blocks(ctx);
|
self.clear_ssh_blocks(ctx);
|
||||||
self.insert_rich_content(
|
self.insert_rich_content(
|
||||||
Some(RichContentType::WarpifySuccessBlock),
|
Some(RichContentType::WormholeSuccessBlock),
|
||||||
ssh_success_block_handle.clone(),
|
ssh_success_block_handle.clone(),
|
||||||
Some(RichContentMetadata::WarpifySuccessBlock {
|
Some(RichContentMetadata::WormholeSuccessBlock {
|
||||||
bootstrap_success_block_handle: ssh_success_block_handle.clone(),
|
bootstrap_success_block_handle: ssh_success_block_handle.clone(),
|
||||||
}),
|
}),
|
||||||
RichContentInsertionPosition::Append {
|
RichContentInsertionPosition::Append {
|
||||||
@@ -9958,30 +9942,30 @@ impl TerminalView {
|
|||||||
},
|
},
|
||||||
ctx,
|
ctx,
|
||||||
);
|
);
|
||||||
self.warpify_state
|
self.wormhole_state
|
||||||
.set_ssh_block_state(SshBlockState::WarpifySuccess {
|
.set_ssh_block_state(SshBlockState::WormholeSuccess {
|
||||||
handle: ssh_success_block_handle,
|
handle: ssh_success_block_handle,
|
||||||
});
|
});
|
||||||
let active_session_id = self.active_block_session_id();
|
let active_session_id = self.active_block_session_id();
|
||||||
self.warpify_state.on_warpify_start(active_session_id);
|
self.wormhole_state.on_wormhole_start(active_session_id);
|
||||||
self.refresh_warp_prompt(ctx);
|
self.refresh_warp_prompt(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_ssh_success_block_events(
|
fn handle_ssh_success_block_events(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: &WarpifySuccessBlockEvent,
|
event: &WormholeSuccessBlockEvent,
|
||||||
ctx: &mut ViewContext<Self>,
|
ctx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
WarpifySuccessBlockEvent::OpenWarpifySettings => {
|
WormholeSuccessBlockEvent::OpenWormholeSettings => {
|
||||||
ctx.emit(Event::OpenSettings(SettingsSection::Warpify));
|
ctx.emit(Event::OpenSettings(SettingsSection::Wormhole));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dismiss_warpify_banner(
|
fn dismiss_wormhole_banner(
|
||||||
&mut self,
|
&mut self,
|
||||||
remember_command: &RememberForWarpification,
|
remember_command: &RememberForWormholing,
|
||||||
ctx: &mut ViewContext<Self>,
|
ctx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
{
|
{
|
||||||
@@ -9989,54 +9973,54 @@ impl TerminalView {
|
|||||||
model.block_list_mut().set_active_block_banner(None);
|
model.block_list_mut().set_active_block_banner(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also clear the warpify footer so it doesn't linger after warpification
|
// Also clear the wormhole footer so it doesn't linger after wormholing
|
||||||
// starts, fails, or is cancelled.
|
// starts, fails, or is cancelled.
|
||||||
if FeatureFlag::WarpifyFooter.is_enabled() {
|
if FeatureFlag::WormholeFooter.is_enabled() {
|
||||||
self.use_agent_footer.update(ctx, |footer, ctx| {
|
self.use_agent_footer.update(ctx, |footer, ctx| {
|
||||||
footer.clear_warpify(ctx);
|
footer.clear_wormhole(ctx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
match remember_command {
|
match remember_command {
|
||||||
RememberForWarpification::RememberSubshellCommand(command) => {
|
RememberForWormholing::RememberSubshellCommand(command) => {
|
||||||
WarpifySettings::handle(ctx).update(ctx, |warpify, ctx| {
|
WormholeSettings::handle(ctx).update(ctx, |wormhole, ctx| {
|
||||||
warpify.denylist_subshell_command(command, ctx);
|
wormhole.denylist_subshell_command(command, ctx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
RememberForWarpification::RememberSSHHost(host) => {
|
RememberForWormholing::RememberSSHHost(host) => {
|
||||||
WarpifySettings::handle(ctx).update(ctx, |warpify, ctx| {
|
WormholeSettings::handle(ctx).update(ctx, |wormhole, ctx| {
|
||||||
warpify.denylist_ssh_host(host, ctx);
|
wormhole.denylist_ssh_host(host, ctx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
RememberForWarpification::DoNotRememberSubshellCommand
|
RememberForWormholing::DoNotRememberSubshellCommand
|
||||||
| RememberForWarpification::DoNotRememberSSHHost => {}
|
| RememberForWormholing::DoNotRememberSSHHost => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_warpify_banner(
|
fn show_wormhole_banner(
|
||||||
&mut self,
|
&mut self,
|
||||||
command: String,
|
command: String,
|
||||||
title: &str,
|
title: &str,
|
||||||
lowercase_title: &str,
|
lowercase_title: &str,
|
||||||
warpify_keybinding: Option<Keystroke>,
|
wormhole_keybinding: Option<Keystroke>,
|
||||||
telemetry_event: TelemetryEvent,
|
telemetry_event: TelemetryEvent,
|
||||||
ctx: &mut ViewContext<Self>,
|
ctx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
if FeatureFlag::WarpifyFooter.is_enabled() {
|
if FeatureFlag::WormholeFooter.is_enabled() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut model = self.model.lock();
|
let mut model = self.model.lock();
|
||||||
|
|
||||||
// Shared session viewers can't initiate warpification currently.
|
// Shared session viewers can't initiate wormholing currently.
|
||||||
// Don't show the warpify banner when an agent is monitoring the command either.
|
// Don't show the wormhole banner when an agent is monitoring the command either.
|
||||||
if model.shared_session_status().is_viewer()
|
if model.shared_session_status().is_viewer()
|
||||||
|| model.block_list().active_block().is_agent_monitoring()
|
|| model.block_list().active_block().is_agent_monitoring()
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let a11y_message = match &warpify_keybinding {
|
let a11y_message = match &wormhole_keybinding {
|
||||||
Some(keystroke) => format!(
|
Some(keystroke) => format!(
|
||||||
"You can press {} to Wormhole this {} for more Galaxy features.",
|
"You can press {} to Wormhole this {} for more Galaxy features.",
|
||||||
keystroke.displayed(),
|
keystroke.displayed(),
|
||||||
@@ -10047,8 +10031,8 @@ impl TerminalView {
|
|||||||
|
|
||||||
model
|
model
|
||||||
.block_list_mut()
|
.block_list_mut()
|
||||||
.set_active_block_banner(Some(WithinBlockBanner::WarpifyBanner(
|
.set_active_block_banner(Some(WithinBlockBanner::WormholeBanner(
|
||||||
WarpifyBannerState::new(command, warpify_keybinding),
|
WormholeBannerState::new(command, wormhole_keybinding),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
let a11y_content = AccessibilityContent::new(
|
let a11y_content = AccessibilityContent::new(
|
||||||
@@ -11310,7 +11294,7 @@ impl TerminalView {
|
|||||||
/// Returns true if the block is considered remote.
|
/// Returns true if the block is considered remote.
|
||||||
///
|
///
|
||||||
/// Note that we don't know for sure if a block is remote, because we can only detect
|
/// Note that we don't know for sure if a block is remote, because we can only detect
|
||||||
/// warpified remote blocks.
|
/// wormholed remote blocks.
|
||||||
///
|
///
|
||||||
/// For some organizations, we accept a regex list that we run against commands to
|
/// For some organizations, we accept a regex list that we run against commands to
|
||||||
/// further make the determination.
|
/// further make the determination.
|
||||||
@@ -11320,7 +11304,7 @@ impl TerminalView {
|
|||||||
command: Option<&str>,
|
command: Option<&str>,
|
||||||
app: &AppContext,
|
app: &AppContext,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let is_warpified_remote = session_id
|
let is_wormholed_remote = session_id
|
||||||
.map(|id| {
|
.map(|id| {
|
||||||
self.sessions
|
self.sessions
|
||||||
.as_ref(app)
|
.as_ref(app)
|
||||||
@@ -11330,7 +11314,7 @@ impl TerminalView {
|
|||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if is_warpified_remote {
|
if is_wormholed_remote {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11980,7 +11964,8 @@ impl TerminalView {
|
|||||||
|
|
||||||
// If this block ran a possible subshell command, and it exited before the 1s timer
|
// If this block ran a possible subshell command, and it exited before the 1s timer
|
||||||
// completed, abort showing the banner.
|
// completed, abort showing the banner.
|
||||||
if let Some(abort_handle) = self.warpify_state.take_subshell_banner_abort_handle() {
|
if let Some(abort_handle) = self.wormhole_state.take_subshell_banner_abort_handle()
|
||||||
|
{
|
||||||
abort_handle.abort();
|
abort_handle.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12022,9 +12007,9 @@ impl TerminalView {
|
|||||||
self.on_user_block_completed(&block_completed_event.block_id, ctx);
|
self.on_user_block_completed(&block_completed_event.block_id, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear any stale warpify footer so it doesn't leak into the next command's footer rendering.
|
// Clear any stale wormhole footer so it doesn't leak into the next command's footer rendering.
|
||||||
self.use_agent_footer.update(ctx, |footer, ctx| {
|
self.use_agent_footer.update(ctx, |footer, ctx| {
|
||||||
footer.clear_warpify(ctx);
|
footer.clear_wormhole(ctx);
|
||||||
});
|
});
|
||||||
self.hide_use_agent_footer_in_blocklist(ctx);
|
self.hide_use_agent_footer_in_blocklist(ctx);
|
||||||
if matches!(block_completed_event.block_type, BlockType::User(_)) {
|
if matches!(block_completed_event.block_type, BlockType::User(_)) {
|
||||||
@@ -12109,7 +12094,7 @@ impl TerminalView {
|
|||||||
self.drop_hidden_passive_ai_blocks(ctx);
|
self.drop_hidden_passive_ai_blocks(ctx);
|
||||||
|
|
||||||
// If the first word of the command is a shell alias, expand it
|
// If the first word of the command is a shell alias, expand it
|
||||||
// for subshell/SSH detection. This enables warpification for
|
// for subshell/SSH detection. This enables wormholing for
|
||||||
// aliased SSH commands (e.g. `alias myssh='ssh user@host'`).
|
// aliased SSH commands (e.g. `alias myssh='ssh user@host'`).
|
||||||
let expanded_command = self
|
let expanded_command = self
|
||||||
.active_block_session_id()
|
.active_block_session_id()
|
||||||
@@ -12119,19 +12104,19 @@ impl TerminalView {
|
|||||||
let alias_value = session.alias_value(first_word)?;
|
let alias_value = session.alias_value(first_word)?;
|
||||||
Some(format!("{alias_value}{rest}"))
|
Some(format!("{alias_value}{rest}"))
|
||||||
});
|
});
|
||||||
let warpify_command = expanded_command.as_deref().unwrap_or(command.as_str());
|
let wormhole_command = expanded_command.as_deref().unwrap_or(command.as_str());
|
||||||
|
|
||||||
// Check if the current running command spawns a subshell eligible for Warpification.
|
// Check if the current running command spawns a subshell eligible for Wormholing.
|
||||||
let shell_family = self.shell_family(ctx);
|
let shell_family = self.shell_family(ctx);
|
||||||
let warpify_settings = WarpifySettings::as_ref(ctx);
|
let wormhole_settings = WormholeSettings::as_ref(ctx);
|
||||||
let is_compatible_subshell_command = warpify_settings
|
let is_compatible_subshell_command = wormhole_settings
|
||||||
.is_compatible_subshell_command(command, shell_family)
|
.is_compatible_subshell_command(command, shell_family)
|
||||||
|| warpify_settings
|
|| wormhole_settings
|
||||||
.is_compatible_subshell_command(warpify_command, shell_family);
|
.is_compatible_subshell_command(wormhole_command, shell_family);
|
||||||
let command_is_denylisted = warpify_settings
|
let command_is_denylisted = wormhole_settings
|
||||||
.is_denylisted_subshell_command(command)
|
.is_denylisted_subshell_command(command)
|
||||||
|| warpify_settings.is_denylisted_subshell_command(warpify_command);
|
|| wormhole_settings.is_denylisted_subshell_command(wormhole_command);
|
||||||
// Never warpify or surface warpification for agent-requested commands.
|
// Never wormhole or surface wormholing for agent-requested commands.
|
||||||
let has_ai_metadata = self
|
let has_ai_metadata = self
|
||||||
.model
|
.model
|
||||||
.lock()
|
.lock()
|
||||||
@@ -12142,30 +12127,30 @@ impl TerminalView {
|
|||||||
|
|
||||||
if is_compatible_subshell_command {
|
if is_compatible_subshell_command {
|
||||||
if command_is_denylisted || has_ai_metadata {
|
if command_is_denylisted || has_ai_metadata {
|
||||||
// Don't auto-warpify or surface warpification for these commands.
|
// Don't auto-wormhole or surface wormholing for these commands.
|
||||||
} else if let Some(shell_type) = self.pending_auto_bootstrap_shell_type.take() {
|
} else if let Some(shell_type) = self.pending_auto_bootstrap_shell_type.take() {
|
||||||
// If there is a subshell we're waiting to bootstrap until we receive
|
// If there is a subshell we're waiting to bootstrap until we receive
|
||||||
// the preexec hook, now we can bootstrap it.
|
// the preexec hook, now we can bootstrap it.
|
||||||
let auto_warpify_abort_handle = ctx.spawn_abortable(
|
let auto_wormhole_abort_handle = ctx.spawn_abortable(
|
||||||
Timer::after(Duration::from_millis(AUTO_WARPIFY_DELAY)),
|
Timer::after(Duration::from_millis(AUTO_WORMHOLE_DELAY)),
|
||||||
move |me, _, ctx| {
|
move |me, _, ctx| {
|
||||||
me.trigger_subshell_bootstrap(Some(shell_type), false, ctx);
|
me.trigger_subshell_bootstrap(Some(shell_type), false, ctx);
|
||||||
},
|
},
|
||||||
|_, _| (),
|
|_, _| (),
|
||||||
);
|
);
|
||||||
self.warpify_state
|
self.wormhole_state
|
||||||
.add_auto_warpify_abort_handle(auto_warpify_abort_handle);
|
.add_auto_wormhole_abort_handle(auto_wormhole_abort_handle);
|
||||||
} else {
|
} else {
|
||||||
// Wait 1 second before showing the banner, just to make sure the
|
// Wait 1 second before showing the banner, just to make sure the
|
||||||
// command stays running for a bit. If the command fails instantly,
|
// command stays running for a bit. If the command fails instantly,
|
||||||
// we don't want to flicker the banner away so quickly.
|
// we don't want to flicker the banner away so quickly.
|
||||||
let command = command.clone();
|
let command = command.clone();
|
||||||
self.warpify_state
|
self.wormhole_state
|
||||||
.add_subshell_banner_abort_handle(ctx.spawn_abortable(
|
.add_subshell_banner_abort_handle(ctx.spawn_abortable(
|
||||||
Timer::after(*SUBSHELL_BANNER_DELAY_DURATION),
|
Timer::after(*SUBSHELL_BANNER_DELAY_DURATION),
|
||||||
|view, _, ctx| {
|
|view, _, ctx| {
|
||||||
if FeatureFlag::WarpifyFooter.is_enabled() {
|
if FeatureFlag::WormholeFooter.is_enabled() {
|
||||||
view.show_warpify_footer(ctx);
|
view.show_wormhole_footer(ctx);
|
||||||
} else {
|
} else {
|
||||||
view.handle_action(
|
view.handle_action(
|
||||||
&TerminalAction::ShowSubshellBanner(command),
|
&TerminalAction::ShowSubshellBanner(command),
|
||||||
@@ -12179,14 +12164,14 @@ impl TerminalView {
|
|||||||
} else {
|
} else {
|
||||||
if !has_ai_metadata {
|
if !has_ai_metadata {
|
||||||
if let Some(ssh_host) =
|
if let Some(ssh_host) =
|
||||||
parse_interactive_ssh_command(warpify_command).map(|cmd| cmd.host)
|
parse_interactive_ssh_command(wormhole_command).map(|cmd| cmd.host)
|
||||||
{
|
{
|
||||||
self.warpify_state
|
self.wormhole_state
|
||||||
.set_pending_ssh_host(warpify_command.to_string(), ssh_host);
|
.set_pending_ssh_host(wormhole_command.to_string(), ssh_host);
|
||||||
self.model.lock().start_notify_on_end_of_ssh_login();
|
self.model.lock().start_notify_on_end_of_ssh_login();
|
||||||
ctx.emit(Event::TerminalViewStateChanged);
|
ctx.emit(Event::TerminalViewStateChanged);
|
||||||
} else {
|
} else {
|
||||||
self.warpify_state.clear_pending_ssh_host();
|
self.wormhole_state.clear_pending_ssh_host();
|
||||||
|
|
||||||
ctx.spawn(
|
ctx.spawn(
|
||||||
Timer::after(Duration::from_millis(
|
Timer::after(Duration::from_millis(
|
||||||
@@ -12284,14 +12269,14 @@ impl TerminalView {
|
|||||||
cloud_workflow_id,
|
cloud_workflow_id,
|
||||||
cloud_env_var_collection_id,
|
cloud_env_var_collection_id,
|
||||||
}) => {
|
}) => {
|
||||||
// To automatically warpify a subshell, we run the relevant command
|
// To automatically wormhole a subshell, we run the relevant command
|
||||||
// subshell and create a future to delay bootstrapping the subshell long enough for
|
// subshell and create a future to delay bootstrapping the subshell long enough for
|
||||||
// the command to complete. We receive AfterBlockCompleted if the subshell command
|
// the command to complete. We receive AfterBlockCompleted if the subshell command
|
||||||
// returns an error or the user exits the subshell. Here we abort the future to
|
// returns an error or the user exits the subshell. Here we abort the future to
|
||||||
// avoid an attempt to trigger bootstrapping if the subshell command failed. If the
|
// avoid an attempt to trigger bootstrapping if the subshell command failed. If the
|
||||||
// future already resolved, abort has no effect. We handle this as early as possible
|
// future already resolved, abort has no effect. We handle this as early as possible
|
||||||
// because the abort is time sensitive.
|
// because the abort is time sensitive.
|
||||||
self.warpify_state.abort_auto_warpify();
|
self.wormhole_state.abort_auto_wormhole();
|
||||||
|
|
||||||
let active_session = self
|
let active_session = self
|
||||||
.active_block_session_id()
|
.active_block_session_id()
|
||||||
@@ -12366,14 +12351,14 @@ impl TerminalView {
|
|||||||
}
|
}
|
||||||
let active_session_id = self.active_block_session_id();
|
let active_session_id = self.active_block_session_id();
|
||||||
if let Some(block_id) = self
|
if let Some(block_id) = self
|
||||||
.warpify_state
|
.wormhole_state
|
||||||
.get_completed_warpify_session_id(active_session_id, ctx)
|
.get_completed_wormhole_session_id(active_session_id, ctx)
|
||||||
{
|
{
|
||||||
self.remove_ssh_block_by_id(block_id);
|
self.remove_ssh_block_by_id(block_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.dismiss_warpify_banner(
|
self.dismiss_wormhole_banner(
|
||||||
&RememberForWarpification::DoNotRememberSubshellCommand,
|
&RememberForWormholing::DoNotRememberSubshellCommand,
|
||||||
ctx,
|
ctx,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -12868,7 +12853,7 @@ impl TerminalView {
|
|||||||
.active_block()
|
.active_block()
|
||||||
.agent_interaction_metadata()
|
.agent_interaction_metadata()
|
||||||
.is_some();
|
.is_some();
|
||||||
// Never warpify for agent-requested commands.
|
// Never wormhole for agent-requested commands.
|
||||||
if has_ai_metadata {
|
if has_ai_metadata {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -13063,8 +13048,8 @@ impl TerminalView {
|
|||||||
me.remove_ssh_remote_server_choice_block(session_id, ctx);
|
me.remove_ssh_remote_server_choice_block(session_id, ctx);
|
||||||
ctx.emit(Event::RemoteServerSkipRequested { session_id });
|
ctx.emit(Event::RemoteServerSkipRequested { session_id });
|
||||||
}
|
}
|
||||||
SshRemoteServerChoiceViewEvent::OpenWarpifySettings => {
|
SshRemoteServerChoiceViewEvent::OpenWormholeSettings => {
|
||||||
ctx.emit(Event::OpenSettings(SettingsSection::Warpify));
|
ctx.emit(Event::OpenSettings(SettingsSection::Wormhole));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -13259,7 +13244,7 @@ impl TerminalView {
|
|||||||
|
|
||||||
// Clear the pending flag up front so the notice is shown at most once, even if the
|
// Clear the pending flag up front so the notice is shown at most once, even if the
|
||||||
// banner is dismissed without interaction or the session ends early.
|
// banner is dismissed without interaction or the session ends early.
|
||||||
WarpifySettings::handle(ctx).update(ctx, |settings, ctx| {
|
WormholeSettings::handle(ctx).update(ctx, |settings, ctx| {
|
||||||
settings.mark_tmux_deprecation_notice_shown(ctx);
|
settings.mark_tmux_deprecation_notice_shown(ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -13690,7 +13675,7 @@ impl TerminalView {
|
|||||||
self.update_incompatible_configuration_banner(session.shell().plugins(), ctx);
|
self.update_incompatible_configuration_banner(session.shell().plugins(), ctx);
|
||||||
|
|
||||||
if let Some(subshell_info) = session.subshell_info() {
|
if let Some(subshell_info) = session.subshell_info() {
|
||||||
self.warpify_state
|
self.wormhole_state
|
||||||
.add_subshell_separator(subshell_info, self.model.clone(), ctx);
|
.add_subshell_separator(subshell_info, self.model.clone(), ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13772,22 +13757,23 @@ impl TerminalView {
|
|||||||
.spawn(async move { session_clone2.load_all_builtins().await })
|
.spawn(async move { session_clone2.load_all_builtins().await })
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
// If we were waiting for a successful warpification, it's come. Stop the timeout.
|
// If we were waiting for a successful wormholing, it's come. Stop the timeout.
|
||||||
self.warpify_state.abort_ssh_warpify_timeout();
|
self.wormhole_state.abort_ssh_wormhole_timeout();
|
||||||
|
|
||||||
let is_warpified_remote = matches!(
|
let is_wormholed_remote = matches!(
|
||||||
bootstrap_event.session_type,
|
bootstrap_event.session_type,
|
||||||
BootstrapSessionType::WarpifiedRemote
|
BootstrapSessionType::WormholedRemote
|
||||||
);
|
);
|
||||||
if bootstrap_event.subshell_info.is_some() {
|
if bootstrap_event.subshell_info.is_some() {
|
||||||
self.add_bootstrap_success_block(bootstrap_event, ctx);
|
self.add_bootstrap_success_block(bootstrap_event, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show the one-time tmux deprecation notice when an SSH session successfully
|
// Show the one-time tmux deprecation notice when an SSH session successfully
|
||||||
// warpifies. The end-of-ssh-login path (`handle_detected_end_of_ssh_login`) only
|
// wormholes. The end-of-ssh-login path (`handle_detected_end_of_ssh_login`) only
|
||||||
// fires for sessions that stay unwarpified, since warpification replaces the
|
// fires for sessions that stay unwormholed, since wormholing replaces the
|
||||||
// original ssh block before login detection can confirm completion.
|
// original ssh block before login detection can confirm completion.
|
||||||
if is_warpified_remote && WarpifySettings::as_ref(ctx).should_show_tmux_deprecation_notice()
|
if is_wormholed_remote
|
||||||
|
&& WormholeSettings::as_ref(ctx).should_show_tmux_deprecation_notice()
|
||||||
{
|
{
|
||||||
self.show_ssh_tmux_deprecation_banner(session_id, ctx);
|
self.show_ssh_tmux_deprecation_banner(session_id, ctx);
|
||||||
}
|
}
|
||||||
@@ -15201,7 +15187,7 @@ impl TerminalView {
|
|||||||
// https://github.com/warpdotdev/command-corrections/blob/df7848d4fb3da7883623e959889a296a07d88053/src/rules/cd/mod.rs#L31-L36
|
// https://github.com/warpdotdev/command-corrections/blob/df7848d4fb3da7883623e959889a296a07d88053/src/rules/cd/mod.rs#L31-L36
|
||||||
// We don't currently support dynamic rules over SSH, so we should not attempt to correct commands if
|
// We don't currently support dynamic rules over SSH, so we should not attempt to correct commands if
|
||||||
// inside ssh session.
|
// inside ssh session.
|
||||||
let is_ssh_command = SshWarpifyCommand::matches(input).is_some();
|
let is_ssh_command = SshWormholeCommand::matches(input).is_some();
|
||||||
if is_ssh_command {
|
if is_ssh_command {
|
||||||
return vec![];
|
return vec![];
|
||||||
}
|
}
|
||||||
@@ -19105,7 +19091,7 @@ impl TerminalView {
|
|||||||
.and_then(|id| self.sessions.as_ref(ctx).get(id))
|
.and_then(|id| self.sessions.as_ref(ctx).get(id))
|
||||||
{
|
{
|
||||||
if let Some(info) = session.subshell_info() {
|
if let Some(info) = session.subshell_info() {
|
||||||
self.warpify_state
|
self.wormhole_state
|
||||||
.add_subshell_separator(info, self.model.clone(), ctx);
|
.add_subshell_separator(info, self.model.clone(), ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20152,9 +20138,9 @@ impl TerminalView {
|
|||||||
env_var_collection_block.clear_selection(ctx);
|
env_var_collection_block.clear_selection(ctx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Some(RichContentMetadata::WarpifySuccessBlock { .. }) => {
|
Some(RichContentMetadata::WormholeSuccessBlock { .. }) => {
|
||||||
// TODO(Simon): We should be checking for WarpifySuccessBlocks here as well.
|
// TODO(Simon): We should be checking for WormholeSuccessBlocks here as well.
|
||||||
// The `WarpifySuccessBlock` implements a `SelectableArea`.
|
// The `WormholeSuccessBlock` implements a `SelectableArea`.
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -23365,7 +23351,7 @@ impl TerminalView {
|
|||||||
} else {
|
} else {
|
||||||
// Remote session: pair CWD with the session's host_id.
|
// Remote session: pair CWD with the session's host_id.
|
||||||
let host_id = match session.session_type() {
|
let host_id = match session.session_type() {
|
||||||
SessionType::WarpifiedRemote { host_id } => host_id,
|
SessionType::WormholedRemote { host_id } => host_id,
|
||||||
SessionType::Local => return None,
|
SessionType::Local => return None,
|
||||||
}?;
|
}?;
|
||||||
let std_path = StandardizedPath::try_new(cwd_str).ok()?;
|
let std_path = StandardizedPath::try_new(cwd_str).ok()?;
|
||||||
@@ -24110,7 +24096,7 @@ impl TerminalView {
|
|||||||
|
|
||||||
let mut subshell_separators = HashMap::new();
|
let mut subshell_separators = HashMap::new();
|
||||||
|
|
||||||
for (id, command) in self.warpify_state.get_subshell_separators() {
|
for (id, command) in self.wormhole_state.get_subshell_separators() {
|
||||||
subshell_separators.insert(*id, render_subshell_separator(command.clone(), appearance));
|
subshell_separators.insert(*id, render_subshell_separator(command.clone(), appearance));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24122,8 +24108,8 @@ impl TerminalView {
|
|||||||
.active_block()
|
.active_block()
|
||||||
.block_banner()
|
.block_banner()
|
||||||
.map(|banner| match banner {
|
.map(|banner| match banner {
|
||||||
WithinBlockBanner::WarpifyBanner(state) => {
|
WithinBlockBanner::WormholeBanner(state) => {
|
||||||
render_warpification_banner(state, appearance)
|
render_wormholing_banner(state, appearance)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -25397,7 +25383,7 @@ impl TerminalView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Replace the terminal input buffer with the given command that is meant to open a subshell.
|
/// Replace the terminal input buffer with the given command that is meant to open a subshell.
|
||||||
/// Set a flag that we should automatically bootstrap AKA "warpify" the subshell when we
|
/// Set a flag that we should automatically bootstrap AKA "wormhole" the subshell when we
|
||||||
/// receive the [`AfterBlockStarted`] event.
|
/// receive the [`AfterBlockStarted`] event.
|
||||||
pub fn insert_subshell_command_and_bootstrap_if_supported(
|
pub fn insert_subshell_command_and_bootstrap_if_supported(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -25631,7 +25617,7 @@ impl TerminalView {
|
|||||||
shell_type: ShellType,
|
shell_type: ShellType,
|
||||||
ctx: &mut ViewContext<Self>,
|
ctx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
// Attempt to auto warpify the subshell when bootstrapped
|
// Attempt to auto wormhole the subshell when bootstrapped
|
||||||
self.pending_auto_bootstrap_shell_type = Some(shell_type);
|
self.pending_auto_bootstrap_shell_type = Some(shell_type);
|
||||||
|
|
||||||
self.input.update(ctx, |input, ctx| {
|
self.input.update(ctx, |input, ctx| {
|
||||||
@@ -25853,7 +25839,7 @@ impl TerminalView {
|
|||||||
ctx: &mut ViewContext<TerminalView>,
|
ctx: &mut ViewContext<TerminalView>,
|
||||||
) {
|
) {
|
||||||
match check_type {
|
match check_type {
|
||||||
SshLoginStatus::RecheckBeforeWarpifying => {
|
SshLoginStatus::RecheckBeforeWormholing => {
|
||||||
// After we receive a line of output from ssh that is NOT prompting for user input (unlike "Enter passphrase: "),
|
// After we receive a line of output from ssh that is NOT prompting for user input (unlike "Enter passphrase: "),
|
||||||
// we wait and repeat the check after a small delay in case the state returned to something that's user-input bound.
|
// we wait and repeat the check after a small delay in case the state returned to something that's user-input bound.
|
||||||
// For example, say the output that kicked off this event was "Permission denied, please try again." and
|
// For example, say the output that kicked off this event was "Permission denied, please try again." and
|
||||||
@@ -25875,11 +25861,11 @@ impl TerminalView {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
SshLoginStatus::ReadyToWarpify => {
|
SshLoginStatus::ReadyToWormhole => {
|
||||||
// The tmux-based SSH warpification flow has been removed in favor of the
|
// The tmux-based SSH wormholing flow has been removed in favor of the
|
||||||
// remote-server SSH extension. If this user had previously opted into the tmux
|
// remote-server SSH extension. If this user had previously opted into the tmux
|
||||||
// wrapper, show them a one-time deprecation notice on their next SSH session.
|
// wrapper, show them a one-time deprecation notice on their next SSH session.
|
||||||
if WarpifySettings::as_ref(ctx).should_show_tmux_deprecation_notice() {
|
if WormholeSettings::as_ref(ctx).should_show_tmux_deprecation_notice() {
|
||||||
if let Some(session_id) = self.active_block_session_id() {
|
if let Some(session_id) = self.active_block_session_id() {
|
||||||
self.show_ssh_tmux_deprecation_banner(session_id, ctx);
|
self.show_ssh_tmux_deprecation_banner(session_id, ctx);
|
||||||
}
|
}
|
||||||
@@ -25922,22 +25908,22 @@ impl TerminalView {
|
|||||||
let alias_value = session.alias_value(first_word)?;
|
let alias_value = session.alias_value(first_word)?;
|
||||||
Some(format!("{alias_value}{rest}"))
|
Some(format!("{alias_value}{rest}"))
|
||||||
});
|
});
|
||||||
let warpify_command = expanded_command.as_deref().unwrap_or(command);
|
let wormhole_command = expanded_command.as_deref().unwrap_or(command);
|
||||||
let shell_family = self.shell_family_for_password_prompt_polling(ctx);
|
let shell_family = self.shell_family_for_password_prompt_polling(ctx);
|
||||||
let warpify_settings = WarpifySettings::as_ref(ctx);
|
let wormhole_settings = WormholeSettings::as_ref(ctx);
|
||||||
let is_compatible_subshell_command = warpify_settings
|
let is_compatible_subshell_command = wormhole_settings
|
||||||
.is_compatible_subshell_command(command, shell_family)
|
.is_compatible_subshell_command(command, shell_family)
|
||||||
|| warpify_settings.is_compatible_subshell_command(warpify_command, shell_family);
|
|| wormhole_settings.is_compatible_subshell_command(wormhole_command, shell_family);
|
||||||
|
|
||||||
!is_compatible_subshell_command
|
!is_compatible_subshell_command
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shows the warpify footer for a detected subshell command.
|
/// Shows the wormhole footer for a detected subshell command.
|
||||||
fn show_warpify_footer(&mut self, ctx: &mut ViewContext<Self>) {
|
fn show_wormhole_footer(&mut self, ctx: &mut ViewContext<Self>) {
|
||||||
let model = self.model.lock();
|
let model = self.model.lock();
|
||||||
|
|
||||||
// Shared session viewers can't initiate warpification currently.
|
// Shared session viewers can't initiate wormholing currently.
|
||||||
// Don't show the warpify footer when an agent is monitoring the command either.
|
// Don't show the wormhole footer when an agent is monitoring the command either.
|
||||||
if model.shared_session_status().is_viewer()
|
if model.shared_session_status().is_viewer()
|
||||||
|| model.block_list().active_block().is_agent_monitoring()
|
|| model.block_list().active_block().is_agent_monitoring()
|
||||||
{
|
{
|
||||||
@@ -25946,11 +25932,11 @@ impl TerminalView {
|
|||||||
drop(model);
|
drop(model);
|
||||||
|
|
||||||
self.use_agent_footer.update(ctx, |footer, ctx| {
|
self.use_agent_footer.update(ctx, |footer, ctx| {
|
||||||
footer.show_warpify(ctx);
|
footer.show_wormhole(ctx);
|
||||||
});
|
});
|
||||||
self.maybe_show_use_agent_footer_in_blocklist(ctx);
|
self.maybe_show_use_agent_footer_in_blocklist(ctx);
|
||||||
|
|
||||||
send_telemetry_from_ctx!(TelemetryEvent::WarpifyFooterShown { is_ssh: false }, ctx);
|
send_telemetry_from_ctx!(TelemetryEvent::WormholeFooterShown { is_ssh: false }, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_initialization_block(&mut self) {
|
fn show_initialization_block(&mut self) {
|
||||||
@@ -26330,7 +26316,7 @@ impl TypedActionView for TerminalView {
|
|||||||
"Showed initialization block",
|
"Showed initialization block",
|
||||||
GalaxyA11yRole::TextareaRole,
|
GalaxyA11yRole::TextareaRole,
|
||||||
)),
|
)),
|
||||||
ShowWarpifySettings => Custom(AccessibilityContent::new_without_help(
|
ShowWormholeSettings => Custom(AccessibilityContent::new_without_help(
|
||||||
"Opened Wormhole Settings",
|
"Opened Wormhole Settings",
|
||||||
GalaxyA11yRole::ButtonRole,
|
GalaxyA11yRole::ButtonRole,
|
||||||
)),
|
)),
|
||||||
@@ -26380,7 +26366,7 @@ impl TypedActionView for TerminalView {
|
|||||||
| ControlSequence(_)
|
| ControlSequence(_)
|
||||||
| TriggerSubshellBootstrap
|
| TriggerSubshellBootstrap
|
||||||
| ShowSubshellBanner(_)
|
| ShowSubshellBanner(_)
|
||||||
| DismissWarpifyBanner(_)
|
| DismissWormholeBanner(_)
|
||||||
| OpenBlockListContextMenu
|
| OpenBlockListContextMenu
|
||||||
| AliasExpansionBanner(_)
|
| AliasExpansionBanner(_)
|
||||||
| VimModeBanner(_)
|
| VimModeBanner(_)
|
||||||
@@ -26891,21 +26877,21 @@ impl TypedActionView for TerminalView {
|
|||||||
TriggerSubshellBootstrap => self.trigger_subshell_bootstrap(None, false, ctx),
|
TriggerSubshellBootstrap => self.trigger_subshell_bootstrap(None, false, ctx),
|
||||||
ShowSubshellBanner(command) => {
|
ShowSubshellBanner(command) => {
|
||||||
// Abort handle is no longer needed since we've waited the 1s already.
|
// Abort handle is no longer needed since we've waited the 1s already.
|
||||||
self.warpify_state.take_subshell_banner_abort_handle();
|
self.wormhole_state.take_subshell_banner_abort_handle();
|
||||||
|
|
||||||
let warpify_keybinding =
|
let wormhole_keybinding =
|
||||||
keybinding_name_to_keystroke("terminal:warpify_subshell", ctx);
|
keybinding_name_to_keystroke("terminal:wormhole_subshell", ctx);
|
||||||
self.show_warpify_banner(
|
self.show_wormhole_banner(
|
||||||
command.to_owned(),
|
command.to_owned(),
|
||||||
"Subshell",
|
"Subshell",
|
||||||
"subshell",
|
"subshell",
|
||||||
warpify_keybinding,
|
wormhole_keybinding,
|
||||||
TelemetryEvent::ShowSubshellBanner,
|
TelemetryEvent::ShowSubshellBanner,
|
||||||
ctx,
|
ctx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
DismissWarpifyBanner(remember) => {
|
DismissWormholeBanner(remember) => {
|
||||||
self.dismiss_warpify_banner(remember, ctx);
|
self.dismiss_wormhole_banner(remember, ctx);
|
||||||
if !remember.is_ssh() {
|
if !remember.is_ssh() {
|
||||||
send_telemetry_from_ctx!(
|
send_telemetry_from_ctx!(
|
||||||
TelemetryEvent::DeclineSubshellBootstrap {
|
TelemetryEvent::DeclineSubshellBootstrap {
|
||||||
@@ -27142,7 +27128,7 @@ impl TypedActionView for TerminalView {
|
|||||||
LoadAgentModeConversation => {
|
LoadAgentModeConversation => {
|
||||||
self.load_agent_mode_conversation(ctx);
|
self.load_agent_mode_conversation(ctx);
|
||||||
}
|
}
|
||||||
ShowWarpifySettings => ctx.emit(Event::OpenSettings(SettingsSection::Warpify)),
|
ShowWormholeSettings => ctx.emit(Event::OpenSettings(SettingsSection::Wormhole)),
|
||||||
DeleteAttachment { index } => {
|
DeleteAttachment { index } => {
|
||||||
self.ai_context_model.update(ctx, |context_model, ctx| {
|
self.ai_context_model.update(ctx, |context_model, ctx| {
|
||||||
context_model.remove_pending_attachment(*index, ctx);
|
context_model.remove_pending_attachment(*index, ctx);
|
||||||
@@ -28374,15 +28360,15 @@ impl View for TerminalView {
|
|||||||
context.set.insert(init::ROOT_CLOUD_MODE_PANE_KEY);
|
context.set.insert(init::ROOT_CLOUD_MODE_PANE_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(WithinBlockBanner::WarpifyBanner(_)) =
|
if let Some(WithinBlockBanner::WormholeBanner(_)) =
|
||||||
model_lock.block_list().active_block().block_banner()
|
model_lock.block_list().active_block().block_banner()
|
||||||
{
|
{
|
||||||
context.set.insert("SubshellBanner");
|
context.set.insert("SubshellBanner");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also set the warpify context when the footer (flag-gated replacement
|
// Also set the wormhole context when the footer (flag-gated replacement
|
||||||
// for the in-block banner) is active, so the ctrl-i keybinding works.
|
// for the in-block banner) is active, so the ctrl-i keybinding works.
|
||||||
if self.use_agent_footer.as_ref(app).is_warpify_active(app) {
|
if self.use_agent_footer.as_ref(app).is_wormhole_active(app) {
|
||||||
context.set.insert("SubshellBanner");
|
context.set.insert("SubshellBanner");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ pub enum OnboardingVersion {
|
|||||||
/// This represents whether entering a subshell for a particular command should become automatic in
|
/// This represents whether entering a subshell for a particular command should become automatic in
|
||||||
/// the future, or to ask again.
|
/// the future, or to ask again.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum RememberForWarpification {
|
pub enum RememberForWormholing {
|
||||||
/// If yes, need to transmit the command itself so it can be persisted to user-defaults
|
/// If yes, need to transmit the command itself so it can be persisted to user-defaults
|
||||||
RememberSubshellCommand(String),
|
RememberSubshellCommand(String),
|
||||||
RememberSSHHost(String),
|
RememberSSHHost(String),
|
||||||
@@ -75,22 +75,22 @@ pub enum RememberForWarpification {
|
|||||||
DoNotRememberSSHHost,
|
DoNotRememberSSHHost,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RememberForWarpification {
|
impl RememberForWormholing {
|
||||||
pub fn as_bool(&self) -> bool {
|
pub fn as_bool(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
RememberForWarpification::RememberSubshellCommand(_) => true,
|
RememberForWormholing::RememberSubshellCommand(_) => true,
|
||||||
RememberForWarpification::RememberSSHHost(_) => true,
|
RememberForWormholing::RememberSSHHost(_) => true,
|
||||||
RememberForWarpification::DoNotRememberSubshellCommand => false,
|
RememberForWormholing::DoNotRememberSubshellCommand => false,
|
||||||
RememberForWarpification::DoNotRememberSSHHost => false,
|
RememberForWormholing::DoNotRememberSSHHost => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_ssh(&self) -> bool {
|
pub fn is_ssh(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
RememberForWarpification::RememberSSHHost(_) => true,
|
RememberForWormholing::RememberSSHHost(_) => true,
|
||||||
RememberForWarpification::DoNotRememberSSHHost => true,
|
RememberForWormholing::DoNotRememberSSHHost => true,
|
||||||
RememberForWarpification::RememberSubshellCommand(_) => false,
|
RememberForWormholing::RememberSubshellCommand(_) => false,
|
||||||
RememberForWarpification::DoNotRememberSubshellCommand => false,
|
RememberForWormholing::DoNotRememberSubshellCommand => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,8 +284,8 @@ pub enum TerminalAction {
|
|||||||
},
|
},
|
||||||
/// Starts a subshell in the active session.
|
/// Starts a subshell in the active session.
|
||||||
TriggerSubshellBootstrap,
|
TriggerSubshellBootstrap,
|
||||||
/// If the user says "no" to Warpification, possibly requesting not to be asked again
|
/// If the user says "no" to Wormholing, possibly requesting not to be asked again
|
||||||
DismissWarpifyBanner(RememberForWarpification),
|
DismissWormholeBanner(RememberForWormholing),
|
||||||
/// Triggers the banner asking to turn the running block into a subshell. The String is the
|
/// Triggers the banner asking to turn the running block into a subshell. The String is the
|
||||||
/// command that the user entered.
|
/// command that the user entered.
|
||||||
ShowSubshellBanner(String),
|
ShowSubshellBanner(String),
|
||||||
@@ -342,7 +342,7 @@ pub enum TerminalAction {
|
|||||||
GenerateCodebaseIndex,
|
GenerateCodebaseIndex,
|
||||||
/// This is for debugging, dev only for now
|
/// This is for debugging, dev only for now
|
||||||
LoadAgentModeConversation,
|
LoadAgentModeConversation,
|
||||||
ShowWarpifySettings,
|
ShowWormholeSettings,
|
||||||
/// Removes a pending attachment (image or file) by index in the unified list.
|
/// Removes a pending attachment (image or file) by index in the unified list.
|
||||||
DeleteAttachment {
|
DeleteAttachment {
|
||||||
index: usize,
|
index: usize,
|
||||||
@@ -622,7 +622,7 @@ impl fmt::Debug for TerminalAction {
|
|||||||
OpenBlockListContextMenu => f.write_str("OpenBlockListContextMenu"),
|
OpenBlockListContextMenu => f.write_str("OpenBlockListContextMenu"),
|
||||||
AskAIAssistant { block_index } => write!(f, "AskAIAssistant({block_index:?})"),
|
AskAIAssistant { block_index } => write!(f, "AskAIAssistant({block_index:?})"),
|
||||||
TriggerSubshellBootstrap => f.write_str("TriggerSubshellBootstrap"),
|
TriggerSubshellBootstrap => f.write_str("TriggerSubshellBootstrap"),
|
||||||
DismissWarpifyBanner(remember) => write!(f, "DismissWarpifyBanner({remember:?})"),
|
DismissWormholeBanner(remember) => write!(f, "DismissWormholeBanner({remember:?})"),
|
||||||
ShowSubshellBanner(_) => f.write_str("ShowSubshellBanner"),
|
ShowSubshellBanner(_) => f.write_str("ShowSubshellBanner"),
|
||||||
InsertMostRecentCommandCorrection => f.write_str("InsertMostRecentCommandCorrection"),
|
InsertMostRecentCommandCorrection => f.write_str("InsertMostRecentCommandCorrection"),
|
||||||
AliasExpansionBanner(action) => write!(f, "AliasExpansionBanner({action:?}"),
|
AliasExpansionBanner(action) => write!(f, "AliasExpansionBanner({action:?}"),
|
||||||
@@ -682,7 +682,7 @@ impl fmt::Debug for TerminalAction {
|
|||||||
ShowInitializationBlock => write!(f, "ShowInitializationBlock"),
|
ShowInitializationBlock => write!(f, "ShowInitializationBlock"),
|
||||||
GenerateCodebaseIndex => write!(f, "GenerateIndexForRepo"),
|
GenerateCodebaseIndex => write!(f, "GenerateIndexForRepo"),
|
||||||
LoadAgentModeConversation => write!(f, "LoadAgentModeConversation"),
|
LoadAgentModeConversation => write!(f, "LoadAgentModeConversation"),
|
||||||
ShowWarpifySettings => write!(f, "ShowWarpifySettings"),
|
ShowWormholeSettings => write!(f, "ShowWormholeSettings"),
|
||||||
DeleteAttachment { index } => write!(f, "DeleteAttachment({index:?})"),
|
DeleteAttachment { index } => write!(f, "DeleteAttachment({index:?})"),
|
||||||
OpenAttachmentLightbox { index } => {
|
OpenAttachmentLightbox { index } => {
|
||||||
write!(f, "OpenAttachmentLightbox({index:?})")
|
write!(f, "OpenAttachmentLightbox({index:?})")
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
//! without a LayoutContext. Use the exported BLOCK_BANNER_HEIGHT const when the banner height
|
//! without a LayoutContext. Use the exported BLOCK_BANNER_HEIGHT const when the banner height
|
||||||
//! needs to be taken into account.
|
//! needs to be taken into account.
|
||||||
|
|
||||||
mod warpify;
|
mod wormhole;
|
||||||
|
|
||||||
use galaxyui::elements::{
|
use galaxyui::elements::{
|
||||||
ConstrainedBox, Container, CornerRadius, Hoverable, MouseState, MouseStateHandle,
|
ConstrainedBox, Container, CornerRadius, Hoverable, MouseState, MouseStateHandle,
|
||||||
ParentElement, Radius, Stack,
|
ParentElement, Radius, Stack,
|
||||||
};
|
};
|
||||||
use galaxyui::Element;
|
use galaxyui::Element;
|
||||||
pub use warpify::*;
|
pub use wormhole::*;
|
||||||
|
|
||||||
use crate::themes::theme::GalaxyTheme;
|
use crate::themes::theme::GalaxyTheme;
|
||||||
|
|
||||||
@@ -25,13 +25,13 @@ const BANNER_H_PADDING: f32 = 8.;
|
|||||||
pub const BLOCK_BANNER_HEIGHT: f32 = CONSTRAINED_BANNER_HEIGHT + BANNER_TOP_MARGIN;
|
pub const BLOCK_BANNER_HEIGHT: f32 = CONSTRAINED_BANNER_HEIGHT + BANNER_TOP_MARGIN;
|
||||||
|
|
||||||
pub enum WithinBlockBanner {
|
pub enum WithinBlockBanner {
|
||||||
WarpifyBanner(WarpifyBannerState),
|
WormholeBanner(WormholeBannerState),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WithinBlockBanner {
|
impl WithinBlockBanner {
|
||||||
pub fn banner_height(&self) -> f32 {
|
pub fn banner_height(&self) -> f32 {
|
||||||
match self {
|
match self {
|
||||||
WithinBlockBanner::WarpifyBanner(_) => BLOCK_BANNER_HEIGHT,
|
WithinBlockBanner::WormholeBanner(_) => BLOCK_BANNER_HEIGHT,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-21
@@ -10,14 +10,14 @@ use pathfinder_color::ColorU;
|
|||||||
|
|
||||||
use super::render_block_banner;
|
use super::render_block_banner;
|
||||||
use crate::appearance::Appearance;
|
use crate::appearance::Appearance;
|
||||||
use crate::terminal::view::{RememberForWarpification, TerminalAction};
|
use crate::terminal::view::{RememberForWormholing, TerminalAction};
|
||||||
use crate::themes::theme::Fill;
|
use crate::themes::theme::Fill;
|
||||||
use crate::ui_components::blended_colors;
|
use crate::ui_components::blended_colors;
|
||||||
|
|
||||||
const CLOSE_BUTTON_DIAMETER: f32 = 20.0;
|
const CLOSE_BUTTON_DIAMETER: f32 = 20.0;
|
||||||
const STANDARD_PADDING: f32 = 8.0;
|
const STANDARD_PADDING: f32 = 8.0;
|
||||||
|
|
||||||
pub struct WarpifyBannerState {
|
pub struct WormholeBannerState {
|
||||||
/// The subshell command that triggered the banner.
|
/// The subshell command that triggered the banner.
|
||||||
pub command: String,
|
pub command: String,
|
||||||
pub height: f32,
|
pub height: f32,
|
||||||
@@ -25,19 +25,19 @@ pub struct WarpifyBannerState {
|
|||||||
pub dont_ask_button_mouse_state: MouseStateHandle,
|
pub dont_ask_button_mouse_state: MouseStateHandle,
|
||||||
pub dismiss_button_mouse_state: MouseStateHandle,
|
pub dismiss_button_mouse_state: MouseStateHandle,
|
||||||
|
|
||||||
/// This keybinding gets rendered in the Warpification banner, but we can't look it up
|
/// This keybinding gets rendered in the Wormholing banner, but we can't look it up
|
||||||
/// during render as a &mut AppContext is not available then. This needs to get
|
/// during render as a &mut AppContext is not available then. This needs to get
|
||||||
/// looked up during action handling and cached here.
|
/// looked up during action handling and cached here.
|
||||||
pub initialize_warpify_keybinding: Option<Keystroke>,
|
pub initialize_wormhole_keybinding: Option<Keystroke>,
|
||||||
pub hover_state: MouseStateHandle,
|
pub hover_state: MouseStateHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WarpifyBannerState {
|
impl WormholeBannerState {
|
||||||
pub fn new(command: String, initialize_warpify_keybinding: Option<Keystroke>) -> Self {
|
pub fn new(command: String, initialize_wormhole_keybinding: Option<Keystroke>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
command,
|
command,
|
||||||
height: 0.0,
|
height: 0.0,
|
||||||
initialize_warpify_keybinding,
|
initialize_wormhole_keybinding,
|
||||||
accept_button_mouse_state: Default::default(),
|
accept_button_mouse_state: Default::default(),
|
||||||
dont_ask_button_mouse_state: Default::default(),
|
dont_ask_button_mouse_state: Default::default(),
|
||||||
dismiss_button_mouse_state: Default::default(),
|
dismiss_button_mouse_state: Default::default(),
|
||||||
@@ -46,18 +46,18 @@ impl WarpifyBannerState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn title(&self) -> &str {
|
pub fn title(&self) -> &str {
|
||||||
"Warpify subshell"
|
"Wormhole subshell"
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn action(&self) -> TerminalAction {
|
pub fn action(&self) -> TerminalAction {
|
||||||
TerminalAction::TriggerSubshellBootstrap
|
TerminalAction::TriggerSubshellBootstrap
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remember_for_warpification(&self, should_remember: bool) -> RememberForWarpification {
|
fn remember_for_wormholing(&self, should_remember: bool) -> RememberForWormholing {
|
||||||
if should_remember {
|
if should_remember {
|
||||||
RememberForWarpification::RememberSubshellCommand(self.command.to_owned())
|
RememberForWormholing::RememberSubshellCommand(self.command.to_owned())
|
||||||
} else {
|
} else {
|
||||||
RememberForWarpification::DoNotRememberSubshellCommand
|
RememberForWormholing::DoNotRememberSubshellCommand
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,18 +65,18 @@ impl WarpifyBannerState {
|
|||||||
/// This banner is shown when the user runs a command which is recognized as a subshell-compatible
|
/// This banner is shown when the user runs a command which is recognized as a subshell-compatible
|
||||||
/// command. It asks if they want to bootstrap a subshell and, if so, whether we should ask again
|
/// command. It asks if they want to bootstrap a subshell and, if so, whether we should ask again
|
||||||
/// next time they run the same command.
|
/// next time they run the same command.
|
||||||
pub fn render_warpification_banner(
|
pub fn render_wormholing_banner(
|
||||||
state: &WarpifyBannerState,
|
state: &WormholeBannerState,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
) -> Box<dyn Element> {
|
) -> Box<dyn Element> {
|
||||||
let yes_button = render_yes_button(
|
let yes_button = render_yes_button(
|
||||||
state,
|
state,
|
||||||
&state.initialize_warpify_keybinding,
|
&state.initialize_wormhole_keybinding,
|
||||||
&state.accept_button_mouse_state,
|
&state.accept_button_mouse_state,
|
||||||
appearance,
|
appearance,
|
||||||
);
|
);
|
||||||
|
|
||||||
let remember = state.remember_for_warpification(true);
|
let remember = state.remember_for_wormholing(true);
|
||||||
let dont_ask_button = Container::new(
|
let dont_ask_button = Container::new(
|
||||||
appearance
|
appearance
|
||||||
.ui_builder()
|
.ui_builder()
|
||||||
@@ -87,7 +87,7 @@ pub fn render_warpification_banner(
|
|||||||
.with_text_label("Do not show again".to_owned())
|
.with_text_label("Do not show again".to_owned())
|
||||||
.build()
|
.build()
|
||||||
.on_click(move |ctx, _, _| {
|
.on_click(move |ctx, _, _| {
|
||||||
ctx.dispatch_typed_action(TerminalAction::DismissWarpifyBanner(
|
ctx.dispatch_typed_action(TerminalAction::DismissWormholeBanner(
|
||||||
remember.to_owned(),
|
remember.to_owned(),
|
||||||
));
|
));
|
||||||
})
|
})
|
||||||
@@ -96,7 +96,7 @@ pub fn render_warpification_banner(
|
|||||||
.with_margin_right(16.)
|
.with_margin_right(16.)
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
let do_not_remember = state.remember_for_warpification(false);
|
let do_not_remember = state.remember_for_wormholing(false);
|
||||||
let close_button = appearance
|
let close_button = appearance
|
||||||
.ui_builder()
|
.ui_builder()
|
||||||
.close_button(
|
.close_button(
|
||||||
@@ -105,7 +105,7 @@ pub fn render_warpification_banner(
|
|||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
.on_click(move |ctx, _, _| {
|
.on_click(move |ctx, _, _| {
|
||||||
ctx.dispatch_typed_action(TerminalAction::DismissWarpifyBanner(
|
ctx.dispatch_typed_action(TerminalAction::DismissWormholeBanner(
|
||||||
do_not_remember.to_owned(),
|
do_not_remember.to_owned(),
|
||||||
));
|
));
|
||||||
})
|
})
|
||||||
@@ -132,12 +132,12 @@ pub fn render_warpification_banner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_yes_button(
|
fn render_yes_button(
|
||||||
state: &WarpifyBannerState,
|
state: &WormholeBannerState,
|
||||||
initialize_warpification_keybinding: &Option<Keystroke>,
|
initialize_wormholing_keybinding: &Option<Keystroke>,
|
||||||
mouse_state: &MouseStateHandle,
|
mouse_state: &MouseStateHandle,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
) -> Box<dyn Element> {
|
) -> Box<dyn Element> {
|
||||||
let yes_button = match initialize_warpification_keybinding {
|
let yes_button = match initialize_wormholing_keybinding {
|
||||||
Some(keystroke) => appearance
|
Some(keystroke) => appearance
|
||||||
.ui_builder()
|
.ui_builder()
|
||||||
.keyboard_shortcut_button(state.title().to_owned(), keystroke, mouse_state.clone())
|
.keyboard_shortcut_button(state.title().to_owned(), keystroke, mouse_state.clone())
|
||||||
@@ -81,8 +81,8 @@ pub fn init(app: &mut AppContext) {
|
|||||||
app.register_binding_validator::<TerminalView>(is_binding_pty_compliant);
|
app.register_binding_validator::<TerminalView>(is_binding_pty_compliant);
|
||||||
|
|
||||||
init_overlapping_keybindings(app);
|
init_overlapping_keybindings(app);
|
||||||
// Register input mode bindings before warpify bindings so ctrl-i warpifies
|
// Register input mode bindings before wormhole bindings so ctrl-i wormholes
|
||||||
// instead of opening inline agent when a warpify banner is visible.
|
// instead of opening inline agent when a wormhole banner is visible.
|
||||||
register_input_mode_bindings(app);
|
register_input_mode_bindings(app);
|
||||||
|
|
||||||
app.register_fixed_bindings([
|
app.register_fixed_bindings([
|
||||||
@@ -320,7 +320,7 @@ pub fn init(app: &mut AppContext) {
|
|||||||
| (id!("Terminal") & !id!("IMEOpen") & id!(flags::CLI_AGENT_RICH_INPUT_OPEN)),
|
| (id!("Terminal") & !id!("IMEOpen") & id!(flags::CLI_AGENT_RICH_INPUT_OPEN)),
|
||||||
),
|
),
|
||||||
EditableBinding::new(
|
EditableBinding::new(
|
||||||
"terminal:warpify_subshell",
|
"terminal:wormhole_subshell",
|
||||||
"Wormhole subshell",
|
"Wormhole subshell",
|
||||||
TerminalAction::TriggerSubshellBootstrap,
|
TerminalAction::TriggerSubshellBootstrap,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use crate::terminal::view::init_environment::InitEnvironmentBlock;
|
|||||||
use crate::terminal::view::ssh_remote_server_choice_view::SshRemoteServerChoiceView;
|
use crate::terminal::view::ssh_remote_server_choice_view::SshRemoteServerChoiceView;
|
||||||
use crate::terminal::view::ssh_remote_server_failed_banner::SshRemoteServerFailedBanner;
|
use crate::terminal::view::ssh_remote_server_failed_banner::SshRemoteServerFailedBanner;
|
||||||
use crate::terminal::view::ssh_tmux_deprecation_banner::SshTmuxDeprecationBanner;
|
use crate::terminal::view::ssh_tmux_deprecation_banner::SshTmuxDeprecationBanner;
|
||||||
use crate::terminal::warpify::success_block::WarpifySuccessBlock;
|
use crate::terminal::wormhole::success_block::WormholeSuccessBlock;
|
||||||
use crate::terminal::TerminalView;
|
use crate::terminal::TerminalView;
|
||||||
|
|
||||||
/// Specifies where to insert rich content in the blocklist.
|
/// Specifies where to insert rich content in the blocklist.
|
||||||
@@ -249,8 +249,8 @@ pub enum RichContentMetadata {
|
|||||||
SshTmuxDeprecationBanner {
|
SshTmuxDeprecationBanner {
|
||||||
handle: ViewHandle<SshTmuxDeprecationBanner>,
|
handle: ViewHandle<SshTmuxDeprecationBanner>,
|
||||||
},
|
},
|
||||||
WarpifySuccessBlock {
|
WormholeSuccessBlock {
|
||||||
bootstrap_success_block_handle: ViewHandle<WarpifySuccessBlock>,
|
bootstrap_success_block_handle: ViewHandle<WormholeSuccessBlock>,
|
||||||
},
|
},
|
||||||
TelemetryBanner {
|
TelemetryBanner {
|
||||||
telemetry_banner_handle: ViewHandle<TelemetryBanner>,
|
telemetry_banner_handle: ViewHandle<TelemetryBanner>,
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ impl FileUpload {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an sftp command that copies a given local file into the PWD of the warpified ssh session, if any.
|
/// Creates an sftp command that copies a given local file into the PWD of the wormholed ssh session, if any.
|
||||||
fn transfer_file_sftp_command(&self, file_upload: &FileUploadInfo) -> String {
|
fn transfer_file_sftp_command(&self, file_upload: &FileUploadInfo) -> String {
|
||||||
// "sftp "
|
// "sftp "
|
||||||
let mut command = String::from("sftp ");
|
let mut command = String::from("sftp ");
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
//! Inline block view that asks the user whether they want to install
|
//! Inline block view that asks the user whether they want to install
|
||||||
//! Warp's SSH extension on the remote host the shell just connected to,
|
//! Wormhole's remote helper on the host the shell just connected to,
|
||||||
//! or continue without installing (falling back to the existing
|
//! or continue without installing (falling back to the existing
|
||||||
//! ControlMaster warpification path).
|
//! ControlMaster wormholing path).
|
||||||
//!
|
//!
|
||||||
//! Designed from frame 6050:2448 of the Figma file
|
//! Designed from frame 6050:2448 of the Figma file
|
||||||
//! [Remote session initialization](https://www.figma.com/design/r0BO9cTZCK6pDE6qerg2K0/Remote-session-initialization).
|
//! [Remote session initialization](https://www.figma.com/design/r0BO9cTZCK6pDE6qerg2K0/Remote-session-initialization).
|
||||||
//!
|
//!
|
||||||
//! The view owns:
|
//! The view owns:
|
||||||
//! - a child [`KeyboardNavigableButtons`] handle for the two selectable
|
//! - a child [`KeyboardNavigableButtons`] handle for the two selectable
|
||||||
//! cards ("Install Warp's SSH extension" / "Continue without installing"),
|
//! cards ("Install Wormhole helper" / "Continue without installing"),
|
||||||
//! - the [`SessionId`] this prompt is scoped to (used for event forwarding),
|
//! - the [`SessionId`] this prompt is scoped to (used for event forwarding),
|
||||||
//! - the current "Don't ask me this again" checked state (purely local to
|
//! - the current "Don't ask me this again" checked state (purely local to
|
||||||
//! this prompt instance; persisted to `ssh_extension_install_mode` only
|
//! this prompt instance; persisted to `ssh_extension_install_mode` only
|
||||||
@@ -37,7 +37,7 @@ use crate::ai::blocklist::inline_action::inline_action_header::{
|
|||||||
};
|
};
|
||||||
use crate::server::telemetry::TelemetryEvent;
|
use crate::server::telemetry::TelemetryEvent;
|
||||||
use crate::terminal::model::session::SessionId;
|
use crate::terminal::model::session::SessionId;
|
||||||
use crate::terminal::warpify::settings::{SshExtensionInstallMode, WarpifySettings};
|
use crate::terminal::wormhole::settings::{SshExtensionInstallMode, WormholeSettings};
|
||||||
use crate::ui_components::blended_colors;
|
use crate::ui_components::blended_colors;
|
||||||
use crate::{send_telemetry_from_ctx, Appearance};
|
use crate::{send_telemetry_from_ctx, Appearance};
|
||||||
|
|
||||||
@@ -48,14 +48,14 @@ pub enum SshRemoteServerChoiceViewAction {
|
|||||||
Install,
|
Install,
|
||||||
Skip,
|
Skip,
|
||||||
ToggleDoNotAskAgain,
|
ToggleDoNotAskAgain,
|
||||||
OpenWarpifySettings,
|
OpenWormholeSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum SshRemoteServerChoiceViewEvent {
|
pub enum SshRemoteServerChoiceViewEvent {
|
||||||
Install,
|
Install,
|
||||||
Skip,
|
Skip,
|
||||||
OpenWarpifySettings,
|
OpenWormholeSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Choice block prompting the user to install the remote-server binary on the remote host or skip.
|
/// Choice block prompting the user to install the remote-server binary on the remote host or skip.
|
||||||
@@ -74,7 +74,7 @@ impl SshRemoteServerChoiceView {
|
|||||||
let buttons = ctx.add_typed_action_view(|_| {
|
let buttons = ctx.add_typed_action_view(|_| {
|
||||||
KeyboardNavigableButtons::new(vec![
|
KeyboardNavigableButtons::new(vec![
|
||||||
rich_navigation_button(
|
rich_navigation_button(
|
||||||
"Install Galaxy's SSH extension".to_string(),
|
"Install Wormhole helper".to_string(),
|
||||||
Some(
|
Some(
|
||||||
"Install Galaxy's extension to enable agent features like file browsing, \
|
"Install Galaxy's extension to enable agent features like file browsing, \
|
||||||
code review, and intelligent command completions in this session."
|
code review, and intelligent command completions in this session."
|
||||||
@@ -171,14 +171,16 @@ impl SshRemoteServerChoiceView {
|
|||||||
.with_child(Container::new(checkbox_label).with_margin_left(4.).finish())
|
.with_child(Container::new(checkbox_label).with_margin_left(4.).finish())
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
// Right: "Manage Warpify settings" link.
|
// Right: "Manage Wormhole settings" link.
|
||||||
let manage_settings_link = appearance
|
let manage_settings_link = appearance
|
||||||
.ui_builder()
|
.ui_builder()
|
||||||
.link(
|
.link(
|
||||||
"Manage Wormhole settings".into(),
|
"Manage Wormhole settings".into(),
|
||||||
None,
|
None,
|
||||||
Some(Box::new(|ctx| {
|
Some(Box::new(|ctx| {
|
||||||
ctx.dispatch_typed_action(SshRemoteServerChoiceViewAction::OpenWarpifySettings);
|
ctx.dispatch_typed_action(
|
||||||
|
SshRemoteServerChoiceViewAction::OpenWormholeSettings,
|
||||||
|
);
|
||||||
})),
|
})),
|
||||||
self.manage_settings_mouse_state.clone(),
|
self.manage_settings_mouse_state.clone(),
|
||||||
)
|
)
|
||||||
@@ -264,7 +266,7 @@ impl TypedActionView for SshRemoteServerChoiceView {
|
|||||||
SshRemoteServerChoiceViewAction::Install => {
|
SshRemoteServerChoiceViewAction::Install => {
|
||||||
if self.do_not_ask_again {
|
if self.do_not_ask_again {
|
||||||
let mode = SshExtensionInstallMode::AlwaysInstall;
|
let mode = SshExtensionInstallMode::AlwaysInstall;
|
||||||
WarpifySettings::handle(ctx).update(ctx, |settings, ctx| {
|
WormholeSettings::handle(ctx).update(ctx, |settings, ctx| {
|
||||||
if let Err(e) = settings.ssh_extension_install_mode.set_value(mode, ctx) {
|
if let Err(e) = settings.ssh_extension_install_mode.set_value(mode, ctx) {
|
||||||
log::error!("Failed to persist ssh_extension_install_mode: {e}");
|
log::error!("Failed to persist ssh_extension_install_mode: {e}");
|
||||||
}
|
}
|
||||||
@@ -281,7 +283,7 @@ impl TypedActionView for SshRemoteServerChoiceView {
|
|||||||
SshRemoteServerChoiceViewAction::Skip => {
|
SshRemoteServerChoiceViewAction::Skip => {
|
||||||
if self.do_not_ask_again {
|
if self.do_not_ask_again {
|
||||||
let mode = SshExtensionInstallMode::NeverInstall;
|
let mode = SshExtensionInstallMode::NeverInstall;
|
||||||
WarpifySettings::handle(ctx).update(ctx, |settings, ctx| {
|
WormholeSettings::handle(ctx).update(ctx, |settings, ctx| {
|
||||||
if let Err(e) = settings.ssh_extension_install_mode.set_value(mode, ctx) {
|
if let Err(e) = settings.ssh_extension_install_mode.set_value(mode, ctx) {
|
||||||
log::error!("Failed to persist ssh_extension_install_mode: {e}");
|
log::error!("Failed to persist ssh_extension_install_mode: {e}");
|
||||||
}
|
}
|
||||||
@@ -305,8 +307,8 @@ impl TypedActionView for SshRemoteServerChoiceView {
|
|||||||
);
|
);
|
||||||
ctx.notify();
|
ctx.notify();
|
||||||
}
|
}
|
||||||
SshRemoteServerChoiceViewAction::OpenWarpifySettings => {
|
SshRemoteServerChoiceViewAction::OpenWormholeSettings => {
|
||||||
ctx.emit(SshRemoteServerChoiceViewEvent::OpenWarpifySettings);
|
ctx.emit(SshRemoteServerChoiceViewEvent::OpenWormholeSettings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//! Banner shown when the remote-server binary check, installation, or connection fails on the remote host.
|
//! Banner shown when the remote-server binary check, installation, or connection fails on the remote host.
|
||||||
//! We fall back to the existing Warpification behavior and display this banner so the user knows why advanced features are unavailable.
|
//! We fall back to the existing Wormholing behavior and display this banner so the user knows why advanced features are unavailable.
|
||||||
|
|
||||||
use galaxy_core::ui::theme::color::internal_colors;
|
use galaxy_core::ui::theme::color::internal_colors;
|
||||||
use galaxy_core::ui::theme::AnsiColorIdentifier;
|
use galaxy_core::ui::theme::AnsiColorIdentifier;
|
||||||
@@ -15,11 +15,11 @@ use crate::terminal::model::session::SessionId;
|
|||||||
use crate::ui_components::icons::Icon;
|
use crate::ui_components::icons::Icon;
|
||||||
use crate::Appearance;
|
use crate::Appearance;
|
||||||
|
|
||||||
const BANNER_TITLE: &str = "Couldn't connect to the Warp SSH extension";
|
const BANNER_TITLE: &str = "Couldn't connect to the Wormhole helper";
|
||||||
|
|
||||||
const BANNER_BODY: &str =
|
const BANNER_BODY: &str =
|
||||||
"While advanced features like file browsing and code review are currently \
|
"While advanced features like file browsing and code review are currently \
|
||||||
disabled, the rest of your Warpified experience is fully available.";
|
disabled, the rest of your Wormholed experience is fully available.";
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum SshRemoteServerFailedBannerAction {
|
pub enum SshRemoteServerFailedBannerAction {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! One-time inline banner shown to users who had previously opted into the now-deprecated
|
//! One-time inline banner shown to users who had previously opted into the now-deprecated
|
||||||
//! tmux-based SSH warpification flow. It explains that tmux SSH warpification has been turned
|
//! tmux-based SSH wormholing flow. It explains that tmux SSH wormholing has been turned
|
||||||
//! off in favor of Warp's SSH extension (remote server) and links to the docs.
|
//! off in favor of Galaxy's SSH extension (remote server).
|
||||||
//!
|
//!
|
||||||
//! The banner is shown at most once per affected user: it is gated on the
|
//! The banner is shown at most once per affected user: it is gated on the
|
||||||
//! `ssh_tmux_deprecation_notice_pending` setting, which is set by a one-time migration and
|
//! `ssh_tmux_deprecation_notice_pending` setting, which is set by a one-time migration and
|
||||||
@@ -8,29 +8,25 @@
|
|||||||
|
|
||||||
use galaxy_core::ui::theme::color::internal_colors;
|
use galaxy_core::ui::theme::color::internal_colors;
|
||||||
use warpui::elements::{
|
use warpui::elements::{
|
||||||
Align, ConstrainedBox, Container, CrossAxisAlignment, Flex, Hoverable, MainAxisAlignment,
|
ConstrainedBox, Container, CrossAxisAlignment, Flex, Hoverable, MainAxisAlignment,
|
||||||
MainAxisSize, MouseStateHandle, ParentElement, Shrinkable, Text,
|
MainAxisSize, MouseStateHandle, ParentElement, Shrinkable, Text,
|
||||||
};
|
};
|
||||||
use warpui::platform::Cursor;
|
use warpui::platform::Cursor;
|
||||||
use warpui::ui_components::components::{UiComponent, UiComponentStyles};
|
|
||||||
use warpui::{AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext};
|
use warpui::{AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext};
|
||||||
|
|
||||||
use crate::terminal::model::session::SessionId;
|
use crate::terminal::model::session::SessionId;
|
||||||
use crate::terminal::warpify::render::SSH_DOCS_URL;
|
|
||||||
use crate::ui_components::icons::Icon;
|
use crate::ui_components::icons::Icon;
|
||||||
use crate::Appearance;
|
use crate::Appearance;
|
||||||
|
|
||||||
const BANNER_TITLE: &str = "Tmux SSH warpification has been deprecated";
|
const BANNER_TITLE: &str = "Legacy tmux SSH wormholing has been retired";
|
||||||
|
|
||||||
const BANNER_BODY: &str = "Warp now connects to remote sessions using the SSH extension, which is \
|
const BANNER_BODY: &str =
|
||||||
|
"Galaxy now connects to remote sessions using the SSH extension, which is \
|
||||||
more robust than the tmux-based flow. The tmux option has been removed.";
|
more robust than the tmux-based flow. The tmux option has been removed.";
|
||||||
|
|
||||||
const LEARN_MORE_LABEL: &str = "Learn more";
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum SshTmuxDeprecationBannerAction {
|
pub enum SshTmuxDeprecationBannerAction {
|
||||||
Dismiss,
|
Dismiss,
|
||||||
LearnMore,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -40,7 +36,6 @@ pub enum SshTmuxDeprecationBannerEvent {
|
|||||||
|
|
||||||
pub struct SshTmuxDeprecationBanner {
|
pub struct SshTmuxDeprecationBanner {
|
||||||
session_id: SessionId,
|
session_id: SessionId,
|
||||||
learn_more_mouse_state: MouseStateHandle,
|
|
||||||
close_mouse_state: MouseStateHandle,
|
close_mouse_state: MouseStateHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +43,6 @@ impl SshTmuxDeprecationBanner {
|
|||||||
pub fn new(session_id: SessionId) -> Self {
|
pub fn new(session_id: SessionId) -> Self {
|
||||||
Self {
|
Self {
|
||||||
session_id,
|
session_id,
|
||||||
learn_more_mouse_state: MouseStateHandle::default(),
|
|
||||||
close_mouse_state: MouseStateHandle::default(),
|
close_mouse_state: MouseStateHandle::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,13 +66,12 @@ impl View for SshTmuxDeprecationBanner {
|
|||||||
let theme = appearance.theme();
|
let theme = appearance.theme();
|
||||||
let fg_color = theme.foreground().into_solid();
|
let fg_color = theme.foreground().into_solid();
|
||||||
let muted_color = internal_colors::neutral_5(theme);
|
let muted_color = internal_colors::neutral_5(theme);
|
||||||
let accent_color = theme.accent().into_solid();
|
|
||||||
let font_size = appearance.monospace_font_size();
|
let font_size = appearance.monospace_font_size();
|
||||||
let small_font_size = font_size - 2.;
|
let small_font_size = font_size - 2.;
|
||||||
|
|
||||||
// Warp icon to match the other warpification blocks.
|
// Galaxy icon to match the other wormholing blocks.
|
||||||
let icon = Container::new(
|
let icon = Container::new(
|
||||||
ConstrainedBox::new(Icon::Warp.to_warpui_icon(fg_color.into()).finish())
|
ConstrainedBox::new(Icon::GalaxyLogo.to_warpui_icon(fg_color.into()).finish())
|
||||||
.with_width(16.)
|
.with_width(16.)
|
||||||
.with_height(16.)
|
.with_height(16.)
|
||||||
.finish(),
|
.finish(),
|
||||||
@@ -103,26 +96,6 @@ impl View for SshTmuxDeprecationBanner {
|
|||||||
.with_color(muted_color)
|
.with_color(muted_color)
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
let learn_more = appearance
|
|
||||||
.ui_builder()
|
|
||||||
.link(
|
|
||||||
LEARN_MORE_LABEL.into(),
|
|
||||||
None,
|
|
||||||
Some(Box::new(|ctx| {
|
|
||||||
ctx.dispatch_typed_action(SshTmuxDeprecationBannerAction::LearnMore);
|
|
||||||
})),
|
|
||||||
self.learn_more_mouse_state.clone(),
|
|
||||||
)
|
|
||||||
.soft_wrap(false)
|
|
||||||
.with_style(UiComponentStyles {
|
|
||||||
font_size: Some(small_font_size),
|
|
||||||
font_family_id: Some(appearance.ui_font_family()),
|
|
||||||
font_color: Some(accent_color),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.build()
|
|
||||||
.finish();
|
|
||||||
|
|
||||||
// Close (X) button
|
// Close (X) button
|
||||||
let close_icon_color = muted_color;
|
let close_icon_color = muted_color;
|
||||||
let close = Hoverable::new(self.close_mouse_state.clone(), move |_| {
|
let close = Hoverable::new(self.close_mouse_state.clone(), move |_| {
|
||||||
@@ -158,26 +131,17 @@ impl View for SshTmuxDeprecationBanner {
|
|||||||
.with_child(close_container)
|
.with_child(close_container)
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
// Body text + learn more link, indented past the icon to align with the title.
|
// Body text, indented past the icon to align with the title.
|
||||||
let body_container = Container::new(body)
|
let body_container = Container::new(body)
|
||||||
.with_margin_top(2.)
|
.with_margin_top(2.)
|
||||||
.with_margin_left(24.)
|
.with_margin_left(24.)
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
// Wrap the link in a left-aligned `Align` so its hover/underline region hugs the
|
|
||||||
// link text instead of stretching to the full banner width (the parent column uses
|
|
||||||
// `CrossAxisAlignment::Stretch`).
|
|
||||||
let learn_more_container = Container::new(Align::new(learn_more).left().finish())
|
|
||||||
.with_margin_top(4.)
|
|
||||||
.with_margin_left(24.)
|
|
||||||
.finish();
|
|
||||||
|
|
||||||
let content = Flex::column()
|
let content = Flex::column()
|
||||||
.with_main_axis_size(MainAxisSize::Min)
|
.with_main_axis_size(MainAxisSize::Min)
|
||||||
.with_cross_axis_alignment(CrossAxisAlignment::Stretch)
|
.with_cross_axis_alignment(CrossAxisAlignment::Stretch)
|
||||||
.with_child(header_row)
|
.with_child(header_row)
|
||||||
.with_child(body_container)
|
.with_child(body_container)
|
||||||
.with_child(learn_more_container)
|
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
Container::new(content)
|
Container::new(content)
|
||||||
@@ -195,10 +159,6 @@ impl TypedActionView for SshTmuxDeprecationBanner {
|
|||||||
SshTmuxDeprecationBannerAction::Dismiss => {
|
SshTmuxDeprecationBannerAction::Dismiss => {
|
||||||
ctx.emit(SshTmuxDeprecationBannerEvent::Dismissed);
|
ctx.emit(SshTmuxDeprecationBannerEvent::Dismissed);
|
||||||
}
|
}
|
||||||
SshTmuxDeprecationBannerAction::LearnMore => {
|
|
||||||
ctx.open_url(SSH_DOCS_URL);
|
|
||||||
ctx.emit(SshTmuxDeprecationBannerEvent::Dismissed);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use crate::terminal::shared_session::{
|
|||||||
SharedSessionActionSource, SharedSessionScrollbackType, SharedSessionSource,
|
SharedSessionActionSource, SharedSessionScrollbackType, SharedSessionSource,
|
||||||
};
|
};
|
||||||
use crate::util::image::{infer_mime_type, MAX_IMAGE_SIZE_BYTES_FOR_CLI_AGENT, MIME_SNIFF_BYTES};
|
use crate::util::image::{infer_mime_type, MAX_IMAGE_SIZE_BYTES_FOR_CLI_AGENT, MIME_SNIFF_BYTES};
|
||||||
mod warpify_footer;
|
mod wormhole_footer;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::{Arc, LazyLock};
|
use std::sync::{Arc, LazyLock};
|
||||||
@@ -44,7 +44,7 @@ use galaxyui::{
|
|||||||
};
|
};
|
||||||
use parking_lot::FairMutex;
|
use parking_lot::FairMutex;
|
||||||
use pathfinder_color::ColorU;
|
use pathfinder_color::ColorU;
|
||||||
use warpify_footer::{WarpifyFooterView, WarpifyFooterViewEvent};
|
use wormhole_footer::{WormholeFooterView, WormholeFooterViewEvent};
|
||||||
|
|
||||||
use super::{RichContentInsertionPosition, TerminalAction, TerminalView};
|
use super::{RichContentInsertionPosition, TerminalAction, TerminalView};
|
||||||
use crate::ai::blocklist::agent_view::agent_view_bg_fill;
|
use crate::ai::blocklist::agent_view::agent_view_bg_fill;
|
||||||
@@ -267,11 +267,11 @@ impl TerminalView {
|
|||||||
UseAgentToolbarEvent::HideRichInput => {
|
UseAgentToolbarEvent::HideRichInput => {
|
||||||
self.close_cli_agent_rich_input_and_disable_auto_toggle(ctx);
|
self.close_cli_agent_rich_input_and_disable_auto_toggle(ctx);
|
||||||
}
|
}
|
||||||
UseAgentToolbarEvent::Warpify => {
|
UseAgentToolbarEvent::Wormhole => {
|
||||||
self.hide_use_agent_footer_in_blocklist(ctx);
|
self.hide_use_agent_footer_in_blocklist(ctx);
|
||||||
self.handle_action(&TerminalAction::TriggerSubshellBootstrap, ctx);
|
self.handle_action(&TerminalAction::TriggerSubshellBootstrap, ctx);
|
||||||
send_telemetry_from_ctx!(
|
send_telemetry_from_ctx!(
|
||||||
TelemetryEvent::WarpifyFooterAcceptedWarpify { is_ssh: false },
|
TelemetryEvent::WormholeFooterAcceptedWormhole { is_ssh: false },
|
||||||
ctx
|
ctx
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -295,8 +295,8 @@ impl TerminalView {
|
|||||||
) -> bool {
|
) -> bool {
|
||||||
let ai_settings = AISettings::as_ref(app);
|
let ai_settings = AISettings::as_ref(app);
|
||||||
|
|
||||||
// If the warpify footer is active, a subshell was detected and we should show the footer.
|
// If the wormhole footer is active, a subshell was detected and we should show the footer.
|
||||||
if self.use_agent_footer.as_ref(app).is_warpify_active(app) {
|
if self.use_agent_footer.as_ref(app).is_wormhole_active(app) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,7 +421,7 @@ impl TerminalView {
|
|||||||
|
|
||||||
if !self.model.lock().is_alt_screen_active() {
|
if !self.model.lock().is_alt_screen_active() {
|
||||||
self.use_agent_footer.update(ctx, |footer, ctx| {
|
self.use_agent_footer.update(ctx, |footer, ctx| {
|
||||||
footer.clear_warpify(ctx);
|
footer.clear_wormhole(ctx);
|
||||||
});
|
});
|
||||||
self.hide_use_agent_footer_in_blocklist(ctx);
|
self.hide_use_agent_footer_in_blocklist(ctx);
|
||||||
}
|
}
|
||||||
@@ -1046,8 +1046,8 @@ pub struct UseAgentToolbar {
|
|||||||
// Shared agent input footer (renders CLI agent mode when a CLI session is active).
|
// Shared agent input footer (renders CLI agent mode when a CLI session is active).
|
||||||
agent_input_footer: ViewHandle<AgentInputFooter>,
|
agent_input_footer: ViewHandle<AgentInputFooter>,
|
||||||
|
|
||||||
// Warpify footer UI (shown when a subshell/SSH command is detected).
|
// Wormhole footer UI (shown when a subshell/SSH command is detected).
|
||||||
warpify_footer_view: ViewHandle<WarpifyFooterView>,
|
wormhole_footer_view: ViewHandle<WormholeFooterView>,
|
||||||
|
|
||||||
// `true` if the user has dismissed the footer.
|
// `true` if the user has dismissed the footer.
|
||||||
//
|
//
|
||||||
@@ -1120,11 +1120,11 @@ impl UseAgentToolbar {
|
|||||||
me.handle_agent_input_footer_event(event, ctx);
|
me.handle_agent_input_footer_event(event, ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
let warpify_footer_view =
|
let wormhole_footer_view =
|
||||||
ctx.add_typed_action_view(|ctx| WarpifyFooterView::new(terminal_model.clone(), ctx));
|
ctx.add_typed_action_view(|ctx| WormholeFooterView::new(terminal_model.clone(), ctx));
|
||||||
|
|
||||||
ctx.subscribe_to_view(&warpify_footer_view, |me, _, event, ctx| {
|
ctx.subscribe_to_view(&wormhole_footer_view, |me, _, event, ctx| {
|
||||||
me.handle_warpify_footer_event(event, ctx);
|
me.handle_wormhole_footer_event(event, ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
ctx.subscribe_to_model(model_event_dispatcher, |me, _, event, ctx| {
|
ctx.subscribe_to_model(model_event_dispatcher, |me, _, event, ctx| {
|
||||||
@@ -1150,7 +1150,7 @@ impl UseAgentToolbar {
|
|||||||
dismiss_button,
|
dismiss_button,
|
||||||
dont_show_again_button,
|
dont_show_again_button,
|
||||||
agent_input_footer,
|
agent_input_footer,
|
||||||
warpify_footer_view,
|
wormhole_footer_view,
|
||||||
terminal_model,
|
terminal_model,
|
||||||
did_user_dismiss: false,
|
did_user_dismiss: false,
|
||||||
}
|
}
|
||||||
@@ -1186,19 +1186,19 @@ impl UseAgentToolbar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_warpify_footer_event(
|
fn handle_wormhole_footer_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: &WarpifyFooterViewEvent,
|
event: &WormholeFooterViewEvent,
|
||||||
ctx: &mut ViewContext<Self>,
|
ctx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
WarpifyFooterViewEvent::Warpify => {
|
WormholeFooterViewEvent::Wormhole => {
|
||||||
ctx.emit(UseAgentToolbarEvent::Warpify);
|
ctx.emit(UseAgentToolbarEvent::Wormhole);
|
||||||
}
|
}
|
||||||
WarpifyFooterViewEvent::UseAgent => {
|
WormholeFooterViewEvent::UseAgent => {
|
||||||
ctx.emit(UseAgentToolbarEvent::UseAgent);
|
ctx.emit(UseAgentToolbarEvent::UseAgent);
|
||||||
}
|
}
|
||||||
WarpifyFooterViewEvent::Dismiss => {
|
WormholeFooterViewEvent::Dismiss => {
|
||||||
ctx.emit(UseAgentToolbarEvent::Dismiss);
|
ctx.emit(UseAgentToolbarEvent::Dismiss);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1207,7 +1207,7 @@ impl UseAgentToolbar {
|
|||||||
pub(in crate::terminal) fn notify_and_notify_children(&mut self, ctx: &mut ViewContext<Self>) {
|
pub(in crate::terminal) fn notify_and_notify_children(&mut self, ctx: &mut ViewContext<Self>) {
|
||||||
ctx.notify();
|
ctx.notify();
|
||||||
self.agent_input_footer.update(ctx, |_, ctx| ctx.notify());
|
self.agent_input_footer.update(ctx, |_, ctx| ctx.notify());
|
||||||
self.warpify_footer_view.update(ctx, |_, ctx| ctx.notify());
|
self.wormhole_footer_view.update(ctx, |_, ctx| ctx.notify());
|
||||||
self.button.update(ctx, |_, ctx| ctx.notify());
|
self.button.update(ctx, |_, ctx| ctx.notify());
|
||||||
self.give_control_back_button
|
self.give_control_back_button
|
||||||
.update(ctx, |_, ctx| ctx.notify());
|
.update(ctx, |_, ctx| ctx.notify());
|
||||||
@@ -1227,26 +1227,26 @@ impl UseAgentToolbar {
|
|||||||
.map(|session| session.agent)
|
.map(|session| session.agent)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Activates the warpify footer. When active, the footer shows the
|
/// Activates the wormhole footer. When active, the footer shows the
|
||||||
/// warpify view instead of the CLI agent or regular "Use agent" views.
|
/// wormhole view instead of the CLI agent or regular "Use agent" views.
|
||||||
pub(in crate::terminal) fn show_warpify(&mut self, ctx: &mut ViewContext<Self>) {
|
pub(in crate::terminal) fn show_wormhole(&mut self, ctx: &mut ViewContext<Self>) {
|
||||||
self.warpify_footer_view.update(ctx, |view, ctx| {
|
self.wormhole_footer_view.update(ctx, |view, ctx| {
|
||||||
view.show(ctx);
|
view.show(ctx);
|
||||||
});
|
});
|
||||||
ctx.notify();
|
ctx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deactivates the warpify footer so it reverts to its default behavior.
|
/// Deactivates the wormhole footer so it reverts to its default behavior.
|
||||||
pub(in crate::terminal) fn clear_warpify(&mut self, ctx: &mut ViewContext<Self>) {
|
pub(in crate::terminal) fn clear_wormhole(&mut self, ctx: &mut ViewContext<Self>) {
|
||||||
self.warpify_footer_view.update(ctx, |view, ctx| {
|
self.wormhole_footer_view.update(ctx, |view, ctx| {
|
||||||
view.clear(ctx);
|
view.clear(ctx);
|
||||||
});
|
});
|
||||||
ctx.notify();
|
ctx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the warpify footer is currently active.
|
/// Returns whether the wormhole footer is currently active.
|
||||||
pub(in crate::terminal) fn is_warpify_active(&self, app: &AppContext) -> bool {
|
pub(in crate::terminal) fn is_wormhole_active(&self, app: &AppContext) -> bool {
|
||||||
self.warpify_footer_view.as_ref(app).is_active()
|
self.wormhole_footer_view.as_ref(app).is_active()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether there's a current CLI agent (like Claude Code).
|
/// Returns whether there's a current CLI agent (like Claude Code).
|
||||||
@@ -1272,8 +1272,8 @@ pub enum UseAgentToolbarEvent {
|
|||||||
OpenRichInput,
|
OpenRichInput,
|
||||||
/// Hide the rich input editor (same as Escape).
|
/// Hide the rich input editor (same as Escape).
|
||||||
HideRichInput,
|
HideRichInput,
|
||||||
/// User chose to warpify the subshell.
|
/// User chose to wormhole the subshell.
|
||||||
Warpify,
|
Wormhole,
|
||||||
/// User chose to use the agent.
|
/// User chose to use the agent.
|
||||||
UseAgent,
|
UseAgent,
|
||||||
StartRemoteControl {
|
StartRemoteControl {
|
||||||
@@ -1292,9 +1292,9 @@ impl View for UseAgentToolbar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self, app: &AppContext) -> Box<dyn Element> {
|
fn render(&self, app: &AppContext) -> Box<dyn Element> {
|
||||||
// If the warpify footer is active, delegate rendering to the warpify footer view.
|
// If the wormhole footer is active, delegate rendering to the wormhole footer view.
|
||||||
if self.warpify_footer_view.as_ref(app).is_active() {
|
if self.wormhole_footer_view.as_ref(app).is_active() {
|
||||||
return ChildView::new(&self.warpify_footer_view).finish();
|
return ChildView::new(&self.wormhole_footer_view).finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide the toolbar entirely when CLI rich input is open,
|
// Hide the toolbar entirely when CLI rich input is open,
|
||||||
|
|||||||
+33
-33
@@ -15,28 +15,28 @@ use crate::view_components::action_button::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Footer view rendered for detected subshell commands, offering both
|
/// Footer view rendered for detected subshell commands, offering both
|
||||||
/// "Warpify" and "Use agent" buttons in a horizontal row.
|
/// "Wormhole" and "Use agent" buttons in a horizontal row.
|
||||||
pub(super) struct WarpifyFooterView {
|
pub(super) struct WormholeFooterView {
|
||||||
terminal_model: Arc<FairMutex<TerminalModel>>,
|
terminal_model: Arc<FairMutex<TerminalModel>>,
|
||||||
warpify_button: ViewHandle<ActionButton>,
|
wormhole_button: ViewHandle<ActionButton>,
|
||||||
use_agent_button: ViewHandle<ActionButton>,
|
use_agent_button: ViewHandle<ActionButton>,
|
||||||
dismiss_button: ViewHandle<ActionButton>,
|
dismiss_button: ViewHandle<ActionButton>,
|
||||||
/// Whether the footer is currently offering subshell warpification.
|
/// Whether the footer is currently offering subshell wormholing.
|
||||||
is_active: bool,
|
is_active: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WarpifyFooterView {
|
impl WormholeFooterView {
|
||||||
pub fn new(terminal_model: Arc<FairMutex<TerminalModel>>, ctx: &mut ViewContext<Self>) -> Self {
|
pub fn new(terminal_model: Arc<FairMutex<TerminalModel>>, ctx: &mut ViewContext<Self>) -> Self {
|
||||||
let button_size = ButtonSize::XSmall;
|
let button_size = ButtonSize::XSmall;
|
||||||
|
|
||||||
let warpify_button = ctx.add_typed_action_view(|_ctx| {
|
let wormhole_button = ctx.add_typed_action_view(|_ctx| {
|
||||||
ActionButton::new("Wormhole subshell", AgentFooterButtonTheme::new(None))
|
ActionButton::new("Wormhole subshell", AgentFooterButtonTheme::new(None))
|
||||||
.with_icon(Icon::Warp)
|
.with_icon(Icon::GalaxyLogo)
|
||||||
.with_size(button_size)
|
.with_size(button_size)
|
||||||
.with_tooltip("Enable Galaxy shell integration in this session")
|
.with_tooltip("Enable Galaxy shell integration in this session")
|
||||||
.with_tooltip_alignment(TooltipAlignment::Left)
|
.with_tooltip_alignment(TooltipAlignment::Left)
|
||||||
.on_click(|ctx| {
|
.on_click(|ctx| {
|
||||||
ctx.dispatch_typed_action(WarpifyFooterViewAction::Warpify);
|
ctx.dispatch_typed_action(WormholeFooterViewAction::Wormhole);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ impl WarpifyFooterView {
|
|||||||
.with_tooltip("Ask the Galaxy agent to assist")
|
.with_tooltip("Ask the Galaxy agent to assist")
|
||||||
.with_tooltip_alignment(TooltipAlignment::Left)
|
.with_tooltip_alignment(TooltipAlignment::Left)
|
||||||
.on_click(|ctx| {
|
.on_click(|ctx| {
|
||||||
ctx.dispatch_typed_action(WarpifyFooterViewAction::UseAgent);
|
ctx.dispatch_typed_action(WormholeFooterViewAction::UseAgent);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -56,24 +56,24 @@ impl WarpifyFooterView {
|
|||||||
ActionButton::new("Dismiss", AgentFooterButtonTheme::new(None))
|
ActionButton::new("Dismiss", AgentFooterButtonTheme::new(None))
|
||||||
.with_size(button_size)
|
.with_size(button_size)
|
||||||
.on_click(|ctx| {
|
.on_click(|ctx| {
|
||||||
ctx.dispatch_typed_action(WarpifyFooterViewAction::Dismiss);
|
ctx.dispatch_typed_action(WormholeFooterViewAction::Dismiss);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
terminal_model,
|
terminal_model,
|
||||||
warpify_button,
|
wormhole_button,
|
||||||
use_agent_button,
|
use_agent_button,
|
||||||
dismiss_button,
|
dismiss_button,
|
||||||
is_active: false,
|
is_active: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Activates the footer so it offers subshell warpification.
|
/// Activates the footer so it offers subshell wormholing.
|
||||||
pub fn show(&mut self, ctx: &mut ViewContext<Self>) {
|
pub fn show(&mut self, ctx: &mut ViewContext<Self>) {
|
||||||
self.warpify_button.update(ctx, |button, ctx| {
|
self.wormhole_button.update(ctx, |button, ctx| {
|
||||||
button.set_keybinding(
|
button.set_keybinding(
|
||||||
Some(KeystrokeSource::Binding("terminal:warpify_subshell")),
|
Some(KeystrokeSource::Binding("terminal:wormhole_subshell")),
|
||||||
ctx,
|
ctx,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -81,7 +81,7 @@ impl WarpifyFooterView {
|
|||||||
ctx.notify();
|
ctx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the footer is currently offering subshell warpification.
|
/// Returns whether the footer is currently offering subshell wormholing.
|
||||||
pub fn is_active(&self) -> bool {
|
pub fn is_active(&self) -> bool {
|
||||||
self.is_active
|
self.is_active
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ impl WarpifyFooterView {
|
|||||||
/// Deactivates the footer.
|
/// Deactivates the footer.
|
||||||
pub fn clear(&mut self, ctx: &mut ViewContext<Self>) {
|
pub fn clear(&mut self, ctx: &mut ViewContext<Self>) {
|
||||||
self.is_active = false;
|
self.is_active = false;
|
||||||
self.warpify_button.update(ctx, |button, ctx| {
|
self.wormhole_button.update(ctx, |button, ctx| {
|
||||||
button.set_keybinding(None, ctx);
|
button.set_keybinding(None, ctx);
|
||||||
});
|
});
|
||||||
ctx.notify();
|
ctx.notify();
|
||||||
@@ -97,25 +97,25 @@ impl WarpifyFooterView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum WarpifyFooterViewAction {
|
pub enum WormholeFooterViewAction {
|
||||||
Warpify,
|
Wormhole,
|
||||||
UseAgent,
|
UseAgent,
|
||||||
Dismiss,
|
Dismiss,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum WarpifyFooterViewEvent {
|
pub enum WormholeFooterViewEvent {
|
||||||
Warpify,
|
Wormhole,
|
||||||
UseAgent,
|
UseAgent,
|
||||||
Dismiss,
|
Dismiss,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for WarpifyFooterView {
|
impl Entity for WormholeFooterView {
|
||||||
type Event = WarpifyFooterViewEvent;
|
type Event = WormholeFooterViewEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View for WarpifyFooterView {
|
impl View for WormholeFooterView {
|
||||||
fn ui_name() -> &'static str {
|
fn ui_name() -> &'static str {
|
||||||
"WarpifyFooterView"
|
"WormholeFooterView"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self, _app: &AppContext) -> Box<dyn Element> {
|
fn render(&self, _app: &AppContext) -> Box<dyn Element> {
|
||||||
@@ -125,7 +125,7 @@ impl View for WarpifyFooterView {
|
|||||||
.with_spacing(4.)
|
.with_spacing(4.)
|
||||||
.with_main_axis_size(MainAxisSize::Max)
|
.with_main_axis_size(MainAxisSize::Max)
|
||||||
.with_cross_axis_alignment(CrossAxisAlignment::Center)
|
.with_cross_axis_alignment(CrossAxisAlignment::Center)
|
||||||
.with_child(ChildView::new(&self.warpify_button).finish())
|
.with_child(ChildView::new(&self.wormhole_button).finish())
|
||||||
.with_child(ChildView::new(&self.use_agent_button).finish())
|
.with_child(ChildView::new(&self.use_agent_button).finish())
|
||||||
.with_child(Expanded::new(1., Empty::new().finish()).finish())
|
.with_child(Expanded::new(1., Empty::new().finish()).finish())
|
||||||
.with_child(ChildView::new(&self.dismiss_button).finish());
|
.with_child(ChildView::new(&self.dismiss_button).finish());
|
||||||
@@ -144,24 +144,24 @@ impl View for WarpifyFooterView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypedActionView for WarpifyFooterView {
|
impl TypedActionView for WormholeFooterView {
|
||||||
type Action = WarpifyFooterViewAction;
|
type Action = WormholeFooterViewAction;
|
||||||
|
|
||||||
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
|
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
|
||||||
match action {
|
match action {
|
||||||
WarpifyFooterViewAction::Warpify => {
|
WormholeFooterViewAction::Wormhole => {
|
||||||
if self.is_active {
|
if self.is_active {
|
||||||
self.clear(ctx);
|
self.clear(ctx);
|
||||||
ctx.emit(WarpifyFooterViewEvent::Warpify);
|
ctx.emit(WormholeFooterViewEvent::Wormhole);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WarpifyFooterViewAction::UseAgent => {
|
WormholeFooterViewAction::UseAgent => {
|
||||||
self.clear(ctx);
|
self.clear(ctx);
|
||||||
ctx.emit(WarpifyFooterViewEvent::UseAgent);
|
ctx.emit(WormholeFooterViewEvent::UseAgent);
|
||||||
}
|
}
|
||||||
WarpifyFooterViewAction::Dismiss => {
|
WormholeFooterViewAction::Dismiss => {
|
||||||
self.clear(ctx);
|
self.clear(ctx);
|
||||||
ctx.emit(WarpifyFooterViewEvent::Dismiss);
|
ctx.emit(WormholeFooterViewEvent::Dismiss);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,12 +10,6 @@ use crate::terminal::model::terminal_model::SubshellInitializationInfo;
|
|||||||
use crate::terminal::shell::ShellType;
|
use crate::terminal::shell::ShellType;
|
||||||
use crate::ASSETS;
|
use crate::ASSETS;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum WarpificationSource {
|
|
||||||
Ssh,
|
|
||||||
Subshell,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum SubshellSource {
|
pub enum SubshellSource {
|
||||||
Command(String),
|
Command(String),
|
||||||
@@ -34,7 +28,7 @@ fn get_subshell_bootstrap_success_block_path(shell_type: ShellType) -> Option<&'
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns OutputGrid bytes to be rendered in the hardcoded "Warpified subshell" block that's added
|
/// Returns OutputGrid bytes to be rendered in the hardcoded "Wormholed subshell" block that's added
|
||||||
/// to the blocklist upon successful subshell bootstrap.
|
/// to the blocklist upon successful subshell bootstrap.
|
||||||
///
|
///
|
||||||
/// The exact block contents varies based on whether or not the session is local or remote, in
|
/// The exact block contents varies based on whether or not the session is local or remote, in
|
||||||
@@ -13,7 +13,7 @@ use pathfinder_color::ColorU;
|
|||||||
use pathfinder_geometry::rect::RectF;
|
use pathfinder_geometry::rect::RectF;
|
||||||
use pathfinder_geometry::vector::Vector2F;
|
use pathfinder_geometry::vector::Vector2F;
|
||||||
|
|
||||||
use super::settings::WarpifySettings;
|
use super::settings::WormholeSettings;
|
||||||
use super::SubshellSource;
|
use super::SubshellSource;
|
||||||
use crate::ai::blocklist::inline_action::inline_action_icons;
|
use crate::ai::blocklist::inline_action::inline_action_icons;
|
||||||
use crate::ui_components::blended_colors;
|
use crate::ui_components::blended_colors;
|
||||||
@@ -31,8 +31,6 @@ const WARP_DRIVE_ENV_VAR_COLLECTION_ICON_COLOR: u32 = 0xC464FFFF;
|
|||||||
const ICON_MARGIN: f32 = 4.;
|
const ICON_MARGIN: f32 = 4.;
|
||||||
const TERMINAL_ICON: &str = "bundled/svg/terminal.svg";
|
const TERMINAL_ICON: &str = "bundled/svg/terminal.svg";
|
||||||
pub const HORIZONTAL_TEXT_MARGIN: f32 = 20.;
|
pub const HORIZONTAL_TEXT_MARGIN: f32 = 20.;
|
||||||
pub const SSH_DOCS_URL: &str = "https://docs.warp.dev/terminal/warpify/ssh";
|
|
||||||
pub const SUBSHELL_DOCS_URL: &str = "https://docs.warp.dev/terminal/warpify/subshells";
|
|
||||||
|
|
||||||
/// Errored blocks have a red stripe, and subshells have a gray one.
|
/// Errored blocks have a red stripe, and subshells have a gray one.
|
||||||
pub const LEFT_STRIPE_WIDTH: f32 = 5.;
|
pub const LEFT_STRIPE_WIDTH: f32 = 5.;
|
||||||
@@ -92,7 +90,7 @@ fn green_check_icon(appearance: &Appearance, size: f32) -> Box<dyn Element> {
|
|||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// UI helper to render the ssh command that caused the warpification prompt.
|
/// UI helper to render the ssh command that caused the wormholing prompt.
|
||||||
pub fn build_command_row(
|
pub fn build_command_row(
|
||||||
command: String,
|
command: String,
|
||||||
theme: &GalaxyTheme,
|
theme: &GalaxyTheme,
|
||||||
@@ -164,21 +162,21 @@ pub fn description_row(
|
|||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renders a "Never Warpify this host" link or nothing.
|
/// Renders a "Never Wormhole this host" link or nothing.
|
||||||
pub fn render_never_warpify_ssh_link(
|
pub fn render_never_wormhole_ssh_link(
|
||||||
ssh_host: &Option<String>,
|
ssh_host: &Option<String>,
|
||||||
app: &AppContext,
|
app: &AppContext,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
mouse_state_handle: MouseStateHandle,
|
mouse_state_handle: MouseStateHandle,
|
||||||
on_never_warpify: fn(&mut EventContext<'_>, ssh_host: String),
|
on_never_wormhole: fn(&mut EventContext<'_>, ssh_host: String),
|
||||||
) -> Option<Box<dyn Element>> {
|
) -> Option<Box<dyn Element>> {
|
||||||
let Some(ssh_host) = ssh_host else {
|
let Some(ssh_host) = ssh_host else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let settings = WarpifySettings::handle(app);
|
let settings = WormholeSettings::handle(app);
|
||||||
if settings.as_ref(app).is_ssh_host_denylisted(ssh_host) {
|
if settings.as_ref(app).is_ssh_host_denylisted(ssh_host) {
|
||||||
// Should only happen if user manually attempts to Warpify a denylisted host.
|
// Should only happen if user manually attempts to Wormhole a denylisted host.
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +187,7 @@ pub fn render_never_warpify_ssh_link(
|
|||||||
None,
|
None,
|
||||||
Some(Box::new({
|
Some(Box::new({
|
||||||
let ssh_host = ssh_host.clone();
|
let ssh_host = ssh_host.clone();
|
||||||
move |ctx| on_never_warpify(ctx, ssh_host.to_owned())
|
move |ctx| on_never_wormhole(ctx, ssh_host.to_owned())
|
||||||
})),
|
})),
|
||||||
mouse_state_handle,
|
mouse_state_handle,
|
||||||
)
|
)
|
||||||
@@ -9,65 +9,65 @@ use settings::{
|
|||||||
};
|
};
|
||||||
use strum_macros::EnumIter;
|
use strum_macros::EnumIter;
|
||||||
|
|
||||||
use crate::terminal::ssh::util::{parse_interactive_ssh_command, SshWarpifyCommand};
|
use crate::terminal::ssh::util::{parse_interactive_ssh_command, SshWormholeCommand};
|
||||||
|
|
||||||
// Cannot directly use Vec<Regex> here b/c Regex doesn't impl Eq, Serialize, and Deserialize.
|
// Cannot directly use Vec<Regex> here b/c Regex doesn't impl Eq, Serialize, and Deserialize.
|
||||||
maybe_define_setting!(AddedSubshellCommands, group: WarpifySettings, {
|
maybe_define_setting!(AddedSubshellCommands, group: WormholeSettings, {
|
||||||
type: Vec<String>,
|
type: Vec<String>,
|
||||||
default: Vec::new(),
|
default: Vec::new(),
|
||||||
supported_platforms: SupportedPlatforms::ALL,
|
supported_platforms: SupportedPlatforms::ALL,
|
||||||
sync_to_cloud: SyncToCloud::Never,
|
sync_to_cloud: SyncToCloud::Never,
|
||||||
private: false,
|
private: false,
|
||||||
toml_path: "warpify.subshells.added_subshell_commands",
|
toml_path: "wormhole.subshells.added_subshell_commands",
|
||||||
description: "Additional regex patterns for commands that should be recognized as subshells.",
|
description: "Additional regex patterns for commands that should be recognized as subshells.",
|
||||||
});
|
});
|
||||||
|
|
||||||
maybe_define_setting!(SubshellCommandsDenylist, group: WarpifySettings, {
|
maybe_define_setting!(SubshellCommandsDenylist, group: WormholeSettings, {
|
||||||
type: Vec<String>,
|
type: Vec<String>,
|
||||||
default: Vec::new(),
|
default: Vec::new(),
|
||||||
supported_platforms: SupportedPlatforms::ALL,
|
supported_platforms: SupportedPlatforms::ALL,
|
||||||
sync_to_cloud: SyncToCloud::Never,
|
sync_to_cloud: SyncToCloud::Never,
|
||||||
private: false,
|
private: false,
|
||||||
toml_path: "warpify.subshells.subshell_commands_denylist",
|
toml_path: "wormhole.subshells.subshell_commands_denylist",
|
||||||
description: "Commands that should not trigger the subshell warpification prompt.",
|
description: "Commands that should not trigger the subshell wormholing prompt.",
|
||||||
});
|
});
|
||||||
|
|
||||||
maybe_define_setting!(SshHostsDenylist, group: WarpifySettings, {
|
maybe_define_setting!(SshHostsDenylist, group: WormholeSettings, {
|
||||||
type: Vec<String>,
|
type: Vec<String>,
|
||||||
default: Vec::new(),
|
default: Vec::new(),
|
||||||
supported_platforms: SupportedPlatforms::ALL,
|
supported_platforms: SupportedPlatforms::ALL,
|
||||||
sync_to_cloud: SyncToCloud::Never,
|
sync_to_cloud: SyncToCloud::Never,
|
||||||
private: false,
|
private: false,
|
||||||
toml_path: "warpify.ssh.ssh_hosts_denylist",
|
toml_path: "wormhole.ssh.ssh_hosts_denylist",
|
||||||
description: "SSH hosts that should not trigger the warpification prompt.",
|
description: "SSH hosts that should not trigger the wormholing prompt.",
|
||||||
});
|
});
|
||||||
|
|
||||||
maybe_define_setting!(EnableSshWarpification, group: WarpifySettings, {
|
maybe_define_setting!(EnableSshWormholing, group: WormholeSettings, {
|
||||||
type: bool,
|
type: bool,
|
||||||
default: true,
|
default: true,
|
||||||
supported_platforms: SupportedPlatforms::ALL,
|
supported_platforms: SupportedPlatforms::ALL,
|
||||||
sync_to_cloud: SyncToCloud::Never,
|
sync_to_cloud: SyncToCloud::Never,
|
||||||
private: false,
|
private: false,
|
||||||
toml_path: "warpify.ssh.enable_ssh_warpification",
|
toml_path: "wormhole.ssh.enable_ssh_wormholing",
|
||||||
description: "Whether to enable Galaxy features in SSH sessions.",
|
description: "Whether to enable Galaxy features in SSH sessions.",
|
||||||
});
|
});
|
||||||
|
|
||||||
// NOTE: This setting has been unified into `enable_ssh_warpification` and is no
|
// NOTE: This setting has been unified into `enable_ssh_wormholing` and is no
|
||||||
// longer surfaced in the UI or used to gate any behavior. It is retained only
|
// longer surfaced in the UI or used to gate any behavior. It is retained only
|
||||||
// so the one-time migration (see `register`) can read a user's previous value
|
// so the one-time migration (see `register`) can read a user's previous value
|
||||||
// and forward it to `enable_ssh_warpification`. It can be deleted in a future
|
// and forward it to `enable_ssh_wormholing`. It can be deleted in a future
|
||||||
// release once the migration has shipped to all users.
|
// release once the migration has shipped to all users.
|
||||||
// The storage key and TOML path are intentionally kept identical to the old
|
// The storage key and TOML path are intentionally kept identical to the old
|
||||||
// `SshSettings::enable_ssh_wrapper` field for backward compatibility.
|
// `SshSettings::enable_ssh_wrapper` field for backward compatibility.
|
||||||
maybe_define_setting!(EnableSshWrapper, group: WarpifySettings, {
|
maybe_define_setting!(EnableSshWrapper, group: WormholeSettings, {
|
||||||
type: bool,
|
type: bool,
|
||||||
default: true,
|
default: true,
|
||||||
supported_platforms: SupportedPlatforms::ALL,
|
supported_platforms: SupportedPlatforms::ALL,
|
||||||
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
|
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
|
||||||
private: false,
|
private: false,
|
||||||
storage_key: "EnableSSHWrapper",
|
storage_key: "EnableSSHWrapper",
|
||||||
toml_path: "warpify.ssh.enable_legacy_ssh_wrapper",
|
toml_path: "wormhole.ssh.enable_legacy_ssh_wrapper",
|
||||||
description: "Deprecated: unified into enable_ssh_warpification. Retained only for one-time migration.",
|
description: "Deprecated: unified into enable_ssh_wormholing. Retained only for one-time migration.",
|
||||||
});
|
});
|
||||||
|
|
||||||
// NOTE: The tmux-based SSH wrapper is deprecated in favor of the remote-server SSH
|
// NOTE: The tmux-based SSH wrapper is deprecated in favor of the remote-server SSH
|
||||||
@@ -75,31 +75,31 @@ maybe_define_setting!(EnableSshWrapper, group: WarpifySettings, {
|
|||||||
// it is retained only so the one-time deprecation migration (see `register`) can read a
|
// it is retained only so the one-time deprecation migration (see `register`) can read a
|
||||||
// user's previous opt-in and reset it. It can be deleted in a future release once the
|
// user's previous opt-in and reset it. It can be deleted in a future release once the
|
||||||
// migration has shipped to all users.
|
// migration has shipped to all users.
|
||||||
maybe_define_setting!(UseSshTmuxWrapper, group: WarpifySettings, {
|
maybe_define_setting!(UseSshTmuxWrapper, group: WormholeSettings, {
|
||||||
type: bool,
|
type: bool,
|
||||||
default: false,
|
default: false,
|
||||||
supported_platforms: SupportedPlatforms::OR(SupportedPlatforms::MAC.into(), SupportedPlatforms::LINUX.into()),
|
supported_platforms: SupportedPlatforms::OR(SupportedPlatforms::MAC.into(), SupportedPlatforms::LINUX.into()),
|
||||||
sync_to_cloud: SyncToCloud::Never,
|
sync_to_cloud: SyncToCloud::Never,
|
||||||
private: false,
|
private: false,
|
||||||
toml_path: "warpify.ssh.use_ssh_tmux_wrapper",
|
toml_path: "wormhole.ssh.use_ssh_tmux_wrapper",
|
||||||
description: "Deprecated: whether to use a tmux-based wrapper for SSH warpification.",
|
description: "Deprecated: whether to use a tmux-based wrapper for SSH wormholing.",
|
||||||
});
|
});
|
||||||
|
|
||||||
// When set, the user previously opted into the now-deprecated tmux SSH wrapper and should
|
// When set, the user previously opted into the now-deprecated tmux SSH wrapper and should
|
||||||
// be shown a one-time inline banner pointing them to the remote-server SSH extension on
|
// be shown a one-time inline banner pointing them to the remote-server SSH extension on
|
||||||
// their next interactive SSH session. Set by the migration in `register`; cleared once the
|
// their next interactive SSH session. Set by the migration in `register`; cleared once the
|
||||||
// banner has been shown.
|
// banner has been shown.
|
||||||
maybe_define_setting!(SshTmuxDeprecationNoticePending, group: WarpifySettings, {
|
maybe_define_setting!(SshTmuxDeprecationNoticePending, group: WormholeSettings, {
|
||||||
type: bool,
|
type: bool,
|
||||||
default: false,
|
default: false,
|
||||||
supported_platforms: SupportedPlatforms::OR(SupportedPlatforms::MAC.into(), SupportedPlatforms::LINUX.into()),
|
supported_platforms: SupportedPlatforms::OR(SupportedPlatforms::MAC.into(), SupportedPlatforms::LINUX.into()),
|
||||||
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
|
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
|
||||||
private: false,
|
private: false,
|
||||||
toml_path: "warpify.ssh.ssh_tmux_deprecation_notice_pending",
|
toml_path: "wormhole.ssh.ssh_tmux_deprecation_notice_pending",
|
||||||
description: "Internal: whether to show the one-time tmux SSH deprecation notice.",
|
description: "Internal: whether to show the one-time tmux SSH deprecation notice.",
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Controls how Warp handles the SSH extension (remote server binary) when connecting
|
/// Controls how Galaxy handles the SSH extension (remote server binary) when connecting
|
||||||
/// to a remote host that does not already have it installed.
|
/// to a remote host that does not already have it installed.
|
||||||
#[derive(
|
#[derive(
|
||||||
Default,
|
Default,
|
||||||
@@ -115,7 +115,7 @@ maybe_define_setting!(SshTmuxDeprecationNoticePending, group: WarpifySettings, {
|
|||||||
)]
|
)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
#[schemars(
|
#[schemars(
|
||||||
description = "Controls SSH extension installation behavior.",
|
description = "Controls Wormhole helper installation behavior.",
|
||||||
rename_all = "snake_case"
|
rename_all = "snake_case"
|
||||||
)]
|
)]
|
||||||
pub enum SshExtensionInstallMode {
|
pub enum SshExtensionInstallMode {
|
||||||
@@ -124,18 +124,18 @@ pub enum SshExtensionInstallMode {
|
|||||||
AlwaysAsk,
|
AlwaysAsk,
|
||||||
/// Automatically install and connect without prompting.
|
/// Automatically install and connect without prompting.
|
||||||
AlwaysInstall,
|
AlwaysInstall,
|
||||||
/// Never install; fall back to wrapper-only SSH warpification.
|
/// Never install; fall back to wrapper-only SSH wormholing.
|
||||||
NeverInstall,
|
NeverInstall,
|
||||||
}
|
}
|
||||||
|
|
||||||
maybe_define_setting!(SshExtensionInstallModeSetting, group: WarpifySettings, {
|
maybe_define_setting!(SshExtensionInstallModeSetting, group: WormholeSettings, {
|
||||||
type: SshExtensionInstallMode,
|
type: SshExtensionInstallMode,
|
||||||
default: SshExtensionInstallMode::default(),
|
default: SshExtensionInstallMode::default(),
|
||||||
supported_platforms: SupportedPlatforms::ALL,
|
supported_platforms: SupportedPlatforms::ALL,
|
||||||
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
|
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
|
||||||
private: false,
|
private: false,
|
||||||
toml_path: "warpify.ssh.ssh_extension_install_mode",
|
toml_path: "wormhole.ssh.ssh_extension_install_mode",
|
||||||
description: "Controls SSH extension installation behavior.",
|
description: "Controls Wormhole helper installation behavior.",
|
||||||
});
|
});
|
||||||
|
|
||||||
impl SshExtensionInstallMode {
|
impl SshExtensionInstallMode {
|
||||||
@@ -151,7 +151,7 @@ impl SshExtensionInstallMode {
|
|||||||
/// Normally we use the define_settings_group! macro for singleton models of settings like this.
|
/// Normally we use the define_settings_group! macro for singleton models of settings like this.
|
||||||
/// However, this model needs to do some extra processing on the added_subshell_commands and store
|
/// However, this model needs to do some extra processing on the added_subshell_commands and store
|
||||||
/// an enriched representation in parsed_added_subshell_commands.
|
/// an enriched representation in parsed_added_subshell_commands.
|
||||||
pub struct WarpifySettings {
|
pub struct WormholeSettings {
|
||||||
/// A list of regexes that users can add to define new subshell-compatible commands. This
|
/// A list of regexes that users can add to define new subshell-compatible commands. This
|
||||||
/// represents the raw, serialized value. Therefore, it is Vec<String>.
|
/// represents the raw, serialized value. Therefore, it is Vec<String>.
|
||||||
pub added_subshell_commands: AddedSubshellCommands,
|
pub added_subshell_commands: AddedSubshellCommands,
|
||||||
@@ -161,9 +161,9 @@ pub struct WarpifySettings {
|
|||||||
/// needs to be kept up-to-date as added_subshell_commands changes. See the Self::register
|
/// needs to be kept up-to-date as added_subshell_commands changes. See the Self::register
|
||||||
/// method for how this is done.
|
/// method for how this is done.
|
||||||
pub parsed_added_subshell_commands: Vec<Result<Regex, regex::Error>>,
|
pub parsed_added_subshell_commands: Vec<Result<Regex, regex::Error>>,
|
||||||
/// A list of commands that we shouldn't attempt to warpify. These can be added either b/c the
|
/// A list of commands that we shouldn't attempt to wormhole. These can be added either b/c the
|
||||||
/// "don't ask again" button was clicked in the trigger banner, or it was added explicitly on
|
/// "don't ask again" button was clicked in the trigger banner, or it was added explicitly on
|
||||||
/// the Warpify settings page. This represents the raw, serialized value.
|
/// the Wormhole settings page. This represents the raw, serialized value.
|
||||||
pub subshell_command_denylist: SubshellCommandsDenylist,
|
pub subshell_command_denylist: SubshellCommandsDenylist,
|
||||||
/// This is subshell_command_denylist compiled to actual executable Regex. This is a Result as we
|
/// This is subshell_command_denylist compiled to actual executable Regex. This is a Result as we
|
||||||
/// cannot guarantee the values are valid regex. Even if we prevent them in the UI from entering
|
/// cannot guarantee the values are valid regex. Even if we prevent them in the UI from entering
|
||||||
@@ -172,11 +172,11 @@ pub struct WarpifySettings {
|
|||||||
/// method for how this is done.
|
/// method for how this is done.
|
||||||
pub parsed_subshell_command_denylist: Vec<Result<Regex, regex::Error>>,
|
pub parsed_subshell_command_denylist: Vec<Result<Regex, regex::Error>>,
|
||||||
|
|
||||||
/// A list of hosts that we shouldn't attempt to warpify. This supports regex.
|
/// A list of hosts that we shouldn't attempt to wormhole. This supports regex.
|
||||||
/// These can be added either b/c the "don't ask again" button was clicked in the trigger banner,
|
/// These can be added either b/c the "don't ask again" button was clicked in the trigger banner,
|
||||||
/// or it was added explicitly on the Warpify settings page.
|
/// or it was added explicitly on the Wormhole settings page.
|
||||||
/// While this could live in the `SshSettings` group, the custom processing shared with the other
|
/// While this could live in the `SshSettings` group, the custom processing shared with the other
|
||||||
/// subshell logic better justifies it living in the `WarpifySettings` group.
|
/// subshell logic better justifies it living in the `WormholeSettings` group.
|
||||||
pub ssh_hosts_denylist: SshHostsDenylist,
|
pub ssh_hosts_denylist: SshHostsDenylist,
|
||||||
/// This is ssh_hosts_denylist compiled to actual executable Regex. This is a Result as we
|
/// This is ssh_hosts_denylist compiled to actual executable Regex. This is a Result as we
|
||||||
/// cannot guarantee the values are valid regex. Even if we prevent them in the UI from entering
|
/// cannot guarantee the values are valid regex. Even if we prevent them in the UI from entering
|
||||||
@@ -185,10 +185,10 @@ pub struct WarpifySettings {
|
|||||||
/// method for how this is done.
|
/// method for how this is done.
|
||||||
pub parsed_ssh_hosts_denylist: Vec<Result<Regex, regex::Error>>,
|
pub parsed_ssh_hosts_denylist: Vec<Result<Regex, regex::Error>>,
|
||||||
|
|
||||||
/// This setting controls whether we should ever warpify ssh sessions.
|
/// This setting controls whether we should ever wormhole ssh sessions.
|
||||||
pub enable_ssh_warpification: EnableSshWarpification,
|
pub enable_ssh_wormholing: EnableSshWormholing,
|
||||||
|
|
||||||
/// Deprecated: unified into `enable_ssh_warpification`. Retained only so the one-time
|
/// Deprecated: unified into `enable_ssh_wormholing`. Retained only so the one-time
|
||||||
/// migration in `register` can read and forward a user's previous opt-out. Not used to
|
/// migration in `register` can read and forward a user's previous opt-out. Not used to
|
||||||
/// gate any behavior.
|
/// gate any behavior.
|
||||||
pub enable_ssh_wrapper: EnableSshWrapper,
|
pub enable_ssh_wrapper: EnableSshWrapper,
|
||||||
@@ -238,7 +238,7 @@ lazy_static! {
|
|||||||
// Matches commands that spawn a pipenv subshell.
|
// Matches commands that spawn a pipenv subshell.
|
||||||
PIPENV_SUBSHELL_COMMAND_REGEX.clone(),
|
PIPENV_SUBSHELL_COMMAND_REGEX.clone(),
|
||||||
|
|
||||||
// https://github.com/warpdotdev/Warp/issues/2736
|
// Matches aws-vault's subshell-spawning exec command.
|
||||||
Regex::new(r"^aws-vault\s+exec\b").expect("aws-vault regex invalid"),
|
Regex::new(r"^aws-vault\s+exec\b").expect("aws-vault regex invalid"),
|
||||||
|
|
||||||
// https://flox.dev/docs/reference/command-reference/flox-activate/
|
// https://flox.dev/docs/reference/command-reference/flox-activate/
|
||||||
@@ -251,7 +251,7 @@ lazy_static! {
|
|||||||
/// define_settings_group! macro, which is the basic template for user-defaults-backed settings.
|
/// define_settings_group! macro, which is the basic template for user-defaults-backed settings.
|
||||||
/// I have separated this stuff from the other impl block, which contains the subshell-specific
|
/// I have separated this stuff from the other impl block, which contains the subshell-specific
|
||||||
/// logic, because this is basically boilerplate.
|
/// logic, because this is basically boilerplate.
|
||||||
impl WarpifySettings {
|
impl WormholeSettings {
|
||||||
fn new_from_storage(ctx: &mut ModelContext<Self>) -> Self {
|
fn new_from_storage(ctx: &mut ModelContext<Self>) -> Self {
|
||||||
let added_subshell_commands = AddedSubshellCommands::new_from_storage(ctx);
|
let added_subshell_commands = AddedSubshellCommands::new_from_storage(ctx);
|
||||||
let subshell_command_denylist = SubshellCommandsDenylist::new_from_storage(ctx);
|
let subshell_command_denylist = SubshellCommandsDenylist::new_from_storage(ctx);
|
||||||
@@ -267,7 +267,7 @@ impl WarpifySettings {
|
|||||||
subshell_command_denylist,
|
subshell_command_denylist,
|
||||||
parsed_ssh_hosts_denylist: Self::parse_ssh_hosts_denylist(&ssh_hosts_denylist),
|
parsed_ssh_hosts_denylist: Self::parse_ssh_hosts_denylist(&ssh_hosts_denylist),
|
||||||
ssh_hosts_denylist,
|
ssh_hosts_denylist,
|
||||||
enable_ssh_warpification: EnableSshWarpification::new_from_storage(ctx),
|
enable_ssh_wormholing: EnableSshWormholing::new_from_storage(ctx),
|
||||||
enable_ssh_wrapper: EnableSshWrapper::new_from_storage(ctx),
|
enable_ssh_wrapper: EnableSshWrapper::new_from_storage(ctx),
|
||||||
use_ssh_tmux_wrapper: UseSshTmuxWrapper::new_from_storage(ctx),
|
use_ssh_tmux_wrapper: UseSshTmuxWrapper::new_from_storage(ctx),
|
||||||
ssh_tmux_deprecation_notice_pending: SshTmuxDeprecationNoticePending::new_from_storage(
|
ssh_tmux_deprecation_notice_pending: SshTmuxDeprecationNoticePending::new_from_storage(
|
||||||
@@ -294,7 +294,7 @@ impl WarpifySettings {
|
|||||||
subshell_command_denylist,
|
subshell_command_denylist,
|
||||||
parsed_ssh_hosts_denylist: Self::parse_ssh_hosts_denylist(&ssh_hosts_denylist),
|
parsed_ssh_hosts_denylist: Self::parse_ssh_hosts_denylist(&ssh_hosts_denylist),
|
||||||
ssh_hosts_denylist,
|
ssh_hosts_denylist,
|
||||||
enable_ssh_warpification: EnableSshWarpification::new(None),
|
enable_ssh_wormholing: EnableSshWormholing::new(None),
|
||||||
enable_ssh_wrapper: EnableSshWrapper::new(None),
|
enable_ssh_wrapper: EnableSshWrapper::new(None),
|
||||||
use_ssh_tmux_wrapper: UseSshTmuxWrapper::new(None),
|
use_ssh_tmux_wrapper: UseSshTmuxWrapper::new(None),
|
||||||
ssh_tmux_deprecation_notice_pending: SshTmuxDeprecationNoticePending::new(None),
|
ssh_tmux_deprecation_notice_pending: SshTmuxDeprecationNoticePending::new(None),
|
||||||
@@ -309,37 +309,37 @@ impl WarpifySettings {
|
|||||||
let handle = ctx.add_singleton_model(Self::new_from_storage);
|
let handle = ctx.add_singleton_model(Self::new_from_storage);
|
||||||
ctx.subscribe_to_model(&handle, |settings, event, ctx| {
|
ctx.subscribe_to_model(&handle, |settings, event, ctx| {
|
||||||
settings.update(ctx, |me, _| match event {
|
settings.update(ctx, |me, _| match event {
|
||||||
WarpifySettingsChangedEvent::AddedSubshellCommands { .. } => {
|
WormholeSettingsChangedEvent::AddedSubshellCommands { .. } => {
|
||||||
me.parsed_added_subshell_commands =
|
me.parsed_added_subshell_commands =
|
||||||
Self::parse_added_subshell_commands(&me.added_subshell_commands)
|
Self::parse_added_subshell_commands(&me.added_subshell_commands)
|
||||||
}
|
}
|
||||||
WarpifySettingsChangedEvent::SubshellCommandsDenylist { .. } => {
|
WormholeSettingsChangedEvent::SubshellCommandsDenylist { .. } => {
|
||||||
me.parsed_subshell_command_denylist =
|
me.parsed_subshell_command_denylist =
|
||||||
Self::parse_subshell_command_denylist(&me.subshell_command_denylist)
|
Self::parse_subshell_command_denylist(&me.subshell_command_denylist)
|
||||||
}
|
}
|
||||||
WarpifySettingsChangedEvent::SshHostsDenylist { .. } => {
|
WormholeSettingsChangedEvent::SshHostsDenylist { .. } => {
|
||||||
me.parsed_ssh_hosts_denylist =
|
me.parsed_ssh_hosts_denylist =
|
||||||
Self::parse_ssh_hosts_denylist(&me.ssh_hosts_denylist)
|
Self::parse_ssh_hosts_denylist(&me.ssh_hosts_denylist)
|
||||||
}
|
}
|
||||||
WarpifySettingsChangedEvent::EnableSshWarpification { .. } => {}
|
WormholeSettingsChangedEvent::EnableSshWormholing { .. } => {}
|
||||||
WarpifySettingsChangedEvent::EnableSshWrapper { .. } => {}
|
WormholeSettingsChangedEvent::EnableSshWrapper { .. } => {}
|
||||||
WarpifySettingsChangedEvent::UseSshTmuxWrapper { .. } => {}
|
WormholeSettingsChangedEvent::UseSshTmuxWrapper { .. } => {}
|
||||||
WarpifySettingsChangedEvent::SshTmuxDeprecationNoticePending { .. } => {}
|
WormholeSettingsChangedEvent::SshTmuxDeprecationNoticePending { .. } => {}
|
||||||
WarpifySettingsChangedEvent::SshExtensionInstallModeSetting { .. } => {}
|
WormholeSettingsChangedEvent::SshExtensionInstallModeSetting { .. } => {}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// One-time migration: if the user had explicitly set the legacy `enable_ssh_wrapper`
|
// One-time migration: if the user had explicitly set the legacy `enable_ssh_wrapper`
|
||||||
// setting to `false` (via `warpify.ssh.enable_legacy_ssh_wrapper = false` in their
|
// setting to `false` (via `wormhole.ssh.enable_legacy_ssh_wrapper = false` in their
|
||||||
// TOML config or the old `EnableSSHWrapper` storage key), honour that intent by
|
// TOML config or the old `EnableSSHWrapper` storage key), honour that intent by
|
||||||
// disabling `enable_ssh_warpification` — the canonical setting that now controls the
|
// disabling `enable_ssh_wormholing` — the canonical setting that now controls the
|
||||||
// same behaviour. Resetting `enable_ssh_wrapper` back to its default (`true`) ensures
|
// same behaviour. Resetting `enable_ssh_wrapper` back to its default (`true`) ensures
|
||||||
// the migration does not run again on subsequent launches.
|
// the migration does not run again on subsequent launches.
|
||||||
handle.update(ctx, |me, ctx| {
|
handle.update(ctx, |me, ctx| {
|
||||||
if me.enable_ssh_wrapper.is_value_explicitly_set() && !*me.enable_ssh_wrapper.value() {
|
if me.enable_ssh_wrapper.is_value_explicitly_set() && !*me.enable_ssh_wrapper.value() {
|
||||||
if let Err(e) = me.enable_ssh_warpification.set_value(false, ctx) {
|
if let Err(e) = me.enable_ssh_wormholing.set_value(false, ctx) {
|
||||||
log::error!(
|
log::error!(
|
||||||
"Failed to migrate enable_ssh_wrapper → enable_ssh_warpification: {e}"
|
"Failed to migrate enable_ssh_wrapper → enable_ssh_wormholing: {e}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if let Err(e) = me.enable_ssh_wrapper.set_value(true, ctx) {
|
if let Err(e) = me.enable_ssh_wrapper.set_value(true, ctx) {
|
||||||
@@ -366,7 +366,7 @@ impl WarpifySettings {
|
|||||||
});
|
});
|
||||||
|
|
||||||
register_settings_events!(
|
register_settings_events!(
|
||||||
WarpifySettings,
|
WormholeSettings,
|
||||||
added_subshell_commands,
|
added_subshell_commands,
|
||||||
AddedSubshellCommands,
|
AddedSubshellCommands,
|
||||||
handle.clone(),
|
handle.clone(),
|
||||||
@@ -374,7 +374,7 @@ impl WarpifySettings {
|
|||||||
);
|
);
|
||||||
|
|
||||||
register_settings_events!(
|
register_settings_events!(
|
||||||
WarpifySettings,
|
WormholeSettings,
|
||||||
subshell_command_denylist,
|
subshell_command_denylist,
|
||||||
SubshellCommandsDenylist,
|
SubshellCommandsDenylist,
|
||||||
handle.clone(),
|
handle.clone(),
|
||||||
@@ -382,15 +382,15 @@ impl WarpifySettings {
|
|||||||
);
|
);
|
||||||
|
|
||||||
register_settings_events!(
|
register_settings_events!(
|
||||||
WarpifySettings,
|
WormholeSettings,
|
||||||
enable_ssh_warpification,
|
enable_ssh_wormholing,
|
||||||
EnableSshWarpification,
|
EnableSshWormholing,
|
||||||
handle.clone(),
|
handle.clone(),
|
||||||
ctx
|
ctx
|
||||||
);
|
);
|
||||||
|
|
||||||
register_settings_events!(
|
register_settings_events!(
|
||||||
WarpifySettings,
|
WormholeSettings,
|
||||||
enable_ssh_wrapper,
|
enable_ssh_wrapper,
|
||||||
EnableSshWrapper,
|
EnableSshWrapper,
|
||||||
handle.clone(),
|
handle.clone(),
|
||||||
@@ -398,7 +398,7 @@ impl WarpifySettings {
|
|||||||
);
|
);
|
||||||
|
|
||||||
register_settings_events!(
|
register_settings_events!(
|
||||||
WarpifySettings,
|
WormholeSettings,
|
||||||
use_ssh_tmux_wrapper,
|
use_ssh_tmux_wrapper,
|
||||||
UseSshTmuxWrapper,
|
UseSshTmuxWrapper,
|
||||||
handle.clone(),
|
handle.clone(),
|
||||||
@@ -406,7 +406,7 @@ impl WarpifySettings {
|
|||||||
);
|
);
|
||||||
|
|
||||||
register_settings_events!(
|
register_settings_events!(
|
||||||
WarpifySettings,
|
WormholeSettings,
|
||||||
ssh_tmux_deprecation_notice_pending,
|
ssh_tmux_deprecation_notice_pending,
|
||||||
SshTmuxDeprecationNoticePending,
|
SshTmuxDeprecationNoticePending,
|
||||||
handle.clone(),
|
handle.clone(),
|
||||||
@@ -414,7 +414,7 @@ impl WarpifySettings {
|
|||||||
);
|
);
|
||||||
|
|
||||||
register_settings_events!(
|
register_settings_events!(
|
||||||
WarpifySettings,
|
WormholeSettings,
|
||||||
ssh_extension_install_mode,
|
ssh_extension_install_mode,
|
||||||
SshExtensionInstallModeSetting,
|
SshExtensionInstallModeSetting,
|
||||||
handle.clone(),
|
handle.clone(),
|
||||||
@@ -422,7 +422,7 @@ impl WarpifySettings {
|
|||||||
);
|
);
|
||||||
|
|
||||||
register_settings_events!(
|
register_settings_events!(
|
||||||
WarpifySettings,
|
WormholeSettings,
|
||||||
ssh_hosts_denylist,
|
ssh_hosts_denylist,
|
||||||
SshHostsDenylist,
|
SshHostsDenylist,
|
||||||
handle,
|
handle,
|
||||||
@@ -432,9 +432,9 @@ impl WarpifySettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// This is also something that would normally be generated by
|
/// This is also something that would normally be generated by
|
||||||
/// define_settings_group!(WarpifySettings). Since we didn't use that macro we define it manually
|
/// define_settings_group!(WormholeSettings). Since we didn't use that macro we define it manually
|
||||||
/// here. It's the event emitted by the setter methods when a setting value changes.
|
/// here. It's the event emitted by the setter methods when a setting value changes.
|
||||||
pub enum WarpifySettingsChangedEvent {
|
pub enum WormholeSettingsChangedEvent {
|
||||||
AddedSubshellCommands {
|
AddedSubshellCommands {
|
||||||
change_event_reason: ChangeEventReason,
|
change_event_reason: ChangeEventReason,
|
||||||
},
|
},
|
||||||
@@ -444,7 +444,7 @@ pub enum WarpifySettingsChangedEvent {
|
|||||||
SshHostsDenylist {
|
SshHostsDenylist {
|
||||||
change_event_reason: ChangeEventReason,
|
change_event_reason: ChangeEventReason,
|
||||||
},
|
},
|
||||||
EnableSshWarpification {
|
EnableSshWormholing {
|
||||||
change_event_reason: ChangeEventReason,
|
change_event_reason: ChangeEventReason,
|
||||||
},
|
},
|
||||||
EnableSshWrapper {
|
EnableSshWrapper {
|
||||||
@@ -461,15 +461,15 @@ pub enum WarpifySettingsChangedEvent {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for WarpifySettings {
|
impl Entity for WormholeSettings {
|
||||||
type Event = WarpifySettingsChangedEvent;
|
type Event = WormholeSettingsChangedEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SingletonEntity for WarpifySettings {}
|
impl SingletonEntity for WormholeSettings {}
|
||||||
|
|
||||||
/// This is the other impl block for this model. This one contains the actual subshell-specific
|
/// This is the other impl block for this model. This one contains the actual subshell-specific
|
||||||
/// logic.
|
/// logic.
|
||||||
impl WarpifySettings {
|
impl WormholeSettings {
|
||||||
fn is_built_in_subshell_match(command: &str) -> bool {
|
fn is_built_in_subshell_match(command: &str) -> bool {
|
||||||
for command_regex in SUBSHELL_COMMAND_REGEXES.iter() {
|
for command_regex in SUBSHELL_COMMAND_REGEXES.iter() {
|
||||||
if command_regex.is_match(command) {
|
if command_regex.is_match(command) {
|
||||||
@@ -494,7 +494,7 @@ impl WarpifySettings {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if SshWarpifyCommand::matches(command).is_some_and(|command| command.is_ssh_like_command())
|
if SshWormholeCommand::matches(command).is_some_and(|command| command.is_ssh_like_command())
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -505,8 +505,8 @@ impl WarpifySettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// While in-band generators are our best option for warpifying ssh sessions from powershell, hard-code
|
// While in-band generators are our best option for wormholing ssh sessions from powershell, hard-code
|
||||||
// the warpify subshell banner to show up.
|
// the wormhole subshell banner to show up.
|
||||||
if matches!(shell_family, ShellFamily::PowerShell)
|
if matches!(shell_family, ShellFamily::PowerShell)
|
||||||
&& parse_interactive_ssh_command(command).is_some()
|
&& parse_interactive_ssh_command(command).is_some()
|
||||||
{
|
{
|
||||||
@@ -602,7 +602,7 @@ impl WarpifySettings {
|
|||||||
new_added_commands_list.push(command_to_add.trim().to_owned());
|
new_added_commands_list.push(command_to_add.trim().to_owned());
|
||||||
|
|
||||||
// The set_value method generated by the maybe_define_setting! macro will take
|
// The set_value method generated by the maybe_define_setting! macro will take
|
||||||
// care of emitting the WarpifySettingsChangedEvent::AddedSubshellCommands event to keep
|
// care of emitting the WormholeSettingsChangedEvent::AddedSubshellCommands event to keep
|
||||||
// parsed_added_subshell_commands in sync.
|
// parsed_added_subshell_commands in sync.
|
||||||
self.added_subshell_commands
|
self.added_subshell_commands
|
||||||
.set_value(new_added_commands_list, ctx)
|
.set_value(new_added_commands_list, ctx)
|
||||||
@@ -611,7 +611,7 @@ impl WarpifySettings {
|
|||||||
ctx.notify();
|
ctx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the user has asked us to remember a command and avoid asking to warpify a subshell.
|
/// Check if the user has asked us to remember a command and avoid asking to wormhole a subshell.
|
||||||
pub fn is_denylisted_subshell_command(&self, command: &str) -> bool {
|
pub fn is_denylisted_subshell_command(&self, command: &str) -> bool {
|
||||||
let command = command.trim();
|
let command = command.trim();
|
||||||
self.parsed_subshell_command_denylist
|
self.parsed_subshell_command_denylist
|
||||||
+20
-20
@@ -1,7 +1,7 @@
|
|||||||
use settings::Setting;
|
use settings::Setting;
|
||||||
use warpui::{App, SingletonEntity};
|
use warpui::{App, SingletonEntity};
|
||||||
|
|
||||||
use super::WarpifySettings;
|
use super::WormholeSettings;
|
||||||
use crate::test_util::settings::initialize_settings_for_tests;
|
use crate::test_util::settings::initialize_settings_for_tests;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -10,12 +10,12 @@ fn test_parsed_subshell_commands_updated_via_self_subscription() {
|
|||||||
initialize_settings_for_tests(&mut app);
|
initialize_settings_for_tests(&mut app);
|
||||||
|
|
||||||
app.read(|ctx| {
|
app.read(|ctx| {
|
||||||
assert!(WarpifySettings::as_ref(ctx)
|
assert!(WormholeSettings::as_ref(ctx)
|
||||||
.parsed_added_subshell_commands
|
.parsed_added_subshell_commands
|
||||||
.is_empty());
|
.is_empty());
|
||||||
});
|
});
|
||||||
|
|
||||||
WarpifySettings::handle(&app).update(&mut app, |settings, ctx| {
|
WormholeSettings::handle(&app).update(&mut app, |settings, ctx| {
|
||||||
settings
|
settings
|
||||||
.added_subshell_commands
|
.added_subshell_commands
|
||||||
.set_value(vec!["^my-custom-shell$".to_string()], ctx)
|
.set_value(vec!["^my-custom-shell$".to_string()], ctx)
|
||||||
@@ -24,7 +24,7 @@ fn test_parsed_subshell_commands_updated_via_self_subscription() {
|
|||||||
|
|
||||||
// The parsed field must now contain the compiled regex.
|
// The parsed field must now contain the compiled regex.
|
||||||
app.read(|ctx| {
|
app.read(|ctx| {
|
||||||
let parsed = &WarpifySettings::as_ref(ctx).parsed_added_subshell_commands;
|
let parsed = &WormholeSettings::as_ref(ctx).parsed_added_subshell_commands;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parsed.len(),
|
parsed.len(),
|
||||||
1,
|
1,
|
||||||
@@ -41,14 +41,14 @@ fn test_parsed_subshell_commands_updated_via_self_subscription() {
|
|||||||
|
|
||||||
/// Verify that a user who previously set `enable_legacy_ssh_wrapper = false`
|
/// Verify that a user who previously set `enable_legacy_ssh_wrapper = false`
|
||||||
/// (old `SshSettings::enable_ssh_wrapper`) has that opt-out forwarded to
|
/// (old `SshSettings::enable_ssh_wrapper`) has that opt-out forwarded to
|
||||||
/// `enable_ssh_warpification` on first launch after the migration.
|
/// `enable_ssh_wormholing` on first launch after the migration.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_enable_ssh_wrapper_false_migrates_to_enable_ssh_warpification_false() {
|
fn test_enable_ssh_wrapper_false_migrates_to_enable_ssh_wormholing_false() {
|
||||||
App::test((), |mut app| async move {
|
App::test((), |mut app| async move {
|
||||||
initialize_settings_for_tests(&mut app);
|
initialize_settings_for_tests(&mut app);
|
||||||
|
|
||||||
// Simulate a user who had explicitly opted out of the legacy SSH wrapper.
|
// Simulate a user who had explicitly opted out of the legacy SSH wrapper.
|
||||||
WarpifySettings::handle(&app).update(&mut app, |settings, ctx| {
|
WormholeSettings::handle(&app).update(&mut app, |settings, ctx| {
|
||||||
settings
|
settings
|
||||||
.enable_ssh_wrapper
|
.enable_ssh_wrapper
|
||||||
.set_value(false, ctx)
|
.set_value(false, ctx)
|
||||||
@@ -63,13 +63,13 @@ fn test_enable_ssh_wrapper_false_migrates_to_enable_ssh_warpification_false() {
|
|||||||
// Simpler approach: confirm the migration logic produces the right state
|
// Simpler approach: confirm the migration logic produces the right state
|
||||||
// by applying it explicitly here.
|
// by applying it explicitly here.
|
||||||
app.update(|ctx| {
|
app.update(|ctx| {
|
||||||
WarpifySettings::handle(ctx).update(ctx, |me, ctx| {
|
WormholeSettings::handle(ctx).update(ctx, |me, ctx| {
|
||||||
if me.enable_ssh_wrapper.is_value_explicitly_set()
|
if me.enable_ssh_wrapper.is_value_explicitly_set()
|
||||||
&& !*me.enable_ssh_wrapper.value()
|
&& !*me.enable_ssh_wrapper.value()
|
||||||
{
|
{
|
||||||
me.enable_ssh_warpification
|
me.enable_ssh_wormholing
|
||||||
.set_value(false, ctx)
|
.set_value(false, ctx)
|
||||||
.expect("migration set enable_ssh_warpification");
|
.expect("migration set enable_ssh_wormholing");
|
||||||
me.enable_ssh_wrapper
|
me.enable_ssh_wrapper
|
||||||
.set_value(true, ctx)
|
.set_value(true, ctx)
|
||||||
.expect("migration reset enable_ssh_wrapper");
|
.expect("migration reset enable_ssh_wrapper");
|
||||||
@@ -78,10 +78,10 @@ fn test_enable_ssh_wrapper_false_migrates_to_enable_ssh_warpification_false() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.read(|ctx| {
|
app.read(|ctx| {
|
||||||
let settings = WarpifySettings::as_ref(ctx);
|
let settings = WormholeSettings::as_ref(ctx);
|
||||||
assert!(
|
assert!(
|
||||||
!*settings.enable_ssh_warpification.value(),
|
!*settings.enable_ssh_wormholing.value(),
|
||||||
"enable_ssh_warpification should be false after migration"
|
"enable_ssh_wormholing should be false after migration"
|
||||||
);
|
);
|
||||||
// The wrapper is reset to true so the migration condition
|
// The wrapper is reset to true so the migration condition
|
||||||
// (`!*enable_ssh_wrapper.value()`) won't fire again on the next launch.
|
// (`!*enable_ssh_wrapper.value()`) won't fire again on the next launch.
|
||||||
@@ -94,22 +94,22 @@ fn test_enable_ssh_wrapper_false_migrates_to_enable_ssh_warpification_false() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Verify that the default state (no legacy setting present) does not
|
/// Verify that the default state (no legacy setting present) does not
|
||||||
/// spuriously disable `enable_ssh_warpification`.
|
/// spuriously disable `enable_ssh_wormholing`.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_enable_ssh_wrapper_default_does_not_affect_enable_ssh_warpification() {
|
fn test_enable_ssh_wrapper_default_does_not_affect_enable_ssh_wormholing() {
|
||||||
App::test((), |mut app| async move {
|
App::test((), |mut app| async move {
|
||||||
initialize_settings_for_tests(&mut app);
|
initialize_settings_for_tests(&mut app);
|
||||||
|
|
||||||
app.read(|ctx| {
|
app.read(|ctx| {
|
||||||
let settings = WarpifySettings::as_ref(ctx);
|
let settings = WormholeSettings::as_ref(ctx);
|
||||||
// Neither setting should be explicitly set — both default to true.
|
// Neither setting should be explicitly set — both default to true.
|
||||||
assert!(
|
assert!(
|
||||||
!settings.enable_ssh_wrapper.is_value_explicitly_set(),
|
!settings.enable_ssh_wrapper.is_value_explicitly_set(),
|
||||||
"enable_ssh_wrapper should not be explicitly set in a fresh install"
|
"enable_ssh_wrapper should not be explicitly set in a fresh install"
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
*settings.enable_ssh_warpification.value(),
|
*settings.enable_ssh_wormholing.value(),
|
||||||
"enable_ssh_warpification should remain true when no migration is needed"
|
"enable_ssh_wormholing should remain true when no migration is needed"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -133,7 +133,7 @@ fn test_wsl_subshell_detection_success() {
|
|||||||
.iter()
|
.iter()
|
||||||
.for_each(|cmd| {
|
.for_each(|cmd| {
|
||||||
assert!(
|
assert!(
|
||||||
WarpifySettings::is_built_in_subshell_match(cmd),
|
WormholeSettings::is_built_in_subshell_match(cmd),
|
||||||
"{} failed to match",
|
"{} failed to match",
|
||||||
*cmd
|
*cmd
|
||||||
)
|
)
|
||||||
@@ -164,7 +164,7 @@ fn test_wsl_subshell_detection_fail() {
|
|||||||
.iter()
|
.iter()
|
||||||
.for_each(|cmd| {
|
.for_each(|cmd| {
|
||||||
assert!(
|
assert!(
|
||||||
!WarpifySettings::is_built_in_subshell_match(cmd),
|
!WormholeSettings::is_built_in_subshell_match(cmd),
|
||||||
"{} accidentally matched",
|
"{} accidentally matched",
|
||||||
*cmd
|
*cmd
|
||||||
)
|
)
|
||||||
+51
-99
@@ -7,14 +7,13 @@ use galaxy_core::ui::theme::GalaxyTheme;
|
|||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use warpui::elements::{
|
use warpui::elements::{
|
||||||
Border, Container, CrossAxisAlignment, Flex, Icon, MainAxisAlignment, MainAxisSize,
|
Border, Container, CrossAxisAlignment, Flex, Icon, MainAxisAlignment, MainAxisSize,
|
||||||
MouseStateHandle, ParentElement, SelectableArea, SelectionHandle, Text,
|
ParentElement, SelectableArea, SelectionHandle, Text,
|
||||||
};
|
};
|
||||||
use warpui::ui_components::components::{UiComponent, UiComponentStyles};
|
|
||||||
use warpui::{AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext};
|
use warpui::{AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext};
|
||||||
|
|
||||||
use super::render::{HORIZONTAL_TEXT_MARGIN, SSH_DOCS_URL, SUBSHELL_DOCS_URL};
|
use super::render::HORIZONTAL_TEXT_MARGIN;
|
||||||
use super::settings::WarpifySettings;
|
use super::settings::WormholeSettings;
|
||||||
use super::{render, subshell_bootstrap_success_block_bytes, WarpificationSource};
|
use super::{render, subshell_bootstrap_success_block_bytes};
|
||||||
use crate::ai::agent::ProgrammingLanguage;
|
use crate::ai::agent::ProgrammingLanguage;
|
||||||
use crate::ai::blocklist::code_block::{render_runnable_code_snippet, CodeSnippetButtonHandles};
|
use crate::ai::blocklist::code_block::{render_runnable_code_snippet, CodeSnippetButtonHandles};
|
||||||
use crate::appearance::Appearance;
|
use crate::appearance::Appearance;
|
||||||
@@ -27,20 +26,19 @@ use crate::workspace::WorkspaceAction;
|
|||||||
const VERTICAL_TEXT_MARGIN: f32 = 16.;
|
const VERTICAL_TEXT_MARGIN: f32 = 16.;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum WarpifySuccessBlockEvent {
|
pub enum WormholeSuccessBlockEvent {
|
||||||
OpenWarpifySettings,
|
OpenWormholeSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub enum WarpifySuccessBlockAction {
|
pub enum WormholeSuccessBlockAction {
|
||||||
ClearAutoWarpifySnippet,
|
ClearAutoWormholeSnippet,
|
||||||
OpenWarpifySettings,
|
OpenWormholeSettings,
|
||||||
OpenUrl(String),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AutoWarpifySnippet {
|
struct AutoWormholeSnippet {
|
||||||
/// On subshell initialization, this will contain the output grid to display,
|
/// On subshell initialization, this will contain the output grid to display,
|
||||||
/// containing info like how to auto-warpify the subshell.
|
/// containing info like how to auto-wormhole the subshell.
|
||||||
output_grid: Cow<'static, str>,
|
output_grid: Cow<'static, str>,
|
||||||
/// The output grid needs to be selectable to allow users to copy the command to their clipboard.
|
/// The output grid needs to be selectable to allow users to copy the command to their clipboard.
|
||||||
selection_handle: SelectionHandle,
|
selection_handle: SelectionHandle,
|
||||||
@@ -52,23 +50,20 @@ struct AutoWarpifySnippet {
|
|||||||
can_write_to_rc: bool,
|
can_write_to_rc: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WarpifySuccessBlock {
|
pub struct WormholeSuccessBlock {
|
||||||
source: WarpificationSource,
|
|
||||||
spawning_command: String,
|
spawning_command: String,
|
||||||
learn_more_link_mouse_states: MouseStateHandle,
|
auto_wormhole_snippet: Option<AutoWormholeSnippet>,
|
||||||
auto_warpify_snippet: Option<AutoWarpifySnippet>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WarpifySuccessBlock {
|
impl WormholeSuccessBlock {
|
||||||
#[allow(clippy::new_without_default)]
|
#[allow(clippy::new_without_default)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
source: WarpificationSource,
|
|
||||||
spawning_command: String,
|
spawning_command: String,
|
||||||
subshell_info: Option<SubshellInitializationInfo>,
|
subshell_info: Option<SubshellInitializationInfo>,
|
||||||
shell: Shell,
|
shell: Shell,
|
||||||
ctx: &mut ViewContext<Self>,
|
ctx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
ctx.subscribe_to_model(&WarpifySettings::handle(ctx), move |_, _, _, ctx| {
|
ctx.subscribe_to_model(&WormholeSettings::handle(ctx), move |_, _, _, ctx| {
|
||||||
ctx.notify();
|
ctx.notify();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -76,17 +71,17 @@ impl WarpifySuccessBlock {
|
|||||||
// getting the OS to write to the correct RC file.
|
// getting the OS to write to the correct RC file.
|
||||||
let remote_os = TargetOS::Linux;
|
let remote_os = TargetOS::Linux;
|
||||||
|
|
||||||
let is_auto_warpify_configured = subshell_info
|
let is_auto_wormhole_configured = subshell_info
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|info| info.was_triggered_by_rc_file_snippet)
|
.map(|info| info.was_triggered_by_rc_file_snippet)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let auto_warpify_snippet = if is_auto_warpify_configured {
|
let auto_wormhole_snippet = if is_auto_wormhole_configured {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
subshell_info.and_then(|subshell_info| {
|
subshell_info.and_then(|subshell_info| {
|
||||||
// If warpification wasn't triggered automatically, show a snippet about
|
// If wormholing wasn't triggered automatically, show a snippet about
|
||||||
// how to automatically warpify.
|
// how to automatically wormhole.
|
||||||
(!subshell_info.was_triggered_by_rc_file_snippet).then(|| {
|
(!subshell_info.was_triggered_by_rc_file_snippet).then(|| {
|
||||||
let (command, is_executable) = subshell_bootstrap_success_block_bytes(
|
let (command, is_executable) = subshell_bootstrap_success_block_bytes(
|
||||||
&subshell_info,
|
&subshell_info,
|
||||||
@@ -108,8 +103,8 @@ impl WarpifySuccessBlock {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
let auto_warpify_snippet = auto_warpify_snippet.map(|(output_grid, can_write_to_rc)| {
|
let auto_wormhole_snippet = auto_wormhole_snippet.map(|(output_grid, can_write_to_rc)| {
|
||||||
AutoWarpifySnippet {
|
AutoWormholeSnippet {
|
||||||
description: (if !output_grid.is_empty() {
|
description: (if !output_grid.is_empty() {
|
||||||
"Run the following to automatically Wormhole in the future:"
|
"Run the following to automatically Wormhole in the future:"
|
||||||
} else {
|
} else {
|
||||||
@@ -125,15 +120,13 @@ impl WarpifySuccessBlock {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
source,
|
|
||||||
learn_more_link_mouse_states: Default::default(),
|
|
||||||
spawning_command,
|
spawning_command,
|
||||||
auto_warpify_snippet,
|
auto_wormhole_snippet,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selected_text(&self) -> Option<String> {
|
pub fn selected_text(&self) -> Option<String> {
|
||||||
self.auto_warpify_snippet
|
self.auto_wormhole_snippet
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|snippet| snippet.selected_text.read().clone())
|
.and_then(|snippet| snippet.selected_text.read().clone())
|
||||||
}
|
}
|
||||||
@@ -156,18 +149,12 @@ impl WarpifySuccessBlock {
|
|||||||
) -> Box<dyn Element> {
|
) -> Box<dyn Element> {
|
||||||
let header_contents = render::build_header_row(
|
let header_contents = render::build_header_row(
|
||||||
"Session Wormholed",
|
"Session Wormholed",
|
||||||
Icon::new(UiIcon::Warp.into(), theme.active_ui_detail()),
|
Icon::new(UiIcon::GalaxyLogo.into(), theme.active_ui_detail()),
|
||||||
theme,
|
theme,
|
||||||
appearance,
|
appearance,
|
||||||
)
|
)
|
||||||
.with_margin_right(8.)
|
.with_margin_right(8.)
|
||||||
.finish();
|
.finish();
|
||||||
let header_contents = Container::new(
|
|
||||||
Flex::row()
|
|
||||||
.with_children([header_contents, self.render_learn_more_link(appearance)])
|
|
||||||
.finish(),
|
|
||||||
)
|
|
||||||
.finish();
|
|
||||||
|
|
||||||
Container::new(
|
Container::new(
|
||||||
Flex::row()
|
Flex::row()
|
||||||
@@ -182,45 +169,13 @@ impl WarpifySuccessBlock {
|
|||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_learn_more_link(&self, appearance: &Appearance) -> Box<dyn Element> {
|
/// Fired when a block ends and we are not in a Wormholed session.
|
||||||
let url = match self.source {
|
pub fn on_wormholed_session_complete(&mut self, ctx: &mut ViewContext<Self>) {
|
||||||
WarpificationSource::Ssh => SSH_DOCS_URL,
|
self.clear_auto_wormhole_snippet(ctx);
|
||||||
WarpificationSource::Subshell => SUBSHELL_DOCS_URL,
|
|
||||||
};
|
|
||||||
|
|
||||||
let font_family_id = appearance.monospace_font_family();
|
|
||||||
let font_size = appearance.monospace_font_size();
|
|
||||||
appearance
|
|
||||||
.ui_builder()
|
|
||||||
.link(
|
|
||||||
"Learn more".into(),
|
|
||||||
None,
|
|
||||||
Some(Box::new({
|
|
||||||
move |ctx| {
|
|
||||||
ctx.dispatch_typed_action(WarpifySuccessBlockAction::OpenUrl(
|
|
||||||
url.to_owned(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
})),
|
|
||||||
self.learn_more_link_mouse_states.clone(),
|
|
||||||
)
|
|
||||||
.soft_wrap(false)
|
|
||||||
.with_style(UiComponentStyles {
|
|
||||||
font_size: Some(font_size),
|
|
||||||
font_family_id: Some(font_family_id),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.build()
|
|
||||||
.finish()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fired when a block ends and we are not in a Warpified session.
|
pub fn clear_auto_wormhole_snippet(&mut self, ctx: &mut ViewContext<Self>) {
|
||||||
pub fn on_warpified_session_complete(&mut self, ctx: &mut ViewContext<Self>) {
|
self.auto_wormhole_snippet = None;
|
||||||
self.clear_auto_warpify_snippet(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear_auto_warpify_snippet(&mut self, ctx: &mut ViewContext<Self>) {
|
|
||||||
self.auto_warpify_snippet = None;
|
|
||||||
ctx.notify();
|
ctx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,16 +186,16 @@ impl WarpifySuccessBlock {
|
|||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
) -> Option<Box<dyn Element>> {
|
) -> Option<Box<dyn Element>> {
|
||||||
let theme = appearance.theme();
|
let theme = appearance.theme();
|
||||||
let auto_warpify_snippet = self.auto_warpify_snippet.as_ref()?;
|
let auto_wormhole_snippet = self.auto_wormhole_snippet.as_ref()?;
|
||||||
|
|
||||||
if auto_warpify_snippet.output_grid.is_empty() {
|
if auto_wormhole_snippet.output_grid.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let shell_language = ProgrammingLanguage::Shell(auto_warpify_snippet.shell_type);
|
let shell_language = ProgrammingLanguage::Shell(auto_wormhole_snippet.shell_type);
|
||||||
let runnable_command = render_runnable_code_snippet(
|
let runnable_command = render_runnable_code_snippet(
|
||||||
&auto_warpify_snippet.output_grid,
|
&auto_wormhole_snippet.output_grid,
|
||||||
if auto_warpify_snippet.can_write_to_rc {
|
if auto_wormhole_snippet.can_write_to_rc {
|
||||||
Some(&shell_language)
|
Some(&shell_language)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -251,7 +206,7 @@ impl WarpifySuccessBlock {
|
|||||||
code_snippet.to_string(),
|
code_snippet.to_string(),
|
||||||
));
|
));
|
||||||
|
|
||||||
ctx.dispatch_typed_action(WarpifySuccessBlockAction::ClearAutoWarpifySnippet);
|
ctx.dispatch_typed_action(WormholeSuccessBlockAction::ClearAutoWormholeSnippet);
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
Some(Box::new({
|
Some(Box::new({
|
||||||
@@ -259,19 +214,19 @@ impl WarpifySuccessBlock {
|
|||||||
ctx.dispatch_typed_action(WorkspaceAction::CopyTextToClipboard(code_snippet));
|
ctx.dispatch_typed_action(WorkspaceAction::CopyTextToClipboard(code_snippet));
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
Some(auto_warpify_snippet.code_snippet_handles.clone()),
|
Some(auto_wormhole_snippet.code_snippet_handles.clone()),
|
||||||
app,
|
app,
|
||||||
);
|
);
|
||||||
|
|
||||||
let semantic_selection = SemanticSelection::as_ref(app);
|
let semantic_selection = SemanticSelection::as_ref(app);
|
||||||
let selected_text = auto_warpify_snippet.selected_text.clone();
|
let selected_text = auto_wormhole_snippet.selected_text.clone();
|
||||||
|
|
||||||
// TODO(Simon): Implement full selection and copying functionality for the WarpifySuccessBlock.
|
// TODO(Simon): Implement full selection and copying functionality for the WormholeSuccessBlock.
|
||||||
// Look to the `EnvVarCollectionBlock` for the existing implementation paradigm. We don't
|
// Look to the `EnvVarCollectionBlock` for the existing implementation paradigm. We don't
|
||||||
// yet have a robust way of ensuring that every aspect of text selection is implemented
|
// yet have a robust way of ensuring that every aspect of text selection is implemented
|
||||||
// properly, so be extra careful not to miss any details!
|
// properly, so be extra careful not to miss any details!
|
||||||
let output_grid = SelectableArea::new(
|
let output_grid = SelectableArea::new(
|
||||||
auto_warpify_snippet.selection_handle.clone(),
|
auto_wormhole_snippet.selection_handle.clone(),
|
||||||
move |selection_args, _, _| {
|
move |selection_args, _, _| {
|
||||||
*selected_text.write() = selection_args.selection;
|
*selected_text.write() = selection_args.selection;
|
||||||
},
|
},
|
||||||
@@ -285,7 +240,7 @@ impl WarpifySuccessBlock {
|
|||||||
.with_child(
|
.with_child(
|
||||||
Container::new(
|
Container::new(
|
||||||
Text::new(
|
Text::new(
|
||||||
auto_warpify_snippet.description.clone(),
|
auto_wormhole_snippet.description.clone(),
|
||||||
appearance.monospace_font_family(),
|
appearance.monospace_font_family(),
|
||||||
appearance.monospace_font_size(),
|
appearance.monospace_font_size(),
|
||||||
)
|
)
|
||||||
@@ -307,15 +262,15 @@ impl WarpifySuccessBlock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for WarpifySuccessBlock {
|
impl Entity for WormholeSuccessBlock {
|
||||||
type Event = WarpifySuccessBlockEvent;
|
type Event = WormholeSuccessBlockEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const WARPIFY_SUCCESS_BLOCK_VISIBLE_KEY: &str = "WarpifySuccessBlockVisible";
|
pub const WORMHOLE_SUCCESS_BLOCK_VISIBLE_KEY: &str = "WormholeSuccessBlockVisible";
|
||||||
|
|
||||||
impl View for WarpifySuccessBlock {
|
impl View for WormholeSuccessBlock {
|
||||||
fn ui_name() -> &'static str {
|
fn ui_name() -> &'static str {
|
||||||
"WarpifySuccessBlock"
|
"WormholeSuccessBlock"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self, app: &AppContext) -> Box<dyn Element> {
|
fn render(&self, app: &AppContext) -> Box<dyn Element> {
|
||||||
@@ -340,19 +295,16 @@ impl View for WarpifySuccessBlock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypedActionView for WarpifySuccessBlock {
|
impl TypedActionView for WormholeSuccessBlock {
|
||||||
type Action = WarpifySuccessBlockAction;
|
type Action = WormholeSuccessBlockAction;
|
||||||
|
|
||||||
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
|
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
|
||||||
match action {
|
match action {
|
||||||
WarpifySuccessBlockAction::OpenWarpifySettings => {
|
WormholeSuccessBlockAction::OpenWormholeSettings => {
|
||||||
ctx.emit(WarpifySuccessBlockEvent::OpenWarpifySettings);
|
ctx.emit(WormholeSuccessBlockEvent::OpenWormholeSettings);
|
||||||
}
|
}
|
||||||
WarpifySuccessBlockAction::OpenUrl(url) => {
|
WormholeSuccessBlockAction::ClearAutoWormholeSnippet => {
|
||||||
ctx.open_url(url);
|
self.clear_auto_wormhole_snippet(ctx);
|
||||||
}
|
|
||||||
WarpifySuccessBlockAction::ClearAutoWarpifySnippet => {
|
|
||||||
self.clear_auto_warpify_snippet(ctx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+39
-39
@@ -6,7 +6,7 @@ use galaxyui::r#async::SpawnedFutureHandle;
|
|||||||
use galaxyui::{EntityId, SingletonEntity as _, ViewContext, ViewHandle};
|
use galaxyui::{EntityId, SingletonEntity as _, ViewContext, ViewHandle};
|
||||||
use parking_lot::FairMutex;
|
use parking_lot::FairMutex;
|
||||||
|
|
||||||
use super::success_block::WarpifySuccessBlock;
|
use super::success_block::WormholeSuccessBlock;
|
||||||
use crate::terminal::model::block::BlockId;
|
use crate::terminal::model::block::BlockId;
|
||||||
use crate::terminal::model::session::SessionId;
|
use crate::terminal::model::session::SessionId;
|
||||||
use crate::terminal::model::terminal_model::SubshellInitializationInfo;
|
use crate::terminal::model::terminal_model::SubshellInitializationInfo;
|
||||||
@@ -40,8 +40,8 @@ impl SubshellSeparatorState {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum SshBlockState {
|
pub enum SshBlockState {
|
||||||
WarpifySuccess {
|
WormholeSuccess {
|
||||||
handle: ViewHandle<WarpifySuccessBlock>,
|
handle: ViewHandle<WormholeSuccessBlock>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,18 +52,18 @@ impl SshBlockState {
|
|||||||
|
|
||||||
pub fn get_block_view_id(&self) -> EntityId {
|
pub fn get_block_view_id(&self) -> EntityId {
|
||||||
match self {
|
match self {
|
||||||
SshBlockState::WarpifySuccess { handle, .. } => handle.id(),
|
SshBlockState::WormholeSuccess { handle, .. } => handle.id(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_warpified_session_complete(
|
pub fn on_wormholed_session_complete(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut ViewContext<TerminalView>,
|
ctx: &mut ViewContext<TerminalView>,
|
||||||
) -> Option<EntityId> {
|
) -> Option<EntityId> {
|
||||||
match self {
|
match self {
|
||||||
SshBlockState::WarpifySuccess { handle } => {
|
SshBlockState::WormholeSuccess { handle } => {
|
||||||
handle.update(ctx, |block, ctx| {
|
handle.update(ctx, |block, ctx| {
|
||||||
block.on_warpified_session_complete(ctx);
|
block.on_wormholed_session_complete(ctx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,29 +71,29 @@ impl SshBlockState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Temporary state used to trigger Warpification.
|
/// Temporary state used to trigger Wormholing.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct WarpifyTriggerState {
|
struct WormholeTriggerState {
|
||||||
block_id: Option<BlockId>,
|
block_id: Option<BlockId>,
|
||||||
|
|
||||||
/// Lets us abort an attempt to auto warpify if the subshell command
|
/// Lets us abort an attempt to auto wormhole if the subshell command
|
||||||
/// hasn't completed.
|
/// hasn't completed.
|
||||||
auto_warpify_abort_handle: Option<SpawnedFutureHandle>,
|
auto_wormhole_abort_handle: Option<SpawnedFutureHandle>,
|
||||||
|
|
||||||
/// The subshell banner waits 1s before showing. This is to see that the command stays running
|
/// The subshell banner waits 1s before showing. This is to see that the command stays running
|
||||||
/// for a while without exiting. We store the abort handle here so that the
|
/// for a while without exiting. We store the abort handle here so that the
|
||||||
/// TerminalEvent::BlockCompleted event can abort the banner.
|
/// TerminalEvent::BlockCompleted event can abort the banner.
|
||||||
subshell_banner_abort_handle: Option<SpawnedFutureHandle>,
|
subshell_banner_abort_handle: Option<SpawnedFutureHandle>,
|
||||||
|
|
||||||
/// The command which may trigger ssh Warpification
|
/// The command which may trigger ssh Wormholing
|
||||||
pending_command: Option<String>,
|
pending_command: Option<String>,
|
||||||
/// The Host which may trigger ssh Warpification
|
/// The Host which may trigger ssh Wormholing
|
||||||
pending_warpify_ssh_host: Option<String>,
|
pending_wormhole_ssh_host: Option<String>,
|
||||||
|
|
||||||
/// Which, if any, SSH block is currently added to the blocklist.
|
/// Which, if any, SSH block is currently added to the blocklist.
|
||||||
ssh_block_state: Option<SshBlockState>,
|
ssh_block_state: Option<SshBlockState>,
|
||||||
|
|
||||||
ssh_warpify_timeout_handle: Option<SpawnedFutureHandle>,
|
ssh_wormhole_timeout_handle: Option<SpawnedFutureHandle>,
|
||||||
|
|
||||||
shell_type: Option<ShellType>,
|
shell_type: Option<ShellType>,
|
||||||
|
|
||||||
@@ -101,17 +101,17 @@ struct WarpifyTriggerState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct WarpifyState {
|
pub struct WormholeState {
|
||||||
session_id: Option<SessionId>,
|
session_id: Option<SessionId>,
|
||||||
|
|
||||||
pending_state: Option<WarpifyTriggerState>,
|
pending_state: Option<WormholeTriggerState>,
|
||||||
/// Stores the metadata needed to render any separators above the first block of a subshell.
|
/// Stores the metadata needed to render any separators above the first block of a subshell.
|
||||||
subshell_separator_state: SubshellSeparatorState,
|
subshell_separator_state: SubshellSeparatorState,
|
||||||
/// A unique-enough ID that is used to validate that a timeout is still valid.
|
/// A unique-enough ID that is used to validate that a timeout is still valid.
|
||||||
timeout_id: u8,
|
timeout_id: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WarpifyState {
|
impl WormholeState {
|
||||||
pub fn delete_state(&mut self) {
|
pub fn delete_state(&mut self) {
|
||||||
self.pending_state.take();
|
self.pending_state.take();
|
||||||
}
|
}
|
||||||
@@ -180,32 +180,32 @@ impl WarpifyState {
|
|||||||
.and_then(|state| state.subshell_banner_abort_handle.take())
|
.and_then(|state| state.subshell_banner_abort_handle.take())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_auto_warpify_abort_handle(&mut self, spawned_future_handle: SpawnedFutureHandle) {
|
pub fn add_auto_wormhole_abort_handle(&mut self, spawned_future_handle: SpawnedFutureHandle) {
|
||||||
let pending_state = self.pending_state.get_or_insert_with(Default::default);
|
let pending_state = self.pending_state.get_or_insert_with(Default::default);
|
||||||
pending_state.auto_warpify_abort_handle = Some(spawned_future_handle);
|
pending_state.auto_wormhole_abort_handle = Some(spawned_future_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn abort_auto_warpify(&mut self) {
|
pub fn abort_auto_wormhole(&mut self) {
|
||||||
if let Some(abort_handle) = self
|
if let Some(abort_handle) = self
|
||||||
.pending_state
|
.pending_state
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.and_then(|state| state.auto_warpify_abort_handle.take())
|
.and_then(|state| state.auto_wormhole_abort_handle.take())
|
||||||
{
|
{
|
||||||
abort_handle.abort();
|
abort_handle.abort();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_ssh_warpify_timeout_handle(&mut self, spawned_future_handle: SpawnedFutureHandle) {
|
pub fn add_ssh_wormhole_timeout_handle(&mut self, spawned_future_handle: SpawnedFutureHandle) {
|
||||||
let pending_state = self.pending_state.get_or_insert_with(Default::default);
|
let pending_state = self.pending_state.get_or_insert_with(Default::default);
|
||||||
pending_state.ssh_warpify_timeout_handle = Some(spawned_future_handle);
|
pending_state.ssh_wormhole_timeout_handle = Some(spawned_future_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn abort_ssh_warpify_timeout(&mut self) {
|
pub fn abort_ssh_wormhole_timeout(&mut self) {
|
||||||
self.replace_timeout_id();
|
self.replace_timeout_id();
|
||||||
if let Some(handle) = self
|
if let Some(handle) = self
|
||||||
.pending_state
|
.pending_state
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.and_then(|state| state.ssh_warpify_timeout_handle.take())
|
.and_then(|state| state.ssh_wormhole_timeout_handle.take())
|
||||||
{
|
{
|
||||||
handle.abort();
|
handle.abort();
|
||||||
};
|
};
|
||||||
@@ -231,31 +231,31 @@ impl WarpifyState {
|
|||||||
pub fn get_pending_ssh_host(&self) -> Option<String> {
|
pub fn get_pending_ssh_host(&self) -> Option<String> {
|
||||||
self.pending_state
|
self.pending_state
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|state: &WarpifyTriggerState| state.pending_warpify_ssh_host.clone())
|
.and_then(|state: &WormholeTriggerState| state.pending_wormhole_ssh_host.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_pending_ssh_command(&self) -> Option<String> {
|
pub fn get_pending_ssh_command(&self) -> Option<String> {
|
||||||
self.pending_state
|
self.pending_state
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|state: &WarpifyTriggerState| state.pending_command.clone())
|
.and_then(|state: &WormholeTriggerState| state.pending_command.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn take_pending_ssh_host(&mut self) -> Option<String> {
|
pub fn take_pending_ssh_host(&mut self) -> Option<String> {
|
||||||
self.pending_state
|
self.pending_state
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.and_then(|state: &mut WarpifyTriggerState| state.pending_warpify_ssh_host.take())
|
.and_then(|state: &mut WormholeTriggerState| state.pending_wormhole_ssh_host.take())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_pending_ssh_host(&mut self) {
|
pub fn clear_pending_ssh_host(&mut self) {
|
||||||
if let Some(ref mut pending_state) = self.pending_state.as_mut() {
|
if let Some(ref mut pending_state) = self.pending_state.as_mut() {
|
||||||
pending_state.pending_warpify_ssh_host = None;
|
pending_state.pending_wormhole_ssh_host = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_pending_ssh_host(&mut self, command: String, ssh_host: Option<String>) {
|
pub fn set_pending_ssh_host(&mut self, command: String, ssh_host: Option<String>) {
|
||||||
let pending_state = self.pending_state.get_or_insert_with(Default::default);
|
let pending_state = self.pending_state.get_or_insert_with(Default::default);
|
||||||
pending_state.pending_command = Some(command);
|
pending_state.pending_command = Some(command);
|
||||||
pending_state.pending_warpify_ssh_host = ssh_host;
|
pending_state.pending_wormhole_ssh_host = ssh_host;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_block_id(&mut self, block_id: BlockId) {
|
pub fn set_block_id(&mut self, block_id: BlockId) {
|
||||||
@@ -290,10 +290,10 @@ impl WarpifyState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Called once whenever we get a local block completed, as opposed to a remote ssh block
|
/// Called once whenever we get a local block completed, as opposed to a remote ssh block
|
||||||
/// and we have a Warpify Success block.
|
/// and we have a Wormhole Success block.
|
||||||
fn on_warpified_session_complete(
|
fn on_wormholed_session_complete(
|
||||||
&mut self,
|
&mut self,
|
||||||
state: WarpifyTriggerState,
|
state: WormholeTriggerState,
|
||||||
ctx: &mut ViewContext<TerminalView>,
|
ctx: &mut ViewContext<TerminalView>,
|
||||||
) -> Option<EntityId> {
|
) -> Option<EntityId> {
|
||||||
self.clear_ssh_block_state();
|
self.clear_ssh_block_state();
|
||||||
@@ -301,16 +301,16 @@ impl WarpifyState {
|
|||||||
let Some(block) = &state.ssh_block_state else {
|
let Some(block) = &state.ssh_block_state else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
block.on_warpified_session_complete(ctx)
|
block.on_wormholed_session_complete(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_warpify_start(&mut self, active_session_id: Option<SessionId>) {
|
pub fn on_wormhole_start(&mut self, active_session_id: Option<SessionId>) {
|
||||||
self.session_id = active_session_id;
|
self.session_id = active_session_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called whenever a block is completed, to determine whether a Warpified session
|
/// Called whenever a block is completed, to determine whether a Wormholed session
|
||||||
/// has been completed.
|
/// has been completed.
|
||||||
pub fn get_completed_warpify_session_id(
|
pub fn get_completed_wormhole_session_id(
|
||||||
&mut self,
|
&mut self,
|
||||||
active_session_id: Option<SessionId>,
|
active_session_id: Option<SessionId>,
|
||||||
ctx: &mut ViewContext<TerminalView>,
|
ctx: &mut ViewContext<TerminalView>,
|
||||||
@@ -319,7 +319,7 @@ impl WarpifyState {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if let Some(state) = self.pending_state.take() {
|
if let Some(state) = self.pending_state.take() {
|
||||||
return self.on_warpified_session_complete(state, ctx);
|
return self.on_wormholed_session_complete(state, ctx);
|
||||||
};
|
};
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,7 @@ use crate::server::server_api::ServerApiProvider;
|
|||||||
use crate::settings::PrivacySettings;
|
use crate::settings::PrivacySettings;
|
||||||
use crate::terminal::model::session::{IsSSHWrapperSession, SessionInfo};
|
use crate::terminal::model::session::{IsSSHWrapperSession, SessionInfo};
|
||||||
use crate::terminal::model_events::{ModelEvent, ModelEventDispatcher};
|
use crate::terminal::model_events::{ModelEvent, ModelEventDispatcher};
|
||||||
use crate::terminal::warpify::settings::{SshExtensionInstallMode, WarpifySettings};
|
use crate::terminal::wormhole::settings::{SshExtensionInstallMode, WormholeSettings};
|
||||||
use crate::{send_telemetry_from_ctx, TelemetryEvent};
|
use crate::{send_telemetry_from_ctx, TelemetryEvent};
|
||||||
|
|
||||||
/// Per-SSH-init state machine. Encoding the state as an enum makes invalid
|
/// Per-SSH-init state machine. Encoding the state as an enum makes invalid
|
||||||
@@ -310,7 +310,7 @@ impl<T: EventLoopSender> RemoteServerController<T> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ok(false) => {
|
Ok(false) => {
|
||||||
let install_mode = *WarpifySettings::as_ref(ctx)
|
let install_mode = *WormholeSettings::as_ref(ctx)
|
||||||
.ssh_extension_install_mode
|
.ssh_extension_install_mode
|
||||||
.value();
|
.value();
|
||||||
match install_mode {
|
match install_mode {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ pub fn initialize_settings_for_tests_with_mode(
|
|||||||
use crate::terminal::session_settings::SessionSettings;
|
use crate::terminal::session_settings::SessionSettings;
|
||||||
use crate::terminal::settings::TerminalSettings;
|
use crate::terminal::settings::TerminalSettings;
|
||||||
use crate::terminal::shared_session::settings::SharedSessionSettings;
|
use crate::terminal::shared_session::settings::SharedSessionSettings;
|
||||||
use crate::terminal::warpify::settings::WarpifySettings;
|
use crate::terminal::wormhole::settings::WormholeSettings;
|
||||||
use crate::terminal::BlockListSettings;
|
use crate::terminal::BlockListSettings;
|
||||||
use crate::undo_close::UndoCloseSettings;
|
use crate::undo_close::UndoCloseSettings;
|
||||||
use crate::user_config::WarpConfig;
|
use crate::user_config::WarpConfig;
|
||||||
@@ -104,7 +104,7 @@ pub fn initialize_settings_for_tests_with_mode(
|
|||||||
ScrollSettings::register(app);
|
ScrollSettings::register(app);
|
||||||
SelectionSettings::register(app);
|
SelectionSettings::register(app);
|
||||||
app.update(|ctx| {
|
app.update(|ctx| {
|
||||||
WarpifySettings::register(ctx);
|
WormholeSettings::register(ctx);
|
||||||
});
|
});
|
||||||
SessionSettings::register(app);
|
SessionSettings::register(app);
|
||||||
SshSettings::register(app);
|
SshSettings::register(app);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user