@limetech/lime-elements 39.6.0 → 39.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/cjs/lime-elements.cjs.js +1 -1
- package/dist/cjs/{limel-action-bar_2.cjs.entry.js → limel-action-bar_3.cjs.entry.js} +111 -93
- package/dist/cjs/limel-ai-avatar.cjs.entry.js +1 -1
- package/dist/cjs/limel-breadcrumbs_7.cjs.entry.js +6 -6
- package/dist/cjs/limel-button-group.cjs.entry.js +7 -8
- package/dist/cjs/limel-callout.cjs.entry.js +1 -1
- package/dist/cjs/limel-chart.cjs.entry.js +1 -1
- package/dist/cjs/limel-chip_2.cjs.entry.js +1 -1
- package/dist/cjs/limel-code-diff.cjs.entry.js +1758 -0
- package/dist/cjs/limel-code-editor.cjs.entry.js +2 -2
- package/dist/cjs/limel-collapsible-section.cjs.entry.js +2 -2
- package/dist/cjs/limel-color-picker-palette.cjs.entry.js +2 -2
- package/dist/cjs/limel-color-picker.cjs.entry.js +1 -1
- package/dist/cjs/limel-dialog.cjs.entry.js +2 -2
- package/dist/cjs/limel-dock.cjs.entry.js +2 -2
- package/dist/cjs/limel-drag-handle.cjs.entry.js +2 -2
- package/dist/cjs/limel-email-viewer.cjs.entry.js +2 -2
- package/dist/cjs/limel-file-dropzone_2.cjs.entry.js +2 -2
- package/dist/cjs/limel-file-viewer.cjs.entry.js +1 -1
- package/dist/cjs/limel-file.cjs.entry.js +2 -2
- package/dist/cjs/limel-flatpickr-adapter.cjs.entry.js +2 -2
- package/dist/cjs/limel-flex-container.cjs.entry.js +1 -1
- package/dist/cjs/limel-form.cjs.entry.js +1 -1
- package/dist/cjs/limel-grid.cjs.entry.js +1 -1
- package/dist/cjs/limel-header.cjs.entry.js +1 -1
- package/dist/cjs/limel-help-content.cjs.entry.js +1 -1
- package/dist/cjs/limel-help.cjs.entry.js +2 -2
- package/dist/cjs/limel-helper-line_2.cjs.entry.js +3 -3
- package/dist/cjs/limel-icon-button.cjs.entry.js +1 -1
- package/dist/cjs/limel-icon.cjs.entry.js +1 -1
- package/dist/cjs/limel-info-tile.cjs.entry.js +2 -2
- package/dist/cjs/limel-linear-progress.cjs.entry.js +1 -1
- package/dist/cjs/limel-list-item.cjs.entry.js +3 -3
- package/dist/cjs/limel-markdown.cjs.entry.js +1 -1
- package/dist/cjs/limel-menu-item-meta.cjs.entry.js +1 -1
- package/dist/cjs/limel-picker.cjs.entry.js +1 -1
- package/dist/cjs/limel-popover_2.cjs.entry.js +2 -2
- package/dist/cjs/limel-portal_3.cjs.entry.js +4 -4
- package/dist/cjs/limel-profile-picture.cjs.entry.js +1 -1
- package/dist/cjs/limel-prosemirror-adapter.cjs.entry.js +2 -2
- package/dist/cjs/limel-radio-button-group.cjs.entry.js +1 -1
- package/dist/cjs/limel-radio-button.cjs.entry.js +2 -2
- package/dist/cjs/limel-select.cjs.entry.js +1 -1
- package/dist/cjs/limel-shortcut.cjs.entry.js +1 -1
- package/dist/cjs/limel-slider.cjs.entry.js +1 -1
- package/dist/cjs/limel-snackbar.cjs.entry.js +3 -3
- package/dist/cjs/limel-split-button.cjs.entry.js +2 -2
- package/dist/cjs/limel-tab-bar.cjs.entry.js +2 -2
- package/dist/cjs/limel-tab-panel.cjs.entry.js +1 -1
- package/dist/cjs/limel-table.cjs.entry.js +4 -4
- package/dist/cjs/limel-text-editor-link-menu.cjs.entry.js +120 -0
- package/dist/cjs/limel-text-editor.cjs.entry.js +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/{translations-BBGWKIVD.js → translations-Bu_0fli7.js} +236 -4
- package/dist/collection/collection-manifest.json +1 -0
- package/dist/collection/components/button-group/button-group.css +38 -650
- package/dist/collection/components/button-group/button-group.js +6 -7
- package/dist/collection/components/code-diff/code-diff.css +519 -0
- package/dist/collection/components/code-diff/code-diff.js +775 -0
- package/dist/collection/components/code-diff/content-utils.js +49 -0
- package/dist/collection/components/code-diff/diff-engine.js +308 -0
- package/dist/collection/components/code-diff/search-utils.js +41 -0
- package/dist/collection/components/code-diff/syntax-highlighter.js +87 -0
- package/dist/collection/components/code-diff/types.js +1 -0
- package/dist/collection/components/code-editor/code-editor.js +1 -1
- package/dist/collection/components/collapsible-section/collapsible-section.js +1 -1
- package/dist/collection/components/color-picker/color-picker-palette.js +2 -2
- package/dist/collection/components/color-picker/color-picker.js +1 -1
- package/dist/collection/components/date-picker/flatpickr-adapter/flatpickr-adapter.js +1 -1
- package/dist/collection/components/dialog/dialog.js +2 -2
- package/dist/collection/components/dock/dock.js +2 -2
- package/dist/collection/components/drag-handle/drag-handle.js +1 -1
- package/dist/collection/components/email-viewer/email-viewer.js +1 -1
- package/dist/collection/components/file/file.js +1 -1
- package/dist/collection/components/file-dropzone/file-dropzone.js +1 -1
- package/dist/collection/components/file-input/file-input.js +1 -1
- package/dist/collection/components/flex-container/flex-container.js +1 -1
- package/dist/collection/components/form/form.js +1 -1
- package/dist/collection/components/grid/grid.js +1 -1
- package/dist/collection/components/header/header.js +1 -1
- package/dist/collection/components/help/help-content.js +1 -1
- package/dist/collection/components/help/help.js +2 -2
- package/dist/collection/components/helper-line/helper-line.js +2 -2
- package/dist/collection/components/icon/icon.js +1 -1
- package/dist/collection/components/icon-button/icon-button.js +1 -1
- package/dist/collection/components/info-tile/info-tile.js +2 -2
- package/dist/collection/components/input-field/input-field.js +1 -1
- package/dist/collection/components/list/list.js +1 -1
- package/dist/collection/components/list-item/list-item.js +2 -2
- package/dist/collection/components/list-item/menu-item-meta/menu-item-meta.js +1 -1
- package/dist/collection/components/markdown/markdown.js +1 -1
- package/dist/collection/components/menu/menu.js +1 -1
- package/dist/collection/components/menu-list/menu-list.js +1 -1
- package/dist/collection/components/menu-surface/menu-surface.js +1 -1
- package/dist/collection/components/notched-outline/notched-outline.js +1 -1
- package/dist/collection/components/picker/picker.js +1 -1
- package/dist/collection/components/popover/popover.js +1 -1
- package/dist/collection/components/popover-surface/popover-surface.js +1 -1
- package/dist/collection/components/portal/portal.js +1 -1
- package/dist/collection/components/radio-button-group/radio-button-group.js +1 -1
- package/dist/collection/components/radio-button-group/radio-button.js +2 -2
- package/dist/collection/components/select/select.js +1 -1
- package/dist/collection/components/shortcut/shortcut.js +1 -1
- package/dist/collection/components/slider/slider.js +1 -1
- package/dist/collection/components/snackbar/snackbar.js +2 -2
- package/dist/collection/components/spinner/spinner.js +1 -1
- package/dist/collection/components/split-button/split-button.js +2 -2
- package/dist/collection/components/tab-bar/tab-bar.js +2 -2
- package/dist/collection/components/tab-panel/tab-panel.js +1 -1
- package/dist/collection/components/table/table.js +3 -3
- package/dist/collection/components/text-editor/link-menu/editor-link-menu.js +3 -3
- package/dist/collection/components/text-editor/prosemirror-adapter/prosemirror-adapter.js +1 -1
- package/dist/collection/components/text-editor/text-editor.js +1 -1
- package/dist/collection/components/tooltip/tooltip-content.js +1 -1
- package/dist/collection/components/tooltip/tooltip.js +2 -2
- package/dist/collection/translations/da.js +29 -0
- package/dist/collection/translations/de.js +29 -0
- package/dist/collection/translations/en.js +29 -0
- package/dist/collection/translations/fi.js +29 -0
- package/dist/collection/translations/fr.js +33 -4
- package/dist/collection/translations/nl.js +29 -0
- package/dist/collection/translations/no.js +29 -0
- package/dist/collection/translations/sv.js +29 -0
- package/dist/esm/lime-elements.js +1 -1
- package/dist/esm/{limel-action-bar_2.entry.js → limel-action-bar_3.entry.js} +110 -93
- package/dist/esm/limel-ai-avatar.entry.js +1 -1
- package/dist/esm/limel-breadcrumbs_7.entry.js +6 -6
- package/dist/esm/limel-button-group.entry.js +7 -8
- package/dist/esm/limel-callout.entry.js +1 -1
- package/dist/esm/limel-chart.entry.js +1 -1
- package/dist/esm/limel-chip_2.entry.js +1 -1
- package/dist/esm/limel-code-diff.entry.js +1756 -0
- package/dist/esm/limel-code-editor.entry.js +2 -2
- package/dist/esm/limel-collapsible-section.entry.js +2 -2
- package/dist/esm/limel-color-picker-palette.entry.js +2 -2
- package/dist/esm/limel-color-picker.entry.js +1 -1
- package/dist/esm/limel-dialog.entry.js +2 -2
- package/dist/esm/limel-dock.entry.js +2 -2
- package/dist/esm/limel-drag-handle.entry.js +2 -2
- package/dist/esm/limel-email-viewer.entry.js +2 -2
- package/dist/esm/limel-file-dropzone_2.entry.js +2 -2
- package/dist/esm/limel-file-viewer.entry.js +1 -1
- package/dist/esm/limel-file.entry.js +2 -2
- package/dist/esm/limel-flatpickr-adapter.entry.js +2 -2
- package/dist/esm/limel-flex-container.entry.js +1 -1
- package/dist/esm/limel-form.entry.js +1 -1
- package/dist/esm/limel-grid.entry.js +1 -1
- package/dist/esm/limel-header.entry.js +1 -1
- package/dist/esm/limel-help-content.entry.js +1 -1
- package/dist/esm/limel-help.entry.js +2 -2
- package/dist/esm/limel-helper-line_2.entry.js +3 -3
- package/dist/esm/limel-icon-button.entry.js +1 -1
- package/dist/esm/limel-icon.entry.js +1 -1
- package/dist/esm/limel-info-tile.entry.js +2 -2
- package/dist/esm/limel-linear-progress.entry.js +1 -1
- package/dist/esm/limel-list-item.entry.js +3 -3
- package/dist/esm/limel-markdown.entry.js +1 -1
- package/dist/esm/limel-menu-item-meta.entry.js +1 -1
- package/dist/esm/limel-picker.entry.js +1 -1
- package/dist/esm/limel-popover_2.entry.js +2 -2
- package/dist/esm/limel-portal_3.entry.js +4 -4
- package/dist/esm/limel-profile-picture.entry.js +1 -1
- package/dist/esm/limel-prosemirror-adapter.entry.js +2 -2
- package/dist/esm/limel-radio-button-group.entry.js +1 -1
- package/dist/esm/limel-radio-button.entry.js +2 -2
- package/dist/esm/limel-select.entry.js +1 -1
- package/dist/esm/limel-shortcut.entry.js +1 -1
- package/dist/esm/limel-slider.entry.js +1 -1
- package/dist/esm/limel-snackbar.entry.js +3 -3
- package/dist/esm/limel-split-button.entry.js +2 -2
- package/dist/esm/limel-tab-bar.entry.js +2 -2
- package/dist/esm/limel-tab-panel.entry.js +1 -1
- package/dist/esm/limel-table.entry.js +4 -4
- package/dist/esm/limel-text-editor-link-menu.entry.js +118 -0
- package/dist/esm/limel-text-editor.entry.js +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/esm/{translations-BHybIZJs.js → translations-DVRaJQvC.js} +236 -4
- package/dist/lime-elements/lime-elements.esm.js +1 -1
- package/dist/lime-elements/{p-58176f7b.entry.js → p-05ff053d.entry.js} +1 -1
- package/dist/lime-elements/{p-7dd4e4bb.entry.js → p-08d1b87a.entry.js} +1 -1
- package/dist/lime-elements/{p-94f78e7a.entry.js → p-0fa2add8.entry.js} +1 -1
- package/dist/lime-elements/{p-40883e25.entry.js → p-1547b9c8.entry.js} +1 -1
- package/dist/lime-elements/{p-16a5f421.entry.js → p-1c244f85.entry.js} +1 -1
- package/dist/lime-elements/{p-ba9d6d42.entry.js → p-21dc4586.entry.js} +1 -1
- package/dist/lime-elements/{p-8e6a36a7.entry.js → p-2292181d.entry.js} +1 -1
- package/dist/lime-elements/{p-889d0000.entry.js → p-26bc957e.entry.js} +1 -1
- package/dist/lime-elements/{p-d4a51f0a.entry.js → p-287c4fb1.entry.js} +1 -1
- package/dist/lime-elements/p-358b277c.entry.js +1 -0
- package/dist/lime-elements/{p-f43e4cb8.entry.js → p-44295cc0.entry.js} +1 -1
- package/dist/lime-elements/{p-78fffaa9.entry.js → p-5178cc39.entry.js} +1 -1
- package/dist/lime-elements/{p-ec5b360a.entry.js → p-518fe33c.entry.js} +2 -2
- package/dist/lime-elements/{p-911db0aa.entry.js → p-53b94806.entry.js} +1 -1
- package/dist/lime-elements/p-5be668d8.entry.js +1 -0
- package/dist/lime-elements/{p-f49e5d8a.entry.js → p-68ffd790.entry.js} +1 -1
- package/dist/lime-elements/{p-fdfecf3d.entry.js → p-6a26ea78.entry.js} +1 -1
- package/dist/lime-elements/{p-5f593160.entry.js → p-6b05db4a.entry.js} +1 -1
- package/dist/lime-elements/p-70e2e60c.entry.js +1 -0
- package/dist/lime-elements/{p-3ad102a1.entry.js → p-756f452c.entry.js} +1 -1
- package/dist/lime-elements/{p-fa6aea91.entry.js → p-8784a57c.entry.js} +1 -1
- package/dist/lime-elements/{p-6d28c7b4.entry.js → p-89dfbd4a.entry.js} +1 -1
- package/dist/lime-elements/{p-5280d11e.entry.js → p-8ec4fdee.entry.js} +1 -1
- package/dist/lime-elements/{p-1b0eec07.entry.js → p-90f8d2ef.entry.js} +1 -1
- package/dist/lime-elements/p-965288d2.entry.js +1 -0
- package/dist/lime-elements/{p-8b77d2a8.entry.js → p-9908b57a.entry.js} +1 -1
- package/dist/lime-elements/{p-2d7a2258.entry.js → p-9e3e4f2c.entry.js} +1 -1
- package/dist/lime-elements/p-DVRaJQvC.js +1 -0
- package/dist/lime-elements/{p-3b18ef34.entry.js → p-a2295fa6.entry.js} +1 -1
- package/dist/lime-elements/{p-14bfd676.entry.js → p-a489f4b0.entry.js} +1 -1
- package/dist/lime-elements/{p-d5e954d4.entry.js → p-aeebf410.entry.js} +1 -1
- package/dist/lime-elements/{p-f4c9301d.entry.js → p-b11751c9.entry.js} +1 -1
- package/dist/lime-elements/{p-a1c1c40d.entry.js → p-b6ccc921.entry.js} +1 -1
- package/dist/lime-elements/{p-60f12574.entry.js → p-b95a42ea.entry.js} +1 -1
- package/dist/lime-elements/{p-8118cd4f.entry.js → p-bb38bb3c.entry.js} +1 -1
- package/dist/lime-elements/{p-d93f1c5f.entry.js → p-c3d565e2.entry.js} +1 -1
- package/dist/lime-elements/{p-a113dc9d.entry.js → p-c3ff8518.entry.js} +1 -1
- package/dist/lime-elements/{p-e00a96bd.entry.js → p-c6b9425b.entry.js} +1 -1
- package/dist/lime-elements/{p-373b7df7.entry.js → p-c6e9af7c.entry.js} +1 -1
- package/dist/lime-elements/{p-7997c118.entry.js → p-ce22f3da.entry.js} +1 -1
- package/dist/lime-elements/{p-8c6dfb19.entry.js → p-d5da5b05.entry.js} +1 -1
- package/dist/lime-elements/{p-b255e8e6.entry.js → p-da4429a8.entry.js} +1 -1
- package/dist/lime-elements/{p-6aa7cd43.entry.js → p-dcf3cc71.entry.js} +1 -1
- package/dist/lime-elements/{p-97f719ae.entry.js → p-de1e5ad9.entry.js} +1 -1
- package/dist/lime-elements/{p-13d0ec04.entry.js → p-eaac5ad2.entry.js} +1 -1
- package/dist/lime-elements/{p-ce178fbd.entry.js → p-ed8129db.entry.js} +1 -1
- package/dist/lime-elements/{p-b92431c8.entry.js → p-ee3afb60.entry.js} +3 -3
- package/dist/lime-elements/{p-8eff8a18.entry.js → p-ef75eed9.entry.js} +1 -1
- package/dist/lime-elements/{p-46b95d7c.entry.js → p-ef9bb368.entry.js} +1 -1
- package/dist/lime-elements/{p-912f53a3.entry.js → p-f70b8487.entry.js} +1 -1
- package/dist/lime-elements/{p-d53b8de5.entry.js → p-f9d5513d.entry.js} +1 -1
- package/dist/lime-elements/{p-bc4b4e46.entry.js → p-fb6c42a6.entry.js} +1 -1
- package/dist/types/components/code-diff/code-diff.d.ts +147 -0
- package/dist/types/components/code-diff/content-utils.d.ts +27 -0
- package/dist/types/components/code-diff/diff-engine.d.ts +36 -0
- package/dist/types/components/code-diff/search-utils.d.ts +30 -0
- package/dist/types/components/code-diff/syntax-highlighter.d.ts +19 -0
- package/dist/types/components/code-diff/types.d.ts +50 -0
- package/dist/types/components.d.ts +175 -0
- package/dist/types/translations/da.d.ts +29 -0
- package/dist/types/translations/de.d.ts +29 -0
- package/dist/types/translations/en.d.ts +29 -0
- package/dist/types/translations/fi.d.ts +29 -0
- package/dist/types/translations/fr.d.ts +29 -0
- package/dist/types/translations/nl.d.ts +29 -0
- package/dist/types/translations/no.d.ts +29 -0
- package/dist/types/translations/sv.d.ts +29 -0
- package/package.json +2 -1
- package/dist/cjs/limel-action-bar-item_2.cjs.entry.js +0 -137
- package/dist/esm/limel-action-bar-item_2.entry.js +0 -134
- package/dist/lime-elements/p-854a3ffe.entry.js +0 -1
- package/dist/lime-elements/p-8f2ac274.entry.js +0 -1
- package/dist/lime-elements/p-BHybIZJs.js +0 -1
- package/dist/lime-elements/p-accc6cc0.entry.js +0 -1
|
@@ -0,0 +1,1758 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var index = require('./index-BjHIBY-I.js');
|
|
4
|
+
var translations = require('./translations-Bu_0fli7.js');
|
|
5
|
+
|
|
6
|
+
function Diff() {}
|
|
7
|
+
Diff.prototype = {
|
|
8
|
+
diff: function diff(oldString, newString) {
|
|
9
|
+
var _options$timeout;
|
|
10
|
+
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
11
|
+
var callback = options.callback;
|
|
12
|
+
if (typeof options === 'function') {
|
|
13
|
+
callback = options;
|
|
14
|
+
options = {};
|
|
15
|
+
}
|
|
16
|
+
var self = this;
|
|
17
|
+
function done(value) {
|
|
18
|
+
value = self.postProcess(value, options);
|
|
19
|
+
if (callback) {
|
|
20
|
+
setTimeout(function () {
|
|
21
|
+
callback(value);
|
|
22
|
+
}, 0);
|
|
23
|
+
return true;
|
|
24
|
+
} else {
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Allow subclasses to massage the input prior to running
|
|
30
|
+
oldString = this.castInput(oldString, options);
|
|
31
|
+
newString = this.castInput(newString, options);
|
|
32
|
+
oldString = this.removeEmpty(this.tokenize(oldString, options));
|
|
33
|
+
newString = this.removeEmpty(this.tokenize(newString, options));
|
|
34
|
+
var newLen = newString.length,
|
|
35
|
+
oldLen = oldString.length;
|
|
36
|
+
var editLength = 1;
|
|
37
|
+
var maxEditLength = newLen + oldLen;
|
|
38
|
+
if (options.maxEditLength != null) {
|
|
39
|
+
maxEditLength = Math.min(maxEditLength, options.maxEditLength);
|
|
40
|
+
}
|
|
41
|
+
var maxExecutionTime = (_options$timeout = options.timeout) !== null && _options$timeout !== void 0 ? _options$timeout : Infinity;
|
|
42
|
+
var abortAfterTimestamp = Date.now() + maxExecutionTime;
|
|
43
|
+
var bestPath = [{
|
|
44
|
+
oldPos: -1,
|
|
45
|
+
lastComponent: undefined
|
|
46
|
+
}];
|
|
47
|
+
|
|
48
|
+
// Seed editLength = 0, i.e. the content starts with the same values
|
|
49
|
+
var newPos = this.extractCommon(bestPath[0], newString, oldString, 0, options);
|
|
50
|
+
if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
|
|
51
|
+
// Identity per the equality and tokenizer
|
|
52
|
+
return done(buildValues(self, bestPath[0].lastComponent, newString, oldString, self.useLongestToken));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Once we hit the right edge of the edit graph on some diagonal k, we can
|
|
56
|
+
// definitely reach the end of the edit graph in no more than k edits, so
|
|
57
|
+
// there's no point in considering any moves to diagonal k+1 any more (from
|
|
58
|
+
// which we're guaranteed to need at least k+1 more edits).
|
|
59
|
+
// Similarly, once we've reached the bottom of the edit graph, there's no
|
|
60
|
+
// point considering moves to lower diagonals.
|
|
61
|
+
// We record this fact by setting minDiagonalToConsider and
|
|
62
|
+
// maxDiagonalToConsider to some finite value once we've hit the edge of
|
|
63
|
+
// the edit graph.
|
|
64
|
+
// This optimization is not faithful to the original algorithm presented in
|
|
65
|
+
// Myers's paper, which instead pointlessly extends D-paths off the end of
|
|
66
|
+
// the edit graph - see page 7 of Myers's paper which notes this point
|
|
67
|
+
// explicitly and illustrates it with a diagram. This has major performance
|
|
68
|
+
// implications for some common scenarios. For instance, to compute a diff
|
|
69
|
+
// where the new text simply appends d characters on the end of the
|
|
70
|
+
// original text of length n, the true Myers algorithm will take O(n+d^2)
|
|
71
|
+
// time while this optimization needs only O(n+d) time.
|
|
72
|
+
var minDiagonalToConsider = -Infinity,
|
|
73
|
+
maxDiagonalToConsider = Infinity;
|
|
74
|
+
|
|
75
|
+
// Main worker method. checks all permutations of a given edit length for acceptance.
|
|
76
|
+
function execEditLength() {
|
|
77
|
+
for (var diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
|
|
78
|
+
var basePath = void 0;
|
|
79
|
+
var removePath = bestPath[diagonalPath - 1],
|
|
80
|
+
addPath = bestPath[diagonalPath + 1];
|
|
81
|
+
if (removePath) {
|
|
82
|
+
// No one else is going to attempt to use this value, clear it
|
|
83
|
+
bestPath[diagonalPath - 1] = undefined;
|
|
84
|
+
}
|
|
85
|
+
var canAdd = false;
|
|
86
|
+
if (addPath) {
|
|
87
|
+
// what newPos will be after we do an insertion:
|
|
88
|
+
var addPathNewPos = addPath.oldPos - diagonalPath;
|
|
89
|
+
canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
|
|
90
|
+
}
|
|
91
|
+
var canRemove = removePath && removePath.oldPos + 1 < oldLen;
|
|
92
|
+
if (!canAdd && !canRemove) {
|
|
93
|
+
// If this path is a terminal then prune
|
|
94
|
+
bestPath[diagonalPath] = undefined;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Select the diagonal that we want to branch from. We select the prior
|
|
99
|
+
// path whose position in the old string is the farthest from the origin
|
|
100
|
+
// and does not pass the bounds of the diff graph
|
|
101
|
+
if (!canRemove || canAdd && removePath.oldPos < addPath.oldPos) {
|
|
102
|
+
basePath = self.addToPath(addPath, true, false, 0, options);
|
|
103
|
+
} else {
|
|
104
|
+
basePath = self.addToPath(removePath, false, true, 1, options);
|
|
105
|
+
}
|
|
106
|
+
newPos = self.extractCommon(basePath, newString, oldString, diagonalPath, options);
|
|
107
|
+
if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
|
|
108
|
+
// If we have hit the end of both strings, then we are done
|
|
109
|
+
return done(buildValues(self, basePath.lastComponent, newString, oldString, self.useLongestToken));
|
|
110
|
+
} else {
|
|
111
|
+
bestPath[diagonalPath] = basePath;
|
|
112
|
+
if (basePath.oldPos + 1 >= oldLen) {
|
|
113
|
+
maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
|
|
114
|
+
}
|
|
115
|
+
if (newPos + 1 >= newLen) {
|
|
116
|
+
minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
editLength++;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Performs the length of edit iteration. Is a bit fugly as this has to support the
|
|
124
|
+
// sync and async mode which is never fun. Loops over execEditLength until a value
|
|
125
|
+
// is produced, or until the edit length exceeds options.maxEditLength (if given),
|
|
126
|
+
// in which case it will return undefined.
|
|
127
|
+
if (callback) {
|
|
128
|
+
(function exec() {
|
|
129
|
+
setTimeout(function () {
|
|
130
|
+
if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
|
|
131
|
+
return callback();
|
|
132
|
+
}
|
|
133
|
+
if (!execEditLength()) {
|
|
134
|
+
exec();
|
|
135
|
+
}
|
|
136
|
+
}, 0);
|
|
137
|
+
})();
|
|
138
|
+
} else {
|
|
139
|
+
while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
|
|
140
|
+
var ret = execEditLength();
|
|
141
|
+
if (ret) {
|
|
142
|
+
return ret;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
addToPath: function addToPath(path, added, removed, oldPosInc, options) {
|
|
148
|
+
var last = path.lastComponent;
|
|
149
|
+
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
150
|
+
return {
|
|
151
|
+
oldPos: path.oldPos + oldPosInc,
|
|
152
|
+
lastComponent: {
|
|
153
|
+
count: last.count + 1,
|
|
154
|
+
added: added,
|
|
155
|
+
removed: removed,
|
|
156
|
+
previousComponent: last.previousComponent
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
} else {
|
|
160
|
+
return {
|
|
161
|
+
oldPos: path.oldPos + oldPosInc,
|
|
162
|
+
lastComponent: {
|
|
163
|
+
count: 1,
|
|
164
|
+
added: added,
|
|
165
|
+
removed: removed,
|
|
166
|
+
previousComponent: last
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
extractCommon: function extractCommon(basePath, newString, oldString, diagonalPath, options) {
|
|
172
|
+
var newLen = newString.length,
|
|
173
|
+
oldLen = oldString.length,
|
|
174
|
+
oldPos = basePath.oldPos,
|
|
175
|
+
newPos = oldPos - diagonalPath,
|
|
176
|
+
commonCount = 0;
|
|
177
|
+
while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldString[oldPos + 1], newString[newPos + 1], options)) {
|
|
178
|
+
newPos++;
|
|
179
|
+
oldPos++;
|
|
180
|
+
commonCount++;
|
|
181
|
+
if (options.oneChangePerToken) {
|
|
182
|
+
basePath.lastComponent = {
|
|
183
|
+
count: 1,
|
|
184
|
+
previousComponent: basePath.lastComponent,
|
|
185
|
+
added: false,
|
|
186
|
+
removed: false
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (commonCount && !options.oneChangePerToken) {
|
|
191
|
+
basePath.lastComponent = {
|
|
192
|
+
count: commonCount,
|
|
193
|
+
previousComponent: basePath.lastComponent,
|
|
194
|
+
added: false,
|
|
195
|
+
removed: false
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
basePath.oldPos = oldPos;
|
|
199
|
+
return newPos;
|
|
200
|
+
},
|
|
201
|
+
equals: function equals(left, right, options) {
|
|
202
|
+
if (options.comparator) {
|
|
203
|
+
return options.comparator(left, right);
|
|
204
|
+
} else {
|
|
205
|
+
return left === right || options.ignoreCase && left.toLowerCase() === right.toLowerCase();
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
removeEmpty: function removeEmpty(array) {
|
|
209
|
+
var ret = [];
|
|
210
|
+
for (var i = 0; i < array.length; i++) {
|
|
211
|
+
if (array[i]) {
|
|
212
|
+
ret.push(array[i]);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return ret;
|
|
216
|
+
},
|
|
217
|
+
castInput: function castInput(value) {
|
|
218
|
+
return value;
|
|
219
|
+
},
|
|
220
|
+
tokenize: function tokenize(value) {
|
|
221
|
+
return Array.from(value);
|
|
222
|
+
},
|
|
223
|
+
join: function join(chars) {
|
|
224
|
+
return chars.join('');
|
|
225
|
+
},
|
|
226
|
+
postProcess: function postProcess(changeObjects) {
|
|
227
|
+
return changeObjects;
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
function buildValues(diff, lastComponent, newString, oldString, useLongestToken) {
|
|
231
|
+
// First we convert our linked list of components in reverse order to an
|
|
232
|
+
// array in the right order:
|
|
233
|
+
var components = [];
|
|
234
|
+
var nextComponent;
|
|
235
|
+
while (lastComponent) {
|
|
236
|
+
components.push(lastComponent);
|
|
237
|
+
nextComponent = lastComponent.previousComponent;
|
|
238
|
+
delete lastComponent.previousComponent;
|
|
239
|
+
lastComponent = nextComponent;
|
|
240
|
+
}
|
|
241
|
+
components.reverse();
|
|
242
|
+
var componentPos = 0,
|
|
243
|
+
componentLen = components.length,
|
|
244
|
+
newPos = 0,
|
|
245
|
+
oldPos = 0;
|
|
246
|
+
for (; componentPos < componentLen; componentPos++) {
|
|
247
|
+
var component = components[componentPos];
|
|
248
|
+
if (!component.removed) {
|
|
249
|
+
if (!component.added && useLongestToken) {
|
|
250
|
+
var value = newString.slice(newPos, newPos + component.count);
|
|
251
|
+
value = value.map(function (value, i) {
|
|
252
|
+
var oldValue = oldString[oldPos + i];
|
|
253
|
+
return oldValue.length > value.length ? oldValue : value;
|
|
254
|
+
});
|
|
255
|
+
component.value = diff.join(value);
|
|
256
|
+
} else {
|
|
257
|
+
component.value = diff.join(newString.slice(newPos, newPos + component.count));
|
|
258
|
+
}
|
|
259
|
+
newPos += component.count;
|
|
260
|
+
|
|
261
|
+
// Common case
|
|
262
|
+
if (!component.added) {
|
|
263
|
+
oldPos += component.count;
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
component.value = diff.join(oldString.slice(oldPos, oldPos + component.count));
|
|
267
|
+
oldPos += component.count;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return components;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function longestCommonPrefix(str1, str2) {
|
|
274
|
+
var i;
|
|
275
|
+
for (i = 0; i < str1.length && i < str2.length; i++) {
|
|
276
|
+
if (str1[i] != str2[i]) {
|
|
277
|
+
return str1.slice(0, i);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return str1.slice(0, i);
|
|
281
|
+
}
|
|
282
|
+
function longestCommonSuffix(str1, str2) {
|
|
283
|
+
var i;
|
|
284
|
+
|
|
285
|
+
// Unlike longestCommonPrefix, we need a special case to handle all scenarios
|
|
286
|
+
// where we return the empty string since str1.slice(-0) will return the
|
|
287
|
+
// entire string.
|
|
288
|
+
if (!str1 || !str2 || str1[str1.length - 1] != str2[str2.length - 1]) {
|
|
289
|
+
return '';
|
|
290
|
+
}
|
|
291
|
+
for (i = 0; i < str1.length && i < str2.length; i++) {
|
|
292
|
+
if (str1[str1.length - (i + 1)] != str2[str2.length - (i + 1)]) {
|
|
293
|
+
return str1.slice(-i);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return str1.slice(-i);
|
|
297
|
+
}
|
|
298
|
+
function replacePrefix(string, oldPrefix, newPrefix) {
|
|
299
|
+
if (string.slice(0, oldPrefix.length) != oldPrefix) {
|
|
300
|
+
throw Error("string ".concat(JSON.stringify(string), " doesn't start with prefix ").concat(JSON.stringify(oldPrefix), "; this is a bug"));
|
|
301
|
+
}
|
|
302
|
+
return newPrefix + string.slice(oldPrefix.length);
|
|
303
|
+
}
|
|
304
|
+
function replaceSuffix(string, oldSuffix, newSuffix) {
|
|
305
|
+
if (!oldSuffix) {
|
|
306
|
+
return string + newSuffix;
|
|
307
|
+
}
|
|
308
|
+
if (string.slice(-oldSuffix.length) != oldSuffix) {
|
|
309
|
+
throw Error("string ".concat(JSON.stringify(string), " doesn't end with suffix ").concat(JSON.stringify(oldSuffix), "; this is a bug"));
|
|
310
|
+
}
|
|
311
|
+
return string.slice(0, -oldSuffix.length) + newSuffix;
|
|
312
|
+
}
|
|
313
|
+
function removePrefix(string, oldPrefix) {
|
|
314
|
+
return replacePrefix(string, oldPrefix, '');
|
|
315
|
+
}
|
|
316
|
+
function removeSuffix(string, oldSuffix) {
|
|
317
|
+
return replaceSuffix(string, oldSuffix, '');
|
|
318
|
+
}
|
|
319
|
+
function maximumOverlap(string1, string2) {
|
|
320
|
+
return string2.slice(0, overlapCount(string1, string2));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Nicked from https://stackoverflow.com/a/60422853/1709587
|
|
324
|
+
function overlapCount(a, b) {
|
|
325
|
+
// Deal with cases where the strings differ in length
|
|
326
|
+
var startA = 0;
|
|
327
|
+
if (a.length > b.length) {
|
|
328
|
+
startA = a.length - b.length;
|
|
329
|
+
}
|
|
330
|
+
var endB = b.length;
|
|
331
|
+
if (a.length < b.length) {
|
|
332
|
+
endB = a.length;
|
|
333
|
+
}
|
|
334
|
+
// Create a back-reference for each index
|
|
335
|
+
// that should be followed in case of a mismatch.
|
|
336
|
+
// We only need B to make these references:
|
|
337
|
+
var map = Array(endB);
|
|
338
|
+
var k = 0; // Index that lags behind j
|
|
339
|
+
map[0] = 0;
|
|
340
|
+
for (var j = 1; j < endB; j++) {
|
|
341
|
+
if (b[j] == b[k]) {
|
|
342
|
+
map[j] = map[k]; // skip over the same character (optional optimisation)
|
|
343
|
+
} else {
|
|
344
|
+
map[j] = k;
|
|
345
|
+
}
|
|
346
|
+
while (k > 0 && b[j] != b[k]) {
|
|
347
|
+
k = map[k];
|
|
348
|
+
}
|
|
349
|
+
if (b[j] == b[k]) {
|
|
350
|
+
k++;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// Phase 2: use these references while iterating over A
|
|
354
|
+
k = 0;
|
|
355
|
+
for (var i = startA; i < a.length; i++) {
|
|
356
|
+
while (k > 0 && a[i] != b[k]) {
|
|
357
|
+
k = map[k];
|
|
358
|
+
}
|
|
359
|
+
if (a[i] == b[k]) {
|
|
360
|
+
k++;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return k;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Based on https://en.wikipedia.org/wiki/Latin_script_in_Unicode
|
|
367
|
+
//
|
|
368
|
+
// Ranges and exceptions:
|
|
369
|
+
// Latin-1 Supplement, 0080–00FF
|
|
370
|
+
// - U+00D7 × Multiplication sign
|
|
371
|
+
// - U+00F7 ÷ Division sign
|
|
372
|
+
// Latin Extended-A, 0100–017F
|
|
373
|
+
// Latin Extended-B, 0180–024F
|
|
374
|
+
// IPA Extensions, 0250–02AF
|
|
375
|
+
// Spacing Modifier Letters, 02B0–02FF
|
|
376
|
+
// - U+02C7 ˇ ˇ Caron
|
|
377
|
+
// - U+02D8 ˘ ˘ Breve
|
|
378
|
+
// - U+02D9 ˙ ˙ Dot Above
|
|
379
|
+
// - U+02DA ˚ ˚ Ring Above
|
|
380
|
+
// - U+02DB ˛ ˛ Ogonek
|
|
381
|
+
// - U+02DC ˜ ˜ Small Tilde
|
|
382
|
+
// - U+02DD ˝ ˝ Double Acute Accent
|
|
383
|
+
// Latin Extended Additional, 1E00–1EFF
|
|
384
|
+
var extendedWordChars = "a-zA-Z0-9_\\u{C0}-\\u{FF}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}";
|
|
385
|
+
|
|
386
|
+
// Each token is one of the following:
|
|
387
|
+
// - A punctuation mark plus the surrounding whitespace
|
|
388
|
+
// - A word plus the surrounding whitespace
|
|
389
|
+
// - Pure whitespace (but only in the special case where this the entire text
|
|
390
|
+
// is just whitespace)
|
|
391
|
+
//
|
|
392
|
+
// We have to include surrounding whitespace in the tokens because the two
|
|
393
|
+
// alternative approaches produce horribly broken results:
|
|
394
|
+
// * If we just discard the whitespace, we can't fully reproduce the original
|
|
395
|
+
// text from the sequence of tokens and any attempt to render the diff will
|
|
396
|
+
// get the whitespace wrong.
|
|
397
|
+
// * If we have separate tokens for whitespace, then in a typical text every
|
|
398
|
+
// second token will be a single space character. But this often results in
|
|
399
|
+
// the optimal diff between two texts being a perverse one that preserves
|
|
400
|
+
// the spaces between words but deletes and reinserts actual common words.
|
|
401
|
+
// See https://github.com/kpdecker/jsdiff/issues/160#issuecomment-1866099640
|
|
402
|
+
// for an example.
|
|
403
|
+
//
|
|
404
|
+
// Keeping the surrounding whitespace of course has implications for .equals
|
|
405
|
+
// and .join, not just .tokenize.
|
|
406
|
+
|
|
407
|
+
// This regex does NOT fully implement the tokenization rules described above.
|
|
408
|
+
// Instead, it gives runs of whitespace their own "token". The tokenize method
|
|
409
|
+
// then handles stitching whitespace tokens onto adjacent word or punctuation
|
|
410
|
+
// tokens.
|
|
411
|
+
var tokenizeIncludingWhitespace = new RegExp("[".concat(extendedWordChars, "]+|\\s+|[^").concat(extendedWordChars, "]"), 'ug');
|
|
412
|
+
var wordDiff = new Diff();
|
|
413
|
+
wordDiff.equals = function (left, right, options) {
|
|
414
|
+
if (options.ignoreCase) {
|
|
415
|
+
left = left.toLowerCase();
|
|
416
|
+
right = right.toLowerCase();
|
|
417
|
+
}
|
|
418
|
+
return left.trim() === right.trim();
|
|
419
|
+
};
|
|
420
|
+
wordDiff.tokenize = function (value) {
|
|
421
|
+
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
422
|
+
var parts;
|
|
423
|
+
if (options.intlSegmenter) {
|
|
424
|
+
if (options.intlSegmenter.resolvedOptions().granularity != 'word') {
|
|
425
|
+
throw new Error('The segmenter passed must have a granularity of "word"');
|
|
426
|
+
}
|
|
427
|
+
parts = Array.from(options.intlSegmenter.segment(value), function (segment) {
|
|
428
|
+
return segment.segment;
|
|
429
|
+
});
|
|
430
|
+
} else {
|
|
431
|
+
parts = value.match(tokenizeIncludingWhitespace) || [];
|
|
432
|
+
}
|
|
433
|
+
var tokens = [];
|
|
434
|
+
var prevPart = null;
|
|
435
|
+
parts.forEach(function (part) {
|
|
436
|
+
if (/\s/.test(part)) {
|
|
437
|
+
if (prevPart == null) {
|
|
438
|
+
tokens.push(part);
|
|
439
|
+
} else {
|
|
440
|
+
tokens.push(tokens.pop() + part);
|
|
441
|
+
}
|
|
442
|
+
} else if (/\s/.test(prevPart)) {
|
|
443
|
+
if (tokens[tokens.length - 1] == prevPart) {
|
|
444
|
+
tokens.push(tokens.pop() + part);
|
|
445
|
+
} else {
|
|
446
|
+
tokens.push(prevPart + part);
|
|
447
|
+
}
|
|
448
|
+
} else {
|
|
449
|
+
tokens.push(part);
|
|
450
|
+
}
|
|
451
|
+
prevPart = part;
|
|
452
|
+
});
|
|
453
|
+
return tokens;
|
|
454
|
+
};
|
|
455
|
+
wordDiff.join = function (tokens) {
|
|
456
|
+
// Tokens being joined here will always have appeared consecutively in the
|
|
457
|
+
// same text, so we can simply strip off the leading whitespace from all the
|
|
458
|
+
// tokens except the first (and except any whitespace-only tokens - but such
|
|
459
|
+
// a token will always be the first and only token anyway) and then join them
|
|
460
|
+
// and the whitespace around words and punctuation will end up correct.
|
|
461
|
+
return tokens.map(function (token, i) {
|
|
462
|
+
if (i == 0) {
|
|
463
|
+
return token;
|
|
464
|
+
} else {
|
|
465
|
+
return token.replace(/^\s+/, '');
|
|
466
|
+
}
|
|
467
|
+
}).join('');
|
|
468
|
+
};
|
|
469
|
+
wordDiff.postProcess = function (changes, options) {
|
|
470
|
+
if (!changes || options.oneChangePerToken) {
|
|
471
|
+
return changes;
|
|
472
|
+
}
|
|
473
|
+
var lastKeep = null;
|
|
474
|
+
// Change objects representing any insertion or deletion since the last
|
|
475
|
+
// "keep" change object. There can be at most one of each.
|
|
476
|
+
var insertion = null;
|
|
477
|
+
var deletion = null;
|
|
478
|
+
changes.forEach(function (change) {
|
|
479
|
+
if (change.added) {
|
|
480
|
+
insertion = change;
|
|
481
|
+
} else if (change.removed) {
|
|
482
|
+
deletion = change;
|
|
483
|
+
} else {
|
|
484
|
+
if (insertion || deletion) {
|
|
485
|
+
// May be false at start of text
|
|
486
|
+
dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, change);
|
|
487
|
+
}
|
|
488
|
+
lastKeep = change;
|
|
489
|
+
insertion = null;
|
|
490
|
+
deletion = null;
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
if (insertion || deletion) {
|
|
494
|
+
dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, null);
|
|
495
|
+
}
|
|
496
|
+
return changes;
|
|
497
|
+
};
|
|
498
|
+
function diffWords(oldStr, newStr, options) {
|
|
499
|
+
return wordDiff.diff(oldStr, newStr, options);
|
|
500
|
+
}
|
|
501
|
+
function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep) {
|
|
502
|
+
// Before returning, we tidy up the leading and trailing whitespace of the
|
|
503
|
+
// change objects to eliminate cases where trailing whitespace in one object
|
|
504
|
+
// is repeated as leading whitespace in the next.
|
|
505
|
+
// Below are examples of the outcomes we want here to explain the code.
|
|
506
|
+
// I=insert, K=keep, D=delete
|
|
507
|
+
// 1. diffing 'foo bar baz' vs 'foo baz'
|
|
508
|
+
// Prior to cleanup, we have K:'foo ' D:' bar ' K:' baz'
|
|
509
|
+
// After cleanup, we want: K:'foo ' D:'bar ' K:'baz'
|
|
510
|
+
//
|
|
511
|
+
// 2. Diffing 'foo bar baz' vs 'foo qux baz'
|
|
512
|
+
// Prior to cleanup, we have K:'foo ' D:' bar ' I:' qux ' K:' baz'
|
|
513
|
+
// After cleanup, we want K:'foo ' D:'bar' I:'qux' K:' baz'
|
|
514
|
+
//
|
|
515
|
+
// 3. Diffing 'foo\nbar baz' vs 'foo baz'
|
|
516
|
+
// Prior to cleanup, we have K:'foo ' D:'\nbar ' K:' baz'
|
|
517
|
+
// After cleanup, we want K'foo' D:'\nbar' K:' baz'
|
|
518
|
+
//
|
|
519
|
+
// 4. Diffing 'foo baz' vs 'foo\nbar baz'
|
|
520
|
+
// Prior to cleanup, we have K:'foo\n' I:'\nbar ' K:' baz'
|
|
521
|
+
// After cleanup, we ideally want K'foo' I:'\nbar' K:' baz'
|
|
522
|
+
// but don't actually manage this currently (the pre-cleanup change
|
|
523
|
+
// objects don't contain enough information to make it possible).
|
|
524
|
+
//
|
|
525
|
+
// 5. Diffing 'foo bar baz' vs 'foo baz'
|
|
526
|
+
// Prior to cleanup, we have K:'foo ' D:' bar ' K:' baz'
|
|
527
|
+
// After cleanup, we want K:'foo ' D:' bar ' K:'baz'
|
|
528
|
+
//
|
|
529
|
+
// Our handling is unavoidably imperfect in the case where there's a single
|
|
530
|
+
// indel between keeps and the whitespace has changed. For instance, consider
|
|
531
|
+
// diffing 'foo\tbar\nbaz' vs 'foo baz'. Unless we create an extra change
|
|
532
|
+
// object to represent the insertion of the space character (which isn't even
|
|
533
|
+
// a token), we have no way to avoid losing information about the texts'
|
|
534
|
+
// original whitespace in the result we return. Still, we do our best to
|
|
535
|
+
// output something that will look sensible if we e.g. print it with
|
|
536
|
+
// insertions in green and deletions in red.
|
|
537
|
+
|
|
538
|
+
// Between two "keep" change objects (or before the first or after the last
|
|
539
|
+
// change object), we can have either:
|
|
540
|
+
// * A "delete" followed by an "insert"
|
|
541
|
+
// * Just an "insert"
|
|
542
|
+
// * Just a "delete"
|
|
543
|
+
// We handle the three cases separately.
|
|
544
|
+
if (deletion && insertion) {
|
|
545
|
+
var oldWsPrefix = deletion.value.match(/^\s*/)[0];
|
|
546
|
+
var oldWsSuffix = deletion.value.match(/\s*$/)[0];
|
|
547
|
+
var newWsPrefix = insertion.value.match(/^\s*/)[0];
|
|
548
|
+
var newWsSuffix = insertion.value.match(/\s*$/)[0];
|
|
549
|
+
if (startKeep) {
|
|
550
|
+
var commonWsPrefix = longestCommonPrefix(oldWsPrefix, newWsPrefix);
|
|
551
|
+
startKeep.value = replaceSuffix(startKeep.value, newWsPrefix, commonWsPrefix);
|
|
552
|
+
deletion.value = removePrefix(deletion.value, commonWsPrefix);
|
|
553
|
+
insertion.value = removePrefix(insertion.value, commonWsPrefix);
|
|
554
|
+
}
|
|
555
|
+
if (endKeep) {
|
|
556
|
+
var commonWsSuffix = longestCommonSuffix(oldWsSuffix, newWsSuffix);
|
|
557
|
+
endKeep.value = replacePrefix(endKeep.value, newWsSuffix, commonWsSuffix);
|
|
558
|
+
deletion.value = removeSuffix(deletion.value, commonWsSuffix);
|
|
559
|
+
insertion.value = removeSuffix(insertion.value, commonWsSuffix);
|
|
560
|
+
}
|
|
561
|
+
} else if (insertion) {
|
|
562
|
+
// The whitespaces all reflect what was in the new text rather than
|
|
563
|
+
// the old, so we essentially have no information about whitespace
|
|
564
|
+
// insertion or deletion. We just want to dedupe the whitespace.
|
|
565
|
+
// We do that by having each change object keep its trailing
|
|
566
|
+
// whitespace and deleting duplicate leading whitespace where
|
|
567
|
+
// present.
|
|
568
|
+
if (startKeep) {
|
|
569
|
+
insertion.value = insertion.value.replace(/^\s*/, '');
|
|
570
|
+
}
|
|
571
|
+
if (endKeep) {
|
|
572
|
+
endKeep.value = endKeep.value.replace(/^\s*/, '');
|
|
573
|
+
}
|
|
574
|
+
// otherwise we've got a deletion and no insertion
|
|
575
|
+
} else if (startKeep && endKeep) {
|
|
576
|
+
var newWsFull = endKeep.value.match(/^\s*/)[0],
|
|
577
|
+
delWsStart = deletion.value.match(/^\s*/)[0],
|
|
578
|
+
delWsEnd = deletion.value.match(/\s*$/)[0];
|
|
579
|
+
|
|
580
|
+
// Any whitespace that comes straight after startKeep in both the old and
|
|
581
|
+
// new texts, assign to startKeep and remove from the deletion.
|
|
582
|
+
var newWsStart = longestCommonPrefix(newWsFull, delWsStart);
|
|
583
|
+
deletion.value = removePrefix(deletion.value, newWsStart);
|
|
584
|
+
|
|
585
|
+
// Any whitespace that comes straight before endKeep in both the old and
|
|
586
|
+
// new texts, and hasn't already been assigned to startKeep, assign to
|
|
587
|
+
// endKeep and remove from the deletion.
|
|
588
|
+
var newWsEnd = longestCommonSuffix(removePrefix(newWsFull, newWsStart), delWsEnd);
|
|
589
|
+
deletion.value = removeSuffix(deletion.value, newWsEnd);
|
|
590
|
+
endKeep.value = replacePrefix(endKeep.value, newWsFull, newWsEnd);
|
|
591
|
+
|
|
592
|
+
// If there's any whitespace from the new text that HASN'T already been
|
|
593
|
+
// assigned, assign it to the start:
|
|
594
|
+
startKeep.value = replaceSuffix(startKeep.value, newWsFull, newWsFull.slice(0, newWsFull.length - newWsEnd.length));
|
|
595
|
+
} else if (endKeep) {
|
|
596
|
+
// We are at the start of the text. Preserve all the whitespace on
|
|
597
|
+
// endKeep, and just remove whitespace from the end of deletion to the
|
|
598
|
+
// extent that it overlaps with the start of endKeep.
|
|
599
|
+
var endKeepWsPrefix = endKeep.value.match(/^\s*/)[0];
|
|
600
|
+
var deletionWsSuffix = deletion.value.match(/\s*$/)[0];
|
|
601
|
+
var overlap = maximumOverlap(deletionWsSuffix, endKeepWsPrefix);
|
|
602
|
+
deletion.value = removeSuffix(deletion.value, overlap);
|
|
603
|
+
} else if (startKeep) {
|
|
604
|
+
// We are at the END of the text. Preserve all the whitespace on
|
|
605
|
+
// startKeep, and just remove whitespace from the start of deletion to
|
|
606
|
+
// the extent that it overlaps with the end of startKeep.
|
|
607
|
+
var startKeepWsSuffix = startKeep.value.match(/\s*$/)[0];
|
|
608
|
+
var deletionWsPrefix = deletion.value.match(/^\s*/)[0];
|
|
609
|
+
var _overlap = maximumOverlap(startKeepWsSuffix, deletionWsPrefix);
|
|
610
|
+
deletion.value = removePrefix(deletion.value, _overlap);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
var wordWithSpaceDiff = new Diff();
|
|
614
|
+
wordWithSpaceDiff.tokenize = function (value) {
|
|
615
|
+
// Slightly different to the tokenizeIncludingWhitespace regex used above in
|
|
616
|
+
// that this one treats each individual newline as a distinct tokens, rather
|
|
617
|
+
// than merging them into other surrounding whitespace. This was requested
|
|
618
|
+
// in https://github.com/kpdecker/jsdiff/issues/180 &
|
|
619
|
+
// https://github.com/kpdecker/jsdiff/issues/211
|
|
620
|
+
var regex = new RegExp("(\\r?\\n)|[".concat(extendedWordChars, "]+|[^\\S\\n\\r]+|[^").concat(extendedWordChars, "]"), 'ug');
|
|
621
|
+
return value.match(regex) || [];
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
var lineDiff = new Diff();
|
|
625
|
+
lineDiff.tokenize = function (value, options) {
|
|
626
|
+
if (options.stripTrailingCr) {
|
|
627
|
+
// remove one \r before \n to match GNU diff's --strip-trailing-cr behavior
|
|
628
|
+
value = value.replace(/\r\n/g, '\n');
|
|
629
|
+
}
|
|
630
|
+
var retLines = [],
|
|
631
|
+
linesAndNewlines = value.split(/(\n|\r\n)/);
|
|
632
|
+
|
|
633
|
+
// Ignore the final empty token that occurs if the string ends with a new line
|
|
634
|
+
if (!linesAndNewlines[linesAndNewlines.length - 1]) {
|
|
635
|
+
linesAndNewlines.pop();
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Merge the content and line separators into single tokens
|
|
639
|
+
for (var i = 0; i < linesAndNewlines.length; i++) {
|
|
640
|
+
var line = linesAndNewlines[i];
|
|
641
|
+
if (i % 2 && !options.newlineIsToken) {
|
|
642
|
+
retLines[retLines.length - 1] += line;
|
|
643
|
+
} else {
|
|
644
|
+
retLines.push(line);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return retLines;
|
|
648
|
+
};
|
|
649
|
+
lineDiff.equals = function (left, right, options) {
|
|
650
|
+
// If we're ignoring whitespace, we need to normalise lines by stripping
|
|
651
|
+
// whitespace before checking equality. (This has an annoying interaction
|
|
652
|
+
// with newlineIsToken that requires special handling: if newlines get their
|
|
653
|
+
// own token, then we DON'T want to trim the *newline* tokens down to empty
|
|
654
|
+
// strings, since this would cause us to treat whitespace-only line content
|
|
655
|
+
// as equal to a separator between lines, which would be weird and
|
|
656
|
+
// inconsistent with the documented behavior of the options.)
|
|
657
|
+
if (options.ignoreWhitespace) {
|
|
658
|
+
if (!options.newlineIsToken || !left.includes('\n')) {
|
|
659
|
+
left = left.trim();
|
|
660
|
+
}
|
|
661
|
+
if (!options.newlineIsToken || !right.includes('\n')) {
|
|
662
|
+
right = right.trim();
|
|
663
|
+
}
|
|
664
|
+
} else if (options.ignoreNewlineAtEof && !options.newlineIsToken) {
|
|
665
|
+
if (left.endsWith('\n')) {
|
|
666
|
+
left = left.slice(0, -1);
|
|
667
|
+
}
|
|
668
|
+
if (right.endsWith('\n')) {
|
|
669
|
+
right = right.slice(0, -1);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return Diff.prototype.equals.call(this, left, right, options);
|
|
673
|
+
};
|
|
674
|
+
function diffLines(oldStr, newStr, callback) {
|
|
675
|
+
return lineDiff.diff(oldStr, newStr, callback);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
var sentenceDiff = new Diff();
|
|
679
|
+
sentenceDiff.tokenize = function (value) {
|
|
680
|
+
return value.split(/(\S.+?[.!?])(?=\s+|$)/);
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
var cssDiff = new Diff();
|
|
684
|
+
cssDiff.tokenize = function (value) {
|
|
685
|
+
return value.split(/([{}:;,]|\s+)/);
|
|
686
|
+
};
|
|
687
|
+
function _typeof(o) {
|
|
688
|
+
"@babel/helpers - typeof";
|
|
689
|
+
|
|
690
|
+
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
|
|
691
|
+
return typeof o;
|
|
692
|
+
} : function (o) {
|
|
693
|
+
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
|
|
694
|
+
}, _typeof(o);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
var jsonDiff = new Diff();
|
|
698
|
+
// Discriminate between two lines of pretty-printed, serialized JSON where one of them has a
|
|
699
|
+
// dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output:
|
|
700
|
+
jsonDiff.useLongestToken = true;
|
|
701
|
+
jsonDiff.tokenize = lineDiff.tokenize;
|
|
702
|
+
jsonDiff.castInput = function (value, options) {
|
|
703
|
+
var undefinedReplacement = options.undefinedReplacement,
|
|
704
|
+
_options$stringifyRep = options.stringifyReplacer,
|
|
705
|
+
stringifyReplacer = _options$stringifyRep === void 0 ? function (k, v) {
|
|
706
|
+
return typeof v === 'undefined' ? undefinedReplacement : v;
|
|
707
|
+
} : _options$stringifyRep;
|
|
708
|
+
return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), stringifyReplacer, ' ');
|
|
709
|
+
};
|
|
710
|
+
jsonDiff.equals = function (left, right, options) {
|
|
711
|
+
return Diff.prototype.equals.call(jsonDiff, left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'), options);
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
// This function handles the presence of circular references by bailing out when encountering an
|
|
715
|
+
// object that is already on the "stack" of items being processed. Accepts an optional replacer
|
|
716
|
+
function canonicalize(obj, stack, replacementStack, replacer, key) {
|
|
717
|
+
stack = stack || [];
|
|
718
|
+
replacementStack = replacementStack || [];
|
|
719
|
+
if (replacer) {
|
|
720
|
+
obj = replacer(key, obj);
|
|
721
|
+
}
|
|
722
|
+
var i;
|
|
723
|
+
for (i = 0; i < stack.length; i += 1) {
|
|
724
|
+
if (stack[i] === obj) {
|
|
725
|
+
return replacementStack[i];
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
var canonicalizedObj;
|
|
729
|
+
if ('[object Array]' === Object.prototype.toString.call(obj)) {
|
|
730
|
+
stack.push(obj);
|
|
731
|
+
canonicalizedObj = new Array(obj.length);
|
|
732
|
+
replacementStack.push(canonicalizedObj);
|
|
733
|
+
for (i = 0; i < obj.length; i += 1) {
|
|
734
|
+
canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, key);
|
|
735
|
+
}
|
|
736
|
+
stack.pop();
|
|
737
|
+
replacementStack.pop();
|
|
738
|
+
return canonicalizedObj;
|
|
739
|
+
}
|
|
740
|
+
if (obj && obj.toJSON) {
|
|
741
|
+
obj = obj.toJSON();
|
|
742
|
+
}
|
|
743
|
+
if (_typeof(obj) === 'object' && obj !== null) {
|
|
744
|
+
stack.push(obj);
|
|
745
|
+
canonicalizedObj = {};
|
|
746
|
+
replacementStack.push(canonicalizedObj);
|
|
747
|
+
var sortedKeys = [],
|
|
748
|
+
_key;
|
|
749
|
+
for (_key in obj) {
|
|
750
|
+
/* istanbul ignore else */
|
|
751
|
+
if (Object.prototype.hasOwnProperty.call(obj, _key)) {
|
|
752
|
+
sortedKeys.push(_key);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
sortedKeys.sort();
|
|
756
|
+
for (i = 0; i < sortedKeys.length; i += 1) {
|
|
757
|
+
_key = sortedKeys[i];
|
|
758
|
+
canonicalizedObj[_key] = canonicalize(obj[_key], stack, replacementStack, replacer, _key);
|
|
759
|
+
}
|
|
760
|
+
stack.pop();
|
|
761
|
+
replacementStack.pop();
|
|
762
|
+
} else {
|
|
763
|
+
canonicalizedObj = obj;
|
|
764
|
+
}
|
|
765
|
+
return canonicalizedObj;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
var arrayDiff = new Diff();
|
|
769
|
+
arrayDiff.tokenize = function (value) {
|
|
770
|
+
return value.slice();
|
|
771
|
+
};
|
|
772
|
+
arrayDiff.join = arrayDiff.removeEmpty = function (value) {
|
|
773
|
+
return value;
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* Compute a structured diff between two strings.
|
|
778
|
+
*
|
|
779
|
+
* @param oldText - the "before" text
|
|
780
|
+
* @param newText - the "after" text
|
|
781
|
+
* @param contextLines - number of unchanged lines to show around each change
|
|
782
|
+
* @returns a DiffResult with hunks, additions, and deletions counts
|
|
783
|
+
*/
|
|
784
|
+
function computeDiff(oldText, newText, contextLines = 3) {
|
|
785
|
+
const allLines = buildDiffLines(oldText, newText);
|
|
786
|
+
return groupIntoHunks(allLines, contextLines);
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Build paired rows for split (side-by-side) view from flat diff lines.
|
|
790
|
+
* Context lines appear on both sides. Adjacent removed+added lines
|
|
791
|
+
* are paired into the same row.
|
|
792
|
+
*
|
|
793
|
+
* @param lines - flat diff lines
|
|
794
|
+
* @returns paired rows for split rendering
|
|
795
|
+
*/
|
|
796
|
+
function buildSplitLines(lines) {
|
|
797
|
+
const rows = [];
|
|
798
|
+
let i = 0;
|
|
799
|
+
while (i < lines.length) {
|
|
800
|
+
const line = lines[i];
|
|
801
|
+
if (line.type === 'context') {
|
|
802
|
+
rows.push({ left: line, right: line });
|
|
803
|
+
i++;
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
i = collectAndPairChanges(lines, i, rows);
|
|
807
|
+
}
|
|
808
|
+
return rows;
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Collect consecutive removed then added lines starting at `index`,
|
|
812
|
+
* pair them into split rows, and return the new index.
|
|
813
|
+
* @param lines - flat diff lines
|
|
814
|
+
* @param index - starting index
|
|
815
|
+
* @param rows - output array to push paired rows into
|
|
816
|
+
*/
|
|
817
|
+
function collectAndPairChanges(lines, index, rows) {
|
|
818
|
+
const removed = [];
|
|
819
|
+
while (index < lines.length && lines[index].type === 'removed') {
|
|
820
|
+
removed.push(lines[index]);
|
|
821
|
+
index++;
|
|
822
|
+
}
|
|
823
|
+
const added = [];
|
|
824
|
+
while (index < lines.length && lines[index].type === 'added') {
|
|
825
|
+
added.push(lines[index]);
|
|
826
|
+
index++;
|
|
827
|
+
}
|
|
828
|
+
const maxPairs = Math.max(removed.length, added.length);
|
|
829
|
+
for (let j = 0; j < maxPairs; j++) {
|
|
830
|
+
rows.push({
|
|
831
|
+
left: j < removed.length ? removed[j] : undefined,
|
|
832
|
+
right: j < added.length ? added[j] : undefined,
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
return index;
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Normalize values for diffing. If `reformatJson` is true,
|
|
839
|
+
* parse and re-stringify with sorted keys and consistent indentation.
|
|
840
|
+
* @param value
|
|
841
|
+
* @param reformatJson
|
|
842
|
+
*/
|
|
843
|
+
function normalizeForDiff(value, reformatJson = false) {
|
|
844
|
+
if (typeof value === 'object' && value !== null) {
|
|
845
|
+
return JSON.stringify(sortKeysDeep(value), null, 4);
|
|
846
|
+
}
|
|
847
|
+
if (typeof value === 'string' && reformatJson) {
|
|
848
|
+
try {
|
|
849
|
+
const parsed = JSON.parse(value);
|
|
850
|
+
return JSON.stringify(sortKeysDeep(parsed), null, 4);
|
|
851
|
+
}
|
|
852
|
+
catch (_a) {
|
|
853
|
+
return value;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
return String(value !== null && value !== void 0 ? value : '');
|
|
857
|
+
}
|
|
858
|
+
function sortKeysDeep(obj) {
|
|
859
|
+
if (Array.isArray(obj)) {
|
|
860
|
+
return obj.map(sortKeysDeep);
|
|
861
|
+
}
|
|
862
|
+
if (obj !== null && typeof obj === 'object') {
|
|
863
|
+
const sorted = {};
|
|
864
|
+
const keys = Object.keys(obj).sort((a, b) => a.localeCompare(b));
|
|
865
|
+
for (const key of keys) {
|
|
866
|
+
sorted[key] = sortKeysDeep(obj[key]);
|
|
867
|
+
}
|
|
868
|
+
return sorted;
|
|
869
|
+
}
|
|
870
|
+
return obj;
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Build a flat list of DiffLines from two text strings.
|
|
874
|
+
* @param oldText
|
|
875
|
+
* @param newText
|
|
876
|
+
*/
|
|
877
|
+
function buildDiffLines(oldText, newText) {
|
|
878
|
+
const changes = diffLines(oldText, newText);
|
|
879
|
+
const lines = [];
|
|
880
|
+
let oldLineNum = 1;
|
|
881
|
+
let newLineNum = 1;
|
|
882
|
+
for (const change of changes) {
|
|
883
|
+
const changeLines = splitIntoLines(change.value);
|
|
884
|
+
for (const line of changeLines) {
|
|
885
|
+
if (change.added) {
|
|
886
|
+
lines.push({
|
|
887
|
+
type: 'added',
|
|
888
|
+
content: line,
|
|
889
|
+
newLineNumber: newLineNum++,
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
else if (change.removed) {
|
|
893
|
+
lines.push({
|
|
894
|
+
type: 'removed',
|
|
895
|
+
content: line,
|
|
896
|
+
oldLineNumber: oldLineNum++,
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
else {
|
|
900
|
+
lines.push({
|
|
901
|
+
type: 'context',
|
|
902
|
+
content: line,
|
|
903
|
+
oldLineNumber: oldLineNum++,
|
|
904
|
+
newLineNumber: newLineNum++,
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
addWordLevelHighlighting(lines);
|
|
910
|
+
return lines;
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Split a string into lines, handling the trailing newline
|
|
914
|
+
* that jsdiff includes in each change value.
|
|
915
|
+
* @param text
|
|
916
|
+
*/
|
|
917
|
+
function splitIntoLines(text) {
|
|
918
|
+
if (!text) {
|
|
919
|
+
return [];
|
|
920
|
+
}
|
|
921
|
+
const lines = text.split('\n');
|
|
922
|
+
// jsdiff includes a trailing newline, producing an empty last element
|
|
923
|
+
if (lines.length > 0 && lines.at(-1) === '') {
|
|
924
|
+
lines.pop();
|
|
925
|
+
}
|
|
926
|
+
return lines;
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Pair adjacent removed+added lines and compute word-level diffs
|
|
930
|
+
* to highlight only the specific segments that changed.
|
|
931
|
+
* @param lines
|
|
932
|
+
*/
|
|
933
|
+
function addWordLevelHighlighting(lines) {
|
|
934
|
+
let i = 0;
|
|
935
|
+
while (i < lines.length) {
|
|
936
|
+
// Find consecutive removed lines
|
|
937
|
+
const removedStart = i;
|
|
938
|
+
while (i < lines.length && lines[i].type === 'removed') {
|
|
939
|
+
i++;
|
|
940
|
+
}
|
|
941
|
+
const removedEnd = i;
|
|
942
|
+
// Find consecutive added lines right after
|
|
943
|
+
const addedStart = i;
|
|
944
|
+
while (i < lines.length && lines[i].type === 'added') {
|
|
945
|
+
i++;
|
|
946
|
+
}
|
|
947
|
+
const addedEnd = i;
|
|
948
|
+
const removedCount = removedEnd - removedStart;
|
|
949
|
+
const addedCount = addedEnd - addedStart;
|
|
950
|
+
// Pair them up for word-level highlighting
|
|
951
|
+
if (removedCount > 0 && addedCount > 0) {
|
|
952
|
+
const pairCount = Math.min(removedCount, addedCount);
|
|
953
|
+
for (let j = 0; j < pairCount; j++) {
|
|
954
|
+
const removedLine = lines[removedStart + j];
|
|
955
|
+
const addedLine = lines[addedStart + j];
|
|
956
|
+
const [removedSegments, addedSegments] = computeWordSegments(removedLine.content, addedLine.content);
|
|
957
|
+
removedLine.segments = removedSegments;
|
|
958
|
+
addedLine.segments = addedSegments;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
// Skip context lines
|
|
962
|
+
if (i === removedStart) {
|
|
963
|
+
i++;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Compute word-level diff segments for a pair of lines.
|
|
969
|
+
* @param oldContent
|
|
970
|
+
* @param newContent
|
|
971
|
+
*/
|
|
972
|
+
function computeWordSegments(oldContent, newContent) {
|
|
973
|
+
const wordChanges = diffWords(oldContent, newContent);
|
|
974
|
+
const removedSegments = [];
|
|
975
|
+
const addedSegments = [];
|
|
976
|
+
for (const change of wordChanges) {
|
|
977
|
+
if (change.added) {
|
|
978
|
+
addedSegments.push({ value: change.value, type: 'added' });
|
|
979
|
+
}
|
|
980
|
+
else if (change.removed) {
|
|
981
|
+
removedSegments.push({ value: change.value, type: 'removed' });
|
|
982
|
+
}
|
|
983
|
+
else {
|
|
984
|
+
removedSegments.push({ value: change.value, type: 'equal' });
|
|
985
|
+
addedSegments.push({ value: change.value, type: 'equal' });
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return [removedSegments, addedSegments];
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Group a flat list of diff lines into hunks with context.
|
|
992
|
+
* @param lines
|
|
993
|
+
* @param contextLines
|
|
994
|
+
*/
|
|
995
|
+
function groupIntoHunks(lines, contextLines) {
|
|
996
|
+
if (lines.length === 0) {
|
|
997
|
+
return { hunks: [], additions: 0, deletions: 0, allLines: lines };
|
|
998
|
+
}
|
|
999
|
+
let additions = 0;
|
|
1000
|
+
let deletions = 0;
|
|
1001
|
+
for (const line of lines) {
|
|
1002
|
+
if (line.type === 'added') {
|
|
1003
|
+
additions++;
|
|
1004
|
+
}
|
|
1005
|
+
else if (line.type === 'removed') {
|
|
1006
|
+
deletions++;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
// If there are no changes, return a single empty result
|
|
1010
|
+
if (additions === 0 && deletions === 0) {
|
|
1011
|
+
return { hunks: [], additions: 0, deletions: 0, allLines: lines };
|
|
1012
|
+
}
|
|
1013
|
+
// Find ranges of changed lines with their context
|
|
1014
|
+
const changeIndices = [];
|
|
1015
|
+
for (const [i, line] of lines.entries()) {
|
|
1016
|
+
if (line.type !== 'context') {
|
|
1017
|
+
changeIndices.push(i);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
// Build hunk boundaries
|
|
1021
|
+
const hunkBoundaries = buildHunkBoundaries(changeIndices, lines.length, contextLines);
|
|
1022
|
+
const hunks = [];
|
|
1023
|
+
let prevEnd = 0;
|
|
1024
|
+
for (const boundary of hunkBoundaries) {
|
|
1025
|
+
const hunkLines = lines.slice(boundary.start, boundary.end);
|
|
1026
|
+
const hiddenBefore = boundary.start - prevEnd;
|
|
1027
|
+
const collapsedBefore = hiddenBefore > 0 ? hiddenBefore : undefined;
|
|
1028
|
+
hunks.push({
|
|
1029
|
+
lines: hunkLines,
|
|
1030
|
+
collapsedBefore,
|
|
1031
|
+
startIndex: boundary.start,
|
|
1032
|
+
});
|
|
1033
|
+
prevEnd = boundary.end;
|
|
1034
|
+
}
|
|
1035
|
+
// Calculate collapsed lines after the last hunk
|
|
1036
|
+
const lastBoundary = hunkBoundaries.at(-1);
|
|
1037
|
+
const collapsedAfter = lastBoundary.end < lines.length
|
|
1038
|
+
? lines.length - lastBoundary.end
|
|
1039
|
+
: undefined;
|
|
1040
|
+
return { hunks, additions, deletions, collapsedAfter, allLines: lines };
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Build the start/end boundaries of each hunk based on change positions.
|
|
1044
|
+
* Merges hunks that overlap or are adjacent.
|
|
1045
|
+
* @param changeIndices
|
|
1046
|
+
* @param totalLines
|
|
1047
|
+
* @param contextLines
|
|
1048
|
+
*/
|
|
1049
|
+
function buildHunkBoundaries(changeIndices, totalLines, contextLines) {
|
|
1050
|
+
if (changeIndices.length === 0) {
|
|
1051
|
+
return [];
|
|
1052
|
+
}
|
|
1053
|
+
const boundaries = [];
|
|
1054
|
+
let currentStart = Math.max(0, changeIndices[0] - contextLines);
|
|
1055
|
+
let currentEnd = Math.min(totalLines, changeIndices[0] + contextLines + 1);
|
|
1056
|
+
for (let i = 1; i < changeIndices.length; i++) {
|
|
1057
|
+
const changeStart = Math.max(0, changeIndices[i] - contextLines);
|
|
1058
|
+
const changeEnd = Math.min(totalLines, changeIndices[i] + contextLines + 1);
|
|
1059
|
+
if (changeStart <= currentEnd) {
|
|
1060
|
+
// Merge overlapping hunks
|
|
1061
|
+
currentEnd = Math.max(currentEnd, changeEnd);
|
|
1062
|
+
}
|
|
1063
|
+
else {
|
|
1064
|
+
boundaries.push({ start: currentStart, end: currentEnd });
|
|
1065
|
+
currentStart = changeStart;
|
|
1066
|
+
currentEnd = changeEnd;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
boundaries.push({ start: currentStart, end: currentEnd });
|
|
1070
|
+
return boundaries;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* Tokenize a text fragment for syntax highlighting.
|
|
1075
|
+
* Returns the original text as a single plain token when the
|
|
1076
|
+
* language is not supported.
|
|
1077
|
+
*
|
|
1078
|
+
* @param text - the text to tokenize
|
|
1079
|
+
* @param language - the language identifier (e.g. "json")
|
|
1080
|
+
* @returns array of syntax tokens
|
|
1081
|
+
*/
|
|
1082
|
+
function tokenize(text, language) {
|
|
1083
|
+
if (!language || text.length === 0) {
|
|
1084
|
+
return [{ value: text, type: 'plain' }];
|
|
1085
|
+
}
|
|
1086
|
+
if (language === 'json') {
|
|
1087
|
+
return tokenizeJson(text);
|
|
1088
|
+
}
|
|
1089
|
+
return [{ value: text, type: 'plain' }];
|
|
1090
|
+
}
|
|
1091
|
+
// ─── JSON tokenizer ─────────────────────────────────────────────────
|
|
1092
|
+
/**
|
|
1093
|
+
* Regex-based JSON tokenizer.
|
|
1094
|
+
* Handles partial lines (individual lines of a JSON document).
|
|
1095
|
+
*/
|
|
1096
|
+
const JSON_PATTERNS = [
|
|
1097
|
+
// String literals (keys and values)
|
|
1098
|
+
[/"(?:[^"\\]|\\.)*"/, 'string'],
|
|
1099
|
+
// Numbers
|
|
1100
|
+
[/-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/, 'number'],
|
|
1101
|
+
// Booleans
|
|
1102
|
+
[/\b(?:true|false)\b/, 'boolean'],
|
|
1103
|
+
// Null
|
|
1104
|
+
[/\bnull\b/, 'null'],
|
|
1105
|
+
// Punctuation
|
|
1106
|
+
[/[{}[\]:,]/, 'punctuation'],
|
|
1107
|
+
];
|
|
1108
|
+
const JSON_REGEX = new RegExp(JSON_PATTERNS.map(([re]) => `(${re.source})`).join('|'), 'g');
|
|
1109
|
+
function tokenizeJson(text) {
|
|
1110
|
+
const tokens = [];
|
|
1111
|
+
let lastIndex = 0;
|
|
1112
|
+
JSON_REGEX.lastIndex = 0;
|
|
1113
|
+
let match = JSON_REGEX.exec(text);
|
|
1114
|
+
while (match !== null) {
|
|
1115
|
+
// Plain text before this match
|
|
1116
|
+
if (match.index > lastIndex) {
|
|
1117
|
+
tokens.push({
|
|
1118
|
+
value: text.slice(lastIndex, match.index),
|
|
1119
|
+
type: 'plain',
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
// Determine which capture group matched
|
|
1123
|
+
const tokenType = getMatchedTokenType(match);
|
|
1124
|
+
const value = match[0];
|
|
1125
|
+
// Distinguish JSON keys from string values:
|
|
1126
|
+
// A key is a string followed by optional whitespace and a colon
|
|
1127
|
+
if (tokenType === 'string') {
|
|
1128
|
+
const afterMatch = text.slice(match.index + value.length);
|
|
1129
|
+
if (/^\s*:/.test(afterMatch)) {
|
|
1130
|
+
tokens.push({ value, type: 'key' });
|
|
1131
|
+
}
|
|
1132
|
+
else {
|
|
1133
|
+
tokens.push({ value, type: 'string' });
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
else {
|
|
1137
|
+
tokens.push({ value, type: tokenType });
|
|
1138
|
+
}
|
|
1139
|
+
lastIndex = match.index + value.length;
|
|
1140
|
+
match = JSON_REGEX.exec(text);
|
|
1141
|
+
}
|
|
1142
|
+
// Remaining plain text
|
|
1143
|
+
if (lastIndex < text.length) {
|
|
1144
|
+
tokens.push({ value: text.slice(lastIndex), type: 'plain' });
|
|
1145
|
+
}
|
|
1146
|
+
return tokens;
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Determine which pattern matched by checking capture groups.
|
|
1150
|
+
* @param match - the regex match result
|
|
1151
|
+
*/
|
|
1152
|
+
function getMatchedTokenType(match) {
|
|
1153
|
+
for (const [index, [, type]] of JSON_PATTERNS.entries()) {
|
|
1154
|
+
if (match[index + 1] !== undefined) {
|
|
1155
|
+
return type;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
return 'plain';
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
/**
|
|
1162
|
+
* Pure utility functions for search-within-diff functionality.
|
|
1163
|
+
*/
|
|
1164
|
+
/**
|
|
1165
|
+
* Escape special regex characters in a search term so it can
|
|
1166
|
+
* be used as a literal pattern in a RegExp constructor.
|
|
1167
|
+
*
|
|
1168
|
+
* @param term - the raw search string
|
|
1169
|
+
* @returns regex-safe string
|
|
1170
|
+
*/
|
|
1171
|
+
function escapeRegex(term) {
|
|
1172
|
+
return term.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw `\$&`);
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Build a case-insensitive regex that captures the search term.
|
|
1176
|
+
* Returns `null` when the term is empty.
|
|
1177
|
+
*
|
|
1178
|
+
* @param term - the raw search string
|
|
1179
|
+
* @returns a RegExp with a single capture group, or null
|
|
1180
|
+
*/
|
|
1181
|
+
function buildSearchRegex(term) {
|
|
1182
|
+
if (!term) {
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
1185
|
+
return new RegExp(`(${escapeRegex(term)})`, 'gi');
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Calculate the next match index when navigating forward or backward,
|
|
1189
|
+
* wrapping around at the boundaries.
|
|
1190
|
+
*
|
|
1191
|
+
* @param currentIndex - current zero-based match index
|
|
1192
|
+
* @param direction - +1 for next, -1 for previous
|
|
1193
|
+
* @param total - total number of matches
|
|
1194
|
+
* @returns the new match index
|
|
1195
|
+
*/
|
|
1196
|
+
function navigateMatchIndex(currentIndex, direction, total) {
|
|
1197
|
+
if (total === 0) {
|
|
1198
|
+
return 0;
|
|
1199
|
+
}
|
|
1200
|
+
return (currentIndex + direction + total) % total;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
/**
|
|
1204
|
+
* Pure utility functions for extracting text content from diff structures.
|
|
1205
|
+
*/
|
|
1206
|
+
/**
|
|
1207
|
+
* Extract the text content of removed lines from a unified change block.
|
|
1208
|
+
*
|
|
1209
|
+
* @param lines - consecutive changed lines from a unified diff hunk
|
|
1210
|
+
* @returns the removed lines joined by newlines, or empty string if none
|
|
1211
|
+
*/
|
|
1212
|
+
function extractRemovedContent(lines) {
|
|
1213
|
+
return lines
|
|
1214
|
+
.filter((line) => line.type === 'removed')
|
|
1215
|
+
.map((line) => line.content)
|
|
1216
|
+
.join('\n');
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Extract the text content of removed lines from a split change block.
|
|
1220
|
+
*
|
|
1221
|
+
* @param rows - consecutive changed rows from a split diff hunk
|
|
1222
|
+
* @returns the removed lines joined by newlines, or empty string if none
|
|
1223
|
+
*/
|
|
1224
|
+
function extractRemovedContentFromSplit(rows) {
|
|
1225
|
+
return rows
|
|
1226
|
+
.filter((row) => { var _a; return ((_a = row.left) === null || _a === void 0 ? void 0 : _a.type) === 'removed'; })
|
|
1227
|
+
.map((row) => row.left.content)
|
|
1228
|
+
.join('\n');
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
const codeDiffCss = () => `@charset "UTF-8";*,*::before,*::after{box-sizing:border-box;min-width:0;min-height:0}:host(limel-code-diff){--diff-added-bg:rgb(var(--color-green-default), 0.1);--diff-added-bg-hover:rgb(var(--color-green-default), 0.3);--diff-added-highlight-bg:rgb(var(--color-green-default), 0.3);--diff-removed-bg:rgb(var(--color-red-default), 0.1);--diff-removed-bg-hover:rgb(var(--color-red-default), 0.3);--diff-removed-highlight-bg:rgb(var(--color-red-default), 0.3);--diff-context-bg:transparent;--diff-indicator-added-color:rgb(var(--color-green-default));--diff-indicator-removed-color:rgb(var(--color-red-default));--diff-stat-added-color:rgb(var(--color-green-default));--diff-stat-removed-color:rgb(var(--color-red-default));--search-match-bg:rgb(var(--color-amber-default), 0.3);--search-match-current-bg:rgb(var(--color-amber-default), 0.6);--diff-line-hover-bg:rgb(var(--contrast-800), 0.08);--diff-gutter-bg:rgb(var(--contrast-200));--diff-gutter-text-color:rgb(var(--contrast-700));--diff-text-color:rgb(var(--contrast-1100));--diff-border-color:rgb(var(--contrast-400));--diff-collapsed-bg:rgb(var(--contrast-200));--diff-collapsed-text-color:rgb(var(--contrast-800));--diff-header-bg:rgb(var(--contrast-200));--diff-empty-text-color:rgb(var(--contrast-700));--diff-split-divider-color:rgb(var(--contrast-400));--diff-empty-cell-bg:rgb(var(--contrast-100));--syntax-string-color:rgb(var(--color-green-dark));--syntax-number-color:rgb(var(--color-blue-default));--syntax-boolean-color:rgb(var(--color-amber-darker));--syntax-key-color:rgb(var(--color-sky-dark));--syntax-null-color:rgb(var(--contrast-700));--syntax-punctuation-color:rgb(var(--contrast-700));--search-bar-bg:rgb(var(--contrast-100));--search-bar-border:rgb(var(--contrast-400));--limel-code-diff-line-number-padding:0.25rem;font-family:ui-sans-serif, system-ui, sans-serif;display:flex;flex-direction:column;width:100%;height:100%;color:var(--diff-text-color);border:1px solid var(--diff-border-color);border-radius:0.5rem;max-height:var(--code-diff-max-height, none)}.screen-reader-only{position:absolute;width:0;height:0;margin:-1px;padding:0;border:0;overflow:hidden;clip:rect(0, 0, 0, 0);clip-path:inset(50%);white-space:nowrap}.diff-header{flex-shrink:0;display:flex;align-items:center;justify-content:space-between;padding:0.125rem 0.125rem 0.125rem 0.5rem;background:var(--diff-header-bg);border-bottom:1px solid var(--diff-border-color);font-family:ui-sans-serif, system-ui, sans-serif;font-size:0.75rem;border-radius:0.5rem 0.5rem 0 0}.diff-header__labels{display:flex;gap:0.75rem;font-weight:500}.diff-header__old,.diff-header__new{padding:0.125rem 0.25rem;border-radius:0.25rem;box-shadow:var(--shadow-brighten-edges-outside)}.diff-header__old{background-color:var(--diff-removed-bg)}.diff-header__new{background-color:var(--diff-added-bg)}.diff-header__actions{display:flex;align-items:center;gap:0.25rem}.search-toggle--active{--limel-theme-on-surface-color:var(--mdc-theme-primary)}.diff-header__stats{font-family:ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, "DejaVu Sans Mono", monospace;display:flex;gap:0.5rem;margin-right:0.5rem}.stat{font-family:ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, "DejaVu Sans Mono", monospace;font-size:0.8125rem;font-weight:600}.stat--added{color:var(--diff-stat-added-color)}.stat--removed{color:var(--diff-stat-removed-color)}.search-bar{flex-shrink:0;display:flex;align-items:center;gap:0.25rem;padding:0.25rem 0 0.25rem 0.25rem;background:var(--search-bar-bg);border-bottom:1px solid var(--search-bar-border)}.search-bar limel-action-bar{--action-bar-background-color:transparent;min-width:2.5rem;flex-shrink:0}.search-bar limel-input-field{flex-grow:1}.search-bar__count{color:var(--diff-collapsed-text-color);white-space:nowrap;min-width:4rem;text-align:center;font-size:0.75rem}.search-match{background:var(--search-match-bg);color:inherit;border-radius:0.125rem}.search-match--current{background:var(--search-match-current-bg);outline:1px solid rgb(var(--color-amber-dark), 0.5)}.change-group{position:relative}.change-group__copy{--icon-background-color:rgb(var(--contrast-100));scale:0.9;position:absolute;top:0.125rem;right:0.5rem;display:none}.change-group:hover .change-group__copy{display:inline-flex}.diff-body{flex-grow:1;overflow-x:auto;font-family:ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, "DejaVu Sans Mono", monospace;font-size:var(--code-diff-font-size, 0.75rem)}.diff-body:focus{outline:none}.diff-body:focus-visible{outline:0.125rem solid rgb(var(--color-blue-default));outline-offset:0.125rem;border-radius:0.25rem}.diff-line{display:flex;align-items:stretch;min-height:1.25rem;line-height:1.25rem}.diff-line:hover .line-content{transition-duration:0.2s;background:var(--diff-line-hover-bg)}.diff-line--added .line-content{background:var(--diff-added-bg)}.diff-line--added .line-indicator{color:var(--diff-indicator-added-color)}.diff-line--added:hover .line-content{background:var(--diff-added-bg-hover)}.diff-line--removed .line-content{background:var(--diff-removed-bg)}.diff-line--removed .line-indicator{color:var(--diff-indicator-removed-color)}.diff-line--removed:hover .line-content{background:var(--diff-removed-bg-hover)}.diff-line--context .line-content{background:var(--diff-context-bg)}.diff-line--collapsed{display:flex;justify-content:center;background:var(--diff-collapsed-bg);border-top:1px solid var(--diff-border-color);border-bottom:1px solid var(--diff-border-color)}.diff-line--focused{outline:0.125rem solid rgb(var(--color-blue-default));outline-offset:-0.125rem;z-index:1}.line-number{display:inline-flex;align-items:center;justify-content:flex-end;min-width:calc(1ch + 2 * var(--limel-code-diff-line-number-padding));width:var(--limel-line-number-min-width);padding:0 var(--limel-code-diff-line-number-padding);background:var(--diff-gutter-bg);color:var(--diff-gutter-text-color);user-select:none;text-align:right;flex-shrink:0}.line-number--old{border-right:1px solid var(--diff-border-color)}.line-indicator{display:inline-flex;align-items:center;justify-content:center;width:1.25rem;flex-shrink:0;user-select:none;font-weight:600;background:var(--diff-gutter-bg)}.line-content{flex:1;padding:0 0.75rem;white-space:pre;overflow-x:visible;min-width:0;transition:background-color 0.6s ease}mark{background:transparent;color:inherit;border-radius:0.125rem}mark.segment--added{background:var(--diff-added-highlight-bg)}mark.segment--removed{background:var(--diff-removed-highlight-bg)}.syntax--string{color:var(--syntax-string-color)}.syntax--number{color:var(--syntax-number-color)}.syntax--boolean{color:var(--syntax-boolean-color)}.syntax--null{color:var(--syntax-null-color);font-style:italic}.syntax--key{color:var(--syntax-key-color)}.syntax--punctuation{color:var(--syntax-punctuation-color)}.expand-button{all:unset;transition:color var(--limel-clickable-transition-speed, 0.4s) ease, background-color var(--limel-clickable-transition-speed, 0.4s) ease, box-shadow var(--limel-clickable-transform-speed, 0.4s) ease, transform var(--limel-clickable-transform-speed, 0.4s) var(--limel-clickable-transform-timing-function, ease);cursor:pointer;color:var(--limel-theme-on-surface-color);background-color:transparent}.expand-button:hover,.expand-button:focus,.expand-button:focus-visible{will-change:color, background-color, box-shadow, transform}.expand-button:hover,.expand-button:focus-visible{transform:translate3d(0, 0.01rem, 0);color:var(--limel-theme-on-surface-color);background-color:var(--lime-elevated-surface-background-color)}.expand-button:hover{box-shadow:var(--button-shadow-hovered)}.expand-button:active{--limel-clickable-transform-timing-function:cubic-bezier( 0.83, -0.15, 0.49, 1.16 );transform:translate3d(0, 0.05rem, 0);box-shadow:var(--button-shadow-pressed)}.expand-button:hover,.expand-button:active{--limel-clickable-transition-speed:0.2s;--limel-clickable-transform-speed:0.16s}.expand-button:focus{outline:none}.expand-button:focus-visible{outline:none;box-shadow:var(--shadow-depth-8-focused)}.expand-button{padding:0 0.75rem;border-radius:0.25rem;margin:0.125rem;width:calc(100% - 0.25rem);text-align:center}.diff-empty{padding:2rem;text-align:center;color:var(--diff-empty-text-color);font-style:italic}.diff-line--split{display:flex;align-items:stretch;min-height:1.25rem;line-height:1.25rem}.diff-line--split:hover .split-content--removed{background:var(--diff-removed-bg-hover)}.diff-line--split:hover .split-content--added{background:var(--diff-added-bg-hover)}.diff-line--split:hover .split-content--context{background:var(--diff-line-hover-bg)}.split-content{flex:1;padding:0 0.75rem;white-space:pre;overflow-x:visible;min-width:0}.split-content--left{border-right:1px solid var(--diff-split-divider-color)}.split-content--removed{background:var(--diff-removed-bg)}.split-content--added{background:var(--diff-added-bg)}.split-content--context{background:var(--diff-context-bg)}.split-content--empty{background:var(--diff-empty-cell-bg)}.split-content--left{overflow-x:hidden;text-overflow:ellipsis}.change-group:has(.diff-line--split)>.change-group__copy{right:calc(50% - var(--limel-line-number-min-width))}:host(limel-code-diff[line-wrapping]) .diff-body{overflow-x:hidden}:host(limel-code-diff[line-wrapping]) .line-content,:host(limel-code-diff[line-wrapping]) .split-content{white-space:pre-wrap;overflow-wrap:break-word;word-break:break-all}:host(limel-code-diff[line-wrapping]) .diff-line--split .split-content{flex:1 1 0%;max-width:calc(50% - var(--limel-line-number-min-width))}`;
|
|
1232
|
+
|
|
1233
|
+
const CodeDiff = class {
|
|
1234
|
+
constructor(hostRef) {
|
|
1235
|
+
index.registerInstance(this, hostRef);
|
|
1236
|
+
/**
|
|
1237
|
+
* The "before" value to compare.
|
|
1238
|
+
* Can be a string or an object (which will be serialized to JSON).
|
|
1239
|
+
*/
|
|
1240
|
+
this.oldValue = '';
|
|
1241
|
+
/**
|
|
1242
|
+
* The "after" value to compare.
|
|
1243
|
+
* Can be a string or an object (which will be serialized to JSON).
|
|
1244
|
+
*/
|
|
1245
|
+
this.newValue = '';
|
|
1246
|
+
/**
|
|
1247
|
+
* The layout of the diff view.
|
|
1248
|
+
* - `unified` — single column with interleaved additions and removals
|
|
1249
|
+
* - `split` — side-by-side comparison with old on left, new on right
|
|
1250
|
+
*/
|
|
1251
|
+
this.layout = 'unified';
|
|
1252
|
+
/**
|
|
1253
|
+
* Number of unchanged context lines to display around each change.
|
|
1254
|
+
*/
|
|
1255
|
+
this.contextLines = 3;
|
|
1256
|
+
/**
|
|
1257
|
+
* When `true`, long lines are wrapped instead of causing
|
|
1258
|
+
* horizontal scrolling. Useful when comparing prose or
|
|
1259
|
+
* config files with long values.
|
|
1260
|
+
*/
|
|
1261
|
+
this.lineWrapping = true;
|
|
1262
|
+
/**
|
|
1263
|
+
* When `true`, JSON values are parsed, keys are sorted,
|
|
1264
|
+
* and indentation is normalized before diffing.
|
|
1265
|
+
* This eliminates noise from formatting or key-order differences.
|
|
1266
|
+
*/
|
|
1267
|
+
this.reformatJson = false;
|
|
1268
|
+
/**
|
|
1269
|
+
* Defines the language for translations.
|
|
1270
|
+
* Will translate all visible labels and announcements.
|
|
1271
|
+
*/
|
|
1272
|
+
this.translationLanguage = 'en';
|
|
1273
|
+
this.diffResult = {
|
|
1274
|
+
hunks: [],
|
|
1275
|
+
additions: 0,
|
|
1276
|
+
deletions: 0,
|
|
1277
|
+
allLines: [],
|
|
1278
|
+
};
|
|
1279
|
+
this.liveAnnouncement = '';
|
|
1280
|
+
this.copyState = 'idle';
|
|
1281
|
+
this.searchVisible = false;
|
|
1282
|
+
this.searchTerm = '';
|
|
1283
|
+
this.currentMatchIndex = 0;
|
|
1284
|
+
this.focusedRowIndex = -1;
|
|
1285
|
+
this.normalizedOldText = '';
|
|
1286
|
+
/**
|
|
1287
|
+
* Render-time counter that increments for each search match
|
|
1288
|
+
* found while rendering removed lines. Used to determine which
|
|
1289
|
+
* match is the "current" one for navigation highlighting.
|
|
1290
|
+
*/
|
|
1291
|
+
this.searchMatchCounter = 0;
|
|
1292
|
+
/**
|
|
1293
|
+
* Total search matches found during the last render pass.
|
|
1294
|
+
*/
|
|
1295
|
+
this.totalSearchMatches = 0;
|
|
1296
|
+
/**
|
|
1297
|
+
* Whether the current render is inside a removed line,
|
|
1298
|
+
* so search highlighting knows when to activate.
|
|
1299
|
+
*/
|
|
1300
|
+
this.isRenderingRemovedLine = false;
|
|
1301
|
+
/**
|
|
1302
|
+
* Cached search regex for the current render pass.
|
|
1303
|
+
* Built once in render() and reused across all renderSearchableText calls.
|
|
1304
|
+
*/
|
|
1305
|
+
this.activeSearchRegex = null;
|
|
1306
|
+
this.prevSearchVisible = false;
|
|
1307
|
+
}
|
|
1308
|
+
componentWillLoad() {
|
|
1309
|
+
this.recomputeDiff();
|
|
1310
|
+
}
|
|
1311
|
+
componentDidRender() {
|
|
1312
|
+
var _a, _b;
|
|
1313
|
+
if (this.searchVisible && !this.prevSearchVisible) {
|
|
1314
|
+
(_a = this.searchInputEl) === null || _a === void 0 ? void 0 : _a.focus();
|
|
1315
|
+
}
|
|
1316
|
+
this.prevSearchVisible = this.searchVisible;
|
|
1317
|
+
if (this.searchTerm && this.totalSearchMatches > 0) {
|
|
1318
|
+
const current = (_b = this.host.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('.search-match--current');
|
|
1319
|
+
current === null || current === void 0 ? void 0 : current.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
render() {
|
|
1323
|
+
this.searchMatchCounter = 0;
|
|
1324
|
+
this.activeSearchRegex = buildSearchRegex(this.searchTerm);
|
|
1325
|
+
const diffContent = this.renderDiff();
|
|
1326
|
+
// Capture total matches after rendering completes
|
|
1327
|
+
this.totalSearchMatches = this.searchMatchCounter;
|
|
1328
|
+
const lineNumberWidth = this.computeLineNumberWidth();
|
|
1329
|
+
return (index.h(index.Host, { key: '8122ede0d323b9021dae44cd5f65703d03083617', style: { '--limel-line-number-min-width': lineNumberWidth } }, this.renderHeader(), this.renderScreenReaderSummary(), this.searchVisible && this.renderSearchBar(), index.h("div", { key: 'fefe6e4898cbc82b6eb87f7dff7af71a14c7c91e', class: "diff-body", role: "table", "aria-label": this.getTranslation('code-diff.table-label'), tabindex: "0", onKeyDown: (event) => this.handleKeyDown(event) }, diffContent), index.h("div", { key: '2af1c7b29f2060a58af13d67fb174cb4de085efb', class: "screen-reader-only", role: "status", "aria-live": "polite", "aria-atomic": "true" }, this.liveAnnouncement)));
|
|
1330
|
+
}
|
|
1331
|
+
watchInputs() {
|
|
1332
|
+
this.recomputeDiff();
|
|
1333
|
+
}
|
|
1334
|
+
recomputeDiff() {
|
|
1335
|
+
const oldText = normalizeForDiff(this.oldValue, this.reformatJson);
|
|
1336
|
+
const newText = normalizeForDiff(this.newValue, this.reformatJson);
|
|
1337
|
+
this.normalizedOldText = oldText;
|
|
1338
|
+
this.diffResult = computeDiff(oldText, newText, this.contextLines);
|
|
1339
|
+
this.focusedRowIndex = -1;
|
|
1340
|
+
}
|
|
1341
|
+
formatSrSummary() {
|
|
1342
|
+
const { additions, deletions } = this.diffResult;
|
|
1343
|
+
if (additions === 0 && deletions === 0) {
|
|
1344
|
+
return null;
|
|
1345
|
+
}
|
|
1346
|
+
const parts = [];
|
|
1347
|
+
if (additions > 0) {
|
|
1348
|
+
const key = additions === 1
|
|
1349
|
+
? 'code-diff.diff-addition'
|
|
1350
|
+
: 'code-diff.diff-additions';
|
|
1351
|
+
parts.push(this.getTranslation(key, { count: additions }));
|
|
1352
|
+
}
|
|
1353
|
+
if (deletions > 0) {
|
|
1354
|
+
const key = deletions === 1
|
|
1355
|
+
? 'code-diff.diff-deletion'
|
|
1356
|
+
: 'code-diff.diff-deletions';
|
|
1357
|
+
parts.push(this.getTranslation(key, { count: deletions }));
|
|
1358
|
+
}
|
|
1359
|
+
return this.getTranslation('code-diff.diff-summary', {
|
|
1360
|
+
parts: parts.join(', '),
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
renderScreenReaderSummary() {
|
|
1364
|
+
const summary = this.formatSrSummary();
|
|
1365
|
+
return (index.h("div", { class: "screen-reader-only", role: "status", "aria-live": "polite" }, summary !== null && summary !== void 0 ? summary : this.getTranslation('code-diff.no-differences-found')));
|
|
1366
|
+
}
|
|
1367
|
+
handleKeyDown(event) {
|
|
1368
|
+
if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') {
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
event.preventDefault();
|
|
1372
|
+
const rows = this.getDiffRows();
|
|
1373
|
+
if (rows.length === 0) {
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
if (event.key === 'ArrowDown') {
|
|
1377
|
+
this.focusedRowIndex = Math.min(this.focusedRowIndex + 1, rows.length - 1);
|
|
1378
|
+
}
|
|
1379
|
+
else {
|
|
1380
|
+
this.focusedRowIndex = Math.max(this.focusedRowIndex - 1, 0);
|
|
1381
|
+
}
|
|
1382
|
+
this.updateRowFocus(rows);
|
|
1383
|
+
}
|
|
1384
|
+
getDiffRows() {
|
|
1385
|
+
var _a;
|
|
1386
|
+
const body = (_a = this.host.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.diff-body');
|
|
1387
|
+
if (!body) {
|
|
1388
|
+
return [];
|
|
1389
|
+
}
|
|
1390
|
+
return [
|
|
1391
|
+
...body.querySelectorAll('.diff-line:not(.diff-line--collapsed)'),
|
|
1392
|
+
];
|
|
1393
|
+
}
|
|
1394
|
+
updateRowFocus(rows) {
|
|
1395
|
+
for (const row of rows) {
|
|
1396
|
+
row.removeAttribute('tabindex');
|
|
1397
|
+
row.classList.remove('diff-line--focused');
|
|
1398
|
+
}
|
|
1399
|
+
const target = rows[this.focusedRowIndex];
|
|
1400
|
+
if (target) {
|
|
1401
|
+
target.setAttribute('tabindex', '-1');
|
|
1402
|
+
target.classList.add('diff-line--focused');
|
|
1403
|
+
target.focus();
|
|
1404
|
+
this.announceLine(target);
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
announceLine(row) {
|
|
1408
|
+
var _a, _b;
|
|
1409
|
+
let lineType = this.getTranslation('code-diff.line-context');
|
|
1410
|
+
if (row.classList.contains('diff-line--added')) {
|
|
1411
|
+
lineType = this.getTranslation('code-diff.line-added');
|
|
1412
|
+
}
|
|
1413
|
+
else if (row.classList.contains('diff-line--removed')) {
|
|
1414
|
+
lineType = this.getTranslation('code-diff.line-removed');
|
|
1415
|
+
}
|
|
1416
|
+
const content = (_b = (_a = row.querySelector('.line-content, .split-content')) === null || _a === void 0 ? void 0 : _a.textContent) !== null && _b !== void 0 ? _b : '';
|
|
1417
|
+
const trimmed = content.length > 80 ? content.slice(0, 80) + '…' : content;
|
|
1418
|
+
this.liveAnnouncement = `${lineType}: ${trimmed}`;
|
|
1419
|
+
}
|
|
1420
|
+
renderHeader() {
|
|
1421
|
+
var _a, _b;
|
|
1422
|
+
const oldHeading = (_a = this.oldHeading) !== null && _a !== void 0 ? _a : this.getTranslation('code-diff.old-heading');
|
|
1423
|
+
const newHeading = (_b = this.newHeading) !== null && _b !== void 0 ? _b : this.getTranslation('code-diff.new-heading');
|
|
1424
|
+
const { additions, deletions } = this.diffResult;
|
|
1425
|
+
const hasDiff = additions > 0 || deletions > 0;
|
|
1426
|
+
return (index.h("div", { class: "diff-header" }, index.h("div", { class: "diff-header__labels" }, index.h("span", { class: "diff-header__old" }, oldHeading), index.h("span", { class: "diff-header__new" }, newHeading)), index.h("div", { class: "diff-header__actions" }, index.h("div", { class: "diff-header__stats" }, additions > 0 && (index.h("span", { class: "stat stat--added" }, "+", additions)), deletions > 0 && (index.h("span", { class: "stat stat--removed" }, "-", deletions))), hasDiff && this.renderCopyButton(), deletions > 0 && this.renderSearchToggle())));
|
|
1427
|
+
}
|
|
1428
|
+
renderCopyButton() {
|
|
1429
|
+
const label = this.copyState === 'copied'
|
|
1430
|
+
? this.getTranslation('code-diff.copied')
|
|
1431
|
+
: this.getTranslation('code-diff.copy-old-version');
|
|
1432
|
+
const icon = this.copyState === 'copied' ? 'checkmark' : 'copy';
|
|
1433
|
+
return (index.h("limel-icon-button", { label: label, icon: icon, onClick: () => this.copyToClipboard(this.normalizedOldText) }));
|
|
1434
|
+
}
|
|
1435
|
+
async copyToClipboard(text) {
|
|
1436
|
+
try {
|
|
1437
|
+
await navigator.clipboard.writeText(text);
|
|
1438
|
+
this.copyState = 'copied';
|
|
1439
|
+
this.liveAnnouncement = this.getTranslation('code-diff.copied-to-clipboard');
|
|
1440
|
+
setTimeout(() => {
|
|
1441
|
+
this.copyState = 'idle';
|
|
1442
|
+
}, 2000);
|
|
1443
|
+
}
|
|
1444
|
+
catch (_a) {
|
|
1445
|
+
// Clipboard API may fail in insecure contexts
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
renderSearchToggle() {
|
|
1449
|
+
return (index.h("limel-icon-button", { class: { 'search-toggle--active': this.searchVisible }, label: this.getTranslation('code-diff.search'), icon: "search", onClick: () => this.toggleSearch() }));
|
|
1450
|
+
}
|
|
1451
|
+
renderSearchBar() {
|
|
1452
|
+
const matchInfo = this.totalSearchMatches === 0
|
|
1453
|
+
? this.getTranslation('code-diff.no-matches')
|
|
1454
|
+
: this.getTranslation('code-diff.match-count', {
|
|
1455
|
+
current: this.currentMatchIndex + 1,
|
|
1456
|
+
total: this.totalSearchMatches,
|
|
1457
|
+
});
|
|
1458
|
+
return (index.h("div", { class: "search-bar" }, index.h("limel-input-field", { class: "search-bar__input", type: "search", placeholder: this.getTranslation('code-diff.search') + '…', value: this.searchTerm, onChange: (e) => this.onSearchInput(e), onKeyDown: (e) => this.onSearchKeyDown(e), ref: (el) => (this.searchInputEl = el) }), index.h("span", { class: "search-bar__count" }, matchInfo), index.h("limel-action-bar", { actions: this.getSearchActions(), onItemSelected: (e) => this.onSearchAction(e) })));
|
|
1459
|
+
}
|
|
1460
|
+
toggleSearch() {
|
|
1461
|
+
this.searchVisible = !this.searchVisible;
|
|
1462
|
+
if (!this.searchVisible) {
|
|
1463
|
+
this.searchTerm = '';
|
|
1464
|
+
this.currentMatchIndex = 0;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
onSearchInput(event) {
|
|
1468
|
+
this.searchTerm = event.detail;
|
|
1469
|
+
this.currentMatchIndex = 0;
|
|
1470
|
+
}
|
|
1471
|
+
onSearchKeyDown(event) {
|
|
1472
|
+
if (event.key === 'Enter') {
|
|
1473
|
+
event.preventDefault();
|
|
1474
|
+
if (event.shiftKey) {
|
|
1475
|
+
this.navigateSearch(-1);
|
|
1476
|
+
}
|
|
1477
|
+
else {
|
|
1478
|
+
this.navigateSearch(1);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
else if (event.key === 'Escape') {
|
|
1482
|
+
this.toggleSearch();
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
computeLineNumberWidth() {
|
|
1486
|
+
const maxLineNumber = this.diffResult.allLines.length;
|
|
1487
|
+
const digits = String(maxLineNumber).length;
|
|
1488
|
+
return `calc(${digits}ch + 2 * var(--limel-code-diff-line-number-padding))`;
|
|
1489
|
+
}
|
|
1490
|
+
getSearchActions() {
|
|
1491
|
+
const noMatches = this.totalSearchMatches === 0;
|
|
1492
|
+
return [
|
|
1493
|
+
{
|
|
1494
|
+
text: this.getTranslation('code-diff.previous-match'),
|
|
1495
|
+
icon: '-lime-caret-top',
|
|
1496
|
+
iconOnly: true,
|
|
1497
|
+
disabled: noMatches,
|
|
1498
|
+
value: 'prev',
|
|
1499
|
+
},
|
|
1500
|
+
{
|
|
1501
|
+
text: this.getTranslation('code-diff.next-match'),
|
|
1502
|
+
icon: '-lime-caret-bottom',
|
|
1503
|
+
iconOnly: true,
|
|
1504
|
+
disabled: noMatches,
|
|
1505
|
+
value: 'next',
|
|
1506
|
+
},
|
|
1507
|
+
{
|
|
1508
|
+
text: this.getTranslation('code-diff.close-search'),
|
|
1509
|
+
icon: 'cancel',
|
|
1510
|
+
iconOnly: true,
|
|
1511
|
+
value: 'close',
|
|
1512
|
+
},
|
|
1513
|
+
];
|
|
1514
|
+
}
|
|
1515
|
+
onSearchAction(event) {
|
|
1516
|
+
const { value } = event.detail;
|
|
1517
|
+
if (value === 'prev') {
|
|
1518
|
+
this.navigateSearch(-1);
|
|
1519
|
+
}
|
|
1520
|
+
else if (value === 'next') {
|
|
1521
|
+
this.navigateSearch(1);
|
|
1522
|
+
}
|
|
1523
|
+
else if (value === 'close') {
|
|
1524
|
+
this.toggleSearch();
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
navigateSearch(direction) {
|
|
1528
|
+
this.currentMatchIndex = navigateMatchIndex(this.currentMatchIndex, direction, this.totalSearchMatches);
|
|
1529
|
+
}
|
|
1530
|
+
renderDiff() {
|
|
1531
|
+
const { hunks, collapsedAfter } = this.diffResult;
|
|
1532
|
+
if (hunks.length === 0) {
|
|
1533
|
+
return (index.h("div", { class: "diff-empty" }, this.getTranslation('code-diff.no-differences')));
|
|
1534
|
+
}
|
|
1535
|
+
const lineRenderer = this.layout === 'split'
|
|
1536
|
+
? (hunk) => this.renderSplitHunkRows(hunk)
|
|
1537
|
+
: (hunk) => this.renderHunkLines(hunk);
|
|
1538
|
+
return this.renderHunks(hunks, collapsedAfter, lineRenderer);
|
|
1539
|
+
}
|
|
1540
|
+
renderHunks(hunks, collapsedAfter, lineRenderer) {
|
|
1541
|
+
const elements = [];
|
|
1542
|
+
for (const [i, hunk] of hunks.entries()) {
|
|
1543
|
+
if (hunk.collapsedBefore) {
|
|
1544
|
+
elements.push(this.renderCollapsedRow(hunk.collapsedBefore, i));
|
|
1545
|
+
}
|
|
1546
|
+
elements.push(...lineRenderer(hunk));
|
|
1547
|
+
}
|
|
1548
|
+
if (collapsedAfter) {
|
|
1549
|
+
elements.push(this.renderCollapsedAfterRow(collapsedAfter));
|
|
1550
|
+
}
|
|
1551
|
+
return elements;
|
|
1552
|
+
}
|
|
1553
|
+
renderHunkLines(hunk) {
|
|
1554
|
+
const elements = [];
|
|
1555
|
+
let i = 0;
|
|
1556
|
+
while (i < hunk.lines.length) {
|
|
1557
|
+
const line = hunk.lines[i];
|
|
1558
|
+
if (line.type === 'context') {
|
|
1559
|
+
elements.push(this.renderLine(line));
|
|
1560
|
+
i++;
|
|
1561
|
+
continue;
|
|
1562
|
+
}
|
|
1563
|
+
// Collect consecutive changed lines as a change block
|
|
1564
|
+
const blockLines = [];
|
|
1565
|
+
while (i < hunk.lines.length && hunk.lines[i].type !== 'context') {
|
|
1566
|
+
blockLines.push(hunk.lines[i]);
|
|
1567
|
+
i++;
|
|
1568
|
+
}
|
|
1569
|
+
elements.push(this.renderChangeBlock(blockLines));
|
|
1570
|
+
}
|
|
1571
|
+
return elements;
|
|
1572
|
+
}
|
|
1573
|
+
renderChangeBlock(lines) {
|
|
1574
|
+
const removedContent = extractRemovedContent(lines);
|
|
1575
|
+
return (index.h("div", { class: "change-group" }, lines.map((line) => this.renderLine(line)), removedContent && this.renderBlockCopyButton(removedContent)));
|
|
1576
|
+
}
|
|
1577
|
+
renderLine(line) {
|
|
1578
|
+
var _a, _b;
|
|
1579
|
+
const lineClass = {
|
|
1580
|
+
'diff-line': true,
|
|
1581
|
+
[`diff-line--${line.type}`]: true,
|
|
1582
|
+
};
|
|
1583
|
+
const indicatorMap = {
|
|
1584
|
+
added: '+',
|
|
1585
|
+
removed: '-',
|
|
1586
|
+
context: ' ',
|
|
1587
|
+
};
|
|
1588
|
+
const indicator = indicatorMap[line.type];
|
|
1589
|
+
return (index.h("div", { class: lineClass, role: "row" }, index.h("span", { class: "line-number line-number--old", role: "cell", "aria-label": line.oldLineNumber
|
|
1590
|
+
? this.getTranslation('code-diff.old-line', {
|
|
1591
|
+
number: line.oldLineNumber,
|
|
1592
|
+
})
|
|
1593
|
+
: undefined }, (_a = line.oldLineNumber) !== null && _a !== void 0 ? _a : ''), index.h("span", { class: "line-number line-number--new", role: "cell", "aria-label": line.newLineNumber
|
|
1594
|
+
? this.getTranslation('code-diff.new-line', {
|
|
1595
|
+
number: line.newLineNumber,
|
|
1596
|
+
})
|
|
1597
|
+
: undefined }, (_b = line.newLineNumber) !== null && _b !== void 0 ? _b : ''), index.h("span", { class: "line-indicator", role: "cell" }, indicator), index.h("span", { class: "line-content", role: "cell" }, this.renderContent(line))));
|
|
1598
|
+
}
|
|
1599
|
+
renderSplitHunkRows(hunk) {
|
|
1600
|
+
var _a, _b, _c, _d;
|
|
1601
|
+
const splitRows = buildSplitLines(hunk.lines);
|
|
1602
|
+
const elements = [];
|
|
1603
|
+
let i = 0;
|
|
1604
|
+
while (i < splitRows.length) {
|
|
1605
|
+
const row = splitRows[i];
|
|
1606
|
+
const isContext = ((_a = row.left) === null || _a === void 0 ? void 0 : _a.type) === 'context' && ((_b = row.right) === null || _b === void 0 ? void 0 : _b.type) === 'context';
|
|
1607
|
+
if (isContext) {
|
|
1608
|
+
elements.push(this.renderSplitRow(row));
|
|
1609
|
+
i++;
|
|
1610
|
+
continue;
|
|
1611
|
+
}
|
|
1612
|
+
// Collect consecutive changed rows
|
|
1613
|
+
const blockRows = [];
|
|
1614
|
+
while (i < splitRows.length) {
|
|
1615
|
+
const r = splitRows[i];
|
|
1616
|
+
const rIsContext = ((_c = r.left) === null || _c === void 0 ? void 0 : _c.type) === 'context' && ((_d = r.right) === null || _d === void 0 ? void 0 : _d.type) === 'context';
|
|
1617
|
+
if (rIsContext) {
|
|
1618
|
+
break;
|
|
1619
|
+
}
|
|
1620
|
+
blockRows.push(r);
|
|
1621
|
+
i++;
|
|
1622
|
+
}
|
|
1623
|
+
elements.push(this.renderSplitChangeBlock(blockRows));
|
|
1624
|
+
}
|
|
1625
|
+
return elements;
|
|
1626
|
+
}
|
|
1627
|
+
renderSplitChangeBlock(rows) {
|
|
1628
|
+
const removedContent = extractRemovedContentFromSplit(rows);
|
|
1629
|
+
return (index.h("div", { class: "change-group" }, rows.map((row) => this.renderSplitRow(row)), removedContent && this.renderBlockCopyButton(removedContent)));
|
|
1630
|
+
}
|
|
1631
|
+
renderSplitRow(row) {
|
|
1632
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
1633
|
+
const leftType = (_b = (_a = row.left) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : 'empty';
|
|
1634
|
+
const rightType = (_d = (_c = row.right) === null || _c === void 0 ? void 0 : _c.type) !== null && _d !== void 0 ? _d : 'empty';
|
|
1635
|
+
const oldLineLabel = ((_e = row.left) === null || _e === void 0 ? void 0 : _e.oldLineNumber)
|
|
1636
|
+
? this.getTranslation('code-diff.old-line', {
|
|
1637
|
+
number: row.left.oldLineNumber,
|
|
1638
|
+
})
|
|
1639
|
+
: undefined;
|
|
1640
|
+
const newLineLabel = ((_f = row.right) === null || _f === void 0 ? void 0 : _f.newLineNumber)
|
|
1641
|
+
? this.getTranslation('code-diff.new-line', {
|
|
1642
|
+
number: row.right.newLineNumber,
|
|
1643
|
+
})
|
|
1644
|
+
: undefined;
|
|
1645
|
+
return (index.h("div", { class: "diff-line diff-line--split", role: "row" }, index.h("span", { class: "line-number line-number--old", role: "cell", "aria-label": oldLineLabel }, (_h = (_g = row.left) === null || _g === void 0 ? void 0 : _g.oldLineNumber) !== null && _h !== void 0 ? _h : ''), index.h("span", { class: `split-content split-content--left split-content--${leftType}`, role: "cell" }, row.left ? this.renderContent(row.left) : ''), index.h("span", { class: "line-number line-number--new", role: "cell", "aria-label": newLineLabel }, (_k = (_j = row.right) === null || _j === void 0 ? void 0 : _j.newLineNumber) !== null && _k !== void 0 ? _k : ''), index.h("span", { class: `split-content split-content--right split-content--${rightType}`, role: "cell" }, row.right ? this.renderContent(row.right) : '')));
|
|
1646
|
+
}
|
|
1647
|
+
renderBlockCopyButton(removedContent) {
|
|
1648
|
+
return (index.h("limel-icon-button", { class: "change-group__copy", elevated: true, label: this.getTranslation('code-diff.copy-change'), icon: "copy", onClick: () => this.copyToClipboard(removedContent) }));
|
|
1649
|
+
}
|
|
1650
|
+
renderContent(line) {
|
|
1651
|
+
this.isRenderingRemovedLine =
|
|
1652
|
+
line.type === 'removed' && this.searchTerm.length > 0;
|
|
1653
|
+
if (!line.segments || line.segments.length === 0) {
|
|
1654
|
+
return this.renderSyntaxTokens(line.content);
|
|
1655
|
+
}
|
|
1656
|
+
return line.segments.map((segment) => this.renderSegment(segment, line.type));
|
|
1657
|
+
}
|
|
1658
|
+
renderSegment(segment, lineType) {
|
|
1659
|
+
const content = this.renderSyntaxTokens(segment.value);
|
|
1660
|
+
if (segment.type === 'equal') {
|
|
1661
|
+
return content;
|
|
1662
|
+
}
|
|
1663
|
+
const segmentClass = lineType === 'removed' ? 'segment--removed' : 'segment--added';
|
|
1664
|
+
return index.h("mark", { class: segmentClass }, content);
|
|
1665
|
+
}
|
|
1666
|
+
renderSyntaxTokens(text) {
|
|
1667
|
+
const tokens = tokenize(text, this.language);
|
|
1668
|
+
if (tokens.length === 1 && tokens[0].type === 'plain') {
|
|
1669
|
+
return this.renderSearchableText(text);
|
|
1670
|
+
}
|
|
1671
|
+
return tokens.map((token) => this.renderSyntaxToken(token));
|
|
1672
|
+
}
|
|
1673
|
+
renderSyntaxToken(token) {
|
|
1674
|
+
const text = this.renderSearchableText(token.value);
|
|
1675
|
+
if (token.type === 'plain') {
|
|
1676
|
+
return text;
|
|
1677
|
+
}
|
|
1678
|
+
return index.h("span", { class: `syntax--${token.type}` }, text);
|
|
1679
|
+
}
|
|
1680
|
+
renderSearchableText(text) {
|
|
1681
|
+
if (!this.isRenderingRemovedLine || !this.activeSearchRegex) {
|
|
1682
|
+
return text;
|
|
1683
|
+
}
|
|
1684
|
+
const parts = text.split(this.activeSearchRegex);
|
|
1685
|
+
if (parts.length === 1) {
|
|
1686
|
+
return text;
|
|
1687
|
+
}
|
|
1688
|
+
return parts.map((part, i) => {
|
|
1689
|
+
// Odd indices are the captured matches from split
|
|
1690
|
+
if (i % 2 === 0) {
|
|
1691
|
+
return part;
|
|
1692
|
+
}
|
|
1693
|
+
const matchIndex = this.searchMatchCounter++;
|
|
1694
|
+
const isCurrent = matchIndex === this.currentMatchIndex;
|
|
1695
|
+
const cls = {
|
|
1696
|
+
'search-match': true,
|
|
1697
|
+
'search-match--current': isCurrent,
|
|
1698
|
+
};
|
|
1699
|
+
return (index.h("mark", { key: `match-${matchIndex}`, class: cls }, part));
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
renderCollapsedRow(count, hunkIndex) {
|
|
1703
|
+
return (index.h("div", { class: "diff-line diff-line--collapsed", role: "row" }, index.h("button", { class: "expand-button", type: "button", onClick: () => this.expandHunk(hunkIndex), "aria-label": this.getTranslation('code-diff.show-hidden-lines', {
|
|
1704
|
+
count,
|
|
1705
|
+
}) }, this.getTranslation('code-diff.hidden-lines', { count }))));
|
|
1706
|
+
}
|
|
1707
|
+
renderCollapsedAfterRow(count) {
|
|
1708
|
+
return (index.h("div", { class: "diff-line diff-line--collapsed", role: "row" }, index.h("button", { class: "expand-button", type: "button", onClick: () => this.expandAfter(), "aria-label": this.getTranslation('code-diff.show-hidden-lines', {
|
|
1709
|
+
count,
|
|
1710
|
+
}) }, this.getTranslation('code-diff.hidden-lines', { count }))));
|
|
1711
|
+
}
|
|
1712
|
+
expandHunk(hunkIndex) {
|
|
1713
|
+
const hunks = [...this.diffResult.hunks];
|
|
1714
|
+
const hunk = hunks[hunkIndex];
|
|
1715
|
+
const prevHunkEnd = hunkIndex > 0
|
|
1716
|
+
? hunks[hunkIndex - 1].startIndex +
|
|
1717
|
+
hunks[hunkIndex - 1].lines.length
|
|
1718
|
+
: 0;
|
|
1719
|
+
const hiddenLines = this.diffResult.allLines.slice(prevHunkEnd, hunk.startIndex);
|
|
1720
|
+
hunks[hunkIndex] = Object.assign(Object.assign({}, hunk), { lines: [...hiddenLines, ...hunk.lines], collapsedBefore: undefined, startIndex: prevHunkEnd });
|
|
1721
|
+
this.diffResult = Object.assign(Object.assign({}, this.diffResult), { hunks });
|
|
1722
|
+
this.liveAnnouncement = this.getTranslation('code-diff.expanded-lines');
|
|
1723
|
+
}
|
|
1724
|
+
expandAfter() {
|
|
1725
|
+
const hunks = [...this.diffResult.hunks];
|
|
1726
|
+
const lastIndex = hunks.length - 1;
|
|
1727
|
+
const lastHunk = hunks[lastIndex];
|
|
1728
|
+
const lastHunkEnd = lastHunk.startIndex + lastHunk.lines.length;
|
|
1729
|
+
const hiddenLines = this.diffResult.allLines.slice(lastHunkEnd);
|
|
1730
|
+
hunks[lastIndex] = Object.assign(Object.assign({}, lastHunk), { lines: [...lastHunk.lines, ...hiddenLines] });
|
|
1731
|
+
this.diffResult = Object.assign(Object.assign({}, this.diffResult), { hunks, collapsedAfter: undefined });
|
|
1732
|
+
this.liveAnnouncement = this.getTranslation('code-diff.expanded-lines-end');
|
|
1733
|
+
}
|
|
1734
|
+
getTranslation(key, params) {
|
|
1735
|
+
return translations.translate.get(key, this.translationLanguage, params);
|
|
1736
|
+
}
|
|
1737
|
+
get host() { return index.getElement(this); }
|
|
1738
|
+
static get watchers() { return {
|
|
1739
|
+
"oldValue": [{
|
|
1740
|
+
"watchInputs": 0
|
|
1741
|
+
}],
|
|
1742
|
+
"newValue": [{
|
|
1743
|
+
"watchInputs": 0
|
|
1744
|
+
}],
|
|
1745
|
+
"contextLines": [{
|
|
1746
|
+
"watchInputs": 0
|
|
1747
|
+
}],
|
|
1748
|
+
"reformatJson": [{
|
|
1749
|
+
"watchInputs": 0
|
|
1750
|
+
}],
|
|
1751
|
+
"layout": [{
|
|
1752
|
+
"watchInputs": 0
|
|
1753
|
+
}]
|
|
1754
|
+
}; }
|
|
1755
|
+
};
|
|
1756
|
+
CodeDiff.style = codeDiffCss();
|
|
1757
|
+
|
|
1758
|
+
exports.limel_code_diff = CodeDiff;
|