Skip to content

Commit

Permalink
Adds alignment in original file
Browse files Browse the repository at this point in the history
Inserted bytes are show in the original file by a stripe data cell.
  • Loading branch information
tomilho committed Jul 3, 2024
1 parent 393d697 commit 6312f63
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 17 deletions.
2 changes: 1 addition & 1 deletion media/editor/dataDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -430,9 +430,9 @@ const LoadingDataRows: React.FC<IDataPageProps> = props => (
);

const DataPageContents: React.FC<IDataPageProps> = props => {
const decorators = useRecoilValue(select.decoratorsPage(props.pageNo));
const dataPageSelector = select.editedDataPages(props.pageNo);
const [data] = useLastAsyncRecoilValue(dataPageSelector);
const decorators = useRecoilValue(select.decoratorsPage(props.pageNo));

return (
<>
Expand Down
41 changes: 38 additions & 3 deletions media/editor/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@
*--------------------------------------------------------*/

import { atom, DefaultValue, selector, selectorFamily } from "recoil";
import { buildEditTimeline, HexDocumentEdit, readUsingRanges } from "../../shared/hexDocumentModel";
import { HexDecoratorType } from "../../shared/decorators";
import {
buildEditTimeline,
HexDocumentEdit,
HexDocumentEditOp,
HexDocumentEmptyInsertEdit,
readUsingRanges,
} from "../../shared/hexDocumentModel";
import {
FromWebviewMessage,
InspectorLocation,
Expand Down Expand Up @@ -150,7 +157,7 @@ export const fileSize = selector({
key: "fileSize",
get: ({ get }) => {
const initial = get(diskFileSize);
const sizeDelta = get(unsavedEditTimeline).sizeDelta;
const sizeDelta = get(unsavedAndDecoratorEditTimeline).sizeDelta;
return initial === undefined ? initial : initial + sizeDelta;
},
});
Expand Down Expand Up @@ -386,13 +393,41 @@ export const unsavedEditTimeline = selector({
},
});

const emptyDecoratorEdits = selector({
key: "emptyDecoratorEdits",
get: ({ get }) => {
return get(readyQuery)
.decorators.filter(record => record.type === HexDecoratorType.Empty)
.map(value => {
return {
op: HexDocumentEditOp.EmptyInsert,
offset: value.range.start,
length: value.range.size,
} as HexDocumentEmptyInsertEdit;
});
},
});

/**
* Creates the edit timeline for the unsaved edits and empty decorators.
*/
export const unsavedAndDecoratorEditTimeline = selector({
key: "unsavedAndDecoratorEditTimeline",
get: ({ get }) => {
return buildEditTimeline([
...get(edits).slice(get(unsavedEditIndex)),
...get(emptyDecoratorEdits),
]);
},
});

export const editedDataPages = selectorFamily({
key: "editedDataPages",
get:
(pageNumber: number) =>
async ({ get }) => {
const pageSize = get(dataPageSize);
const { ranges } = get(unsavedEditTimeline);
const { ranges } = get(unsavedAndDecoratorEditTimeline);
const target = new Uint8Array(pageSize);
const it = readUsingRanges(
{
Expand Down
20 changes: 18 additions & 2 deletions media/editor/util.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,25 @@
/* Decorators */

.diff-insert {
background-color: green;
background-color: rgba(156, 204, 44, 0.4);
filter: opacity(100%);
}

.diff-delete {
background-color: red;
background-color: rgba(255, 0, 0, 0.4);
}

.diff-empty {
background-image: linear-gradient(
-45deg,
var(--vscode-diffEditor-diagonalFill) 12.5%,
#0000 12.5%,
#0000 50%,
var(--vscode-diffEditor-diagonalFill) 50%,
var(--vscode-diffEditor-diagonalFill) 62.5%,
#0000 62.5%,
#0000 100%
);
background-size: 8px 8px;
color: transparent !important;
}
1 change: 1 addition & 0 deletions media/editor/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,4 +185,5 @@ export const getScrollDimensions = (() => {
export const HexDecoratorStyles: { [key in HexDecoratorType]: string } = {
[HexDecoratorType.Insert]: style.diffInsert,
[HexDecoratorType.Delete]: style.diffDelete,
[HexDecoratorType.Empty]: style.diffEmpty,
};
1 change: 1 addition & 0 deletions shared/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Range } from "./util/range";
export enum HexDecoratorType {
Insert,
Delete,
Empty,
}

export interface HexDecorator {
Expand Down
2 changes: 1 addition & 1 deletion shared/hexDiffModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class HexDiffModel {
) {}

public async computeDecorators(uri: vscode.Uri): Promise<HexDecorator[]> {
return await this.saveGuard.execute(async () => {
return this.saveGuard.execute(async () => {
if (this.decorators === undefined) {
const editScript = await MyersDiff.lcs(this.originalModel, this.modifiedModel);
this.decorators = MyersDiff.toDecorator(editScript);
Expand Down
62 changes: 56 additions & 6 deletions shared/hexDocumentModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const enum HexDocumentEditOp {
Insert,
Delete,
Replace,
EmptyInsert,
}

export interface GenericHexDocumentEdit {
Expand All @@ -35,6 +36,19 @@ export interface HexDocumentInsertEdit extends GenericHexDocumentEdit {
value: Uint8Array;
}

/**
* Similar to {@link HexDocumentInsertEdit} but only intended to be used
* when comparing files. Unlike {@link HexDocumentInsertEdit}, this allows us
* to align files without creating unnecessary Uint8Arrays.
*
* note: this is only a visual edit, so shouldn't be treated as a normal
* edit (that's why HexDocumentEdit does not include it)
*/
export interface HexDocumentEmptyInsertEdit extends GenericHexDocumentEdit {
op: HexDocumentEditOp.EmptyInsert;
length: number;
}

export type HexDocumentEdit =
| HexDocumentInsertEdit
| HexDocumentDeleteEdit
Expand Down Expand Up @@ -74,6 +88,7 @@ export const enum EditRangeOp {
Read,
Skip,
Insert,
EmptyInsert,
}

export type EditRange =
Expand All @@ -82,7 +97,8 @@ export type EditRange =
/** Skip starting at "offset" in the edited version of the file */
| { op: EditRangeOp.Skip; editIndex: number; offset: number }
/** Insert "value" at the "offset" in th edited version of the file */
| { op: EditRangeOp.Insert; editIndex: number; offset: number; value: Uint8Array };
| { op: EditRangeOp.Insert; editIndex: number; offset: number; value: Uint8Array }
| { op: EditRangeOp.EmptyInsert; editIndex: number; offset: number; length: number };

export interface IEditTimeline {
/** Instructions on how to read the file, in order. */
Expand Down Expand Up @@ -332,6 +348,21 @@ export async function* readUsingRanges(
continue;
}

if (range.op === EditRangeOp.EmptyInsert) {
const readLast = range.offset + range.length - fromOffset;
if (readLast <= 0) {
continue;
}
const toYield =
readLast < range.length
? buf.fill(0, -readLast).subarray(-readLast)
: buf.fill(0, -range.length).subarray(-range.length);
if (toYield.length > 0) {
yield toYield;
}
continue;
}

if (range.op === EditRangeOp.Insert) {
const readLast = range.offset + range.value.length - fromOffset;
if (readLast <= 0) {
Expand Down Expand Up @@ -363,7 +394,9 @@ export async function* readUsingRanges(
}
}

export const buildEditTimeline = (edits: readonly HexDocumentEdit[]): IEditTimeline => {
export const buildEditTimeline = (
edits: readonly (HexDocumentEdit | HexDocumentEmptyInsertEdit)[],
): IEditTimeline => {
// Serialize all edits to a single, continuous "timeline", which we'll
// iterate through in order to read data and yield bytes.
const ranges: EditRange[] = [{ op: EditRangeOp.Read, editIndex: -1, roffset: 0, offset: 0 }];
Expand All @@ -389,7 +422,7 @@ export const buildEditTimeline = (edits: readonly HexDocumentEdit[]): IEditTimel
before: { op: EditRangeOp.Skip, editIndex, offset: split.offset },
after: { op: EditRangeOp.Skip, editIndex, offset: split.offset + atByte },
};
} else {
} else if (split.op === EditRangeOp.Insert) {
return {
before: {
op: EditRangeOp.Insert,
Expand All @@ -404,6 +437,21 @@ export const buildEditTimeline = (edits: readonly HexDocumentEdit[]): IEditTimel
value: split.value.subarray(atByte),
},
};
} else {
return {
before: {
op: EditRangeOp.EmptyInsert,
editIndex,
offset: split.offset,
length: atByte,
},
after: {
op: EditRangeOp.EmptyInsert,
editIndex,
offset: split.offset + atByte,
length: split.length - atByte,
},
};
}
};

Expand All @@ -426,16 +474,18 @@ export const buildEditTimeline = (edits: readonly HexDocumentEdit[]): IEditTimel
}
const split = ranges[i];

if (edit.op === HexDocumentEditOp.Insert) {
if (edit.op === HexDocumentEditOp.Insert || edit.op === HexDocumentEditOp.EmptyInsert) {
const { before, after } = getSplit(editIndex, split, edit.offset - split.offset);
ranges.splice(
i,
1,
before,
{ op: EditRangeOp.Insert, editIndex, offset: edit.offset, value: edit.value },
edit.op === HexDocumentEditOp.Insert
? { op: EditRangeOp.Insert, editIndex, offset: edit.offset, value: edit.value }
: { op: EditRangeOp.EmptyInsert, editIndex, offset: edit.offset, length: edit.length },
after,
);
shiftAfter(i + 2, edit.value.length);
shiftAfter(i + 2, edit.op === HexDocumentEditOp.Insert ? edit.value.length : edit.length);
} else if (edit.op === HexDocumentEditOp.Delete || edit.op === HexDocumentEditOp.Replace) {
const { before } = getSplit(editIndex, split, edit.offset - split.offset);
let until = searcher(edit.offset + edit.previous.length, ranges);
Expand Down
8 changes: 4 additions & 4 deletions shared/util/myers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,10 @@ export class MyersDiff {
range: new Range(diffType.atPositionOriginal, diffType.atPositionOriginal + 1),
});
} else {
//out.modified.push({
// type: HexDecoratorType.Insert,
// range: new Range(diffType.atPositionOriginal, diffType.atPositionOriginal + 1),
//});
out.original.push({
type: HexDecoratorType.Empty,
range: new Range(diffType.atPositionOriginal, diffType.atPositionOriginal + 1),
});
out.modified.push({
type: HexDecoratorType.Insert,
range: new Range(diffType.valuePositionModified, diffType.valuePositionModified + 1),
Expand Down

0 comments on commit 6312f63

Please sign in to comment.