Quellcode durchsuchen

debugger support for dx (#3814)

* add vscode debugger for apps

* todo: need to disconnect when rebuilding

* update debugger

* add uri handler to remote launch the debugger

* attach

* dont panic

* fixed it - hung processes are not errors

* wip: android debugger

* clean it up a bit

* revert ios stdout/stderr

* wire up pid from ios

* use target family instead of target_arch

* fix android

* bring back softkill

* typo
Jonathan Kelley vor 1 Monat
Ursprung
Commit
b2bd1f48d4

+ 209 - 29
packages/cli/src/build/builder.rs

@@ -1,10 +1,11 @@
 use crate::{
-    BuildArtifacts, BuildRequest, BuildStage, BuilderUpdate, Platform, ProgressRx, ProgressTx,
-    Result, StructuredOutput,
+    serve::WebServer, BuildArtifacts, BuildRequest, BuildStage, BuilderUpdate, Platform,
+    ProgressRx, ProgressTx, Result, StructuredOutput,
 };
 use anyhow::Context;
 use dioxus_cli_opt::process_file_to;
 use futures_util::{future::OptionFuture, pin_mut, FutureExt};
+use itertools::Itertools;
 use std::{
     env,
     time::{Duration, Instant, SystemTime},
@@ -91,6 +92,9 @@ pub(crate) struct AppBuilder {
     pub compile_end: Option<Instant>,
     pub bundle_start: Option<Instant>,
     pub bundle_end: Option<Instant>,
+
+    /// The debugger for the app - must be enabled with the `d` key
+    pub(crate) pid: Option<u32>,
 }
 
 impl AppBuilder {
@@ -156,6 +160,7 @@ impl AppBuilder {
             entropy_app_exe: None,
             artifacts: None,
             patch_cache: None,
+            pid: None,
         })
     }
 
@@ -183,12 +188,16 @@ impl AppBuilder {
                 StderrReceived {  msg }
             },
             Some(status) = OptionFuture::from(self.child.as_mut().map(|f| f.wait())) => {
-                // Panicking here is on purpose. If the task crashes due to a JoinError (a panic),
-                // we want to propagate that panic up to the serve controller.
-                let status = status.unwrap();
-                self.child = None;
-
-                ProcessExited { status }
+                match status {
+                    Ok(status) => {
+                        self.child = None;
+                        ProcessExited { status }
+                    },
+                    Err(err) => {
+                        let () = futures_util::future::pending().await;
+                        ProcessWaitFailed { err }
+                    }
+                }
             }
         };
 
@@ -263,6 +272,7 @@ impl AppBuilder {
             StdoutReceived { .. } => {}
             StderrReceived { .. } => {}
             ProcessExited { .. } => {}
+            ProcessWaitFailed { .. } => {}
         }
 
         update
@@ -402,6 +412,7 @@ impl AppBuilder {
                 BuilderUpdate::StdoutReceived { .. } => {}
                 BuilderUpdate::StderrReceived { .. } => {}
                 BuilderUpdate::ProcessExited { .. } => {}
+                BuilderUpdate::ProcessWaitFailed { .. } => {}
             }
         }
     }
@@ -464,7 +475,7 @@ impl AppBuilder {
         }
 
         // We try to use stdin/stdout to communicate with the app
-        let running_process = match self.build.platform {
+        match self.build.platform {
             // Unfortunately web won't let us get a proc handle to it (to read its stdout/stderr) so instead
             // use use the websocket to communicate with it. I wish we could merge the concepts here,
             // like say, opening the socket as a subprocess, but alas, it's simpler to do that somewhere else.
@@ -473,15 +484,12 @@ impl AppBuilder {
                 if open_browser {
                     self.open_web(open_address.unwrap_or(devserver_ip));
                 }
-
-                None
             }
 
-            Platform::Ios => Some(self.open_ios_sim(envs).await?),
+            Platform::Ios => self.open_ios_sim(envs).await?,
 
             Platform::Android => {
                 self.open_android_sim(false, devserver_ip, envs).await?;
-                None
             }
 
             // These are all just basically running the main exe, but with slightly different resource dir paths
@@ -489,18 +497,9 @@ impl AppBuilder {
             | Platform::MacOS
             | Platform::Windows
             | Platform::Linux
-            | Platform::Liveview => Some(self.open_with_main_exe(envs)?),
+            | Platform::Liveview => self.open_with_main_exe(envs)?,
         };
 
-        // If we have a running process, we need to attach to it and wait for its outputs
-        if let Some(mut child) = running_process {
-            let stdout = BufReader::new(child.stdout.take().unwrap());
-            let stderr = BufReader::new(child.stderr.take().unwrap());
-            self.stdout = Some(stdout.lines());
-            self.stderr = Some(stderr.lines());
-            self.child = Some(child);
-        }
-
         self.builds_opened += 1;
 
         Ok(())
@@ -728,19 +727,25 @@ impl AppBuilder {
     /// paths right now, but they will when we start to enable things like swift integration.
     ///
     /// Server/liveview/desktop are all basically the same, though
-    fn open_with_main_exe(&mut self, envs: Vec<(&str, String)>) -> Result<Child> {
+    fn open_with_main_exe(&mut self, envs: Vec<(&str, String)>) -> Result<()> {
         let main_exe = self.app_exe();
 
         tracing::debug!("Opening app with main exe: {main_exe:?}");
 
-        let child = Command::new(main_exe)
+        let mut child = Command::new(main_exe)
             .envs(envs)
             .stderr(Stdio::piped())
             .stdout(Stdio::piped())
             .kill_on_drop(true)
             .spawn()?;
 
-        Ok(child)
+        let stdout = BufReader::new(child.stdout.take().unwrap());
+        let stderr = BufReader::new(child.stderr.take().unwrap());
+        self.stdout = Some(stdout.lines());
+        self.stderr = Some(stderr.lines());
+        self.child = Some(child);
+
+        Ok(())
     }
 
     /// Open the web app by opening the browser to the given address.
@@ -765,7 +770,7 @@ impl AppBuilder {
     ///
     /// TODO(jon): we should probably check if there's a simulator running before trying to install,
     /// and open the simulator if we have to.
-    async fn open_ios_sim(&mut self, envs: Vec<(&str, String)>) -> Result<Child> {
+    async fn open_ios_sim(&mut self, envs: Vec<(&str, String)>) -> Result<()> {
         tracing::debug!("Installing app to simulator {:?}", self.build.root_dir());
 
         let res = Command::new("xcrun")
@@ -784,7 +789,7 @@ impl AppBuilder {
             .iter()
             .map(|(k, v)| (format!("SIMCTL_CHILD_{k}"), v.clone()));
 
-        let child = Command::new("xcrun")
+        let mut child = Command::new("xcrun")
             .arg("simctl")
             .arg("launch")
             .arg("--console")
@@ -796,7 +801,13 @@ impl AppBuilder {
             .kill_on_drop(true)
             .spawn()?;
 
-        Ok(child)
+        let stdout = BufReader::new(child.stdout.take().unwrap());
+        let stderr = BufReader::new(child.stderr.take().unwrap());
+        self.stdout = Some(stdout.lines());
+        self.stderr = Some(stderr.lines());
+        self.child = Some(child);
+
+        Ok(())
     }
 
     /// We have this whole thing figured out, but we don't actually use it yet.
@@ -1339,4 +1350,173 @@ We checked the folder: {}
     pub(crate) fn can_receive_hotreloads(&self) -> bool {
         matches!(&self.stage, BuildStage::Success | BuildStage::Failed)
     }
+
+    pub(crate) async fn open_debugger(&mut self, server: &WebServer) -> Result<()> {
+        let url = match self.build.platform {
+            Platform::MacOS
+            | Platform::Windows
+            | Platform::Linux
+            | Platform::Server
+            | Platform::Liveview => {
+                let Some(Some(pid)) = self.child.as_mut().map(|f| f.id()) else {
+                    tracing::warn!("No process to attach debugger to");
+                    return Ok(());
+                };
+
+                format!(
+                    "vscode://vadimcn.vscode-lldb/launch/config?{{'request':'attach','pid':{}}}",
+                    pid
+                )
+            }
+
+            Platform::Web => {
+                // code --open-url "vscode://DioxusLabs.dioxus/debugger?uri=http://127.0.0.1:8080"
+                // todo - debugger could open to the *current* page afaik we don't have a way to have that info
+                let address = server.devserver_address();
+                let base_path = self.build.config.web.app.base_path.clone();
+                let https = self.build.config.web.https.enabled.unwrap_or_default();
+                let protocol = if https { "https" } else { "http" };
+                let base_path = match base_path.as_deref() {
+                    Some(base_path) => format!("/{}", base_path.trim_matches('/')),
+                    None => "".to_owned(),
+                };
+                format!("vscode://DioxusLabs.dioxus/debugger?uri={protocol}://{address}{base_path}")
+            }
+
+            Platform::Ios => {
+                let Some(pid) = self.pid else {
+                    tracing::warn!("No process to attach debugger to");
+                    return Ok(());
+                };
+
+                format!(
+                    "vscode://vadimcn.vscode-lldb/launch/config?{{'request':'attach','pid':{pid}}}"
+                )
+            }
+
+            // https://stackoverflow.com/questions/53733781/how-do-i-use-lldb-to-debug-c-code-on-android-on-command-line/64997332#64997332
+            // https://android.googlesource.com/platform/development/+/refs/heads/main/scripts/gdbclient.py
+            // run lldbserver on the device and then connect
+            //
+            // # TODO: https://code.visualstudio.com/api/references/vscode-api#debug and
+            // #       https://code.visualstudio.com/api/extension-guides/debugger-extension and
+            // #       https://github.com/vadimcn/vscode-lldb/blob/6b775c439992b6615e92f4938ee4e211f1b060cf/extension/pickProcess.ts#L6
+            //
+            // res = {
+            //     "name": "(lldbclient.py) Attach {} (port: {})".format(binary_name.split("/")[-1], port),
+            //     "type": "lldb",
+            //     "request": "custom",
+            //     "relativePathBase": root,
+            //     "sourceMap": { "/b/f/w" : root, '': root, '.': root },
+            //     "initCommands": ['settings append target.exec-search-paths {}'.format(' '.join(solib_search_path))],
+            //     "targetCreateCommands": ["target create {}".format(binary_name),
+            //                              "target modules search-paths add / {}/".format(sysroot)],
+            //     "processCreateCommands": ["gdb-remote {}".format(str(port))]
+            // }
+            //
+            // https://github.com/vadimcn/codelldb/issues/213
+            //
+            // lots of pain to figure this out:
+            //
+            // (lldb) image add target/dx/tw6/debug/android/app/app/src/main/jniLibs/arm64-v8a/libdioxusmain.so
+            // (lldb) settings append target.exec-search-paths target/dx/tw6/debug/android/app/app/src/main/jniLibs/arm64-v8a/libdioxusmain.so
+            // (lldb) process handle SIGSEGV --pass true --stop false --notify true (otherwise the java threads cause crash)
+            //
+            Platform::Android => {
+                // adb push ./sdk/ndk/29.0.13113456/toolchains/llvm/prebuilt/darwin-x86_64/lib/clang/20/lib/linux/aarch64/lldb-server /tmp
+                // adb shell "/tmp/lldb-server --server --listen ..."
+                // "vscode://vadimcn.vscode-lldb/launch/config?{{'request':'connect','port': {}}}",
+                // format!(
+                //     "vscode://vadimcn.vscode-lldb/launch/config?{{'request':'attach','pid':{pid}}}"
+                // )
+                let tools = &self.build.workspace.android_tools()?;
+
+                // get the pid of the app
+                let pid = Command::new(&tools.adb)
+                    .arg("shell")
+                    .arg("pidof")
+                    .arg(self.build.bundle_identifier())
+                    .output()
+                    .await
+                    .ok()
+                    .and_then(|output| String::from_utf8(output.stdout).ok())
+                    .and_then(|s| s.trim().parse::<u32>().ok())
+                    .unwrap();
+
+                // copy the lldb-server to the device
+                let lldb_server = tools
+                    .android_tools_dir()
+                    .parent()
+                    .unwrap()
+                    .join("lib")
+                    .join("clang")
+                    .join("20")
+                    .join("lib")
+                    .join("linux")
+                    .join("aarch64")
+                    .join("lldb-server");
+
+                tracing::info!("Copying lldb-server to device: {lldb_server:?}");
+
+                _ = Command::new(&tools.adb)
+                    .arg("push")
+                    .arg(lldb_server)
+                    .arg("/tmp/lldb-server")
+                    .output()
+                    .await;
+
+                // Forward requests on 10086 to the device
+                _ = Command::new(&tools.adb)
+                    .arg("forward")
+                    .arg("tcp:10086")
+                    .arg("tcp:10086")
+                    .output()
+                    .await;
+
+                // start the server - running it multiple times will make the subsequent ones fail (which is fine)
+                _ = Command::new(&tools.adb)
+                    .arg("shell")
+                    .arg(r#"cd /tmp && ./lldb-server platform --server --listen '*:10086'"#)
+                    .kill_on_drop(false)
+                    .stdin(Stdio::null())
+                    .stdout(Stdio::piped())
+                    .stderr(Stdio::piped())
+                    .spawn();
+
+                let program_path = self.build.main_exe();
+                format!(
+                    r#"vscode://vadimcn.vscode-lldb/launch/config?{{
+                        'name':'Attach to Android',
+                        'type':'lldb',
+                        'request':'attach',
+                        'pid': '{pid}',
+                        'processCreateCommands': [
+                            'platform select remote-android',
+                            'platform connect connect://localhost:10086',
+                            'settings set target.inherit-env false',
+                            'settings set target.inline-breakpoint-strategy always',
+                            'settings set target.process.thread.step-avoid-regexp \"JavaBridge|JDWP|Binder|ReferenceQueueDaemon\"',
+                            'process handle SIGSEGV --pass true --stop false --notify true"',
+                            'settings append target.exec-search-paths {program_path}',
+                            'attach --pid {pid}',
+                            'continue'
+                        ]
+                    }}"#,
+                    program_path = program_path.display(),
+                )
+                .lines()
+                .map(|line| line.trim())
+                .join("")
+            }
+        };
+
+        tracing::info!("Opening debugger for [{}]: {url}", self.build.platform);
+
+        _ = tokio::process::Command::new("code")
+            .arg("--open-url")
+            .arg(url)
+            .spawn();
+
+        Ok(())
+    }
 }

+ 6 - 0
packages/cli/src/build/context.rs

@@ -65,6 +65,12 @@ pub enum BuilderUpdate {
     ProcessExited {
         status: ExitStatus,
     },
+
+    /// Waiting for the process failed. This might be because it's hung or being debugged.
+    /// This is not the same as the process exiting, so it should just be logged but not treated as an error.
+    ProcessWaitFailed {
+        err: std::io::Error,
+    },
 }
 
 impl BuildContext {

+ 6 - 0
packages/cli/src/build/request.rs

@@ -2128,6 +2128,12 @@ impl BuildRequest {
             ));
         }
 
+        // for debuggability, we need to make sure android studio can properly understand our build
+        // https://stackoverflow.com/questions/68481401/debugging-a-prebuilt-shared-library-in-android-studio
+        if self.platform == Platform::Android {
+            cargo_args.push("-Clink-arg=-Wl,--build-id=sha1".to_string());
+        }
+
         // Handle frameworks/dylibs by setting the rpath
         // This is dependent on the bundle structure - in this case, appimage and appbundle for mac/linux
         // todo: we need to figure out what to do for windows

+ 1 - 0
packages/cli/src/cli/run.rs

@@ -123,6 +123,7 @@ impl RunArgs {
 
                             break;
                         }
+                        BuilderUpdate::ProcessWaitFailed { .. } => {}
                     }
                 }
                 _ => {}

+ 15 - 2
packages/cli/src/serve/mod.rs

@@ -90,7 +90,11 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
 
             // Run the server in the background
             // Waiting for updates here lets us tap into when clients are added/removed
-            ServeUpdate::NewConnection { id, aslr_reference } => {
+            ServeUpdate::NewConnection {
+                id,
+                aslr_reference,
+                pid,
+            } => {
                 devserver
                     .send_hotreload(builder.applied_hot_reload_changes(BuildId::CLIENT))
                     .await;
@@ -101,7 +105,7 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
                         .await;
                 }
 
-                builder.client_connected(id, aslr_reference).await;
+                builder.client_connected(id, aslr_reference, pid).await;
             }
 
             // Received a message from the devtools server - currently we only use this for
@@ -173,6 +177,11 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
                             tracing::error!("Application [{platform}] exited with error: {status}");
                         }
                     }
+                    BuilderUpdate::ProcessWaitFailed { err } => {
+                        tracing::warn!(
+                            "Failed to wait for process - maybe it's hung or being debugged?: {err}"
+                        );
+                    }
                 }
             }
 
@@ -202,6 +211,10 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
                 )
             }
 
+            ServeUpdate::OpenDebugger { id } => {
+                builder.open_debugger(&devserver, id).await;
+            }
+
             ServeUpdate::Exit { error } => {
                 _ = builder.shutdown().await;
                 _ = devserver.shutdown().await;

+ 11 - 1
packages/cli/src/serve/output.rs

@@ -1,6 +1,6 @@
 use crate::{
     serve::{ansi_buffer::AnsiStringLine, ServeUpdate, WebServer},
-    BuildStage, BuilderUpdate, Platform, TraceContent, TraceMsg, TraceSrc,
+    BuildId, BuildStage, BuilderUpdate, Platform, TraceContent, TraceMsg, TraceSrc,
 };
 use cargo_metadata::diagnostic::Diagnostic;
 use crossterm::{
@@ -245,6 +245,16 @@ impl Output {
                 self.trace = !self.trace;
                 tracing::info!("Tracing is now {}", if self.trace { "on" } else { "off" });
             }
+            KeyCode::Char('D') => {
+                return Ok(Some(ServeUpdate::OpenDebugger {
+                    id: BuildId::SERVER,
+                }));
+            }
+            KeyCode::Char('d') => {
+                return Ok(Some(ServeUpdate::OpenDebugger {
+                    id: BuildId::CLIENT,
+                }));
+            }
 
             KeyCode::Char('c') => {
                 stdout()

+ 22 - 0
packages/cli/src/serve/runner.rs

@@ -692,6 +692,7 @@ impl AppServer {
         &mut self,
         build_id: BuildId,
         aslr_reference: Option<u64>,
+        pid: Option<u32>,
     ) {
         match build_id {
             BuildId::CLIENT => {
@@ -701,6 +702,9 @@ impl AppServer {
                     if let Some(aslr_reference) = aslr_reference {
                         self.client.aslr_reference = Some(aslr_reference);
                     }
+                    if let Some(pid) = pid {
+                        self.client.pid = Some(pid);
+                    }
                 }
             }
             BuildId::SERVER => {
@@ -970,6 +974,24 @@ impl AppServer {
 
         server.compiled_crates as f64 / server.expected_crates as f64
     }
+
+    pub(crate) async fn open_debugger(&mut self, dev: &WebServer, build: BuildId) {
+        if self.use_hotpatch_engine {
+            tracing::warn!("Debugging symbols might not work properly with hotpatching enabled. Consider disabling hotpatching for debugging.");
+        }
+
+        match build {
+            BuildId::CLIENT => {
+                _ = self.client.open_debugger(dev).await;
+            }
+            BuildId::SERVER => {
+                if let Some(server) = self.server.as_mut() {
+                    _ = server.open_debugger(dev).await;
+                }
+            }
+            _ => {}
+        }
+    }
 }
 
 /// Bind a listener to any point and return it

+ 7 - 3
packages/cli/src/serve/server.rs

@@ -69,6 +69,7 @@ pub(crate) struct ConnectedWsClient {
     socket: WebSocket,
     build_id: Option<BuildId>,
     aslr_reference: Option<u64>,
+    pid: Option<u32>,
 }
 
 impl WebServer {
@@ -146,12 +147,13 @@ impl WebServer {
             new_hot_reload_socket = &mut new_hot_reload_socket => {
                 if let Some(new_socket) = new_hot_reload_socket {
                     let aslr_reference = new_socket.aslr_reference;
+                    let pid = new_socket.pid;
                     let id = new_socket.build_id.unwrap_or(BuildId::CLIENT);
 
                     drop(new_message);
                     self.hot_reload_sockets.push(new_socket);
 
-                    return ServeUpdate::NewConnection { aslr_reference, id };
+                    return ServeUpdate::NewConnection { aslr_reference, id, pid };
                 } else {
                     panic!("Could not receive a socket - the devtools could not boot - the port is likely already in use");
                 }
@@ -261,6 +263,7 @@ impl WebServer {
             BuilderUpdate::StdoutReceived { .. } => {}
             BuilderUpdate::StderrReceived { .. } => {}
             BuilderUpdate::ProcessExited { .. } => {}
+            BuilderUpdate::ProcessWaitFailed { .. } => {}
         }
     }
 
@@ -495,6 +498,7 @@ fn build_devserver_router(
     struct ConnectionQuery {
         aslr_reference: Option<u64>,
         build_id: Option<BuildId>,
+        pid: Option<u32>,
     }
 
     // Setup websocket endpoint - and pass in the extension layer immediately after
@@ -506,7 +510,7 @@ fn build_devserver_router(
                 get(
                     |ws: WebSocketUpgrade, ext: Extension<UnboundedSender<ConnectedWsClient>>, query: Query<ConnectionQuery>| async move {
                         tracing::debug!("New devtool websocket connection: {:?}", query);
-                        ws.on_upgrade(move |socket| async move { _ = ext.0.unbounded_send(ConnectedWsClient { socket, aslr_reference: query.aslr_reference, build_id: query.build_id }) })
+                        ws.on_upgrade(move |socket| async move { _ = ext.0.unbounded_send(ConnectedWsClient { socket, aslr_reference: query.aslr_reference, build_id: query.build_id, pid: query.pid }) })
                     },
                 ),
             )
@@ -515,7 +519,7 @@ fn build_devserver_router(
                 "/build_status",
                 get(
                     |ws: WebSocketUpgrade, ext: Extension<UnboundedSender<ConnectedWsClient>>| async move {
-                        ws.on_upgrade(move |socket| async move { _ = ext.0.unbounded_send(ConnectedWsClient { socket, aslr_reference: None, build_id: None }) })
+                        ws.on_upgrade(move |socket| async move { _ = ext.0.unbounded_send(ConnectedWsClient { socket, aslr_reference: None, build_id: None, pid: None }) })
                     },
                 ),
             )

+ 5 - 0
packages/cli/src/serve/update.rs

@@ -10,6 +10,7 @@ pub(crate) enum ServeUpdate {
     NewConnection {
         id: BuildId,
         aslr_reference: Option<u64>,
+        pid: Option<u32>,
     },
     WsMessage {
         platform: Platform,
@@ -32,6 +33,10 @@ pub(crate) enum ServeUpdate {
 
     ToggleShouldRebuild,
 
+    OpenDebugger {
+        id: BuildId,
+    },
+
     Redraw,
 
     TracingLog {

+ 6 - 5
packages/devtools/src/lib.rs

@@ -49,7 +49,7 @@ pub fn try_apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) -> Result<(), Pat
 /// Connect to the devserver and handle its messages with a callback.
 ///
 /// This doesn't use any form of security or protocol, so it's not safe to expose to the internet.
-#[cfg(not(target_arch = "wasm32"))]
+#[cfg(not(target_family = "wasm"))]
 pub fn connect(callback: impl FnMut(DevserverMsg) + Send + 'static) {
     let Some(endpoint) = dioxus_cli_config::devserver_ws_endpoint() else {
         return;
@@ -64,7 +64,7 @@ pub fn connect(callback: impl FnMut(DevserverMsg) + Send + 'static) {
 /// This is intended to be used by non-dioxus projects that want to use hotpatching.
 ///
 /// To handle the full devserver protocol, use `connect` instead.
-#[cfg(not(target_arch = "wasm32"))]
+#[cfg(not(target_family = "wasm"))]
 pub fn connect_subsecond() {
     connect(|msg| {
         if let DevserverMsg::HotReload(hot_reload_msg) = msg {
@@ -75,13 +75,14 @@ pub fn connect_subsecond() {
     });
 }
 
-#[cfg(not(target_arch = "wasm32"))]
+#[cfg(not(target_family = "wasm"))]
 pub fn connect_at(endpoint: String, mut callback: impl FnMut(DevserverMsg) + Send + 'static) {
     std::thread::spawn(move || {
         let uri = format!(
-            "{endpoint}?aslr_reference={}&build_id={}",
+            "{endpoint}?aslr_reference={}&build_id={}&pid={}",
             subsecond::aslr_reference(),
-            dioxus_cli_config::build_id()
+            dioxus_cli_config::build_id(),
+            std::process::id()
         );
 
         let (mut websocket, _req) = match tungstenite::connect(uri) {

+ 1 - 0
packages/extension/.gitignore

@@ -11,3 +11,4 @@ tsconfig.lsif.json
 *.lsif
 *.db
 *.vsix
+pkg/

+ 1 - 1
packages/extension/package.json

@@ -2,7 +2,7 @@
     "name": "dioxus",
     "displayName": "Dioxus",
     "description": "Useful tools for working with Dioxus",
-    "version": "0.6.0",
+    "version": "0.7.0",
     "publisher": "DioxusLabs",
     "private": true,
     "license": "MIT",

+ 15 - 0
packages/extension/src/main.ts

@@ -22,6 +22,9 @@ export async function activate(context: vscode.ExtensionContext) {
 		vscode.commands.registerCommand('extension.formatRsxDocument', formatRsxDocument),
 		vscode.workspace.onWillSaveTextDocument(fmtDocumentOnSave)
 	);
+
+	context.subscriptions.push(vscode.window.registerUriHandler(new UriLaunchServer()));
+
 }
 
 function translate(component: boolean) {
@@ -171,3 +174,15 @@ function fmtDocument(document: vscode.TextDocument) {
 		vscode.window.showWarningMessage(`Errors occurred while formatting. Make sure you have the most recent Dioxus-CLI installed! \n${error}`);
 	}
 }
+
+class UriLaunchServer implements vscode.UriHandler {
+	handleUri(uri: vscode.Uri): vscode.ProviderResult<void> {
+		if (uri.path === '/debugger') {
+			let query = decodeURIComponent(uri.query);
+			let params = new URLSearchParams(query);
+			let route = params.get('uri');
+			vscode.window.showInformationMessage(`Opening Chrome debugger: ${route}`);
+			vscode.commands.executeCommand('extension.js-debug.debugLink', route);
+		}
+	}
+}