diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 78b4137..e517187 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,71 +15,70 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 - - name: Base Setup - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - name: Install dependencies - run: python -m pip install -U "jupyterlab>=4.0.0,<5" + - name: Install dependencies + run: python -m pip install -U "jupyterlab>=4.0.0,<5" - - name: Lint the extension - run: | - set -eux - jlpm - jlpm run lint:check + - name: Lint the extension + run: | + set -eux + jlpm + jlpm run lint:check - - name: Build the extension - run: | - set -eux - python -m pip install .[test] + - name: Build the extension + run: | + set -eux + python -m pip install .[test] - jupyter labextension list - jupyter labextension list 2>&1 | grep -ie "jupyterlab-cell-diff.*OK" - python -m jupyterlab.browser_check + jupyter labextension list + jupyter labextension list 2>&1 | grep -ie "jupyterlab-cell-diff.*OK" + python -m jupyterlab.browser_check - - name: Package the extension - run: | - set -eux + - name: Package the extension + run: | + set -eux - pip install build - python -m build - pip uninstall -y "jupyterlab_cell_diff" jupyterlab + pip install build + python -m build + pip uninstall -y "jupyterlab_cell_diff" jupyterlab - - name: Upload extension packages - uses: actions/upload-artifact@v4 - with: - name: extension-artifacts - path: dist/jupyterlab_cell_diff* - if-no-files-found: error + - name: Upload extension packages + uses: actions/upload-artifact@v4 + with: + name: extension-artifacts + path: dist/jupyterlab_cell_diff* + if-no-files-found: error test_isolated: needs: build runs-on: ubuntu-latest steps: - - name: Install Python - uses: actions/setup-python@v5 - with: - python-version: '3.9' - architecture: 'x64' - - uses: actions/download-artifact@v4 - with: - name: extension-artifacts - - name: Install and Test - run: | - set -eux - # Remove NodeJS, twice to take care of system and locally installed node versions. - sudo rm -rf $(which node) - sudo rm -rf $(which node) - - pip install "jupyterlab>=4.0.0,<5" jupyterlab_cell_diff*.whl - - jupyter labextension list - jupyter labextension list 2>&1 | grep -ie "jupyterlab-cell-diff.*OK" - python -m jupyterlab.browser_check --no-browser-test - + - name: Install Python + uses: actions/setup-python@v5 + with: + python-version: '3.9' + architecture: 'x64' + - uses: actions/download-artifact@v4 + with: + name: extension-artifacts + - name: Install and Test + run: | + set -eux + # Remove NodeJS, twice to take care of system and locally installed node versions. + sudo rm -rf $(which node) + sudo rm -rf $(which node) + + pip install "jupyterlab>=4.0.0,<5" jupyterlab_cell_diff*.whl + + jupyter labextension list + jupyter labextension list 2>&1 | grep -ie "jupyterlab-cell-diff.*OK" + python -m jupyterlab.browser_check --no-browser-test check_links: name: Check Links diff --git a/.github/workflows/check-release.yml b/.github/workflows/check-release.yml index b13d506..fdfdf2b 100644 --- a/.github/workflows/check-release.yml +++ b/.github/workflows/check-release.yml @@ -1,9 +1,9 @@ name: Check Release on: push: - branches: ["main"] + branches: ['main'] pull_request: - branches: ["*"] + branches: ['*'] concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} @@ -23,7 +23,6 @@ jobs: - name: Check Release uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2 with: - token: ${{ secrets.GITHUB_TOKEN }} - name: Upload Distributions diff --git a/.github/workflows/prep-release.yml b/.github/workflows/prep-release.yml index 8eeaec8..5a63169 100644 --- a/.github/workflows/prep-release.yml +++ b/.github/workflows/prep-release.yml @@ -1,26 +1,26 @@ -name: "Step 1: Prep Release" +name: 'Step 1: Prep Release' on: workflow_dispatch: inputs: version_spec: - description: "New Version Specifier" - default: "next" + description: 'New Version Specifier' + default: 'next' required: false branch: - description: "The branch to target" + description: 'The branch to target' required: false post_version_spec: - description: "Post Version Specifier" + description: 'Post Version Specifier' required: false # silent: # description: "Set a placeholder in the changelog and don't publish the release." # required: false # type: boolean since: - description: "Use PRs with activity since this date or git reference" + description: 'Use PRs with activity since this date or git reference' required: false since_last_stable: - description: "Use PRs with activity since the last stable git tag" + description: 'Use PRs with activity since the last stable git tag' required: false type: boolean jobs: @@ -45,6 +45,6 @@ jobs: since: ${{ github.event.inputs.since }} since_last_stable: ${{ github.event.inputs.since_last_stable }} - - name: "** Next Step **" + - name: '** Next Step **' run: | echo "Optional): Review Draft Release: ${{ steps.prep-release.outputs.release_url }}" diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 3f3a8de..99edc0e 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -1,15 +1,15 @@ -name: "Step 2: Publish Release" +name: 'Step 2: Publish Release' on: workflow_dispatch: inputs: branch: - description: "The target branch" + description: 'The target branch' required: false release_url: - description: "The URL of the draft GitHub release" + description: 'The URL of the draft GitHub release' required: false steps_to_skip: - description: "Comma separated list of steps to skip" + description: 'Comma separated list of steps to skip' required: false jobs: @@ -47,13 +47,13 @@ jobs: token: ${{ steps.app-token.outputs.token }} release_url: ${{ steps.populate-release.outputs.release_url }} - - name: "** Next Step **" + - name: '** Next Step **' if: ${{ success() }} run: | echo "Verify the final release" echo ${{ steps.finalize-release.outputs.release_url }} - - name: "** Failure Message **" + - name: '** Failure Message **' if: ${{ failure() }} run: | echo "Failed to Publish the Draft Release Url:" diff --git a/README.md b/README.md index 1704e48..8232abc 100644 --- a/README.md +++ b/README.md @@ -32,25 +32,53 @@ jupyter labextension develop . --overwrite ### Commands -The extension provides a command to show cell diffs: +The extension provides commands to show diffs in multiple formats: -- `jupyterlab-cell-diff:show-codemirror` - Show diff using `@codemirror/merge` +- `jupyterlab-cell-diff:split-cell-diff` - Show cell diff using split view (side-by-side comparison) +- `jupyterlab-cell-diff:unified-cell-diff` - Show cell diff using unified view +- `jupyterlab-cell-diff:unified-file-diff` - Show file diff using unified view for regular Python files and other text files https://github.com/user-attachments/assets/0dacd7f0-5963-4ebe-81da-2958f0117071 ### Programmatic Usage +#### Split Cell Diff (Side-by-side View) + ```typescript -app.commands.execute('jupyterlab-cell-diff:show-codemirror', { +app.commands.execute('jupyterlab-cell-diff:split-cell-diff', { cellId: 'cell-id', originalSource: 'print("Hello")', - newSource: 'print("Hello, World!")' + newSource: 'print("Hello, World!")', + showActionButtons: true, + openDiff: true }); ``` -#### Command Arguments +#### Unified Cell Diff + +```typescript +app.commands.execute('jupyterlab-cell-diff:unified-cell-diff', { + cellId: 'cell-id', + originalSource: 'print("Hello")', + newSource: 'print("Hello, World!")', + showActionButtons: true +}); +``` -The `jupyterlab-cell-diff:show-codemirror` command accepts the following arguments: +#### Unified File Diff + +```typescript +app.commands.execute('jupyterlab-cell-diff:unified-file-diff', { + filePath: '/path/to/file.py', + originalSource: 'print("Hello")', + newSource: 'print("Hello, World!")', + showActionButtons: true +}); +``` + +### Command Arguments + +#### `jupyterlab-cell-diff:split-cell-diff` (Split View) | Argument | Type | Required | Description | | ------------------- | --------- | -------- | ------------------------------------------------------------------------------------ | @@ -61,6 +89,35 @@ The `jupyterlab-cell-diff:show-codemirror` command accepts the following argumen | `notebookPath` | `string` | No | Path to the notebook containing the cell. If not provided, uses the current notebook | | `openDiff` | `boolean` | No | Whether to open the diff widget automatically (default: `true`) | +#### `jupyterlab-cell-diff:unified-cell-diff` (Unified View) + +| Argument | Type | Required | Description | +| ------------------- | --------- | -------- | ------------------------------------------------------------------------------------ | +| `cellId` | `string` | No | ID of the cell to show diff for. If not provided, uses the active cell | +| `originalSource` | `string` | Yes | Original source code to compare against | +| `newSource` | `string` | Yes | New source code to compare with | +| `showActionButtons` | `boolean` | No | Whether to show action buttons for chunk acceptance (default: `true`) | +| `notebookPath` | `string` | No | Path to the notebook containing the cell. If not provided, uses the current notebook | + +#### `jupyterlab-cell-diff:unified-file-diff` (File Diff) + +| Argument | Type | Required | Description | +| ------------------- | --------- | -------- | --------------------------------------------------------------------- | +| `filePath` | `string` | No | Path to the file to diff. Defaults to current file in editor. | +| `originalSource` | `string` | Yes | Original source code to compare against | +| `newSource` | `string` | Yes | New source code to compare with | +| `showActionButtons` | `boolean` | No | Whether to show action buttons for chunk acceptance (default: `true`) | + +## Architecture + +### Diff Strategies + +The extension provides two diff viewing strategies: + +- **Split diff** (`split-cell-diff`): Uses CodeMirror's two-pane view. Displays original and modified code side-by-side in separate panels with diff highlighting. + +- **Unified diff** (`unified-cell-diff`/`unified-file-diff`): Uses CodeMirror's `unifiedMergeView`. Displays changes in a single unified view with added/removed lines clearly marked. Can be used for both cell diffs and regular file diffs. + ## Uninstall To remove the extension, execute: diff --git a/package.json b/package.json index 29156b9..f25c529 100644 --- a/package.json +++ b/package.json @@ -57,13 +57,15 @@ }, "dependencies": { "@codemirror/lang-python": "^6.2.1", - "@codemirror/merge": "^6.10.2", + "@codemirror/merge": "^6.11.0", "@codemirror/view": "^6.38.2", + "@jupyter/ydoc": "^3.1.0", "@jupyterlab/application": "^4.0.0", "@jupyterlab/cells": "^4.0.0", "@jupyterlab/codeeditor": "^4.0.0", "@jupyterlab/codemirror": "^4.4.7", "@jupyterlab/coreutils": "^6.0.0", + "@jupyterlab/fileeditor": "^4.0.0", "@jupyterlab/notebook": "^4.0.0", "@jupyterlab/services": "^7.0.0", "@jupyterlab/translation": "^4.0.0", diff --git a/src/diff/base-unified-diff.ts b/src/diff/base-unified-diff.ts new file mode 100644 index 0000000..aaff741 --- /dev/null +++ b/src/diff/base-unified-diff.ts @@ -0,0 +1,180 @@ +import { CodeMirrorEditor } from '@jupyterlab/codemirror'; +import { EditorView } from '@codemirror/view'; +import { Compartment } from '@codemirror/state'; +import { TranslationBundle } from '@jupyterlab/translation'; +import { ToolbarButton } from '@jupyterlab/ui-components'; +import { applyDiff } from './utils'; +import type { ISharedText } from '@jupyter/ydoc'; + +/** + * Base options for unified diff managers + */ +export interface IBaseUnifiedDiffOptions { + /** + * The code editor widget + */ + editor: CodeMirrorEditor; + + /** + * The original source code + */ + originalSource: string; + + /** + * The new/modified source code + */ + newSource: string; + + /** + * The translation bundle + */ + trans: TranslationBundle; + + /** + * Whether to show accept/reject buttons + */ + showActionButtons?: boolean; +} + +/** + * Base class for unified diff managers + */ +export abstract class BaseUnifiedDiffManager { + /** + * Construct a new BaseUnifiedDiffManager + */ + constructor(options: IBaseUnifiedDiffOptions) { + this.editor = options.editor; + this._originalSource = options.originalSource; + this._newSource = options.newSource; + this.trans = options.trans; + this.showActionButtons = options.showActionButtons ?? true; + this._isInitialized = false; + this._isDisposed = false; + this._diffCompartment = new Compartment(); + } + + /** + * Whether the manager is disposed + */ + get isDisposed(): boolean { + return this._isDisposed; + } + + /** + * Dispose of the manager and clean up resources + */ + dispose(): void { + if (this._isDisposed) { + return; + } + this._isDisposed = true; + this._deactivate(); + } + + /** + * Get the shared model for source manipulation + * Subclasses must implement this to return the appropriate shared model + */ + protected abstract getSharedModel(): ISharedText; + + /** + * Add toolbar buttons to the appropriate location + * Subclasses must implement this to add buttons to their specific UI location + */ + protected abstract addToolbarButtons(): void; + + /** + * Remove toolbar buttons from the appropriate location + * Subclasses must implement this to remove buttons from their specific UI location + */ + protected abstract removeToolbarButtons(): void; + + /** + * Activate the diff view + */ + protected activate(): void { + this._applyDiff(); + this.addToolbarButtons(); + } + + /** + * Deactivate the diff view + */ + private _deactivate(): void { + this.removeToolbarButtons(); + this._cleanupEditor(); + } + + /** + * Clean up the editor by removing the diff view + */ + private _cleanupEditor(): void { + const editorView = this._getEditorView(); + if (!editorView) { + return; + } + + editorView.dispatch({ + effects: [this._diffCompartment.reconfigure([])] + }); + } + + /** + * Accept all changes + */ + protected acceptAll(): void { + // simply accept the current state + this._deactivate(); + } + + /** + * Reject all changes + */ + protected rejectAll(): void { + const sharedModel = this.getSharedModel(); + sharedModel.setSource(this._originalSource); + this._deactivate(); + } + + /** + * Apply the diff to the editor + */ + private _applyDiff(): void { + const editorView = this._getEditorView(); + if (!editorView) { + console.warn('No editor view found for diff'); + return; + } + + applyDiff({ + editorView, + compartment: this._diffCompartment, + originalSource: this._originalSource, + newSource: this._newSource, + isInitialized: this._isInitialized, + sharedModel: this.getSharedModel(), + onChunkChange: () => this._deactivate() + }); + + this._isInitialized = true; + } + + /** + * Get the CodeMirror EditorView from the JupyterLab CodeMirrorEditor + */ + private _getEditorView(): EditorView | null { + return this.editor?.editor || null; + } + + protected editor: CodeMirrorEditor; + protected trans: TranslationBundle; + protected showActionButtons: boolean; + protected acceptAllButton: ToolbarButton | null = null; + protected rejectAllButton: ToolbarButton | null = null; + private _originalSource: string; + private _newSource: string; + private _isInitialized: boolean; + private _isDisposed: boolean; + private _diffCompartment: Compartment; +} diff --git a/src/diff/codemirror.ts b/src/diff/cell.ts similarity index 70% rename from src/diff/codemirror.ts rename to src/diff/cell.ts index 18231e1..4265a9b 100644 --- a/src/diff/codemirror.ts +++ b/src/diff/cell.ts @@ -8,17 +8,17 @@ import { basicSetup } from 'codemirror'; import { IDiffWidgetOptions, BaseDiffWidget } from '../widget'; /** - * A Lumino widget that contains a CodeMirror diff view + * A Lumino widget that contains a CodeMirror split view (side-by-side comparison) */ -class CodeMirrorDiffWidget extends BaseDiffWidget { +class CodeMirrorSplitDiffWidget extends BaseDiffWidget { /** - * Construct a new CodeMirrorDiffWidget. + * Construct a new CodeMirrorSplitDiffWidget. */ constructor(options: IDiffWidgetOptions) { super(options); this._originalCode = options.originalSource; this._modifiedCode = options.newSource; - this.addClass('jp-DiffView'); + this.addClass('jp-SplitDiffView'); } /** @@ -26,26 +26,26 @@ class CodeMirrorDiffWidget extends BaseDiffWidget { */ protected onAfterAttach(msg: Message): void { super.onAfterAttach(msg); - this._createMergeView(); + this._createSplitView(); } /** * Handle before-detach messages for the widget. */ protected onBeforeDetach(msg: Message): void { - this._destroyMergeView(); + this._destroySplitView(); super.onBeforeDetach(msg); } /** - * Create the merge view with CodeMirror diff functionality. + * Create the split view with CodeMirror diff functionality. */ - private _createMergeView(): void { - if (this._mergeView) { + private _createSplitView(): void { + if (this._splitView) { return; } - this._mergeView = new MergeView({ + this._splitView = new MergeView({ a: { doc: this._originalCode, extensions: [ @@ -69,21 +69,21 @@ class CodeMirrorDiffWidget extends BaseDiffWidget { } /** - * Destroy the merge view and clean up resources. + * Destroy the split view and clean up resources. */ - private _destroyMergeView(): void { - if (this._mergeView) { - this._mergeView.destroy(); - this._mergeView = null; + private _destroySplitView(): void { + if (this._splitView) { + this._splitView.destroy(); + this._splitView = null; } } private _originalCode: string; private _modifiedCode: string; - private _mergeView: MergeView | null = null; + private _splitView: MergeView | null = null; } -export async function createCodeMirrorDiffWidget( +export async function createCodeMirrorSplitDiffWidget( options: IDiffWidgetOptions ): Promise { const { @@ -96,7 +96,7 @@ export async function createCodeMirrorDiffWidget( openDiff = true } = options; - const diffWidget = new CodeMirrorDiffWidget({ + const diffWidget = new CodeMirrorSplitDiffWidget({ originalSource, newSource, cell, diff --git a/src/diff/unified-cell.ts b/src/diff/unified-cell.ts new file mode 100644 index 0000000..b30453e --- /dev/null +++ b/src/diff/unified-cell.ts @@ -0,0 +1,120 @@ +import { ICellModel } from '@jupyterlab/cells'; +import { checkIcon, ToolbarButton, undoIcon } from '@jupyterlab/ui-components'; +import { ICellFooterTracker } from 'jupyterlab-cell-input-footer'; +import { + BaseUnifiedDiffManager, + IBaseUnifiedDiffOptions +} from './base-unified-diff'; +import type { ISharedText } from '@jupyter/ydoc'; + +/** + * Options for creating a unified diff view for a cell + */ +export interface IUnifiedCellDiffOptions extends IBaseUnifiedDiffOptions { + /** + * The cell to show the diff for + */ + cell: ICellModel; + + /** + * The cell footer tracker + */ + cellFooterTracker?: ICellFooterTracker; +} + +/** + * Manages unified diff view directly in cell editors + */ +export class UnifiedCellDiffManager extends BaseUnifiedDiffManager { + /** + * Construct a new UnifiedCellDiffManager + */ + constructor(options: IUnifiedCellDiffOptions) { + super(options); + this._cell = options.cell; + this._cellFooterTracker = options.cellFooterTracker; + this.activate(); + } + + /** + * Get the shared model for source manipulation + */ + protected getSharedModel(): ISharedText { + return this._cell.sharedModel; + } + + /** + * Add toolbar buttons to the cell footer + */ + protected addToolbarButtons(): void { + if (!this._cellFooterTracker || !this._cell) { + return; + } + + const cellId = this._cell.id; + const footer = this._cellFooterTracker.getFooter(cellId); + if (!footer) { + return; + } + + this.acceptAllButton = new ToolbarButton({ + icon: checkIcon, + label: this.trans.__('Accept All'), + tooltip: this.trans.__('Accept all chunks'), + enabled: true, + className: 'jp-UnifiedDiff-acceptAll', + onClick: () => this.acceptAll() + }); + + this.rejectAllButton = new ToolbarButton({ + icon: undoIcon, + label: this.trans.__('Reject All'), + tooltip: this.trans.__('Reject all chunks'), + enabled: true, + className: 'jp-UnifiedDiff-rejectAll', + onClick: () => this.rejectAll() + }); + + if (this.showActionButtons) { + footer.addToolbarItemOnRight('reject-all', this.rejectAllButton); + footer.addToolbarItemOnRight('accept-all', this.acceptAllButton); + } + + this._cellFooterTracker.showFooter(cellId); + } + + /** + * Remove toolbar buttons from the cell footer + */ + protected removeToolbarButtons(): void { + if (!this._cellFooterTracker || !this._cell) { + return; + } + + const cellId = this._cell.id; + const footer = this._cellFooterTracker.getFooter(cellId); + if (!footer) { + return; + } + + if (this.showActionButtons) { + footer.removeToolbarItem('accept-all'); + footer.removeToolbarItem('reject-all'); + } + + // Hide the footer if no other items remain + this._cellFooterTracker.hideFooter(cellId); + } + + private _cell: ICellModel; + private _cellFooterTracker?: ICellFooterTracker; +} + +/** + * Create a unified diff view for a cell + */ +export async function createUnifiedCellDiffView( + options: IUnifiedCellDiffOptions +): Promise { + return new UnifiedCellDiffManager(options); +} diff --git a/src/diff/unified-file.ts b/src/diff/unified-file.ts new file mode 100644 index 0000000..4677465 --- /dev/null +++ b/src/diff/unified-file.ts @@ -0,0 +1,141 @@ +import { IDocumentWidget } from '@jupyterlab/docregistry'; +import { FileEditor } from '@jupyterlab/fileeditor'; +import { + checkIcon, + Toolbar, + ToolbarButton, + undoIcon +} from '@jupyterlab/ui-components'; +import { Widget } from '@lumino/widgets'; +import { + BaseUnifiedDiffManager, + IBaseUnifiedDiffOptions +} from './base-unified-diff'; +import type { ISharedText } from '@jupyter/ydoc'; + +/** + * Options for applying a unified diff to a file editor + */ +export interface IUnifiedFileDiffOptions extends IBaseUnifiedDiffOptions { + /** + * The file editor widget (IDocumentWidget containing the editor) + */ + fileEditorWidget?: IDocumentWidget; +} + +/** + * Manages unified file diffs in the editor using CodeMirror compartments + */ +export class UnifiedFileDiffManager extends BaseUnifiedDiffManager { + /** + * Construct a new UnifiedFileDiffManager + */ + constructor(options: IUnifiedFileDiffOptions) { + super(options); + this._fileEditorWidget = options.fileEditorWidget; + this.activate(); + } + + /** + * Get the shared model for source manipulation + */ + protected getSharedModel(): ISharedText { + return this.editor.model.sharedModel; + } + + /** + * Add toolbar buttons to the file editor toolbar + */ + protected addToolbarButtons(): void { + if (!this._fileEditorWidget || !this.showActionButtons) { + return; + } + + const toolbar = this._fileEditorWidget.toolbar; + if (!toolbar) { + return; + } + + // Show the toolbar + toolbar.node.hidden = false; + + // Create a spacer to push buttons to the right + this._spacer = Toolbar.createSpacerItem(); + + // Accept all button + this.acceptAllButton = new ToolbarButton({ + icon: checkIcon, + label: this.trans.__('Accept All'), + tooltip: this.trans.__('Accept all chunks'), + enabled: true, + className: 'jp-UnifiedFileDiff-acceptAll', + onClick: () => this.acceptAll() + }); + + // Reject all button + this.rejectAllButton = new ToolbarButton({ + icon: undoIcon, + label: this.trans.__('Reject All'), + tooltip: this.trans.__('Reject all chunks'), + enabled: true, + className: 'jp-UnifiedFileDiff-rejectAll', + onClick: () => this.rejectAll() + }); + + toolbar.addItem('diff-spacer', this._spacer); + toolbar.addItem('reject-all-diff', this.rejectAllButton); + toolbar.addItem('accept-all-diff', this.acceptAllButton); + } + + /** + * Remove toolbar buttons from the file editor toolbar + */ + protected removeToolbarButtons(): void { + if (!this._fileEditorWidget) { + return; + } + + const toolbar = this._fileEditorWidget.toolbar; + if (!toolbar) { + return; + } + + // Remove and dispose items only if they were added + if (this.showActionButtons) { + // Dispose of the spacer + if (this._spacer) { + this._spacer.dispose(); + this._spacer = null; + } + + // Dispose of the buttons + if (this.acceptAllButton) { + this.acceptAllButton.dispose(); + this.acceptAllButton = null; + } + if (this.rejectAllButton) { + this.rejectAllButton.dispose(); + this.rejectAllButton = null; + } + } + + // Check if there are any remaining items in the toolbar + // If not, hide the toolbar + const remainingItems = Array.from(toolbar.names()); + if (remainingItems.length === 0) { + toolbar.node.hidden = true; + } + } + + private _fileEditorWidget?: IDocumentWidget; + private _spacer: Widget | null = null; +} + +/** + * Create a unified diff view for a file editor + */ +export async function createUnifiedFileDiff( + options: IUnifiedFileDiffOptions +): Promise { + return new UnifiedFileDiffManager(options); +} diff --git a/src/diff/utils.ts b/src/diff/utils.ts new file mode 100644 index 0000000..61216f3 --- /dev/null +++ b/src/diff/utils.ts @@ -0,0 +1,146 @@ +import { EditorView } from '@codemirror/view'; +import { Extension, Compartment, StateEffect } from '@codemirror/state'; +import { unifiedMergeView, getChunks } from '@codemirror/merge'; +import { checkIcon, undoIcon } from '@jupyterlab/ui-components'; +import { ISharedText } from '@jupyter/ydoc'; + +/** + * Render a custom merge button with JupyterLab icons + */ +export function renderMergeButton( + type: 'accept' | 'reject', + action: (e: MouseEvent) => void +): HTMLElement { + const button = document.createElement('button'); + button.className = `jp-merge-${type}-button`; + button.onclick = (e: MouseEvent) => { + e.preventDefault(); + action(e); + }; + + const icon = type === 'accept' ? checkIcon : undoIcon; + const iconElement = icon.element({ + tag: 'span', + elementSize: 'small' + }); + + button.appendChild(iconElement); + return button; +} + +/** + * Create a merge extension with the given options + * + * @param originalSource The original source to compare against + * @param options Additional options for the merge view + */ +export function createMergeExtension( + originalSource: string, + options?: Record +): Extension { + return unifiedMergeView({ + original: originalSource, + ...options, + // TODO: make configurable + // allowInlineDiffs: true, + mergeControls: ( + type: 'accept' | 'reject', + action: (e: MouseEvent) => void + ) => { + return renderMergeButton(type, action); + } + }); +} + +/** + * Check if all chunks have been resolved + */ +export function hasRemainingChunks(editorView: EditorView): boolean { + const chunksInfo = getChunks(editorView.state); + return !!(chunksInfo && chunksInfo.chunks.length > 0); +} + +/** + * Options for applying a diff to an editor + */ +export interface IApplyDiffOptions { + /** + * The CodeMirror editor view + */ + editorView: EditorView; + + /** + * The compartment for the diff extensions + */ + compartment: Compartment; + + /** + * The original source to compare against + */ + originalSource: string; + + /** + * The new source to show + */ + newSource: string; + + /** + * Whether the diff has been initialized before + */ + isInitialized: boolean; + + /** + * The shared text model + */ + sharedModel: ISharedText; + + /** + * Optional callback when chunks are resolved + */ + onChunkChange?: () => void; +} + +/** + * Apply a diff to an editor with automatic chunk cleanup + */ +export function applyDiff(options: IApplyDiffOptions): void { + const { + editorView, + compartment, + originalSource, + newSource, + isInitialized, + sharedModel, + onChunkChange + } = options; + + const mergeExtension = createMergeExtension(originalSource); + + // Create an update listener to track chunk resolution + const updateListener = EditorView.updateListener.of(update => { + if (update.transactions.length > 0) { + if (onChunkChange && !hasRemainingChunks(editorView)) { + onChunkChange(); + } + } + }); + + // Bundle both the merge extension and update listener in the compartment + // This ensures they're managed together and properly cleaned up + const bundledExtensions = [mergeExtension, updateListener]; + const effects: StateEffect[] = []; + + if (!isInitialized) { + // First time: add compartment with bundled extensions + effects.push( + StateEffect.appendConfig.of(compartment.of(bundledExtensions)) + ); + } else { + // Subsequent times: reconfigure compartment with new bundled extensions + // This replaces the old extensions (including the old listener) cleanly + effects.push(compartment.reconfigure(bundledExtensions)); + } + + sharedModel.setSource(newSource); + editorView.dispatch({ effects }); +} diff --git a/src/plugin.ts b/src/plugin.ts index ed48514..3998f2d 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -5,10 +5,20 @@ import { import { ICellModel } from '@jupyterlab/cells'; import { INotebookTracker, NotebookPanel } from '@jupyterlab/notebook'; import { ITranslator, nullTranslator } from '@jupyterlab/translation'; +import { IEditorTracker } from '@jupyterlab/fileeditor'; import { ICellFooterTracker } from 'jupyterlab-cell-input-footer'; import { IDiffWidgetOptions } from './widget'; -import { createCodeMirrorDiffWidget } from './diff/codemirror'; +import { createCodeMirrorSplitDiffWidget } from './diff/cell'; +import { + createUnifiedCellDiffView, + UnifiedCellDiffManager +} from './diff/unified-cell'; +import { + createUnifiedFileDiff, + UnifiedFileDiffManager +} from './diff/unified-file'; +import { CodeMirrorEditor } from '@jupyterlab/codemirror'; /** * The translation namespace for the plugin. @@ -54,11 +64,11 @@ export function findCell( } /** - * CodeMirror diff plugin + * Split cell diff plugin - shows side-by-side comparison */ -const codeMirrorPlugin: JupyterFrontEndPlugin = { - id: 'jupyterlab-cell-diff:codemirror-plugin', - description: 'Expose a command to show cell diffs using CodeMirror', +const splitCellDiffPlugin: JupyterFrontEndPlugin = { + id: 'jupyterlab-cell-diff:split-cell-diff-plugin', + description: 'Show cell diff using side-by-side split view', requires: [ICellFooterTracker, INotebookTracker], optional: [ITranslator], autoStart: true, @@ -71,8 +81,8 @@ const codeMirrorPlugin: JupyterFrontEndPlugin = { const { commands } = app; const trans = (translator ?? nullTranslator).load(TRANSLATION_NAMESPACE); - commands.addCommand('jupyterlab-cell-diff:show-codemirror', { - label: trans.__('Show Cell Diff (CodeMirror)'), + commands.addCommand('jupyterlab-cell-diff:split-cell-diff', { + label: trans.__('Show Cell Diff (Split View)'), describedBy: { args: { type: 'object', @@ -147,24 +157,258 @@ const codeMirrorPlugin: JupyterFrontEndPlugin = { return; } - try { - const options: IDiffWidgetOptions = { - cell, - cellFooterTracker, - originalSource, - newSource, - showActionButtons, - openDiff, - trans - }; + const options: IDiffWidgetOptions = { + cell, + cellFooterTracker, + originalSource, + newSource, + showActionButtons, + openDiff, + trans + }; + + await createCodeMirrorSplitDiffWidget(options); + } + }); + } +}; + +/** + * Unified cell diff plugin + */ +const unifiedCellDiffPlugin: JupyterFrontEndPlugin = { + id: 'jupyterlab-cell-diff:unified-cell-diff-plugin', + description: 'Show cell diff using unified view', + requires: [ICellFooterTracker, INotebookTracker], + optional: [ITranslator], + autoStart: true, + activate: async ( + app: JupyterFrontEnd, + cellFooterTracker: ICellFooterTracker, + notebookTracker: INotebookTracker, + translator: ITranslator | null + ) => { + const { commands } = app; + const trans = (translator ?? nullTranslator).load(TRANSLATION_NAMESPACE); + + // Track active unified diff managers to avoid creating duplicates + const cellDiffManagers = new Map(); + + commands.addCommand('jupyterlab-cell-diff:unified-cell-diff', { + label: trans.__('Show Cell Diff (Unified)'), + describedBy: { + args: { + type: 'object', + properties: { + cellId: { + type: 'string', + description: trans.__('ID of the cell to show diff for') + }, + originalSource: { + type: 'string', + description: trans.__('Original source code to compare against') + }, + newSource: { + type: 'string', + description: trans.__('New source code to compare with') + }, + showActionButtons: { + type: 'boolean', + description: trans.__( + 'Whether to show action buttons for chunk acceptance' + ) + }, + notebookPath: { + type: 'string', + description: trans.__('Path to the notebook containing the cell') + } + }, + required: ['originalSource', 'newSource'] + } + }, + execute: async (args: any = {}) => { + const { + cellId, + originalSource, + newSource, + showActionButtons = true, + notebookPath + } = args; + + if (!originalSource || !newSource) { + console.error( + trans.__('Missing required arguments: originalSource and newSource') + ); + return; + } - await createCodeMirrorDiffWidget(options); - } catch (error) { - console.error(trans.__('Failed to create diff widget: %1'), error); + const currentNotebook = findNotebook(notebookTracker, notebookPath); + if (!currentNotebook) { + return; } + + const cell = findCell(currentNotebook, cellId); + if (!cell) { + console.error( + trans.__( + 'Missing required arguments: cellId (or no active cell found)' + ) + ); + return; + } + + // Get the cell widget that corresponds to the found cell + const cellWidget = currentNotebook.content.widgets.find( + widget => widget.model.id === cell.id + ); + if (!cellWidget || !cellWidget.editor) { + console.error(trans.__('No editor found for cell %1', cell.id)); + return; + } + + // Dispose any existing manager for this cell + const existingManager = cellDiffManagers.get(cell.id); + if (existingManager && !existingManager.isDisposed) { + existingManager.dispose(); + } + + // Create a new manager + const manager = await createUnifiedCellDiffView({ + cell, + editor: cellWidget.editor as CodeMirrorEditor, + cellFooterTracker, + originalSource, + newSource, + showActionButtons, + trans + }); + cellDiffManagers.set(cell.id, manager); + } + }); + } +}; + +/** + * Unified file diff plugin + */ +const unifiedFileDiffPlugin: JupyterFrontEndPlugin = { + id: 'jupyterlab-cell-diff:unified-file-diff-plugin', + description: 'Show file diff using unified view', + requires: [IEditorTracker], + optional: [ITranslator], + autoStart: true, + activate: async ( + app: JupyterFrontEnd, + editorTracker: IEditorTracker, + translator: ITranslator | null + ) => { + const { commands } = app; + const trans = (translator ?? nullTranslator).load(TRANSLATION_NAMESPACE); + + // Track active unified diff managers to avoid creating duplicates + const fileDiffManagers = new Map(); + + commands.addCommand('jupyterlab-cell-diff:unified-file-diff', { + label: trans.__('Diff File (Unified)'), + describedBy: { + args: { + type: 'object', + properties: { + filePath: { + type: 'string', + description: trans.__( + 'Path to the file to diff. Defaults to current file in editor.' + ) + }, + originalSource: { + type: 'string', + description: trans.__('Original source code to compare against') + }, + newSource: { + type: 'string', + description: trans.__('New source code to compare with') + }, + showActionButtons: { + type: 'boolean', + description: trans.__( + 'Whether to show action buttons for chunk acceptance. Defaults to true.' + ) + } + }, + required: ['originalSource', 'newSource'] + } + }, + execute: async (args: any = {}) => { + const { + filePath, + originalSource, + newSource, + showActionButtons = true + } = args; + + if (!originalSource || !newSource) { + console.error( + trans.__('Missing required arguments: originalSource and newSource') + ); + return; + } + + // Try to find the file editor widget by its filepath using IEditorTracker + let fileEditorWidget = editorTracker.currentWidget; + if (filePath) { + // Search through all open file editors in the tracker + const fileEditors = editorTracker.find(widget => { + return widget.context?.path === filePath; + }); + if (fileEditors) { + fileEditorWidget = fileEditors; + } + } + + // If no specific file editor found, try to get the current widget from the tracker + if (!fileEditorWidget) { + fileEditorWidget = editorTracker.currentWidget; + } + + if (!fileEditorWidget) { + console.error(trans.__('No editor found for the file')); + return; + } + + // Try to get the editor from the file editor widget + const editor = fileEditorWidget.content.editor as CodeMirrorEditor; + if (!editor) { + console.error(trans.__('No code editor found in the file widget')); + return; + } + + // Use the file path as the key, or a default key if not available + const managerKey = + filePath || fileEditorWidget.context?.path || 'default'; + + // Dispose any existing manager for this file + const existingManager = fileDiffManagers.get(managerKey); + if (existingManager && !existingManager.isDisposed) { + existingManager.dispose(); + } + + // Create a new manager + const manager = await createUnifiedFileDiff({ + editor, + fileEditorWidget, + originalSource, + newSource, + showActionButtons, + trans + }); + fileDiffManagers.set(managerKey, manager); } }); } }; -export default [codeMirrorPlugin]; +export default [ + splitCellDiffPlugin, + unifiedCellDiffPlugin, + unifiedFileDiffPlugin +]; diff --git a/src/widget.ts b/src/widget.ts index 176b1c6..0e927d5 100644 --- a/src/widget.ts +++ b/src/widget.ts @@ -3,6 +3,7 @@ import { TranslationBundle } from '@jupyterlab/translation'; import { checkIcon, ToolbarButton, undoIcon } from '@jupyterlab/ui-components'; import { Widget } from '@lumino/widgets'; import { ICellFooterTracker } from 'jupyterlab-cell-input-footer'; +import { CellFooterWidget } from 'jupyterlab-cell-input-footer/lib/widget'; /** * Options for creating a diff widget @@ -128,7 +129,7 @@ export abstract class BaseDiffWidget extends Widget { /** * Create accept, reject, and toggle buttons for the footer toolbar. */ - private _createButtons(footer: any): void { + private _createButtons(footer: CellFooterWidget): void { this._toggleButton = new ToolbarButton({ label: this._trans.__('Compare changes'), tooltip: this._trans.__('Compare changes'), @@ -187,12 +188,12 @@ export abstract class BaseDiffWidget extends Widget { } } - protected _cell: ICellModel; - protected _cellFooterTracker: ICellFooterTracker; - protected _originalSource: string; - protected _newSource: string; - protected _showActionButtons: boolean; - protected _openDiff: boolean; - protected _toggleButton: ToolbarButton | null = null; + private _cell: ICellModel; + private _cellFooterTracker: ICellFooterTracker; + private _originalSource: string; + private _newSource: string; + private _showActionButtons: boolean; + private _openDiff: boolean; + private _toggleButton: ToolbarButton | null = null; private _trans: TranslationBundle; } diff --git a/style/base.css b/style/base.css index 1ef8594..82fd63e 100644 --- a/style/base.css +++ b/style/base.css @@ -4,32 +4,76 @@ https://jupyterlab.readthedocs.io/en/stable/developer/css.html */ -/* CodeMirror merge view styling */ -.jp-cellfooter .cm-merge-2pane { - display: grid; - padding: 0; +/* Unified merge view styling */ +.cm-unified { + background-color: var(--jp-layout-color0); + color: var(--jp-ui-font-color1); +} - /* editor | gap | editor */ - grid-template-columns: 49% 2% 49%; - grid-auto-rows: minmax(18px, auto); +/* Removed lines in unified diff */ +.cm-deletedLine { + background-color: rgb(255 0 0 / 10%); } -/* Diff widget styling */ -.jp-DiffView { - background-color: var(--jp-layout-color0); +.cm-deletedChunk { + background-color: rgb(255 0 0 / 15%); +} + +/* Added lines in unified diff */ +.cm-addedLine { + background-color: rgb(0 128 0 / 10%); +} + +.cm-addedChunk { + background-color: rgb(0 128 0 / 15%); } -.jp-DiffView .cm-merge-2pane { - min-height: 100px; - max-height: 400px; - overflow: auto; +/* Unchanged lines */ +.cm-unchangedLine { + background-color: transparent; } -.jp-DiffView .cm-activeLineGutter { +/* Conflict markers */ +.cm-conflictMarker { background-color: var(--jp-layout-color3); - color: var(--jp-ui-font-color1); + color: var(--jp-error-color0); } -.jp-DiffView .cm-lineNumbers .cm-gutterElement { - color: var(--jp-ui-font-color2); +/* Custom merge control styling with JupyterLab icons */ +.cm-chunkButtons { + display: flex; + flex-direction: row-reverse; + gap: 2px; + padding: 0; + line-height: 1; +} + +/* Style our custom JupyterLab icon buttons to match CodeMirror line height */ +.jp-merge-accept-button, +.jp-merge-reject-button { + cursor: pointer; + padding: 1px; + border-radius: 2px; + background-color: transparent; + border: 1px solid transparent; + display: inline-flex; + align-items: center; + justify-content: center; + transition: background-color 0.1s; + height: 1.4em; + width: auto; + vertical-align: middle; +} + +/* Make the icon smaller to fit the line height */ +.jp-merge-accept-button svg, +.jp-merge-reject-button svg { + width: 14px; + height: 14px; +} + +.jp-merge-accept-button:hover, +.jp-merge-reject-button:hover { + background-color: var(--jp-layout-color3); + border-color: var(--jp-border-color1); } diff --git a/ui-tests/tests/split-cell-diff.spec.ts b/ui-tests/tests/split-cell-diff.spec.ts index c69ed8d..cc49678 100644 --- a/ui-tests/tests/split-cell-diff.spec.ts +++ b/ui-tests/tests/split-cell-diff.spec.ts @@ -4,7 +4,7 @@ import { NotebookPanel } from '@jupyterlab/notebook'; /** * The command to open the cell split diff view. */ -const SPLIT_CELL_DIFF_COMMAND = 'jupyterlab-cell-diff:show-codemirror'; +const SPLIT_CELL_DIFF_COMMAND = 'jupyterlab-cell-diff:split-cell-diff'; /** * Setup a notebook cell with diff view. diff --git a/ui-tests/tests/unified-cell-diff.spec.ts b/ui-tests/tests/unified-cell-diff.spec.ts new file mode 100644 index 0000000..3fd9289 --- /dev/null +++ b/ui-tests/tests/unified-cell-diff.spec.ts @@ -0,0 +1,96 @@ +import { expect, IJupyterLabPageFixture, test } from '@jupyterlab/galata'; +import { NotebookPanel } from '@jupyterlab/notebook'; + +/** + * The command to open the cell unified diff view. + */ +const UNIFIED_CELL_DIFF_COMMAND = 'jupyterlab-cell-diff:unified-cell-diff'; + +/** + * Setup a notebook cell with unified diff view. + */ +async function setupCellWithUnifiedDiff( + page: IJupyterLabPageFixture, + originalSource: string, + newSource: string +) { + await page.notebook.createNew(); + await page.notebook.setCell(0, 'code', originalSource); + + await page.evaluate( + async ({ originalSource, newSource, command }) => { + await window.jupyterapp.commands.execute(command, { + originalSource, + newSource, + showActionButtons: true + }); + }, + { originalSource, newSource, command: UNIFIED_CELL_DIFF_COMMAND } + ); +} + +/** + * Get the content of the first cell in the active notebook. + * + * TODO: use getCellTextInput from galata? + * See https://github.com/jupyterlab/jupyterlab/blob/1abbdf39fb204e47941e8d8021d85366a0ecece9/galata/src/helpers/notebook.ts#L677-L707 + */ +async function getCellContent(page: IJupyterLabPageFixture): Promise { + return await page.evaluate(() => { + const nbPanel = window.jupyterapp.shell.currentWidget as NotebookPanel; + return nbPanel.content.widgets[0].model.sharedModel.getSource(); + }); +} + +test.describe('Unified Cell Diff Extension', () => { + test.beforeEach(async ({ page }) => { + await page.sidebar.close(); + }); + + test('should show unified diff with action buttons', async ({ page }) => { + const originalSource = 'print("Hello, World!")'; + const newSource = 'print("Hello, JupyterLab!")\nprint("Testing diffs")'; + + await setupCellWithUnifiedDiff(page, originalSource, newSource); + + const acceptButton = page.getByRole('button', { name: 'Accept All' }); + await expect(acceptButton).toBeVisible(); + + const rejectButton = page.getByRole('button', { name: 'Reject All' }); + await expect(rejectButton).toBeVisible(); + }); + + test('should accept all changes when the accept button is clicked', async ({ + page + }) => { + const originalSource = 'x = 1'; + const newSource = 'x = 2'; + + await setupCellWithUnifiedDiff(page, originalSource, newSource); + + const acceptButton = page.getByText('Accept All'); + await acceptButton.click(); + + await expect(acceptButton).not.toBeVisible(); + + const cellContent = await getCellContent(page); + expect(cellContent).toBe(newSource); + }); + + test('should reject all changes when the reject button is clicked', async ({ + page + }) => { + const originalSource = 'y = 10'; + const newSource = 'y = 20'; + + await setupCellWithUnifiedDiff(page, originalSource, newSource); + + const rejectButton = page.getByText('Reject All'); + await rejectButton.click(); + + await expect(rejectButton).not.toBeVisible(); + + const cellContent = await getCellContent(page); + expect(cellContent).toBe(originalSource); + }); +}); diff --git a/ui-tests/tests/unified-file-diff.spec.ts b/ui-tests/tests/unified-file-diff.spec.ts new file mode 100644 index 0000000..0639e7e --- /dev/null +++ b/ui-tests/tests/unified-file-diff.spec.ts @@ -0,0 +1,98 @@ +import { expect, IJupyterLabPageFixture, test } from '@jupyterlab/galata'; + +/** + * The command to open the file unified diff view. + */ +const UNIFIED_FILE_DIFF_COMMAND = 'jupyterlab-cell-diff:unified-file-diff'; + +/** + * Default name for new Python files. + */ +const DEFAULT_NAME = 'untitled.py'; + +/** + * Setup a file editor with unified diff view. + */ +async function setupFileWithUnifiedDiff( + page: IJupyterLabPageFixture, + originalSource: string, + newSource: string +) { + await page.evaluate( + async ({ originalSource, newSource, command }) => { + await window.jupyterapp.commands.execute(command, { + originalSource, + newSource, + showActionButtons: true + }); + }, + { originalSource, newSource, command: UNIFIED_FILE_DIFF_COMMAND } + ); +} + +/** + * Get the content of the current file in the editor. + * + * TODO: follow the same approach as in JupyterLab, with getEditorText? + * See https://github.com/jupyterlab/jupyterlab/blob/1abbdf39fb204e47941e8d8021d85366a0ecece9/galata/test/jupyterlab/file-edit.test.ts#L73-L85 + */ +async function getFileContent(page: IJupyterLabPageFixture): Promise { + return await page.evaluate(() => { + const widget = window.jupyterapp.shell.currentWidget; + if (widget && 'content' in widget) { + const editor = (widget as any).content.editor; + return editor.model.sharedModel.getSource(); + } + return ''; + }); +} + +test.describe('Unified File Diff Extension', () => { + test.beforeEach(async ({ page }) => { + await page.menu.clickMenuItem('File>New>Python File'); + await page.sidebar.close(); + }); + + test('should show unified diff with action buttons', async ({ page }) => { + const originalSource = 'print("Hello, World!")'; + const newSource = 'print("Hello, JupyterLab!")\nprint("Testing diffs")'; + + await setupFileWithUnifiedDiff(page, originalSource, newSource); + + const acceptButton = page.getByRole('button', { name: 'Accept All' }); + await expect(acceptButton).toBeVisible(); + + const rejectButton = page.getByRole('button', { name: 'Reject All' }); + await expect(rejectButton).toBeVisible(); + }); + + test('should accept all changes when accept button is clicked', async ({ + page + }) => { + const originalSource = 'x = 1'; + const newSource = 'x = 2'; + + await setupFileWithUnifiedDiff(page, originalSource, newSource); + + const acceptButton = page.getByText('Accept All'); + await acceptButton.click(); + + const fileContent = await getFileContent(page); + expect(fileContent).toBe(newSource); + }); + + test('should reject all changes when reject button is clicked', async ({ + page + }) => { + const originalSource = 'y = 10'; + const newSource = 'y = 20'; + + await setupFileWithUnifiedDiff(page, originalSource, newSource); + + const rejectButton = page.getByText('Reject All'); + await rejectButton.click(); + + const fileContent = await getFileContent(page); + expect(fileContent).toBe(originalSource); + }); +}); diff --git a/yarn.lock b/yarn.lock index cc47d8e..e662989 100644 --- a/yarn.lock +++ b/yarn.lock @@ -247,16 +247,16 @@ __metadata: languageName: node linkType: hard -"@codemirror/merge@npm:^6.10.2": - version: 6.10.2 - resolution: "@codemirror/merge@npm:6.10.2" +"@codemirror/merge@npm:^6.11.0": + version: 6.11.0 + resolution: "@codemirror/merge@npm:6.11.0" dependencies: "@codemirror/language": ^6.0.0 "@codemirror/state": ^6.0.0 "@codemirror/view": ^6.17.0 "@lezer/highlight": ^1.0.0 style-mod: ^4.1.0 - checksum: 7083a3d1c61e57c038dc77ec645cd4d189bf4c636abeb351b93df7b9fcce26462f93b28f89d52cdcd3bc39d5e6610113896c67ae97e2d06615f35ff1aaf0eae9 + checksum: b095c03cf7a7dc00f9d2d938f521ae23d714abd601a9cfba31bf4b039cab7c097f61ee71629763b0dd0b75d3a65d0fd12b84e5898fb3eae6d75217fbedcf9af8 languageName: node linkType: hard @@ -538,19 +538,19 @@ __metadata: languageName: node linkType: hard -"@jupyterlab/apputils@npm:^4.4.3, @jupyterlab/apputils@npm:^4.5.7": - version: 4.5.7 - resolution: "@jupyterlab/apputils@npm:4.5.7" +"@jupyterlab/apputils@npm:^4.4.3, @jupyterlab/apputils@npm:^4.5.9": + version: 4.5.9 + resolution: "@jupyterlab/apputils@npm:4.5.9" dependencies: - "@jupyterlab/coreutils": ^6.4.7 - "@jupyterlab/observables": ^5.4.7 - "@jupyterlab/rendermime-interfaces": ^3.12.7 - "@jupyterlab/services": ^7.4.7 - "@jupyterlab/settingregistry": ^4.4.7 - "@jupyterlab/statedb": ^4.4.7 - "@jupyterlab/statusbar": ^4.4.7 - "@jupyterlab/translation": ^4.4.7 - "@jupyterlab/ui-components": ^4.4.7 + "@jupyterlab/coreutils": ^6.4.9 + "@jupyterlab/observables": ^5.4.9 + "@jupyterlab/rendermime-interfaces": ^3.12.9 + "@jupyterlab/services": ^7.4.9 + "@jupyterlab/settingregistry": ^4.4.9 + "@jupyterlab/statedb": ^4.4.9 + "@jupyterlab/statusbar": ^4.4.9 + "@jupyterlab/translation": ^4.4.9 + "@jupyterlab/ui-components": ^4.4.9 "@lumino/algorithm": ^2.0.3 "@lumino/commands": ^2.3.2 "@lumino/coreutils": ^2.2.1 @@ -563,7 +563,7 @@ __metadata: "@types/react": ^18.0.26 react: ^18.2.0 sanitize-html: ~2.12.1 - checksum: d74320e64f195062f83183a9361e01b4b6dbb545824fe8efe0b31c56943373cdaf7e932ce9a54a6a2bd70cb0e0f3ef754c402b330f86219c067d85be67cc5004 + checksum: 19c75e607ca9c5422cb0128d8c4c279d7c75d3d9681c9d45112e0291323279034550f19754a203ed0c09c3dc51b3eb16ee2c0c29be1777dddcf499ef4e6d4b5f languageName: node linkType: hard @@ -658,19 +658,19 @@ __metadata: languageName: node linkType: hard -"@jupyterlab/codeeditor@npm:^4.0.0, @jupyterlab/codeeditor@npm:^4.3.3, @jupyterlab/codeeditor@npm:^4.4.7": - version: 4.4.7 - resolution: "@jupyterlab/codeeditor@npm:4.4.7" +"@jupyterlab/codeeditor@npm:^4.0.0, @jupyterlab/codeeditor@npm:^4.3.3, @jupyterlab/codeeditor@npm:^4.4.9": + version: 4.4.9 + resolution: "@jupyterlab/codeeditor@npm:4.4.9" dependencies: "@codemirror/state": ^6.5.2 "@jupyter/ydoc": ^3.1.0 - "@jupyterlab/apputils": ^4.5.7 - "@jupyterlab/coreutils": ^6.4.7 - "@jupyterlab/nbformat": ^4.4.7 - "@jupyterlab/observables": ^5.4.7 - "@jupyterlab/statusbar": ^4.4.7 - "@jupyterlab/translation": ^4.4.7 - "@jupyterlab/ui-components": ^4.4.7 + "@jupyterlab/apputils": ^4.5.9 + "@jupyterlab/coreutils": ^6.4.9 + "@jupyterlab/nbformat": ^4.4.9 + "@jupyterlab/observables": ^5.4.9 + "@jupyterlab/statusbar": ^4.4.9 + "@jupyterlab/translation": ^4.4.9 + "@jupyterlab/ui-components": ^4.4.9 "@lumino/coreutils": ^2.2.1 "@lumino/disposable": ^2.1.4 "@lumino/dragdrop": ^2.1.6 @@ -678,13 +678,13 @@ __metadata: "@lumino/signaling": ^2.1.4 "@lumino/widgets": ^2.7.1 react: ^18.2.0 - checksum: 19b8dda6990afed5fe49e9bb79bf99ff2170019bba97870ae6c58fd82f1add7f851dde6b47f37b8e9f79285f1586c0cffd0e45c262be19d5e652e42012c10604 + checksum: 1e1e374a3fadf12c30b6239b20d19c00e704403c5ac1dc70f9a168f460bf67610525b4f3c4da606ada17eff5940fe28cf022415d6bd3aa1714751e00a0eddfe4 languageName: node linkType: hard -"@jupyterlab/codemirror@npm:^4.3.3, @jupyterlab/codemirror@npm:^4.4.7": - version: 4.4.7 - resolution: "@jupyterlab/codemirror@npm:4.4.7" +"@jupyterlab/codemirror@npm:^4.3.3, @jupyterlab/codemirror@npm:^4.4.7, @jupyterlab/codemirror@npm:^4.4.9": + version: 4.4.9 + resolution: "@jupyterlab/codemirror@npm:4.4.9" dependencies: "@codemirror/autocomplete": ^6.18.6 "@codemirror/commands": ^6.8.1 @@ -707,11 +707,11 @@ __metadata: "@codemirror/state": ^6.5.2 "@codemirror/view": ^6.38.1 "@jupyter/ydoc": ^3.1.0 - "@jupyterlab/codeeditor": ^4.4.7 - "@jupyterlab/coreutils": ^6.4.7 - "@jupyterlab/documentsearch": ^4.4.7 - "@jupyterlab/nbformat": ^4.4.7 - "@jupyterlab/translation": ^4.4.7 + "@jupyterlab/codeeditor": ^4.4.9 + "@jupyterlab/coreutils": ^6.4.9 + "@jupyterlab/documentsearch": ^4.4.9 + "@jupyterlab/nbformat": ^4.4.9 + "@jupyterlab/translation": ^4.4.9 "@lezer/common": ^1.2.1 "@lezer/generator": ^1.7.0 "@lezer/highlight": ^1.2.0 @@ -720,13 +720,13 @@ __metadata: "@lumino/disposable": ^2.1.4 "@lumino/signaling": ^2.1.4 yjs: ^13.5.40 - checksum: b70a3cfe247d0f39e280e1ebebe73f6038ba37dfcca42bd0963ec613a6682fe402d7b6d2f837cf97de14f499b8bbe91dc609a3c3c7a4947b0b451f99541cb478 + checksum: 68cc6f265c990e33b2fd4122ca28cc7fc5b039d8a829a7513415ac7245ce9a9ff0183515e48b1bac0085507bddfd5666e45ae8fbfef6de90347a421d4341e4a4 languageName: node linkType: hard -"@jupyterlab/coreutils@npm:^6.0.0, @jupyterlab/coreutils@npm:^6.3.3, @jupyterlab/coreutils@npm:^6.4.7": - version: 6.4.7 - resolution: "@jupyterlab/coreutils@npm:6.4.7" +"@jupyterlab/coreutils@npm:^6.0.0, @jupyterlab/coreutils@npm:^6.3.3, @jupyterlab/coreutils@npm:^6.4.9": + version: 6.4.9 + resolution: "@jupyterlab/coreutils@npm:6.4.9" dependencies: "@lumino/coreutils": ^2.2.1 "@lumino/disposable": ^2.1.4 @@ -734,7 +734,7 @@ __metadata: minimist: ~1.2.0 path-browserify: ^1.0.0 url-parse: ~1.5.4 - checksum: b346c479f139c641f947634c6dce697af0b30008c725e93fc3841120ebfb6873594fc753d8a9ee8b1723c4a8a3a0844583e7c7e7e9f2058f9113872ce17a8fa7 + checksum: 50c92dca8750c3a6bbe16c3a8af150db786636f15b0a0eba1e1f5ed2c0ae8ce06884cc7a580991c9d5f141000616c0fc5aa537e70974b0e19ad981a5cf21886b languageName: node linkType: hard @@ -763,39 +763,39 @@ __metadata: languageName: node linkType: hard -"@jupyterlab/docregistry@npm:^4.3.3": - version: 4.3.3 - resolution: "@jupyterlab/docregistry@npm:4.3.3" +"@jupyterlab/docregistry@npm:^4.3.3, @jupyterlab/docregistry@npm:^4.4.9": + version: 4.4.9 + resolution: "@jupyterlab/docregistry@npm:4.4.9" dependencies: - "@jupyter/ydoc": ^3.0.0 - "@jupyterlab/apputils": ^4.4.3 - "@jupyterlab/codeeditor": ^4.3.3 - "@jupyterlab/coreutils": ^6.3.3 - "@jupyterlab/observables": ^5.3.3 - "@jupyterlab/rendermime": ^4.3.3 - "@jupyterlab/rendermime-interfaces": ^3.11.3 - "@jupyterlab/services": ^7.3.3 - "@jupyterlab/translation": ^4.3.3 - "@jupyterlab/ui-components": ^4.3.3 - "@lumino/algorithm": ^2.0.2 - "@lumino/coreutils": ^2.2.0 - "@lumino/disposable": ^2.1.3 - "@lumino/messaging": ^2.0.2 - "@lumino/properties": ^2.0.2 - "@lumino/signaling": ^2.1.3 - "@lumino/widgets": ^2.5.0 + "@jupyter/ydoc": ^3.1.0 + "@jupyterlab/apputils": ^4.5.9 + "@jupyterlab/codeeditor": ^4.4.9 + "@jupyterlab/coreutils": ^6.4.9 + "@jupyterlab/observables": ^5.4.9 + "@jupyterlab/rendermime": ^4.4.9 + "@jupyterlab/rendermime-interfaces": ^3.12.9 + "@jupyterlab/services": ^7.4.9 + "@jupyterlab/translation": ^4.4.9 + "@jupyterlab/ui-components": ^4.4.9 + "@lumino/algorithm": ^2.0.3 + "@lumino/coreutils": ^2.2.1 + "@lumino/disposable": ^2.1.4 + "@lumino/messaging": ^2.0.3 + "@lumino/properties": ^2.0.3 + "@lumino/signaling": ^2.1.4 + "@lumino/widgets": ^2.7.1 react: ^18.2.0 - checksum: 14b44c8ae0c1a5059da6786396f47dfc79160e2489db509c3d37a58e1aaf8b2648dfac44eafac3dfaaabcb114f6985f52c0085b663718d5cac4901492e138d14 + checksum: d02e1f530166b50232dca590185bc9d19b75944624dc59e3abf4cfdfba3f8bc4de7f37283301f98721ebaacb93c7b3e017357de9f4d586ef475a42c13c3d2762 languageName: node linkType: hard -"@jupyterlab/documentsearch@npm:^4.3.3, @jupyterlab/documentsearch@npm:^4.4.7": - version: 4.4.7 - resolution: "@jupyterlab/documentsearch@npm:4.4.7" +"@jupyterlab/documentsearch@npm:^4.3.3, @jupyterlab/documentsearch@npm:^4.4.9": + version: 4.4.9 + resolution: "@jupyterlab/documentsearch@npm:4.4.9" dependencies: - "@jupyterlab/apputils": ^4.5.7 - "@jupyterlab/translation": ^4.4.7 - "@jupyterlab/ui-components": ^4.4.7 + "@jupyterlab/apputils": ^4.5.9 + "@jupyterlab/translation": ^4.4.9 + "@jupyterlab/ui-components": ^4.4.9 "@lumino/commands": ^2.3.2 "@lumino/coreutils": ^2.2.1 "@lumino/disposable": ^2.1.4 @@ -804,7 +804,7 @@ __metadata: "@lumino/signaling": ^2.1.4 "@lumino/widgets": ^2.7.1 react: ^18.2.0 - checksum: 665940f320edf537ff146848a7677017d338ba258b039cbc340961be5791ad3cc516a38ad48420cade7fde10fa406148d78006620fc5ab32bacc30fa1ee206e5 + checksum: a2a8e5016300cd3c88dbd931a148a5394d0f20ab638b26dd43eb131a72b14d820fe2f955dc9f7afd01383ce0d8b4cb8cd1e1642e298c9f8044b7e2e54124b937 languageName: node linkType: hard @@ -836,35 +836,61 @@ __metadata: languageName: node linkType: hard -"@jupyterlab/lsp@npm:^4.3.3": - version: 4.3.3 - resolution: "@jupyterlab/lsp@npm:4.3.3" +"@jupyterlab/fileeditor@npm:^4.0.0": + version: 4.4.9 + resolution: "@jupyterlab/fileeditor@npm:4.4.9" dependencies: - "@jupyterlab/apputils": ^4.4.3 - "@jupyterlab/codeeditor": ^4.3.3 - "@jupyterlab/codemirror": ^4.3.3 - "@jupyterlab/coreutils": ^6.3.3 - "@jupyterlab/docregistry": ^4.3.3 - "@jupyterlab/services": ^7.3.3 - "@jupyterlab/translation": ^4.3.3 - "@lumino/coreutils": ^2.2.0 - "@lumino/disposable": ^2.1.3 - "@lumino/signaling": ^2.1.3 - "@lumino/widgets": ^2.5.0 + "@jupyter/ydoc": ^3.1.0 + "@jupyterlab/apputils": ^4.5.9 + "@jupyterlab/codeeditor": ^4.4.9 + "@jupyterlab/codemirror": ^4.4.9 + "@jupyterlab/coreutils": ^6.4.9 + "@jupyterlab/docregistry": ^4.4.9 + "@jupyterlab/documentsearch": ^4.4.9 + "@jupyterlab/lsp": ^4.4.9 + "@jupyterlab/statusbar": ^4.4.9 + "@jupyterlab/toc": ^6.4.9 + "@jupyterlab/translation": ^4.4.9 + "@jupyterlab/ui-components": ^4.4.9 + "@lumino/commands": ^2.3.2 + "@lumino/coreutils": ^2.2.1 + "@lumino/messaging": ^2.0.3 + "@lumino/widgets": ^2.7.1 + react: ^18.2.0 + regexp-match-indices: ^1.0.2 + checksum: 0b0cd227610105e7bc742d864fcb969ca243707eac4882d1c69dab32f7232bdaaa3b83ebf994ba6aedcadd581ac96d7a07663345f5bca3401809c564bdc7d6f3 + languageName: node + linkType: hard + +"@jupyterlab/lsp@npm:^4.3.3, @jupyterlab/lsp@npm:^4.4.9": + version: 4.4.9 + resolution: "@jupyterlab/lsp@npm:4.4.9" + dependencies: + "@jupyterlab/apputils": ^4.5.9 + "@jupyterlab/codeeditor": ^4.4.9 + "@jupyterlab/codemirror": ^4.4.9 + "@jupyterlab/coreutils": ^6.4.9 + "@jupyterlab/docregistry": ^4.4.9 + "@jupyterlab/services": ^7.4.9 + "@jupyterlab/translation": ^4.4.9 + "@lumino/coreutils": ^2.2.1 + "@lumino/disposable": ^2.1.4 + "@lumino/signaling": ^2.1.4 + "@lumino/widgets": ^2.7.1 lodash.mergewith: ^4.6.1 vscode-jsonrpc: ^6.0.0 vscode-languageserver-protocol: ^3.17.0 vscode-ws-jsonrpc: ~1.0.2 - checksum: 4928b2f0990682842d6c60c5ae876d1aa06ab954615db79b1186f26e978a25d816872f6f5b599ee5335bc65ca6af19b4d46fcf3ebc58b16007b83fc951184950 + checksum: c89fb34f14ce7e6cbfba931eb4c60060a12ef1e4acb72c8d46a3e05ca931e30fd63f67d80cc392c1327802756246c34637965ae18ffad54160208f6f33527406 languageName: node linkType: hard -"@jupyterlab/nbformat@npm:^3.0.0 || ^4.0.0-alpha.21 || ^4.0.0, @jupyterlab/nbformat@npm:^4.3.3, @jupyterlab/nbformat@npm:^4.4.7": - version: 4.4.7 - resolution: "@jupyterlab/nbformat@npm:4.4.7" +"@jupyterlab/nbformat@npm:^3.0.0 || ^4.0.0-alpha.21 || ^4.0.0, @jupyterlab/nbformat@npm:^4.3.3, @jupyterlab/nbformat@npm:^4.4.9": + version: 4.4.9 + resolution: "@jupyterlab/nbformat@npm:4.4.9" dependencies: "@lumino/coreutils": ^2.2.1 - checksum: 4ce6173b937a5873b3a634a7da6a8472f400f25c28be93b04d4570070178e4804f598d1d3b4c0e2901935e18b86639d5f780304f2acd70ec761d97e2236a5ac1 + checksum: 59c73ac19ebd8d121e85916af91fc2b42e71f24d52bb7b1ac3efe58965d279edd8050934bc1079d7986c1fc061aae007e741e6889ea9a4e2f58f56b8e9c42e83 languageName: node linkType: hard @@ -906,16 +932,16 @@ __metadata: languageName: node linkType: hard -"@jupyterlab/observables@npm:^5.3.3, @jupyterlab/observables@npm:^5.4.7": - version: 5.4.7 - resolution: "@jupyterlab/observables@npm:5.4.7" +"@jupyterlab/observables@npm:^5.3.3, @jupyterlab/observables@npm:^5.4.9": + version: 5.4.9 + resolution: "@jupyterlab/observables@npm:5.4.9" dependencies: "@lumino/algorithm": ^2.0.3 "@lumino/coreutils": ^2.2.1 "@lumino/disposable": ^2.1.4 "@lumino/messaging": ^2.0.3 "@lumino/signaling": ^2.1.4 - checksum: 03ecc2c3e9aa0943d670d41174fad9ac571f2140aee9697e9657f2c69a78083b814a33facc821a57b93f02acc48a7501f745cb3ae516ae9a4c3a4fb513c67a62 + checksum: ce7705d469bb1d1358c15c8797cb89cb4a5f22171c17f503cfab72f19e6c73ebeae627d8b26b0e75b68a7b749c463357ea32ee8fdb9689608d03da68d501e88d languageName: node linkType: hard @@ -941,61 +967,61 @@ __metadata: languageName: node linkType: hard -"@jupyterlab/rendermime-interfaces@npm:^3.11.3, @jupyterlab/rendermime-interfaces@npm:^3.12.7": - version: 3.12.7 - resolution: "@jupyterlab/rendermime-interfaces@npm:3.12.7" +"@jupyterlab/rendermime-interfaces@npm:^3.11.3, @jupyterlab/rendermime-interfaces@npm:^3.12.9": + version: 3.12.9 + resolution: "@jupyterlab/rendermime-interfaces@npm:3.12.9" dependencies: "@lumino/coreutils": ^1.11.0 || ^2.2.1 "@lumino/widgets": ^1.37.2 || ^2.7.1 - checksum: 474946fecab328eaa62961a768a026b7f24fbaa7a1cd803ad995b2738904f561bcb5eef0c487e4558d2182fe5e0f10892d002ec72e10e3564de6f59d9d77a67a + checksum: c0db30ed6ea584d59d397857bb61ef36a15b166bbdaf80eafdf1a731c5a95589663ed7c3a7de698397b16348251b22e23dc90399a41d19d4993fba98964d4dea languageName: node linkType: hard -"@jupyterlab/rendermime@npm:^4.3.3": - version: 4.3.3 - resolution: "@jupyterlab/rendermime@npm:4.3.3" +"@jupyterlab/rendermime@npm:^4.3.3, @jupyterlab/rendermime@npm:^4.4.9": + version: 4.4.9 + resolution: "@jupyterlab/rendermime@npm:4.4.9" dependencies: - "@jupyterlab/apputils": ^4.4.3 - "@jupyterlab/coreutils": ^6.3.3 - "@jupyterlab/nbformat": ^4.3.3 - "@jupyterlab/observables": ^5.3.3 - "@jupyterlab/rendermime-interfaces": ^3.11.3 - "@jupyterlab/services": ^7.3.3 - "@jupyterlab/translation": ^4.3.3 - "@lumino/coreutils": ^2.2.0 - "@lumino/messaging": ^2.0.2 - "@lumino/signaling": ^2.1.3 - "@lumino/widgets": ^2.5.0 + "@jupyterlab/apputils": ^4.5.9 + "@jupyterlab/coreutils": ^6.4.9 + "@jupyterlab/nbformat": ^4.4.9 + "@jupyterlab/observables": ^5.4.9 + "@jupyterlab/rendermime-interfaces": ^3.12.9 + "@jupyterlab/services": ^7.4.9 + "@jupyterlab/translation": ^4.4.9 + "@lumino/coreutils": ^2.2.1 + "@lumino/messaging": ^2.0.3 + "@lumino/signaling": ^2.1.4 + "@lumino/widgets": ^2.7.1 lodash.escape: ^4.0.1 - checksum: 8e40cc9e4ca6cecc3451cdbb460bfa075be5b43993b5511e9ebde101f246522fe6823272cdff4a2d5ef2b23d7b18a0e3a00471c4aa9c7b2487de45b4621e4497 + checksum: 1d32ab4deb855b35d4827143daea939b16e69b03c66490aea8472de9bab383de29c81ef8d5cd3a5a2e5e185f0b747b2091ab02344abcdc99dbc5a565501b2af2 languageName: node linkType: hard -"@jupyterlab/services@npm:^7.0.0, @jupyterlab/services@npm:^7.3.3, @jupyterlab/services@npm:^7.4.7": - version: 7.4.7 - resolution: "@jupyterlab/services@npm:7.4.7" +"@jupyterlab/services@npm:^7.0.0, @jupyterlab/services@npm:^7.3.3, @jupyterlab/services@npm:^7.4.9": + version: 7.4.9 + resolution: "@jupyterlab/services@npm:7.4.9" dependencies: "@jupyter/ydoc": ^3.1.0 - "@jupyterlab/coreutils": ^6.4.7 - "@jupyterlab/nbformat": ^4.4.7 - "@jupyterlab/settingregistry": ^4.4.7 - "@jupyterlab/statedb": ^4.4.7 + "@jupyterlab/coreutils": ^6.4.9 + "@jupyterlab/nbformat": ^4.4.9 + "@jupyterlab/settingregistry": ^4.4.9 + "@jupyterlab/statedb": ^4.4.9 "@lumino/coreutils": ^2.2.1 "@lumino/disposable": ^2.1.4 "@lumino/polling": ^2.1.4 "@lumino/properties": ^2.0.3 "@lumino/signaling": ^2.1.4 ws: ^8.11.0 - checksum: 27693f5df1f12f7c54667aed65222b7a85682d34ba8a95727430c1516eb9c1940e32d4c47ca0e125daa3569814ff234eb861fff2eb65323476188e41ba3e42f6 + checksum: b1af994a2b752ed0ea60e5a53b85da3bb8a1d702407029a2ea747877a36aed142bc1d605bdef8e8f2002dc3d1ed516c2d77945dc9906cfcb58b957b9b2f6de22 languageName: node linkType: hard -"@jupyterlab/settingregistry@npm:^4.3.3, @jupyterlab/settingregistry@npm:^4.4.7": - version: 4.4.7 - resolution: "@jupyterlab/settingregistry@npm:4.4.7" +"@jupyterlab/settingregistry@npm:^4.3.3, @jupyterlab/settingregistry@npm:^4.4.9": + version: 4.4.9 + resolution: "@jupyterlab/settingregistry@npm:4.4.9" dependencies: - "@jupyterlab/nbformat": ^4.4.7 - "@jupyterlab/statedb": ^4.4.7 + "@jupyterlab/nbformat": ^4.4.9 + "@jupyterlab/statedb": ^4.4.9 "@lumino/commands": ^2.3.2 "@lumino/coreutils": ^2.2.1 "@lumino/disposable": ^2.1.4 @@ -1005,28 +1031,28 @@ __metadata: json5: ^2.2.3 peerDependencies: react: ">=16" - checksum: 8040c2e14581fe95c56c0f7d64c2a4a0010d65bb66a72e9b907d939ece18402f3d4f4c8019f43685d3fd7d926e1a24e59c5eaf3cc689b284695d8e9c7ec094cc + checksum: f1937b8ba0486d2ebd1a7c5573c8b1cdf42aaacdfd644f1697e30c3c6d36c66faf6218908102287571d095b4e4c5d647feb1364290213c9d3f9a3ff4c74f3c73 languageName: node linkType: hard -"@jupyterlab/statedb@npm:^4.3.3, @jupyterlab/statedb@npm:^4.4.7": - version: 4.4.7 - resolution: "@jupyterlab/statedb@npm:4.4.7" +"@jupyterlab/statedb@npm:^4.3.3, @jupyterlab/statedb@npm:^4.4.9": + version: 4.4.9 + resolution: "@jupyterlab/statedb@npm:4.4.9" dependencies: "@lumino/commands": ^2.3.2 "@lumino/coreutils": ^2.2.1 "@lumino/disposable": ^2.1.4 "@lumino/properties": ^2.0.3 "@lumino/signaling": ^2.1.4 - checksum: 036a323f439c28f95d391994517623b746938baa9b64abe3c14a6b2a162e45b1541751e23872abfd5146973eec32c40576db2eb03827be91dfd725a2e7114557 + checksum: ec53df2570c03f3ba7e40fb0a30eb2da441c196d6261a7f347529e211b71775fa90e9174d8d3f473a0af6bc1a4e269f3edceffe5755f7f0f0557a7a645ace8be languageName: node linkType: hard -"@jupyterlab/statusbar@npm:^4.3.3, @jupyterlab/statusbar@npm:^4.4.7": - version: 4.4.7 - resolution: "@jupyterlab/statusbar@npm:4.4.7" +"@jupyterlab/statusbar@npm:^4.3.3, @jupyterlab/statusbar@npm:^4.4.9": + version: 4.4.9 + resolution: "@jupyterlab/statusbar@npm:4.4.9" dependencies: - "@jupyterlab/ui-components": ^4.4.7 + "@jupyterlab/ui-components": ^4.4.9 "@lumino/algorithm": ^2.0.3 "@lumino/coreutils": ^2.2.1 "@lumino/disposable": ^2.1.4 @@ -1034,56 +1060,56 @@ __metadata: "@lumino/signaling": ^2.1.4 "@lumino/widgets": ^2.7.1 react: ^18.2.0 - checksum: 8482565f18afe98a64d95a3737449c98a243cf55bc1f55007c446c38f694189dbca960e0d51614a147c37ca5f9906676b1409820f91eaaa535d44c66e8ba2fad + checksum: c74382d378990e34323ad046d79985da7885af154d6bdaaad1c8e12175058978c9b2698956bad32a9592d6d543968567af1cd1eb7412d594e8ee3279675617fc languageName: node linkType: hard -"@jupyterlab/toc@npm:^6.3.3": - version: 6.3.3 - resolution: "@jupyterlab/toc@npm:6.3.3" +"@jupyterlab/toc@npm:^6.3.3, @jupyterlab/toc@npm:^6.4.9": + version: 6.4.9 + resolution: "@jupyterlab/toc@npm:6.4.9" dependencies: "@jupyter/react-components": ^0.16.6 - "@jupyterlab/apputils": ^4.4.3 - "@jupyterlab/coreutils": ^6.3.3 - "@jupyterlab/docregistry": ^4.3.3 - "@jupyterlab/observables": ^5.3.3 - "@jupyterlab/rendermime": ^4.3.3 - "@jupyterlab/rendermime-interfaces": ^3.11.3 - "@jupyterlab/translation": ^4.3.3 - "@jupyterlab/ui-components": ^4.3.3 - "@lumino/coreutils": ^2.2.0 - "@lumino/disposable": ^2.1.3 - "@lumino/messaging": ^2.0.2 - "@lumino/signaling": ^2.1.3 - "@lumino/widgets": ^2.5.0 + "@jupyterlab/apputils": ^4.5.9 + "@jupyterlab/coreutils": ^6.4.9 + "@jupyterlab/docregistry": ^4.4.9 + "@jupyterlab/observables": ^5.4.9 + "@jupyterlab/rendermime": ^4.4.9 + "@jupyterlab/rendermime-interfaces": ^3.12.9 + "@jupyterlab/translation": ^4.4.9 + "@jupyterlab/ui-components": ^4.4.9 + "@lumino/coreutils": ^2.2.1 + "@lumino/disposable": ^2.1.4 + "@lumino/messaging": ^2.0.3 + "@lumino/signaling": ^2.1.4 + "@lumino/widgets": ^2.7.1 react: ^18.2.0 - checksum: a99ebcc69a0477a0313af942b0cfba6d96b612a48efc682fee66297a609ff6ea068612a356e8e1bef49a1b73237ff6d6c30023a35dc3bb78f5ba2b694f047d74 + checksum: e548f940cef829fafa0c5f71ba5c7574967ab9b5df4c7dcc223795c303b74e3fc5f710194a405987dd41c0aae658c57e7413ff362750369656beb6d39b5a9f11 languageName: node linkType: hard -"@jupyterlab/translation@npm:^4.0.0, @jupyterlab/translation@npm:^4.3.3, @jupyterlab/translation@npm:^4.4.7": - version: 4.4.7 - resolution: "@jupyterlab/translation@npm:4.4.7" +"@jupyterlab/translation@npm:^4.0.0, @jupyterlab/translation@npm:^4.3.3, @jupyterlab/translation@npm:^4.4.9": + version: 4.4.9 + resolution: "@jupyterlab/translation@npm:4.4.9" dependencies: - "@jupyterlab/coreutils": ^6.4.7 - "@jupyterlab/rendermime-interfaces": ^3.12.7 - "@jupyterlab/services": ^7.4.7 - "@jupyterlab/statedb": ^4.4.7 + "@jupyterlab/coreutils": ^6.4.9 + "@jupyterlab/rendermime-interfaces": ^3.12.9 + "@jupyterlab/services": ^7.4.9 + "@jupyterlab/statedb": ^4.4.9 "@lumino/coreutils": ^2.2.1 - checksum: 775e542e83d9fef59c01acbcd629a62bfceceaf7a6c8729f07ce8b85fe69bc0a54788a08addf76259d698ffdb1c82d5603e05b17a46ddbf51c60640a49df2570 + checksum: 9747def9f9d6d0cf4400e615ac837ccc759e0a43adc87a97e4fa91220215bfd5ce88f3af777f9bbc9fe07f950fb85714c7ff555f2df02dc8aa30a2dcda71e3c1 languageName: node linkType: hard -"@jupyterlab/ui-components@npm:^4.0.0, @jupyterlab/ui-components@npm:^4.3.3, @jupyterlab/ui-components@npm:^4.4.7": - version: 4.4.7 - resolution: "@jupyterlab/ui-components@npm:4.4.7" +"@jupyterlab/ui-components@npm:^4.0.0, @jupyterlab/ui-components@npm:^4.3.3, @jupyterlab/ui-components@npm:^4.4.9": + version: 4.4.9 + resolution: "@jupyterlab/ui-components@npm:4.4.9" dependencies: "@jupyter/react-components": ^0.16.6 "@jupyter/web-components": ^0.16.6 - "@jupyterlab/coreutils": ^6.4.7 - "@jupyterlab/observables": ^5.4.7 - "@jupyterlab/rendermime-interfaces": ^3.12.7 - "@jupyterlab/translation": ^4.4.7 + "@jupyterlab/coreutils": ^6.4.9 + "@jupyterlab/observables": ^5.4.9 + "@jupyterlab/rendermime-interfaces": ^3.12.9 + "@jupyterlab/translation": ^4.4.9 "@lumino/algorithm": ^2.0.3 "@lumino/commands": ^2.3.2 "@lumino/coreutils": ^2.2.1 @@ -1101,7 +1127,7 @@ __metadata: typestyle: ^2.0.4 peerDependencies: react: ^18.2.0 - checksum: f8fe3b6479e958f3952af107c26506cdb968fcc298e7e2e3c062e2f411dabe0de9691f814479ec621b25553878e4611464c2b6096a11f135afb9f9b7e737de46 + checksum: 84c3e71b27a4a8031963c1e0f27b2409b781a1bc2ade569fa08e05a1263172ef7cd2664fe8e670ce3f704e2bec7a56fd21ecff1b6616062d34ddd632f049d606 languageName: node linkType: hard @@ -3570,14 +3596,16 @@ __metadata: resolution: "jupyterlab-cell-diff@workspace:." dependencies: "@codemirror/lang-python": ^6.2.1 - "@codemirror/merge": ^6.10.2 + "@codemirror/merge": ^6.11.0 "@codemirror/view": ^6.38.2 + "@jupyter/ydoc": ^3.1.0 "@jupyterlab/application": ^4.0.0 "@jupyterlab/builder": ^4.0.0 "@jupyterlab/cells": ^4.0.0 "@jupyterlab/codeeditor": ^4.0.0 "@jupyterlab/codemirror": ^4.4.7 "@jupyterlab/coreutils": ^6.0.0 + "@jupyterlab/fileeditor": ^4.0.0 "@jupyterlab/notebook": ^4.0.0 "@jupyterlab/services": ^7.0.0 "@jupyterlab/translation": ^4.0.0 @@ -4525,6 +4553,24 @@ __metadata: languageName: node linkType: hard +"regexp-match-indices@npm:^1.0.2": + version: 1.0.2 + resolution: "regexp-match-indices@npm:1.0.2" + dependencies: + regexp-tree: ^0.1.11 + checksum: 8cc779f6cf8f404ead828d09970a7d4bd66bd78d43ab9eb2b5e65f2ef2ba1ed53536f5b5fa839fb90b350365fb44b6a851c7f16289afc3f37789c113ab2a7916 + languageName: node + linkType: hard + +"regexp-tree@npm:^0.1.11": + version: 0.1.27 + resolution: "regexp-tree@npm:0.1.27" + bin: + regexp-tree: bin/regexp-tree + checksum: 129aebb34dae22d6694ab2ac328be3f99105143737528ab072ef624d599afecbcfae1f5c96a166fa9e5f64fa1ecf30b411c4691e7924c3e11bbaf1712c260c54 + languageName: node + linkType: hard + "require-from-string@npm:^2.0.2": version: 2.0.2 resolution: "require-from-string@npm:2.0.2"