mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-25 13:16:11 +03:00
Merge pull request #123 from mitchellh/selection
Scrolling screen with full scrollback should modify selection if set
This commit is contained in:
@ -822,8 +822,8 @@ pub fn charCallback(self: *Surface, codepoint: u21) !void {
|
|||||||
defer self.renderer_state.mutex.unlock();
|
defer self.renderer_state.mutex.unlock();
|
||||||
|
|
||||||
// Clear the selction if we have one.
|
// Clear the selction if we have one.
|
||||||
if (self.io.terminal.selection != null) {
|
if (self.io.terminal.screen.selection != null) {
|
||||||
self.io.terminal.selection = null;
|
self.io.terminal.screen.selection = null;
|
||||||
try self.queueRender();
|
try self.queueRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -941,7 +941,7 @@ pub fn keyCallback(
|
|||||||
.copy_to_clipboard => {
|
.copy_to_clipboard => {
|
||||||
// We can read from the renderer state without holding
|
// We can read from the renderer state without holding
|
||||||
// the lock because only we will write to this field.
|
// the lock because only we will write to this field.
|
||||||
if (self.io.terminal.selection) |sel| {
|
if (self.io.terminal.screen.selection) |sel| {
|
||||||
var buf = self.io.terminal.screen.selectionString(
|
var buf = self.io.terminal.screen.selectionString(
|
||||||
self.alloc,
|
self.alloc,
|
||||||
sel,
|
sel,
|
||||||
@ -1584,8 +1584,8 @@ pub fn mouseButtonCallback(
|
|||||||
|
|
||||||
switch (self.mouse.left_click_count) {
|
switch (self.mouse.left_click_count) {
|
||||||
// First mouse click, clear selection
|
// First mouse click, clear selection
|
||||||
1 => if (self.io.terminal.selection != null) {
|
1 => if (self.io.terminal.screen.selection != null) {
|
||||||
self.io.terminal.selection = null;
|
self.io.terminal.screen.selection = null;
|
||||||
try self.queueRender();
|
try self.queueRender();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1593,7 +1593,7 @@ pub fn mouseButtonCallback(
|
|||||||
2 => {
|
2 => {
|
||||||
const sel_ = self.io.terminal.screen.selectWord(self.mouse.left_click_point);
|
const sel_ = self.io.terminal.screen.selectWord(self.mouse.left_click_point);
|
||||||
if (sel_) |sel| {
|
if (sel_) |sel| {
|
||||||
self.io.terminal.selection = sel;
|
self.io.terminal.screen.selection = sel;
|
||||||
try self.queueRender();
|
try self.queueRender();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1602,7 +1602,7 @@ pub fn mouseButtonCallback(
|
|||||||
3 => {
|
3 => {
|
||||||
const sel_ = self.io.terminal.screen.selectLine(self.mouse.left_click_point);
|
const sel_ = self.io.terminal.screen.selectLine(self.mouse.left_click_point);
|
||||||
if (sel_) |sel| {
|
if (sel_) |sel| {
|
||||||
self.io.terminal.selection = sel;
|
self.io.terminal.screen.selection = sel;
|
||||||
try self.queueRender();
|
try self.queueRender();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1686,7 +1686,7 @@ fn dragLeftClickDouble(
|
|||||||
// We may not have a selection if we started our dbl-click in an area
|
// We may not have a selection if we started our dbl-click in an area
|
||||||
// that had no data, then we dragged our mouse into an area with data.
|
// that had no data, then we dragged our mouse into an area with data.
|
||||||
var sel = self.io.terminal.screen.selectWord(self.mouse.left_click_point) orelse {
|
var sel = self.io.terminal.screen.selectWord(self.mouse.left_click_point) orelse {
|
||||||
self.io.terminal.selection = word;
|
self.io.terminal.screen.selection = word;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1696,7 +1696,7 @@ fn dragLeftClickDouble(
|
|||||||
} else {
|
} else {
|
||||||
sel.end = word.end;
|
sel.end = word.end;
|
||||||
}
|
}
|
||||||
self.io.terminal.selection = sel;
|
self.io.terminal.screen.selection = sel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Triple-click dragging moves the selection one "line" at a time.
|
/// Triple-click dragging moves the selection one "line" at a time.
|
||||||
@ -1711,7 +1711,7 @@ fn dragLeftClickTriple(
|
|||||||
// We may not have a selection if we started our dbl-click in an area
|
// We may not have a selection if we started our dbl-click in an area
|
||||||
// that had no data, then we dragged our mouse into an area with data.
|
// that had no data, then we dragged our mouse into an area with data.
|
||||||
var sel = self.io.terminal.screen.selectLine(self.mouse.left_click_point) orelse {
|
var sel = self.io.terminal.screen.selectLine(self.mouse.left_click_point) orelse {
|
||||||
self.io.terminal.selection = word;
|
self.io.terminal.screen.selection = word;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1721,7 +1721,7 @@ fn dragLeftClickTriple(
|
|||||||
} else {
|
} else {
|
||||||
sel.end = word.end;
|
sel.end = word.end;
|
||||||
}
|
}
|
||||||
self.io.terminal.selection = sel;
|
self.io.terminal.screen.selection = sel;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dragLeftClickSingle(
|
fn dragLeftClickSingle(
|
||||||
@ -1738,13 +1738,13 @@ fn dragLeftClickSingle(
|
|||||||
// If we were selecting, and we switched directions, then we restart
|
// If we were selecting, and we switched directions, then we restart
|
||||||
// calculations because it forces us to reconsider if the first cell is
|
// calculations because it forces us to reconsider if the first cell is
|
||||||
// selected.
|
// selected.
|
||||||
if (self.io.terminal.selection) |sel| {
|
if (self.io.terminal.screen.selection) |sel| {
|
||||||
const reset: bool = if (sel.end.before(sel.start))
|
const reset: bool = if (sel.end.before(sel.start))
|
||||||
sel.start.before(screen_point)
|
sel.start.before(screen_point)
|
||||||
else
|
else
|
||||||
screen_point.before(sel.start);
|
screen_point.before(sel.start);
|
||||||
|
|
||||||
if (reset) self.io.terminal.selection = null;
|
if (reset) self.io.terminal.screen.selection = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Our logic for determing if the starting cell is selected:
|
// Our logic for determing if the starting cell is selected:
|
||||||
@ -1776,7 +1776,7 @@ fn dragLeftClickSingle(
|
|||||||
else
|
else
|
||||||
cell_xpos < cell_xboundary;
|
cell_xpos < cell_xboundary;
|
||||||
|
|
||||||
self.io.terminal.selection = if (selected) .{
|
self.io.terminal.screen.selection = if (selected) .{
|
||||||
.start = screen_point,
|
.start = screen_point,
|
||||||
.end = screen_point,
|
.end = screen_point,
|
||||||
} else null;
|
} else null;
|
||||||
@ -1786,7 +1786,7 @@ fn dragLeftClickSingle(
|
|||||||
|
|
||||||
// If this is a different cell and we haven't started selection,
|
// If this is a different cell and we haven't started selection,
|
||||||
// we determine the starting cell first.
|
// we determine the starting cell first.
|
||||||
if (self.io.terminal.selection == null) {
|
if (self.io.terminal.screen.selection == null) {
|
||||||
// - If we're moving to a point before the start, then we select
|
// - If we're moving to a point before the start, then we select
|
||||||
// the starting cell if we started after the boundary, else
|
// the starting cell if we started after the boundary, else
|
||||||
// we start selection of the prior cell.
|
// we start selection of the prior cell.
|
||||||
@ -1818,7 +1818,7 @@ fn dragLeftClickSingle(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.io.terminal.selection = .{ .start = start, .end = screen_point };
|
self.io.terminal.screen.selection = .{ .start = start, .end = screen_point };
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1827,8 +1827,8 @@ fn dragLeftClickSingle(
|
|||||||
|
|
||||||
// We moved! Set the selection end point. The start point should be
|
// We moved! Set the selection end point. The start point should be
|
||||||
// set earlier.
|
// set earlier.
|
||||||
assert(self.io.terminal.selection != null);
|
assert(self.io.terminal.screen.selection != null);
|
||||||
self.io.terminal.selection.?.end = screen_point;
|
self.io.terminal.screen.selection.?.end = screen_point;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn posToViewport(self: Surface, xpos: f64, ypos: f64) terminal.point.Viewport {
|
fn posToViewport(self: Surface, xpos: f64, ypos: f64) terminal.point.Viewport {
|
||||||
|
@ -537,7 +537,7 @@ pub fn render(
|
|||||||
|
|
||||||
// Convert our selection to viewport points because we copy only
|
// Convert our selection to viewport points because we copy only
|
||||||
// the viewport above.
|
// the viewport above.
|
||||||
const selection: ?terminal.Selection = if (state.terminal.selection) |sel|
|
const selection: ?terminal.Selection = if (state.terminal.screen.selection) |sel|
|
||||||
sel.toViewport(&state.terminal.screen)
|
sel.toViewport(&state.terminal.screen)
|
||||||
else
|
else
|
||||||
null;
|
null;
|
||||||
|
@ -745,7 +745,7 @@ pub fn render(
|
|||||||
|
|
||||||
// Convert our selection to viewport points because we copy only
|
// Convert our selection to viewport points because we copy only
|
||||||
// the viewport above.
|
// the viewport above.
|
||||||
const selection: ?terminal.Selection = if (state.terminal.selection) |sel|
|
const selection: ?terminal.Selection = if (state.terminal.screen.selection) |sel|
|
||||||
sel.toViewport(&state.terminal.screen)
|
sel.toViewport(&state.terminal.screen)
|
||||||
else
|
else
|
||||||
null;
|
null;
|
||||||
|
@ -774,6 +774,9 @@ cursor: Cursor = .{},
|
|||||||
/// Saved cursor saved with DECSC (ESC 7).
|
/// Saved cursor saved with DECSC (ESC 7).
|
||||||
saved_cursor: Cursor = .{},
|
saved_cursor: Cursor = .{},
|
||||||
|
|
||||||
|
/// The selection for this screen (if any).
|
||||||
|
selection: ?Selection = null,
|
||||||
|
|
||||||
/// Initialize a new screen.
|
/// Initialize a new screen.
|
||||||
pub fn init(
|
pub fn init(
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
@ -1437,68 +1440,85 @@ fn scrollDelta(self: *Screen, delta: isize, grow: bool) !void {
|
|||||||
// then we expand out to there.
|
// then we expand out to there.
|
||||||
const rows_written = self.rowsWritten();
|
const rows_written = self.rowsWritten();
|
||||||
const viewport_bottom = self.viewport + self.rows;
|
const viewport_bottom = self.viewport + self.rows;
|
||||||
if (viewport_bottom > rows_written) {
|
if (viewport_bottom <= rows_written) return;
|
||||||
// The number of new rows we need is the number of rows off our
|
|
||||||
// previous bottom we are growing.
|
|
||||||
const new_rows_needed = viewport_bottom - rows_written;
|
|
||||||
|
|
||||||
// If we can't fit into our capacity but we have space, resize the
|
// The number of new rows we need is the number of rows off our
|
||||||
// buffer to allocate more scrollback.
|
// previous bottom we are growing.
|
||||||
const rows_final = rows_written + new_rows_needed;
|
const new_rows_needed = viewport_bottom - rows_written;
|
||||||
if (rows_final > self.rowsCapacity()) {
|
|
||||||
const max_capacity = self.maxCapacity();
|
|
||||||
if (self.storage.capacity() < max_capacity) {
|
|
||||||
// The capacity we want to allocate. We take whatever is greater
|
|
||||||
// of what we actually need and two pages. We don't want to
|
|
||||||
// allocate one row at a time (common for scrolling) so we do this
|
|
||||||
// to chunk it.
|
|
||||||
const needed_capacity = @max(
|
|
||||||
rows_final * (self.cols + 1),
|
|
||||||
@min(self.storage.capacity() * 2, max_capacity),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Allocate what we can.
|
// If we can't fit into our capacity but we have space, resize the
|
||||||
try self.storage.resize(
|
// buffer to allocate more scrollback.
|
||||||
self.alloc,
|
const rows_final = rows_written + new_rows_needed;
|
||||||
@min(max_capacity, needed_capacity),
|
if (rows_final > self.rowsCapacity()) {
|
||||||
);
|
const max_capacity = self.maxCapacity();
|
||||||
}
|
if (self.storage.capacity() < max_capacity) {
|
||||||
|
// The capacity we want to allocate. We take whatever is greater
|
||||||
|
// of what we actually need and two pages. We don't want to
|
||||||
|
// allocate one row at a time (common for scrolling) so we do this
|
||||||
|
// to chunk it.
|
||||||
|
const needed_capacity = @max(
|
||||||
|
rows_final * (self.cols + 1),
|
||||||
|
@min(self.storage.capacity() * 2, max_capacity),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Allocate what we can.
|
||||||
|
try self.storage.resize(
|
||||||
|
self.alloc,
|
||||||
|
@min(max_capacity, needed_capacity),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we can't fit our rows into our capacity, we delete some scrollback.
|
|
||||||
const rows_deleted = if (rows_final > self.rowsCapacity()) deleted: {
|
|
||||||
const rows_to_delete = rows_final - self.rowsCapacity();
|
|
||||||
|
|
||||||
// Fast-path: we have no graphemes.
|
|
||||||
// Slow-path: we have graphemes, we have to check each row
|
|
||||||
// we're going to delete to see if they contain graphemes and
|
|
||||||
// clear the ones that do so we clear memory properly.
|
|
||||||
if (self.graphemes.count() > 0) {
|
|
||||||
var y: usize = 0;
|
|
||||||
while (y < rows_to_delete) : (y += 1) {
|
|
||||||
const row = self.getRow(.{ .active = y });
|
|
||||||
if (row.storage[0].header.flags.grapheme) row.clear(.{});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.viewport -= rows_to_delete;
|
|
||||||
self.storage.deleteOldest(rows_to_delete * (self.cols + 1));
|
|
||||||
break :deleted rows_to_delete;
|
|
||||||
} else 0;
|
|
||||||
|
|
||||||
// If we have more rows than what shows on our screen, we have a
|
|
||||||
// history boundary.
|
|
||||||
const rows_written_final = rows_final - rows_deleted;
|
|
||||||
if (rows_written_final > self.rows) {
|
|
||||||
self.history = rows_written_final - self.rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure we have "written" our last row so that it shows up
|
|
||||||
_ = self.storage.getPtrSlice(
|
|
||||||
(rows_written_final - 1) * (self.cols + 1),
|
|
||||||
self.cols + 1,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we can't fit our rows into our capacity, we delete some scrollback.
|
||||||
|
const rows_deleted = if (rows_final > self.rowsCapacity()) deleted: {
|
||||||
|
const rows_to_delete = rows_final - self.rowsCapacity();
|
||||||
|
|
||||||
|
// Fast-path: we have no graphemes.
|
||||||
|
// Slow-path: we have graphemes, we have to check each row
|
||||||
|
// we're going to delete to see if they contain graphemes and
|
||||||
|
// clear the ones that do so we clear memory properly.
|
||||||
|
if (self.graphemes.count() > 0) {
|
||||||
|
var y: usize = 0;
|
||||||
|
while (y < rows_to_delete) : (y += 1) {
|
||||||
|
const row = self.getRow(.{ .active = y });
|
||||||
|
if (row.storage[0].header.flags.grapheme) row.clear(.{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.viewport -= rows_to_delete;
|
||||||
|
self.storage.deleteOldest(rows_to_delete * (self.cols + 1));
|
||||||
|
break :deleted rows_to_delete;
|
||||||
|
} else 0;
|
||||||
|
|
||||||
|
// If we are deleting rows and have a selection, then we need to offset
|
||||||
|
// the selection by the rows we're deleting.
|
||||||
|
if (self.selection) |*sel| {
|
||||||
|
// If we're deleting more rows than our Y values, we also move
|
||||||
|
// the X over to 0 because we're in the middle of the selection now.
|
||||||
|
if (rows_deleted > sel.start.y) sel.start.x = 0;
|
||||||
|
if (rows_deleted > sel.end.y) sel.end.x = 0;
|
||||||
|
|
||||||
|
// Remove the deleted rows from both y values. We use saturating
|
||||||
|
// subtraction so that we can detect when we're at zero.
|
||||||
|
sel.start.y -|= rows_deleted;
|
||||||
|
sel.end.y -|= rows_deleted;
|
||||||
|
|
||||||
|
// If the selection is now empty, just clear it.
|
||||||
|
if (sel.empty()) self.selection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have more rows than what shows on our screen, we have a
|
||||||
|
// history boundary.
|
||||||
|
const rows_written_final = rows_final - rows_deleted;
|
||||||
|
if (rows_written_final > self.rows) {
|
||||||
|
self.history = rows_written_final - self.rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we have "written" our last row so that it shows up
|
||||||
|
_ = self.storage.getPtrSlice(
|
||||||
|
(rows_written_final - 1) * (self.cols + 1),
|
||||||
|
self.cols + 1,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the raw text associated with a selection. This will unwrap
|
/// Returns the raw text associated with a selection. This will unwrap
|
||||||
@ -2773,6 +2793,128 @@ test "Screen: scrollback empty" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "Screen: scrolling moves selection" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var s = try init(alloc, 3, 5, 0);
|
||||||
|
defer s.deinit();
|
||||||
|
try s.testWriteString("1ABCD\n2EFGH\n3IJKL");
|
||||||
|
try testing.expect(s.viewportIsBottom());
|
||||||
|
|
||||||
|
// Select a single line
|
||||||
|
s.selection = .{
|
||||||
|
.start = .{ .x = 0, .y = 1 },
|
||||||
|
.end = .{ .x = s.cols - 1, .y = 1 },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Scroll down, should still be bottom
|
||||||
|
try s.scroll(.{ .delta = 1 });
|
||||||
|
try testing.expect(s.viewportIsBottom());
|
||||||
|
|
||||||
|
// Our selection should've moved up
|
||||||
|
try testing.expectEqual(Selection{
|
||||||
|
.start = .{ .x = 0, .y = 0 },
|
||||||
|
.end = .{ .x = s.cols - 1, .y = 0 },
|
||||||
|
}, s.selection.?);
|
||||||
|
|
||||||
|
{
|
||||||
|
// Test our contents rotated
|
||||||
|
var contents = try s.testString(alloc, .viewport);
|
||||||
|
defer alloc.free(contents);
|
||||||
|
try testing.expectEqualStrings("2EFGH\n3IJKL", contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scrolling to the bottom does nothing
|
||||||
|
try s.scroll(.{ .bottom = {} });
|
||||||
|
|
||||||
|
// Our selection should've stayed the same
|
||||||
|
try testing.expectEqual(Selection{
|
||||||
|
.start = .{ .x = 0, .y = 0 },
|
||||||
|
.end = .{ .x = s.cols - 1, .y = 0 },
|
||||||
|
}, s.selection.?);
|
||||||
|
|
||||||
|
{
|
||||||
|
// Test our contents rotated
|
||||||
|
var contents = try s.testString(alloc, .viewport);
|
||||||
|
defer alloc.free(contents);
|
||||||
|
try testing.expectEqualStrings("2EFGH\n3IJKL", contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll up again
|
||||||
|
try s.scroll(.{ .delta = 1 });
|
||||||
|
|
||||||
|
// Our selection should be null because it left the screen.
|
||||||
|
try testing.expect(s.selection == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Screen: scrolling with scrollback available doesn't move selection" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var s = try init(alloc, 3, 5, 1);
|
||||||
|
defer s.deinit();
|
||||||
|
try s.testWriteString("1ABCD\n2EFGH\n3IJKL");
|
||||||
|
try testing.expect(s.viewportIsBottom());
|
||||||
|
|
||||||
|
// Select a single line
|
||||||
|
s.selection = .{
|
||||||
|
.start = .{ .x = 0, .y = 1 },
|
||||||
|
.end = .{ .x = s.cols - 1, .y = 1 },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Scroll down, should still be bottom
|
||||||
|
try s.scroll(.{ .delta = 1 });
|
||||||
|
try testing.expect(s.viewportIsBottom());
|
||||||
|
|
||||||
|
// Our selection should NOT move since we have scrollback
|
||||||
|
try testing.expectEqual(Selection{
|
||||||
|
.start = .{ .x = 0, .y = 1 },
|
||||||
|
.end = .{ .x = s.cols - 1, .y = 1 },
|
||||||
|
}, s.selection.?);
|
||||||
|
|
||||||
|
{
|
||||||
|
// Test our contents rotated
|
||||||
|
var contents = try s.testString(alloc, .viewport);
|
||||||
|
defer alloc.free(contents);
|
||||||
|
try testing.expectEqualStrings("2EFGH\n3IJKL", contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scrolling back should make it visible again
|
||||||
|
try s.scroll(.{ .delta = -1 });
|
||||||
|
try testing.expect(!s.viewportIsBottom());
|
||||||
|
|
||||||
|
// Our selection should NOT move since we have scrollback
|
||||||
|
try testing.expectEqual(Selection{
|
||||||
|
.start = .{ .x = 0, .y = 1 },
|
||||||
|
.end = .{ .x = s.cols - 1, .y = 1 },
|
||||||
|
}, s.selection.?);
|
||||||
|
|
||||||
|
{
|
||||||
|
// Test our contents rotated
|
||||||
|
var contents = try s.testString(alloc, .viewport);
|
||||||
|
defer alloc.free(contents);
|
||||||
|
try testing.expectEqualStrings("1ABCD\n2EFGH\n3IJKL", contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll down, this sends us off the scrollback
|
||||||
|
try s.scroll(.{ .delta = 2 });
|
||||||
|
try testing.expect(s.viewportIsBottom());
|
||||||
|
|
||||||
|
// Selection should move since we went down 2
|
||||||
|
try testing.expectEqual(Selection{
|
||||||
|
.start = .{ .x = 0, .y = 0 },
|
||||||
|
.end = .{ .x = s.cols - 1, .y = 0 },
|
||||||
|
}, s.selection.?);
|
||||||
|
|
||||||
|
{
|
||||||
|
// Test our contents rotated
|
||||||
|
var contents = try s.testString(alloc, .viewport);
|
||||||
|
defer alloc.free(contents);
|
||||||
|
try testing.expectEqualStrings("3IJKL", contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test "Screen: history region with no scrollback" {
|
test "Screen: history region with no scrollback" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
@ -33,6 +33,11 @@ pub fn toViewport(self: Selection, screen: *const Screen) ?Selection {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the selection is empty.
|
||||||
|
pub fn empty(self: Selection) bool {
|
||||||
|
return self.start.x == self.end.x and self.start.y == self.end.y;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if the selection contains the given point.
|
/// Returns true if the selection contains the given point.
|
||||||
///
|
///
|
||||||
/// This recalculates top left and bottom right each call. If you have
|
/// This recalculates top left and bottom right each call. If you have
|
||||||
|
@ -15,7 +15,6 @@ const ansi = @import("ansi.zig");
|
|||||||
const charsets = @import("charsets.zig");
|
const charsets = @import("charsets.zig");
|
||||||
const csi = @import("csi.zig");
|
const csi = @import("csi.zig");
|
||||||
const sgr = @import("sgr.zig");
|
const sgr = @import("sgr.zig");
|
||||||
const Selection = @import("Selection.zig");
|
|
||||||
const Tabstops = @import("Tabstops.zig");
|
const Tabstops = @import("Tabstops.zig");
|
||||||
const trace = @import("tracy").trace;
|
const trace = @import("tracy").trace;
|
||||||
const color = @import("color.zig");
|
const color = @import("color.zig");
|
||||||
@ -39,9 +38,6 @@ active_screen: ScreenType,
|
|||||||
screen: Screen,
|
screen: Screen,
|
||||||
secondary_screen: Screen,
|
secondary_screen: Screen,
|
||||||
|
|
||||||
/// The current selection (if any).
|
|
||||||
selection: ?Selection = null,
|
|
||||||
|
|
||||||
/// Whether we're currently writing to the status line (DECSASD and DECSSDT).
|
/// Whether we're currently writing to the status line (DECSASD and DECSSDT).
|
||||||
/// We don't support a status line currently so we just black hole this
|
/// We don't support a status line currently so we just black hole this
|
||||||
/// data so that it doesn't mess up our main display.
|
/// data so that it doesn't mess up our main display.
|
||||||
@ -204,7 +200,7 @@ pub fn alternateScreen(self: *Terminal, options: AlternateScreenOptions) void {
|
|||||||
self.screen.cursor = .{};
|
self.screen.cursor = .{};
|
||||||
|
|
||||||
// Clear our selection
|
// Clear our selection
|
||||||
self.selection = null;
|
self.screen.selection = null;
|
||||||
|
|
||||||
if (options.clear_on_enter) {
|
if (options.clear_on_enter) {
|
||||||
self.eraseDisplay(.complete);
|
self.eraseDisplay(.complete);
|
||||||
@ -231,7 +227,7 @@ pub fn primaryScreen(self: *Terminal, options: AlternateScreenOptions) void {
|
|||||||
self.active_screen = .primary;
|
self.active_screen = .primary;
|
||||||
|
|
||||||
// Clear our selection
|
// Clear our selection
|
||||||
self.selection = null;
|
self.screen.selection = null;
|
||||||
|
|
||||||
// Restore the cursor from the primary screen
|
// Restore the cursor from the primary screen
|
||||||
if (options.cursor_save) self.restoreCursor();
|
if (options.cursor_save) self.restoreCursor();
|
||||||
@ -1393,7 +1389,6 @@ pub fn setScrollingRegion(self: *Terminal, top: usize, bottom: usize) void {
|
|||||||
/// Full reset
|
/// Full reset
|
||||||
pub fn fullReset(self: *Terminal) void {
|
pub fn fullReset(self: *Terminal) void {
|
||||||
self.primaryScreen(.{ .clear_on_exit = true, .cursor_save = true });
|
self.primaryScreen(.{ .clear_on_exit = true, .cursor_save = true });
|
||||||
self.selection = null;
|
|
||||||
self.charset = .{};
|
self.charset = .{};
|
||||||
self.eraseDisplay(.scrollback);
|
self.eraseDisplay(.scrollback);
|
||||||
self.eraseDisplay(.complete);
|
self.eraseDisplay(.complete);
|
||||||
@ -1401,6 +1396,7 @@ pub fn fullReset(self: *Terminal) void {
|
|||||||
self.tabstops.reset(0);
|
self.tabstops.reset(0);
|
||||||
self.screen.cursor = .{};
|
self.screen.cursor = .{};
|
||||||
self.screen.saved_cursor = .{};
|
self.screen.saved_cursor = .{};
|
||||||
|
self.screen.selection = null;
|
||||||
self.scrolling_region = .{ .top = 0, .bottom = self.rows - 1 };
|
self.scrolling_region = .{ .top = 0, .bottom = self.rows - 1 };
|
||||||
self.previous_char = null;
|
self.previous_char = null;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user