Skip to content

Commit f07ae2a

Browse files
committed
Allow CSI_CUB (CSI n D) [move cursor left] to wrap around the left margin. Fixes bug 2969.
1 parent 6f43144 commit f07ae2a

File tree

3 files changed

+201
-4
lines changed

3 files changed

+201
-4
lines changed

VT100Grid.m

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -412,11 +412,45 @@ - (int)moveCursorDownOneLineScrollingIntoLineBuffer:(LineBuffer *)lineBuffer
412412
}
413413

414414
- (void)moveCursorLeft:(int)n {
415-
int x = cursor_.x - n;
416-
const int leftMargin = [self leftMargin];
415+
if ([self haveColumnScrollRegion]) {
416+
// Don't allow cursor to wrap around the left margin when there is a
417+
// column scroll region.
418+
int x = cursor_.x - n;
419+
const int leftMargin = [self leftMargin];
420+
421+
x = MAX(leftMargin, x);
422+
self.cursorX = x;
423+
return;
424+
}
417425

418-
x = MAX(leftMargin, x);
419-
self.cursorX = x;
426+
while (n > 0) {
427+
if (self.cursorX == 0 && self.cursorY == 0) {
428+
// Can't move any farther left.
429+
return;
430+
} else if (self.cursorX == 0) {
431+
// Wrap around?
432+
switch ([self continuationMarkForLineNumber:self.cursorY - 1]) {
433+
case EOL_SOFT:
434+
case EOL_DWC:
435+
// Wrap around to the end of the previous line, even if this leaves the cursor
436+
// on a DWC_RIGHT.
437+
n--;
438+
self.cursorX = [self rightMargin];
439+
self.cursorY = self.cursorY - 1;
440+
break;
441+
442+
case EOL_HARD:
443+
// Can't wrap across EOL_HARD.
444+
return;
445+
}
446+
} else {
447+
// Move as far as the left margin
448+
int x = MAX(0, cursor_.x - n);
449+
int moved = self.cursorX - x;
450+
n -= moved;
451+
self.cursorX = x;
452+
}
453+
}
420454
}
421455

422456
- (void)moveCursorRight:(int)n {
@@ -733,6 +767,15 @@ - (int)appendCharsAtCursor:(screen_char_t *)buffer
733767
int lineNumber = cursor_.y;
734768
aLine = [self screenCharsAtLineNumber:cursor_.y];
735769

770+
BOOL mayStompSplitDwc = NO;
771+
if (newx == size_.width) {
772+
// The cursor is ending at the right margin. See if there's a DWC_SKIP/EOL_DWC pair
773+
// there which may be affected.
774+
mayStompSplitDwc = (!useScrollRegionCols_ &&
775+
aLine[size_.width].code == EOL_DWC &&
776+
aLine[size_.width - 1].code == DWC_SKIP);
777+
}
778+
736779
if (insert) {
737780
if (cursor_.x + charsToInsert < rightMargin) {
738781
#ifdef VERBOSE_STRING
@@ -816,6 +859,15 @@ - (int)appendCharsAtCursor:(screen_char_t *)buffer
816859
aLine[cursor_.x].complexChar = NO;
817860
}
818861

862+
if (mayStompSplitDwc &&
863+
aLine[size_.width - 1].code != DWC_SKIP &&
864+
aLine[size_.width].code == EOL_DWC) {
865+
// The line no longer ends in a DWC_SKIP, but the continuation mark is still EOL_DWC.
866+
// Change the continuation mark to EOL_SOFT since there's presumably still a DWC at the
867+
// start of the next line.
868+
aLine[size_.width].code = EOL_SOFT;
869+
}
870+
819871
// The next char in the buffer shouldn't be DWC_RIGHT because we
820872
// wouldn't have inserted its first half due to a check at the top.
821873
assert(!(idx < len && buffer[idx].code == DWC_RIGHT));

iTermTests/VT100GridTest.m

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,106 @@ - (void)testMoveCursorLeft {
600600
assert(grid.cursorX == 2);
601601
}
602602

603+
// Make sure that moveCursorLeft wraps around soft EOLs
604+
- (void)testMoveCursorLeftWrappingAroundSoftEOL {
605+
VT100Grid *grid = [self mediumGrid]; // 4x4
606+
607+
NSString *string = @"abcdef";
608+
screen_char_t *line = [self screenCharLineForString:string];
609+
LineBuffer *lineBuffer = [[[LineBuffer alloc] initWithBlockSize:1000] autorelease];
610+
[grid appendCharsAtCursor:line
611+
length:[string length]
612+
scrollingIntoLineBuffer:lineBuffer
613+
unlimitedScrollback:YES
614+
useScrollbackWithRegion:NO
615+
wraparound:YES
616+
ansi:NO
617+
insert:NO];
618+
assert([[grid compactLineDump] isEqualToString:
619+
@"abcd\n"
620+
@"ef..\n"
621+
@"....\n"
622+
@"...."]);
623+
624+
assert(grid.cursorX == 2);
625+
assert(grid.cursorY == 1);
626+
[grid moveCursorLeft:4];
627+
assert(grid.cursorX == 2);
628+
assert(grid.cursorY == 0);
629+
}
630+
631+
// Make sure that moveCursorLeft wraps around soft EOLs
632+
- (void)testMoveCursorLeftWrappingAroundDoubleWideCharEOL {
633+
VT100Grid *grid = [[[VT100Grid alloc] initWithSize:VT100GridSizeMake(3, 3)
634+
delegate:self] autorelease];
635+
636+
NSString *string = @"abc-"; // c is double-width
637+
screen_char_t *line = [self screenCharLineForString:string];
638+
LineBuffer *lineBuffer = [[[LineBuffer alloc] initWithBlockSize:1000] autorelease];
639+
[grid appendCharsAtCursor:line
640+
length:[string length]
641+
scrollingIntoLineBuffer:lineBuffer
642+
unlimitedScrollback:YES
643+
useScrollbackWithRegion:NO
644+
wraparound:YES
645+
ansi:NO
646+
insert:NO];
647+
assert([[grid compactLineDump] isEqualToString:
648+
@"ab>\n"
649+
@"c-.\n"
650+
@"..."]);
651+
652+
assert(grid.cursorX == 2);
653+
assert(grid.cursorY == 1);
654+
[grid moveCursorLeft:4];
655+
assert(grid.cursorX == 1);
656+
assert(grid.cursorY == 0);
657+
}
658+
659+
// Make sure that moveCursorLeft wraps around soft EOLs
660+
- (void)testMoveCursorLeftNotWrappingAroundHardEOL {
661+
VT100Grid *grid = [self mediumGrid]; // 4x4
662+
663+
NSString *string = @"abc";
664+
screen_char_t *line = [self screenCharLineForString:string];
665+
LineBuffer *lineBuffer = [[[LineBuffer alloc] initWithBlockSize:1000] autorelease];
666+
[grid appendCharsAtCursor:line
667+
length:[string length]
668+
scrollingIntoLineBuffer:lineBuffer
669+
unlimitedScrollback:YES
670+
useScrollbackWithRegion:NO
671+
wraparound:YES
672+
ansi:NO
673+
insert:NO];
674+
[grid moveCursorDownOneLineScrollingIntoLineBuffer:lineBuffer
675+
unlimitedScrollback:YES
676+
useScrollbackWithRegion:NO];
677+
grid.cursorX = 0;
678+
679+
string = @"d";
680+
line = [self screenCharLineForString:string];
681+
[grid appendCharsAtCursor:line
682+
length:[string length]
683+
scrollingIntoLineBuffer:lineBuffer
684+
unlimitedScrollback:YES
685+
useScrollbackWithRegion:NO
686+
wraparound:YES
687+
ansi:NO
688+
insert:NO];
689+
690+
assert([[grid compactLineDump] isEqualToString:
691+
@"abc.\n"
692+
@"d...\n"
693+
@"....\n"
694+
@"...."]);
695+
696+
assert(grid.cursorX == 1);
697+
assert(grid.cursorY == 1);
698+
[grid moveCursorLeft:4];
699+
assert(grid.cursorX == 0);
700+
assert(grid.cursorY == 1);
701+
}
702+
603703
- (void)testMoveCursorRight {
604704
VT100Grid *grid = [self mediumGrid];
605705
grid.cursorX = 2;
@@ -2377,6 +2477,37 @@ - (void)testAppendCharsAtCursor {
23772477
expectCursor:VT100GridCoordMake(3, 0)
23782478
expectLineBuffer:@""
23792479
expectDropped:0];
2480+
2481+
// Overwriting a DWC_SKIP converts EOL_DWC to EOL_SOFT
2482+
isAnsi_ = NO;
2483+
wraparoundMode_ = YES;
2484+
[self doAppendCharsAtCursorTestWithInitialBuffer:@"abc>>\n"
2485+
@"D-..!"
2486+
scrollRegion:VT100GridRectMake(0, 0, -1, -1)
2487+
useScrollbackWithRegion:YES
2488+
unlimitedScrollback:NO
2489+
appending:@"d"
2490+
at:VT100GridCoordMake(3, 0)
2491+
expect:@"abcd+\n"
2492+
@"D-..!"
2493+
expectCursor:VT100GridCoordMake(4, 0)
2494+
expectLineBuffer:@""
2495+
expectDropped:0];
2496+
2497+
isAnsi_ = NO;
2498+
wraparoundMode_ = YES;
2499+
[self doAppendCharsAtCursorTestWithInitialBuffer:@"abc>>\n"
2500+
@"D-..!"
2501+
scrollRegion:VT100GridRectMake(0, 0, -1, -1)
2502+
useScrollbackWithRegion:YES
2503+
unlimitedScrollback:NO
2504+
appending:@"def"
2505+
at:VT100GridCoordMake(3, 0)
2506+
expect:@"abcd+\n"
2507+
@"ef..!"
2508+
expectCursor:VT100GridCoordMake(2, 1)
2509+
expectLineBuffer:@""
2510+
expectDropped:0];
23802511
}
23812512

23822513
- (void)testCoordinateBefore {

tests/move_cursor_left_wraps

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Make sure your terminal is 80 cols wide.
2+
3+
Puts a ! in the empty spot at the end of the first line v
4+
yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxea!
5+
6+
7+
Puts a ! over the x v
8+
yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxea!
9+
10+
11+
Puts a ! between the >marks<
12+
v
13+
x>O<xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx!
14+

0 commit comments

Comments
 (0)