194 lines
5.4 KiB
TypeScript
194 lines
5.4 KiB
TypeScript
import wasmInit, {
|
|
CursorPosition as wasmCursorPosition,
|
|
reconcile as wasmReconcile,
|
|
TextWithCursors as wasmTextWithCursors,
|
|
SpanWithHistory as wasmSpanWithHistory,
|
|
BuiltinTokenizer,
|
|
reconcileWithHistory as wasmReconcileWithHistory,
|
|
History,
|
|
InitInput,
|
|
} from "reconcile";
|
|
|
|
export interface TextWithCursors {
|
|
/** The document's entire content */
|
|
text: string;
|
|
/** List of cursor positions, can be null or undefined if there are no cursors */
|
|
cursors: null | undefined | CursorPosition[];
|
|
}
|
|
|
|
/**
|
|
* Represents a cursor position with a unique identifier.
|
|
*/
|
|
export interface CursorPosition {
|
|
/** Unique identifier for the cursor */
|
|
id: number;
|
|
/** Character position in the text, 0-based */
|
|
position: number;
|
|
}
|
|
|
|
export interface TextWithCursorsAndHistory {
|
|
/** The document's entire content */
|
|
text: string;
|
|
/** List of cursor positions, can be null or undefined if there are no cursors */
|
|
cursors: null | undefined | CursorPosition[];
|
|
/** List of operations leading to `text` from the 3 ancestors */
|
|
history: SpanWithHistory[];
|
|
}
|
|
|
|
export interface SpanWithHistory {
|
|
/** Span of text associated with the historical opearion */
|
|
text: string;
|
|
/** Origin of the `text` span */
|
|
history: History;
|
|
}
|
|
|
|
/**
|
|
* Supported tokenizer types for text processing.
|
|
*/
|
|
export type Tokenizer = "word" | "character";
|
|
|
|
let isInitialised = false;
|
|
|
|
const UNINITIALISED_MODULE_ERROR =
|
|
"Reconcile module has not been initialized. Please call init() before using any other functions.";
|
|
|
|
/**
|
|
* Initializes the WASM module for text reconciliation.
|
|
* Must be called before using any other functions.
|
|
*
|
|
* The function is idempotent.
|
|
*
|
|
* @param content - Optional initialization input for the WASM module during testing.
|
|
* @returns Promise that resolves when initialization is complete
|
|
*/
|
|
export async function init(content?: InitInput) {
|
|
if (isInitialised) {
|
|
return;
|
|
}
|
|
|
|
await wasmInit(content);
|
|
|
|
isInitialised = true;
|
|
}
|
|
|
|
/**
|
|
* Merges three versions of text (original, left, right) and cursor positions.
|
|
*
|
|
* @param original - The original/base version of the text
|
|
* @param left - The left version of the text, either as string or TextWithCursors
|
|
* @param right - The right version of the text, either as string or TextWithCursors
|
|
* @param tokenizer - The tokenization strategy to use (default: "Word")
|
|
* @returns The reconciled text with merged cursor positions
|
|
*/
|
|
export function reconcile(
|
|
original: string,
|
|
left: string | TextWithCursors,
|
|
right: string | TextWithCursors,
|
|
tokenizer: BuiltinTokenizer = "Word"
|
|
): TextWithCursors {
|
|
if (!isInitialised) {
|
|
throw new Error(UNINITIALISED_MODULE_ERROR);
|
|
}
|
|
|
|
const leftCursor = toWasmTextWithCursors(left);
|
|
const rightCursor = toWasmTextWithCursors(right);
|
|
|
|
const result = wasmReconcile(original, leftCursor, rightCursor, tokenizer);
|
|
|
|
leftCursor.free();
|
|
rightCursor.free();
|
|
|
|
const jsResult = toTextWithCursors(result);
|
|
result.free();
|
|
|
|
return jsResult;
|
|
}
|
|
|
|
/**
|
|
* Merges three versions of text and returns the result with historical information.
|
|
*
|
|
* Calculating the `history` is somewhat more expensive, otherwise this function behaves like `reconcile`.
|
|
*
|
|
* @param original - The original/base version of the text
|
|
* @param left - The left version of the text, either as string or TextWithCursors
|
|
* @param right - The right version of the text, either as string or TextWithCursors
|
|
* @param tokenizer - The tokenization strategy to use (default: "Word")
|
|
* @returns The reconciled text with cursor positions and history of changes
|
|
*/
|
|
export function reconcileWithHistory(
|
|
original: string,
|
|
left: string | TextWithCursors,
|
|
right: string | TextWithCursors,
|
|
tokenizer: BuiltinTokenizer = "Word"
|
|
): TextWithCursorsAndHistory {
|
|
if (!isInitialised) {
|
|
throw new Error(UNINITIALISED_MODULE_ERROR);
|
|
}
|
|
|
|
const leftCursor = toWasmTextWithCursors(left);
|
|
const rightCursor = toWasmTextWithCursors(right);
|
|
|
|
const result = wasmReconcileWithHistory(
|
|
original,
|
|
leftCursor,
|
|
rightCursor,
|
|
tokenizer
|
|
);
|
|
|
|
leftCursor.free();
|
|
rightCursor.free();
|
|
|
|
const jsResult = toTextWithCursors(result);
|
|
const history = result.history().map(toSpanWithHistory);
|
|
result.free();
|
|
|
|
return {
|
|
...jsResult,
|
|
history,
|
|
};
|
|
}
|
|
|
|
function toWasmTextWithCursors(
|
|
text: string | TextWithCursors
|
|
): wasmTextWithCursors {
|
|
const isInputString = typeof text == "string";
|
|
const leftText = isInputString ? text : text.text;
|
|
const leftCursors = isInputString ? [] : text.cursors ?? [];
|
|
|
|
return new wasmTextWithCursors(
|
|
leftText,
|
|
leftCursors.map(toWasmCursorPosition)
|
|
);
|
|
}
|
|
|
|
function toWasmCursorPosition({
|
|
id,
|
|
position,
|
|
}: CursorPosition): wasmCursorPosition {
|
|
return new wasmCursorPosition(id, position);
|
|
}
|
|
|
|
function toTextWithCursors(
|
|
textWithCursor: wasmTextWithCursors
|
|
): TextWithCursors {
|
|
return {
|
|
text: textWithCursor.text(),
|
|
cursors: textWithCursor.cursors().map(toCursorPosition),
|
|
};
|
|
}
|
|
|
|
function toCursorPosition(cursor: wasmCursorPosition): CursorPosition {
|
|
return {
|
|
id: cursor.id(),
|
|
position: cursor.characterPosition(),
|
|
};
|
|
}
|
|
|
|
function toSpanWithHistory(
|
|
textWithHistory: wasmSpanWithHistory
|
|
): SpanWithHistory {
|
|
return {
|
|
text: textWithHistory.text(),
|
|
history: textWithHistory.history(),
|
|
};
|
|
}
|