namespace com.keyman.text { // Defines the base Deadkey-tracking object. export class Deadkey { p: number; // Position of deadkey d: number; // Numerical id of the deadkey o: number; // Ordinal value of the deadkey (resolves same-place conflicts) matched: number; static ordinalSeed: number = 0; constructor(pos: number, id: number) { this.p = pos; this.d = id; this.o = Deadkey.ordinalSeed++; } match(p: number, d: number): boolean { var result:boolean = (this.p == p && this.d == d); return result; } set(): void { this.matched = 1; } reset(): void { this.matched = 0; } before(other: Deadkey): boolean { return this.o < other.o; } clone(): Deadkey { let dk = new Deadkey(this.p, this.d); dk.o = this.o; return dk; } /** * Sorts the deadkeys in reverse order. */ static sortFunc = function(a: Deadkey, b: Deadkey) { // We want descending order, so we want 'later' deadkeys first. if(a.p != b.p) { return b.p - a.p; } else { return b.o - a.o; } }; } // Object-orients deadkey management. export class DeadkeyTracker { dks: Deadkey[] = []; toSortedArray(): Deadkey[] { this.dks = this.dks.sort(Deadkey.sortFunc); return [].concat(this.dks); } clone(): DeadkeyTracker { let dkt = new DeadkeyTracker(); let dks = this.toSortedArray(); // Make sure to clone the deadkeys themselves - the Deadkey object is mutable. dkt.dks = []; dks.forEach(function(value: Deadkey) { dkt.dks.push(value.clone()); }); return dkt; } /** * Function isMatch * Scope Public * @param {number} caretPos current cursor position * @param {number} n expected offset of deadkey from cursor * @param {number} d deadkey * @return {boolean} True if deadkey found selected context matches val * Description Match deadkey at current cursor position */ isMatch(caretPos: number, n: number, d: number): boolean { if(this.dks.length == 0) { return false; // I3318 } var sp=caretPos; n = sp - n; for(var i = 0; i < this.dks.length; i++) { // Don't re-match an already-matched deadkey. It's possible to have two identical // entries, and they should be kept separately. if(this.dks[i].match(n, d) && !this.dks[i].matched) { this.dks[i].set(); // Assumption: since we match the first possible entry in the array, we // match the entry with the lower ordinal - the 'first' deadkey in the position. return true; // I3318 } } this.resetMatched(); // I3318 return false; } add(dk: Deadkey) { this.dks = this.dks.concat(dk); } remove(dk: Deadkey) { var index = this.dks.indexOf(dk); this.dks.splice(index, 1); } clear() { this.dks = []; } resetMatched() { for(let dk of this.dks) { dk.reset(); } } deleteMatched(): void { for(var Li = 0; Li < this.dks.length; Li++) { if(this.dks[Li].matched) { this.dks.splice(Li--, 1); // Don't forget to decrement! } } } /** * Function adjustPositions (formerly _DeadkeyAdjustPos) * Scope Private * @param {number} Lstart start position in context * @param {number} Ldelta characters to adjust by * Description Adjust saved positions of deadkeys in context */ adjustPositions(Lstart: number, Ldelta: number): void { if(Ldelta == 0) { return; } for(let dk of this.dks) { if(dk.p > Lstart) { dk.p += Ldelta; } } } count(): number { return this.dks.length; } } }