@mariozechner/pi-tui 0.57.1 → 0.58.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,9 +3,69 @@ import { decodeKittyPrintable, matchesKey } from "../keys.js";
3
3
  import { KillRing } from "../kill-ring.js";
4
4
  import { CURSOR_MARKER } from "../tui.js";
5
5
  import { UndoStack } from "../undo-stack.js";
6
- import { getSegmenter, isPunctuationChar, isWhitespaceChar, visibleWidth } from "../utils.js";
6
+ import { getSegmenter, isPunctuationChar, isWhitespaceChar, truncateToWidth, visibleWidth } from "../utils.js";
7
7
  import { SelectList } from "./select-list.js";
8
- const segmenter = getSegmenter();
8
+ const baseSegmenter = getSegmenter();
9
+ /** Regex matching paste markers like `[paste #1 +123 lines]` or `[paste #2 1234 chars]`. */
10
+ const PASTE_MARKER_REGEX = /\[paste #(\d+)( (\+\d+ lines|\d+ chars))?\]/g;
11
+ /** Non-global version for single-segment testing. */
12
+ const PASTE_MARKER_SINGLE = /^\[paste #(\d+)( (\+\d+ lines|\d+ chars))?\]$/;
13
+ /** Check if a segment is a paste marker (i.e. was merged by segmentWithMarkers). */
14
+ function isPasteMarker(segment) {
15
+ return segment.length >= 10 && PASTE_MARKER_SINGLE.test(segment);
16
+ }
17
+ /**
18
+ * A segmenter that wraps Intl.Segmenter and merges graphemes that fall
19
+ * within paste markers into single atomic segments. This makes cursor
20
+ * movement, deletion, word-wrap, etc. treat paste markers as single units.
21
+ *
22
+ * Only markers whose numeric ID exists in `validIds` are merged.
23
+ */
24
+ function segmentWithMarkers(text, validIds) {
25
+ // Fast path: no paste markers in the text or no valid IDs.
26
+ if (validIds.size === 0 || !text.includes("[paste #")) {
27
+ return baseSegmenter.segment(text);
28
+ }
29
+ // Find all marker spans with valid IDs.
30
+ const markers = [];
31
+ for (const m of text.matchAll(PASTE_MARKER_REGEX)) {
32
+ const id = Number.parseInt(m[1], 10);
33
+ if (!validIds.has(id))
34
+ continue;
35
+ markers.push({ start: m.index, end: m.index + m[0].length });
36
+ }
37
+ if (markers.length === 0) {
38
+ return baseSegmenter.segment(text);
39
+ }
40
+ // Build merged segment list.
41
+ const baseSegments = baseSegmenter.segment(text);
42
+ const result = [];
43
+ let markerIdx = 0;
44
+ for (const seg of baseSegments) {
45
+ // Skip past markers that are entirely before this segment.
46
+ while (markerIdx < markers.length && markers[markerIdx].end <= seg.index) {
47
+ markerIdx++;
48
+ }
49
+ const marker = markerIdx < markers.length ? markers[markerIdx] : null;
50
+ if (marker && seg.index >= marker.start && seg.index < marker.end) {
51
+ // This segment falls inside a marker.
52
+ // If this is the first segment of the marker, emit a merged segment.
53
+ if (seg.index === marker.start) {
54
+ const markerText = text.slice(marker.start, marker.end);
55
+ result.push({
56
+ segment: markerText,
57
+ index: marker.start,
58
+ input: text,
59
+ });
60
+ }
61
+ // Otherwise skip (already merged into the first segment).
62
+ }
63
+ else {
64
+ result.push(seg);
65
+ }
66
+ }
67
+ return result;
68
+ }
9
69
  /**
10
70
  * Split a line into word-wrapped chunks.
11
71
  * Wraps at word boundaries when possible, falling back to character-level
@@ -13,9 +73,11 @@ const segmenter = getSegmenter();
13
73
  *
14
74
  * @param line - The text line to wrap
15
75
  * @param maxWidth - Maximum visible width per chunk
76
+ * @param preSegmented - Optional pre-segmented graphemes (e.g. with paste-marker awareness).
77
+ * When omitted the default Intl.Segmenter is used.
16
78
  * @returns Array of chunks with text and position information
17
79
  */
18
- export function wordWrapLine(line, maxWidth) {
80
+ export function wordWrapLine(line, maxWidth, preSegmented) {
19
81
  if (!line || maxWidth <= 0) {
20
82
  return [{ text: "", startIndex: 0, endIndex: 0 }];
21
83
  }
@@ -24,7 +86,7 @@ export function wordWrapLine(line, maxWidth) {
24
86
  return [{ text: line, startIndex: 0, endIndex: line.length }];
25
87
  }
26
88
  const chunks = [];
27
- const segments = [...segmenter.segment(line)];
89
+ const segments = preSegmented ?? [...baseSegmenter.segment(line)];
28
90
  let currentWidth = 0;
29
91
  let chunkStart = 0;
30
92
  // Wrap opportunity: the position after the last whitespace before a non-whitespace
@@ -36,30 +98,51 @@ export function wordWrapLine(line, maxWidth) {
36
98
  const grapheme = seg.segment;
37
99
  const gWidth = visibleWidth(grapheme);
38
100
  const charIndex = seg.index;
39
- const isWs = isWhitespaceChar(grapheme);
101
+ const isWs = !isPasteMarker(grapheme) && isWhitespaceChar(grapheme);
40
102
  // Overflow check before advancing.
41
103
  if (currentWidth + gWidth > maxWidth) {
42
- if (wrapOppIndex >= 0) {
43
- // Backtrack to last wrap opportunity.
104
+ if (wrapOppIndex >= 0 && currentWidth - wrapOppWidth + gWidth <= maxWidth) {
105
+ // Backtrack to last wrap opportunity (the remaining content
106
+ // plus the current grapheme still fits within maxWidth).
44
107
  chunks.push({ text: line.slice(chunkStart, wrapOppIndex), startIndex: chunkStart, endIndex: wrapOppIndex });
45
108
  chunkStart = wrapOppIndex;
46
109
  currentWidth -= wrapOppWidth;
47
110
  }
48
111
  else if (chunkStart < charIndex) {
49
- // No wrap opportunity: force-break at current position.
112
+ // No viable wrap opportunity: force-break at current position.
113
+ // This also handles the case where backtracking to a word
114
+ // boundary wouldn't help because the remaining content plus
115
+ // the current grapheme (e.g. a wide character) still exceeds
116
+ // maxWidth.
50
117
  chunks.push({ text: line.slice(chunkStart, charIndex), startIndex: chunkStart, endIndex: charIndex });
51
118
  chunkStart = charIndex;
52
119
  currentWidth = 0;
53
120
  }
54
121
  wrapOppIndex = -1;
55
122
  }
123
+ if (gWidth > maxWidth) {
124
+ // Single atomic segment wider than maxWidth (e.g. paste marker
125
+ // in a narrow terminal). Re-wrap it at grapheme granularity.
126
+ // The segment remains logically atomic for cursor
127
+ // movement / editing — the split is purely visual for word-wrap layout.
128
+ const subChunks = wordWrapLine(grapheme, maxWidth);
129
+ for (let j = 0; j < subChunks.length - 1; j++) {
130
+ const sc = subChunks[j];
131
+ chunks.push({ text: sc.text, startIndex: charIndex + sc.startIndex, endIndex: charIndex + sc.endIndex });
132
+ }
133
+ const last = subChunks[subChunks.length - 1];
134
+ chunkStart = charIndex + last.startIndex;
135
+ currentWidth = visibleWidth(last.text);
136
+ wrapOppIndex = -1;
137
+ continue;
138
+ }
56
139
  // Advance.
57
140
  currentWidth += gWidth;
58
141
  // Record wrap opportunity: whitespace followed by non-whitespace.
59
142
  // Multiple spaces join (no break between them); the break point is
60
143
  // after the last space before the next word.
61
144
  const next = segments[i + 1];
62
- if (isWs && next && !isWhitespaceChar(next.segment)) {
145
+ if (isWs && next && (isPasteMarker(next.segment) || !isWhitespaceChar(next.segment))) {
63
146
  wrapOppIndex = next.index;
64
147
  wrapOppWidth = currentWidth;
65
148
  }
@@ -121,6 +204,14 @@ export class Editor {
121
204
  const maxVisible = options.autocompleteMaxVisible ?? 5;
122
205
  this.autocompleteMaxVisible = Number.isFinite(maxVisible) ? Math.max(3, Math.min(20, Math.floor(maxVisible))) : 5;
123
206
  }
207
+ /** Set of currently valid paste IDs, for marker-aware segmentation. */
208
+ validPasteIds() {
209
+ return new Set(this.pastes.keys());
210
+ }
211
+ /** Segment text with paste-marker awareness, only merging markers with valid IDs. */
212
+ segment(text) {
213
+ return segmentWithMarkers(text, this.validPasteIds());
214
+ }
124
215
  getPaddingX() {
125
216
  return this.paddingX;
126
217
  }
@@ -196,7 +287,7 @@ export class Editor {
196
287
  }
197
288
  /** Internal setText that doesn't reset history state - used by navigateHistory */
198
289
  setTextInternal(text) {
199
- const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
290
+ const lines = text.split("\n");
200
291
  this.state.lines = lines.length === 0 ? [""] : lines;
201
292
  this.state.cursorLine = this.state.lines.length - 1;
202
293
  this.setCursorCol(this.state.lines[this.state.cursorLine]?.length || 0);
@@ -247,7 +338,12 @@ export class Editor {
247
338
  if (this.scrollOffset > 0) {
248
339
  const indicator = `─── ↑ ${this.scrollOffset} more `;
249
340
  const remaining = width - visibleWidth(indicator);
250
- result.push(this.borderColor(indicator + "─".repeat(Math.max(0, remaining))));
341
+ if (remaining >= 0) {
342
+ result.push(this.borderColor(indicator + "─".repeat(remaining)));
343
+ }
344
+ else {
345
+ result.push(this.borderColor(truncateToWidth(indicator, width)));
346
+ }
251
347
  }
252
348
  else {
253
349
  result.push(horizontal.repeat(width));
@@ -268,7 +364,7 @@ export class Editor {
268
364
  if (after.length > 0) {
269
365
  // Cursor is on a character (grapheme) - replace it with highlighted version
270
366
  // Get the first grapheme from 'after'
271
- const afterGraphemes = [...segmenter.segment(after)];
367
+ const afterGraphemes = [...this.segment(after)];
272
368
  const firstGrapheme = afterGraphemes[0]?.segment || "";
273
369
  const restAfter = after.slice(firstGrapheme.length);
274
370
  const cursor = `\x1b[7m${firstGrapheme}\x1b[0m`;
@@ -607,7 +703,7 @@ export class Editor {
607
703
  }
608
704
  else {
609
705
  // Line needs wrapping - use word-aware wrapping
610
- const chunks = wordWrapLine(line, contentWidth);
706
+ const chunks = wordWrapLine(line, contentWidth, [...this.segment(line)]);
611
707
  for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
612
708
  const chunk = chunks[chunkIndex];
613
709
  if (!chunk)
@@ -659,17 +755,20 @@ export class Editor {
659
755
  getText() {
660
756
  return this.state.lines.join("\n");
661
757
  }
758
+ expandPasteMarkers(text) {
759
+ let result = text;
760
+ for (const [pasteId, pasteContent] of this.pastes) {
761
+ const markerRegex = new RegExp(`\\[paste #${pasteId}( (\\+\\d+ lines|\\d+ chars))?\\]`, "g");
762
+ result = result.replace(markerRegex, () => pasteContent);
763
+ }
764
+ return result;
765
+ }
662
766
  /**
663
767
  * Get text with paste markers expanded to their actual content.
664
768
  * Use this when you need the full content (e.g., for external editor).
665
769
  */
666
770
  getExpandedText() {
667
- let result = this.state.lines.join("\n");
668
- for (const [pasteId, pasteContent] of this.pastes) {
669
- const markerRegex = new RegExp(`\\[paste #${pasteId}( (\\+\\d+ lines|\\d+ chars))?\\]`, "g");
670
- result = result.replace(markerRegex, pasteContent);
671
- }
672
- return result;
771
+ return this.expandPasteMarkers(this.state.lines.join("\n"));
673
772
  }
674
773
  getLines() {
675
774
  return [...this.state.lines];
@@ -680,11 +779,12 @@ export class Editor {
680
779
  setText(text) {
681
780
  this.lastAction = null;
682
781
  this.historyIndex = -1; // Exit history browsing mode
782
+ const normalized = this.normalizeText(text);
683
783
  // Push undo snapshot if content differs (makes programmatic changes undoable)
684
- if (this.getText() !== text) {
784
+ if (this.getText() !== normalized) {
685
785
  this.pushUndoSnapshot();
686
786
  }
687
- this.setTextInternal(text);
787
+ this.setTextInternal(normalized);
688
788
  }
689
789
  /**
690
790
  * Insert text at the current cursor position.
@@ -699,6 +799,14 @@ export class Editor {
699
799
  this.historyIndex = -1;
700
800
  this.insertTextAtCursorInternal(text);
701
801
  }
802
+ /**
803
+ * Normalize text for editor storage:
804
+ * - Normalize line endings (\r\n and \r -> \n)
805
+ * - Expand tabs to 4 spaces
806
+ */
807
+ normalizeText(text) {
808
+ return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\t/g, " ");
809
+ }
702
810
  /**
703
811
  * Internal text insertion at cursor. Handles single and multi-line text.
704
812
  * Does not push undo snapshots or trigger autocomplete - caller is responsible.
@@ -707,8 +815,8 @@ export class Editor {
707
815
  insertTextAtCursorInternal(text) {
708
816
  if (!text)
709
817
  return;
710
- // Normalize line endings
711
- const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
818
+ // Normalize line endings and tabs
819
+ const normalized = this.normalizeText(text);
712
820
  const insertedLines = normalized.split("\n");
713
821
  const currentLine = this.state.lines[this.state.cursorLine] || "";
714
822
  const beforeCursor = currentLine.slice(0, this.state.cursorCol);
@@ -799,12 +907,10 @@ export class Editor {
799
907
  this.historyIndex = -1; // Exit history browsing mode
800
908
  this.lastAction = null;
801
909
  this.pushUndoSnapshot();
802
- // Clean the pasted text
803
- const cleanText = pastedText.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
804
- // Convert tabs to spaces (4 spaces per tab)
805
- const tabExpandedText = cleanText.replace(/\t/g, " ");
910
+ // Clean the pasted text: normalize line endings, expand tabs
911
+ const cleanText = this.normalizeText(pastedText);
806
912
  // Filter out non-printable characters except newlines
807
- let filteredText = tabExpandedText
913
+ let filteredText = cleanText
808
914
  .split("")
809
915
  .filter((char) => char === "\n" || char.charCodeAt(0) >= 32)
810
916
  .join("");
@@ -871,11 +977,7 @@ export class Editor {
871
977
  return this.state.cursorCol > 0 && currentLine[this.state.cursorCol - 1] === "\\";
872
978
  }
873
979
  submitValue() {
874
- let result = this.state.lines.join("\n").trim();
875
- for (const [pasteId, pasteContent] of this.pastes) {
876
- const markerRegex = new RegExp(`\\[paste #${pasteId}( (\\+\\d+ lines|\\d+ chars))?\\]`, "g");
877
- result = result.replace(markerRegex, pasteContent);
878
- }
980
+ const result = this.expandPasteMarkers(this.state.lines.join("\n")).trim();
879
981
  this.state = { lines: [""], cursorLine: 0, cursorCol: 0 };
880
982
  this.pastes.clear();
881
983
  this.pasteCounter = 0;
@@ -897,7 +999,7 @@ export class Editor {
897
999
  const line = this.state.lines[this.state.cursorLine] || "";
898
1000
  const beforeCursor = line.slice(0, this.state.cursorCol);
899
1001
  // Find the last grapheme in the text before cursor
900
- const graphemes = [...segmenter.segment(beforeCursor)];
1002
+ const graphemes = [...this.segment(beforeCursor)];
901
1003
  const lastGrapheme = graphemes[graphemes.length - 1];
902
1004
  const graphemeLength = lastGrapheme ? lastGrapheme.segment.length : 1;
903
1005
  const before = line.slice(0, this.state.cursorCol - graphemeLength);
@@ -966,6 +1068,21 @@ export class Editor {
966
1068
  const targetCol = targetVL.startCol + moveToVisualCol;
967
1069
  const logicalLine = this.state.lines[targetVL.logicalLine] || "";
968
1070
  this.state.cursorCol = Math.min(targetCol, logicalLine.length);
1071
+ // Snap cursor to atomic segment boundary (e.g. paste markers)
1072
+ // so the cursor never lands in the middle of a multi-grapheme unit.
1073
+ // Single-grapheme segments don't need snapping.
1074
+ const segments = [...this.segment(logicalLine)];
1075
+ for (const seg of segments) {
1076
+ if (seg.index > this.state.cursorCol)
1077
+ break;
1078
+ if (seg.segment.length <= 1)
1079
+ continue;
1080
+ if (this.state.cursorCol < seg.index + seg.segment.length) {
1081
+ // jump to the start of the segment when moving up, to the end when moving down.
1082
+ this.state.cursorCol = currentVisualLine > targetVisualLine ? seg.index : seg.index + seg.segment.length;
1083
+ break;
1084
+ }
1085
+ }
969
1086
  }
970
1087
  }
971
1088
  /**
@@ -1152,7 +1269,7 @@ export class Editor {
1152
1269
  // Delete grapheme at cursor position (handles emojis, combining characters, etc.)
1153
1270
  const afterCursor = currentLine.slice(this.state.cursorCol);
1154
1271
  // Find the first grapheme at cursor
1155
- const graphemes = [...segmenter.segment(afterCursor)];
1272
+ const graphemes = [...this.segment(afterCursor)];
1156
1273
  const firstGrapheme = graphemes[0];
1157
1274
  const graphemeLength = firstGrapheme ? firstGrapheme.segment.length : 1;
1158
1275
  const before = currentLine.slice(0, this.state.cursorCol);
@@ -1207,7 +1324,7 @@ export class Editor {
1207
1324
  }
1208
1325
  else {
1209
1326
  // Line needs wrapping - use word-aware wrapping
1210
- const chunks = wordWrapLine(line, width);
1327
+ const chunks = wordWrapLine(line, width, [...this.segment(line)]);
1211
1328
  for (const chunk of chunks) {
1212
1329
  visualLines.push({
1213
1330
  logicalLine: i,
@@ -1256,7 +1373,7 @@ export class Editor {
1256
1373
  // Moving right - move by one grapheme (handles emojis, combining characters, etc.)
1257
1374
  if (this.state.cursorCol < currentLine.length) {
1258
1375
  const afterCursor = currentLine.slice(this.state.cursorCol);
1259
- const graphemes = [...segmenter.segment(afterCursor)];
1376
+ const graphemes = [...this.segment(afterCursor)];
1260
1377
  const firstGrapheme = graphemes[0];
1261
1378
  this.setCursorCol(this.state.cursorCol + (firstGrapheme ? firstGrapheme.segment.length : 1));
1262
1379
  }
@@ -1277,7 +1394,7 @@ export class Editor {
1277
1394
  // Moving left - move by one grapheme (handles emojis, combining characters, etc.)
1278
1395
  if (this.state.cursorCol > 0) {
1279
1396
  const beforeCursor = currentLine.slice(0, this.state.cursorCol);
1280
- const graphemes = [...segmenter.segment(beforeCursor)];
1397
+ const graphemes = [...this.segment(beforeCursor)];
1281
1398
  const lastGrapheme = graphemes[graphemes.length - 1];
1282
1399
  this.setCursorCol(this.state.cursorCol - (lastGrapheme ? lastGrapheme.segment.length : 1));
1283
1400
  }
@@ -1316,17 +1433,25 @@ export class Editor {
1316
1433
  return;
1317
1434
  }
1318
1435
  const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
1319
- const graphemes = [...segmenter.segment(textBeforeCursor)];
1436
+ const graphemes = [...this.segment(textBeforeCursor)];
1320
1437
  let newCol = this.state.cursorCol;
1321
1438
  // Skip trailing whitespace
1322
- while (graphemes.length > 0 && isWhitespaceChar(graphemes[graphemes.length - 1]?.segment || "")) {
1439
+ while (graphemes.length > 0 &&
1440
+ !isPasteMarker(graphemes[graphemes.length - 1]?.segment || "") &&
1441
+ isWhitespaceChar(graphemes[graphemes.length - 1]?.segment || "")) {
1323
1442
  newCol -= graphemes.pop()?.segment.length || 0;
1324
1443
  }
1325
1444
  if (graphemes.length > 0) {
1326
1445
  const lastGrapheme = graphemes[graphemes.length - 1]?.segment || "";
1327
- if (isPunctuationChar(lastGrapheme)) {
1446
+ if (isPasteMarker(lastGrapheme)) {
1447
+ // Paste marker is a single atomic word
1448
+ newCol -= graphemes.pop()?.segment.length || 0;
1449
+ }
1450
+ else if (isPunctuationChar(lastGrapheme)) {
1328
1451
  // Skip punctuation run
1329
- while (graphemes.length > 0 && isPunctuationChar(graphemes[graphemes.length - 1]?.segment || "")) {
1452
+ while (graphemes.length > 0 &&
1453
+ isPunctuationChar(graphemes[graphemes.length - 1]?.segment || "") &&
1454
+ !isPasteMarker(graphemes[graphemes.length - 1]?.segment || "")) {
1330
1455
  newCol -= graphemes.pop()?.segment.length || 0;
1331
1456
  }
1332
1457
  }
@@ -1334,7 +1459,8 @@ export class Editor {
1334
1459
  // Skip word run
1335
1460
  while (graphemes.length > 0 &&
1336
1461
  !isWhitespaceChar(graphemes[graphemes.length - 1]?.segment || "") &&
1337
- !isPunctuationChar(graphemes[graphemes.length - 1]?.segment || "")) {
1462
+ !isPunctuationChar(graphemes[graphemes.length - 1]?.segment || "") &&
1463
+ !isPasteMarker(graphemes[graphemes.length - 1]?.segment || "")) {
1338
1464
  newCol -= graphemes.pop()?.segment.length || 0;
1339
1465
  }
1340
1466
  }
@@ -1497,27 +1623,34 @@ export class Editor {
1497
1623
  return;
1498
1624
  }
1499
1625
  const textAfterCursor = currentLine.slice(this.state.cursorCol);
1500
- const segments = segmenter.segment(textAfterCursor);
1626
+ const segments = this.segment(textAfterCursor);
1501
1627
  const iterator = segments[Symbol.iterator]();
1502
1628
  let next = iterator.next();
1503
1629
  let newCol = this.state.cursorCol;
1504
1630
  // Skip leading whitespace
1505
- while (!next.done && isWhitespaceChar(next.value.segment)) {
1631
+ while (!next.done && !isPasteMarker(next.value.segment) && isWhitespaceChar(next.value.segment)) {
1506
1632
  newCol += next.value.segment.length;
1507
1633
  next = iterator.next();
1508
1634
  }
1509
1635
  if (!next.done) {
1510
1636
  const firstGrapheme = next.value.segment;
1511
- if (isPunctuationChar(firstGrapheme)) {
1637
+ if (isPasteMarker(firstGrapheme)) {
1638
+ // Paste marker is a single atomic word
1639
+ newCol += firstGrapheme.length;
1640
+ }
1641
+ else if (isPunctuationChar(firstGrapheme)) {
1512
1642
  // Skip punctuation run
1513
- while (!next.done && isPunctuationChar(next.value.segment)) {
1643
+ while (!next.done && isPunctuationChar(next.value.segment) && !isPasteMarker(next.value.segment)) {
1514
1644
  newCol += next.value.segment.length;
1515
1645
  next = iterator.next();
1516
1646
  }
1517
1647
  }
1518
1648
  else {
1519
1649
  // Skip word run
1520
- while (!next.done && !isWhitespaceChar(next.value.segment) && !isPunctuationChar(next.value.segment)) {
1650
+ while (!next.done &&
1651
+ !isWhitespaceChar(next.value.segment) &&
1652
+ !isPunctuationChar(next.value.segment) &&
1653
+ !isPasteMarker(next.value.segment)) {
1521
1654
  newCol += next.value.segment.length;
1522
1655
  next = iterator.next();
1523
1656
  }