diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProcessConsole.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProcessConsole.java index de066bcaa..6b384c628 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProcessConsole.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ProcessConsole.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2017 Microsoft Corporation and others. +* Copyright (c) 2017-2019 Microsoft Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -17,18 +17,18 @@ import java.io.InputStreamReader; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.stream.Stream; -import io.reactivex.functions.Consumer; +import com.microsoft.java.debug.core.protocol.Events.OutputEvent.Category; + +import io.reactivex.Observable; +import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.PublishSubject; public class ProcessConsole { - private Process process; - private String name; - private Charset encoding; - private PublishSubject stdoutSubject = PublishSubject.create(); - private PublishSubject stderrSubject = PublishSubject.create(); - private Thread stdoutThread = null; - private Thread stderrThread = null; + private InputStreamObservable stdoutStream; + private InputStreamObservable stderrStream; + private Observable observable = null; public ProcessConsole(Process process) { this(process, "Process", StandardCharsets.UTF_8); @@ -44,76 +44,126 @@ public ProcessConsole(Process process) { * the process encoding format */ public ProcessConsole(Process process, String name, Charset encoding) { - this.process = process; - this.name = name; - this.encoding = encoding; + this.stdoutStream = new InputStreamObservable(name + " Stdout Handler", process.getInputStream(), encoding); + this.stderrStream = new InputStreamObservable(name + " Stderr Handler", process.getErrorStream(), encoding); + Observable stdout = this.stdoutStream.messages().map((message) -> new ConsoleMessage(message, Category.stdout)); + Observable stderr = this.stderrStream.messages().map((message) -> new ConsoleMessage(message, Category.stderr)); + this.observable = Observable.mergeArrayDelayError(stdout, stderr).observeOn(Schedulers.newThread()); } /** - * Start two separate threads to monitor the messages from stdout and stderr streams of the target process. + * Start monitoring the stdout/stderr streams of the target process. */ public void start() { - this.stdoutThread = new Thread(this.name + " Stdout Handler") { - public void run() { - monitor(process.getInputStream(), stdoutSubject); - } - }; - stdoutThread.setDaemon(true); - stdoutThread.start(); - - this.stderrThread = new Thread(this.name + " Stderr Handler") { - public void run() { - monitor(process.getErrorStream(), stderrSubject); - } - }; - stderrThread.setDaemon(true); - stderrThread.start(); + stdoutStream.start(); + stderrStream.start(); } /** - * Stop the process console handlers. + * Stop monitoring the process console. */ public void stop() { - if (this.stdoutThread != null) { - this.stdoutThread.interrupt(); - this.stdoutThread = null; - } + stdoutStream.stop(); + stderrStream.stop(); + } - if (this.stderrThread != null) { - this.stderrThread.interrupt(); - this.stderrThread = null; - } + public Observable messages() { + return observable; } - public void onStdout(Consumer callback) { - stdoutSubject.subscribe(callback); + public Observable stdoutMessages() { + return this.messages().filter((message) -> message.category == Category.stdout); } - public void onStderr(Consumer callback) { - stderrSubject.subscribe(callback); + public Observable stderrMessages() { + return this.messages().filter((message) -> message.category == Category.stderr); } - private void monitor(InputStream input, PublishSubject subject) { - BufferedReader reader = new BufferedReader(new InputStreamReader(input, encoding)); - final int BUFFERSIZE = 4096; - char[] buffer = new char[BUFFERSIZE]; - while (true) { - try { - if (Thread.interrupted()) { - subject.onComplete(); - return; + /** + * Split the stdio message to lines, and return them as a new Observable. + */ + public Observable lineMessages() { + return this.messages().map((message) -> { + String[] lines = message.output.split("(?<=\n)"); + return Stream.of(lines).map((line) -> new ConsoleMessage(line, message.category)).toArray(ConsoleMessage[]::new); + }).concatMap((lines) -> Observable.fromArray(lines)); + } + + public static class InputStreamObservable { + private PublishSubject rxSubject = PublishSubject.create(); + private String name; + private InputStream inputStream; + private Charset encoding; + private Thread loopingThread; + + /** + * Constructor. + */ + public InputStreamObservable(String name, InputStream inputStream, Charset encoding) { + this.name = name; + this.inputStream = inputStream; + this.encoding = encoding; + } + + /** + * Starts the stream. + */ + public void start() { + loopingThread = new Thread(name) { + public void run() { + monitor(inputStream, rxSubject); } - int read = reader.read(buffer, 0, BUFFERSIZE); - if (read == -1) { - subject.onComplete(); + }; + loopingThread.setDaemon(true); + loopingThread.start(); + } + + /** + * Stops the stream. + */ + public void stop() { + if (loopingThread != null) { + loopingThread.interrupt(); + loopingThread = null; + } + } + + private void monitor(InputStream input, PublishSubject subject) { + BufferedReader reader = new BufferedReader(new InputStreamReader(input, encoding)); + final int BUFFERSIZE = 4096; + char[] buffer = new char[BUFFERSIZE]; + while (true) { + try { + if (Thread.interrupted()) { + subject.onComplete(); + return; + } + int read = reader.read(buffer, 0, BUFFERSIZE); + if (read == -1) { + subject.onComplete(); + return; + } + + subject.onNext(new String(buffer, 0, read)); + } catch (IOException e) { + subject.onError(e); return; } - - subject.onNext(new String(buffer, 0, read)); - } catch (IOException e) { - subject.onError(e); - return; } } + + public Observable messages() { + return rxSubject; + } + } + + public static class ConsoleMessage { + public String output; + public Category category; + + public ConsoleMessage(String message, Category category) { + this.output = message; + this.category = category; + } } } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ConfigurationDoneRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ConfigurationDoneRequestHandler.java index 977690d28..cb460ea7f 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ConfigurationDoneRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ConfigurationDoneRequestHandler.java @@ -79,14 +79,7 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession, context.setVmTerminated(); context.getProtocolServer().sendEvent(new Events.ExitedEvent(0)); } else if (event instanceof VMDisconnectEvent) { - context.setVmTerminated(); - context.getProtocolServer().sendEvent(new Events.TerminatedEvent()); - // Terminate eventHub thread. - try { - debugSession.getEventHub().close(); - } catch (Exception e) { - // do nothing. - } + // ignore since LaunchRequestHandler has already handled. } else if (event instanceof ThreadStartEvent) { ThreadReference startThread = ((ThreadStartEvent) event).thread(); Events.ThreadEvent threadEvent = new Events.ThreadEvent("started", startThread.uniqueID()); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ILaunchDelegate.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ILaunchDelegate.java index edb865a78..ac12a09d5 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ILaunchDelegate.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/ILaunchDelegate.java @@ -11,11 +11,14 @@ package com.microsoft.java.debug.core.adapter.handler; +import java.io.IOException; import java.util.concurrent.CompletableFuture; import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; import com.microsoft.java.debug.core.protocol.Messages.Response; import com.microsoft.java.debug.core.protocol.Requests.LaunchArguments; +import com.sun.jdi.connect.IllegalConnectorArgumentsException; +import com.sun.jdi.connect.VMStartException; public interface ILaunchDelegate { void postLaunch(LaunchArguments launchArguments, IDebugAdapterContext context); @@ -24,6 +27,7 @@ public interface ILaunchDelegate { CompletableFuture launchInTerminal(LaunchArguments launchArguments, Response response, IDebugAdapterContext context); - CompletableFuture launchInternally(LaunchArguments launchArguments, Response response, IDebugAdapterContext context); + Process launch(LaunchArguments launchArguments, IDebugAdapterContext context) + throws IOException, IllegalConnectorArgumentsException, VMStartException; } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java index cdfca7fbf..5e07e21c7 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java @@ -14,6 +14,7 @@ import java.io.File; import java.io.IOException; import java.net.MalformedURLException; +import java.net.URISyntaxException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Path; @@ -24,31 +25,47 @@ import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import com.microsoft.java.debug.core.Configuration; +import com.microsoft.java.debug.core.DebugException; import com.microsoft.java.debug.core.DebugSettings; import com.microsoft.java.debug.core.DebugUtility; +import com.microsoft.java.debug.core.IDebugSession; import com.microsoft.java.debug.core.adapter.AdapterUtils; import com.microsoft.java.debug.core.adapter.ErrorCode; import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; import com.microsoft.java.debug.core.adapter.IDebugRequestHandler; import com.microsoft.java.debug.core.adapter.LaunchMode; +import com.microsoft.java.debug.core.adapter.ProcessConsole; +import com.microsoft.java.debug.core.protocol.Events; +import com.microsoft.java.debug.core.protocol.Events.OutputEvent; +import com.microsoft.java.debug.core.protocol.Events.OutputEvent.Category; import com.microsoft.java.debug.core.protocol.Messages.Response; import com.microsoft.java.debug.core.protocol.Requests.Arguments; import com.microsoft.java.debug.core.protocol.Requests.CONSOLE; import com.microsoft.java.debug.core.protocol.Requests.Command; import com.microsoft.java.debug.core.protocol.Requests.LaunchArguments; import com.microsoft.java.debug.core.protocol.Requests.ShortenApproach; +import com.microsoft.java.debug.core.protocol.Types; +import com.sun.jdi.connect.IllegalConnectorArgumentsException; +import com.sun.jdi.connect.VMStartException; +import com.sun.jdi.event.VMDisconnectEvent; public class LaunchRequestHandler implements IDebugRequestHandler { protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); protected static final long RUNINTERMINAL_TIMEOUT = 10 * 1000; protected ILaunchDelegate activeLaunchHandler; + private CompletableFuture waitForDebuggeeConsole = new CompletableFuture<>(); @Override public List getTargetCommands() { @@ -58,7 +75,8 @@ public List getTargetCommands() { @Override public CompletableFuture handle(Command command, Arguments arguments, Response response, IDebugAdapterContext context) { LaunchArguments launchArguments = (LaunchArguments) arguments; - activeLaunchHandler = launchArguments.noDebug ? new LaunchWithoutDebuggingDelegate() : new LaunchWithDebuggingDelegate(); + activeLaunchHandler = launchArguments.noDebug ? new LaunchWithoutDebuggingDelegate((daContext) -> handleTerminatedEvent(daContext)) + : new LaunchWithDebuggingDelegate(); return handleLaunchCommand(arguments, response, context); } @@ -124,10 +142,39 @@ protected CompletableFuture handleLaunchCommand(Arguments arguments, R if (res.success) { activeLaunchHandler.postLaunch(launchArguments, context); } + + IDebugSession debugSession = context.getDebugSession(); + if (debugSession != null) { + debugSession.getEventHub().events() + .filter((debugEvent) -> debugEvent.event instanceof VMDisconnectEvent) + .subscribe((debugEvent) -> { + context.setVmTerminated(); + // Terminate eventHub thread. + try { + debugSession.getEventHub().close(); + } catch (Exception e) { + // do nothing. + } + + handleTerminatedEvent(context); + }); + } return CompletableFuture.completedFuture(res); }); } + protected void handleTerminatedEvent(IDebugAdapterContext context) { + CompletableFuture.runAsync(() -> { + try { + waitForDebuggeeConsole.get(5, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + // do nothing. + } + + context.getProtocolServer().sendEvent(new Events.TerminatedEvent()); + }); + } + /** * Construct the Java command lines based on the given launch arguments. * @param launchArguments - The launch arguments @@ -178,9 +225,56 @@ protected CompletableFuture launch(LaunchArguments launchArguments, Re if (context.supportsRunInTerminalRequest() && (launchArguments.console == CONSOLE.integratedTerminal || launchArguments.console == CONSOLE.externalTerminal)) { return activeLaunchHandler.launchInTerminal(launchArguments, response, context); - } else { - return activeLaunchHandler.launchInternally(launchArguments, response, context); } + + CompletableFuture resultFuture = new CompletableFuture<>(); + try { + Process debuggeeProcess = activeLaunchHandler.launch(launchArguments, context); + context.setDebuggeeProcess(debuggeeProcess); + ProcessConsole debuggeeConsole = new ProcessConsole(debuggeeProcess, "Debuggee", context.getDebuggeeEncoding()); + debuggeeConsole.lineMessages() + .map((message) -> convertToOutputEvent(message.output, message.category, context)) + .doFinally(() -> waitForDebuggeeConsole.complete(true)) + .subscribe((event) -> context.getProtocolServer().sendEvent(event)); + debuggeeConsole.start(); + resultFuture.complete(response); + } catch (IOException | IllegalConnectorArgumentsException | VMStartException e) { + resultFuture.completeExceptionally( + new DebugException( + String.format("Failed to launch debuggee VM. Reason: %s", e.toString()), + ErrorCode.LAUNCH_FAILURE.getId() + ) + ); + } + + return resultFuture; + } + + private static final Pattern STACKTRACE_PATTERN = Pattern.compile("\\s+at\\s+(([\\w$]+\\.)*[\\w$]+)\\(([\\w-$]+\\.java:\\d+)\\)"); + + private static OutputEvent convertToOutputEvent(String message, Category category, IDebugAdapterContext context) { + Matcher matcher = STACKTRACE_PATTERN.matcher(message); + if (matcher.find()) { + String methodField = matcher.group(1); + String locationField = matcher.group(matcher.groupCount()); + String fullyQualifiedName = methodField.substring(0, methodField.lastIndexOf(".")); + String packageName = fullyQualifiedName.lastIndexOf(".") > -1 ? fullyQualifiedName.substring(0, fullyQualifiedName.lastIndexOf(".")) : ""; + String[] locations = locationField.split(":"); + String sourceName = locations[0]; + int lineNumber = Integer.parseInt(locations[1]); + String sourcePath = StringUtils.isBlank(packageName) ? sourceName + : packageName.replace('.', File.separatorChar) + File.separatorChar + sourceName; + Types.Source source = null; + try { + source = StackTraceRequestHandler.convertDebuggerSourceToClient(fullyQualifiedName, sourceName, sourcePath, context); + } catch (URISyntaxException e) { + // do nothing. + } + + return new OutputEvent(category, message, source, lineNumber); + } + + return new OutputEvent(category, message); } protected static String[] constructEnvironmentVariables(LaunchArguments launchArguments) { diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithDebuggingDelegate.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithDebuggingDelegate.java index 807e579ff..0f690773a 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithDebuggingDelegate.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithDebuggingDelegate.java @@ -37,7 +37,6 @@ import com.microsoft.java.debug.core.adapter.IHotCodeReplaceProvider; import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider; import com.microsoft.java.debug.core.adapter.IVirtualMachineManagerProvider; -import com.microsoft.java.debug.core.adapter.ProcessConsole; import com.microsoft.java.debug.core.protocol.Events; import com.microsoft.java.debug.core.protocol.JsonUtils; import com.microsoft.java.debug.core.protocol.Messages.Request; @@ -169,7 +168,8 @@ public CompletableFuture launchInTerminal(LaunchArguments launchArgume return resultFuture; } - private Process launchInternalDebuggeeProcess(LaunchArguments launchArguments, IDebugAdapterContext context) + @Override + public Process launch(LaunchArguments launchArguments, IDebugAdapterContext context) throws IOException, IllegalConnectorArgumentsException, VMStartException { IVirtualMachineManagerProvider vmProvider = context.getProvider(IVirtualMachineManagerProvider.class); @@ -188,38 +188,6 @@ private Process launchInternalDebuggeeProcess(LaunchArguments launchArguments, I return debugSession.process(); } - @Override - public CompletableFuture launchInternally(LaunchArguments launchArguments, Response response, IDebugAdapterContext context) { - CompletableFuture resultFuture = new CompletableFuture<>(); - - try { - Process debugeeProcess = launchInternalDebuggeeProcess(launchArguments, context); - - ProcessConsole debuggeeConsole = new ProcessConsole(debugeeProcess, "Debuggee", context.getDebuggeeEncoding()); - debuggeeConsole.onStdout((output) -> { - // When DA receives a new OutputEvent, it just shows that on Debug Console and doesn't affect the DA's dispatching workflow. - // That means the debugger can send OutputEvent to DA at any time. - context.getProtocolServer().sendEvent(Events.OutputEvent.createStdoutOutput(output)); - }); - - debuggeeConsole.onStderr((err) -> { - context.getProtocolServer().sendEvent(Events.OutputEvent.createStderrOutput(err)); - }); - debuggeeConsole.start(); - - resultFuture.complete(response); - } catch (IOException | IllegalConnectorArgumentsException | VMStartException e) { - resultFuture.completeExceptionally( - new DebugException( - String.format("Failed to launch debuggee VM. Reason: %s", e.toString()), - ErrorCode.LAUNCH_FAILURE.getId() - ) - ); - } - - return resultFuture; - } - @Override public void postLaunch(LaunchArguments launchArguments, IDebugAdapterContext context) { Map options = new HashMap<>(); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithoutDebuggingDelegate.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithoutDebuggingDelegate.java index 7d5ab8c18..804ee58ca 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithoutDebuggingDelegate.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchWithoutDebuggingDelegate.java @@ -17,6 +17,7 @@ import java.nio.file.Paths; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; +import java.util.function.Consumer; import java.util.logging.Logger; import com.google.gson.JsonObject; @@ -24,7 +25,6 @@ import com.microsoft.java.debug.core.DebugException; import com.microsoft.java.debug.core.adapter.ErrorCode; import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; -import com.microsoft.java.debug.core.adapter.ProcessConsole; import com.microsoft.java.debug.core.protocol.Events; import com.microsoft.java.debug.core.protocol.JsonUtils; import com.microsoft.java.debug.core.protocol.Messages.Request; @@ -40,8 +40,14 @@ public class LaunchWithoutDebuggingDelegate implements ILaunchDelegate { protected static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); protected static final String TERMINAL_TITLE = "Java Process Console"; protected static final long RUNINTERMINAL_TIMEOUT = 10 * 1000; + private Consumer terminateHandler; - private Process launchInternalDebuggeeProcess(LaunchArguments launchArguments, IDebugAdapterContext context) + public LaunchWithoutDebuggingDelegate(Consumer terminateHandler) { + this.terminateHandler = terminateHandler; + } + + @Override + public Process launch(LaunchArguments launchArguments, IDebugAdapterContext context) throws IOException, IllegalConnectorArgumentsException, VMStartException { String[] cmds = LaunchRequestHandler.constructLaunchCommands(launchArguments, false, null); File workingDir = null; @@ -58,7 +64,7 @@ public void run() { logger.warning(String.format("Current thread is interrupted. Reason: %s", ignore.toString())); debuggeeProcess.destroy(); } finally { - context.getProtocolServer().sendEvent(new Events.TerminatedEvent()); + terminateHandler.accept(context); } } }.start(); @@ -66,39 +72,6 @@ public void run() { return debuggeeProcess; } - @Override - public CompletableFuture launchInternally(LaunchArguments launchArguments, Response response, IDebugAdapterContext context) { - CompletableFuture resultFuture = new CompletableFuture<>(); - - try { - Process debuggeeProcess = launchInternalDebuggeeProcess(launchArguments, context); - context.setDebuggeeProcess(debuggeeProcess); - - ProcessConsole debuggeeConsole = new ProcessConsole(debuggeeProcess, "Debuggee", context.getDebuggeeEncoding()); - debuggeeConsole.onStdout((output) -> { - // When DA receives a new OutputEvent, it just shows that on Debug Console and doesn't affect the DA's dispatching workflow. - // That means the debugger can send OutputEvent to DA at any time. - context.getProtocolServer().sendEvent(Events.OutputEvent.createStdoutOutput(output)); - }); - - debuggeeConsole.onStderr((err) -> { - context.getProtocolServer().sendEvent(Events.OutputEvent.createStderrOutput(err)); - }); - debuggeeConsole.start(); - - resultFuture.complete(response); - } catch (IOException | IllegalConnectorArgumentsException | VMStartException e) { - resultFuture.completeExceptionally( - new DebugException( - String.format("Failed to launch debuggee VM. Reason: %s", e.toString()), - ErrorCode.LAUNCH_FAILURE.getId() - ) - ); - } - - return resultFuture; - } - @Override public void postLaunch(LaunchArguments launchArguments, IDebugAdapterContext context) { // For NO_DEBUG launch mode, the debugger does not respond to requests like diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java index 1947eecde..089a55b9d 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java @@ -123,10 +123,17 @@ private Types.Source convertDebuggerSourceToClient(Location location, IDebugAdap relativeSourcePath = enclosingType.replace('.', File.separatorChar) + ".java"; } - final String finalRelativeSourcePath = relativeSourcePath; + return convertDebuggerSourceToClient(fullyQualifiedName, sourceName, relativeSourcePath, context); + } + + /** + * Find the source mapping for the specified source file name. + */ + public static Types.Source convertDebuggerSourceToClient(String fullyQualifiedName, String sourceName, String relativeSourcePath, + IDebugAdapterContext context) throws URISyntaxException { // use a lru cache for better performance String uri = context.getSourceLookupCache().computeIfAbsent(fullyQualifiedName, key -> { - String fromProvider = context.getProvider(ISourceLookUpProvider.class).getSourceFileURI(key, finalRelativeSourcePath); + String fromProvider = context.getProvider(ISourceLookUpProvider.class).getSourceFileURI(key, relativeSourcePath); // avoid return null which will cause the compute function executed again return StringUtils.isBlank(fromProvider) ? "" : fromProvider; }); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/AbstractProtocolServer.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/AbstractProtocolServer.java index 1cbe7d52d..9bec3c228 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/AbstractProtocolServer.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/AbstractProtocolServer.java @@ -100,6 +100,8 @@ public void run() { } catch (IOException e) { logger.log(Level.SEVERE, String.format("Read data from io exception: %s", e.toString()), e); } + + requestSubject.onComplete(); } /** diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Events.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Events.java index 0dea97b6f..ea18f183c 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Events.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/protocol/Events.java @@ -11,6 +11,8 @@ package com.microsoft.java.debug.core.protocol; +import com.microsoft.java.debug.core.protocol.Types.Source; + /** * The event types defined by VSCode Debug Protocol. */ @@ -136,6 +138,11 @@ public enum Category { public Category category; public String output; + public int variablesReference; + public Source source; + public int line; + public int column; + public Object data; /** * Constructor. @@ -146,6 +153,17 @@ public OutputEvent(Category category, String output) { this.output = output; } + /** + * Constructor. + */ + public OutputEvent(Category category, String output, Source source, int line) { + super("output"); + this.category = category; + this.output = output; + this.source = source; + this.line = line; + } + public static OutputEvent createConsoleOutput(String output) { return new OutputEvent(Category.console, output); } @@ -154,10 +172,24 @@ public static OutputEvent createStdoutOutput(String output) { return new OutputEvent(Category.stdout, output); } + /** + * Construct an stdout output event with source info. + */ + public static OutputEvent createStdoutOutputWithSource(String output, Source source, int line) { + return new OutputEvent(Category.stdout, output, source, line); + } + public static OutputEvent createStderrOutput(String output) { return new OutputEvent(Category.stderr, output); } + /** + * Construct an stderr output event with source info. + */ + public static OutputEvent createStderrOutputWithSource(String output, Source source, int line) { + return new OutputEvent(Category.stderr, output, source, line); + } + public static OutputEvent createTelemetryOutput(String output) { return new OutputEvent(Category.telemetry, output); }