Feed one delta; returns the visible text to emit (suppressed spans removed).
(delta: string)
| 152 | |
| 153 | /** Feed one delta; returns the visible text to emit (suppressed spans removed). */ |
| 154 | push(delta: string): string { |
| 155 | const work = this.buf + delta; |
| 156 | this.buf = ''; |
| 157 | let out = ''; |
| 158 | let i = 0; |
| 159 | while (i < work.length) { |
| 160 | const c = work[i]!; |
| 161 | if (c !== '<') { |
| 162 | if (!this.closeTarget) out += this.emit(c); |
| 163 | i++; |
| 164 | continue; |
| 165 | } |
| 166 | const rest = work.slice(i); |
| 167 | const lower = rest.toLowerCase(); |
| 168 | |
| 169 | if (this.closeTarget) { |
| 170 | // Inside a suppressed block — only the matching close tag ends it. |
| 171 | if (lower.startsWith(this.closeTarget)) { |
| 172 | i += this.closeTarget.length; |
| 173 | this.closeTarget = null; |
| 174 | continue; |
| 175 | } |
| 176 | if (isProperPrefixOfAny(lower, [this.closeTarget])) { this.buf = rest; return out; } |
| 177 | i++; // some other '<' inside the block (e.g. <parameter=…>) — keep suppressing |
| 178 | continue; |
| 179 | } |
| 180 | |
| 181 | // Visible: try an opening marker, then a stray closer to swallow. |
| 182 | const open = this.matchOpen(rest, lower); |
| 183 | if (open === 'partial') { this.buf = rest; return out; } |
| 184 | if (open) { this.closeTarget = open.close; i += open.len; continue; } |
| 185 | |
| 186 | const stray = this.matchStrayClose(lower); |
| 187 | if (stray === 'partial') { this.buf = rest; return out; } |
| 188 | if (stray) { i += stray; continue; } // swallow stray closer, emit nothing |
| 189 | |
| 190 | out += this.emit(c); // literal '<' (e.g. "a < b", "<div>") |
| 191 | i++; |
| 192 | } |
| 193 | return out; |
| 194 | } |
| 195 | |
| 196 | /** End of stream: a block that never closed is dropped; any other tail is emitted. */ |
| 197 | flush(): string { |