forked from ghostty-org/ghostty
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes ghostty-org#5257 Specify environment variables to pass to commands launched in a terminal surface. The format is `env=KEY=VALUE`. `env = foo=bar` `env = bar=baz` Setting `env` to an empty string will reset the entire map to default (empty). `env =` Setting a key to an empty string will remove that particular key and corresponding value from the map. `env = foo=bar` `env = foo=` will result in `foo` not being passed to the launched commands. Setting a key multiple times will overwrite previous entries. `env = foo=bar` `env = foo=baz` will result in `foo=baz` being passed to the launched commands. These environment variables _will not_ be passed to commands run by Ghostty for other purposes, like `open` or `xdg-open` used to open URLs in your browser.
- Loading branch information
Showing
5 changed files
with
237 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
/// RepeatableStringMap is a key/value that can be repeated to accumulate a | ||
/// string map. This isn't called "StringMap" because I find that sometimes | ||
/// leads to confusion that it _accepts_ a map such as JSON dict. | ||
const RepeatableStringMap = @This(); | ||
const std = @import("std"); | ||
|
||
const formatterpkg = @import("formatter.zig"); | ||
|
||
const Map = std.ArrayHashMapUnmanaged([:0]const u8, [:0]const u8, std.array_hash_map.StringContext, true); | ||
|
||
// Allocator for the list is the arena for the parent config. | ||
map: Map = .{}, | ||
|
||
pub fn parseCLI(self: *RepeatableStringMap, alloc: std.mem.Allocator, input: ?[]const u8) !void { | ||
const value = input orelse return error.ValueRequired; | ||
|
||
// Empty value resets the list | ||
if (value.len == 0) { | ||
var it = self.map.iterator(); | ||
while (it.next()) |entry| { | ||
alloc.free(entry.key_ptr.*); | ||
alloc.free(entry.value_ptr.*); | ||
} | ||
self.map.clearRetainingCapacity(); | ||
return; | ||
} | ||
|
||
const index = std.mem.indexOfScalar(u8, value, '=') orelse return error.ValueRequired; | ||
|
||
const key = std.mem.trim(u8, value[0..index], &std.ascii.whitespace); | ||
const val = std.mem.trim(u8, value[index + 1 ..], &std.ascii.whitespace); | ||
|
||
const key_copy = try alloc.dupeZ(u8, key); | ||
errdefer alloc.free(key_copy); | ||
|
||
if (val.len == 0) { | ||
if (self.map.fetchOrderedRemove(key_copy)) |entry| { | ||
alloc.free(entry.key); | ||
alloc.free(entry.value); | ||
} | ||
alloc.free(key_copy); | ||
return; | ||
} | ||
|
||
const val_copy = try alloc.dupeZ(u8, val); | ||
errdefer alloc.free(val_copy); | ||
|
||
if (try self.map.fetchPut(alloc, key_copy, val_copy)) |entry| { | ||
alloc.free(key_copy); | ||
alloc.free(entry.value); | ||
} | ||
} | ||
|
||
/// Deep copy of the struct. Required by Config. | ||
pub fn clone(self: *const RepeatableStringMap, alloc: std.mem.Allocator) std.mem.Allocator.Error!RepeatableStringMap { | ||
var map: Map = .{}; | ||
try map.ensureTotalCapacity(alloc, self.map.count()); | ||
|
||
errdefer { | ||
var it = map.iterator(); | ||
while (it.next()) |entry| { | ||
alloc.free(entry.key_ptr.*); | ||
alloc.free(entry.value_ptr.*); | ||
} | ||
map.deinit(alloc); | ||
} | ||
|
||
var it = self.map.iterator(); | ||
while (it.next()) |entry| { | ||
const key = try alloc.dupeZ(u8, entry.key_ptr.*); | ||
const value = try alloc.dupeZ(u8, entry.value_ptr.*); | ||
map.putAssumeCapacity(key, value); | ||
} | ||
|
||
return .{ .map = map }; | ||
} | ||
|
||
/// The number of items in the map | ||
pub fn count(self: RepeatableStringMap) usize { | ||
return self.map.count(); | ||
} | ||
|
||
/// Iterator over the entries in the map. | ||
pub fn iterator(self: RepeatableStringMap) Map.Iterator { | ||
return self.map.iterator(); | ||
} | ||
|
||
/// Compare if two of our value are requal. Required by Config. | ||
pub fn equal(self: RepeatableStringMap, other: RepeatableStringMap) bool { | ||
if (self.map.count() != other.map.count()) return false; | ||
var it = self.map.iterator(); | ||
while (it.next()) |entry| { | ||
const value = other.map.get(entry.key_ptr.*) orelse return false; | ||
if (!std.mem.eql(u8, entry.value_ptr.*, value)) return false; | ||
} else return true; | ||
} | ||
|
||
/// Used by formatter | ||
pub fn formatEntry(self: RepeatableStringMap, formatter: anytype) !void { | ||
// If no items, we want to render an empty field. | ||
if (self.map.count() == 0) { | ||
try formatter.formatEntry(void, {}); | ||
return; | ||
} | ||
|
||
var it = self.map.iterator(); | ||
while (it.next()) |entry| { | ||
var buf: [256]u8 = undefined; | ||
const value = std.fmt.bufPrint(&buf, "{s}={s}", .{ entry.key_ptr.*, entry.value_ptr.* }) catch |err| switch (err) { | ||
error.NoSpaceLeft => return error.OutOfMemory, | ||
}; | ||
try formatter.formatEntry([]const u8, value); | ||
} | ||
} | ||
|
||
test "RepeatableStringMap: parseCLI" { | ||
const testing = std.testing; | ||
var arena = std.heap.ArenaAllocator.init(testing.allocator); | ||
defer arena.deinit(); | ||
const alloc = arena.allocator(); | ||
|
||
var map: RepeatableStringMap = .{}; | ||
|
||
try testing.expectError(error.ValueRequired, map.parseCLI(alloc, "A")); | ||
|
||
try map.parseCLI(alloc, "A=B"); | ||
try map.parseCLI(alloc, "B=C"); | ||
try testing.expectEqual(@as(usize, 2), map.count()); | ||
|
||
try map.parseCLI(alloc, ""); | ||
try testing.expectEqual(@as(usize, 0), map.count()); | ||
|
||
try map.parseCLI(alloc, "A=B"); | ||
try testing.expectEqual(@as(usize, 1), map.count()); | ||
try map.parseCLI(alloc, "A=C"); | ||
try testing.expectEqual(@as(usize, 1), map.count()); | ||
} | ||
|
||
test "RepeatableStringMap: formatConfig empty" { | ||
const testing = std.testing; | ||
var buf = std.ArrayList(u8).init(testing.allocator); | ||
defer buf.deinit(); | ||
|
||
var list: RepeatableStringMap = .{}; | ||
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); | ||
try std.testing.expectEqualSlices(u8, "a = \n", buf.items); | ||
} | ||
|
||
test "RepeatableStringMap: formatConfig single item" { | ||
const testing = std.testing; | ||
|
||
var arena = std.heap.ArenaAllocator.init(testing.allocator); | ||
defer arena.deinit(); | ||
const alloc = arena.allocator(); | ||
|
||
{ | ||
var buf = std.ArrayList(u8).init(testing.allocator); | ||
defer buf.deinit(); | ||
var map: RepeatableStringMap = .{}; | ||
try map.parseCLI(alloc, "A=B"); | ||
try map.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); | ||
try std.testing.expectEqualSlices(u8, "a = A=B\n", buf.items); | ||
} | ||
{ | ||
var buf = std.ArrayList(u8).init(testing.allocator); | ||
defer buf.deinit(); | ||
var map: RepeatableStringMap = .{}; | ||
try map.parseCLI(alloc, " A = B "); | ||
try map.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); | ||
try std.testing.expectEqualSlices(u8, "a = A=B\n", buf.items); | ||
} | ||
} | ||
|
||
test "RepeatableStringMap: formatConfig multiple items" { | ||
const testing = std.testing; | ||
|
||
var arena = std.heap.ArenaAllocator.init(testing.allocator); | ||
defer arena.deinit(); | ||
const alloc = arena.allocator(); | ||
|
||
{ | ||
var buf = std.ArrayList(u8).init(testing.allocator); | ||
defer buf.deinit(); | ||
var list: RepeatableStringMap = .{}; | ||
try list.parseCLI(alloc, "A=B"); | ||
try list.parseCLI(alloc, "B = C"); | ||
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); | ||
try std.testing.expectEqualSlices(u8, "a = A=B\na = B=C\n", buf.items); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters