feat: add text editing
parent
dad4b23cf5
commit
e87f718f5d
|
@ -4,8 +4,32 @@ const xkbcommon = @import("xkbcommon");
|
||||||
const font8x8 = @cImport({
|
const font8x8 = @cImport({
|
||||||
@cInclude("font8x8.h");
|
@cInclude("font8x8.h");
|
||||||
});
|
});
|
||||||
|
const PieceTable = @import("PieceTable.zig").PieceTable;
|
||||||
|
|
||||||
const Pixel = [4]u8;
|
const Pixel = [4]u8;
|
||||||
|
const Theme = struct {
|
||||||
|
const background = 0x282A36;
|
||||||
|
const current_line = 0x44475A;
|
||||||
|
const foreground = 0xF8F8F2;
|
||||||
|
const comment = 0x6272A4;
|
||||||
|
|
||||||
|
const cyan = 0x8BE9FD;
|
||||||
|
const green = 0x50FA7B;
|
||||||
|
const orange = 0xFFB86C;
|
||||||
|
const pink = 0xFF79C6;
|
||||||
|
const purple = 0xBD93F9;
|
||||||
|
const red = 0xFF5555;
|
||||||
|
const yellow = 0xF1FA8C;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn toPixel(color: u24) Pixel {
|
||||||
|
return .{
|
||||||
|
@intCast(color & 0xFF),
|
||||||
|
@intCast(color >> 8 & 0xFF),
|
||||||
|
@intCast(color >> 16 & 0xFF),
|
||||||
|
0xFF,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
var general_allocator = std.heap.GeneralPurposeAllocator(.{}){};
|
var general_allocator = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
@ -27,6 +51,7 @@ pub fn main() !void {
|
||||||
wayland.xdg.WmBase,
|
wayland.xdg.WmBase,
|
||||||
wayland.core.Seat,
|
wayland.core.Seat,
|
||||||
wayland.zxdg.DecorationManagerV1,
|
wayland.zxdg.DecorationManagerV1,
|
||||||
|
wayland.zwp.TextInputManagerV3,
|
||||||
});
|
});
|
||||||
|
|
||||||
const DISPLAY_ID = 1;
|
const DISPLAY_ID = 1;
|
||||||
|
@ -34,6 +59,7 @@ pub fn main() !void {
|
||||||
const compositor_id = ids[1] orelse return error.NeccessaryWaylandExtensionMissing;
|
const compositor_id = ids[1] orelse return error.NeccessaryWaylandExtensionMissing;
|
||||||
const xdg_wm_base_id = ids[2] orelse return error.NeccessaryWaylandExtensionMissing;
|
const xdg_wm_base_id = ids[2] orelse return error.NeccessaryWaylandExtensionMissing;
|
||||||
const wl_seat_id = ids[3] orelse return error.NeccessaryWaylandExtensionMissing;
|
const wl_seat_id = ids[3] orelse return error.NeccessaryWaylandExtensionMissing;
|
||||||
|
const zwp_text_input_manager_v3 = ids[5] orelse return error.NeccessaryWaylandExtensionMissing;
|
||||||
|
|
||||||
const surface_id = id_pool.create();
|
const surface_id = id_pool.create();
|
||||||
try conn.send(
|
try conn.send(
|
||||||
|
@ -63,6 +89,16 @@ pub fn main() !void {
|
||||||
} },
|
} },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const zwp_text_input_v3_id = id_pool.create();
|
||||||
|
try conn.send(
|
||||||
|
wayland.zwp.TextInputManagerV3.Request,
|
||||||
|
zwp_text_input_manager_v3,
|
||||||
|
.{ .get_text_input = .{
|
||||||
|
.id = zwp_text_input_v3_id,
|
||||||
|
.seat = wl_seat_id,
|
||||||
|
} },
|
||||||
|
);
|
||||||
|
|
||||||
var zxdg_toplevel_decoration_id_opt: ?u32 = null;
|
var zxdg_toplevel_decoration_id_opt: ?u32 = null;
|
||||||
if (ids[4]) |zxdg_decoration_manager_id| {
|
if (ids[4]) |zxdg_decoration_manager_id| {
|
||||||
zxdg_toplevel_decoration_id_opt = id_pool.create();
|
zxdg_toplevel_decoration_id_opt = id_pool.create();
|
||||||
|
@ -265,6 +301,20 @@ pub fn main() !void {
|
||||||
xkb_state.unref();
|
xkb_state.unref();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var piece_table = try PieceTable.init(gpa, "Hello, World!");
|
||||||
|
defer piece_table.deinit();
|
||||||
|
|
||||||
|
var edit_buffer: [1024]u8 = [1]u8{0} ** 1024;
|
||||||
|
var edit_slice: ?[]u8 = null;
|
||||||
|
|
||||||
|
var delete_before: usize = 0;
|
||||||
|
var delete_after: usize = 0;
|
||||||
|
|
||||||
|
// var preedit_buffer: [1024]u8 = [1]u8{0} ** 1024;
|
||||||
|
// var preedit_slice: ?[]u8 = null;
|
||||||
|
|
||||||
|
var cursor_pos: usize = piece_table.getTotalSize();
|
||||||
|
|
||||||
var running = true;
|
var running = true;
|
||||||
while (running) {
|
while (running) {
|
||||||
const header, const body = try conn.recv();
|
const header, const body = try conn.recv();
|
||||||
|
@ -288,8 +338,10 @@ pub fn main() !void {
|
||||||
// put some interesting colors into the new_framebuffer
|
// put some interesting colors into the new_framebuffer
|
||||||
renderGradient(new_framebuffer, window_size);
|
renderGradient(new_framebuffer, window_size);
|
||||||
|
|
||||||
|
const text = try piece_table.writeAllAlloc();
|
||||||
|
defer gpa.free(text);
|
||||||
// blit some characters
|
// blit some characters
|
||||||
renderText(new_framebuffer, window_size, .{ 10, 10 }, "Hello, World!");
|
renderText(new_framebuffer, window_size, .{ 10, 10 }, text);
|
||||||
|
|
||||||
try conn.send(
|
try conn.send(
|
||||||
wayland.core.ShmPool.Request,
|
wayland.core.ShmPool.Request,
|
||||||
|
@ -325,12 +377,6 @@ pub fn main() !void {
|
||||||
} },
|
} },
|
||||||
);
|
);
|
||||||
|
|
||||||
try conn.send(
|
|
||||||
wayland.core.Surface.Request,
|
|
||||||
surface_id,
|
|
||||||
wayland.core.Surface.Request.commit,
|
|
||||||
);
|
|
||||||
|
|
||||||
// commit the configuration
|
// commit the configuration
|
||||||
try conn.send(
|
try conn.send(
|
||||||
wayland.core.Surface.Request,
|
wayland.core.Surface.Request,
|
||||||
|
@ -390,25 +436,180 @@ pub fn main() !void {
|
||||||
xkb_keymap_opt = xkbcommon.Keymap.newFromString(xkb_ctx, @ptrCast(mem), .text_v1, .no_flags) orelse return error.XKBKeymap;
|
xkb_keymap_opt = xkbcommon.Keymap.newFromString(xkb_ctx, @ptrCast(mem), .text_v1, .no_flags) orelse return error.XKBKeymap;
|
||||||
xkb_state_opt = xkbcommon.State.new(xkb_keymap_opt.?) orelse return error.XKBStateInit;
|
xkb_state_opt = xkbcommon.State.new(xkb_keymap_opt.?) orelse return error.XKBStateInit;
|
||||||
},
|
},
|
||||||
|
.modifiers => |mods| {
|
||||||
|
if (xkb_state_opt) |xkb_state| {
|
||||||
|
_ = xkb_state.updateMask(
|
||||||
|
mods.mods_depressed,
|
||||||
|
mods.mods_latched,
|
||||||
|
mods.mods_locked,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
.key => |key| {
|
.key => |key| {
|
||||||
if (xkb_state_opt) |xkb_state| {
|
if (xkb_state_opt) |xkb_state| {
|
||||||
const keycode: xkbcommon.Keycode = key.key + 8;
|
const keycode: xkbcommon.Keycode = key.key + 8;
|
||||||
const keysym: xkbcommon.Keysym = xkb_state.keyGetOneSym(keycode);
|
const keysym: xkbcommon.Keysym = xkb_state.keyGetOneSym(keycode);
|
||||||
var buf: [64]u8 = undefined;
|
var buf: [64]u8 = undefined;
|
||||||
const name_len = keysym.getName(&buf, buf.len);
|
// const name_len = keysym.getName(&buf, buf.len);
|
||||||
std.debug.print("{s}\n", .{buf[0..@intCast(name_len)]});
|
// std.debug.print("{s}\n", .{buf[0..@intCast(name_len)]});
|
||||||
|
|
||||||
const changed = if (key.state == .pressed)
|
if (key.state == .pressed) {
|
||||||
xkb_state.updateKey(keycode, .down)
|
const sym = xkbcommon.Keysym;
|
||||||
else
|
switch (@as(u32, @intFromEnum(keysym))) {
|
||||||
xkb_state.updateKey(keycode, .up);
|
sym.BackSpace => {
|
||||||
_ = changed;
|
try piece_table.delete(cursor_pos - 1, 1);
|
||||||
|
cursor_pos -= 1;
|
||||||
|
},
|
||||||
|
sym.Delete => {
|
||||||
|
piece_table.delete(cursor_pos, 1) catch |e| switch (e) {
|
||||||
|
error.OutOfBounds => {},
|
||||||
|
else => return e,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
sym.Left => {
|
||||||
|
cursor_pos -|= 1;
|
||||||
|
},
|
||||||
|
sym.Right => {
|
||||||
|
cursor_pos += 1;
|
||||||
|
cursor_pos = @min(cursor_pos, piece_table.getTotalSize());
|
||||||
|
},
|
||||||
|
else => if (key.state == .pressed) {
|
||||||
|
const size = xkb_state.keyGetUtf8(keycode, &buf);
|
||||||
|
try piece_table.insert(cursor_pos, buf[0..size]);
|
||||||
|
cursor_pos += size;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const new_buffer_id, const new_framebuffer = try getFramebuffer(&framebuffers, &id_pool, pool_alloc, window_size);
|
||||||
|
|
||||||
|
// put some interesting colors into the new_framebuffer
|
||||||
|
renderGradient(new_framebuffer, window_size);
|
||||||
|
|
||||||
|
const text = try piece_table.writeAllAlloc();
|
||||||
|
defer gpa.free(text);
|
||||||
|
// blit some characters
|
||||||
|
renderText(new_framebuffer, window_size, .{ 10, 10 }, text);
|
||||||
|
|
||||||
|
try conn.send(
|
||||||
|
wayland.core.ShmPool.Request,
|
||||||
|
wl_shm_pool_id,
|
||||||
|
.{ .create_buffer = .{
|
||||||
|
.new_id = new_buffer_id,
|
||||||
|
.offset = @intCast(@intFromPtr(new_framebuffer.ptr) - @intFromPtr(pool_bytes.ptr)),
|
||||||
|
.width = @intCast(window_size[0]),
|
||||||
|
.height = @intCast(window_size[1]),
|
||||||
|
.stride = @as(i32, @intCast(window_size[0])) * @sizeOf([4]u8),
|
||||||
|
.format = .argb8888,
|
||||||
|
} },
|
||||||
|
);
|
||||||
|
|
||||||
|
try conn.send(
|
||||||
|
wayland.core.Surface.Request,
|
||||||
|
surface_id,
|
||||||
|
.{ .attach = .{
|
||||||
|
.buffer = new_buffer_id,
|
||||||
|
.x = 0,
|
||||||
|
.y = 0,
|
||||||
|
} },
|
||||||
|
);
|
||||||
|
|
||||||
|
try conn.send(
|
||||||
|
wayland.core.Surface.Request,
|
||||||
|
surface_id,
|
||||||
|
.{ .damage = .{
|
||||||
|
.x = 0,
|
||||||
|
.y = 0,
|
||||||
|
.width = std.math.maxInt(i32),
|
||||||
|
.height = std.math.maxInt(i32),
|
||||||
|
} },
|
||||||
|
);
|
||||||
|
|
||||||
|
// commit the configuration
|
||||||
|
try conn.send(
|
||||||
|
wayland.core.Surface.Request,
|
||||||
|
surface_id,
|
||||||
|
wayland.core.Surface.Request.commit,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
else => {
|
else => {
|
||||||
std.debug.print("<- wl_keyboard@{}\n", .{event});
|
std.debug.print("<- wl_keyboard@{}\n", .{event});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
} else if (header.object_id == zwp_text_input_v3_id) {
|
||||||
|
const event = try wayland.deserialize(wayland.zwp.TextInputV3.Event, header, body);
|
||||||
|
std.debug.print("<- zwp_text_input_v3@{} event {}\n", .{ zwp_text_input_v3_id, event });
|
||||||
|
switch (event) {
|
||||||
|
.enter => |e| {
|
||||||
|
_ = e;
|
||||||
|
|
||||||
|
// if (e.surface == surface_id) {
|
||||||
|
try conn.send(
|
||||||
|
wayland.zwp.TextInputV3.Request,
|
||||||
|
zwp_text_input_v3_id,
|
||||||
|
.enable,
|
||||||
|
);
|
||||||
|
|
||||||
|
try conn.send(
|
||||||
|
wayland.zwp.TextInputV3.Request,
|
||||||
|
zwp_text_input_v3_id,
|
||||||
|
.{ .set_content_type = .{
|
||||||
|
.hint = .multiline,
|
||||||
|
.purpose = .normal,
|
||||||
|
} },
|
||||||
|
);
|
||||||
|
|
||||||
|
try conn.send(
|
||||||
|
wayland.zwp.TextInputV3.Request,
|
||||||
|
zwp_text_input_v3_id,
|
||||||
|
.commit,
|
||||||
|
);
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
.leave => |e| {
|
||||||
|
_ = e;
|
||||||
|
|
||||||
|
// if (e.surface == surface_id) {
|
||||||
|
try conn.send(
|
||||||
|
wayland.zwp.TextInputV3.Request,
|
||||||
|
zwp_text_input_v3_id,
|
||||||
|
.disable,
|
||||||
|
);
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
.preedit_string => {},
|
||||||
|
.commit_string => |commit| {
|
||||||
|
edit_slice = edit_buffer[0..commit.text.len];
|
||||||
|
@memcpy(edit_slice.?, commit.text);
|
||||||
|
},
|
||||||
|
.delete_surrounding_text => |offset| {
|
||||||
|
delete_before = offset.before_length;
|
||||||
|
delete_after = offset.after_length;
|
||||||
|
},
|
||||||
|
.done => |_| {
|
||||||
|
// 1 replace existing pre-edit string with cursor
|
||||||
|
// 2 delete requested surrounding text
|
||||||
|
const start = cursor_pos - delete_before;
|
||||||
|
const end = cursor_pos + delete_after;
|
||||||
|
const length = end - start;
|
||||||
|
if (length != 0) {
|
||||||
|
try piece_table.delete(start, length);
|
||||||
|
}
|
||||||
|
// 3 insert commit string with cursor at its end
|
||||||
|
if (edit_slice) |slice| {
|
||||||
|
try piece_table.insert(cursor_pos, slice);
|
||||||
|
cursor_pos += slice.len;
|
||||||
|
edit_slice = null;
|
||||||
|
}
|
||||||
|
// 4 calculate surrounding text to send
|
||||||
|
// 5 insert new preedit text in cursor position
|
||||||
|
// 6 place cursor inside predit text
|
||||||
|
},
|
||||||
|
}
|
||||||
} else if (framebuffers.get(header.object_id)) |framebuffer_slice| {
|
} else if (framebuffers.get(header.object_id)) |framebuffer_slice| {
|
||||||
const event = try wayland.deserialize(wayland.core.Buffer.Event, header, body);
|
const event = try wayland.deserialize(wayland.core.Buffer.Event, header, body);
|
||||||
switch (event) {
|
switch (event) {
|
||||||
|
@ -440,6 +641,13 @@ fn cmsg(comptime T: type) type {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn getFramebuffer(framebuffers: *std.AutoHashMap(u32, []Pixel), id_pool: *wayland.IdPool, pool_alloc: std.mem.Allocator, fb_size: [2]u32) !struct { u32, []Pixel } {
|
||||||
|
const new_buffer_id = id_pool.create();
|
||||||
|
const new_framebuffer = try pool_alloc.alloc(Pixel, fb_size[0] * fb_size[1]);
|
||||||
|
try framebuffers.put(new_buffer_id, new_framebuffer);
|
||||||
|
return .{ new_buffer_id, new_framebuffer };
|
||||||
|
}
|
||||||
|
|
||||||
fn renderGradient(framebuffer: []Pixel, fb_size: [2]u32) void {
|
fn renderGradient(framebuffer: []Pixel, fb_size: [2]u32) void {
|
||||||
for (0..fb_size[1]) |y| {
|
for (0..fb_size[1]) |y| {
|
||||||
const row = framebuffer[y * fb_size[0] .. (y + 1) * fb_size[0]];
|
const row = framebuffer[y * fb_size[0] .. (y + 1) * fb_size[0]];
|
||||||
|
@ -472,19 +680,9 @@ fn renderText(framebuffer: []Pixel, fb_size: [2]u32, pos: [2]usize, str: []const
|
||||||
const char = font8x8.font8x8_basic[which_char];
|
const char = font8x8.font8x8_basic[which_char];
|
||||||
const line = char[(y - top) % 8];
|
const line = char[(y - top) % 8];
|
||||||
if ((line >> @intCast((x - left) % 8)) & 0x1 != 0) {
|
if ((line >> @intCast((x - left) % 8)) & 0x1 != 0) {
|
||||||
pixel.* = .{
|
pixel.* = toPixel(Theme.foreground);
|
||||||
0xFF,
|
|
||||||
0xFF,
|
|
||||||
0xFF,
|
|
||||||
0xFF,
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
pixel.* = .{
|
pixel.* = toPixel(Theme.background);
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0xFF,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,449 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
/// Represents buffer and a set of changes to the buffer.
|
||||||
|
///
|
||||||
|
/// All insertions are copied internally. Deletions to the buffer will not free memory.
|
||||||
|
///
|
||||||
|
/// To initialize without text, use struct initialization syntax and specify an allocator
|
||||||
|
/// like so: `var table = PieceTable{ .allocator = allocator };`
|
||||||
|
pub const PieceTable = struct {
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
buffers: std.ArrayListUnmanaged([]u8) = .{},
|
||||||
|
pieces: std.ArrayListUnmanaged(Piece) = .{},
|
||||||
|
|
||||||
|
pub const Piece = struct {
|
||||||
|
slice: []const u8,
|
||||||
|
tag: Tag = .added,
|
||||||
|
pub const Tag = enum {
|
||||||
|
original,
|
||||||
|
added,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, original: []const u8) !PieceTable {
|
||||||
|
if (original.len == 0) {
|
||||||
|
// An empty string was passed, skip making a copy of it
|
||||||
|
return .{
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const original_copy = try allocator.dupe(u8, original);
|
||||||
|
|
||||||
|
// Store original text
|
||||||
|
var buffers = try std.ArrayListUnmanaged([]u8).initCapacity(allocator, 1);
|
||||||
|
buffers.appendAssumeCapacity(original_copy);
|
||||||
|
|
||||||
|
// Create piece pointing to original text
|
||||||
|
var pieces = try std.ArrayListUnmanaged(Piece).initCapacity(allocator, 1);
|
||||||
|
pieces.appendAssumeCapacity(.{
|
||||||
|
.slice = original_copy,
|
||||||
|
.tag = .original,
|
||||||
|
});
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.buffers = buffers,
|
||||||
|
.pieces = pieces,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(table: *PieceTable) void {
|
||||||
|
for (table.buffers.items) |buffer| {
|
||||||
|
table.allocator.free(buffer);
|
||||||
|
}
|
||||||
|
table.buffers.deinit(table.allocator);
|
||||||
|
table.pieces.deinit(table.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts `new_text` into buffer at `index`.
|
||||||
|
///
|
||||||
|
/// `new_text` is owned by caller.
|
||||||
|
///
|
||||||
|
/// It is an error to insert outside of the bounds of the piece table. If the
|
||||||
|
/// table is empty, 0 is the only valid argument for index.
|
||||||
|
pub fn insert(table: *PieceTable, index: usize, new_text: []const u8) !void {
|
||||||
|
const text = try table.allocator.dupe(u8, new_text);
|
||||||
|
try table.buffers.append(table.allocator, text);
|
||||||
|
|
||||||
|
// Insert at the start of the file, catches empty tables
|
||||||
|
if (index == 0) {
|
||||||
|
try table.pieces.insert(table.allocator, 0, .{ .slice = text, .tag = .added });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var p_i: usize = 0;
|
||||||
|
var b_i: usize = 0;
|
||||||
|
while (p_i < table.pieces.items.len) : (p_i += 1) {
|
||||||
|
const p = table.pieces.items[p_i];
|
||||||
|
if (index == b_i + p.slice.len) {
|
||||||
|
if (p_i + 1 == table.pieces.items.len) {
|
||||||
|
// The new index is the end of the file
|
||||||
|
try table.pieces.append(table.allocator, .{ .slice = text, .tag = .added });
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// The new index is directly after an existing node, but not at the end of the file.
|
||||||
|
try table.pieces.insert(table.allocator, p_i + 1, .{ .slice = text, .tag = .added });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (index < b_i + p.slice.len) {
|
||||||
|
// new piece is within another piece; split the old one into 2
|
||||||
|
// and insert the new piece between
|
||||||
|
|
||||||
|
// ignore the returned slice since we will also want the
|
||||||
|
// piece right before the insertion
|
||||||
|
_ = try table.pieces.addManyAt(table.allocator, p_i + 1, 2);
|
||||||
|
const pieces = table.pieces.items[p_i..][0..3];
|
||||||
|
|
||||||
|
const sub_i = index - b_i;
|
||||||
|
|
||||||
|
pieces[0].slice = p.slice[0..sub_i];
|
||||||
|
pieces[1].slice = text;
|
||||||
|
pieces[2].slice = p.slice[sub_i..];
|
||||||
|
|
||||||
|
// set the tag for the pieces 1 and 2
|
||||||
|
// elide setting the tag for pieces[0], it should be correct already
|
||||||
|
pieces[1].tag = .added;
|
||||||
|
switch (p.tag) {
|
||||||
|
.original => pieces[2].tag = .original,
|
||||||
|
.added => pieces[2].tag = .added,
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
b_i += p.slice.len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@panic("Impossible state while inserting into PieceTable");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes the data from start to start+length from the piece table.
|
||||||
|
/// Will not free any memory.
|
||||||
|
pub fn delete(table: *PieceTable, start: usize, length: usize) !void {
|
||||||
|
if (length == 0) return error.InvalidLength;
|
||||||
|
const endi = start + length;
|
||||||
|
|
||||||
|
var b_i: usize = 0; // buffer index
|
||||||
|
const p_start, const start_subi, const start_piece = for (table.pieces.items, 0..) |piece, i| {
|
||||||
|
if (start < b_i + piece.slice.len) {
|
||||||
|
// start found
|
||||||
|
break .{ i, start - b_i, piece };
|
||||||
|
}
|
||||||
|
b_i += piece.slice.len;
|
||||||
|
} else return error.OutOfBounds;
|
||||||
|
|
||||||
|
// reuse b_i
|
||||||
|
const p_end, const end_subi, const end_piece = for (table.pieces.items[p_start..], p_start..) |piece, i| {
|
||||||
|
if (endi < b_i + piece.slice.len) {
|
||||||
|
break .{ i, endi - b_i, piece };
|
||||||
|
}
|
||||||
|
b_i += piece.slice.len;
|
||||||
|
} else .{ p_start, start_piece.slice.len, start_piece };
|
||||||
|
|
||||||
|
// Removal cases:
|
||||||
|
// 1. the deletion starts on one piece boundary and ends on another piece boundary
|
||||||
|
// - Delete all pieces between start and end
|
||||||
|
// 2. the deletion starts within a piece and ends on a boundary
|
||||||
|
// - Delete all but the start piece
|
||||||
|
// - modify slice end in start piece
|
||||||
|
// 3. the deletion starts on a bondary and ends within a piece
|
||||||
|
// - Delete all but the end piece
|
||||||
|
// - modify slice start in end piece
|
||||||
|
// 4. the deletion starts within a piece and ends within a piece
|
||||||
|
// - Delet all the start and end pieces
|
||||||
|
// - modify slice end in start piece
|
||||||
|
// - modify slice end in end piece
|
||||||
|
|
||||||
|
const is_start_on_boundary = start_subi == 0;
|
||||||
|
const is_end_on_boundary = end_subi == end_piece.slice.len;
|
||||||
|
|
||||||
|
const remove_len = (p_end + 1) - p_start;
|
||||||
|
if (is_start_on_boundary and is_end_on_boundary) {
|
||||||
|
table.pieces.replaceRange(table.allocator, p_start, remove_len, &.{}) catch unreachable;
|
||||||
|
} else {
|
||||||
|
if (is_start_on_boundary) {
|
||||||
|
const new = &[_]PieceTable.Piece{
|
||||||
|
.{ .slice = end_piece.slice[end_subi..], .tag = end_piece.tag },
|
||||||
|
};
|
||||||
|
table.pieces.replaceRange(table.allocator, p_start, remove_len, new) catch unreachable;
|
||||||
|
} else if (is_end_on_boundary) {
|
||||||
|
const new = &[_]PieceTable.Piece{
|
||||||
|
.{ .slice = start_piece.slice[0..start_subi], .tag = start_piece.tag },
|
||||||
|
};
|
||||||
|
table.pieces.replaceRange(table.allocator, p_start, remove_len, new) catch unreachable;
|
||||||
|
} else {
|
||||||
|
const new = &[_]PieceTable.Piece{
|
||||||
|
.{ .slice = start_piece.slice[0..start_subi], .tag = start_piece.tag },
|
||||||
|
.{ .slice = end_piece.slice[end_subi..], .tag = end_piece.tag },
|
||||||
|
};
|
||||||
|
table.pieces.replaceRange(table.allocator, p_start, remove_len, new) catch unreachable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getTotalSize(table: PieceTable) usize {
|
||||||
|
var length: usize = 0;
|
||||||
|
for (table.pieces.items) |piece| {
|
||||||
|
length += piece.slice.len;
|
||||||
|
}
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeAll(table: PieceTable, buffer: []u8) void {
|
||||||
|
std.debug.assert(table.getTotalSize() == buffer.len);
|
||||||
|
var current_buffer = buffer[0..];
|
||||||
|
for (table.pieces.items) |piece| {
|
||||||
|
@memcpy(current_buffer[0..piece.slice.len], piece.slice);
|
||||||
|
current_buffer = current_buffer[piece.slice.len..];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeAllAlloc(table: PieceTable) ![]u8 {
|
||||||
|
const size = table.getTotalSize();
|
||||||
|
const buffer = try table.allocator.alloc(u8, size);
|
||||||
|
var current_buffer = buffer[0..];
|
||||||
|
for (table.pieces.items) |piece| {
|
||||||
|
@memcpy(current_buffer[0..piece.slice.len], piece.slice);
|
||||||
|
current_buffer = current_buffer[piece.slice.len..];
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test "Init empty PieceTable" {
|
||||||
|
var table = try PieceTable.init(testing.allocator, "");
|
||||||
|
defer table.deinit();
|
||||||
|
|
||||||
|
var out_buf: [0]u8 = undefined;
|
||||||
|
table.writeAll(&out_buf);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings("", &out_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Insert into empty PieceTable" {
|
||||||
|
var table = try PieceTable.init(testing.allocator, "");
|
||||||
|
defer table.deinit();
|
||||||
|
|
||||||
|
try table.insert(0, "the quick brown fox\njumped over the lazy dog");
|
||||||
|
|
||||||
|
var out_buf: [44]u8 = undefined;
|
||||||
|
table.writeAll(&out_buf);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings(
|
||||||
|
\\the quick brown fox
|
||||||
|
\\jumped over the lazy dog
|
||||||
|
, &out_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Init Piecetable" {
|
||||||
|
const original = "the quick brown fox\njumped over the lazy dog";
|
||||||
|
var table = try PieceTable.init(testing.allocator, original);
|
||||||
|
defer table.deinit();
|
||||||
|
|
||||||
|
var out_buf: [44]u8 = undefined;
|
||||||
|
table.writeAll(&out_buf);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings(
|
||||||
|
\\the quick brown fox
|
||||||
|
\\jumped over the lazy dog
|
||||||
|
, &out_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Insert into PieceTable" {
|
||||||
|
const original = "the quick brown fox\njumped over the lazy dog";
|
||||||
|
var table = try PieceTable.init(testing.allocator, original);
|
||||||
|
defer table.deinit();
|
||||||
|
|
||||||
|
try table.insert(20, "went to the park and\n");
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(usize, 3), table.pieces.items.len);
|
||||||
|
try testing.expectEqual(PieceTable.Piece.Tag.original, table.pieces.items[0].tag);
|
||||||
|
try testing.expectEqual(PieceTable.Piece.Tag.added, table.pieces.items[1].tag);
|
||||||
|
try testing.expectEqual(PieceTable.Piece.Tag.original, table.pieces.items[2].tag);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings("the quick brown fox\n", table.pieces.items[0].slice);
|
||||||
|
try testing.expectEqualStrings("went to the park and\n", table.pieces.items[1].slice);
|
||||||
|
try testing.expectEqualStrings("jumped over the lazy dog", table.pieces.items[2].slice);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(usize, 65), table.getTotalSize());
|
||||||
|
|
||||||
|
var out_buf: [65]u8 = undefined;
|
||||||
|
table.writeAll(&out_buf);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings(
|
||||||
|
\\the quick brown fox
|
||||||
|
\\went to the park and
|
||||||
|
\\jumped over the lazy dog
|
||||||
|
, &out_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Insert at end of Piece" {
|
||||||
|
const original = "the quick brown fox\njumped over the lazy dog";
|
||||||
|
var table = try PieceTable.init(testing.allocator, original);
|
||||||
|
defer table.deinit();
|
||||||
|
|
||||||
|
try table.insert(20, "went to the park and\n");
|
||||||
|
try table.insert(41, "ate a burger and\n");
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(usize, 4), table.pieces.items.len);
|
||||||
|
try testing.expectEqual(PieceTable.Piece.Tag.original, table.pieces.items[0].tag);
|
||||||
|
try testing.expectEqual(PieceTable.Piece.Tag.added, table.pieces.items[1].tag);
|
||||||
|
try testing.expectEqual(PieceTable.Piece.Tag.added, table.pieces.items[2].tag);
|
||||||
|
try testing.expectEqual(PieceTable.Piece.Tag.original, table.pieces.items[3].tag);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings("the quick brown fox\n", table.pieces.items[0].slice);
|
||||||
|
try testing.expectEqualStrings("went to the park and\n", table.pieces.items[1].slice);
|
||||||
|
try testing.expectEqualStrings("ate a burger and\n", table.pieces.items[2].slice);
|
||||||
|
try testing.expectEqualStrings("jumped over the lazy dog", table.pieces.items[3].slice);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(usize, 82), table.getTotalSize());
|
||||||
|
|
||||||
|
var out_buf: [82]u8 = undefined;
|
||||||
|
table.writeAll(&out_buf);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings(
|
||||||
|
\\the quick brown fox
|
||||||
|
\\went to the park and
|
||||||
|
\\ate a burger and
|
||||||
|
\\jumped over the lazy dog
|
||||||
|
, &out_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Insert at end of file" {
|
||||||
|
const original = "the quick brown fox";
|
||||||
|
var table = try PieceTable.init(testing.allocator, original);
|
||||||
|
defer table.deinit();
|
||||||
|
|
||||||
|
try table.insert(19, "\njumped over the lazy dog");
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(usize, 2), table.pieces.items.len);
|
||||||
|
try testing.expectEqual(PieceTable.Piece.Tag.original, table.pieces.items[0].tag);
|
||||||
|
try testing.expectEqual(PieceTable.Piece.Tag.added, table.pieces.items[1].tag);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings("the quick brown fox", table.pieces.items[0].slice);
|
||||||
|
try testing.expectEqualStrings("\njumped over the lazy dog", table.pieces.items[1].slice);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(usize, 44), table.getTotalSize());
|
||||||
|
|
||||||
|
var out_buf: [44]u8 = undefined;
|
||||||
|
table.writeAll(&out_buf);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings(
|
||||||
|
\\the quick brown fox
|
||||||
|
\\jumped over the lazy dog
|
||||||
|
, &out_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Delete one entire Piece" {
|
||||||
|
const original = "the quick brown fox";
|
||||||
|
var table = try PieceTable.init(testing.allocator, original);
|
||||||
|
defer table.deinit();
|
||||||
|
|
||||||
|
try table.delete(0, 19);
|
||||||
|
try testing.expectEqual(@as(usize, 0), table.pieces.items.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Delete multiple entire Pieces" {
|
||||||
|
const original = "the quick brown fox\njumped over the lazy dog";
|
||||||
|
var table = try PieceTable.init(testing.allocator, original);
|
||||||
|
defer table.deinit();
|
||||||
|
|
||||||
|
try table.insert(20, "went to the park and\n");
|
||||||
|
try table.insert(41, "ate a burger and\n");
|
||||||
|
try table.delete(20, 38);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(usize, 2), table.pieces.items.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Delete inside a Piece" {
|
||||||
|
const original = "the quick brown fox";
|
||||||
|
var table = try PieceTable.init(testing.allocator, original);
|
||||||
|
defer table.deinit();
|
||||||
|
|
||||||
|
// delete "brown "
|
||||||
|
try table.delete(10, 6);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(usize, 2), table.pieces.items.len);
|
||||||
|
try testing.expectEqualStrings("the quick ", table.pieces.items[0].slice);
|
||||||
|
try testing.expectEqualStrings("fox", table.pieces.items[1].slice);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(usize, 13), table.getTotalSize());
|
||||||
|
|
||||||
|
var out_buf: [13]u8 = undefined;
|
||||||
|
table.writeAll(&out_buf);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings(
|
||||||
|
\\the quick fox
|
||||||
|
, &out_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Delete from within one piece to within another piece" {
|
||||||
|
const original = "the quick brown fox\njumped over the lazy dog";
|
||||||
|
var table = try PieceTable.init(testing.allocator, original);
|
||||||
|
defer table.deinit();
|
||||||
|
|
||||||
|
try table.insert(20, "went to the park and\n");
|
||||||
|
try table.insert(41, "ate a burger and\n");
|
||||||
|
try table.delete(45, 13 + 12);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(usize, 4), table.pieces.items.len);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(usize, 57), table.getTotalSize());
|
||||||
|
|
||||||
|
var out_buf: [57]u8 = undefined;
|
||||||
|
table.writeAll(&out_buf);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings(
|
||||||
|
\\the quick brown fox
|
||||||
|
\\went to the park and
|
||||||
|
\\ate the lazy dog
|
||||||
|
, &out_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Delete from start of piece to within piece" {
|
||||||
|
const original = "the quick brown fox\njumped over the lazy dog";
|
||||||
|
var table = try PieceTable.init(testing.allocator, original);
|
||||||
|
defer table.deinit();
|
||||||
|
|
||||||
|
try table.insert(20, "went to the park and\n");
|
||||||
|
try table.insert(41, "ate a burger and\n");
|
||||||
|
try table.delete(41, 13);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(usize, 4), table.pieces.items.len);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(usize, 69), table.getTotalSize());
|
||||||
|
|
||||||
|
var out_buf: [69]u8 = undefined;
|
||||||
|
table.writeAll(&out_buf);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings(
|
||||||
|
\\the quick brown fox
|
||||||
|
\\went to the park and
|
||||||
|
\\and
|
||||||
|
\\jumped over the lazy dog
|
||||||
|
, &out_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Delete from within piece to end of piece" {
|
||||||
|
const original = "the quick brown fox\njumped over the lazy dog";
|
||||||
|
var table = try PieceTable.init(testing.allocator, original);
|
||||||
|
defer table.deinit();
|
||||||
|
|
||||||
|
try table.insert(20, "went to the park and\n");
|
||||||
|
try table.insert(41, "ate a burger and\n");
|
||||||
|
try table.delete(45, 13);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(usize, 4), table.pieces.items.len);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(usize, 69), table.getTotalSize());
|
||||||
|
|
||||||
|
var out_buf: [69]u8 = undefined;
|
||||||
|
table.writeAll(&out_buf);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings(
|
||||||
|
\\the quick brown fox
|
||||||
|
\\went to the park and
|
||||||
|
\\ate jumped over the lazy dog
|
||||||
|
, &out_buf);
|
||||||
|
}
|
37
src/main.zig
37
src/main.zig
|
@ -3,6 +3,7 @@ const testing = std.testing;
|
||||||
pub const core = @import("./core.zig");
|
pub const core = @import("./core.zig");
|
||||||
pub const xdg = @import("./xdg.zig");
|
pub const xdg = @import("./xdg.zig");
|
||||||
pub const zxdg = @import("./zxdg.zig");
|
pub const zxdg = @import("./zxdg.zig");
|
||||||
|
pub const zwp = @import("./zwp.zig");
|
||||||
pub const types = @import("./types.zig");
|
pub const types = @import("./types.zig");
|
||||||
|
|
||||||
pub fn getDisplayPath(gpa: std.mem.Allocator) ![]u8 {
|
pub fn getDisplayPath(gpa: std.mem.Allocator) ![]u8 {
|
||||||
|
@ -82,10 +83,11 @@ pub fn readInt(buffer: []const u32, parent_pos: *usize) !i32 {
|
||||||
return int;
|
return int;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn readString(buffer: []const u32, parent_pos: *usize) ![:0]const u8 {
|
pub fn readString(buffer: []const u32, parent_pos: *usize) !?[:0]const u8 {
|
||||||
var pos = parent_pos.*;
|
var pos = parent_pos.*;
|
||||||
|
|
||||||
const len = try readUInt(buffer, &pos);
|
const len = try readUInt(buffer, &pos);
|
||||||
|
if (len == 0) return null;
|
||||||
const wordlen = std.mem.alignForward(usize, len, @sizeOf(u32)) / @sizeOf(u32);
|
const wordlen = std.mem.alignForward(usize, len, @sizeOf(u32)) / @sizeOf(u32);
|
||||||
|
|
||||||
if (pos + wordlen > buffer.len) return error.EndOfStream;
|
if (pos + wordlen > buffer.len) return error.EndOfStream;
|
||||||
|
@ -126,12 +128,21 @@ pub fn deserializeArguments(comptime Signature: type, buffer: []const u32) !Sign
|
||||||
},
|
},
|
||||||
.Pointer => |ptr| switch (ptr.size) {
|
.Pointer => |ptr| switch (ptr.size) {
|
||||||
.Slice => if (ptr.child == u8) {
|
.Slice => if (ptr.child == u8) {
|
||||||
@field(result, field.name) = try readString(buffer, &pos);
|
@field(result, field.name) = try readString(buffer, &pos) orelse return error.UnexpectedNullString;
|
||||||
} else {
|
} else {
|
||||||
@field(result, field.name) = try readArray(ptr.child, buffer, &pos);
|
@field(result, field.name) = try readArray(ptr.child, buffer, &pos);
|
||||||
},
|
},
|
||||||
else => @compileError("Unsupported type " ++ @typeName(field.type)),
|
else => @compileError("Unsupported type " ++ @typeName(field.type)),
|
||||||
},
|
},
|
||||||
|
.Optional => |opt| switch (@typeInfo(opt.child)) {
|
||||||
|
.Pointer => |ptr| switch (ptr.size) {
|
||||||
|
.Slice => if (ptr.child == u8) {
|
||||||
|
@field(result, field.name) = try readString(buffer, &pos);
|
||||||
|
} else @compileError("Unsupported type " ++ @typeName(field.type)),
|
||||||
|
else => @compileError("Unsupported type " ++ @typeName(field.type)),
|
||||||
|
},
|
||||||
|
else => @compileError("Unsupported type " ++ @typeName(field.type)),
|
||||||
|
},
|
||||||
else => @compileError("Unsupported type " ++ @typeName(field.type)),
|
else => @compileError("Unsupported type " ++ @typeName(field.type)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -232,6 +243,27 @@ pub fn serializeArguments(comptime Signature: type, buffer: []u32, message: Sign
|
||||||
},
|
},
|
||||||
else => @compileError("Unsupported type " ++ @typeName(field.type)),
|
else => @compileError("Unsupported type " ++ @typeName(field.type)),
|
||||||
},
|
},
|
||||||
|
.Optional => |opt| switch (@typeInfo(opt.child)) {
|
||||||
|
.Pointer => |ptr| switch (ptr.size) {
|
||||||
|
.Slice => if (ptr.child == u8) {
|
||||||
|
const str = @field(message, field.name);
|
||||||
|
if (str.len >= std.math.maxInt(u32)) return error.StringTooLong;
|
||||||
|
|
||||||
|
buffer[pos] = @intCast(str.len + 1);
|
||||||
|
pos += 1;
|
||||||
|
|
||||||
|
const str_len_aligned = std.mem.alignForward(usize, str.len + 1, @sizeOf(u32));
|
||||||
|
const padding_len = str_len_aligned - str.len;
|
||||||
|
if (str_len_aligned / @sizeOf(u32) >= buffer[pos..].len) return error.OutOfMemory;
|
||||||
|
const buffer_bytes = std.mem.sliceAsBytes(buffer[pos..]);
|
||||||
|
@memcpy(buffer_bytes[0..str.len], str);
|
||||||
|
@memset(buffer_bytes[str.len..][0..padding_len], 0);
|
||||||
|
pos += str_len_aligned / @sizeOf(u32);
|
||||||
|
} else @compileError("Unsupported type " ++ @typeName(field.type)),
|
||||||
|
else => @compileError("Unsupported type " ++ @typeName(field.type)),
|
||||||
|
},
|
||||||
|
else => @compileError("Unsupported type " ++ @typeName(field.type)),
|
||||||
|
},
|
||||||
else => @compileError("Unsupported type " ++ @typeName(field.type)),
|
else => @compileError("Unsupported type " ++ @typeName(field.type)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -496,6 +528,7 @@ pub const Conn = struct {
|
||||||
conn.send_buffer = try conn.allocator.realloc(conn.send_buffer, conn.send_buffer.len * 2);
|
conn.send_buffer = try conn.allocator.realloc(conn.send_buffer, conn.send_buffer.len * 2);
|
||||||
continue;
|
continue;
|
||||||
},
|
},
|
||||||
|
else => return e,
|
||||||
};
|
};
|
||||||
|
|
||||||
break msg;
|
break msg;
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
pub const TextInputManagerV3 = struct {
|
||||||
|
pub const INTERFACE = "zwp_text_input_manager_v3";
|
||||||
|
pub const VERSION = 1;
|
||||||
|
|
||||||
|
pub const Request = union(Request.Tag) {
|
||||||
|
destroy: void,
|
||||||
|
get_text_input: struct {
|
||||||
|
id: u32,
|
||||||
|
seat: u32,
|
||||||
|
},
|
||||||
|
pub const Tag = enum(u16) {
|
||||||
|
destroy,
|
||||||
|
get_text_input,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const TextInputV3 = struct {
|
||||||
|
pub const Request = union(Tag) {
|
||||||
|
destroy,
|
||||||
|
enable,
|
||||||
|
disable,
|
||||||
|
set_surrounding_text: struct {
|
||||||
|
text: []const u8,
|
||||||
|
cursor: i32,
|
||||||
|
anchor: i32,
|
||||||
|
},
|
||||||
|
set_text_change_cause: struct {
|
||||||
|
cause: ChangeCause,
|
||||||
|
},
|
||||||
|
set_content_type: struct {
|
||||||
|
hint: ContentHint,
|
||||||
|
purpose: ContentPurpose,
|
||||||
|
},
|
||||||
|
set_cursor_rectangle: struct {
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
},
|
||||||
|
commit,
|
||||||
|
pub const Tag = enum(u16) {
|
||||||
|
destroy,
|
||||||
|
enable,
|
||||||
|
disable,
|
||||||
|
set_surrounding_text,
|
||||||
|
set_text_change_cause,
|
||||||
|
set_content_type,
|
||||||
|
set_cursor_rectangle,
|
||||||
|
commit,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Event = union(Tag) {
|
||||||
|
enter: struct { surface: u32 },
|
||||||
|
leave: struct { surface: u32 },
|
||||||
|
preedit_string: struct {
|
||||||
|
text: ?[]const u8,
|
||||||
|
cursor_begin: i32,
|
||||||
|
cursor_end: i32,
|
||||||
|
},
|
||||||
|
commit_string: struct {
|
||||||
|
text: []const u8,
|
||||||
|
},
|
||||||
|
delete_surrounding_text: struct {
|
||||||
|
before_length: usize,
|
||||||
|
after_length: usize,
|
||||||
|
},
|
||||||
|
done: struct {
|
||||||
|
serial: u32,
|
||||||
|
},
|
||||||
|
pub const Tag = enum {
|
||||||
|
enter,
|
||||||
|
leave,
|
||||||
|
preedit_string,
|
||||||
|
commit_string,
|
||||||
|
delete_surrounding_text,
|
||||||
|
done,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ChangeCause = enum(u32) {
|
||||||
|
input_method,
|
||||||
|
other,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ContentHint = enum(u32) {
|
||||||
|
none,
|
||||||
|
completion,
|
||||||
|
spellcheck,
|
||||||
|
auto_capitalization,
|
||||||
|
lowercase,
|
||||||
|
uppercase,
|
||||||
|
titlecase,
|
||||||
|
hidden_text,
|
||||||
|
sensitive_data,
|
||||||
|
latin,
|
||||||
|
multiline,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ContentPurpose = enum(u32) {
|
||||||
|
normal,
|
||||||
|
alpha,
|
||||||
|
digits,
|
||||||
|
number,
|
||||||
|
phone,
|
||||||
|
url,
|
||||||
|
email,
|
||||||
|
name,
|
||||||
|
password,
|
||||||
|
pin,
|
||||||
|
date,
|
||||||
|
time,
|
||||||
|
datetime,
|
||||||
|
terminal,
|
||||||
|
};
|
||||||
|
};
|
Loading…
Reference in New Issue