export function isObject(val: any) { return val !== null && typeof val === 'object' && Array.isArray(val) === false }; export interface StringChange { start: number end: number replacement: string } /** * Apply the changes to the string such that a change in the length * of the string does not break the indexes of the subsequent changes. */ export function spliceChangesIntoString(str: string, changes: StringChange[]) { // If there are no changes, return the original string const firstChange = changes[0] if (!firstChange) { return str } // Sort all changes in order to make it easier to apply them changes.sort((a, b) => { return a.end - b.end || a.start - b.start }) // Append original string between each chunk, and then the chunk itself // This is sort of a String Builder pattern, thus creating less memory pressure let result = '' let previous = firstChange result += str.slice(0, previous.start) result += previous.replacement for (let i = 1; i < changes.length; ++i) { const change = changes[i] if (!change) { continue } result += str.slice(previous.end, change.start) result += change.replacement previous = change } // Add leftover string from last chunk to end result += str.slice(previous.end) return result }