ソースを参照

Backport PR #11566 on branch 3.3.x (Show the kernel sources as a debugger tab and allow the user to break in kernel sources) (#11954)

* Backport PR #11566 on branch 3.3.x (Show the kernel sources as a debugger tab and allow the user to break in kernel sources)

* lint
Eric Charles 3 年 前
コミット
5e196b432c

+ 6 - 0
packages/debugger-extension/schema/main.json

@@ -107,6 +107,12 @@
           "__spec__"
         ]
       }
+    },
+    "defaultKernelSourcesFilter": {
+      "title": "Default kernel sources regexp filter",
+      "description": "A regular expression filter to apply by default when showing the kernel sources",
+      "type": "string",
+      "default": ""
     }
   },
   "additionalProperties": false,

+ 14 - 0
packages/debugger-extension/src/index.ts

@@ -541,6 +541,9 @@ const sidebar: JupyterFrontEndPlugin<IDebugger.ISidebar> = {
         if (kernel && filters[kernel]) {
           sidebar.variables.filter = new Set<string>(filters[kernel]);
         }
+        const kernelSourcesFilter = setting.get('defaultKernelSourcesFilter')
+          .composite as string;
+        sidebar.kernelSources.filter = kernelSourcesFilter;
       };
       updateSettings();
       setting.changed.connect(updateSettings);
@@ -795,6 +798,16 @@ const main: JupyterFrontEndPlugin<void> = {
             });
           });
       };
+      const onKernelSourceOpened = (
+        _: IDebugger.Model.IKernelSources | null,
+        source: IDebugger.Source,
+        breakpoint?: IDebugger.IBreakpoint
+      ): void => {
+        if (!source) {
+          return;
+        }
+        onCurrentSourceOpened(null, source, breakpoint);
+      };
 
       const onCurrentSourceOpened = (
         _: IDebugger.Model.ISources | null,
@@ -856,6 +869,7 @@ const main: JupyterFrontEndPlugin<void> = {
 
       model.callstack.currentFrameChanged.connect(onCurrentFrameChanged);
       model.sources.currentSourceOpened.connect(onCurrentSourceOpened);
+      model.kernelSources.kernelSourceOpened.connect(onKernelSourceOpened);
       model.breakpoints.clicked.connect(async (_, breakpoint) => {
         const path = breakpoint.source?.path;
         const source = await service.getSource({

+ 6 - 0
packages/debugger/src/handler.ts

@@ -336,6 +336,9 @@ export class DebuggerHandler implements DebuggerHandler.IHandler {
       this._previousConnection = connection;
       await this._service.restoreState(true);
       await this._service.displayDefinedVariables();
+      if (this._service.session?.capabilities?.supportsModulesRequest) {
+        await this._service.displayModules();
+      }
     };
 
     const toggleDebugging = async (): Promise<void> => {
@@ -375,6 +378,9 @@ export class DebuggerHandler implements DebuggerHandler.IHandler {
     await this._service.restoreState(false);
     if (this._service.isStarted && !this._service.hasStoppedThreads()) {
       await this._service.displayDefinedVariables();
+      if (this._service.session?.capabilities?.supportsModulesRequest) {
+        await this._service.displayModules();
+      }
     }
 
     updateIconButtonState(

+ 6 - 0
packages/debugger/src/icons.ts

@@ -10,6 +10,7 @@ import stepOverSvgStr from '../style/icons/step-over.svg';
 import variableSvgStr from '../style/icons/variable.svg';
 import pauseSvgStr from '../style/icons/pause.svg';
 import viewBreakpointSvgStr from '../style/icons/view-breakpoint.svg';
+import openKernelSourceSvgStr from '../style/icons/open-kernel-source.svg';
 
 export {
   runIcon as continueIcon,
@@ -50,3 +51,8 @@ export const viewBreakpointIcon = new LabIcon({
   name: 'debugger:view-breakpoint',
   svgstr: viewBreakpointSvgStr
 });
+
+export const openKernelSourceIcon = new LabIcon({
+  name: 'debugger:open-kernel-source',
+  svgstr: openKernelSourceSvgStr
+});

+ 9 - 0
packages/debugger/src/model.ts

@@ -11,6 +11,8 @@ import { CallstackModel } from './panels/callstack/model';
 
 import { SourcesModel } from './panels/sources/model';
 
+import { KernelSourcesModel } from './panels/kernelSources/model';
+
 import { VariablesModel } from './panels/variables/model';
 
 /**
@@ -27,6 +29,7 @@ export class DebuggerModel implements IDebugger.Model.IService {
     this.sources = new SourcesModel({
       currentFrameChanged: this.callstack.currentFrameChanged
     });
+    this.kernelSources = new KernelSourcesModel();
   }
 
   /**
@@ -49,6 +52,11 @@ export class DebuggerModel implements IDebugger.Model.IService {
    */
   readonly sources: SourcesModel;
 
+  /**
+   * The kernel sources model.
+   */
+  readonly kernelSources: KernelSourcesModel;
+
   /**
    * A signal emitted when the debugger widget is disposed.
    */
@@ -133,6 +141,7 @@ export class DebuggerModel implements IDebugger.Model.IService {
     this.callstack.frames = [];
     this.variables.scopes = [];
     this.sources.currentSource = null;
+    this.kernelSources.kernelSources = null;
     this.title = '-';
   }
 

+ 134 - 0
packages/debugger/src/panels/kernelSources/body.ts

@@ -0,0 +1,134 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import { openKernelSourceIcon } from '../../icons';
+
+import { ReactWidget, ToolbarButton } from '@jupyterlab/apputils';
+
+import { showErrorMessage } from '@jupyterlab/apputils';
+
+import { ITranslator, nullTranslator } from '@jupyterlab/translation';
+
+import { PanelLayout, Widget } from '@lumino/widgets';
+
+import { KernelSourcesFilter } from './filter';
+
+import { IDebugger } from '../../tokens';
+
+/**
+ * The class name added to the filterbox node.
+ */
+const FILTERBOX_CLASS = 'jp-DebuggerKernelSource-filterBox';
+
+/**
+ * The class name added to hide the filterbox node.
+ */
+const FILTERBOX_HIDDEN_CLASS = 'jp-DebuggerKernelSource-filterBox-hidden';
+
+/**
+ * The body for a Sources Panel.
+ */
+export class KernelSourcesBody extends Widget {
+  /**
+   * Instantiate a new Body for the KernelSourcesBody widget.
+   *
+   * @param options The instantiation options for a KernelSourcesBody.
+   */
+  constructor(options: KernelSourcesBody.IOptions) {
+    super();
+    this._model = options.model;
+    this._debuggerService = options.service;
+    const trans = (options.translator ?? nullTranslator).load('jupyterlab');
+
+    this.layout = new PanelLayout();
+    this.addClass('jp-DebuggerKernelSources-body');
+
+    this._kernelSourcesFilter = KernelSourcesFilter({
+      model: this._model
+    });
+    this._kernelSourcesFilter.addClass(FILTERBOX_CLASS);
+    this._kernelSourcesFilter.addClass(FILTERBOX_HIDDEN_CLASS);
+
+    (this.layout as PanelLayout).addWidget(this._kernelSourcesFilter);
+
+    this._model.changed.connect((_, kernelSources) => {
+      this._clear();
+      console.log('---', kernelSources);
+      if (kernelSources) {
+        kernelSources.forEach(module => {
+          const name = module.name;
+          const path = module.path;
+          const button = new ToolbarButton({
+            icon: openKernelSourceIcon,
+            label: name,
+            tooltip: path
+          });
+          button.node.addEventListener('dblclick', () => {
+            this._debuggerService
+              .getSource({
+                sourceReference: 0,
+                path: path
+              })
+              .then(source => {
+                this._model.open(source);
+              })
+              .catch(reason => {
+                void showErrorMessage(
+                  trans.__('Fail to get source'),
+                  trans.__("Fail to get '%1' source:\n%2", path, reason)
+                );
+              });
+          });
+          (this.layout as PanelLayout).addWidget(button);
+        });
+      }
+    });
+  }
+
+  /**
+   * Show or hide the filter box.
+   */
+  public toggleFilterbox(): void {
+    this._kernelSourcesFilter.node.classList.contains(FILTERBOX_HIDDEN_CLASS)
+      ? this._kernelSourcesFilter.node.classList.remove(FILTERBOX_HIDDEN_CLASS)
+      : this._kernelSourcesFilter.node.classList.add(FILTERBOX_HIDDEN_CLASS);
+  }
+
+  /**
+   * Clear the content of the kernel source read-only editor.
+   */
+  private _clear(): void {
+    while ((this.layout as PanelLayout).widgets.length > 1) {
+      (this.layout as PanelLayout).removeWidgetAt(1);
+    }
+  }
+
+  private _model: IDebugger.Model.IKernelSources;
+  private _kernelSourcesFilter: ReactWidget;
+  private _debuggerService: IDebugger;
+}
+
+/**
+ * A namespace for SourcesBody `statics`.
+ */
+export namespace KernelSourcesBody {
+  /**
+   * Instantiation options for `Breakpoints`.
+   */
+  export interface IOptions {
+    /**
+     * The debug service.
+     */
+    service: IDebugger;
+
+    /**
+     * The sources model.
+     */
+    model: IDebugger.Model.IKernelSources;
+
+    /**
+     * The application language translator
+     */
+    translator?: ITranslator;
+  }
+}

+ 58 - 0
packages/debugger/src/panels/kernelSources/filter.tsx

@@ -0,0 +1,58 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import { ReactWidget, UseSignal } from '@jupyterlab/apputils';
+
+import { InputGroup } from '@jupyterlab/ui-components';
+
+import React from 'react';
+
+import { IDebugger } from '../../tokens';
+
+/**
+ * The class name added to the filebrowser crumbs node.
+ */
+export interface IFilterBoxProps {
+  model: IDebugger.Model.IKernelSources;
+  onFilterChange?: (e: any) => void;
+}
+
+const FilterBox = (props: IFilterBoxProps) => {
+  /**
+   * Handler for search input changes.
+  const handleChange = (e: React.FormEvent<HTMLElement>) => {
+    const target = e.target as HTMLInputElement;
+    setFilter(target.value);
+    props.model.filter = target.value;
+  };
+  */
+  return (
+    <InputGroup
+      type="text"
+      onChange={props.onFilterChange}
+      placeholder="Filter the kernel sources"
+      value={props.model.filter}
+    />
+  );
+};
+
+/**
+ * A widget which hosts a input textbox to filter on file names.
+ */
+export const KernelSourcesFilter = (props: IFilterBoxProps): ReactWidget => {
+  return ReactWidget.create(
+    <UseSignal
+      signal={props.model.changed}
+      initialArgs={props.model.kernelSources}
+    >
+      {model => (
+        <FilterBox
+          model={props.model}
+          onFilterChange={(e: any) => {
+            props.model.filter = (e.target as HTMLInputElement).value;
+          }}
+        />
+      )}
+    </UseSignal>
+  );
+};

+ 44 - 0
packages/debugger/src/panels/kernelSources/header.tsx

@@ -0,0 +1,44 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import { Toolbar } from '@jupyterlab/apputils';
+
+import { ITranslator, nullTranslator } from '@jupyterlab/translation';
+
+import { PanelLayout, Widget } from '@lumino/widgets';
+
+import { IDebugger } from '../../tokens';
+
+/**
+ * The header for a Kernel Source Panel.
+ */
+export class KernelSourcesHeader extends Widget {
+  /**
+   * Instantiate a new SourcesHeader.
+   *
+   * @param model The model for the Sources.
+   */
+  constructor(model: IDebugger.Model.IKernelSources, translator?: ITranslator) {
+    super({ node: document.createElement('div') });
+    this.node.classList.add('jp-stack-panel-header');
+
+    translator = translator || nullTranslator;
+    const trans = translator.load('jupyterlab');
+
+    const layout = new PanelLayout();
+    this.layout = layout;
+
+    const title = new Widget({ node: document.createElement('h2') });
+    title.node.textContent = trans.__('Kernel Sources');
+
+    layout.addWidget(title);
+    layout.addWidget(this.toolbar);
+
+    this.addClass('jp-DebuggerSources-header');
+  }
+
+  /**
+   * The toolbar for the sources header.
+   */
+  readonly toolbar = new Toolbar();
+}

+ 112 - 0
packages/debugger/src/panels/kernelSources/index.tsx

@@ -0,0 +1,112 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import { ITranslator, nullTranslator } from '@jupyterlab/translation';
+
+import { showErrorMessage } from '@jupyterlab/apputils';
+
+import { ToolbarButton } from '@jupyterlab/apputils';
+
+import { refreshIcon, searchIcon } from '@jupyterlab/ui-components';
+
+import { Panel } from '@lumino/widgets';
+
+import { IDebugger } from '../../tokens';
+
+import { KernelSourcesBody } from './body';
+
+import { KernelSourcesHeader } from './header';
+
+/**
+ * A Panel that shows a preview of the source code while debugging.
+ */
+export class KernelSources extends Panel {
+  /**
+   * Instantiate a new Sources preview Panel.
+   *
+   * @param options The Sources instantiation options.
+   */
+  constructor(options: KernelSources.IOptions) {
+    super();
+    const { model, service } = options;
+    this._model = model;
+    const trans = (options.translator ?? nullTranslator).load('jupyterlab');
+    this.title.label = trans.__('Kernel Sources');
+
+    const header = new KernelSourcesHeader(model, options.translator);
+
+    header.addClass('jp-DebuggerKernelSources-header');
+
+    this._body = new KernelSourcesBody({
+      service,
+      model,
+      translator: options.translator
+    });
+
+    header.toolbar.addItem(
+      'open-filter',
+      new ToolbarButton({
+        icon: searchIcon,
+        onClick: async (): Promise<void> => {
+          this._body.toggleFilterbox();
+        },
+        tooltip: trans.__('Toggle search filter')
+      })
+    );
+
+    header.toolbar.addItem(
+      'refresh',
+      new ToolbarButton({
+        icon: refreshIcon,
+        onClick: () => {
+          this._model.kernelSources = [];
+          void service.displayModules().catch(reason => {
+            void showErrorMessage(
+              trans.__('Fail to get kernel sources'),
+              trans.__('Fail to get kernel sources:\n%2', reason)
+            );
+          });
+        },
+        tooltip: trans.__('Refresh kernel sources')
+      })
+    );
+
+    this.addClass('jp-DebuggerKernelSources-header');
+    this.addClass('jp-DebuggerKernelSources');
+
+    this.addWidget(header);
+    this.addWidget(this._body);
+  }
+
+  public set filter(filter: string) {
+    this._model.filter = filter;
+  }
+
+  private _model: IDebugger.Model.IKernelSources;
+  private _body: KernelSourcesBody;
+}
+
+/**
+ * A namespace for `Sources` statics.
+ */
+export namespace KernelSources {
+  /**
+   * The options used to create a Sources.
+   */
+  export interface IOptions {
+    /**
+     * The debugger service.
+     */
+    service: IDebugger;
+
+    /**
+     * The model for the sources.
+     */
+    model: IDebugger.Model.IKernelSources;
+
+    /**
+     * The application language translator
+     */
+    translator?: ITranslator;
+  }
+}

+ 114 - 0
packages/debugger/src/panels/kernelSources/model.ts

@@ -0,0 +1,114 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import { ISignal, Signal } from '@lumino/signaling';
+
+import { IDebugger } from '../../tokens';
+
+const compare = (a: IDebugger.KernelSource, b: IDebugger.KernelSource) => {
+  if (a.name < b.name) {
+    return -1;
+  }
+  if (a.name > b.name) {
+    return 1;
+  }
+  return 0;
+};
+
+/**
+ * The model to keep track of the current source being displayed.
+ */
+export class KernelSourcesModel implements IDebugger.Model.IKernelSources {
+  /**
+   * Get the filter.
+   */
+  get filter(): string {
+    return this._filter;
+  }
+
+  /**
+   * Set the filter.
+   */
+  set filter(filter: string) {
+    this._filter = filter;
+    this.refresh();
+  }
+
+  /**
+   * Get the kernel sources.
+   */
+  get kernelSources(): IDebugger.KernelSource[] | null {
+    return this._kernelSources;
+  }
+
+  /**
+   * Set the kernel sources and emit a changed signal.
+   */
+  set kernelSources(kernelSources: IDebugger.KernelSource[] | null) {
+    this._kernelSources = kernelSources;
+    this.refresh();
+  }
+
+  /**
+   * Signal emitted when the current source changes.
+   */
+  get changed(): ISignal<this, IDebugger.KernelSource[] | null> {
+    return this._changed;
+  }
+
+  /**
+   * Signal emitted when a kernel source should be open in the main area.
+   */
+  get kernelSourceOpened(): ISignal<this, IDebugger.Source | null> {
+    return this._kernelSourceOpened;
+  }
+
+  /**
+   * Open a source in the main area.
+   */
+  open(kernelSource: IDebugger.Source): void {
+    this._kernelSourceOpened.emit(kernelSource);
+  }
+
+  private getFilteredKernelSources() {
+    const regexp = new RegExp(this._filter);
+    return this._kernelSources!.filter(module => regexp.test(module.name));
+  }
+
+  private refresh() {
+    if (this._kernelSources) {
+      this._filteredKernelSources = this._filter
+        ? this.getFilteredKernelSources()
+        : this._kernelSources;
+      this._filteredKernelSources.sort(compare);
+    } else {
+      this._kernelSources = new Array<IDebugger.KernelSource>();
+      this._filteredKernelSources = new Array<IDebugger.KernelSource>();
+    }
+    this._changed.emit(this._filteredKernelSources);
+  }
+
+  private _kernelSources: IDebugger.KernelSource[] | null = null;
+  private _filteredKernelSources: IDebugger.KernelSource[] | null = null;
+  private _filter = '';
+  private _changed = new Signal<this, IDebugger.KernelSource[] | null>(this);
+  private _kernelSourceOpened = new Signal<this, IDebugger.Source | null>(this);
+}
+
+/**
+ * A namespace for SourcesModel `statics`.
+ */
+export namespace KernelSourcesModel {
+  /**
+   * The options used to initialize a SourcesModel object.
+   */
+  export interface IOptions {
+    /**
+     * Signal emitted when the current frame changes.
+     */
+    currentFrameChanged: ISignal<
+      IDebugger.Model.ICallstack,
+      IDebugger.IStackFrame | null
+    >;
+  }
+}

+ 16 - 1
packages/debugger/src/service.ts

@@ -851,6 +851,22 @@ export class DebuggerService implements IDebugger, IDisposable {
     this._model.variables.scopes = variableScopes;
   }
 
+  async displayModules(): Promise<void> {
+    if (!this.session) {
+      throw new Error('No active debugger session');
+    }
+
+    const modules = await this.session.sendRequest('modules', {});
+    this._model.kernelSources.kernelSources = modules.body.modules.map(
+      module => {
+        return {
+          name: Object.keys(module)[0],
+          path: Object.values(module)[0]
+        };
+      }
+    );
+  }
+
   /**
    * Handle a variable expanded event and request variables from the kernel.
    *
@@ -913,7 +929,6 @@ export class DebuggerService implements IDebugger, IDisposable {
     breakpoints: Map<string, IDebugger.IBreakpoint[]>
   ): Promise<void> {
     for (const [source, points] of breakpoints) {
-      console.log(source);
       await this._setBreakpoints(
         points
           .filter(({ line }) => typeof line === 'number')

+ 16 - 4
packages/debugger/src/session.ts

@@ -34,6 +34,13 @@ export class DebuggerSession implements IDebugger.ISession {
     return this._isDisposed;
   }
 
+  /**
+   * Returns the initialize response .
+   */
+  get capabilities(): DebugProtocol.Capabilities | undefined {
+    return this._capabilities;
+  }
+
   /**
    * A signal emitted when the debug session is disposed.
    */
@@ -138,7 +145,7 @@ export class DebuggerSession implements IDebugger.ISession {
    * Start a new debug session
    */
   async start(): Promise<void> {
-    const reply = await this.sendRequest('initialize', {
+    const initializeResponse = await this.sendRequest('initialize', {
       clientID: 'jupyterlab',
       clientName: 'JupyterLab',
       adapterID: this.connection?.kernel?.name ?? '',
@@ -151,11 +158,15 @@ export class DebuggerSession implements IDebugger.ISession {
       locale: document.documentElement.lang
     });
 
-    if (!reply.success) {
-      throw new Error(`Could not start the debugger: ${reply.message}`);
+    if (!initializeResponse.success) {
+      throw new Error(
+        `Could not start the debugger: ${initializeResponse.message}`
+      );
     }
+    this._capabilities = initializeResponse.body;
     this._isStarted = true;
-    this._exceptionBreakpointFilters = reply.body?.exceptionBreakpointFilters;
+    this._exceptionBreakpointFilters =
+      initializeResponse.body?.exceptionBreakpointFilters;
     await this.sendRequest('attach', {});
   }
 
@@ -245,6 +256,7 @@ export class DebuggerSession implements IDebugger.ISession {
   private _seq = 0;
   private _ready = new PromiseDelegate<void>();
   private _connection: Session.ISessionConnection | null;
+  private _capabilities: DebugProtocol.Capabilities | undefined;
   private _isDisposed = false;
   private _isStarted = false;
   private _pausingOnExceptions: string[] = [];

+ 14 - 0
packages/debugger/src/sidebar.ts

@@ -17,6 +17,8 @@ import { Callstack as CallstackPanel } from './panels/callstack';
 
 import { Sources as SourcesPanel } from './panels/sources';
 
+import { KernelSources as KernelSourcesPanel } from './panels/kernelSources';
+
 import { Variables as VariablesPanel } from './panels/variables';
 
 import { IDebugger } from './tokens';
@@ -74,6 +76,12 @@ export class DebuggerSidebar extends Panel implements IDebugger.ISidebar {
       translator
     });
 
+    this.kernelSources = new KernelSourcesPanel({
+      model: model.kernelSources,
+      service,
+      translator
+    });
+
     const header = new DebuggerSidebar.Header();
 
     this.addWidget(header);
@@ -90,6 +98,7 @@ export class DebuggerSidebar extends Panel implements IDebugger.ISidebar {
     this.addItem(this.callstack);
     this.addItem(this.breakpoints);
     this.addItem(this.sources);
+    this.addItem(this.kernelSources);
   }
 
   /**
@@ -162,6 +171,11 @@ export class DebuggerSidebar extends Panel implements IDebugger.ISidebar {
    */
   readonly sources: SourcesPanel;
 
+  /**
+   * The kernel sources widget.
+   */
+  readonly kernelSources: KernelSourcesPanel;
+
   /**
    * Container for debugger panels.
    */

+ 66 - 0
packages/debugger/src/tokens.ts

@@ -134,6 +134,11 @@ export interface IDebugger {
    */
   next(): Promise<void>;
 
+  /**
+   * Requests all the loaded modules and display them.
+   */
+  displayModules(): Promise<void>;
+
   /**
    * Restart the debugger.
    * Precondition: isStarted
@@ -243,6 +248,21 @@ export namespace IDebugger {
     breakpoints: Map<string, IDebugger.IBreakpoint[]>;
   };
 
+  /**
+   * The type for a kernel source file.
+   */
+  export type KernelSource = {
+    /**
+     * The name of the source.
+     */
+    name: string;
+
+    /**
+     * The path of the source.
+     */
+    path: string;
+  };
+
   /**
    * Debugger file and hashing configuration.
    */
@@ -306,6 +326,11 @@ export namespace IDebugger {
      */
     connection: Session.ISessionConnection | null;
 
+    /*
+     * Returns the initialize response .
+     */
+    readonly capabilities: DebugProtocol.Capabilities | undefined;
+
     /**
      * Whether the debug session is started.
      */
@@ -836,6 +861,11 @@ export namespace IDebugger {
        */
       readonly sources: ISources;
 
+      /**
+       * The kernel sources UI model.
+       */
+      readonly kernelSources: IKernelSources;
+
       /**
        * The set of threads in stopped state.
        */
@@ -896,6 +926,42 @@ export namespace IDebugger {
       open(): void;
     }
 
+    /**
+     * The kernel sources UI model.
+     */
+    export interface IKernelSources {
+      /**
+       * The kernel source.
+       */
+      kernelSources: IDebugger.KernelSource[] | null;
+
+      /**
+       * The filter to apply.
+       */
+      filter: string;
+
+      /**
+       * Signal emitted when the kernel sources have changed.
+       */
+      readonly changed: ISignal<
+        IDebugger.Model.IKernelSources,
+        IDebugger.KernelSource[] | null
+      >;
+
+      /**
+       * Signal emitted when a kernel source has be opened in the main area.
+       */
+      readonly kernelSourceOpened: ISignal<
+        IDebugger.Model.IKernelSources,
+        IDebugger.Source | null
+      >;
+
+      /**
+       * Open a source in the main area.
+       */
+      open(source: IDebugger.Source): void;
+    }
+
     /**
      * The variables UI model.
      */

+ 1 - 0
packages/debugger/style/base.css

@@ -8,6 +8,7 @@
 @import './icons.css';
 @import './sidebar.css';
 @import './sources.css';
+@import './kernelSources.css';
 @import './variables.css';
 
 .jp-left-truncated {

+ 6 - 0
packages/debugger/style/icons/open-kernel-source.svg

@@ -0,0 +1,6 @@
+<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+    <g class="jp-icon3" fill="#616161">
+        <path d="M5 2H15L20 7V20C20 20.5304 19.7893 21.0391 19.4142 21.4142C19.0391 21.7893 18.5304 22 18 22H5C4.46957 22 3.96086 21.7893 3.58579 21.4142C3.21071 21.0391 3 20.5304 3 20V14H4V16L8 13L4 10V12H3V4C3 3.46957 3.21071 2.96086 3.58579 2.58579C3.96086 2.21071 4.46957 2 5 2ZM12 18H16V16H12V18ZM12 14H18V12H12V14ZM12 10H18V8H12V10ZM10 14C10.5523 14 11 13.5523 11 13C11 12.4477 10.5523 12 10 12C9.44771 12 9 12.4477 9 13C9 13.5523 9.44771 14 10 14Z"/>
+        <path d="M3 12V14H1V13V12H3Z"/>
+    </g>
+</svg>

+ 42 - 0
packages/debugger/style/kernelSources.css

@@ -0,0 +1,42 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+.jp-DebuggerKernelSources {
+  min-height: 50px;
+  margin-top: 3px;
+}
+
+[data-jp-debugger='true'].jp-Editor .jp-mod-readOnly {
+  background: var(--jp-layout-color2);
+  height: 100%;
+}
+
+.jp-DebuggerKernelSources-body [data-jp-debugger='true'].jp-Editor {
+  height: 100%;
+}
+
+.jp-DebuggerKernelSources-body {
+  height: 100%;
+  overflow-y: auto;
+}
+
+.jp-DebuggerKernelSources-header > div > span {
+  overflow: hidden;
+  cursor: pointer;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  font-size: var(--jp-ui-font-size0);
+  color: var(--jp-ui-font-color1);
+}
+
+.jp-DebuggerKernelSource-filterBox {
+  padding: 0px;
+  flex: 0 0 auto;
+  margin: 0px 0px 0px 0px;
+}
+
+.jp-DebuggerKernelSource-filterBox-hidden {
+  display: none;
+}