Skylark debugging protocol: include frames information in ThreadPausedEvents.

This is almost always desirable -- if a thread is paused, the IDE expects
to know the context.

PiperOrigin-RevId: 199865078
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkdebug/proto/skylark_debugging.proto b/src/main/java/com/google/devtools/build/lib/skylarkdebug/proto/skylark_debugging.proto
index ed398e7..649f293 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkdebug/proto/skylark_debugging.proto
+++ b/src/main/java/com/google/devtools/build/lib/skylarkdebug/proto/skylark_debugging.proto
@@ -184,6 +184,11 @@
 message ThreadPausedEvent {
   // The thread that was paused.
   Thread thread = 1;
+
+  // The list of stack frames for the paused thread. The first element in the
+  // list represents the topmost frame (that is, the current innermost
+  // function).
+  repeated Frame frame = 2;
 }
 
 // An event indicating that a thread has continued execution after being paused.
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/DebugEventHelper.java b/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/DebugEventHelper.java
index 0254484..aa2279f 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/DebugEventHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/DebugEventHelper.java
@@ -130,9 +130,9 @@
         .build();
   }
 
-  static DebugEvent threadPausedEvent(Thread thread) {
+  static DebugEvent threadPausedEvent(Thread thread, Collection<Frame> frames) {
     return DebugEvent.newBuilder()
-        .setThreadPaused(ThreadPausedEvent.newBuilder().setThread(thread))
+        .setThreadPaused(ThreadPausedEvent.newBuilder().setThread(thread).addAllFrame(frames))
         .build();
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/ThreadHandler.java b/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/ThreadHandler.java
index fd30c64..e1f74f9 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/ThreadHandler.java
+++ b/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/ThreadHandler.java
@@ -225,6 +225,11 @@
     }
     // no need to list frames within the synchronize block: threads can only be resumed in response
     // to a client request, and requests are handled serially
+    return listFrames(debuggable, pausedState);
+  }
+
+  private static ImmutableList<SkylarkDebuggingProtos.Frame> listFrames(
+      Debuggable debuggable, PausedThreadState pausedState) {
     return debuggable
         .listFrames(pausedState.location)
         .stream()
@@ -265,6 +270,7 @@
 
     SkylarkDebuggingProtos.Thread threadProto;
     PausedThreadState pausedState;
+    Debuggable debuggable;
     synchronized (threads) {
       ThreadState thread = threads.get(threadId);
       if (thread == null) {
@@ -276,13 +282,15 @@
         transport.postEvent(DebugEventHelper.threadStartedEvent(threadId, fallbackThreadName));
         thread = doRegisterThread(threadId, fallbackThreadName, env);
       }
+      debuggable = thread.debuggable;
       pausedState = new PausedThreadState(location);
       thread.pausedState = pausedState;
       // get proto after setting the paused state, so that it's up to date
       threadProto = getThreadProto(thread);
     }
 
-    transport.postEvent(DebugEventHelper.threadPausedEvent(threadProto));
+    transport.postEvent(
+        DebugEventHelper.threadPausedEvent(threadProto, listFrames(debuggable, pausedState)));
     pausedState.semaphore.acquireUninterruptibly();
     transport.postEvent(
         DebugEventHelper.threadContinuedEvent(
diff --git a/src/test/java/com/google/devtools/build/lib/skylarkdebug/server/SkylarkDebugServerTest.java b/src/test/java/com/google/devtools/build/lib/skylarkdebug/server/SkylarkDebugServerTest.java
index d667493..8aea188 100644
--- a/src/test/java/com/google/devtools/build/lib/skylarkdebug/server/SkylarkDebugServerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylarkdebug/server/SkylarkDebugServerTest.java
@@ -169,7 +169,7 @@
     Environment env = newEnvironment();
 
     Location breakpoint =
-        Location.newBuilder().setLineNumber(2).setPath("/a/build/file/BUILD").build();
+        Location.newBuilder().setLineNumber(1).setPath("/a/build/file/BUILD").build();
     setBreakpoints(ImmutableList.of(breakpoint));
 
     Thread evaluationThread = execInWorkerThread(buildFile, env);
@@ -179,6 +179,22 @@
     // wait for breakpoint to be hit
     client.waitForEvent(DebugEvent::hasThreadPaused, Duration.ofSeconds(5));
 
+    assertThat(client.unnumberedEvents)
+        .contains(
+            DebugEventHelper.threadPausedEvent(
+                SkylarkDebuggingProtos.Thread.newBuilder()
+                    .setName(threadName)
+                    .setId(threadId)
+                    .setIsPaused(true)
+                    .setLocation(breakpoint.toBuilder().setColumnNumber(1))
+                    .build(),
+                ImmutableList.of(
+                    Frame.newBuilder()
+                        .setFunctionName("<top level>")
+                        .setLocation(breakpoint.toBuilder().setColumnNumber(1))
+                        .addScope(Scope.newBuilder().setName("global"))
+                        .build())));
+
     assertThat(listThreads().getThreadList())
         .containsExactly(
             SkylarkDebuggingProtos.Thread.newBuilder()