mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
Merge pull request #628 from mitchellh/xt-ich
xterm audit: insert characters (ICH)
This commit is contained in:
@ -1581,8 +1581,20 @@ pub fn insertBlanks(self: *Terminal, count: usize) void {
|
||||
// Unset pending wrap state without wrapping
|
||||
self.screen.cursor.pending_wrap = false;
|
||||
|
||||
// If our cursor is outside the margins then do nothing. We DO reset
|
||||
// wrap state still so this must remain below the above logic.
|
||||
if (self.screen.cursor.x < self.scrolling_region.left or
|
||||
self.screen.cursor.x > self.scrolling_region.right) return;
|
||||
|
||||
// The limit we can shift to is our right margin. We add 1 since the
|
||||
// math around this is 1-indexed.
|
||||
const right_limit = self.scrolling_region.right + 1;
|
||||
|
||||
// If our count is larger than the remaining amount, we just erase right.
|
||||
if (count > self.cols - self.screen.cursor.x) {
|
||||
// We only do this if we can erase the entire line (no right margin).
|
||||
if (right_limit == self.cols and
|
||||
count > right_limit - self.screen.cursor.x)
|
||||
{
|
||||
self.eraseLine(.right, false);
|
||||
return;
|
||||
}
|
||||
@ -1597,7 +1609,7 @@ pub fn insertBlanks(self: *Terminal, count: usize) void {
|
||||
// This is the number of spaces we have left to shift existing data.
|
||||
// If count is bigger than the available space left after the cursor,
|
||||
// we may have no space at all for copying.
|
||||
const copyable = self.screen.cols - pivot;
|
||||
const copyable = right_limit - pivot;
|
||||
if (copyable > 0) {
|
||||
// This is the index of the final copyable value that we need to copy.
|
||||
const copyable_end = start + copyable - 1;
|
||||
@ -1606,14 +1618,19 @@ pub fn insertBlanks(self: *Terminal, count: usize) void {
|
||||
// allocated new space, otherwise we'll copy duplicates.
|
||||
var i: usize = 0;
|
||||
while (i < copyable) : (i += 1) {
|
||||
const to = self.screen.cols - 1 - i;
|
||||
const to = right_limit - 1 - i;
|
||||
const from = copyable_end - i;
|
||||
row.getCellPtr(to).* = row.getCell(from);
|
||||
const src = row.getCell(from);
|
||||
const dst = row.getCellPtr(to);
|
||||
dst.* = src;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert zero
|
||||
row.fillSlice(.{}, start, pivot);
|
||||
// Insert blanks. The blanks preserve the background color.
|
||||
row.fillSlice(.{
|
||||
.bg = self.screen.cursor.pen.bg,
|
||||
.attrs = .{ .has_bg = self.screen.cursor.pen.attrs.has_bg },
|
||||
}, start, pivot);
|
||||
}
|
||||
|
||||
/// Insert amount lines at the current cursor row. The contents of the line
|
||||
@ -3128,6 +3145,103 @@ test "Terminal: insertBlanks more than size" {
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: insertBlanks no scroll region, fits" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 10, 10);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
for ("ABC") |c| try t.print(c);
|
||||
t.setCursorPos(1, 1);
|
||||
t.insertBlanks(2);
|
||||
|
||||
{
|
||||
var str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings(" ABC", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: insertBlanks preserves background sgr" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 10, 10);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
const pen: Screen.Cell = .{
|
||||
.bg = .{ .r = 0xFF, .g = 0x00, .b = 0x00 },
|
||||
.attrs = .{ .has_bg = true },
|
||||
};
|
||||
|
||||
for ("ABC") |c| try t.print(c);
|
||||
t.setCursorPos(1, 1);
|
||||
t.screen.cursor.pen = pen;
|
||||
t.insertBlanks(2);
|
||||
|
||||
{
|
||||
var str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings(" ABC", str);
|
||||
const cell = t.screen.getCell(.active, 0, 0);
|
||||
try testing.expectEqual(pen, cell);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: insertBlanks shift off screen" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 5, 10);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
for (" ABC") |c| try t.print(c);
|
||||
t.setCursorPos(1, 3);
|
||||
t.insertBlanks(2);
|
||||
try t.print('X');
|
||||
|
||||
{
|
||||
var str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings(" X A", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: insertBlanks inside left/right scroll region" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 10, 10);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
t.scrolling_region.left = 2;
|
||||
t.scrolling_region.right = 4;
|
||||
t.setCursorPos(1, 3);
|
||||
for ("ABC") |c| try t.print(c);
|
||||
t.setCursorPos(1, 3);
|
||||
t.insertBlanks(2);
|
||||
try t.print('X');
|
||||
|
||||
{
|
||||
var str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings(" X A", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: insertBlanks outside left/right scroll region" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 10, 10);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
t.scrolling_region.left = 2;
|
||||
t.scrolling_region.right = 4;
|
||||
t.setCursorPos(1, 3);
|
||||
for ("ABC") |c| try t.print(c);
|
||||
t.setCursorPos(1, 1);
|
||||
t.insertBlanks(2);
|
||||
try t.print('X');
|
||||
|
||||
{
|
||||
var str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("X ABC", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: insert mode with space" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 10, 2);
|
||||
|
128
website/app/vt/ich/page.mdx
Normal file
128
website/app/vt/ich/page.mdx
Normal file
@ -0,0 +1,128 @@
|
||||
import VTSequence from "@/components/VTSequence";
|
||||
|
||||
# Insert Character (ICH)
|
||||
|
||||
<VTSequence sequence={["CSI", "Pn", "@"]} />
|
||||
|
||||
Insert `n` blank characters at the current cursor position and shift
|
||||
existing cell contents right.
|
||||
|
||||
The parameter `n` must be an integer greater than or equal to 1. If `n` is less than
|
||||
or equal to 0, adjust `n` to be 1. If `n` is omitted, `n` defaults to 1.
|
||||
|
||||
This sequence always unsets the pending wrap state.
|
||||
|
||||
If the cursor position is outside of the [left and right margins](#TODO),
|
||||
this sequence does not change the screen, but the pending wrap state is
|
||||
still reset.
|
||||
|
||||
Existing cells shifted beyond the right margin are deleted. Inserted cells
|
||||
are blank with the background color colored according to the current SGR state.
|
||||
|
||||
If a multi-cell character (such as "橋") is shifted so that the cell is split
|
||||
in half, the multi-cell character can either be clipped or erased. Typical
|
||||
behavior is to clip at the right edge of the screen and erase at a right
|
||||
margin, but either behavior is acceptable.
|
||||
|
||||
## Validation
|
||||
|
||||
### ICH V-1: No Scroll Region, Fits on Screen
|
||||
|
||||
```bash
|
||||
printf "ABC"
|
||||
printf "\033[1G"
|
||||
printf "\033[2@"
|
||||
```
|
||||
|
||||
```
|
||||
|XcABC_____|
|
||||
```
|
||||
|
||||
### ICH V-2: SGR State
|
||||
|
||||
```bash
|
||||
printf "ABC"
|
||||
printf "\033[1G"
|
||||
printf "\033[41m"
|
||||
printf "\033[2@"
|
||||
printf "X"
|
||||
```
|
||||
|
||||
```
|
||||
|c_ABC_____|
|
||||
```
|
||||
|
||||
The `c_` cells should both have a red background. The `ABC` cells should
|
||||
remain unchanged in style.
|
||||
|
||||
### ICH V-3: Shifting Content Off the Screen
|
||||
|
||||
```bash
|
||||
cols=$(tput cols)
|
||||
printf "\033[${cols}G"
|
||||
printf "\033[2D"
|
||||
printf "ABC"
|
||||
printf "\033[2D"
|
||||
printf "\033[2@"
|
||||
printf "X"
|
||||
```
|
||||
|
||||
```
|
||||
|_______XcA|
|
||||
```
|
||||
|
||||
### ICH V-4: Inside Left/Right Scroll Region
|
||||
|
||||
```bash
|
||||
printf "\033[1;1H" # move to top-left
|
||||
printf "\033[0J" # clear screen
|
||||
printf "\033[?69h" # enable left/right margins
|
||||
printf "\033[3;5s" # scroll region left/right
|
||||
printf "\033[3G"
|
||||
printf "ABC"
|
||||
printf "\033[3G"
|
||||
printf "\033[2@"
|
||||
printf "X"
|
||||
```
|
||||
|
||||
```
|
||||
|__XcA_____|
|
||||
```
|
||||
|
||||
### ICH V-5: Outside Left/Right Scroll Region
|
||||
|
||||
```bash
|
||||
printf "\033[1;1H" # move to top-left
|
||||
printf "\033[0J" # clear screen
|
||||
printf "\033[?69h" # enable left/right margins
|
||||
printf "\033[3;5s" # scroll region left/right
|
||||
printf "\033[3G"
|
||||
printf "ABC"
|
||||
printf "\033[1G"
|
||||
printf "\033[2@"
|
||||
printf "X"
|
||||
```
|
||||
|
||||
```
|
||||
|XcABC_____|
|
||||
```
|
||||
|
||||
### ICH V-6: Split Wide Character
|
||||
|
||||
```bash
|
||||
cols=$(tput cols)
|
||||
printf "\033[${cols}G"
|
||||
printf "\033[1D"
|
||||
printf "橋"
|
||||
printf "\033[2D"
|
||||
printf "\033[@"
|
||||
printf "X"
|
||||
```
|
||||
|
||||
```
|
||||
|_______Xc_|
|
||||
```
|
||||
|
||||
In this case, it is valid for the last cell to be blank or to clip the
|
||||
multi-cell character. xterm clips the character but many other terminals
|
||||
erase the cell.
|
Reference in New Issue
Block a user