mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
Merge branch 'ghostty-org:main' into ms_MY
This commit is contained in:
@ -186,6 +186,12 @@ class AppDelegate: NSObject,
|
|||||||
name: .ghosttyConfigDidChange,
|
name: .ghosttyConfigDidChange,
|
||||||
object: nil
|
object: nil
|
||||||
)
|
)
|
||||||
|
NotificationCenter.default.addObserver(
|
||||||
|
self,
|
||||||
|
selector: #selector(ghosttyBellDidRing(_:)),
|
||||||
|
name: .ghosttyBellDidRing,
|
||||||
|
object: nil
|
||||||
|
)
|
||||||
|
|
||||||
// Configure user notifications
|
// Configure user notifications
|
||||||
let actions = [
|
let actions = [
|
||||||
@ -502,6 +508,11 @@ class AppDelegate: NSObject,
|
|||||||
ghosttyConfigDidChange(config: config)
|
ghosttyConfigDidChange(config: config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func ghosttyBellDidRing(_ notification: Notification) {
|
||||||
|
// Bounce the dock icon if we're not focused.
|
||||||
|
NSApp.requestUserAttention(.informationalRequest)
|
||||||
|
}
|
||||||
|
|
||||||
private func ghosttyConfigDidChange(config: Ghostty.Config) {
|
private func ghosttyConfigDidChange(config: Ghostty.Config) {
|
||||||
// Update the config we need to store
|
// Update the config we need to store
|
||||||
self.derivedConfig = DerivedConfig(config)
|
self.derivedConfig = DerivedConfig(config)
|
||||||
|
@ -495,14 +495,20 @@ class QuickTerminalController: BaseTerminalController {
|
|||||||
private func onToggleFullscreen() {
|
private func onToggleFullscreen() {
|
||||||
// We ignore the configured fullscreen style and always use non-native
|
// We ignore the configured fullscreen style and always use non-native
|
||||||
// because the way the quick terminal works doesn't support native.
|
// because the way the quick terminal works doesn't support native.
|
||||||
//
|
let mode: FullscreenMode
|
||||||
// An additional detail is that if the is NOT frontmost, then our
|
if (NSApp.isFrontmost) {
|
||||||
// NSApp.presentationOptions will not take effect so we must always
|
// If we're frontmost and we have a notch then we keep padding
|
||||||
// do the visible menu mode since we can't get rid of the menu.
|
// so all lines of the terminal are visible.
|
||||||
let mode: FullscreenMode = if (NSApp.isFrontmost) {
|
if (window?.screen?.hasNotch ?? false) {
|
||||||
.nonNative
|
mode = .nonNativePaddedNotch
|
||||||
|
} else {
|
||||||
|
mode = .nonNative
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
.nonNativeVisibleMenu
|
// An additional detail is that if the is NOT frontmost, then our
|
||||||
|
// NSApp.presentationOptions will not take effect so we must always
|
||||||
|
// do the visible menu mode since we can't get rid of the menu.
|
||||||
|
mode = .nonNativeVisibleMenu
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleFullscreen(mode: mode)
|
toggleFullscreen(mode: mode)
|
||||||
|
@ -538,6 +538,9 @@ extension Ghostty {
|
|||||||
case GHOSTTY_ACTION_COLOR_CHANGE:
|
case GHOSTTY_ACTION_COLOR_CHANGE:
|
||||||
colorChange(app, target: target, change: action.action.color_change)
|
colorChange(app, target: target, change: action.action.color_change)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_RING_BELL:
|
||||||
|
ringBell(app, target: target)
|
||||||
|
|
||||||
case GHOSTTY_ACTION_CLOSE_ALL_WINDOWS:
|
case GHOSTTY_ACTION_CLOSE_ALL_WINDOWS:
|
||||||
fallthrough
|
fallthrough
|
||||||
case GHOSTTY_ACTION_TOGGLE_TAB_OVERVIEW:
|
case GHOSTTY_ACTION_TOGGLE_TAB_OVERVIEW:
|
||||||
@ -747,6 +750,30 @@ extension Ghostty {
|
|||||||
appDelegate.toggleVisibility(self)
|
appDelegate.toggleVisibility(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func ringBell(
|
||||||
|
_ app: ghostty_app_t,
|
||||||
|
target: ghostty_target_s) {
|
||||||
|
switch (target.tag) {
|
||||||
|
case GHOSTTY_TARGET_APP:
|
||||||
|
// Technically we could still request app attention here but there
|
||||||
|
// are no known cases where the bell is rang with an app target so
|
||||||
|
// I think its better to warn.
|
||||||
|
Ghostty.logger.warning("ring bell does nothing with an app target")
|
||||||
|
return
|
||||||
|
|
||||||
|
case GHOSTTY_TARGET_SURFACE:
|
||||||
|
guard let surface = target.target.surface else { return }
|
||||||
|
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: .ghosttyBellDidRing,
|
||||||
|
object: surfaceView
|
||||||
|
)
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static func moveTab(
|
private static func moveTab(
|
||||||
_ app: ghostty_app_t,
|
_ app: ghostty_app_t,
|
||||||
target: ghostty_target_s,
|
target: ghostty_target_s,
|
||||||
|
@ -116,6 +116,14 @@ extension Ghostty {
|
|||||||
/// details on what each means. We only add documentation if there is a strange conversion
|
/// details on what each means. We only add documentation if there is a strange conversion
|
||||||
/// due to the embedded library and Swift.
|
/// due to the embedded library and Swift.
|
||||||
|
|
||||||
|
var bellFeatures: BellFeatures {
|
||||||
|
guard let config = self.config else { return .init() }
|
||||||
|
var v: CUnsignedInt = 0
|
||||||
|
let key = "bell-features"
|
||||||
|
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return .init() }
|
||||||
|
return .init(rawValue: v)
|
||||||
|
}
|
||||||
|
|
||||||
var initialWindow: Bool {
|
var initialWindow: Bool {
|
||||||
guard let config = self.config else { return true }
|
guard let config = self.config else { return true }
|
||||||
var v = true;
|
var v = true;
|
||||||
@ -543,6 +551,12 @@ extension Ghostty.Config {
|
|||||||
case download
|
case download
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct BellFeatures: OptionSet {
|
||||||
|
let rawValue: CUnsignedInt
|
||||||
|
|
||||||
|
static let system = BellFeatures(rawValue: 1 << 0)
|
||||||
|
}
|
||||||
|
|
||||||
enum MacHidden : String {
|
enum MacHidden : String {
|
||||||
case never
|
case never
|
||||||
case always
|
case always
|
||||||
|
@ -253,6 +253,9 @@ extension Notification.Name {
|
|||||||
|
|
||||||
/// Resize the window to a default size.
|
/// Resize the window to a default size.
|
||||||
static let ghosttyResetWindowSize = Notification.Name("com.mitchellh.ghostty.resetWindowSize")
|
static let ghosttyResetWindowSize = Notification.Name("com.mitchellh.ghostty.resetWindowSize")
|
||||||
|
|
||||||
|
/// Ring the bell
|
||||||
|
static let ghosttyBellDidRing = Notification.Name("com.mitchellh.ghostty.ghosttyBellDidRing")
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: I am moving all of these to Notification.Name extensions over time. This
|
// NOTE: I am moving all of these to Notification.Name extensions over time. This
|
||||||
|
@ -59,6 +59,15 @@ extension Ghostty {
|
|||||||
|
|
||||||
@EnvironmentObject private var ghostty: Ghostty.App
|
@EnvironmentObject private var ghostty: Ghostty.App
|
||||||
|
|
||||||
|
var title: String {
|
||||||
|
var result = surfaceView.title
|
||||||
|
if (surfaceView.bell) {
|
||||||
|
result = "🔔 \(result)"
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
let center = NotificationCenter.default
|
let center = NotificationCenter.default
|
||||||
|
|
||||||
@ -74,7 +83,7 @@ extension Ghostty {
|
|||||||
|
|
||||||
Surface(view: surfaceView, size: geo.size)
|
Surface(view: surfaceView, size: geo.size)
|
||||||
.focused($surfaceFocus)
|
.focused($surfaceFocus)
|
||||||
.focusedValue(\.ghosttySurfaceTitle, surfaceView.title)
|
.focusedValue(\.ghosttySurfaceTitle, title)
|
||||||
.focusedValue(\.ghosttySurfacePwd, surfaceView.pwd)
|
.focusedValue(\.ghosttySurfacePwd, surfaceView.pwd)
|
||||||
.focusedValue(\.ghosttySurfaceView, surfaceView)
|
.focusedValue(\.ghosttySurfaceView, surfaceView)
|
||||||
.focusedValue(\.ghosttySurfaceCellSize, surfaceView.cellSize)
|
.focusedValue(\.ghosttySurfaceCellSize, surfaceView.cellSize)
|
||||||
|
@ -63,6 +63,9 @@ extension Ghostty {
|
|||||||
/// dynamically updated. Otherwise, the background color is the default background color.
|
/// dynamically updated. Otherwise, the background color is the default background color.
|
||||||
@Published private(set) var backgroundColor: Color? = nil
|
@Published private(set) var backgroundColor: Color? = nil
|
||||||
|
|
||||||
|
/// True when the bell is active. This is set inactive on focus or event.
|
||||||
|
@Published private(set) var bell: Bool = false
|
||||||
|
|
||||||
// An initial size to request for a window. This will only affect
|
// An initial size to request for a window. This will only affect
|
||||||
// then the view is moved to a new window.
|
// then the view is moved to a new window.
|
||||||
var initialSize: NSSize? = nil
|
var initialSize: NSSize? = nil
|
||||||
@ -190,6 +193,11 @@ extension Ghostty {
|
|||||||
selector: #selector(ghosttyColorDidChange(_:)),
|
selector: #selector(ghosttyColorDidChange(_:)),
|
||||||
name: .ghosttyColorDidChange,
|
name: .ghosttyColorDidChange,
|
||||||
object: self)
|
object: self)
|
||||||
|
center.addObserver(
|
||||||
|
self,
|
||||||
|
selector: #selector(ghosttyBellDidRing(_:)),
|
||||||
|
name: .ghosttyBellDidRing,
|
||||||
|
object: self)
|
||||||
center.addObserver(
|
center.addObserver(
|
||||||
self,
|
self,
|
||||||
selector: #selector(windowDidChangeScreen),
|
selector: #selector(windowDidChangeScreen),
|
||||||
@ -300,9 +308,12 @@ extension Ghostty {
|
|||||||
SecureInput.shared.setScoped(ObjectIdentifier(self), focused: focused)
|
SecureInput.shared.setScoped(ObjectIdentifier(self), focused: focused)
|
||||||
}
|
}
|
||||||
|
|
||||||
// On macOS 13+ we can store our continuous clock...
|
|
||||||
if (focused) {
|
if (focused) {
|
||||||
|
// On macOS 13+ we can store our continuous clock...
|
||||||
focusInstant = ContinuousClock.now
|
focusInstant = ContinuousClock.now
|
||||||
|
|
||||||
|
// We unset our bell state if we gained focus
|
||||||
|
bell = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -556,6 +567,11 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func ghosttyBellDidRing(_ notification: SwiftUI.Notification) {
|
||||||
|
// Bell state goes to true
|
||||||
|
bell = true
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func windowDidChangeScreen(notification: SwiftUI.Notification) {
|
@objc private func windowDidChangeScreen(notification: SwiftUI.Notification) {
|
||||||
guard let window = self.window else { return }
|
guard let window = self.window else { return }
|
||||||
guard let object = notification.object as? NSWindow, window == object else { return }
|
guard let object = notification.object as? NSWindow, window == object else { return }
|
||||||
@ -855,6 +871,9 @@ extension Ghostty {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// On any keyDown event we unset our bell state
|
||||||
|
bell = false
|
||||||
|
|
||||||
// We need to translate the mods (maybe) to handle configs such as option-as-alt
|
// We need to translate the mods (maybe) to handle configs such as option-as-alt
|
||||||
let translationModsGhostty = Ghostty.eventModifierFlags(
|
let translationModsGhostty = Ghostty.eventModifierFlags(
|
||||||
mods: ghostty_surface_key_translation_mods(
|
mods: ghostty_surface_key_translation_mods(
|
||||||
|
@ -35,6 +35,9 @@ extension Ghostty {
|
|||||||
// on supported platforms.
|
// on supported platforms.
|
||||||
@Published var focusInstant: ContinuousClock.Instant? = nil
|
@Published var focusInstant: ContinuousClock.Instant? = nil
|
||||||
|
|
||||||
|
/// True when the bell is active. This is set inactive on focus or event.
|
||||||
|
@Published var bell: Bool = false
|
||||||
|
|
||||||
// Returns sizing information for the surface. This is the raw C
|
// Returns sizing information for the surface. This is the raw C
|
||||||
// structure because I'm lazy.
|
// structure because I'm lazy.
|
||||||
var surfaceSize: ghostty_surface_size_s? {
|
var surfaceSize: ghostty_surface_size_s? {
|
||||||
|
@ -34,4 +34,11 @@ extension NSScreen {
|
|||||||
|
|
||||||
return visibleFrame.height < (frame.height - max(menuHeight, notchInset) - boundaryAreaPadding)
|
return visibleFrame.height < (frame.height - max(menuHeight, notchInset) - boundaryAreaPadding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the screen has a visible notch (i.e., a non-zero safe area inset at the top).
|
||||||
|
var hasNotch: Bool {
|
||||||
|
// We assume that a top safe area means notch, since we don't currently
|
||||||
|
// know any other situation this is true.
|
||||||
|
return safeAreaInsets.top > 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Japanese translations for com.mitchellh.ghostty package
|
# Japanese translations for com.mitchellh.ghostty package
|
||||||
# com.mitchellh.ghostty パッケージに対する英訳.
|
# com.mitchellh.ghostty パッケージに対する和訳.
|
||||||
# Copyright (C) 2025 Mitchell Hashimoto
|
# Copyright (C) 2025 Mitchell Hashimoto
|
||||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||||
# Lon Sagisawa <lon@sagisawa.me>, 2025.
|
# Lon Sagisawa <lon@sagisawa.me>, 2025.
|
||||||
@ -37,7 +37,7 @@ msgstr "OK"
|
|||||||
|
|
||||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5
|
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5
|
||||||
msgid "Configuration Errors"
|
msgid "Configuration Errors"
|
||||||
msgstr "設定エラー"
|
msgstr "設定ファイルにエラーがあります"
|
||||||
|
|
||||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6
|
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -192,7 +192,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6
|
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6
|
||||||
msgid "Warning: Potentially Unsafe Paste"
|
msgid "Warning: Potentially Unsafe Paste"
|
||||||
msgstr "警告: 危険な可能性のあるペースト"
|
msgstr "警告: 危険な可能性のある貼り付け"
|
||||||
|
|
||||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7
|
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -232,19 +232,19 @@ msgstr "分割ウィンドウを閉じますか?"
|
|||||||
|
|
||||||
#: src/apprt/gtk/CloseDialog.zig:96
|
#: src/apprt/gtk/CloseDialog.zig:96
|
||||||
msgid "All terminal sessions will be terminated."
|
msgid "All terminal sessions will be terminated."
|
||||||
msgstr "すべてのターミナルセッションが終了されます。"
|
msgstr "すべてのターミナルセッションが終了します。"
|
||||||
|
|
||||||
#: src/apprt/gtk/CloseDialog.zig:97
|
#: src/apprt/gtk/CloseDialog.zig:97
|
||||||
msgid "All terminal sessions in this window will be terminated."
|
msgid "All terminal sessions in this window will be terminated."
|
||||||
msgstr "ウィンドウ内のすべてのターミナルセッションが終了されます。"
|
msgstr "ウィンドウ内のすべてのターミナルセッションが終了します。"
|
||||||
|
|
||||||
#: src/apprt/gtk/CloseDialog.zig:98
|
#: src/apprt/gtk/CloseDialog.zig:98
|
||||||
msgid "All terminal sessions in this tab will be terminated."
|
msgid "All terminal sessions in this tab will be terminated."
|
||||||
msgstr "タブ内のすべてのターミナルセッションが終了されます。"
|
msgstr "タブ内のすべてのターミナルセッションが終了します。"
|
||||||
|
|
||||||
#: src/apprt/gtk/CloseDialog.zig:99
|
#: src/apprt/gtk/CloseDialog.zig:99
|
||||||
msgid "The currently running process in this split will be terminated."
|
msgid "The currently running process in this split will be terminated."
|
||||||
msgstr "分割ウィンドウ内のすべてのターミナルセッションが終了されます。"
|
msgstr "分割ウィンドウ内のすべてのプロセスが終了します。"
|
||||||
|
|
||||||
#: src/apprt/gtk/Window.zig:200
|
#: src/apprt/gtk/Window.zig:200
|
||||||
msgid "Main Menu"
|
msgid "Main Menu"
|
||||||
|
@ -1874,7 +1874,13 @@ keybind: Keybinds = .{},
|
|||||||
/// for instance under the "Sound > Alert Sound" setting in GNOME,
|
/// for instance under the "Sound > Alert Sound" setting in GNOME,
|
||||||
/// or the "Accessibility > System Bell" settings in KDE Plasma.
|
/// or the "Accessibility > System Bell" settings in KDE Plasma.
|
||||||
///
|
///
|
||||||
/// Currently only implemented on Linux.
|
/// On macOS this has no affect.
|
||||||
|
///
|
||||||
|
/// On macOS, if the app is unfocused, it will bounce the app icon in the dock
|
||||||
|
/// once. Additionally, the title of the window with the alerted terminal
|
||||||
|
/// surface will contain a bell emoji (🔔) until the terminal is focused
|
||||||
|
/// or a key is pressed. These are not currently configurable since they're
|
||||||
|
/// considered unobtrusive.
|
||||||
@"bell-features": BellFeatures = .{},
|
@"bell-features": BellFeatures = .{},
|
||||||
|
|
||||||
/// Control the in-app notifications that Ghostty shows.
|
/// Control the in-app notifications that Ghostty shows.
|
||||||
|
@ -88,6 +88,7 @@ fn writeSyntax(writer: anytype) !void {
|
|||||||
\\let s:cpo_save = &cpo
|
\\let s:cpo_save = &cpo
|
||||||
\\set cpo&vim
|
\\set cpo&vim
|
||||||
\\
|
\\
|
||||||
|
\\syn iskeyword @,48-57,-
|
||||||
\\syn keyword ghosttyConfigKeyword
|
\\syn keyword ghosttyConfigKeyword
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -924,8 +924,8 @@ fn cursorScrollAboveRotate(self: *Screen) !void {
|
|||||||
fastmem.rotateOnceR(Row, cur_rows[self.cursor.page_pin.y..cur_page.size.rows]);
|
fastmem.rotateOnceR(Row, cur_rows[self.cursor.page_pin.y..cur_page.size.rows]);
|
||||||
self.clearCells(
|
self.clearCells(
|
||||||
cur_page,
|
cur_page,
|
||||||
&cur_rows[0],
|
&cur_rows[self.cursor.page_pin.y],
|
||||||
cur_page.getCells(&cur_rows[0]),
|
cur_page.getCells(&cur_rows[self.cursor.page_pin.y]),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Set all the rows we rotated and cleared dirty
|
// Set all the rows we rotated and cleared dirty
|
||||||
@ -1256,6 +1256,17 @@ pub fn clearCells(
|
|||||||
self.assertIntegrity();
|
self.assertIntegrity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (comptime std.debug.runtime_safety) {
|
||||||
|
// Our row and cells should be within the page.
|
||||||
|
const page_rows = page.rows.ptr(page.memory.ptr);
|
||||||
|
assert(@intFromPtr(row) >= @intFromPtr(&page_rows[0]));
|
||||||
|
assert(@intFromPtr(row) <= @intFromPtr(&page_rows[page.size.rows - 1]));
|
||||||
|
|
||||||
|
const row_cells = page.getCells(row);
|
||||||
|
assert(@intFromPtr(&cells[0]) >= @intFromPtr(&row_cells[0]));
|
||||||
|
assert(@intFromPtr(&cells[cells.len - 1]) <= @intFromPtr(&row_cells[row_cells.len - 1]));
|
||||||
|
}
|
||||||
|
|
||||||
// If this row has graphemes, then we need go through a slow path
|
// If this row has graphemes, then we need go through a slow path
|
||||||
// and delete the cell graphemes.
|
// and delete the cell graphemes.
|
||||||
if (row.grapheme) {
|
if (row.grapheme) {
|
||||||
@ -4818,6 +4829,83 @@ test "Screen: scroll above creates new page" {
|
|||||||
try testing.expect(s.pages.isDirty(.{ .active = .{ .x = 0, .y = 2 } }));
|
try testing.expect(s.pages.isDirty(.{ .active = .{ .x = 0, .y = 2 } }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "Screen: scroll above with cursor on non-final row" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var s = try init(alloc, 10, 4, 10);
|
||||||
|
defer s.deinit();
|
||||||
|
|
||||||
|
// Get the cursor to be 2 rows above a new page
|
||||||
|
const first_page_size = s.pages.pages.first.?.data.capacity.rows;
|
||||||
|
s.pages.pages.first.?.data.pauseIntegrityChecks(true);
|
||||||
|
for (0..first_page_size - 3) |_| try s.testWriteString("\n");
|
||||||
|
s.pages.pages.first.?.data.pauseIntegrityChecks(false);
|
||||||
|
|
||||||
|
// Write 3 lines of text, forcing the last line into the first
|
||||||
|
// row of a new page. Move our cursor onto the previous page.
|
||||||
|
try s.setAttribute(.{ .direct_color_bg = .{ .r = 155 } });
|
||||||
|
try s.testWriteString("1AB\n2BC\n3DE\n4FG");
|
||||||
|
s.cursorAbsolute(0, 1);
|
||||||
|
s.pages.clearDirty();
|
||||||
|
|
||||||
|
// Ensure we're still on the first page. So our cursor is on the first
|
||||||
|
// page but we have two pages of data.
|
||||||
|
try testing.expect(s.cursor.page_pin.node == s.pages.pages.first.?);
|
||||||
|
|
||||||
|
// +----------+ = PAGE 0
|
||||||
|
// ... : :
|
||||||
|
// +-------------+ ACTIVE
|
||||||
|
// 4305 |1AB0000000| | 0
|
||||||
|
// 4306 |2BC0000000| | 1
|
||||||
|
// :^ : : = PIN 0
|
||||||
|
// 4307 |3DE0000000| | 2
|
||||||
|
// +----------+ :
|
||||||
|
// +----------+ : = PAGE 1
|
||||||
|
// 0 |4FG0000000| | 3
|
||||||
|
// +----------+ :
|
||||||
|
// +-------------+
|
||||||
|
try s.cursorScrollAbove();
|
||||||
|
|
||||||
|
// +----------+ = PAGE 0
|
||||||
|
// ... : :
|
||||||
|
// 4305 |1AB0000000|
|
||||||
|
// +-------------+ ACTIVE
|
||||||
|
// 4306 |2BC0000000| | 0
|
||||||
|
// 4307 | | | 1
|
||||||
|
// :^ : : = PIN 0
|
||||||
|
// +----------+ :
|
||||||
|
// +----------+ : = PAGE 1
|
||||||
|
// 0 |3DE0000000| | 2
|
||||||
|
// 1 |4FG0000000| | 3
|
||||||
|
// +----------+ :
|
||||||
|
// +-------------+
|
||||||
|
// try s.pages.diagram(std.io.getStdErr().writer());
|
||||||
|
|
||||||
|
{
|
||||||
|
const contents = try s.dumpStringAlloc(alloc, .{ .viewport = .{} });
|
||||||
|
defer alloc.free(contents);
|
||||||
|
try testing.expectEqualStrings("2BC\n\n3DE\n4FG", contents);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const list_cell = s.pages.getCell(.{ .active = .{ .x = 0, .y = 1 } }).?;
|
||||||
|
const cell = list_cell.cell;
|
||||||
|
try testing.expect(cell.content_tag == .bg_color_rgb);
|
||||||
|
try testing.expectEqual(Cell.RGB{
|
||||||
|
.r = 155,
|
||||||
|
.g = 0,
|
||||||
|
.b = 0,
|
||||||
|
}, cell.content.color_rgb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page 0's penultimate row is dirty because the cursor moved off of it.
|
||||||
|
try testing.expect(s.pages.isDirty(.{ .active = .{ .x = 0, .y = 0 } }));
|
||||||
|
// Page 0's final row is dirty because it was cleared.
|
||||||
|
try testing.expect(s.pages.isDirty(.{ .active = .{ .x = 0, .y = 1 } }));
|
||||||
|
// Page 1's row is dirty because it's new.
|
||||||
|
try testing.expect(s.pages.isDirty(.{ .active = .{ .x = 0, .y = 2 } }));
|
||||||
|
}
|
||||||
|
|
||||||
test "Screen: scroll above no scrollback bottom of page" {
|
test "Screen: scroll above no scrollback bottom of page" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
Reference in New Issue
Block a user