Skip to content

Commit

Permalink
fix: Invalid emoji deleting
Browse files Browse the repository at this point in the history
  • Loading branch information
miyanokomiya committed Oct 26, 2023
1 parent f7bb724 commit 3ee80e5
Show file tree
Hide file tree
Showing 3 changed files with 225 additions and 22 deletions.
26 changes: 4 additions & 22 deletions src/composables/textEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
getDocRawLength,
getLineLength,
} from "../utils/textEditor";
import * as textEditorUtil from "../utils/textEditor";
import { Size } from "../models";

export function newTextEditorController() {
Expand Down Expand Up @@ -104,10 +105,7 @@ export function newTextEditorController() {
}

function getOutputSelection(): number {
const cursor = getCursor();
const outputFrom = getRawCursor(_composition, cursor);
const outputTo = getRawCursor(_composition, cursor + getSelection());
return outputTo - outputFrom;
return textEditorUtil.getOutputSelection(_composition, getCursor(), getSelection());
}

// This value refers to the last moving position.
Expand Down Expand Up @@ -274,28 +272,12 @@ export function newTextEditorController() {

function getDeltaAndCursorByBackspace(): { delta: DocDelta; cursor: number } {
if (_isDocEmpty) return { delta: getInitialOutput(), cursor: 0 };
if (_composition.length === 1) return { delta: [], cursor: 0 };

const outputCursor = getOutputCursor();
const outputSelection = getOutputSelection();
if (outputSelection > 0) {
return { cursor: outputCursor, delta: [{ retain: outputCursor }, { delete: Math.max(1, outputSelection) }] };
} else {
return { cursor: outputCursor - 1, delta: [{ retain: outputCursor - 1 }, { delete: 1 }] };
}
return textEditorUtil.getDeltaAndCursorByBackspace(_composition, getCursor(), getSelection());
}

function getDeltaAndCursorByDelete(): { delta: DocDelta; cursor: number } {
if (_isDocEmpty) return { delta: getInitialOutput(), cursor: 0 };
if (_composition.length === 1) return { delta: [], cursor: 0 };

const outputCursor = getOutputCursor();
const outputSelection = getOutputSelection();
if (outputSelection > 0) {
return { cursor: outputCursor, delta: [{ retain: outputCursor }, { delete: Math.max(1, outputSelection) }] };
} else {
return { cursor: outputCursor, delta: [{ retain: outputCursor }, { delete: 1 }] };
}
return textEditorUtil.getDeltaAndCursorByDelete(_composition, docLength, getCursor(), getSelection());
}

function getDeltaByApplyInlineStyle(attrs: DocAttributes): DocDelta {
Expand Down
173 changes: 173 additions & 0 deletions src/utils/textEditor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
applyAttrInfoToDocOutput,
applyRangeWidthToLineWord,
getCursorLocationAt,
getDeltaAndCursorByBackspace,
getDeltaAndCursorByDelete,
getDeltaByApplyBlockStyleToDoc,
getDeltaByApplyDocStyle,
getDeltaByApplyInlineStyleToDoc,
Expand All @@ -15,6 +17,7 @@ import {
getLineEndIndex,
getLineHeadIndex,
getLineHeight,
getOutputSelection,
getRawCursor,
getWordRangeAtCursor,
isCursorInDoc,
Expand Down Expand Up @@ -879,3 +882,173 @@ describe("getWordRangeAtCursor", () => {
expect(getWordRangeAtCursor(comp1, 3)).toEqual([3, 0]);
});
});

describe("getOutputSelection", () => {
const bounds = { x: 0, y: 0, width: 4, height: 10 };
const composition = [
{ char: "a", bounds },
{ char: "b", bounds },
{ char: "😄", bounds },
{ char: "😄", bounds },
{ char: "c", bounds },
{ char: "d", bounds },
{ char: "\n", bounds },
];
const nocontent = [{ char: "\n", bounds }];
const empty: DocCompositionItem[] = [];

test("should return raw selection", () => {
expect(getOutputSelection(empty, 0, 0)).toEqual(0);
expect(getOutputSelection(nocontent, 0, 0)).toEqual(0);
expect(getOutputSelection(composition, 0, 1)).toEqual(1);
expect(getOutputSelection(composition, 0, 2)).toEqual(2);
expect(getOutputSelection(composition, 0, 3)).toEqual(4);
expect(getOutputSelection(composition, 0, 4)).toEqual(6);
expect(getOutputSelection(composition, 1, 2)).toEqual(3);
});
});

describe("getDeltaAndCursorByBackspace", () => {
const bounds = { x: 0, y: 0, width: 4, height: 10 };
const composition = [
{ char: "a", bounds },
{ char: "b", bounds },
{ char: "😄", bounds },
{ char: "😄", bounds },
{ char: "c", bounds },
{ char: "d", bounds },
{ char: "\n", bounds },
];
const nocontent = [{ char: "\n", bounds }];
const empty: DocCompositionItem[] = [];

test("should return delta and next cursor: no target", () => {
expect(getDeltaAndCursorByBackspace(empty, 0, 0)).toEqual({
delta: [],
cursor: 0,
});
expect(getDeltaAndCursorByBackspace(nocontent, 0, 0)).toEqual({
delta: [],
cursor: 0,
});
});

test("should return delta and next cursor", () => {
expect(getDeltaAndCursorByBackspace(composition, 1, 0)).toEqual({
delta: [{ retain: 0 }, { delete: 1 }],
cursor: 0,
});
expect(getDeltaAndCursorByBackspace(composition, 2, 0)).toEqual({
delta: [{ retain: 1 }, { delete: 1 }],
cursor: 1,
});
expect(getDeltaAndCursorByBackspace(composition, 1, 1)).toEqual({
delta: [{ retain: 1 }, { delete: 1 }],
cursor: 1,
});
expect(getDeltaAndCursorByBackspace(composition, 0, 2)).toEqual({
delta: [{ retain: 0 }, { delete: 2 }],
cursor: 0,
});
});

test("should return delta and next cursor: emoji", () => {
expect(getDeltaAndCursorByBackspace(composition, 3, 0)).toEqual({
delta: [{ retain: 2 }, { delete: 2 }],
cursor: 2,
});
expect(getDeltaAndCursorByBackspace(composition, 4, 0)).toEqual({
delta: [{ retain: 4 }, { delete: 2 }],
cursor: 3,
});
expect(getDeltaAndCursorByBackspace(composition, 2, 1)).toEqual({
delta: [{ retain: 2 }, { delete: 2 }],
cursor: 2,
});
expect(getDeltaAndCursorByBackspace(composition, 2, 2)).toEqual({
delta: [{ retain: 2 }, { delete: 4 }],
cursor: 2,
});
expect(getDeltaAndCursorByBackspace(composition, 1, 2)).toEqual({
delta: [{ retain: 1 }, { delete: 3 }],
cursor: 1,
});
});
});

describe("getDeltaAndCursorByDelete", () => {
const bounds = { x: 0, y: 0, width: 4, height: 10 };
const composition = [
{ char: "a", bounds },
{ char: "b", bounds },
{ char: "😄", bounds },
{ char: "😄", bounds },
{ char: "c", bounds },
{ char: "d", bounds },
{ char: "\n", bounds },
];
const docLength = composition.length;
const nocontent = [{ char: "\n", bounds }];
const empty: DocCompositionItem[] = [];

test("should return delta and next cursor: no target", () => {
expect(getDeltaAndCursorByDelete(empty, 0, 0, 0)).toEqual({
delta: [],
cursor: 0,
});
expect(getDeltaAndCursorByDelete(nocontent, 1, 0, 0)).toEqual({
delta: [],
cursor: 0,
});
});

test("should return delta and next cursor", () => {
expect(getDeltaAndCursorByDelete(composition, docLength, 0, 0)).toEqual({
delta: [{ retain: 0 }, { delete: 1 }],
cursor: 0,
});
expect(getDeltaAndCursorByDelete(composition, docLength, 1, 0)).toEqual({
delta: [{ retain: 1 }, { delete: 1 }],
cursor: 1,
});
expect(getDeltaAndCursorByDelete(composition, docLength, 5, 0)).toEqual({
delta: [{ retain: 7 }, { delete: 1 }],
cursor: 5,
});
expect(getDeltaAndCursorByDelete(composition, docLength, 6, 0)).toEqual({
delta: [{ retain: 8 }, { delete: 0 }],
cursor: 6,
});
expect(getDeltaAndCursorByDelete(composition, docLength, 1, 1)).toEqual({
delta: [{ retain: 1 }, { delete: 1 }],
cursor: 1,
});
expect(getDeltaAndCursorByDelete(composition, docLength, 0, 2)).toEqual({
delta: [{ retain: 0 }, { delete: 2 }],
cursor: 0,
});
});

test("should return delta and next cursor: emoji", () => {
expect(getDeltaAndCursorByDelete(composition, docLength, 2, 0)).toEqual({
delta: [{ retain: 2 }, { delete: 2 }],
cursor: 2,
});
expect(getDeltaAndCursorByDelete(composition, docLength, 3, 0)).toEqual({
delta: [{ retain: 4 }, { delete: 2 }],
cursor: 3,
});
expect(getDeltaAndCursorByDelete(composition, docLength, 2, 1)).toEqual({
delta: [{ retain: 2 }, { delete: 2 }],
cursor: 2,
});
expect(getDeltaAndCursorByDelete(composition, docLength, 2, 2)).toEqual({
delta: [{ retain: 2 }, { delete: 4 }],
cursor: 2,
});
expect(getDeltaAndCursorByDelete(composition, docLength, 1, 2)).toEqual({
delta: [{ retain: 1 }, { delete: 3 }],
cursor: 1,
});
});
});
48 changes: 48 additions & 0 deletions src/utils/textEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -854,3 +854,51 @@ export function calcOriginalDocSize(doc: DocOutput, ctx: CanvasRenderingContext2
});
return { width, height };
}

export function getOutputSelection(composition: DocCompositionItem[], cursor: number, selection: number): number {
const outputFrom = getRawCursor(composition, cursor);
const outputTo = getRawCursor(composition, cursor + selection);
return outputTo - outputFrom;
}

export function getDeltaAndCursorByBackspace(
composition: DocCompositionItem[],
cursor: number,
selection: number,
): { delta: DocDelta; cursor: number } {
if (composition.length <= 1) return { delta: [], cursor: 0 };

const outputCursor = getRawCursor(composition, cursor);
const outputSelection = getOutputSelection(composition, cursor, selection);
if (outputSelection > 0) {
return { cursor, delta: [{ retain: outputCursor }, { delete: outputSelection }] };
} else {
const cursorMinus1 = Math.max(cursor - 1, 0);
const outputCursorMinus1 = getRawCursor(composition, cursorMinus1);
return {
cursor: cursorMinus1,
delta: [{ retain: outputCursorMinus1 }, { delete: outputCursor - outputCursorMinus1 }],
};
}
}

export function getDeltaAndCursorByDelete(
composition: DocCompositionItem[],
docLength: number,
cursor: number,
selection: number,
): { delta: DocDelta; cursor: number } {
if (composition.length <= 1) return { delta: [], cursor: 0 };

const outputCursor = getRawCursor(composition, cursor);
const outputSelection = getOutputSelection(composition, cursor, selection);
if (outputSelection > 0) {
return { cursor, delta: [{ retain: outputCursor }, { delete: outputSelection }] };
} else {
const outputCursorPlus1 = getRawCursor(composition, Math.min(cursor + 1, docLength - 1));
return {
cursor,
delta: [{ retain: outputCursor }, { delete: outputCursorPlus1 - outputCursor }],
};
}
}

0 comments on commit 3ee80e5

Please sign in to comment.