Skip to content

Commit b83f1b7

Browse files
committed
Allow diffing with @codemirror/merge
1 parent 03cbfe4 commit b83f1b7

File tree

13 files changed

+1230
-520
lines changed

13 files changed

+1230
-520
lines changed

README.md

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,59 @@
1-
# JupyterLab Plugin: Show a Cell Diff
1+
# jupyterlab-cell-diff
22

33
[![Github Actions Status](https://github.com/Zsailer/jupyterlab-cell-diff/workflows/Build/badge.svg)](https://github.com/Zsailer/jupyterlab-cell-diff/actions/workflows/build.yml)
4+
[![PyPI version](https://badge.fury.io/py/jupyterlab-cell-diff.svg)](https://badge.fury.io/py/jupyterlab-cell-diff)
45

5-
A JupyterLab Extension for showing cell (git) diffs.
6-
7-
This extension is composed of a Python package named `jupyterlab_cell_diff`
8-
for the server extension and a NPM package named `jupyterlab-cell-diff`
9-
for the frontend extension.
6+
A JupyterLab extension for showing cell diffs with multiple diffing strategies.
107

118
## Requirements
129

1310
- JupyterLab >= 4.0.0
1411

15-
## Install
12+
## Installation
1613

17-
To install the extension, execute:
14+
### PyPI Installation
1815

1916
```bash
2017
pip install jupyterlab_cell_diff
2118
```
2219

20+
### Development Installation
21+
22+
```bash
23+
# Clone the repository
24+
git clone https://github.com/jupyter-ai-contrib/jupyterlab-cell-diff.git
25+
cd jupyterlab-cell-diff
26+
27+
# Install the extension in development mode
28+
pip install -e .
29+
jupyter labextension develop . --overwrite
30+
```
31+
32+
## Usage
33+
34+
### Commands
35+
36+
The extension provides several commands:
37+
38+
- `jupyterlab-cell-diff:show-codemirror` - Show diff using `@codemirror/merge`
39+
- `jupyterlab-cell-diff:show-nbdime` - Show diff using NBDime
40+
41+
### Programmatic Usage
42+
43+
```typescript
44+
app.commands.execute('jupyterlab-cell-diff:show-codemirror', {
45+
cellId: 'cell-id',
46+
originalSource: 'print("Hello")',
47+
newSource: 'print("Hello, World!")'
48+
});
49+
50+
app.commands.execute('jupyterlab-cell-diff:show-nbdime', {
51+
cellId: 'cell-id',
52+
originalSource: 'print("Hello")',
53+
newSource: 'print("Hello, World!")'
54+
});
55+
```
56+
2357
## Uninstall
2458

2559
To remove the extension, execute:

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
"files": [
2020
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
2121
"style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
22-
"src/**/*.{ts,tsx}"
22+
"src/**/*.{ts,tsx}",
23+
"schema/*.json"
2324
],
2425
"main": "lib/index.js",
2526
"types": "lib/index.d.ts",
@@ -55,14 +56,19 @@
5556
"watch:labextension": "jupyter labextension watch ."
5657
},
5758
"dependencies": {
59+
"@codemirror/lang-python": "^6.2.1",
60+
"@codemirror/merge": "^6.10.2",
61+
"@codemirror/view": "^6.38.2",
5862
"@jupyterlab/application": "^4.0.0",
5963
"@jupyterlab/cells": "^4.0.0",
6064
"@jupyterlab/codeeditor": "^4.0.0",
65+
"@jupyterlab/codemirror": "^4.4.7",
6166
"@jupyterlab/coreutils": "^6.0.0",
6267
"@jupyterlab/notebook": "^4.0.0",
6368
"@jupyterlab/services": "^7.0.0",
6469
"@jupyterlab/ui-components": "^4.0.0",
6570
"@lumino/widgets": "^2.0.0",
71+
"codemirror": "^6.0.2",
6672
"jupyterlab-cell-input-footer": "^0.3.0",
6773
"nbdime": "^7.0.1"
6874
},

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ classifiers = [
2525
dependencies = [
2626
"nbdime",
2727
"jupyter_server>=2.4.0,<3",
28-
"jupyterlab-eventlistener>=0.4.0",
29-
"jupyterlab-cell-input-footer>=0.2.0"
28+
"jupyterlab-eventlistener>=0.4.0,<0.5",
29+
"jupyterlab-cell-input-footer>=0.2.0,<0.3"
3030
]
3131
dynamic = ["version", "description", "authors", "urls", "keywords"]
3232

src/command.ts

Lines changed: 0 additions & 97 deletions
This file was deleted.

src/diff/codemirror.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { python } from '@codemirror/lang-python';
2+
import { MergeView } from '@codemirror/merge';
3+
import { EditorView } from '@codemirror/view';
4+
import { ICellModel } from '@jupyterlab/cells';
5+
import { jupyterTheme } from '@jupyterlab/codemirror';
6+
import { Message } from '@lumino/messaging';
7+
import { Widget } from '@lumino/widgets';
8+
import { basicSetup } from 'codemirror';
9+
import { ICellFooterTracker } from 'jupyterlab-cell-input-footer';
10+
import { IDiffStrategy, IDiffWidgetOptions } from '../interfaces';
11+
import { BaseDiffWidget } from '../widget';
12+
13+
/**
14+
* CodeMirror-based diff strategy that performs client-side diffing
15+
*/
16+
export class CodeMirrorStrategy implements IDiffStrategy {
17+
readonly id = 'codemirror';
18+
readonly name = 'CodeMirror (Client-side)';
19+
20+
/**
21+
* Create a diff widget using CodeMirror merge view
22+
*/
23+
async createDiffWidget(options: IDiffWidgetOptions): Promise<Widget> {
24+
const {
25+
cell,
26+
cellFooterTracker,
27+
originalSource,
28+
newSource,
29+
showActionButtons = true,
30+
openDiff = true
31+
} = options;
32+
33+
const modifiedCode = newSource || cell.sharedModel.getSource();
34+
35+
const diffWidget = new CodeMirrorDiffWidget({
36+
originalCode: originalSource,
37+
modifiedCode,
38+
cell,
39+
cellFooterTracker,
40+
showActionButtons,
41+
openDiff
42+
});
43+
44+
diffWidget.addClass('jupyterlab-cell-diff');
45+
diffWidget.addToFooter();
46+
47+
return diffWidget;
48+
}
49+
}
50+
51+
/**
52+
* A Lumino widget that contains a CodeMirror diff view
53+
*/
54+
class CodeMirrorDiffWidget extends BaseDiffWidget {
55+
private _originalCode: string;
56+
private _modifiedCode: string;
57+
private _mergeView: MergeView | null = null;
58+
59+
/**
60+
* Construct a new CodeMirrorDiffWidget.
61+
*/
62+
constructor(options: CodeMirrorDiffWidget.IOptions) {
63+
super({
64+
cell: options.cell,
65+
cellFooterTracker: options.cellFooterTracker,
66+
originalSource: options.originalCode,
67+
newSource: options.modifiedCode,
68+
showActionButtons: options.showActionButtons,
69+
openDiff: options.openDiff
70+
});
71+
this._originalCode = options.originalCode;
72+
this._modifiedCode = options.modifiedCode;
73+
this.addClass('jp-DiffView');
74+
}
75+
76+
/**
77+
* Handle after-attach messages for the widget.
78+
*/
79+
protected onAfterAttach(msg: Message): void {
80+
super.onAfterAttach(msg);
81+
this._createMergeView();
82+
}
83+
84+
/**
85+
* Handle before-detach messages for the widget.
86+
*/
87+
protected onBeforeDetach(msg: Message): void {
88+
this._destroyMergeView();
89+
super.onBeforeDetach(msg);
90+
}
91+
92+
/**
93+
* Create the merge view with CodeMirror diff functionality.
94+
*/
95+
private _createMergeView(): void {
96+
if (this._mergeView) {
97+
return;
98+
}
99+
100+
this._mergeView = new MergeView({
101+
a: {
102+
doc: this._originalCode,
103+
extensions: [
104+
basicSetup,
105+
python(),
106+
EditorView.editable.of(false),
107+
jupyterTheme
108+
]
109+
},
110+
b: {
111+
doc: this._modifiedCode,
112+
extensions: [
113+
basicSetup,
114+
python(),
115+
EditorView.editable.of(false),
116+
jupyterTheme
117+
]
118+
},
119+
parent: this.node
120+
});
121+
}
122+
123+
/**
124+
* Destroy the merge view and clean up resources.
125+
*/
126+
private _destroyMergeView(): void {
127+
if (this._mergeView) {
128+
this._mergeView.destroy();
129+
this._mergeView = null;
130+
}
131+
}
132+
}
133+
134+
/**
135+
* A namespace for `CodeMirrorDiffWidget` statics.
136+
*/
137+
namespace CodeMirrorDiffWidget {
138+
/**
139+
* The options used to construct a `CodeMirrorDiffWidget`.
140+
*/
141+
export interface IOptions {
142+
originalCode: string;
143+
modifiedCode: string;
144+
cell: ICellModel;
145+
cellFooterTracker: ICellFooterTracker;
146+
showActionButtons?: boolean;
147+
openDiff?: boolean;
148+
}
149+
}

0 commit comments

Comments
 (0)