|
5 | 5 |
|
6 | 6 | import { assert } from 'chai'; |
7 | 7 | import { ITerminal } from './Types'; |
8 | | -import { Buffer, DEFAULT_ATTR } from './Buffer'; |
| 8 | +import { Buffer, DEFAULT_ATTR, CHAR_DATA_CHAR_INDEX } from './Buffer'; |
9 | 9 | import { CircularList } from './common/CircularList'; |
10 | | -import { MockTerminal } from './utils/TestUtils.test'; |
| 10 | +import { MockTerminal, TestTerminal } from './utils/TestUtils.test'; |
11 | 11 | import { BufferLine } from './BufferLine'; |
12 | 12 |
|
13 | 13 | const INIT_COLS = 80; |
@@ -347,4 +347,171 @@ describe('Buffer', () => { |
347 | 347 | assert.equal(str3, 'πa'); |
348 | 348 | }); |
349 | 349 | }); |
| 350 | + describe('stringIndexToBufferIndex', () => { |
| 351 | + let terminal: TestTerminal; |
| 352 | + |
| 353 | + beforeEach(() => { |
| 354 | + terminal = new TestTerminal({rows: 5, cols: 10}); |
| 355 | + }); |
| 356 | + |
| 357 | + it('multiline ascii', () => { |
| 358 | + const input = 'This is ASCII text spanning multiple lines.'; |
| 359 | + terminal.writeSync(input); |
| 360 | + const s = terminal.buffer.iterator(true).next().content; |
| 361 | + assert.equal(input, s); |
| 362 | + for (let i = 0; i < input.length; ++i) { |
| 363 | + const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); |
| 364 | + assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex); |
| 365 | + } |
| 366 | + }); |
| 367 | + |
| 368 | + it('combining e\u0301 in a sentence', () => { |
| 369 | + const input = 'Sitting in the cafe\u0301 drinking coffee.'; |
| 370 | + terminal.writeSync(input); |
| 371 | + const s = terminal.buffer.iterator(true).next().content; |
| 372 | + assert.equal(input, s); |
| 373 | + for (let i = 0; i < 19; ++i) { |
| 374 | + const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); |
| 375 | + assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex); |
| 376 | + } |
| 377 | + // string index 18 & 19 point to combining char e\u0301 ---> same buffer Index |
| 378 | + assert.deepEqual( |
| 379 | + terminal.buffer.stringIndexToBufferIndex(0, 18), |
| 380 | + terminal.buffer.stringIndexToBufferIndex(0, 19)); |
| 381 | + // after the combining char every string index has an offset of -1 |
| 382 | + for (let i = 19; i < input.length; ++i) { |
| 383 | + const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); |
| 384 | + assert.deepEqual([((i - 1) / terminal.cols) | 0, (i - 1) % terminal.cols], bufferIndex); |
| 385 | + } |
| 386 | + }); |
| 387 | + |
| 388 | + it('multiline combining e\u0301', () => { |
| 389 | + const input = 'e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301'; |
| 390 | + terminal.writeSync(input); |
| 391 | + const s = terminal.buffer.iterator(true).next().content; |
| 392 | + assert.equal(input, s); |
| 393 | + // every buffer cell index contains 2 string indices |
| 394 | + for (let i = 0; i < input.length; ++i) { |
| 395 | + const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); |
| 396 | + assert.deepEqual([((i >> 1) / terminal.cols) | 0, (i >> 1) % terminal.cols], bufferIndex); |
| 397 | + } |
| 398 | + }); |
| 399 | + |
| 400 | + it('surrogate char in a sentence', () => { |
| 401 | + const input = 'The π is a clef widely used in modern notation.'; |
| 402 | + terminal.writeSync(input); |
| 403 | + const s = terminal.buffer.iterator(true).next().content; |
| 404 | + assert.equal(input, s); |
| 405 | + for (let i = 0; i < 5; ++i) { |
| 406 | + const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); |
| 407 | + assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex); |
| 408 | + } |
| 409 | + // string index 4 & 5 point to surrogate char π ---> same buffer Index |
| 410 | + assert.deepEqual( |
| 411 | + terminal.buffer.stringIndexToBufferIndex(0, 4), |
| 412 | + terminal.buffer.stringIndexToBufferIndex(0, 5)); |
| 413 | + // after the combining char every string index has an offset of -1 |
| 414 | + for (let i = 5; i < input.length; ++i) { |
| 415 | + const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); |
| 416 | + assert.deepEqual([((i - 1) / terminal.cols) | 0, (i - 1) % terminal.cols], bufferIndex); |
| 417 | + } |
| 418 | + }); |
| 419 | + |
| 420 | + it('multiline surrogate char', () => { |
| 421 | + const input = 'πππππππππππππππππππππππππππ'; |
| 422 | + terminal.writeSync(input); |
| 423 | + const s = terminal.buffer.iterator(true).next().content; |
| 424 | + assert.equal(input, s); |
| 425 | + // every buffer cell index contains 2 string indices |
| 426 | + for (let i = 0; i < input.length; ++i) { |
| 427 | + const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); |
| 428 | + assert.deepEqual([((i >> 1) / terminal.cols) | 0, (i >> 1) % terminal.cols], bufferIndex); |
| 429 | + } |
| 430 | + }); |
| 431 | + |
| 432 | + it('surrogate char with combining', () => { |
| 433 | + // eye of Ra with acute accent - string length of 3 |
| 434 | + const input = 'π\u0301 - the eye hiroglyph with an acute accent.'; |
| 435 | + terminal.writeSync(input); |
| 436 | + const s = terminal.buffer.iterator(true).next().content; |
| 437 | + assert.equal(input, s); |
| 438 | + // index 0..2 should map to 0 |
| 439 | + assert.deepEqual([0, 0], terminal.buffer.stringIndexToBufferIndex(0, 1)); |
| 440 | + assert.deepEqual([0, 0], terminal.buffer.stringIndexToBufferIndex(0, 2)); |
| 441 | + for (let i = 2; i < input.length; ++i) { |
| 442 | + const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); |
| 443 | + assert.deepEqual([((i - 2) / terminal.cols) | 0, (i - 2) % terminal.cols], bufferIndex); |
| 444 | + } |
| 445 | + }); |
| 446 | + |
| 447 | + it('multiline surrogate with combining', () => { |
| 448 | + const input = 'π\u0301π\u0301π\u0301π\u0301π\u0301π\u0301π\u0301π\u0301π\u0301π\u0301π\u0301π\u0301π\u0301π\u0301'; |
| 449 | + terminal.writeSync(input); |
| 450 | + const s = terminal.buffer.iterator(true).next().content; |
| 451 | + assert.equal(input, s); |
| 452 | + // every buffer cell index contains 3 string indices |
| 453 | + for (let i = 0; i < input.length; ++i) { |
| 454 | + const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); |
| 455 | + assert.deepEqual([(((i / 3) | 0) / terminal.cols) | 0, ((i / 3) | 0) % terminal.cols], bufferIndex); |
| 456 | + } |
| 457 | + }); |
| 458 | + |
| 459 | + it('fullwidth chars', () => { |
| 460 | + const input = 'These οΌοΌοΌ are some fat numbers.'; |
| 461 | + terminal.writeSync(input); |
| 462 | + const s = terminal.buffer.iterator(true).next().content; |
| 463 | + assert.equal(input, s); |
| 464 | + for (let i = 0; i < 6; ++i) { |
| 465 | + const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); |
| 466 | + assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex); |
| 467 | + } |
| 468 | + // string index 6, 7, 8 take 2 cells |
| 469 | + assert.deepEqual([0, 8], terminal.buffer.stringIndexToBufferIndex(0, 7)); |
| 470 | + assert.deepEqual([1, 0], terminal.buffer.stringIndexToBufferIndex(0, 8)); |
| 471 | + // rest of the string has offset of +3 |
| 472 | + for (let i = 9; i < input.length; ++i) { |
| 473 | + const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); |
| 474 | + assert.deepEqual([((i + 3) / terminal.cols) | 0, (i + 3) % terminal.cols], bufferIndex); |
| 475 | + } |
| 476 | + }); |
| 477 | + |
| 478 | + it('multiline fullwidth chars', () => { |
| 479 | + const input = 'οΌοΌοΌοΌοΌοΌοΌοΌοΌοΌοΌοΌοΌοΌοΌοΌοΌοΌοΌοΌ'; |
| 480 | + terminal.writeSync(input); |
| 481 | + const s = terminal.buffer.iterator(true).next().content; |
| 482 | + assert.equal(input, s); |
| 483 | + for (let i = 9; i < input.length; ++i) { |
| 484 | + const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); |
| 485 | + assert.deepEqual([((i << 1) / terminal.cols) | 0, (i << 1) % terminal.cols], bufferIndex); |
| 486 | + } |
| 487 | + }); |
| 488 | + |
| 489 | + it('fullwidth combining with emoji - match emoji cell', () => { |
| 490 | + const input = 'Lots of οΏ₯\u0301 make me π.'; |
| 491 | + terminal.writeSync(input); |
| 492 | + const s = terminal.buffer.iterator(true).next().content; |
| 493 | + assert.equal(input, s); |
| 494 | + const stringIndex = s.match(/π/).index; |
| 495 | + const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, stringIndex); |
| 496 | + assert(terminal.buffer.lines.get(bufferIndex[0]).get(bufferIndex[1])[CHAR_DATA_CHAR_INDEX], 'π'); |
| 497 | + }); |
| 498 | + |
| 499 | + it('multiline fullwidth chars with offset 1 (currently tests for broken behavior)', () => { |
| 500 | + const input = 'aοΌοΌοΌοΌοΌοΌοΌοΌοΌοΌοΌοΌοΌοΌοΌοΌοΌοΌοΌοΌ'; |
| 501 | + // the 'a' at the beginning moves all fullwidth chars one to the right |
| 502 | + // now the end of the line contains a dangling empty cell since |
| 503 | + // the next fullwidth char has to wrap early |
| 504 | + // the dangling last cell is wrongly added in the string |
| 505 | + // --> fixable after resolving #1685 |
| 506 | + terminal.writeSync(input); |
| 507 | + // TODO: reenable after fix |
| 508 | + // const s = terminal.buffer.contents(true).toArray()[0]; |
| 509 | + // assert.equal(input, s); |
| 510 | + for (let i = 10; i < input.length; ++i) { |
| 511 | + const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i + 1); // TODO: remove +1 after fix |
| 512 | + const j = (i - 0) << 1; |
| 513 | + assert.deepEqual([(j / terminal.cols) | 0, j % terminal.cols], bufferIndex); |
| 514 | + } |
| 515 | + }); |
| 516 | + }); |
350 | 517 | }); |
0 commit comments