Merge branch 'ghostty-org:main' into feature-cursor-height

This commit is contained in:
I Duran
2024-11-05 12:30:22 +03:00
committed by GitHub
11 changed files with 114 additions and 33 deletions

View File

@ -63,7 +63,7 @@ placed at `$XDG_CONFIG_HOME/ghostty/config`, which defaults to
The file format is documented below as an example:
```
```ini
# The syntax is "key = value". The whitespace around the equals doesn't matter.
background = 282c34
foreground= ffffff
@ -375,9 +375,9 @@ test cases.
We believe Ghostty is one of the most compliant terminal emulators available.
Terminal behavior is partially a dejour standard
Terminal behavior is partially a de jure standard
(i.e. [ECMA-48](https://ecma-international.org/publications-and-standards/standards/ecma-48/))
but mostly a defacto standard as defined by popular terminal emulators
but mostly a de facto standard as defined by popular terminal emulators
worldwide. Ghostty takes the approach that our behavior is defined by
(1) standards, if available, (2) xterm, if the feature exists, (3)
other popular terminals, in that order. This defines what the Ghostty project

View File

@ -499,6 +499,22 @@ pub fn build(b: *std.Build) !void {
});
}
// Neovim plugin
// This is just a copy-paste of the Vim plugin, but using a Neovim subdir.
// By default, Neovim doesn't look inside share/vim/vimfiles. Some distros
// configure it to do that however. Fedora, does not as a counterexample.
{
const wf = b.addWriteFiles();
_ = wf.add("syntax/ghostty.vim", config_vim.syntax);
_ = wf.add("ftdetect/ghostty.vim", config_vim.ftdetect);
_ = wf.add("ftplugin/ghostty.vim", config_vim.ftplugin);
b.installDirectory(.{
.source_dir = wf.getDirectory(),
.install_dir = .prefix,
.install_subdir = "share/nvim/site",
});
}
// Documentation
if (emit_docs) {
try buildDocumentation(b, config);
@ -1227,14 +1243,7 @@ fn addDeps(
.optimize = optimize,
});
// This is a bit of a hack that should probably be fixed upstream
// in zig-objc, but we need to add the apple SDK paths to the
// zig-objc module so that it can find the objc runtime headers.
const module = objc_dep.module("objc");
module.resolved_target = step.root_module.resolved_target;
try @import("apple_sdk").addPaths(b, module);
step.root_module.addImport("objc", module);
step.root_module.addImport("objc", objc_dep.module("objc"));
step.root_module.addImport("macos", macos_dep.module("macos"));
step.linkLibrary(macos_dep.artifact("macos"));
try static_libs.append(macos_dep.artifact("macos").getEmittedBin());

View File

@ -14,8 +14,8 @@
.lazy = true,
},
.zig_objc = .{
.url = "https://github.com/mitchellh/zig-objc/archive/fe5ac419530cf800294369d996133fe9cd067aec.tar.gz",
.hash = "122034b3e15d582d8d101a7713e5f13c872b8b8eb6d9cb47515b8e34ee75e122630d",
.url = "https://github.com/mitchellh/zig-objc/archive/9b8ba849b0f58fe207ecd6ab7c147af55b17556e.tar.gz",
.hash = "1220e17e64ef0ef561b3e4b9f3a96a2494285f2ec31c097721bf8c8677ec4415c634",
},
.zig_js = .{
.url = "https://github.com/mitchellh/zig-js/archive/d0b8b0a57c52fbc89f9d9fecba75ca29da7dd7d1.tar.gz",

View File

@ -1,3 +1,3 @@
# This file is auto-generated! check build-support/check-zig-cache-hash.sh for
# more details.
"sha256-5LBZAExb4PJefW+M0Eo+TcoszhBdIFTGBOv6lte5L0Q="
"sha256-dNGDbVaPxDbIrDUkDwjzeRHHVcX4KnWKciXiTp1c7lE="

View File

@ -929,6 +929,13 @@ fn loadRuntimeCss(
\\ --headerbar-bg-color: rgb({d},{d},{d});
\\ --headerbar-backdrop-color: oklab(from var(--headerbar-bg-color) calc(l * 0.9) a b / alpha);
\\}}
\\windowhandle {{
\\ background-color: var(--headerbar-bg-color);
\\ color: var(--headerbar-fg-color);
\\}}
\\windowhandle:backdrop {{
\\ background-color: var(--headerbar-backdrop-color);
\\}}
, .{
headerbar_foreground.r,
headerbar_foreground.g,

View File

@ -14,6 +14,7 @@ pub const emoji = @embedFile("res/NotoColorEmoji.ttf");
pub const emoji_text = @embedFile("res/NotoEmoji-Regular.ttf");
/// Fonts with general properties
pub const arabic = @embedFile("res/KawkabMono-Regular.ttf");
pub const variable = @embedFile("res/Lilex-VF.ttf");
/// Font with nerd fonts embedded.

Binary file not shown.

View File

@ -190,6 +190,11 @@ pub const Shaper = struct {
// Reset the buffer for our current run
self.shaper.hb_buf.reset();
self.shaper.hb_buf.setContentType(.unicode);
// We don't support RTL text because RTL in terminals is messy.
// Its something we want to improve. For now, we force LTR because
// our renderers assume a strictly increasing X value.
self.shaper.hb_buf.setDirection(.ltr);
}
pub fn addCodepoint(self: RunIteratorHook, cp: u32, cluster: u32) !void {
@ -453,6 +458,46 @@ test "shape monaspace ligs" {
}
}
// Ghostty doesn't currently support RTL and our renderers assume
// that cells are in strict LTR order. This means that we need to
// force RTL text to be LTR for rendering. This test ensures that
// we are correctly forcing RTL text to be LTR.
test "shape arabic forced LTR" {
const testing = std.testing;
const alloc = testing.allocator;
var testdata = try testShaperWithFont(alloc, .arabic);
defer testdata.deinit();
var screen = try terminal.Screen.init(alloc, 120, 30, 0);
defer screen.deinit();
try screen.testWriteString(@embedFile("testdata/arabic.txt"));
var shaper = &testdata.shaper;
var it = shaper.runIterator(
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
count += 1;
try testing.expectEqual(@as(usize, 25), run.cells);
const cells = try shaper.shape(run);
try testing.expectEqual(@as(usize, 25), cells.len);
var x: u16 = cells[0].x;
for (cells[1..]) |cell| {
try testing.expectEqual(x + 1, cell.x);
x = cell.x;
}
}
try testing.expectEqual(@as(usize, 1), count);
}
test "shape emoji width" {
const testing = std.testing;
const alloc = testing.allocator;
@ -1146,6 +1191,7 @@ const TestShaper = struct {
const TestFont = enum {
inconsolata,
monaspace_neon,
arabic,
};
/// Helper to return a fully initialized shaper.
@ -1159,6 +1205,7 @@ fn testShaperWithFont(alloc: Allocator, font_req: TestFont) !TestShaper {
const testFont = switch (font_req) {
.inconsolata => font.embedded.inconsolata,
.monaspace_neon => font.embedded.monaspace_neon,
.arabic => font.embedded.arabic,
};
var lib = try Library.init();

3
src/font/shaper/testdata/arabic.txt vendored Normal file
View File

@ -0,0 +1,3 @@
غريبه لاني عربي أبا عن جد
واتكلم الانجليزية بطلاقة اكثر من ٢٥ سنه
ومع هذا اجد العربيه افضل لان فيها الكثير من المفردات الاكثر دقه بالوصف

View File

@ -175,9 +175,9 @@ pub const GPUState = struct {
instance: InstanceBuffer, // MTLBuffer
pub fn init() !GPUState {
const device = objc.Object.fromId(mtl.MTLCreateSystemDefaultDevice());
const device = try chooseDevice();
const queue = device.msgSend(objc.Object, objc.sel("newCommandQueue"), .{});
errdefer queue.msgSend(void, objc.sel("release"), .{});
errdefer queue.release();
var instance = try InstanceBuffer.initFill(device, &.{
0, 1, 3, // Top-left triangle
@ -200,13 +200,33 @@ pub const GPUState = struct {
return result;
}
fn chooseDevice() error{NoMetalDevice}!objc.Object {
const devices = objc.Object.fromId(mtl.MTLCopyAllDevices());
defer devices.release();
var chosen_device: ?objc.Object = null;
var iter = devices.iterate();
while (iter.next()) |device| {
// We want a GPU thats connected to a display.
if (device.getProperty(bool, "isHeadless")) continue;
chosen_device = device;
// If the user has an eGPU plugged in, they probably want
// to use it. Otherwise, integrated GPUs are better for
// battery life and thermals.
if (device.getProperty(bool, "isRemovable") or
device.getProperty(bool, "isLowPower")) break;
}
const device = chosen_device orelse return error.NoMetalDevice;
return device.retain();
}
pub fn deinit(self: *GPUState) void {
// Wait for all of our inflight draws to complete so that
// we can cleanly deinit our GPU state.
for (0..BufferCount) |_| self.frame_sema.wait();
for (&self.frames) |*frame| frame.deinit();
self.instance.deinit();
self.queue.msgSend(void, objc.sel("release"), .{});
self.queue.release();
self.device.release();
}
/// Get the next frame state to draw to. This will wait on the
@ -269,13 +289,13 @@ pub const FrameState = struct {
.size = 8,
.format = .grayscale,
});
errdefer deinitMTLResource(grayscale);
errdefer grayscale.release();
const color = try initAtlasTexture(device, &.{
.data = undefined,
.size = 8,
.format = .rgba,
});
errdefer deinitMTLResource(color);
errdefer color.release();
return .{
.uniforms = uniforms,
@ -290,8 +310,8 @@ pub const FrameState = struct {
self.uniforms.deinit();
self.cells.deinit();
self.cells_bg.deinit();
deinitMTLResource(self.grayscale);
deinitMTLResource(self.color);
self.grayscale.release();
self.color.release();
}
};
@ -319,8 +339,8 @@ pub const CustomShaderState = struct {
}
pub fn deinit(self: *CustomShaderState) void {
deinitMTLResource(self.front_texture);
deinitMTLResource(self.back_texture);
self.front_texture.release();
self.back_texture.release();
self.sampler.deinit();
}
};
@ -2057,8 +2077,8 @@ pub fn setScreenSize(
// Only free our previous texture if this isn't our first
// time setting the custom shader state.
if (state.uniforms.resolution[0] > 0) {
deinitMTLResource(state.front_texture);
deinitMTLResource(state.back_texture);
state.front_texture.release();
state.back_texture.release();
}
state.uniforms.resolution = .{
@ -2982,7 +3002,7 @@ fn syncAtlasTexture(device: objc.Object, atlas: *const font.Atlas, texture: *obj
const width = texture.getProperty(c_ulong, "width");
if (atlas.size > width) {
// Free our old texture
deinitMTLResource(texture.*);
texture.*.release();
// Reallocate
texture.* = try initAtlasTexture(device, atlas);
@ -3049,12 +3069,6 @@ fn initAtlasTexture(device: objc.Object, atlas: *const font.Atlas) !objc.Object
return objc.Object.fromId(id);
}
/// Deinitialize a metal resource (buffer, texture, etc.) and free the
/// memory associated with it.
fn deinitMTLResource(obj: objc.Object) void {
obj.msgSend(void, objc.sel("release"), .{});
}
test {
_ = mtl_cell;
}

View File

@ -175,4 +175,4 @@ pub const MTLSize = extern struct {
depth: c_ulong,
};
pub extern "c" fn MTLCreateSystemDefaultDevice() ?*anyopaque;
pub extern "c" fn MTLCopyAllDevices() ?*anyopaque;