Skip to content

Commit

Permalink
Adds provisional diff algorithm
Browse files Browse the repository at this point in the history
MVP to show how diff process can work. Still very much a WIP.
  • Loading branch information
tomilho committed May 16, 2024
1 parent c597547 commit cb68131
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 19 deletions.
9 changes: 9 additions & 0 deletions media/editor/dataDisplay.css
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,12 @@
.diff-added {
background-color: green;
}

.diff-removed {
background-color: red;
}

/* background-color for testing*/
.diff-none {
background-color: chocolate;
}
2 changes: 1 addition & 1 deletion media/editor/dataDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ const LoadingDataRows: React.FC<IDataPageProps> = props => (
const DataPageContents: React.FC<IDataPageProps> = props => {
const pageSelector = select.editedDataPages(props.pageNo);
const [data] = useLastAsyncRecoilValue(pageSelector);
const [decorators] = useLastAsyncRecoilValue(select.decorators);
const decorators = useRecoilValue(select.decorators);
return (
<>
{generateRows(props, (offset, isRowWithInsertDataCell) => (
Expand Down
14 changes: 5 additions & 9 deletions media/editor/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
*--------------------------------------------------------*/

import { atom, DefaultValue, selector, selectorFamily } from "recoil";
import { HexEditorDecorationMap, HexEditorDecorationType } from "../../shared/decorators";
import { buildEditTimeline, HexDocumentEdit, readUsingRanges } from "../../shared/hexDocumentModel";
import {
FromWebviewMessage,
Expand All @@ -16,7 +15,7 @@ import {
SearchResultsWithProgress,
ToWebviewMessage,
} from "../../shared/protocol";
import { deserializeEdits, serializeEdits } from "../../shared/serialization";
import { deserializeDecorators, deserializeEdits, serializeEdits } from "../../shared/serialization";
import { Range } from "../../shared/util/range";
import { clamp } from "./util";

Expand Down Expand Up @@ -374,17 +373,14 @@ export const unsavedEditTimeline = selector({
},
});


export const decorators = selector({
key: "decorators",
get: async () => {

const dec = new HexEditorDecorationMap();
const response = await messageHandler.sendRequest<ReadDecoratorsResponseMessage>({
type: MessageType.ReadDecoratorsRequest
const { data } = await messageHandler.sendRequest<ReadDecoratorsResponseMessage>({
type: MessageType.ReadDecoratorsRequest,
});
dec.add(HexEditorDecorationType.DiffAdded, [new Range(0, 10), new Range(10, 15)]);
return dec;
const decoratorMap = deserializeDecorators(data)
return decoratorMap;
},
});

Expand Down
17 changes: 15 additions & 2 deletions shared/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ export const enum HexEditorDecorationType {
}

export class HexEditorDecorationMap {
private readonly map: Map<HexEditorDecorationType, Range[]> = new Map();
private readonly map: Map<HexEditorDecorationType, Range[]>;

constructor() {
this.map = new Map();
}

public add(type: HexEditorDecorationType, range: Range[]) {
if(this.map.has(type)) {
Expand All @@ -17,18 +21,27 @@ export class HexEditorDecorationMap {
}
}

// Returns an array of each decoration type
public getEntries() {
return this.map.entries();
}

// Returns an array the decoration type per byte
public slice(begin: number, end: number) {
const rowRange = new Range(begin, end);
const decorationPerByte: HexEditorDecorationType[] = [];
for(let i = rowRange.start; i < rowRange.end; i++) {
for(const [decorationType, ranges] of this.map.entries()) {
let found = false;
for(const range of ranges) {
if(range.includes(i)) {
decorationPerByte.push(decorationType);
found = true;
break;
}
}
if(!found) {
decorationPerByte.push(HexEditorDecorationType.None);
}
}
}
return decorationPerByte;
Expand Down
62 changes: 60 additions & 2 deletions shared/hexDiffModel.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import * as vscode from "vscode";
import { HexEditorDecorationMap, HexEditorDecorationType } from "./decorators";
import { HexDocumentModel } from "./hexDocumentModel";
import { Range } from "./util/range";
import { HexDocument } from "../src/hexDocument";

export type HexDiffModelBuilder = typeof HexDiffModel.Builder.prototype;

Expand All @@ -9,7 +12,59 @@ export class HexDiffModel {
private readonly modifiedModel: HexDocumentModel,
) {}

public async readDecorators() {}
// Provisional algorithm to test and finalize the decorators.
public async readDecorators(doc: HexDocument): Promise<HexEditorDecorationMap> {
const originalDecoraMap = new HexEditorDecorationMap();
const modifiedDecorMap = new HexEditorDecorationMap();

const oChunks = this.originalModel.readWithUnsavedEdits();
const mChunks = this.modifiedModel.readWithUnsavedEdits();
const oSize = await this.originalModel.size();
const mSize = await this.modifiedModel.size();
if (oSize === undefined || mSize === undefined) {
console.error("Files have infinite size");
return new HexEditorDecorationMap();
}
const { highestSize, highestChunks } =
oSize > mSize
? { highestSize: oSize, highestChunks: oChunks }
: { highestSize: mSize, highestChunks: mChunks };

const lowestChunks = highestChunks === oChunks ? mChunks : oChunks;
let offset = 0;
let range: null | Range = null;
for await (const hChunk of highestChunks) {
const lChunk = await lowestChunks.next();
let i = 0;
for (; i < lChunk.value.length; i++) {
if (hChunk[i] !== lChunk.value[i]) {
if (range) {
range = range.expandToContain(offset + i);
} else {
range = new Range(offset + i, offset + i + 1);
}
} else {
if (range) {
originalDecoraMap.add(HexEditorDecorationType.DiffRemoved, [range]);
modifiedDecorMap.add(HexEditorDecorationType.DiffAdded, [range]);
range = null;
}
}
}
if (i < hChunk.length) {
if (range) {
originalDecoraMap.add(HexEditorDecorationType.DiffRemoved, [range]);
modifiedDecorMap.add(HexEditorDecorationType.DiffAdded, [range]);
}
const tmpRange = new Range(offset + i + 1, highestSize);
originalDecoraMap.add(HexEditorDecorationType.DiffRemoved, [tmpRange]);
modifiedDecorMap.add(HexEditorDecorationType.DiffAdded, [tmpRange]);
}
offset += hChunk.length;
}
return doc.uri.toString() === this.originalModel.uri.toString() ? originalDecoraMap : modifiedDecorMap;
}

/**
* Class to coordinate the creation of HexDiffModel
* as both documents have to be created first by VSCode
Expand Down Expand Up @@ -56,7 +111,10 @@ export class HexDiffModel {
}

public async build() {
const [originalModel, modifiedModel] = await Promise.all([this.originalModel, this.modifiedModel]);
const [originalModel, modifiedModel] = await Promise.all([
this.originalModel,
this.modifiedModel,
]);
if (this.onBuild) {
this.onBuild();
}
Expand Down
1 change: 1 addition & 0 deletions shared/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

import { HexEditorDecorationMap } from "./decorators";
import { HexDocumentEditOp } from "./hexDocumentModel";
import { ISerializedEdits } from "./serialization";

Expand Down
23 changes: 22 additions & 1 deletion shared/serialization.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { HexEditorDecorationMap, HexEditorDecorationType } from "./decorators";
import { HexDocumentEdit, HexDocumentEditOp } from "./hexDocumentModel";
import { Range } from "./util/range";
import { Uint8ArrayMap } from "./util/uint8ArrayMap";

export interface ISerializedEdits {
edits: readonly unknown[];
data: Uint8Array;
Expand Down Expand Up @@ -64,3 +65,23 @@ export const deserializeEdits = ({ edits, data }: ISerializedEdits): HexDocument
}
});
};

export type SerializedDecorators = {
[key in HexEditorDecorationType]? : [number, number][];
}

export const serializeDecorators = (decoratorMap: HexEditorDecorationMap): SerializedDecorators => {
const serialized: SerializedDecorators = {}
for(const [decoratorType, ranges] of decoratorMap.getEntries()) {
serialized[decoratorType] = ranges.map((r) => [r.start, r.end]);
}
return serialized;
}

export const deserializeDecorators = (serializedData: SerializedDecorators): HexEditorDecorationMap => {
const decorationMap = new HexEditorDecorationMap();
for(const [decoratorType, ranges] of Object.entries(serializedData)) {
decorationMap.add(parseInt(decoratorType) as HexEditorDecorationType, ranges.map(([start, end]) => new Range(start, end)));
}
return decorationMap;
}
10 changes: 8 additions & 2 deletions src/hexDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Backup } from "./backup";
import { Disposable } from "./dispose";
import { accessFile } from "./fileSystemAdaptor";
import { SearchProvider } from "./searchProvider";
import { HexEditorDecorationMap } from "../shared/decorators";

export interface ISelectionState {
/** Number of selected bytes */
Expand Down Expand Up @@ -102,12 +103,17 @@ export class HexDocument extends Disposable implements vscode.CustomDocument {
return vscode.Uri.parse(this.model.uri);
}

/**
* Returns the decorators
*/
public async readDecorators() {
if(this.diffModel) {
return await this.diffModel.readDecorators();
return await this.diffModel.readDecorators(this);
}
return true;
const t = new HexEditorDecorationMap();
return t;
}

/**
* Reads data including unsaved edits from the model, returning an iterable
* of Uint8Array chunks.
Expand Down
4 changes: 2 additions & 2 deletions src/hexEditorProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
PasteMode,
ToWebviewMessage,
} from "../shared/protocol";
import { deserializeEdits, serializeEdits } from "../shared/serialization";
import { deserializeEdits, serializeDecorators, serializeEdits } from "../shared/serialization";
import { ILocalizedStrings, placeholder1 } from "../shared/strings";
import { copyAsFormats } from "./copyAs";
import { DataInspectorView } from "./dataInspectorView";
Expand Down Expand Up @@ -347,7 +347,7 @@ export class HexEditorProvider implements vscode.CustomEditorProvider<HexDocumen
return { type: MessageType.ReadRangeResponse, data: getCorrectArrayBuffer(data) };
case MessageType.ReadDecoratorsRequest:
const decorators = await document.readDecorators();
return { type: MessageType.ReadDecoratorsResponse, data: true };
return { type: MessageType.ReadDecoratorsResponse, data: serializeDecorators(decorators) };
case MessageType.MakeEdits:
this.publishEdit(messaging, document, document.makeEdits(deserializeEdits(message.edits)));
return;
Expand Down

0 comments on commit cb68131

Please sign in to comment.