Repository: microsoft/qsharp
Pull request #2248 (2025-03-21 00:28:14)

Enable debug lowering for Interpreter::from


Patch:
diff --git a/compiler/qsc/src/interpret.rs b/compiler/qsc/src/interpret.rs
index 829c073e..1c81fc09 100644
--- a/compiler/qsc/src/interpret.rs
+++ b/compiler/qsc/src/interpret.rs
@@ -260,6 +260,7 @@ impl Interpreter {
     }
 
     pub fn from(
+        dbg: bool,
         store: PackageStore,
         source_package_id: qsc_hir::hir::PackageId,
         capabilities: TargetCapabilityFlags,
@@ -277,7 +278,7 @@ impl Interpreter {
 
         let mut fir_store = fir::PackageStore::new();
         for (id, unit) in compiler.package_store() {
-            let mut lowerer = qsc_lowerer::Lowerer::new();
+            let mut lowerer = qsc_lowerer::Lowerer::new().with_debug(dbg);
             let pkg = lowerer.lower_package(&unit.package, &fir_store);
             fir_store.insert(map_hir_package_to_fir(id), pkg);
         }
@@ -310,7 +311,7 @@ impl Interpreter {
             lines: 0,
             capabilities,
             fir_store,
-            lowerer: qsc_lowerer::Lowerer::new(),
+            lowerer: qsc_lowerer::Lowerer::new().with_debug(dbg),
             expr_graph: None,
             env: Env::default(),
             sim: sim_circuit_backend(),
@@ -1060,6 +1061,17 @@ impl Debugger {
         })
     }
 
+    pub fn from(interpreter: Interpreter, position_encoding: Encoding) -> Self {
+        let source_package_id = interpreter.source_package;
+        let unit = interpreter.fir_store.get(source_package_id);
+        let entry_exec_graph = unit.entry_exec_graph.clone();
+        Self {
+            interpreter,
+            position_encoding,
+            state: State::new(source_package_id, entry_exec_graph, None),
+        }
+    }
+
     /// Resumes execution with specified `StepAction`.
     /// # Errors
     /// Returns a vector of errors if evaluating the entry point fails.
diff --git a/npm/qsharp/ux/histogram.tsx b/npm/qsharp/ux/histogram.tsx
index 502048d9..b4dd4d17 100644
--- a/npm/qsharp/ux/histogram.tsx
+++ b/npm/qsharp/ux/histogram.tsx
@@ -3,9 +3,6 @@
 
 import { useRef, useState } from "preact/hooks";
 
-const enablePanning = false;
-const altKeyPans = true;
-
 const menuItems = [
   {
     category: "itemCount",
@@ -181,6 +178,21 @@ export function Histogram(props: {
   const showLabels = barBoxWidth > 5 && menuSelection["labels"] !== 2;
 
   function onWheel(e: WheelEvent): void {
+    // Ctrl+scroll is the event sent by pinch-to-zoom on a trackpad. Shift+scroll is common for
+    // panning horizontally. See https://danburzo.ro/dom-gestures/ for the messy details.
+    if (!e.ctrlKey && !e.shiftKey) return;
+
+    // When using a mouse wheel, the deltaY is the scroll amount, but if the shift key is pressed
+    // this swaps and deltaX is the scroll amount. The swap doesn't happen for trackpad scrolling.
+    // To complicate matters more, on the trackpad sometimes both deltaX and deltaY have a value.
+    // So, if the shift key is pressed and deltaY is 0, then assume mouse wheel and use deltaX.
+    let delta = e.shiftKey && !e.deltaY ? e.deltaX : e.deltaY;
+
+    // Scrolling with the wheel can result in really large deltas, so we need to cap them.
+    if (Math.abs(delta) > 20) {
+      delta = Math.sign(delta) * 20;
+    }
+
     e.preventDefault();
 
     // currentTarget is the element the listener is attached to, the main svg
@@ -207,9 +219,9 @@ export function Histogram(props: {
     let newScrollOffset = scale.offset;
     let newZoom = scale.zoom;
 
-    // *** First handle any zooming ***
-    if (!altKeyPans || !e.altKey) {
-      newZoom = scale.zoom + e.deltaY * 0.05;
+    if (!e.shiftKey) {
+      // *** Zooming ***
+      newZoom = scale.zoom - delta * 0.05;
       newZoom = Math.min(Math.max(1, newZoom), 50);
 
       // On zooming in, need to shift left to maintain mouse point, and vice verca.
@@ -220,14 +232,9 @@ export function Histogram(props: {
         newZoom * barAreaWidth - scale.zoom * barAreaWidth;
       const shiftLeftAdjust = percentRightOnChart * chartWidthGrowth;
       newScrollOffset = scale.offset - shiftLeftAdjust;
-    }
-
-    // *** Then handle any panning ***
-    if (enablePanning) {
-      newScrollOffset -= e.deltaX;
-    }
-    if (!enablePanning && altKeyPans && e.altKey) {
-      newScrollOffset -= e.deltaY;
+    } else {
+      // *** Panning ***
+      newScrollOffset -= delta;
     }
 
     // Don't allow offset > 1 (scrolls the first bar right of the left edge of the area)
@@ -414,18 +421,18 @@ export function Histogram(props: {
               Click the top-left 'settings' icon for display options.
             </tspan>
             <tspan x="10" dy="10">
-              You can zoom the chart using the mouse scroll wheel.
+              You can zoom the chart using the pinch-to-zoom gesture,
             </tspan>
-            <tspan x="10" dy="7">
-              (Or using a trackpad gesture).
+            <tspan x="10" dy="10">
+              or use Ctrl+scroll wheel to zoom in/out.
             </tspan>
             <tspan x="10" dy="10">
-              When zoomed, to pan left &amp; right, press 'Alt' while scrolling.
+              To pan left &amp; right, press Shift while zooming.
             </tspan>
             <tspan x="10" dy="10">
               Click on a bar to filter the shot details to that result.
             </tspan>
-            <tspan x="10" dy="12">
+            <tspan x="10" dy="10">
               Click anywhere in this box to dismiss it.
             </tspan>
           </text>
diff --git a/pip/src/interop.rs b/pip/src/interop.rs
index dee2fd15..b109061d 100644
--- a/pip/src/interop.rs
+++ b/pip/src/interop.rs
@@ -558,6 +558,7 @@ fn create_interpreter_from_ast(
     let source_package_id = store.insert(unit);
 
     interpret::Interpreter::from(
+        false,
         store,
         source_package_id,
         capabilities,
diff --git a/vscode/src/copilot/webview/copilot.css b/vscode/src/copilot/webview/copilot.css
index fb5cb2bf..f56ca78d 100644
--- a/vscode/src/copilot/webview/copilot.css
+++ b/vscode/src/copilot/webview/copilot.css
@@ -38,10 +38,13 @@ h1 {
 }
 
 .status-indicator {
-  height: 30px;
-  font-weight: bold;
-  text-align: right;
-  font-size: smaller;
+  display: flex;
+  justify-content: flex-end;
+  flex-wrap: wrap;
+  align-content: center;
+  height: 28px;
+  font-style: italic;
+  font-size: 0.9em;
 }
 
 .input-textarea {
@@ -165,6 +168,7 @@ pre code {
 
 .histogram-container {
   width: 100%;
+  margin-bottom: 10px;
 }
 
 /* If not specified it defaults to the 'pre code' default monospace font  */
@@ -187,6 +191,13 @@ pre code {
   --qs-code-strings: #a31515;
   --qs-code-numbers: #098658;
   --qs-code-calls: #795e26;
+  --qs-welcome-background: linear-gradient(
+    90deg,
+    hsl(0deg 0% 100%),
+    hsl(218 16% 98%)
+  );
+  --qs-welcome-color: #222;
+  --qs-welcome-chatbox: #fff;
 }
 
 /* Explicit dark theme set */
@@ -202,6 +213,13 @@ pre code {
   --qs-code-strings: #ce9178;
   --qs-code-numbers: #b5cea8;
   --qs-code-calls: #dcdcaa;
+  --qs-welcome-background: linear-gradient(
+    90deg,
+    hsl(0deg 0% 13.31%),
+    hsl(218deg 2.12% 15.67%)
+  );
+  --qs-welcome-color: #eee;
+  --qs-welcome-chatbox: #313131;
 }
 
 /* Code block styles for highlight.js */
@@ -279,3 +297,47 @@ code.hljs {
 .hljs-emphasis {
   font-style: italic;
 }
+
+/* Welcome message styles */
+#qs-copilot-welcome {
+  color: var(--qs-welcome-color);
+  max-width: 500px;
+  border: 0.5px solid #ccc;
+  border-radius: 0.8em;
+  background: var(--qs-welcome-background);
+  box-shadow: 0.1em 0.1em 0.4em rgba(0, 0, 0, 0.1);
+  font-family: system-ui;
+  font-size: 0.9em;
+  padding: 1em;
+}
+
+.qs-copilot-title {
+  display: flex;
+  flex-wrap: wrap;
+  align-content: center;
+  font-size: 1.5em;
+  font-weight: 400;
+  margin-bottom: 0.8em;
+}
+
+.qs-copilot-blurb {
+  margin-bottom: 0.8em;
+  line-height: 1.33em;
+}
+
+.qs-copilot-demo1 {
+  border: 0.5px solid #888;
+  border-radius: 0.4em;
+  background: var(--qs-welcome-chatbox);
+  /* dark: #313131 */
+  margin: 0.4em 0.8em;
+  padding: 0.4em 0.6em;
+  display: inline-block;
+  font-style: italic;
+}
+
+.qs-copilot-disclaimer {
+  font-size: 0.8em;
+  color: gray;
+  margin-top: 0.8em;
+}
diff --git a/vscode/src/copilot/webview/copilot.tsx b/vscode/src/copilot/webview/copilot.tsx
index ebd03e92..f150da4e 100644
--- a/vscode/src/copilot/webview/copilot.tsx
+++ b/vscode/src/copilot/webview/copilot.tsx
@@ -20,6 +20,7 @@ import hljs from "highlight.js/lib/core";
 import CopyButtonPlugin from "highlightjs-copy";
 import "highlightjs-copy/styles/highlightjs-copy.css";
 import python from "highlight.js/lib/languages/python";
+import bash from "highlight.js/lib/languages/bash"; // Used sometimes for plain text
 import markdownIt from "markdown-it";
 import { useEffect, useRef } from "preact/hooks";
 import { Histogram, Markdown, setRenderer } from "qsharp-lang/ux";
@@ -34,9 +35,10 @@ import { WebviewApi } from "vscode-webview";
 
 const vscodeApi: WebviewApi<ChatElement[]> = acquireVsCodeApi();
 
-// Only include the Python and Q# languages so as not
+// Only include a small set of languages so as not
 // to bloat the code
 hljs.registerLanguage("python", python);
+hljs.registerLanguage("bash", bash);
 hljs.registerLanguage("qsharp", hlsjQsharp);
 hljs.addPlugin(new CopyButtonPlugin());
 const md = markdownIt("commonmark");
@@ -46,30 +48,150 @@ md.use(mk as any, {
 });
 setRenderer((input: string) => md.render(input));
 
+function Welcome() {
+  return (
+    <div id="qs-copilot-welcome">
+      <div class="qs-copilot-title">
+        <svg
+          width="24"
+          height="24"
+          viewBox="0 0 24 24"
+          fill="none"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="M17.0722 3.66246C16.7827 2.67691 15.8784 2 14.8512 2L14.1735 2C13.0569 2 12.0994 2.7971 11.897 3.8952L10.7119 10.3247L11.0335 9.22215C11.3216 8.23453 12.2269 7.55555 13.2557 7.55555L17.1772 7.55556L18.8242 8.19709L20.4119 7.55556H19.9483C18.9212 7.55556 18.0168 6.87864 17.7273 5.89309L17.0722 3.66246Z"
+            fill="url(#paint0_radial_301_554)"
+          ></path>
+          <path
+            d="M7.16561 20.328C7.45189 21.3183 8.35852 22 9.38937 22H10.8432C12.0912 22 13.1145 21.0107 13.1567 19.7634L13.3712 13.4201L12.9681 14.7851C12.6776 15.7691 11.774 16.4444 10.7481 16.4444L6.78679 16.4444L5.37506 15.6786L3.84668 16.4444H4.3025C5.33335 16.4444 6.23998 17.1261 6.52626 18.1164L7.16561 20.328Z"
+            fill="url(#paint1_radial_301_554)"
+          ></path>
+          <path
+            d="M14.7507 2H6.73041C4.43891 2 3.06401 5.02777 2.14741 8.05553C1.06148 11.6426 -0.359484 16.4401 3.75146 16.4401H7.21482C8.24955 16.4401 9.15794 15.7559 9.44239 14.7611C10.0445 12.6551 11.0997 8.98146 11.9285 6.18489C12.3497 4.76367 12.7005 3.5431 13.239 2.783C13.5409 2.35686 14.044 2 14.7507 2Z"
+            fill="url(#paint2_linear_301_554)"
+          ></path>
+          <path
+            d="M14.7507 2H6.73041C4.43891 2 3.06401 5.02777 2.14741 8.05553C1.06148 11.6426 -0.359484 16.4401 3.75146 16.4401H7.21482C8.24955 16.4401 9.15794 15.7559 9.44239 14.7611C10.0445 12.6551 11.0997 8.98146 11.9285 6.18489C12.3497 4.76367 12.7005 3.5431 13.239 2.783C13.5409 2.35686 14.044 2 14.7507 2Z"
+            fill="url(#paint3_linear_301_554)"
+          ></path>
+          <path
+            d="M9.24951 22H17.2698C19.5613 22 20.9362 18.9722 21.8528 15.9445C22.9387 12.3574 24.3597 7.55991 20.2487 7.55991H16.7854C15.7506 7.55991 14.8422 8.24407 14.5578 9.23894C13.9556 11.3449 12.9005 15.0186 12.0717 17.8151C11.6505 19.2363 11.2996 20.4569 10.7612 21.217C10.4593 21.6431 9.95619 22 9.24951 22Z"
+            fill="url(#paint4_radial_301_554)"
+          ></path>
+          <path
+            d="M9.24951 22H17.2698C19.5613 22 20.9362 18.9722 21.8528 15.9445C22.9387 12.3574 24.3597 7.55991 20.2487 7.55991H16.7854C15.7506 7.55991 14.8422 8.24407 14.5578 9.23894C13.9556 11.3449 12.9005 15.0186 12.0717 17.8151C11.6505 19.2363 11.2996 20.4569 10.7612 21.217C10.4593 21.6431 9.95619 22 9.24951 22Z"
+            fill="url(#paint5_linear_301_554)"
+          ></path>
+          <defs>
+            <radialGradient
+              id="paint0_radial_301_554"
+              cx="0"
+              cy="0"
+              r="1"
+              gradientUnits="userSpaceOnUse"
+              gradientTransform="translate(18.9994 10.3791) rotate(-128.978) scale(8.73886 8.198)"
+            >
+              <stop offset="0.0955758" stop-color="#0078D4"></stop>
+              <stop offset="0.715277" stop-color="#0C709B"></stop>
+              <stop offset="1" stop-color="#0A5079"></stop>
+            </radialGradient>
+            <radialGradient
+              id="paint1_radial_301_554"
+              cx="0"
+              cy="0"
+              r="1"
+              gradientUnits="userSpaceOnUse"
+              gradientTransform="translate(5.57463 16.2453) rotate(45.7) scale(8.04078 7.90145)"
+            >
+              <stop stop-color="#0091EB"></stop>
+              <stop offset="0.523516" stop-color="#2764E7"></stop>
+              <stop offset="0.923392" stop-color="#0636C3"></stop>
+            </radialGradient>
+            <linearGradient
+              id="paint2_linear_301_554"
+              x1="5.16831"
+              y1="2"
+              x2="7.75605"
+              y2="17.2359"
+              gradientUnits="userSpaceOnUse"
+            >
+              <stop offset="0.289817" stop-color="#00A5D9"></stop>
+              <stop offset="0.662336" stop-color="#21CAB2"></stop>
+              <stop offset="0.950002" stop-color="#6ADC90"></stop>
+            </linearGradient>
+            <linearGradient
+              id="paint3_linear_301_554"
+              x1="7.25046"
+              y1="2"
+              x2="7.87502"
+              y2="16.4401"
+              gradientUnits="userSpaceOnUse"
+            >
+              <stop stop-color="#10C9EC"></stop>
+              <stop
+                offset="0.166667"
+                stop-color="#01AEE4"
+                stop-opacity="0"
+              ></stop>
+            </linearGradient>
+            <radialGradient
+              id="paint4_radial_301_554"
+              cx="0"
+              cy="0"
+              r="1"
+              gradientUnits="userSpaceOnUse"
+              gradientTransform="translate(20.6607 6.14612) rotate(111.466) scale(19.1552 22.9833)"
+            >
+              <stop offset="0.154405" stop-color="#2771D8"></stop>
+              <stop offset="0.678875" stop-color="#14B1FF"></stop>
+              <stop offset="0.931138" stop-color="#16BFDF"></stop>
+            </radialGradient>
+            <linearGradient
+              id="paint5_linear_301_554"
+              x1="21.2403"
+              y1="7.00944"
+              x2="20.306"
+              y2="12.5796"
+              gradientUnits="userSpaceOnUse"
+            >
+              <stop offset="0.0581535" stop-color="#14B1FF"></stop>
+              <stop
+                offset="0.708063"
+                stop-color="#2976DB"
+                stop-opacity="0"
+              ></stop>
+            </linearGradient>
+          </defs>
+        </svg>
+        <div style="margin-left: 8px;">Azure Quantum Copilot</div>
+      </div>
+      <div class="qs-copilot-blurb">
+        Welcome! Azure Quantum Copilot is designed to help you develop and run
+        quantum programs. You can ask questions such as:
+      </div>
+      <div class="qs-copilot-demo1">
+        Can you help me implement Grover's search?
+      </div>
+      <div class="qs-copilot-demo1" style="margin-left: 1.5em">
+        Please submit this job to my Azure Quantum workspace
+      </div>
+      <div class="qs-copilot-demo1">
+        Show me the results for the last job as a histogram
+      </div>
+      <div class="qs-copilot-disclaimer">
+        Copilot is powered by AI, so mistakes are possible. Review output
+        carefully before use.
+      </div>
+    </div>
+  );
+}
+
 function App({ model }: { model: ChatViewModel }) {
   return (
     <div className="app">
       <div className="chat">
-        {model.history.length > 0 ? (
-          <ChatHistory model={model} />
-        ) : (
-          <Markdown
-            className="welcome-message"
-            markdown={`# Azure Quantum<br/>Copilot
-
-I'm here to assist you with your Azure Quantum workspace and help you explore Q# programming.
-
-Try:
-
-_Can you submit this Q# program to Azure Quantum for execution?_
-
-_What are the currently available hardware providers for my workspace?_
-
-_Can you show the results from my last job?_
-
-Azure Quantum Copilot is powered by AI, so mistakes are possible. Review the output carefully before use.`}
-          ></Markdown>
-        )}
+        {model.history.length > 0 ? <ChatHistory model={model} /> : <Welcome />}
         <StatusIndicator status={model.status} />
         <InputBox
           disable={
@@ -162,13 +284,18 @@ function ChatHistory({ model }: { model: ChatViewModel }) {
 function StatusIndicator({ status }: { status: Status }) {
   return (
     <div className="status-indicator">
-      {status.status === "waitingAssistantResponse"
-        ? "🕒"
-        : status.status === "executingTool"
-          ? "🕒 " + status.toolName
-          : status.status === "assistantConnectionError"
-            ? "There was an error communicating with Azure Quantum Copilot. Please check your Internet connection and try again."
-            : ""}
+      {status.status === "waitingAssistantResponse" ? (
+        <span class="codicon codicon-loading codicon-modifier-spin"></span>
+      ) : status.status === "executingTool" ? (
+        <>
+          <span>{status.toolName}&nbsp;</span>
+          <span class="codicon codicon-loading codicon-modifier-spin"></span>
+        </>
+      ) : status.status === "assistantConnectionError" ? (
+        "There was an error communicating with Azure Quantum Copilot. Please check your Internet connection and try again."
+      ) : (
+        ""
+      )}
     </div>
   );
 }


Test patch:
diff --git a/compiler/qsc/src/interpret/tests.rs b/compiler/qsc/src/interpret/tests.rs
index 99942ba5..06e2f294 100644
--- a/compiler/qsc/src/interpret/tests.rs
+++ b/compiler/qsc/src/interpret/tests.rs
@@ -1928,6 +1928,7 @@ mod given_interpreter {
             let package_id = store.insert(unit);
 
             let mut interpreter = Interpreter::from(
+                false,
                 store,
                 package_id,
                 capabilities,