#libvaxis: a TUI library for zig
1 messages · Page 3 of 1
So it sends a bunch and the last one is \x1bO
And the next read will be B\x1bOB....
i just pushed to this branch https://github.com/Rexicon226/osmium/tree/debug
libvaxis doesn't currently support the style used in the post does it?
Not natively no, you would have to make it your own
It's complicated to do well
ahhh, I see. thanks for pointing it out though :)
What do I run to get into the tui?
ok so build it
then uh
i guess you could just run any old python file
do zig-out/osmium examples/calculator.py --debug
:(
what zig version?
error: FileNotFound
/home/tim/.zig/versions/zig-linux-x86_64-0.13.0/lib/std/process/Child.zig:423:5: 0x1196f67 in waitPosix (build)
return self.term.?;
^
/home/tim/.zig/versions/zig-linux-x86_64-0.13.0/lib/std/process/Child.zig:300:9: 0x1196fe3 in wait (build)
try self.waitPosix();
^
/home/tim/.zig/versions/zig-linux-x86_64-0.13.0/lib/std/process/Child.zig:400:17: 0x1138b5f in run (build)
.term = try child.wait(),
^
/home/tim/repos/osmium/tests/matrix.zig:30:20: 0x11387ed in addCase (build)
const result = try std.process.Child.run(.{
^
/home/tim/repos/osmium/tests/matrix.zig:21:26: 0x1139310 in addCases (build)
const test_run = try addCase(b, test_file, exe);
^
/home/tim/repos/osmium/tests/cases.zig:19:30: 0x1139878 in addCases (build)
parent_step.dependOn(try matrix.addCases(b, dir, exe));
^
/home/tim/repos/osmium/build.zig:97:5: 0x113a5e0 in build (build)
try cases.addCases(b, exe, test_step);
^
/home/tim/.zig/versions/zig-linux-x86_64-0.13.0/lib/std/Build.zig:2117:24: 0x111caa7 in runBuild__anon_8821 (build)
.ErrorUnion => try build_zig.build(b),
^
/home/tim/.zig/versions/zig-linux-x86_64-0.13.0/lib/compiler/build_runner.zig:301:9: 0x1117dcf in main (build)
try builder.runBuild(root);
^
error: the following build command failed with exit code 1:
/home/tim/repos/osmium/.zig-cache/o/ed52e6c312b90fa99360cb68c1cf0811/build /home/tim/.zig/versions/zig-linux-x86_64-0.13.0/zig /home/tim/repos/osmium /home/tim/repos/osmium/.zig-cache /home/tim/.cache/zig --seed 0xeda4b3bb -Zed9a954b4dc94e10
uh, well what's further up the stack trace?
ah well, you're running the tests
they expect you to have a python3.10 binary in your PATH
i probably should make it use the python it builds instead, but i can do that later
just build with zig build osmium
actually i dont have a step for osmium
lol
interesting
would you just comment out that try cases.addCases line?
Ok it's building now!
nice
I assume this is on ghostty?
ok
hence me being 70% sure it's the terminal's fault
I was wondering why it was getting SS3s for arrows, I know ghostty does the CSI variant by default
Both are valid
does mouse wheel down just translate to arrow down?
Sometimes, yes
There is a mode that when in alternate screen, mouse wheel sends arrow keys
vaxis doesn't enable this - so rio must have it on by default, or maybe only on if mouse reports aren't turned on
Ok I am in the tui and I can press arrows right away and it goes between the two
I can't trigger the panic
I will install rio
you tried scrolling really hard?
I type and nothing happens
i can confirm that the panic only happens in rio
so i must assume it's a rio skill issue
i'll just scroll really gently
I am still going to add this if statement for a partial event
I didn't allow for partial events when I first wrote the parser
I just am not sure how you ended upa t the else arm
\x1b O O is "/"
I mean, unless rio has opted for non-standard encodings
do you also have kitty installed?
kitty doesn't run on windows
kitten show-key would be interesting to see what you generate from your mouse
Ohh this is windows?
mhm
well
it's a windows terminal that is sshed into a linux machine
i'm 99% sure that ssh isn't the issue
ok in your shell there, type cat -v and press arrows and your mouse wheel
on the linux machine i mean
Ok, these are what ghostty sends as well
ok now try printf "\x1b[?1049h" && cat -v
Your mouse will probably send stuff there
me as well :P
Ok so you set it to return 0 bytes and you get an unhandled 1b
1b is escape
ah
I would guess rio is writing all their events, and the last one fails to write completely - so they rewrite it at the beginning of the next syscall
So the sequential writes come in as \x1bOB\x1O....next write \x1bOB\x1bOB...
they didn't writeAll 😉
gl! I know zero rust
Yeah, i had to install rust to build it 🙂
man that will throw off my parser state really bad
at least it isn't your bug :P
That's why you are getting hte OB spammed
Because it eats \x1bO\x1b and the rest never gets parsed as an escape sequence
Becaaause it's now off by one
that is my bug 🙂
Need to recover from that nicely
returning the N = length is the best way :P
just skip over it
that would probably mess up some further stuff tho
That works in this case with rio, but presumably anyone could send an \x1bO\x1b sequence at anytime
The standard state machine handles this fine
I just made some shortcuts for input parsing
ah
And because - until now, i have never seen a borked escape sequence like this
i am sort of an expert at breaking things
im about to suffocate in my room, brb while it builds :D
this cali summer is no joke
Just pushed the fix
const key: Key = switch (input[2]) {
0x1B => return .{
.event = null,
.n = input.len,
},
I also guard for the len < 3, but by this point we've already check that 0 and 1 are what we want, but if 2 is an escape, we have an invalid event - consume the bytes and move along
Ah...it should be input.len -1
Well, it should be 2 🙂
yep
and the warning is gone of course
currently installing vs 2017 so i can build rio
gonna try tracking down this rio bug
There is a chance this is not a bug on their end
I mean, it's kind of a bug but they could be delivering all of the correct events already
It's only a bug if the partial write results in a mismatch of the number of events sent
SO don't kill yourself on it 😉
i will take that as an excuse to not hunt for it, and get back to designing this bad tui :P
this BOBOBO thing is kinda annoying tho
It shouldn’t still be spamming that?
tis is
(╯°□°)╯︵ ┻━┻
no panics tho!
Alright I will take a look this week at making the parser more robust
aside and possibly silly question. i'm printing this line to seperate cli commands but i dont know how to correctly calculate how long it should be
right now i have:
try writer.writeBytesNTimes("⎯", state.text_width);
it's "" because that character is a utf8 and doesnt fit in ascii
text_width is just the width of the box
but it's a bit too long right now
yeah it's one level down from the vx.window(), where the width passed to the child function is .{ .limit = win.width / 2 }
The child window has it's width set when you create it
you are writing direct to the tty?
no
Or is that in the scrollview?
so writer is an ArrayList(u8) which buffers the input
then i create a Segment whos text is the items of that array list
Ah ok
all the logic there is fine i think, i just dont know how to correctly calculate the width of the character
it seems to be a bit larger than a cell
win.gwidth
maybe i just chose a stupid character
Something seems off though
It should be one cell
Oh maybe not
yeah that is a weird one
hopefully measuring with gwidth will fix it, otherwise use a box drawing character: ━
using the box drawing character worked perfectly
A lot of terminals will custom draw these to have no gaps (ghostty, kitty, foot, alacritty, etc)
it is a bit thick though
the shitty ass kernel console:
how has no one been able to make a decent-looking alternative to the kernel console
why would you
That hting has to work everywhere and be tiny!
and have an embedded font that is tiny as well
well using gwidth isn't really useful because it obviously can't return a fractional width or something
probably rio is having issues with rendering
using ─ is perfect
rio crash #1 😄
@obsidian rover The unicode on windows fix is not quite as simple as I had hoped. It seems we need to decode UTF-16 first before we can encode to UTF-8.
which makes sense
The PR is ready now.
@obsidian rover it looks like bracketed paste is supported in conhost. Does this mean we can enable it somehow in libvaxis on windows?
Yes - it should be enabled by default though.
We just blindly issue the enabling code
ah ok. I will test it again. I thought it was not working.
nope, it doesn't seem to work
It looks to me like windows\Tty.zig eats all escape sequences and never actually sends them to parser.parse anywhere. So I don't see how bracketed paste can work.
Ohh right. Because it's using the win32 api only I didn't plumb in any escapes
Does it allow bracketed paste mode if there is no vt processing?
I guess not. I tried passing all escape sequences to Parser.parse, but I didn't magically get bracketed paste support.
It might still be a good idea to actually parse escape sequences into events even on windows. Do you want me to PR my changes?
Yeah - I think I remember reading a case where at least on the input side you can send VTs even without vt input processing turned on
So there's likely some case somewhere that happens on the output side
Yes, I've definitely seen a few escape sequences arriving. That's why I assumed there would be bracketed paste sequences too.
@obsidian rover any idea why when in -Doptimize=ReleaseSafe, debug stuff is still printed to the term?
is there a way to stop this without redirecting output
Not sure why - that is odd. One of my plans for this week is to clean up logging and put it into a single log scope so it can be more easily filtered with a custom logFn
(Which is how I would stop it: custom logFn and filter the scopes you don’t want)
sounds good, thanks for that man
any updates on the github issue I put up btw? It's all good if you haven't had the time, i appreciate your work :) just curious
are you passing in the optimization mode to the libvaxis dependency
Haven’t touched it again yet…I started but it was more complicated than I originally thought so needed to spend some more time on it
no stress man, take your time :)
:O just target, shit. let me try that now
same thing lol
fn build_targets(b: *std.Build, build_options_module: *std.Build.Module) !void {
const targets: []const std.Target.Query = &.{
.{ .cpu_arch = .aarch64, .os_tag = .linux },
.{ .cpu_arch = .x86_64, .os_tag = .linux },
};
const optimize = .ReleaseSafe;
for (targets) |t| {
const target = b.resolveTargetQuery(t);
const libvaxis = b.dependency("vaxis", .{ .target = target, .optimize = optimize }).module("vaxis");
const fuzzig = b.dependency("fuzzig", .{ .target = target, .optimize = optimize }).module("fuzzig");
const exe = b.addExecutable(.{
.name = "sftm",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("fuzzig", fuzzig);
exe.root_module.addImport("vaxis", libvaxis);
exe.root_module.addImport("options", build_options_module);
b.installArtifact(exe);
const target_output = b.addInstallArtifact(exe, .{
.dest_dir = .{
.override = .{
.custom = try t.zigTriple(b.allocator),
},
},
});
b.getInstallStep().dependOn(&target_output.step);
}
}
fyi you don't need custom logfn to filter log scopes, std_options has log_scope_levels
pub const std_options: std.Options = .{
.log_scope_levels = &.{
.{
.scope = .terminal,
.level = .err,
},
},
};
i'd prefix the scopes in vaxis with vaxis_ though, it's good idea for libraries to do that imo
You have to filter each scope then right, there is no globbing on the enum?
yeah you have to add all of them to the array
you can do it reverse too though, have default scope level be err and only list your own scopes to override
I was thinking of dropping them all under a single .vaxis scope to make it easy. I'm not sure more granularity than that is needed
But prefixing would be a good idea too - we'll see where it ends up!
yeah as long as the scope names avoid name collisions was the point
I guess you could filter with std.mem.startsWith(u8, @tagName(.scope), "vaxis") in a custom logFn
@tagName(scope) probably :P
Hey @obsidian rover, is there planned mac support for pty?
The terminal widget? Yes
any ideas on why the terminal would glitched and a weird offset after quitting ?
If you didn’t call tty.deinit
that fixed it ! they were missing in some examples, thanks for the help !
If you are willing to, a PR to fix the examples you found without it would be awesome!
yess planning to, just tinkering with the library and get the hang of it, it s very nice so far
when handling events, if the event won't change anything visually, is it a good idea to just continue instead of rerendering everything ?
Yep! If there are no visual changes, then no reason to re-render.
any suggestions on how you handle borders ? to avoid this type of stuff 😂
Im using the box drawing characters you ve mentioned before
from the wiki
The gap where they meet? If you know the exact coordinate then I would draw the lines and then at that cell draw the connecting glyph
yess
the way im drawing them is the following
┬
header:
pub fn header_component(parent: vaxis.Window, opts: ?OffsetOptions) !vaxis.Window {
const opts_or_default = opts orelse OffsetOptions{};
const header_child = parent.child(.{
.x_off = opts_or_default.x_off,
.y_off = opts_or_default.y_off,
.height = .{ .limit = parent.height * 3 / 100 },
.width = .{ .limit = parent.width },
});
const segment: vaxis.Segment = .{
.text = header,
};
_ = try header_child.printSegment(segment, .{
.col_offset = header_child.width / 2 - header.len / 2,
.row_offset = header_child.height * 20 / 100,
});
const h_cell: vaxis.Cell = .{
.char = .{ .grapheme = "━" },
};
for (0..header_child.width) |i| {
header_child.writeCell(i, header_child.height - 1, h_cell);
}
return header_child;
}
sidebar:
pub fn sidebar_component(parent: vaxis.Window, opts: ?OffsetOptions) !vaxis.Window {
const opts_or_default = opts orelse OffsetOptions{};
const sidebar_child = parent.child(.{
.x_off = opts_or_default.x_off,
.y_off = opts_or_default.y_off,
.height = .{ .limit = parent.height - opts_or_default.y_off },
.width = .{ .limit = parent.width * 30 / 100 },
});
const border_char: vaxis.Cell = .{
.char = .{ .grapheme = "┃" },
};
for (0..sidebar_child.height) |i| {
sidebar_child.writeCell(sidebar_child.width - 1, i, border_char);
}
return sidebar_child;
}
but for the sidebar i pass y_off eql to the header_component.height
lemme try
worked perfectly
win.writeCell(sidebar_component.width - 1, header_component.height - 1, intersection);
thanks !
is there a way to get the []u8 inside the TextInput widget without clearing the inner slice ?
just input.buf.items?
You can directly access input.buf.items and get the second half (it’s a gap buffer) from input.buf.secondHalf()
but it mentions that the pointers might get invalidated by various functions of the GapBuffer still fine to use ?
same way it s used in the toOwnedSlice i would say without the freeing
basically this is what i am trying to do, the input takes a search term, and it would show the result as the files that contains the term (col, line etc) i don t see the need to allocate each time i ll send the search term when clicking enter
don t mind the result just for debugging purposes 😂
Yeah I agree
I almost wonder if this shouldn't change into a TextArea widget (implying it is for large text editing, and give it proper wrapping etc) and make a simpler TextInput widget meant for small inputs
That way it's easy to access the full input value without an extra buffer or allocator
Oh text input is the one on the left, the right side is just a segment, just wanted to see the data that i get from ripgrep process
OHH now i get what you mean nvm
I ll try that
I meant the TextInput widget - I think the gap buffer is nice for larger text editing needs but small inputs like a search box, etc really don't need that
And a simple ArrayList can make it easier to use
btw any reason on why the offset in the segment is only in the first line ? or should i split them by new line and make a segment for each is that how the Segment is supposed to be used ?
For right now though, you could get the length from input.buf.realLength, then allocate a buffer and memcpy input.buf.items into it, then memcpy input.buf.secondHalf() into it
ahhh so realLength always takes in consideration of the size of the secondHalf beforehand ?
Ah - the offset is only for the first line so you can have multiple chunks of text with different styles but still printed as if a single body of text
Imagine a paragraph with a single bold word in the middle
It would need three Segments
If you have "different" chunks of text (bulletted lists, etc), I would print those with separate function calls
Ohh now i understand how Segments work, it s even shown in one of the examples as well i think in table example i should ve just payed attention
thanks !
Think of a segment as a singular run of constant styled text within a larger body of text
What are you making?
something similar to this
search and replace on multiple files
Oh cool
seems to be fun, and so far libvaxis seems very intuitive, specially how you can handle both vaxis events and your own custom events in the same event_loop
Glad to hear!
any tips and tricks on how to debug ? the only way im debugging with so far is just start the process attach lldb to it and breakpoints
I’m a printf debugger so I print statements, redirect stderr and tail -f the logfile in another terminal: zig build run 2> foo.log
OHHH that s neat thanks ! thanks
Depends on exactly what issue you need to debug.
Yess ! is just that the formatting gets corrupted sometimes and what rock suggested fixed it, that s why i was using lldb and printing the variables in certain breakpoints
when rendering a window that has some segments/cells written, is there anything specific that has to be done in case you want that window to be empty again ? like saving a ref of that window and call clear or something ?
You want it clear in the same render cycle or the next?
the next !
pub fn add_result_view_component(
result_view: *ResultView,
parent: vaxis.Window,
result: []const u8,
opts: vaxis.Window.ChildOptions,
allocator: std.mem.Allocator,
) !vaxis.Window {
const child = parent.child(opts);
log.debug("result: {s}, len: {d}\n", .{ result, result.len });
if (result.len == 0) return child;
var lines = std.mem.splitScalar(u8, result, '\n');
var row_offset: usize = 1;
var line_number: usize = 1;
while (lines.next()) |line| {
defer line_number += 1;
const line_number_str = try std.fmt.allocPrint(allocator, "{d}: ", .{line_number});
try result_view.line_numbers.append(allocator, line_number_str);
print_line_number(&child, line_number_str, row_offset);
row_offset += try print_line(
&child,
line,
row_offset,
);
}
return child;
}
even though i am sure i am not printing those line numbers in when there is no result
I usually clear every cycle and draw everything fresh
Clear the root, redraw everything
doesn t this clear the root ?
const win = app.vx.window();
win.clear();
Yeah
i think something wrong on my side if that s the case, thanks for the help !
It seems like there might be a desync happening somehow…like those characters are leftover due to an incorrect width calculation
What terminal, and are there possibly control bytes in what’s being printed?
im using wezterm ! and no i don t think so !
as it shouldn't go through that loop in the first place to print the line_numbers
you can see in the logs here
last render of result_view is called with a result that has len: 0
tried in iterm2 same thing, it will be just something dumb i have done, i ll keep you updated in case it is not !
Hmm, yes something odd is happening!
I'm currently rendering in the event loop, but sometimes I just want to signal a re-render from other sources without an input coming in. What is the best way to do that? I guess I need to separate the rendering and event loop? Is there a way to simply add a timer event as well?
Look at the terminal widget example for a timer based render. Otherwise you can send in any event you want from whatever source.
Ie a redraw event with no payload that gets posted from some thread on a timer
Ah I just add it to my own event union and somehow add it to the event loop?
ah and that is thread safe?
Yes
neat
@obsidian rover How do I get colored undercurls? I set cell.style.ul to an rgb color and cell.style.ul_style to .curly, but nothing happens. The undercurls are always in the cell.style.fg color. I tried it on a bunch of terminals
Doesn't seem to make any difference. Does ul apply to any understyling? Or just underline?
@obsidian rover I pushed it to flow anyway. Even though it doesn't seem to have an effect. So if you want something to test style.ul you can use flow.
It should apply to any understyle
Works for me?
Is there a reason in particular for why you chose the MIT license for this project?
I wanted a permissive license but didn't want any user of the library to be forced to GPL
There are licenses like the GPL that don't force share-alike tho?
Is there a problem with MIT though?
I'm just intrigued by why people use it so much, even in projects where community engagement is supposed to prime over proprietary adoption.
It doesn't make sense to me that people just use it as a go-to license even when the project is community-oriented
I do this for fun - the more people that use the library the more fun it is for me
Do you care mostly about individuals or companies using it? :v
Makes no difference to me
TBH if tigerbeetle made some TUI app with this I would be honored
I mean I sort of get that
But don't you think that if you provide a free, open-source work to someone, they should give you the favor back?
Not always, no. There are some things I do that I don't care and some that I do
For example - This is an investment in the terminal ecosystem. If someone can make a business off it somehow I'll still be pretty happy because I like to see the ecosystem move in a certain direction
providing for free, but when a lot businesses will use it, some features/bugs will have to be dealt with, meaning it will have to be maintained more frequently, meaning someone will have to get paid to do so
is there a way to higlight a part of a word or the whole line like on the sidebar ?
Yeah there are a few ways to accomplish this
One way is to handle it via Segments:
window.print(&.{
.{.text = "re"},
.{.text = "main",
.style = .{.reverse = true}},
.{.text = "ing"},
});
You can do reverse, or set a bg color
Another is to do something like
var cell = window.readCell(row, col);
cell.style.bg = true;
window.writeCell(row, col, cell);
i can do this will writing that specific cell as well right ?
Yeah
this is neat
thanks !
what terminal is that?
Ghostty
I tried both rgb and indexed underline colors and they both worked
Still uncovered a vaxis bug though!
is this a working lsp in flow 👀
working good
Yes it is. Flow has had LSP based goto definition and diagnostics support for a while now.
OHH i didn t know that the last time i tried it, neat !
im still facing the same issue of back then where i am having some already rendered cells not cleared after calling win.clear
allocated line numbers as a str
--------------------
len: 11
4:
7:
8:
10:
18:
20:
21:
22:
28:
29:
32:
--------------------
len: 6
4:
93:
155:
160:
163:
201:
changing it from using a Cell to a Segment fixed it realized that i was using Cell in the wrong way i was passing []const u8 that was represented in more than one col which i guess Cell has to represent only one column ? which is the reason why it clears only one column in the screenshots above
Yep, the value in cell should only be for one column
What are good examples of how to use widgets?
Ah I found your comlink repo, that looks good 👍
I was going to say that the examples directory has examples for each widget
But comlink uses a few as well
I was trying to understand TextView, or basically any scrolling text widget with buffer. Didn't see an example for that sadly
Check out @still hare ‘s spurdo editor
Which Zig does it build with? 0.13 doesn't work for me
Oh I’m not sure for spurdo. Libvaxis is pinned to 0.13 though
this looks great! i'm going to try it out for my next project :)
i'm loosely following the text input example, and i'm wondering what i'm doing wrong ... when i add this bottom line with the border, the program crashes with an integer overflow
after some poking around, i found out the reason for the crash: for some reason the parent window (which i got from doing vx.window()) has a width and a height of 0
i guess it seems like Screen never gets initialized with width and height values other than 0 ?
idk ;w;
Are you initing a Loop?
When you receive a winsize event you have to pass it in to vx.resize. The resize function is what sets the screen size
It’s handled that way to prevent a lock on the screen which would be pretty expensive with how often it would need locking
oh oops, i forgot to add the winsize event to my loop
thats probably the issue, i test later
is it normal for the program to crash if you don t call win.clear on each event iteration ? (win coming from vx.window() where vx is instance of Vaxis), want to see if there is any way to rerender only a child instead of the whole window
No, that shouldn’t be happening. Do you have a stack trace?
This library looks amazing. I think it would do wonders for the zig ecosystem to get some of this kind of zig tui work in front of a broader set of eyes with a site like https://terminaltrove.com/
I think zig is not yet registering with the wider cli community but I think it 100% should be
I agree! I think flow is extremly polished and should definitely be put up on terminaltrove - as possibly the first zig project on there. @fleet schooner
yeah it turned out the way i was using vx.window() was wrong, is there any example that shows how to render individual child windows without rerendereing the whole thing ?
No, I think all of the examples operate in immediate mode
but it s possible right ?
Yeah - it should work just about the same
Do you have some code I could take a look at?
example on when the window have to resize all components should rerender
.winsize => |ws| {
try app.vx.resize(app.allocator, app.tty.anyWriter(), ws);
win = app.vx.window();
win.clear();
header_view_component.update_win_component(&win, .{
.width = .{ .limit = win.width },
.height = .{ .limit = win.height * 3 / 100 },
});
sidebar_view_component.update_win_component(&win, .{
.width = .{ .limit = @min(50, win.width * 30 / 100) },
.height = .{ .limit = win.height - header_view.win.height },
});
search_input_view_component.update_win_component(&sidebar_view.win, .{
.width = .{ .limit = sidebar_view.win.width * 98 / 100 },
.height = .{ .limit = 3 },
});
result_view_component.update_win_component(&win, .{
.x_off = sidebar_view.win.width + 1,
.y_off = header_view.win.height,
});
selected_path_view_component.update_win_component(&win, .{
.y_off = 9,
});
// queue renders
try app.render_queue.writeItem(&header_view_component);
try app.render_queue.writeItem(&sidebar_view_component);
try app.render_queue.writeItem(&search_input_view_component);
try app.render_queue.writeItem(&result_view_component);
try app.render_queue.writeItem(&selected_path_view_component);
win.writeCell(sidebar_view.win.width - 1, header_view.win.height - 1, intersection);
},```
and at the end of each iteration i rerender the components that need to rerender
...
while (app.render_queue.readItem()) |component| {
try component.render_component();
}
// Render the screen
try app.vx.render(app.tty.anyWriter());
Component type:
pub const Component = struct {
ptr: *anyopaque,
render: *const fn (ptr: *anyopaque) anyerror!void,
update_win: *const fn (ptr: *anyopaque, parent: *const vaxis.Window, opts: vaxis.Window.ChildOptions) void,
pub fn render_component(component: *Component) !void {
return component.render(component.ptr);
}
pub fn update_win_component(component: *Component, parent: *const vaxis.Window, opts: vaxis.Window.ChildOptions) void {
return component.update_win(component.ptr, parent, opts);
}
};```
update_win_component is ran when the window is resized
.winsize => |ws| {
try app.vx.resize(app.allocator, app.tty.anyWriter(), ws);
win = app.vx.window();
win.clear();
header_view_component.update_win_component(&win, .{
.width = .{ .limit = win.width },
.height = .{ .limit = win.height * 3 / 100 },
});
Does the component retain the reference to the window?
The window goes out of scope after .winsize block
oh yeah that s true is the reason why it segfautls, but the thing is, win width and height is set to 0 unless you get a new instance of Window from vx.window after calling vx.resize
The windows hold a reference to the underlying screen, which is replaced when you call resize
pub fn resize(
self: *Vaxis,
alloc: std.mem.Allocator,
tty: AnyWriter,
winsize: Winsize,
) !void {
log.debug("resizing screen: width={d} height={d}", .{ winsize.cols, winsize.rows });
self.screen.deinit(alloc);
self.screen = try Screen.init(alloc, winsize, &self.unicode);
So after resize, any previous window is no longer valid
You could reimplement Window in your application though, and instead of it holding a reference to *Screen, it could hold a reference to *Vaxis and then access vaxis.screen and always have an available screen
Window is really just a helper to make it easier to limit regions of printing to the Screen - nothing about it is internal to Vaxis. You could just copy the file and remove the stuff you aren't using
i should try that
i ll keep you updated !
Once I finally rewrite zf (fuzzy finder) to use vaxis I’m considering submitting it to terminal trove… maybe 😅
Oooh didn't know you were considering that!
Although my current code works, it’s getting messy to add new features. I’ve been following vaxis from the day you first shared it and I started rewriting the UI of zf last week to use vaxis. It’s probably going to be a while before it’s done because I don’t have much free time these days
But I should say, thanks for libvaxis! It’s been really nice to use so far
And thank you for zf, ziglua, great blog 🙂
the gap_buffer in build.zig.zon probably should be changed to point to a commit rather than the main branch
Yeah. I was thinking about vendoring that one in and reducing it to the bare minimum that vaxis uses
Sounds like Kovid is on board for implementing in Kitty
Kitty has implemented!
That was fast
@proud blade When you are working on your zf rewrite, it would be cool if zf could be made easily into a widget. Basically have a couple easy ways to pass it events and draw to a window. Nearly everything I make has some usecase for a fuzzy finder
I was working on zf this morning actually. This sounds like a great idea to me
Would that be weird for vaxis and zf to depend on each other? 
Haha - I was actually thinking of it as a third party widget...import zf and use whatever struct you provide as a widget
There are already a few applications using both libvaxis and zf
That makes a lot more sense! I'll get the rewrite finished and then see about making it a widget
Oh wow, that prompted me to do a search and I didn't realize how many people were using zf... feeling increased pressure to keep things working 😅
Happy to help out with the effort, too!
Thanks!
I actually do have a question on the zf vaxis rewrite. I don't want to use the alternate screen and instead draw the UI under the terminal prompt (like zf currently does). I'm not sure how I would best approach this though...
I'm just not finding a way to determine where the cursor is at the beginning so I can do all of my printing relative to that. Any thoughts on this?
If you don't enter the alternate screen, it will print on the primary screen and all drawing is done with relative cursor motions instead of absolute motions like the alt screen
It's safe to call clear, but the screen will scroll to the furthest row printed that contains a non-default cell
My guess is you'll grab a vx.window(), clear it,, then create a child the exact size you want to limit all printing to ... win.child(.{.height = .{.limit = <h>}}). Print anything to this, then render
I haven't tried making a child window yet... thanks!
Right now the relative printing works until I resize the terminal... then everything becomes relative to the top left of the terminal window, will a child window fix that?
Hmm it didn't...
That's a good question
Currently on resize, we go to column 0 ('\r') then RI up the number of rows from where our cursor ended
This might conflict with what the shell prompt does
Hmm I just tried in bash and zsh where I don't have any fancy prompt stuff setup and I still see the issue where resizing changes the relative drawing origin
Yeah, I have some work to do on this part it looks like.
🙏 thank you. Lmk if I can help with anything
I won't be able to fix today, but I think it shouldn't be too bad. I need to add some linefeeds to prevent reflow from messing everything up too much and then change some logic in vx.resize
No rush, I've got plenty of other projects to keep myself busy in the meantime
Tagged a new release since we are at 0 issues and 0 PRs - thanks everyone for all your contributions and feedback! https://github.com/rockorager/libvaxis/releases/tag/v0.4.0
I forgot to include a fix for @proud blade's resize-on-the-primary-screen issue. I've (mostly*) fixed that and tagged 0.4.1!
@proud blade It should work much better now. there is still some issues if you have a border. I can fix this by disabling autowrap in the terminal, but I'm not sure yet if I want to do that because it's pretty unexpected - since libvaxis requires library users to have a tty they can write to anyways people could do this on their own if it is absolutely needed
But I did a little demo of what i imagine zf will look like and the resize worked great now
@obsidian rover thank you! I just tested really quick and it seems like everything works
@obsidian rover is it possible to use the same zg as libvaxis? without adding it to my zon file
Not currently - I will rexport it though
is there perhaps a build system way to access sub dependencies of a dependency?
Not that I know of :/
Since zg is broken up into modules, I will only export the modules vaxis loads (grapheme, code_point, and DisplayWidth)
Hmm
I think another way is that I can export the module from build.zig
I guess I could just read the import_table field of the vaxis Module
Oh yeah, that shoudl do it
hmm, libvaxis doesn't actually use the module I need though. I guess I'll leave it as a local dependency then.
Which module are you using?
CaseData
idk if this is meant to be officially supported should be able to do something like b.dependency("libvaxis", .{}).builder.dependency("zg", .{})
Perfect! That's exactly what I want, thanks. This prevents a duplicate download of zg, which is really slow. If it breaks in future I can always just add the local dep back, so I don't mind if it's not "official".
hey, any chance this project just works with master? I saw that the target version is 0.13
It doesn't work on master right now
aight, thanks for the quick reply
Are we going to get OSC 176 (set application ID) support in libvaxis too?
I am for adding it. AFAIK, foot is the only terminal supporting it but it's a noop everywhere else
I think that paste_allocator will need to be renamed allocator, and the library will hold onto the first value it receives from a query so it can restore it prior to exiting
makes sense
common foot win
@obsidian rover I would have PRed a fix instead of opening #65, but I didn't have time and I didn't want to forget about it.
How do I post my own event in the loop, postEvent?
Yes, you should be able to use postEvent just like libvaxis does.
Yeah I was putting a try before it and got the weirdest error message, but now it works!
@obsidian rover Have you ever tried flow on windows? It looks pretty good, but there is a weird issue where cells are randomly rendered at half brightness. Have you seen this before?
It seems to be differential update related. Full renders are usually fine.
It’s been awhile since I tried it on windows last. I wonder if there is some style reset not implemented by WT or maybe swallowed by conpty. I’ll check it out tomorrow
Is that whole line supposed to be red background?
Oh I see, it’s the imports too, hmm
no, I have cursor line highlight off in this screenshot
its the imports
It looks like I can query the default terminal background with queryColor(..., .bg), but not set it. I'd like to set it and have it reset automatically in Vasix.resetState. Shall I PR this? (I could also just do it in Flow directly)
I would accept that PR!
@fleet schooner Have you seen XTPOPCOLORS / XTPUSHCOLORS? I'm thinking of implementing that at some point
kitty, xterm, and just recently foot added it
Yeah, I saw it. I don't have a use case for it in flow though.
How compliant is libvaxis with the X/Open Curses standard of the Single Unix Specification?
I haven't heard of this - do you have a link?
The SUS is like POSIX but with the X/Open Curses added for user interfaces
To be fair I also struggle to find copies of it :/
oh wow that was quick
skill issue
But this is a very prescriptive specification regarding memory, function names, etc
And libvaxis does not comply with that
want it to stay that way?
Without knowing the benefit of compliance, yeah...it would be pretty simple to write a compliance wrapper it seems
But I don't know what compliance gains
Hey, I’ve been lurking here for a while and following Vaxis development for a while. It looks like a fantastic library!
I’ve got a similar library for c++ code (although less battle tested and well rounded) and I’m trying to develop a way of managing splits across the terminal, something like how vim splits or tmux works. I wanted to ask how Vaxis handles splits or panes or windows within a terminal window. What approach did you take here?
Vaxis uses the concept of "Windows". So when you draw a widget, you are bound to only draw within that window. If you had two panes of text, you'd create two child windows off the root window...each full height and half width. When the text widgets draw, they are bound to their window and the internal model manages all of the state of where each character is
Is there an example on the repo that shows what the interface is like here? How does the window link to the text it’s meant to draw? Does it take a (might use CPP terminology here) pointer to a vector of strings and just draw that?
The two main datastructures you'll want to look at are src/Screen.zig and src/InternalScreen.zig. Both of these have a "cell" structure for each cell on the terminal window. When drawing, a pointer to the grapheme for that particular cell is stored in Screen, the pointer must live until render is called. Before rendering, Any widget can overwrite any particular cell's value (this makes for efficient / easy drawing and clearing of areas). Once the drawing is complete, the Screen represents the state we want the terminal to look like so we call render. InternalScreen is the state of our last frame that we drew. So render compares each cell for equality (grapheme, style, urls, etc). If the cell is different, it updates the terminal. the state of the cell is copied into the InternalScreen structure. The copy is so that you can compare frame-to-frame
That’s pretty clever. I guess you can then use the difference between the two for unit testing as well?
unit what? 😂
I’ll take more of a look later on - I was skimming through src/Window.zig because I thought to start there before, but thanks for pointing me in the right direction
I don't have much testing in the diff code.
Hey @obsidian rover, is there anyway to have the image loading done in the background? that way instead of freezing, users can still interact with the application. I know Zig doesn't have async so would I need to spawn a thread in the background to do this?
There are a few new apis for images. The best performing would be to use transmitLocalImagepath. This will be much faster, but doesn't work over ssh (and currently silently fails). Next would be to spawn a thread which loads the image, converts the pixels to an allowable format, and base 64 encodes the image. You can then call transmitPreEncodedImage from the main thread which will chunk up the data and send it. This should be quite a bit faster than current, but you should always send on the main thread since the tty writer is not thread safe
For the thread spawning one, you can look at the code in transmitImage - basically do that function in a thread, but then call transmitPreEncodedImage in the main thread
oh awesome! I'll make sure to update libvaxis then. thanks
The next option is to do all the sending yourself - you could chunk, send a chunk, check for input events, then repeat loop and send another chunk.
right, that's also possible
thanks, i'll mess around with these options and see what works best :)
by "doesn't work over ssh", will it crash/error or simply not load? If I can catch on error, then I can just display an error over ssh
Not load, but you won’t get an error. The terminal sends an error as an escape code by libvaxis doesn’t check or parse these currently
It’d be delivered async
hmm, okay. i'll investigate into these options then. thanks man :)
No problem!
@obsidian rover it's been a while, but I'm making slow and steady progress with rewriting zf to use libvaxis! Got a nice preview pane working today. It is much easier to add new features now. Thanks for vaxis!
Looks awesome!
Hey @obsidian rover, is there a way to force a flush in instances like this where I need to free memory after writing?
You should be able to just call render…or am I missing something?
oh... I think i'm the stupid one lol
i'm calling render after the memory is freed
😭 sorry man, I need a rubber duck or something aye
No worries! Happy hacking
I'm doing some gardening in libvaxis. Lots of breaking changes are currently on the unreleased HEAD, but it will all be worth it 🙂
One of the major changes coming is the introduction of a TUI framework. The framework (named vxfw..."Vaxis framework") is by no means mandatory, and is basically just built on top of the Vaxis core that is out there today. There is a draft PR up https://github.com/rockorager/libvaxis/pull/105. Would appreciate any commentary if people have thoughts! Overall the idea is for vxfw to be the high level side of libvaxis. For those familiar with Rust, imagine vxfw as Ratatui and libvaxis as crossterm. Or in Go...vxfw is tview and libvaxis is tcell. Or something to that effect.
As a quick example, here is a very basic fuzzy finder implemented in 235 LOC with full unicode and full mouse support. This one uses Text, RichText, ListView, and TextField.
I was a big fan of Ratatui and used it back in my Rust days. Very much looking forward to this :)
https://github.com/rockorager/libvaxis/pull/110
I made a PR for some unchecked errors I was running into
So meaning we will be able to provide our own backends? eg: rendering TUI in web browser.
Hey @obsidian rover, do you know of any libraries that render md content that work with libvaxis?
No, I'm not aware of any
Would be a good project though 😉
haha, totally.
do you know in terms of the image library if there's a way to see what formats are currently supported? e.g. for my file explorer, i would love to know if the file i'm about to pass to the library is even an image type that it supports
currently i'm just passing it any file the user selects and seeing if it spits an error or not lol, not ideal
like, i can manually check the libraries readme and hardcode these,,, but ideally there would be something that updates automatically for this
I’m not sure - I’d have to look through their docs, my guess would be to do what you are doing … zigimg has pretty good errors so you could probably do something by switching on the specific error you get
I think that table is out of date
JPEG is definitely supported
only problem is sometimes things just die...
for example, this error occurs when you attempt to pass a .ico file into the function
Hmm, that one seems like a zigimg bug, not an issue with unsupported formats
jpg also seems unsupported?
hmm, okay. i'll create an issue on their end then
I wonder if it has to jpeg https://github.com/zigimg/zigimg/blob/master/src/formats/jpeg.zig
I can do something like so which seems to fix a lot of the issues when passing in dud files
var match = false;
inline for (@typeInfo(vaxis.zigimg.Image.Format).Enum.fields) |field| {
const entry_ext = std.mem.trimLeft(u8, std.fs.path.extension(entry.name), ".");
if (std.mem.eql(u8, entry_ext, field.name)) {
match = true;
}
}
if (!match) break :unsupported;
still unsure as to why jpg isn't working.. i'll try look into it and pass through a jpeg image and see if that helps
Hey @obsidian rover, I was wondering if you had any further thoughts on issue https://github.com/rockorager/libvaxis/issues/79 in regards to sixel images and the latest two comments
- https://github.com/rockorager/libvaxis/issues/79#issuecomment-2438616652 : sixel performance
- https://github.com/rockorager/libvaxis/issues/79#issuecomment-2496185509 : supporting chafa instead
I'm actually eyeing up this zig sixel implementation: https://github.com/daredemo/zixel
that would be awesome, looks promising. excited to follow the development :)
Hey everyone! So I've decided to use Github Discussions as a more general purpose support / Q&A place. I'll still respond to questions here but just wanted people to know that there is that available as well (and is a bit easier to follow than a giant discord thread).
Also there is a Show and Tell section, please post whatever you have built or are building with libvaxis there, it's always fun to see what people are working on!
Are there any examples and screenshots of what can be built using this library? Really interested in taking a further look.
I'm mostly interested in building some kind of basic dashboard with separate table sections
Thank you
bit late, but you can also take a look at fancy-cat
pinging here in case you didn't see my question in the Github Discussions tab (my apologies if u did)
Oh I saw it and forgot about it! Ill respond on github
No worries! Thank you
Hey @obsidian rover, does Libvaxis support all of these image formats? https://github.com/zigimg/zigimg?tab=readme-ov-file#supported-image-formats
I noticed GIF is technically supported but won't actually render. Is there a way to know which image types will actually be rendered to the terminal?
e.g. I'm using this to do a simple check to see if zig-img supports the image but this doesn't seem to mean libvaxis supports the rendering
var match = false;
inline for (@typeInfo(vaxis.zigimg.Image.Format).@"enum".fields) |field| {
const entry_ext = std.mem.trimLeft(
u8,
std.fs.path.extension(entry.name),
".",
);
if (std.mem.eql(u8, entry_ext, field.name)) {
match = true;
}
}
if (!match) break :unsupported;
I’ll have to look at how I’m using it. I think it’s using zigimg to decode and then encoding it as a png to upload to the terminal - so I would expect them all to be supported. I’ll take a look tomorrow
sounds good, thanks for that :)
here's where i'm using it if it helps you debug in any way https://github.com/brookjeynes/jido/blob/main/src/drawer.zig#L143
It should be working with anything supported by zigimg. When you load the file, zigimg decodes the image to pixels - the vaxis transmit function will then convert those to either png, rgb, or rgba.
I'm not sure exactly how this interacts with gifs because those are a sort of container of separate frames
Maybe we only get the first frame of a gif?
getting the first frame of a gif would be fine. it just seems that it wouldn't load the image at all but maybe it's my usage of the library
just did some further testing, seems gifs are working correctly with Kitty. I failed to realise my friend who reported the issue was using Iterm. sorry about that
For allocs during draw I have an arena allocator i init before I call draw and I deinit after it is rendered. What is the standard way of doing this?
Do not deinit, use the reset function with retain_capacity
You want to reuse the same arena between draws
Cool, thanks
@obsidian rover does the mouse scroll event listner work ? couldn't find any example. as well
Yep, if you enable the mouse you’ll get mouse wheel events.
do you have any example code there. I couldn't find any documentation or anything for it so looking at example code is all what i can do.
I don't see an explicit example...here is how you can handle these events (they are delivered as a .mouse event)
To enable mouse, you to call vx.setMouseMode(writer, true)
Hello !
I have a background thread working, and I'd like to display data it generates.
With the "low level API" it's easily done, as I have access the the loop and its postEventthread-safe function.
Yesterday, I played around using the vxfwhigh-level API. While its usage is simpler, and gives access to more widgets, it seems it has no capability to inject user events.
Yes, I can eventually update the model from the thread with atomics, and continously redraw instead of waiting for events, but still, I'm wondering if I missed something.
tl;dr -> Does vxfwallows user events in its loop ? If yes, how ?
I don't have a way to plumb that through nicely yet. I've held off because I haven't come up with a nice design for it. I am trying to keep the backend app runner completely decoupled from the widgets (IE a widget should never depend on what the event loop is)
So...in some way we need to asynchronously wake up the loop with a posted event, or maybe a wakeup that the app has events available (in it's own queue, for example).
I get around this in comlink currently by having a timer callback and I check for events. Not event based, which is icky
Hi,
Thank you very much for your detailed reply.
No worries, I understand the situation and I’ve already a workaround (continuously redrawing)
Thanks again !
If you have any ideas on how you would like the API to look for injecting custom events or waking the loop, I am all ears!
Sure ! I think there is a quick easy fix first: the possibility to simply ask for a redrawing.
So a thread update some atomics and inform the app to trigger a redraw.
It’s not as clean as user events but get the job done without constantly redrawing.
For instance, rust cursive has a noop event
I admit it’s not clean nor sustainable on big projects
Yeah I was basically thinking of a noop event / wakeup to tell the app to wakeup and deliver a noop to some widget
For that route, I think it would be delivered to the widgets as an object on the EventContext:
pub const Wakeup = struct {
ptr: ?*anyopaque,
wakeupFn: *const fn (?*anyopaque, Widget) void,
pub fn wakeup(self: Wakeup, widget: Widget) {
self.wakeupFn(self.ptr, widget);
}
}
Something like that
Where ptr is the event loop implementation
All the pointers would be stable, so this object could be held onto by the application to call from whatever thread it needs
Also, I know we don’t have access to the loop, and it’s on purpose. But maybe we can try to find a way to configure the loop Event enum at comptime and expose a postEvent public function ?
But that will make things more complex for only a special case (external threads)
Another approach: do nothing and wait for async back so we can select()
The problem with this is plumbing that event enum through to every widget. And ideally widgets will not all be within the libvaxis repo, but people can make their own widget libraries as well
Oh ok I understand. That was my main approach, but I didn't really understand the context.
Regarding my last sentence, I know it's confusing, but maybe it's too early to solve this. Workarounds exist, and async could solve this problem (for instance, Rust Ratatui can use Tokyo's select!)
Hey @obsidian rover, am i misunderstanding how the postEvent function works?
I have some code which spawns up a thread to do some background work, then posts an event if all goes well
{
const load_img_thread = std.Thread.spawn(.{}, loadImage, .{
app,
self.current_item_path,
}) catch break :unsupported;
load_img_thread.detach();
}
fn loadImage(
app: *App,
path: []const u8,
) error{ Unsupported, OutOfMemory }!void {
var image = vaxis.zigimg.Image.fromFilePath(
app.alloc,
path,
) catch {
return error.Unsupported;
};
defer image.deinit();
if (app.vx.transmitImage(app.alloc, app.tty.anyWriter(), &image, .rgba)) |img| {
app.image.data = img;
if (app.image.path) |p| app.alloc.free(p);
app.image.path = try app.alloc.dupe(u8, path);
app.loop.postEvent(.{ .image_ready = app.image.path orelse "test" });
} else |_| {
if (app.image.data) |img| {
app.vx.freeImage(app.tty.anyWriter(), img.id);
}
app.image = .{};
return error.Unsupported;
}
}
This is what my polling looks like
self.loop.pollEvent();
while (self.loop.tryEvent()) |event| {
switch (event) {
.key_press => {
// ...
},
.image_ready => {
std.debug.print("\nooooopppppps\n", .{});
},
else => {},
}
}
The problem is, it seems that i have to key press a couple times for the event to be posted, i.e. the image to be shown
EDIT: disregard, this is working correctly. I simply had an incorrect if statement surrounding the logic
Hey @obsidian rover, any clue why this is breaking images on subsequent loads from "cache" (hashmap)?
https://github.com/BrookJeynes/jido/compare/main...bj/2025-05-23/feat/cache-images
what’s the panic that’s happening? My guess is there is some index reuse…vaxis will assign an id and maybe those are getting corrupted?
Here's the log
terminal messy but here's another log from what i presume to be the same panic (did the same actions as above)
Doesn't seem obvious, do you have that in a branch I can test locally?
This is the panic I am getting...invalid free
I think what is happening is that you are caching the data of the file. On first transmitImage, vaxis calls zigimg.convert to get it to the right pixel format. zigimg frees the old pixel buffer. Then, the next round you go to transmit and it's bogus data and an invalid free when you try to convert it
What I woudl do to make it all better - you don't need to cache the image data, the terminal does that for you. Once you have transmitted an image once you can show it by referencing it's ID (which in vaxis terms, just means draw it)
So on first load of image - transmit it and cache the vaxis.Image object (which is just an id, width, and height)
Subsequent draws will be very fast since they won't do any conversion or transmission
when does the terminal clear this "cached image"
is something like this to do "safe" with the terminal caching the image data (the data i fetch via the id). this seems to be working fine from my testing however
https://github.com/BrookJeynes/jido/compare/main...bj/2025-05-23/feat/cache-images#diff-face6d58eec5a8a75042e86c55da013110761229f0824c7fdd2156e1f63e2012R203
Either if you explicitly tell it to (this would be image.destroy in vaxis) or I believe most do a LRU cache for them
Within your app it should be safe
There is a query to see if an id is still in mem (I haven’t implemented it)
right, makes sense. I've handled all possible errors i can on my end so i'll jsut hope its enough :) thanks for the help
Hey @obsidian rover, do you know why when running something like cd $(jido), the alt screens seem to break completely?
https://github.com/brookjeynes/jido/blob/main/src/events.zig#L421 here i'm handling opening up $EDITOR on a file, and jumping back when that editor closes.
doing something like this in cd $(jido) seems to just present me with a blank screen that still accepts input but doesn't show anything
I'm not sure I get it. I tried locally and it seems to behave the same if I do cd $(jido) or just jido
Your gif looks like a blank screen momentarlily and then it loads the dir?
ah sorry, maybe the gif wasn't obvious with what was happening
when i attempt to open a file, which enters altscreen and spawns $EDITOR (in my instance neovim), the screen remains blank but still accepts keyboard input
this only occurs when running jido in something like cd $(jido)
i'm wondering if this is something i'm handling incorrect with my alt screen usage or if this is something on libvaxis end
@obsidian rover here is what openFile is doing after the alt screen is left https://github.com/BrookJeynes/jido/blob/bedb34417b1bc96ad5c0767ef822c0f305b2f778/src/environment.zig#L51
So you did a suspend and then call that?
That's the flow there
I wonder if somehow the subshell is messing with it
Hmm, interesting. Is this something I can fix on my end or a libvaxis implementation
I'm not sure - what editor are you using?
neovim, were you able to repro?
Yeah I can repro
Hm
EDITOR=flow works fine
EDITOR=helix doesn't, neither does nvim
Which makes me think its a subshell thing? I know for sure that flow opens /dev/tty...helix and nvim might be checking stdin/stdout/stderr and the subshell messes with it? I don't think that should be happening though
I'll keep thinking about it. On the mend from covid and a bit brain foggy today
that's okay, take your time! I hope you recover well and quickly ~
thanks for helping out on this one
how have you been feeling lately? hope you're recovering well. just wondering if there were any further developments into this?
Hi, is there any up-to-date tutorial or examples for using libvaxis with Zig 0.14.1 in order to get started with the library? The libvaxis-starter repository example seems to be broken with the latest libvaxis master version (I guess tagged releases don't work with Zig 0.14.1 as they are ancient?)
updated the libvaxis starter for zig version 0.15.1, hopefully it helps
https://github.com/rockorager/libvaxis/pull/260
Started adding tmux support for kitty images, would appreciate help from someone more familiar with kitty unicode placement
This PR adds basic tmux support for rendering images with the Kitty graphics protocol.
Passthrough is working
Positioning is not
Proper positioning requires using Unicode placeholders:
https://sw...
Hey @obsidian rover, is rendering libvaxis via a buffered writer no longer an option in 0.15.0?
It is, it accepts the new std.io.Writer which has buffering built in
oh neat, so no need to do this
var buffered = self.tty.bufferedWriter();
try self.vx.render(buffered.writer().any());
try buffered.flush();
just this now?
try vx.render(tty.writer());
hmm,, I seem to be getting some not so great errors when trying to render an image after upgrading to 0.15.1. any help would be appreciated 🙏
https://github.com/brookjeynes/jido/blob/0b1d69cded56dc2677993d9b62461650b6de4fc8/src/drawer.zig#L650
You can run your program as your_program 2>your_program.err to write the errors into a file so it's more readable and easier to debug.
So true 🤦♂️ I'll do that when I get to my laptop next.
here you go, hope that's a bit easier to see
i was working on a TUI library as well https://github.com/adxdits/zigtui
Hey @obsidian rover, is there a reason we now need a buffer when reading image from path? Is there a way for this to be allocated instead of a hard-coded buffer size like in the example? I am currently caching loaded images for quick access later but I'm now needing to keep a new buffer per image alive?
fn loadImage(app: *App, path: []const u8, buffer: *[1024]u8) error{ Unsupported, OutOfMemory }!void {
_ = buffer;
var buf: [1024 * 1024]u8 = undefined;
const data = vaxis.zigimg.Image.fromFilePath(app.alloc, path, &buf) catch {
const message = try std.fmt.allocPrint(app.alloc, "Failed to load image '{s}' - error occurred while attempting to read image from path.", .{path});
defer app.alloc.free(message);
app.notification.write(message, .err) catch {};
if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {};
return error.Unsupported;
};
app.images.mutex.lock();
if (app.images.cache.getPtr(path)) |entry| {
entry.status = .ready;
entry.data = data;
} else {
const message = try std.fmt.allocPrint(app.alloc, "Failed to load image '{s}' - error occurred while attempting to add image to cache.", .{path});
defer app.alloc.free(message);
app.notification.write(message, .err) catch {};
if (app.file_logger) |file_logger| file_logger.write(message, .err) catch {};
return error.Unsupported;
}
app.images.mutex.unlock();
app.loop.postEvent(.image_ready);
}
I believe there to be an issue with the deallocation of images, im unsure what i'm doing wrong unfortunately - https://github.com/brookjeynes/jido/blob/0b1d69cded56dc2677993d9b62461650b6de4fc8/src/app.zig#L95
Segmentation fault at address 0x7f4c8e8f1010
/home/bjeyn/.zvm/0.15.1/lib/std/mem/Allocator.zig:428:26: 0x13d7168 in free__anon_58795 (std.zig)
@memset(non_const_ptr[0..bytes_len], undefined);
^
/home/bjeyn/.cache/zig/p/zigimg-0.1.0-8_eo2vUZFgAAtN1c6dAO5DdqL0d4cEWHtn6iR5ucZJti/src/color.zig:1030:45: 0x13663d1 in deinit (zigimg.zig)
.rgba32 => |data| allocator.free(data),
^
/home/bjeyn/.cache/zig/p/zigimg-0.1.0-8_eo2vUZFgAAtN1c6dAO5DdqL0d4cEWHtn6iR5ucZJti/src/Image.zig:137:23: 0x12e1ec3 in deinit (zigimg.zig)
self.pixels.deinit(allocator);
^
/home/bjeyn/projects/jido/src/app.zig:103:21: 0x127b557 in deinit (main.zig)
d.deinit(alloc);
^
/home/bjeyn/projects/jido/src/app.zig:207:29: 0x1226561 in deinit (main.zig)
img.value_ptr.deinit(self.alloc, self.vx, &self.tty);
^
/home/bjeyn/projects/jido/src/main.zig:116:25: 0x12189bb in main (main.zig)
defer app.deinit();
^
/home/bjeyn/.zvm/0.15.1/lib/std/start.zig:627:37: 0x1219db9 in posixCallMainAndExit (std.zig)
const result = root.main() catch |err| {
^
/home/bjeyn/.zvm/0.15.1/lib/std/start.zig:232:5: 0x11c32f1 in _start (std.zig)
asm volatile (switch (native_arch) {
^
???:?:?: 0x0 in ??? (???)
Seems this was actually an issue from within Libvaxis - PR here https://github.com/rockorager/libvaxis/pull/293
Hey rockorager, in what instances would cursorLeft() and cursorRight() fail to work? Running the textinput example, left and right works but in the usage within my app it seems it fails to actually move the cursor left and right.
I'm a little confused as to why as i'm not doing anything particularly different to that example
