mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-24 20:26:08 +00:00
cli: when attaching, always print the connection link (#178445)
Fixes #178090
This commit is contained in:
@@ -73,6 +73,8 @@ pub fn get_default_user_agent() -> String {
|
||||
)
|
||||
}
|
||||
|
||||
const NO_COLOR_ENV: &str = "NO_COLOR";
|
||||
|
||||
lazy_static! {
|
||||
pub static ref TUNNEL_SERVICE_USER_AGENT: String =
|
||||
match std::env::var(TUNNEL_SERVICE_USER_AGENT_ENV_VAR) {
|
||||
@@ -101,5 +103,11 @@ lazy_static! {
|
||||
option_env!("VSCODE_CLI_SERVER_NAME_MAP").and_then(|s| serde_json::from_str(s).unwrap());
|
||||
|
||||
/// Whether i/o interactions are allowed in the current CLI.
|
||||
pub static ref IS_INTERACTIVE_CLI: bool = atty::is(atty::Stream::Stdin) && std::env::var(NONINTERACTIVE_VAR).is_err();
|
||||
pub static ref IS_A_TTY: bool = atty::is(atty::Stream::Stdin);
|
||||
|
||||
/// Whether i/o interactions are allowed in the current CLI.
|
||||
pub static ref COLORS_ENABLED: bool = *IS_A_TTY && std::env::var(NO_COLOR_ENV).is_err();
|
||||
|
||||
/// Whether i/o interactions are allowed in the current CLI.
|
||||
pub static ref IS_INTERACTIVE_CLI: bool = *IS_A_TTY && std::env::var(NONINTERACTIVE_VAR).is_err();
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@ use opentelemetry::{
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::{env, path::Path, sync::Arc};
|
||||
use std::{
|
||||
io::Write,
|
||||
sync::atomic::{AtomicU32, Ordering},
|
||||
};
|
||||
use std::{path::Path, sync::Arc};
|
||||
|
||||
const NO_COLOR_ENV: &str = "NO_COLOR";
|
||||
use crate::constants::COLORS_ENABLED;
|
||||
|
||||
static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
@@ -71,7 +71,7 @@ impl Level {
|
||||
}
|
||||
|
||||
pub fn color_code(&self) -> Option<&str> {
|
||||
if env::var(NO_COLOR_ENV).is_ok() || !atty::is(atty::Stream::Stdout) {
|
||||
if !*COLORS_ENABLED {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
use super::paths::{InstalledServer, LastUsedServers, ServerPaths};
|
||||
use crate::async_pipe::get_socket_name;
|
||||
use crate::constants::{APPLICATION_NAME, QUALITYLESS_PRODUCT_NAME, QUALITYLESS_SERVER_NAME};
|
||||
use crate::constants::{
|
||||
APPLICATION_NAME, EDITOR_WEB_URL, QUALITYLESS_PRODUCT_NAME, QUALITYLESS_SERVER_NAME,
|
||||
};
|
||||
use crate::options::{Quality, TelemetryLevel};
|
||||
use crate::state::LauncherPaths;
|
||||
use crate::update_service::{
|
||||
@@ -797,3 +799,40 @@ fn parse_port_from(text: &str) -> Option<u16> {
|
||||
.and_then(|path| path.as_str().parse::<u16>().ok())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn print_listening(log: &log::Logger, tunnel_name: &str) {
|
||||
debug!(
|
||||
log,
|
||||
"{} is listening for incoming connections", QUALITYLESS_SERVER_NAME
|
||||
);
|
||||
|
||||
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(""));
|
||||
let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(""));
|
||||
|
||||
let dir = if home_dir == current_dir {
|
||||
PathBuf::from("")
|
||||
} else {
|
||||
current_dir
|
||||
};
|
||||
|
||||
let base_web_url = match EDITOR_WEB_URL {
|
||||
Some(u) => u,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let mut addr = url::Url::parse(base_web_url).unwrap();
|
||||
{
|
||||
let mut ps = addr.path_segments_mut().unwrap();
|
||||
ps.push("tunnel");
|
||||
ps.push(tunnel_name);
|
||||
for segment in &dir {
|
||||
let as_str = segment.to_string_lossy();
|
||||
if !(as_str.len() == 1 && as_str.starts_with(std::path::MAIN_SEPARATOR)) {
|
||||
ps.push(as_str.as_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let message = &format!("\nOpen this link in your browser {}\n", addr);
|
||||
log.result(message);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
use crate::async_pipe::get_socket_rw_stream;
|
||||
use crate::constants::{CONTROL_PORT, EDITOR_WEB_URL, QUALITYLESS_SERVER_NAME};
|
||||
use crate::constants::CONTROL_PORT;
|
||||
use crate::log;
|
||||
use crate::rpc::{MaybeSync, RpcBuilder, RpcDispatcher, Serialization};
|
||||
use crate::self_update::SelfUpdate;
|
||||
@@ -25,8 +25,6 @@ use opentelemetry::trace::SpanKind;
|
||||
use opentelemetry::KeyValue;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
@@ -117,43 +115,6 @@ pub struct ServerTermination {
|
||||
pub tunnel: ActiveTunnel,
|
||||
}
|
||||
|
||||
fn print_listening(log: &log::Logger, tunnel_name: &str) {
|
||||
debug!(
|
||||
log,
|
||||
"{} is listening for incoming connections", QUALITYLESS_SERVER_NAME
|
||||
);
|
||||
|
||||
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(""));
|
||||
let current_dir = env::current_dir().unwrap_or_else(|_| PathBuf::from(""));
|
||||
|
||||
let dir = if home_dir == current_dir {
|
||||
PathBuf::from("")
|
||||
} else {
|
||||
current_dir
|
||||
};
|
||||
|
||||
let base_web_url = match EDITOR_WEB_URL {
|
||||
Some(u) => u,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let mut addr = url::Url::parse(base_web_url).unwrap();
|
||||
{
|
||||
let mut ps = addr.path_segments_mut().unwrap();
|
||||
ps.push("tunnel");
|
||||
ps.push(tunnel_name);
|
||||
for segment in &dir {
|
||||
let as_str = segment.to_string_lossy();
|
||||
if !(as_str.len() == 1 && as_str.starts_with(std::path::MAIN_SEPARATOR)) {
|
||||
ps.push(as_str.as_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let message = &format!("\nOpen this link in your browser {}\n", addr);
|
||||
log.result(message);
|
||||
}
|
||||
|
||||
// Runs the launcher server. Exits on a ctrl+c or when requested by a user.
|
||||
// Note that client connections may not be closed when this returns; use
|
||||
// `close_all_clients()` on the ServerTermination to make this happen.
|
||||
@@ -166,8 +127,6 @@ pub async fn serve(
|
||||
mut shutdown_rx: Barrier<ShutdownSignal>,
|
||||
) -> Result<ServerTermination, AnyError> {
|
||||
let mut port = tunnel.add_port_direct(CONTROL_PORT).await?;
|
||||
print_listening(log, &tunnel.name);
|
||||
|
||||
let mut forwarding = PortForwardingProcessor::new();
|
||||
let (tx, mut rx) = mpsc::channel::<ServerSignal>(4);
|
||||
let (exit_barrier, signal_exit) = new_barrier();
|
||||
|
||||
@@ -184,6 +184,15 @@ pub mod singleton {
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Status {
|
||||
pub ok: bool,
|
||||
pub tunnel: TunnelState,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct LogReplayFinished {}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub enum TunnelState {
|
||||
Disconnected,
|
||||
Connected { name: String },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,10 @@ use tokio::sync::mpsc;
|
||||
use crate::{
|
||||
async_pipe::{socket_stream_split, AsyncPipe},
|
||||
constants::IS_INTERACTIVE_CLI,
|
||||
json_rpc::{new_json_rpc, start_json_rpc},
|
||||
json_rpc::{new_json_rpc, start_json_rpc, JsonRpcSerializer},
|
||||
log,
|
||||
tunnels::protocol::EmptyObject,
|
||||
rpc::RpcCaller,
|
||||
tunnels::{code_server::print_listening, protocol::EmptyObject},
|
||||
util::sync::Barrier,
|
||||
};
|
||||
|
||||
@@ -34,6 +35,7 @@ pub struct SingletonClientArgs {
|
||||
struct SingletonServerContext {
|
||||
log: log::Logger,
|
||||
exit_entirely: Arc<AtomicBool>,
|
||||
caller: RpcCaller<JsonRpcSerializer>,
|
||||
}
|
||||
|
||||
const CONTROL_INSTRUCTIONS_COMMON: &str =
|
||||
@@ -61,7 +63,7 @@ pub async fn start_singleton_client(args: SingletonClientArgs) -> bool {
|
||||
"An existing tunnel is running on this machine, connecting to it..."
|
||||
);
|
||||
|
||||
let stdin_handle = rpc.get_caller(msg_tx);
|
||||
let stdin_handle = rpc.get_caller(msg_tx.clone());
|
||||
thread::spawn(move || {
|
||||
let mut input = String::new();
|
||||
loop {
|
||||
@@ -85,9 +87,11 @@ pub async fn start_singleton_client(args: SingletonClientArgs) -> bool {
|
||||
}
|
||||
});
|
||||
|
||||
let caller = rpc.get_caller(msg_tx);
|
||||
let mut rpc = rpc.methods(SingletonServerContext {
|
||||
log: args.log.clone(),
|
||||
exit_entirely: exit_entirely.clone(),
|
||||
caller,
|
||||
});
|
||||
|
||||
rpc.register_sync(protocol::singleton::METHOD_SHUTDOWN, |_: EmptyObject, c| {
|
||||
@@ -95,15 +99,29 @@ pub async fn start_singleton_client(args: SingletonClientArgs) -> bool {
|
||||
Ok(())
|
||||
});
|
||||
|
||||
rpc.register_sync(
|
||||
rpc.register_async(
|
||||
protocol::singleton::METHOD_LOG_REPLY_DONE,
|
||||
|_: EmptyObject, c| {
|
||||
|_: EmptyObject, c| async move {
|
||||
c.log.result(if *IS_INTERACTIVE_CLI {
|
||||
CONTROL_INSTRUCTIONS_INTERACTIVE
|
||||
} else {
|
||||
CONTROL_INSTRUCTIONS_COMMON
|
||||
});
|
||||
|
||||
let res = c.caller.call::<_, _, protocol::singleton::Status>(
|
||||
protocol::singleton::METHOD_STATUS,
|
||||
protocol::EmptyObject {},
|
||||
);
|
||||
|
||||
// we want to ensure the "listening" string always gets printed for
|
||||
// consumers (i.e. VS Code). Ask for it. If the tunnel is not currently
|
||||
// connected though, it will be soon, and that'll be in the log replays.
|
||||
if let Ok(Ok(s)) = res.await {
|
||||
if let protocol::singleton::TunnelState::Connected { name } = s.tunnel {
|
||||
print_listening(&c.log, &name);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
|
||||
@@ -22,6 +22,7 @@ use crate::{
|
||||
rpc::{RpcCaller, RpcDispatcher},
|
||||
singleton::SingletonServer,
|
||||
state::LauncherPaths,
|
||||
tunnels::code_server::print_listening,
|
||||
update_service::Platform,
|
||||
util::{
|
||||
errors::{AnyError, CodeError},
|
||||
@@ -52,11 +53,13 @@ struct SingletonServerContext {
|
||||
log: log::Logger,
|
||||
shutdown_tx: broadcast::Sender<ShutdownSignal>,
|
||||
broadcast_tx: broadcast::Sender<Vec<u8>>,
|
||||
current_name: Arc<Mutex<Option<String>>>,
|
||||
}
|
||||
|
||||
pub struct RpcServer {
|
||||
fut: JoinHandle<Result<(), CodeError>>,
|
||||
shutdown_broadcast: broadcast::Sender<ShutdownSignal>,
|
||||
current_name: Arc<Mutex<Option<String>>>,
|
||||
}
|
||||
|
||||
pub fn make_singleton_server(
|
||||
@@ -68,10 +71,12 @@ pub fn make_singleton_server(
|
||||
let (shutdown_broadcast, _) = broadcast::channel(4);
|
||||
let rpc = new_json_rpc();
|
||||
|
||||
let current_name = Arc::new(Mutex::new(None));
|
||||
let mut rpc = rpc.methods(SingletonServerContext {
|
||||
log: log.clone(),
|
||||
shutdown_tx: shutdown_broadcast.clone(),
|
||||
broadcast_tx: log_broadcast.get_brocaster(),
|
||||
current_name: current_name.clone(),
|
||||
});
|
||||
|
||||
rpc.register_sync(
|
||||
@@ -85,8 +90,13 @@ pub fn make_singleton_server(
|
||||
|
||||
rpc.register_sync(
|
||||
protocol::singleton::METHOD_STATUS,
|
||||
|_: protocol::EmptyObject, _| {
|
||||
Ok(protocol::singleton::Status { ok: true }) // mostly placeholder
|
||||
|_: protocol::EmptyObject, c| {
|
||||
Ok(protocol::singleton::Status {
|
||||
tunnel: match c.current_name.lock().unwrap().clone() {
|
||||
Some(name) => protocol::singleton::TunnelState::Connected { name },
|
||||
None => protocol::singleton::TunnelState::Disconnected,
|
||||
},
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
@@ -114,6 +124,7 @@ pub fn make_singleton_server(
|
||||
});
|
||||
RpcServer {
|
||||
shutdown_broadcast,
|
||||
current_name,
|
||||
fut,
|
||||
}
|
||||
}
|
||||
@@ -126,6 +137,12 @@ pub async fn start_singleton_server<'a>(
|
||||
ShutdownRequest::Derived(Box::new(args.shutdown.clone())),
|
||||
]);
|
||||
|
||||
{
|
||||
print_listening(&args.log, &args.tunnel.name);
|
||||
let mut name = args.server.current_name.lock().unwrap();
|
||||
*name = Some(args.tunnel.name.clone())
|
||||
}
|
||||
|
||||
let serve_fut = super::serve(
|
||||
&args.log,
|
||||
args.tunnel,
|
||||
|
||||
Reference in New Issue
Block a user